PageRenderTime 324ms CodeModel.GetById 193ms app.highlight 119ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/inets/src/ftp/ftp.erl

https://github.com/bsmr-erlang/otp
Erlang | 2278 lines | 1454 code | 340 blank | 484 comment | 7 complexity | 3120cf89c88e6c05d7fe8e264c5f52ad MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1%%
   2%% %CopyrightBegin%
   3%%
   4%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
   5%%
   6%% The contents of this file are subject to the Erlang Public License,
   7%% Version 1.1, (the "License"); you may not use this file except in
   8%% compliance with the License. You should have received a copy of the
   9%% Erlang Public License along with this software. If not, it can be
  10%% retrieved online at http://www.erlang.org/.
  11%%
  12%% Software distributed under the License is distributed on an "AS IS"
  13%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  14%% the License for the specific language governing rights and limitations
  15%% under the License.
  16%%
  17%% %CopyrightEnd%
  18%%
  19%%
  20%% Description: This module implements an ftp client, RFC 959. 
  21%% It also supports ipv6 RFC 2428.
  22
  23-module(ftp).
  24
  25-behaviour(gen_server).
  26-behaviour(inets_service).
  27
  28
  29%%  API - Client interface
  30-export([cd/2, close/1, delete/2, formaterror/1, 
  31	 lcd/2, lpwd/1, ls/1, ls/2, 
  32	 mkdir/2, nlist/1, nlist/2, 
  33	 open/1, open/2, 
  34	 pwd/1, quote/2,
  35	 recv/2, recv/3, recv_bin/2, 
  36	 recv_chunk_start/2, recv_chunk/1, 
  37	 rename/3, rmdir/2, 
  38	 send/2, send/3, send_bin/3, 
  39	 send_chunk_start/2, send_chunk/2, send_chunk_end/1, 
  40	 type/2, user/3, user/4, account/2,
  41	 append/3, append/2, append_bin/3,
  42	 append_chunk/2, append_chunk_end/1, append_chunk_start/2, info/1]).
  43
  44%% gen_server callbacks
  45-export([init/1, handle_call/3, handle_cast/2, 
  46	 handle_info/2, terminate/2, code_change/3]).
  47
  48%% supervisor callbacks
  49-export([start_link/1, start_link/2]).
  50
  51%% Behavior callbacks
  52-export([start_standalone/1, start_service/1, 
  53	 stop_service/1, services/0, service_info/1]).
  54
  55-include("ftp_internal.hrl").
  56
  57%% Constante used in internal state definition
  58-define(CONNECTION_TIMEOUT,  60*1000).
  59-define(DATA_ACCEPT_TIMEOUT, infinity).
  60-define(DEFAULT_MODE,        passive).
  61-define(PROGRESS_DEFAULT,    ignore).
  62
  63%% Internal Constants
  64-define(FTP_PORT, 21).
  65-define(FILE_BUFSIZE, 4096).
  66
  67%% Internal state
  68-record(state, {
  69	  csock   = undefined, % socket() - Control connection socket 
  70	  dsock   = undefined, % socket() - Data connection socket 
  71	  verbose = false,   % boolean() 
  72	  ldir    = undefined,  % string() - Current local directory
  73	  type    = ftp_server_default,  % atom() - binary | ascii 
  74	  chunk   = false,     % boolean() - Receiving data chunks 
  75	  mode    = ?DEFAULT_MODE,    % passive | active
  76	  timeout = ?CONNECTION_TIMEOUT, % integer()
  77	  %% Data received so far on the data connection
  78	  data    = <<>>,   % binary()
  79	  %% Data received so far on the control connection
  80	  %% {BinStream, AccLines}. If a binary sequence
  81	  %% ends with ?CR then keep it in the binary to
  82	  %% be able to detect if the next received byte is ?LF
  83	  %% and hence the end of the response is reached!
  84	  ctrl_data = {<<>>, [], start},  % {binary(), [bytes()], LineStatus}
  85	  %% pid() - Client pid (note not the same as "From")
  86	  owner = undefined,   
  87	  client = undefined,  % "From" to be used in gen_server:reply/2
  88	  %% Function that activated a connection and maybe some
  89	  %% data needed further on.
  90	  caller = undefined, % term()     
  91	  ipfamily,     % inet | inet6 | inet6fb4
  92	  progress = ignore,   % ignore | pid()	    
  93	  dtimeout = ?DATA_ACCEPT_TIMEOUT  % non_neg_integer() | infinity
  94	 }).
  95
  96
  97-type shortage_reason()  :: 'etnospc' | 'epnospc'.
  98-type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'.
  99-type common_reason() ::  'econn' | 'eclosed' | term().
 100-type file_write_error_reason() :: term(). % See file:write for more info
 101
 102
 103%%%=========================================================================
 104%%%  API - CLIENT FUNCTIONS
 105%%%=========================================================================
 106
 107%%--------------------------------------------------------------------------
 108%% open(HostOrOtpList, <Port>, <Flags>) -> {ok, Pid} | {error, ehost}
 109%%	HostOrOtpList = string() | [{option_list, Options}] 
 110%%      Port = integer(), 
 111%%      Flags = [Flag], 
 112%%      Flag = verbose | debug | trace
 113%%
 114%% Description:  Start an ftp client and connect to a host.
 115%%--------------------------------------------------------------------------
 116
 117-spec open(Host :: string() | inet:ip_address()) ->
 118    {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
 119
 120%% <BACKWARD-COMPATIBILLITY>
 121open({option_list, Options}) when is_list(Options) ->
 122    try
 123	{ok, StartOptions} = start_options(Options),
 124	{ok, OpenOptions}  = open_options(Options), 
 125	case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
 126	    {ok, Pid} ->
 127		call(Pid, {open, ip_comm, OpenOptions}, plain);
 128	    Error1 ->
 129		Error1
 130	end
 131    catch 
 132	throw:Error2 ->
 133	    Error2
 134    end;
 135%% </BACKWARD-COMPATIBILLITY>
 136
 137open(Host) ->
 138    open(Host, []).
 139
 140-spec open(Host :: string() | inet:ip_address(), Opts :: list()) ->
 141    {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
 142
 143%% <BACKWARD-COMPATIBILLITY>
 144open(Host, Port) when is_integer(Port) ->
 145    open(Host, [{port, Port}]);
 146%% </BACKWARD-COMPATIBILLITY>
 147
 148open(Host, Opts) when is_list(Opts) ->
 149    ?fcrt("open", [{host, Host}, {opts, Opts}]), 
 150    try
 151	{ok, StartOptions} = start_options(Opts), 
 152	?fcrt("open", [{start_options, StartOptions}]), 
 153	{ok, OpenOptions}  = open_options([{host, Host}|Opts]), 
 154	?fcrt("open", [{open_options, OpenOptions}]), 
 155	case start_link(StartOptions, []) of
 156	    {ok, Pid} ->
 157		?fcrt("open - ok", [{pid, Pid}]), 
 158		call(Pid, {open, ip_comm, OpenOptions}, plain);
 159	    Error1 ->
 160		?fcrt("open - error", [{error1, Error1}]), 
 161		Error1
 162	end
 163    catch
 164	throw:Error2 ->
 165	    ?fcrt("open - error", [{error2, Error2}]), 
 166	    Error2
 167    end.
 168
 169
 170%%--------------------------------------------------------------------------
 171%% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn} 
 172%%                                    | {error, eacct}
 173%%	Pid = pid(), 
 174%%      User = Pass =  Acc = string()
 175%%
 176%% Description:  Login with or without a supplied account name.
 177%%--------------------------------------------------------------------------
 178-spec user(Pid  :: pid(), 
 179	   User :: string(), 
 180	   Pass :: string()) ->
 181    'ok' | {'error', Reason :: 'euser' | common_reason()}.
 182
 183user(Pid, User, Pass) ->
 184    call(Pid, {user, User, Pass}, atom).
 185
 186-spec user(Pid  :: pid(), 
 187	   User :: string(), 
 188	   Pass :: string(), 
 189	   Acc  :: string()) ->
 190    'ok' | {'error', Reason :: 'euser' | common_reason()}.
 191
 192user(Pid, User, Pass, Acc) ->
 193    call(Pid, {user, User, Pass, Acc}, atom).
 194
 195
 196%%--------------------------------------------------------------------------
 197%% account(Pid, Acc)  -> ok | {error, eacct}
 198%%	Pid = pid()
 199%%	Acc= string()
 200%%
 201%% Description:  Set a user Account.
 202%%--------------------------------------------------------------------------
 203
 204-spec account(Pid :: pid(), Acc :: string()) ->
 205    'ok' | {'error', Reason :: 'eacct' | common_reason()}.
 206
 207account(Pid, Acc) ->
 208    call(Pid, {account, Acc}, atom).
 209
 210
 211%%--------------------------------------------------------------------------
 212%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn} 
 213%%	Pid = pid()
 214%%      Dir = string()
 215%%
 216%% Description:  Get the current working directory at remote server.
 217%%--------------------------------------------------------------------------
 218
 219-spec pwd(Pid :: pid()) ->
 220    {'ok', Dir :: string()} | 
 221	{'error', Reason :: restriction_reason() | common_reason()}.
 222
 223pwd(Pid) ->
 224    call(Pid, pwd, ctrl).
 225
 226
 227%%--------------------------------------------------------------------------
 228%% lpwd(Pid) ->  {ok, Dir} 
 229%%	Pid = pid()
 230%%      Dir = string()
 231%%
 232%% Description:  Get the current working directory at local server.
 233%%--------------------------------------------------------------------------
 234
 235-spec lpwd(Pid :: pid()) ->
 236    {'ok', Dir :: string()}.
 237
 238lpwd(Pid) ->
 239    call(Pid, lpwd, string).
 240
 241
 242%%--------------------------------------------------------------------------
 243%% cd(Pid, Dir) ->  ok | {error, epath} | {error, elogin} | {error, econn}
 244%%	Pid = pid()
 245%%	Dir = string()
 246%%
 247%% Description:  Change current working directory at remote server.
 248%%--------------------------------------------------------------------------
 249
 250-spec cd(Pid :: pid(), Dir :: string()) ->
 251    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
 252
 253cd(Pid, Dir) ->
 254    call(Pid, {cd, Dir}, atom).
 255
 256
 257%%--------------------------------------------------------------------------
 258%% lcd(Pid, Dir) ->  ok | {error, epath}
 259%%	Pid = pid()
 260%%	Dir = string()
 261%%
 262%% Description:  Change current working directory for the local client.
 263%%--------------------------------------------------------------------------
 264
 265-spec lcd(Pid :: pid(), Dir :: string()) ->
 266    'ok' | {'error', Reason :: restriction_reason()}.
 267
 268lcd(Pid, Dir) ->
 269    call(Pid, {lcd, Dir}, string).
 270
 271
 272%%--------------------------------------------------------------------------
 273%% ls(Pid) -> Result
 274%% ls(Pid, <Dir>) -> Result
 275%% 
 276%%	Pid = pid()
 277%%	Dir = string()
 278%%      Result = {ok, Listing} | {error, Reason}
 279%%      Listing = string()
 280%%      Reason = epath | elogin | econn
 281%%
 282%% Description: Returns a list of files in long format.
 283%%--------------------------------------------------------------------------
 284
 285-spec ls(Pid :: pid()) ->
 286    {'ok', Listing :: string()} | 
 287	{'error', Reason :: restriction_reason() | common_reason()}.
 288
 289ls(Pid) ->
 290  ls(Pid, "").
 291
 292-spec ls(Pid :: pid(), Dir :: string()) ->
 293    {'ok', Listing :: string()} | 
 294	{'error', Reason ::  restriction_reason() | common_reason()}.
 295
 296ls(Pid, Dir) ->
 297    call(Pid, {dir, long, Dir}, string).
 298
 299
 300%%--------------------------------------------------------------------------
 301%% nlist(Pid) -> Result
 302%% nlist(Pid, Pathname) -> Result
 303%% 
 304%%	Pid = pid()
 305%%	Pathname = string()
 306%%      Result = {ok, Listing} | {error, Reason}
 307%%      Listing = string()
 308%%      Reason = epath | elogin | econn
 309%%
 310%% Description:  Returns a list of files in short format
 311%%--------------------------------------------------------------------------
 312
 313-spec nlist(Pid :: pid()) ->
 314    {'ok', Listing :: string()} | 
 315	{'error', Reason :: restriction_reason() | common_reason()}.
 316
 317nlist(Pid) ->
 318  nlist(Pid, "").
 319
 320-spec nlist(Pid :: pid(), Pathname :: string()) ->
 321    {'ok', Listing :: string()} | 
 322	{'error', Reason :: restriction_reason() | common_reason()}.
 323
 324nlist(Pid, Dir) ->
 325    call(Pid, {dir, short, Dir}, string).
 326
 327
 328%%--------------------------------------------------------------------------
 329%% rename(Pid, Old, New) ->  ok | {error, epath} | {error, elogin} 
 330%%                              | {error, econn}
 331%%	Pid = pid()
 332%%	CurrFile = NewFile = string()
 333%%
 334%% Description:  Rename a file at remote server.
 335%%--------------------------------------------------------------------------
 336
 337-spec rename(Pid :: pid(), Old :: string(), New :: string()) ->
 338    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
 339
 340rename(Pid, Old, New) ->
 341    call(Pid, {rename, Old, New}, string).
 342
 343
 344%%--------------------------------------------------------------------------
 345%% delete(Pid, File) ->  ok | {error, epath} | {error, elogin} | 
 346%%                       {error, econn}
 347%%	Pid = pid()
 348%%	File = string()
 349%%
 350%% Description:  Remove file at remote server.
 351%%--------------------------------------------------------------------------
 352
 353-spec delete(Pid :: pid(), File :: string()) ->
 354    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
 355
 356delete(Pid, File) ->
 357    call(Pid, {delete, File}, string).
 358
 359
 360%%--------------------------------------------------------------------------
 361%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
 362%%	Pid = pid(), 
 363%%	Dir = string()
 364%%
 365%% Description:  Make directory at remote server.
 366%%--------------------------------------------------------------------------
 367
 368-spec mkdir(Pid :: pid(), Dir :: string()) ->
 369    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
 370
 371mkdir(Pid, Dir) ->
 372    call(Pid, {mkdir, Dir}, atom).
 373
 374
 375%%--------------------------------------------------------------------------
 376%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
 377%%	Pid = pid(), 
 378%%	Dir = string()
 379%%
 380%% Description:  Remove directory at remote server.
 381%%--------------------------------------------------------------------------
 382
 383-spec rmdir(Pid :: pid(), Dir :: string()) ->
 384    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
 385
 386rmdir(Pid, Dir) ->
 387    call(Pid, {rmdir, Dir}, atom).
 388
 389
 390%%--------------------------------------------------------------------------
 391%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn}
 392%%	Pid = pid() 
 393%%	Type = ascii | binary
 394%%
 395%% Description:  Set transfer type.
 396%%--------------------------------------------------------------------------
 397
 398-spec type(Pid :: pid(), Type :: ascii | binary) ->
 399    'ok' | 
 400	{'error', Reason :: 'etype' | restriction_reason() | common_reason()}.
 401
 402type(Pid, Type) ->
 403    call(Pid, {type, Type}, atom).
 404
 405
 406%%--------------------------------------------------------------------------
 407%% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} |
 408%%                                          {error, elogin} | {error, econn}
 409%%	Pid = pid()
 410%%	RemoteFileName = LocalFileName = string()
 411%%
 412%% Description:  Transfer file from remote server.
 413%%--------------------------------------------------------------------------
 414
 415-spec recv(Pid :: pid(), RemoteFileName :: string()) ->
 416    'ok' | {'error', Reason :: restriction_reason() | 
 417                               common_reason() | 
 418                               file_write_error_reason()}.
 419
 420recv(Pid, RemotFileName) ->
 421  recv(Pid, RemotFileName, RemotFileName).
 422
 423-spec recv(Pid            :: pid(), 
 424	   RemoteFileName :: string(), 
 425	   LocalFileName  :: string()) ->
 426    'ok' | {'error', Reason :: term()}.
 427
 428recv(Pid, RemotFileName, LocalFileName) ->
 429    call(Pid, {recv, RemotFileName, LocalFileName}, atom).
 430
 431
 432%%--------------------------------------------------------------------------
 433%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin} 
 434%%			   | {error, econn}
 435%%	Pid = pid()
 436%%	RemoteFile = string()
 437%%      Bin = binary()
 438%%
 439%% Description:  Transfer file from remote server into binary.
 440%%--------------------------------------------------------------------------
 441
 442-spec recv_bin(Pid        :: pid(), 
 443	       RemoteFile :: string()) ->
 444    {'ok', Bin :: binary()} | 
 445	{'error', Reason :: restriction_reason() | common_reason()}.
 446
 447recv_bin(Pid, RemoteFile) ->
 448    call(Pid, {recv_bin, RemoteFile}, bin).
 449
 450
 451%%--------------------------------------------------------------------------
 452%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} 
 453%%                                 | {error, econn}
 454%%	Pid = pid()
 455%%	RemoteFile = string()
 456%%
 457%% Description:  Start receive of chunks of remote file.
 458%%--------------------------------------------------------------------------
 459
 460-spec recv_chunk_start(Pid        :: pid(), 
 461		       RemoteFile :: string()) ->
 462    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
 463
 464recv_chunk_start(Pid, RemoteFile) ->
 465    call(Pid, {recv_chunk_start, RemoteFile}, atom).
 466
 467
 468%%--------------------------------------------------------------------------
 469%% recv_chunk(Pid, RemoteFile) ->  ok | {ok, Bin} | {error, Reason}
 470%%	Pid = pid()
 471%%	RemoteFile = string()
 472%%
 473%% Description:  Transfer file from remote server into binary in chunks
 474%%--------------------------------------------------------------------------
 475
 476-spec recv_chunk(Pid :: pid()) ->
 477    'ok' | 
 478	{'ok', Bin :: binary()} | 
 479	{'error', Reason :: restriction_reason() | common_reason()}.
 480
 481recv_chunk(Pid) ->
 482    call(Pid, recv_chunk, atom).
 483
 484
 485%%--------------------------------------------------------------------------
 486%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} 
 487%%                                                  | {error, elogin} 
 488%%                                                  | {error, econn}
 489%%	Pid = pid()
 490%%	LocalFileName = RemotFileName = string()
 491%%
 492%% Description:  Transfer file to remote server.
 493%%--------------------------------------------------------------------------
 494
 495-spec send(Pid :: pid(), LocalFileName :: string()) ->
 496    'ok' | 
 497	{'error', Reason :: restriction_reason() | 
 498                            common_reason() | 
 499                            shortage_reason()}.
 500
 501send(Pid, LocalFileName) ->
 502  send(Pid, LocalFileName, LocalFileName).
 503
 504-spec send(Pid            :: pid(), 
 505	   LocalFileName  :: string(), 
 506	   RemoteFileName :: string()) ->
 507    'ok' | 
 508	{'error', Reason :: restriction_reason() | 
 509                            common_reason() | 
 510                            shortage_reason()}.
 511
 512send(Pid, LocalFileName, RemotFileName) ->
 513    call(Pid, {send, LocalFileName, RemotFileName}, atom).
 514
 515
 516%%--------------------------------------------------------------------------
 517%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} 
 518%%                             | {error, enotbinary} | {error, econn}
 519%%	Pid = pid()
 520%%	Bin = binary()
 521%%	RemoteFile = string()
 522%%
 523%% Description:  Transfer a binary to a remote file.
 524%%--------------------------------------------------------------------------
 525
 526-spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) ->
 527    'ok' | 
 528	{'error', Reason :: restriction_reason() | 
 529                            common_reason() | 
 530                            shortage_reason()}.
 531
 532send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
 533    call(Pid, {send_bin, Bin, RemoteFile}, atom);
 534send_bin(_Pid, _Bin, _RemoteFile) ->
 535  {error, enotbinary}.
 536
 537
 538%%--------------------------------------------------------------------------
 539%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} 
 540%%                                 | {error, econn}
 541%%	Pid = pid()
 542%%	RemoteFile = string()
 543%%
 544%% Description:  Start transfer of chunks to remote file.
 545%%--------------------------------------------------------------------------
 546
 547-spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
 548    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
 549
 550send_chunk_start(Pid, RemoteFile) ->
 551    call(Pid, {send_chunk_start, RemoteFile}, atom).
 552
 553
 554%%--------------------------------------------------------------------------
 555%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | 
 556%%                                        {error, epath} | {error, econn}
 557%%	Pid = pid()
 558%%	RemoteFile = string()
 559%%
 560%% Description:  Start append chunks of data to remote file.
 561%%--------------------------------------------------------------------------
 562
 563-spec append_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
 564    'ok' | {'error', Reason :: term()}.
 565
 566append_chunk_start(Pid, RemoteFile) ->
 567    call(Pid, {append_chunk_start, RemoteFile}, atom).
 568
 569
 570%%--------------------------------------------------------------------------
 571%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} 
 572%%                       | {error, echunk} | {error, econn}
 573%%      Pid = pid()
 574%%	Bin = binary().
 575%%
 576%% Purpose:  Send chunk to remote file.
 577%%--------------------------------------------------------------------------
 578
 579-spec send_chunk(Pid :: pid(), Bin :: binary()) ->
 580    'ok' | 
 581	{'error', Reason :: 'echunk' | 
 582                            restriction_reason() | 
 583                            common_reason()}.
 584
 585send_chunk(Pid, Bin) when is_binary(Bin) ->
 586    call(Pid, {transfer_chunk, Bin}, atom);
 587send_chunk(_Pid, _Bin) ->
 588  {error, enotbinary}.
 589
 590
 591%%--------------------------------------------------------------------------
 592%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} 
 593%%			     | {error, echunk} | {error, econn}
 594%%	Pid = pid()
 595%%	Bin = binary()
 596%%
 597%% Description:  Append chunk to remote file.
 598%%--------------------------------------------------------------------------
 599
 600-spec append_chunk(Pid :: pid(), Bin :: binary()) ->
 601    'ok' | 
 602	{'error', Reason :: 'echunk' | 
 603                            restriction_reason() | 
 604                            common_reason()}.
 605
 606append_chunk(Pid, Bin) when is_binary(Bin) ->
 607    call(Pid, {transfer_chunk, Bin}, atom);
 608append_chunk(_Pid, _Bin) ->
 609  {error, enotbinary}.
 610
 611
 612%%--------------------------------------------------------------------------
 613%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} 
 614%%			  | {error, econn}
 615%%	Pid = pid()
 616%%
 617%% Description:  End sending of chunks to remote file.
 618%%--------------------------------------------------------------------------
 619
 620-spec send_chunk_end(Pid :: pid()) ->
 621    'ok' | 
 622	{'error', Reason :: restriction_reason() | 
 623                            common_reason() | 
 624                            shortage_reason()}.
 625
 626send_chunk_end(Pid) ->
 627    call(Pid, chunk_end, atom).
 628
 629
 630%%--------------------------------------------------------------------------
 631%% append_chunk_end(Pid) ->  ok | {error, elogin} | {error, echunk} 
 632%%			     | {error, econn}
 633%%	Pid = pid()
 634%%
 635%% Description:  End appending of chunks to remote file.
 636%%--------------------------------------------------------------------------
 637
 638-spec append_chunk_end(Pid :: pid()) ->
 639    'ok' | 
 640	{'error', Reason :: restriction_reason() | 
 641                            common_reason() | 
 642                            shortage_reason()}.
 643
 644append_chunk_end(Pid) ->
 645    call(Pid, chunk_end, atom).
 646
 647
 648%%--------------------------------------------------------------------------
 649%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} 
 650%%                                                    | {error, elogin} 
 651%%                                                    | {error, econn}
 652%%	Pid = pid()
 653%%	LocalFileName = RemotFileName = string()
 654%%
 655%% Description:  Append the local file to the remote file
 656%%--------------------------------------------------------------------------
 657
 658-spec append(Pid :: pid(), LocalFileName :: string()) ->
 659    'ok' | 
 660	{'error', Reason :: 'epath'    | 
 661                            'elogin'   | 
 662                            'etnospc'  | 
 663                            'epnospc'  | 
 664                            'efnamena' | common_reason()}.
 665
 666append(Pid, LocalFileName) ->
 667    append(Pid, LocalFileName, LocalFileName).
 668
 669-spec append(Pid            :: pid(), 
 670	     LocalFileName  :: string(), 
 671	     RemoteFileName :: string()) ->
 672    'ok' | {'error', Reason :: term()}.
 673
 674append(Pid, LocalFileName, RemotFileName) ->
 675    call(Pid, {append, LocalFileName, RemotFileName}, atom).
 676
 677
 678%%--------------------------------------------------------------------------
 679%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} 
 680%%				  | {error, enotbinary} | {error, econn}
 681%%	Pid = pid()
 682%%	Bin = binary()
 683%%	RemoteFile = string()
 684%%
 685%% Purpose:  Append a binary to a remote file.
 686%%--------------------------------------------------------------------------
 687
 688-spec append_bin(Pid        :: pid(), 
 689		 Bin        :: binary(), 
 690		 RemoteFile :: string()) ->
 691    'ok' | 
 692	{'error', Reason :: restriction_reason() | 
 693                            common_reason() | 
 694                            shortage_reason()}.
 695
 696append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
 697    call(Pid, {append_bin, Bin, RemoteFile}, atom);
 698append_bin(_Pid, _Bin, _RemoteFile) ->
 699    {error, enotbinary}.
 700
 701
 702%%--------------------------------------------------------------------------
 703%% quote(Pid, Cmd) -> list()
 704%%	Pid = pid()
 705%%	Cmd = string()
 706%%
 707%% Description: Send arbitrary ftp command.
 708%%--------------------------------------------------------------------------
 709
 710-spec quote(Pid :: pid(), Cmd :: string()) -> list().
 711
 712quote(Pid, Cmd) when is_list(Cmd) ->
 713    call(Pid, {quote, Cmd}, atom).
 714
 715
 716%%--------------------------------------------------------------------------
 717%% close(Pid) -> ok
 718%%	Pid = pid()
 719%%
 720%% Description:  End the ftp session.
 721%%--------------------------------------------------------------------------
 722
 723-spec close(Pid :: pid()) -> 'ok'.
 724
 725close(Pid) ->
 726    cast(Pid, close),
 727    ok.
 728
 729
 730%%--------------------------------------------------------------------------
 731%% formaterror(Tag) -> string()
 732%%	Tag = atom() | {error, atom()}
 733%%
 734%% Description:  Return diagnostics.
 735%%--------------------------------------------------------------------------
 736
 737-spec formaterror(Tag :: term()) -> string().
 738
 739formaterror(Tag) ->
 740  ftp_response:error_string(Tag).
 741
 742
 743info(Pid) ->
 744    call(Pid, info, list).
 745
 746
 747%%%========================================================================
 748%%% Behavior callbacks
 749%%%========================================================================
 750start_standalone(Options) ->
 751    try
 752	{ok, StartOptions} = start_options(Options),
 753	{ok, OpenOptions}  = open_options(Options), 
 754	case start_link(StartOptions, []) of
 755	    {ok, Pid} ->
 756		call(Pid, {open, ip_comm, OpenOptions}, plain);
 757	    Error1 ->
 758		Error1
 759	end
 760    catch 
 761	throw:Error2 ->
 762	    Error2
 763    end.
 764
 765start_service(Options) ->
 766    try
 767	{ok, StartOptions} = start_options(Options),
 768	{ok, OpenOptions}  = open_options(Options), 
 769	case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
 770	    {ok, Pid} ->
 771		call(Pid, {open, ip_comm, OpenOptions}, plain);
 772	    Error1 ->
 773		Error1
 774	end
 775    catch 
 776	throw:Error2 ->
 777	    Error2
 778    end.
 779
 780stop_service(Pid) ->
 781    close(Pid).
 782
 783services() ->
 784    [{ftpc, Pid} || {_, Pid, _, _} <- 
 785			supervisor:which_children(ftp_sup)].
 786service_info(Pid) ->
 787    {ok, Info} = call(Pid, info, list),
 788    {ok, [proplists:lookup(mode, Info), 
 789	  proplists:lookup(local_port, Info),
 790	  proplists:lookup(peer, Info),
 791	  proplists:lookup(peer_port, Info)]}.
 792
 793
 794%% This function extracts the start options from the 
 795%% Valid options: 
 796%%     debug, 
 797%%     verbose 
 798%%     ipfamily
 799%%     priority
 800%%     flags    (for backward compatibillity)
 801start_options(Options) ->
 802    ?fcrt("start_options", [{options, Options}]), 
 803    case lists:keysearch(flags, 1, Options) of
 804	{value, {flags, Flags}} ->
 805	    Verbose = lists:member(verbose, Flags),
 806	    IsTrace = lists:member(trace, Flags), 
 807	    IsDebug = lists:member(debug, Flags), 
 808	    DebugLevel = 
 809		if 
 810		    (IsTrace =:= true) ->
 811			trace;
 812		    IsDebug =:= true ->
 813			debug;
 814		    true ->
 815			disable
 816		end,
 817	    {ok, [{verbose,  Verbose}, 
 818		  {debug,    DebugLevel}, 
 819		  {priority, low}]};
 820	false ->
 821	    ValidateVerbose = 
 822		fun(true) -> true;
 823		   (false) -> true;
 824		   (_) -> false
 825		end,
 826	    ValidateDebug = 
 827		fun(trace) -> true;
 828		   (debug) -> true;
 829		   (disable) -> true;
 830		   (_) -> false
 831		end,
 832	    ValidatePriority = 
 833		fun(low) -> true;
 834		   (normal) -> true;
 835		   (high) -> true;
 836		   (_) -> false
 837		end,
 838	    ValidOptions = 
 839		[{verbose,  ValidateVerbose,  false, false}, 
 840		 {debug,    ValidateDebug,    false, disable}, 
 841		 {priority, ValidatePriority, false, low}], 
 842	    validate_options(Options, ValidOptions, [])
 843    end.
 844
 845
 846%% This function extracts and validates the open options from the 
 847%% Valid options: 
 848%%    mode
 849%%    host
 850%%    port
 851%%    timeout
 852%%    dtimeout
 853%%    progress
 854open_options(Options) ->
 855    ?fcrt("open_options", [{options, Options}]), 
 856    ValidateMode = 
 857	fun(active) -> true;
 858	   (passive) -> true;
 859	   (_) -> false
 860	end,
 861    ValidateHost = 
 862	fun(Host) when is_list(Host) ->
 863		true;
 864	   (Host) when is_tuple(Host) andalso 
 865		       ((size(Host) =:= 4) orelse (size(Host) =:= 8)) ->
 866		true;
 867	   (_) ->
 868		false
 869	end,
 870    ValidatePort = 
 871	fun(Port) when is_integer(Port) andalso (Port > 0) -> true;
 872	   (_) -> false
 873	end,
 874    ValidateIpFamily = 
 875	fun(inet) -> true;
 876	   (inet6) -> true;
 877	   (inet6fb4) -> true;
 878	   (_) -> false
 879	end,
 880    ValidateTimeout = 
 881	fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true;
 882	   (_) -> false
 883	end,
 884    ValidateDTimeout = 
 885	fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true;
 886	   (infinity) -> true;
 887	   (_) -> false
 888	end,
 889    ValidateProgress = 
 890	fun(ignore) -> 
 891		true;
 892	   ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso 
 893					     is_atom(Func) -> 
 894		true;
 895	   (_) ->
 896		false
 897	end,
 898    ValidOptions = 
 899	[{mode,     ValidateMode,     false, ?DEFAULT_MODE}, 
 900	 {host,     ValidateHost,     true,  ehost},
 901	 {port,     ValidatePort,     false, ?FTP_PORT},
 902	 {ipfamily, ValidateIpFamily, false, inet},
 903	 {timeout,  ValidateTimeout,  false, ?CONNECTION_TIMEOUT}, 
 904	 {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT}, 
 905	 {progress, ValidateProgress, false, ?PROGRESS_DEFAULT}], 
 906    validate_options(Options, ValidOptions, []).
 907
 908validate_options([], [], Acc) ->
 909    ?fcrt("validate_options -> done", [{acc, Acc}]), 
 910    {ok, lists:reverse(Acc)};
 911validate_options([], ValidOptions, Acc) ->
 912    ?fcrt("validate_options -> done", 
 913	  [{valid_options, ValidOptions}, {acc, Acc}]), 
 914    %% Check if any mandatory options are missing!
 915    case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of
 916	[] ->
 917	    Defaults = 
 918		[{Key, Default} || {Key, _, _, Default} <- ValidOptions], 
 919	    {ok, lists:reverse(Defaults ++ Acc)};
 920	[{_, Reason}|_Missing] ->
 921	    throw({error, Reason})
 922    end;
 923validate_options([{Key, Value}|Options], ValidOptions, Acc) ->
 924    ?fcrt("validate_options -> check", 
 925	  [{key, Key}, {value, Value}, {acc, Acc}]), 
 926    case lists:keysearch(Key, 1, ValidOptions) of
 927	{value, {Key, Validate, _, Default}} ->
 928	    case (catch Validate(Value)) of
 929		true ->
 930		    ?fcrt("validate_options -> check - accept", []),
 931		    NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
 932		    validate_options(Options, NewValidOptions, 
 933				     [{Key, Value} | Acc]);
 934		_ ->
 935		    ?fcrt("validate_options -> check - reject", 
 936			  [{default, Default}]),
 937		    NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
 938		    validate_options(Options, NewValidOptions, 
 939				     [{Key, Default} | Acc])
 940	    end;
 941	false ->
 942	    validate_options(Options, ValidOptions, Acc)
 943    end;
 944validate_options([_|Options], ValidOptions, Acc) ->
 945    validate_options(Options, ValidOptions, Acc).
 946
 947
 948
 949%%%========================================================================
 950%%% gen_server callback functions 
 951%%%========================================================================
 952
 953%%-------------------------------------------------------------------------
 954%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}
 955%% Description: Initiates the erlang process that manages a ftp connection.
 956%%-------------------------------------------------------------------------
 957init(Options) ->
 958    process_flag(trap_exit, true),
 959
 960    %% Keep track of the client
 961    {value, {client, Client}} = lists:keysearch(client, 1, Options), 
 962    erlang:monitor(process, Client),
 963
 964    %% Make sure inet is started
 965    inet_db:start(),
 966    
 967    %% Where are we
 968    {ok, Dir} = file:get_cwd(),
 969
 970    %% Maybe activate dbg
 971    case key_search(debug, Options, disable) of
 972	trace ->
 973	    dbg:tracer(),
 974	    dbg:p(all, [call]),
 975	    dbg:tpl(ftp, [{'_', [], [{return_trace}]}]),
 976	    dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]),
 977	    dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]);
 978	debug ->
 979	    dbg:tracer(),
 980	    dbg:p(all, [call]),
 981	    dbg:tp(ftp, [{'_', [], [{return_trace}]}]),
 982	    dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]),
 983	    dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]); 
 984	_ ->
 985	    %% Keep silent
 986	    ok
 987    end,
 988
 989    %% Verbose?
 990    Verbose  = key_search(verbose, Options, false),
 991
 992    %% IpFamily?
 993    IpFamily = key_search(ipfamily, Options, inet),
 994
 995    State    = #state{owner    = Client, 
 996		      verbose  = Verbose, 
 997		      ipfamily = IpFamily, 
 998		      ldir     = Dir},
 999    
1000    %% Set process prio
1001    Priority = key_search(priority, Options, low),
1002    process_flag(priority, Priority),
1003
1004    %% And we are done
1005    {ok, State}.
1006
1007
1008%%--------------------------------------------------------------------------
1009%% handle_call(Request, From, State) -> {reply, Reply, State} |
1010%%                                      {reply, Reply, State, Timeout} |
1011%%                                      {noreply, State}               |
1012%%                                      {noreply, State, Timeout}      |
1013%%                                      {stop, Reason, Reply, State}   |
1014%% Description: Handle incoming requests. 
1015%%-------------------------------------------------------------------------
1016
1017%% Anyone can ask this question
1018handle_call({_, info}, _, #state{verbose  = Verbose,
1019				 mode     = Mode,
1020				 timeout  = Timeout,
1021				 ipfamily = IpFamily,
1022				 csock    = Socket,
1023				 progress = Progress} = State) ->
1024    {ok, {_, LocalPort}}  = inet:sockname(Socket),
1025    {ok, {Address, Port}} = inet:peername(Socket),
1026    Options = [{verbose,    Verbose}, 
1027	       {ipfamily,   IpFamily},
1028	       {mode,       Mode}, 
1029	       {peer,       Address}, 
1030	       {peer_port,  Port}, 
1031	       {local_port, LocalPort},
1032	       {timeout,    Timeout}, 
1033	       {progress,   Progress}],
1034    {reply, {ok, Options}, State};
1035
1036%% But everything else must come from the owner
1037handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid ->
1038    {reply, {error, not_connection_owner}, State};
1039
1040handle_call({_, {open, ip_comm, Opts}}, From, State) ->
1041    ?fcrd("handle_call(open)", [{opts, Opts}]), 
1042    case key_search(host, Opts, undefined) of
1043	undefined ->
1044	    {stop, normal, {error, ehost}, State};
1045	Host ->
1046	    Mode     = key_search(mode,     Opts, ?DEFAULT_MODE),
1047	    Port     = key_search(port,     Opts, ?FTP_PORT), 
1048	    Timeout  = key_search(timeout,  Opts, ?CONNECTION_TIMEOUT),
1049	    DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT),
1050	    Progress = key_search(progress, Opts, ignore),
1051	    IpFamily = key_search(ipfamily, Opts, inet),
1052
1053	    State2 = State#state{client   = From, 
1054				 mode     = Mode,
1055				 progress = progress(Progress),
1056				 ipfamily = IpFamily, 
1057				 dtimeout = DTimeout}, 
1058
1059	    ?fcrd("handle_call(open) -> setup ctrl connection with", 
1060		  [{host, Host}, {port, Port}, {timeout, Timeout}]), 
1061	    case setup_ctrl_connection(Host, Port, Timeout, State2) of
1062		{ok, State3, WaitTimeout} ->
1063		    ?fcrd("handle_call(open) -> ctrl connection setup done", 
1064			  [{waittimeout, WaitTimeout}]), 
1065		    {noreply, State3, WaitTimeout};
1066		{error, Reason} ->
1067		    ?fcrd("handle_call(open) -> ctrl connection setup failed", 
1068			  [{reason, Reason}]), 
1069		    gen_server:reply(From, {error, ehost}),
1070		    {stop, normal, State2#state{client = undefined}}
1071	    end
1072    end;	
1073
1074handle_call({_, {open, ip_comm, Host, Opts}}, From, State) ->
1075    Mode     = key_search(mode,     Opts, ?DEFAULT_MODE),
1076    Port     = key_search(port,     Opts, ?FTP_PORT), 
1077    Timeout  = key_search(timeout,  Opts, ?CONNECTION_TIMEOUT),
1078    DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT),
1079    Progress = key_search(progress, Opts, ignore),
1080    
1081    State2 = State#state{client   = From, 
1082			 mode     = Mode,
1083			 progress = progress(Progress), 
1084			 dtimeout = DTimeout}, 
1085
1086    case setup_ctrl_connection(Host, Port, Timeout, State2) of
1087	{ok, State3, WaitTimeout} ->
1088	    {noreply, State3, WaitTimeout};
1089	{error, _Reason} ->
1090	    gen_server:reply(From, {error, ehost}),
1091	    {stop, normal, State2#state{client = undefined}}
1092    end;	
1093
1094handle_call({_, {user, User, Password}}, From, 
1095	    #state{csock = CSock} = State) when (CSock =/= undefined) ->
1096    handle_user(User, Password, "", State#state{client = From});
1097
1098handle_call({_, {user, User, Password, Acc}}, From, 
1099	    #state{csock = CSock} = State) when (CSock =/= undefined) ->
1100    handle_user(User, Password, Acc, State#state{client = From});
1101   
1102handle_call({_, {account, Acc}}, From, State)->
1103    handle_user_account(Acc, State#state{client = From});
1104
1105handle_call({_, pwd}, From, #state{chunk = false} = State) ->
1106    send_ctrl_message(State, mk_cmd("PWD", [])), 
1107    activate_ctrl_connection(State),
1108    {noreply, State#state{client = From, caller = pwd}};
1109
1110handle_call({_, lpwd}, From,  #state{ldir = LDir} = State) ->
1111    {reply, {ok, LDir}, State#state{client = From}};
1112
1113handle_call({_, {cd, Dir}}, From,  #state{chunk = false} = State) ->
1114    send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])), 
1115    activate_ctrl_connection(State),
1116    {noreply, State#state{client = From, caller = cd}};
1117
1118handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) ->
1119    LDir = filename:absname(Dir, LDir0),
1120    case file:read_file_info(LDir) of %% FIX better check that LDir is a dir.
1121	{ok, _ } ->
1122	    {reply, ok, State#state{ldir = LDir}};
1123	_  ->
1124	    {reply, {error, epath}, State}
1125    end;
1126
1127handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From, 
1128	    #state{chunk = false} = State) ->
1129    setup_data_connection(State#state{caller = {dir, Dir, Len},
1130				      client = From});
1131handle_call({_, {rename, CurrFile, NewFile}}, From,
1132	    #state{chunk = false} = State) ->
1133    send_ctrl_message(State, mk_cmd("RNFR ~s", [CurrFile])),
1134    activate_ctrl_connection(State),
1135    {noreply, State#state{caller = {rename, NewFile}, client = From}};
1136
1137handle_call({_, {delete, File}}, {_Pid, _} = From, 
1138	    #state{chunk = false} = State) ->
1139    send_ctrl_message(State, mk_cmd("DELE ~s", [File])),
1140    activate_ctrl_connection(State),
1141    {noreply, State#state{client = From}};
1142
1143handle_call({_, {mkdir, Dir}}, From,  #state{chunk = false} = State) ->
1144    send_ctrl_message(State, mk_cmd("MKD ~s", [Dir])),
1145    activate_ctrl_connection(State),
1146    {noreply, State#state{client = From}};
1147
1148handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State) ->
1149    send_ctrl_message(State, mk_cmd("RMD ~s", [Dir])),
1150    activate_ctrl_connection(State),
1151    {noreply, State#state{client = From}};
1152
1153handle_call({_,{type, Type}}, From,  #state{chunk = false} 
1154	    = State) ->  
1155    case Type of
1156	ascii ->
1157	    send_ctrl_message(State, mk_cmd("TYPE A", [])),
1158	    activate_ctrl_connection(State),
1159	    {noreply, State#state{caller = type, type = ascii, 
1160				  client = From}};
1161	binary ->
1162	    send_ctrl_message(State, mk_cmd("TYPE I", [])),
1163	    activate_ctrl_connection(State),
1164	    {noreply, State#state{caller = type, type = binary, 
1165				  client = From}};
1166	_ ->
1167	    {reply, {error, etype}, State}
1168    end;
1169
1170handle_call({_,{recv, RemoteFile, LocalFile}}, From, 
1171	    #state{chunk = false, ldir = LocalDir} = State) ->
1172    progress_report({remote_file, RemoteFile}, State),
1173    NewLocalFile = filename:absname(LocalFile, LocalDir),
1174
1175    case file_open(NewLocalFile, write) of
1176	{ok, Fd} ->
1177	    setup_data_connection(State#state{client = From,
1178					      caller = 
1179					      {recv_file, 
1180					       RemoteFile, Fd}});
1181	{error, _What} ->
1182	    {reply, {error, epath}, State}
1183    end;
1184
1185handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} = 
1186	    State) ->
1187    setup_data_connection(State#state{caller = {recv_bin, RemoteFile},
1188				      client = From});
1189
1190handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false} 
1191	    = State) ->
1192    setup_data_connection(State#state{caller = {start_chunk_transfer,
1193						"RETR", RemoteFile},
1194				      client = From});
1195
1196handle_call({_, recv_chunk}, _, #state{chunk = false} = State) ->
1197    {reply, {error, "ftp:recv_chunk_start/2 not called"}, State}; 
1198
1199handle_call({_, recv_chunk}, From, #state{chunk = true} = State) ->
1200    activate_data_connection(State),
1201    {noreply, State#state{client = From, caller = recv_chunk}};
1202    
1203handle_call({_, {send, LocalFile, RemoteFile}}, From, 
1204	    #state{chunk = false, ldir = LocalDir} = State) ->
1205    progress_report({local_file, filename:absname(LocalFile, LocalDir)}, 
1206		    State),
1207    setup_data_connection(State#state{caller = {transfer_file,
1208						   {"STOR", 
1209						    LocalFile, RemoteFile}},
1210					 client = From});
1211handle_call({_, {append, LocalFile, RemoteFile}}, From, 
1212	    #state{chunk = false} = State) ->
1213    setup_data_connection(State#state{caller = {transfer_file,
1214						{"APPE", 
1215						 LocalFile, RemoteFile}},
1216				      client = From});
1217handle_call({_, {send_bin, Bin, RemoteFile}}, From, 
1218	    #state{chunk = false} = State) ->
1219    setup_data_connection(State#state{caller = {transfer_data,
1220					       {"STOR", Bin, RemoteFile}},
1221				      client = From});
1222handle_call({_,{append_bin, Bin, RemoteFile}}, From, 
1223	    #state{chunk = false} = State) ->
1224    setup_data_connection(State#state{caller = {transfer_data,
1225						{"APPE", Bin, RemoteFile}},
1226				      client = From});
1227handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false} 
1228	    = State) ->
1229    setup_data_connection(State#state{caller = {start_chunk_transfer,
1230						"STOR", RemoteFile},
1231				      client = From});
1232handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false} 
1233	    = State) ->
1234    setup_data_connection(State#state{caller = {start_chunk_transfer,
1235						"APPE", RemoteFile},
1236				      client = From});
1237handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) ->
1238    send_data_message(State, Bin),
1239    {reply, ok, State};
1240
1241handle_call({_, chunk_end}, From, #state{chunk = true} = State) ->
1242    close_data_connection(State),
1243    activate_ctrl_connection(State),
1244    {noreply, State#state{client = From, dsock = undefined, 
1245			  caller = end_chunk_transfer, chunk = false}};
1246
1247handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State) ->
1248    send_ctrl_message(State, mk_cmd(Cmd, [])),
1249    activate_ctrl_connection(State),
1250    {noreply, State#state{client = From, caller = quote}};
1251
1252handle_call({_, _Req}, _From, #state{csock = CSock} = State) 
1253  when (CSock =:= undefined) ->
1254    {reply, {error, not_connected}, State};
1255
1256handle_call(_, _, #state{chunk = true} = State) ->
1257    {reply, {error, echunk}, State};
1258
1259%% Catch all -  This can only happen if the application programmer writes 
1260%% really bad code that violates the API.
1261handle_call(Request, _Timeout, State) ->
1262    {stop, {'API_violation_connection_closed', Request},
1263     {error, {connection_terminated, 'API_violation'}}, State}.
1264
1265%%--------------------------------------------------------------------------
1266%% handle_cast(Request, State) -> {noreply, State} | 
1267%%                                {noreply, State, Timeout} |
1268%%                                {stop, Reason, State} 
1269%% Description: Handles cast messages.         
1270%%-------------------------------------------------------------------------
1271handle_cast({Pid, close}, #state{owner = Pid} = State) ->
1272    send_ctrl_message(State, mk_cmd("QUIT", [])),
1273    close_ctrl_connection(State),
1274    close_data_connection(State),
1275    {stop, normal, State#state{csock = undefined, dsock = undefined}};
1276
1277handle_cast({Pid, close}, State) ->
1278    Report = io_lib:format("A none owner process ~p tried to close an "
1279			     "ftp connection: ~n", [Pid]),
1280    error_logger:info_report(Report),
1281    {noreply, State};
1282
1283%% Catch all -  This can oly happen if the application programmer writes 
1284%% really bad code that violates the API.
1285handle_cast(Msg, State) ->
1286  {stop, {'API_violation_connection_closed', Msg}, State}.
1287
1288%%--------------------------------------------------------------------------
1289%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} |
1290%%			      {stop, Reason, State}
1291%% Description: Handles tcp messages from the ftp-server.
1292%% Note: The order of the function clauses is significant.
1293%%--------------------------------------------------------------------------
1294
1295handle_info(timeout, #state{caller = open} = State) ->
1296    {stop, timeout, State};
1297
1298handle_info(timeout, State) ->
1299    {noreply, State};
1300
1301%%% Data socket messages %%%
1302handle_info({tcp, Socket, Data}, 
1303	    #state{dsock = Socket, 
1304		   caller = {recv_file, Fd}} = State) ->    
1305    file_write(binary_to_list(Data), Fd),
1306    progress_report({binary, Data}, State),
1307    activate_data_connection(State),
1308    {noreply, State};
1309
1310handle_info({tcp, Socket, Data}, #state{dsock = Socket, client = From,	
1311					caller = recv_chunk} 
1312	    = State)  ->    
1313    gen_server:reply(From, {ok, Data}),
1314    {noreply, State#state{client = undefined, data = <<>>}};
1315
1316handle_info({tcp, Socket, Data}, #state{dsock = Socket} = State) ->
1317    activate_data_connection(State),
1318    {noreply, State#state{data = <<(State#state.data)/binary,
1319				  Data/binary>>}};
1320
1321handle_info({tcp_closed, Socket}, #state{dsock = Socket,
1322					 caller = {recv_file, Fd}} 
1323	    = State) ->
1324    file_close(Fd),
1325    progress_report({transfer_size, 0}, State),
1326    activate_ctrl_connection(State),
1327    {noreply, State#state{dsock = undefined, data = <<>>}};
1328
1329handle_info({tcp_closed, Socket}, #state{dsock = Socket, client = From,
1330					 caller = recv_chunk} 
1331	    = State) ->
1332    gen_server:reply(From, ok),
1333    {noreply, State#state{dsock = undefined, client = undefined,
1334			  data = <<>>, caller = undefined,
1335			  chunk = false}};
1336
1337handle_info({tcp_closed, Socket}, #state{dsock = Socket, caller = recv_bin, 
1338					 data = Data} = State) ->
1339    activate_ctrl_connection(State),
1340    {noreply, State#state{dsock = undefined, data = <<>>, 
1341			  caller = {recv_bin, Data}}};
1342
1343handle_info({tcp_closed, Socket}, #state{dsock = Socket, data = Data,
1344					 caller = {handle_dir_result, Dir}} 
1345	    = State) ->
1346    activate_ctrl_connection(State),
1347    {noreply, State#state{dsock = undefined, 
1348			  caller = {handle_dir_result, Dir, Data},
1349%			  data = <<?CR,?LF>>}};
1350			  data = <<>>}};
1351	    
1352handle_info({tcp_error, Socket, Reason}, #state{dsock = Socket,
1353						client = From} = State) ->
1354    gen_server:reply(From, {error, Reason}),
1355    close_data_connection(State),
1356    {noreply, State#state{dsock = undefined, client = undefined,
1357			  data = <<>>, caller = undefined, chunk = false}};
1358
1359%%% Ctrl socket messages %%%
1360handle_info({tcp, Socket, Data}, #state{csock = Socket, 
1361					verbose = Verbose,
1362					caller = Caller,
1363					client = From,
1364					ctrl_data = {CtrlData, AccLines, 
1365						     LineStatus}} 
1366	    = State) ->    
1367    case ftp_response:parse_lines(<<CtrlData/binary, Data/binary>>, 
1368				  AccLines, LineStatus) of
1369	{ok, Lines, NextMsgData} ->
1370	    verbose(Lines, Verbose, 'receive'),
1371	    CtrlResult = ftp_response:interpret(Lines), 
1372	    case Caller of
1373		quote ->
1374		    gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
1375		    {noreply, State#state{client = undefined, 
1376					  caller = undefined,
1377					  ctrl_data = {NextMsgData, [], 
1378						       start}}};
1379		_ ->
1380		    handle_ctrl_result(CtrlResult,
1381				       State#state{ctrl_data = 
1382						   {NextMsgData, [], start}})
1383	    end;
1384	{continue, NewCtrlData} ->
1385	    activate_ctrl_connection(State),
1386	    {noreply, State#state{ctrl_data = NewCtrlData}}
1387    end;
1388
1389handle_info({tcp_closed, Socket}, #state{csock = Socket}) ->  
1390    %% If the server closes the control channel it is 
1391    %% the expected behavior that connection process terminates.
1392    exit(normal); %% User will get error message from terminate/2
1393
1394handle_info({tcp_error, Socket, Reason}, _) ->
1395    Report = 
1396	io_lib:format("tcp_error on socket: ~p  for reason: ~p~n",
1397		      [Socket, Reason]),
1398    error_logger:error_report(Report),
1399    %% If tcp does not work the only option is to terminate,
1400    %% this is the expected behavior under these circumstances.
1401    exit(normal); %% User will get error message from terminate/2
1402
1403%% Monitor messages - if the process owning the ftp connection goes
1404%% down there is no point in continuing.
1405handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->
1406    {stop, normal, State#state{client = undefined}};
1407
1408handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
1409    {stop, normal, State#state{client = undefined}};
1410    
1411handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->
1412    {stop, normal, State#state{client = undefined}};
1413 
1414handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
1415    {stop, {stopped, {'EXIT', Process, Reason}},
1416     State#state{client = undefined}};
1417
1418handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) ->
1419    Report = io_lib:format("Progress reporting stopped for reason ~p~n",
1420			   Reason),
1421    error_logger:info_report(Report),
1422    {noreply, State#state{progress = ignore}};
1423   
1424%% Catch all - throws away unknown messages (This could happen by "accident"
1425%% so we do not want to crash, but we make a log entry as it is an
1426%% unwanted behaviour.) 
1427handle_info(Info, State) ->
1428    Report = io_lib:format("ftp : ~p : Unexpected message: ~p\n", 
1429			   [self(), Info]),
1430    error_logger:info_report(Report),
1431    {noreply, State}.
1432
1433%%--------------------------------------------------------------------------
1434%% terminate/2 and code_change/3
1435%%--------------------------------------------------------------------------
1436terminate(normal, State) ->
1437    %% If terminate reason =/= normal the progress reporting process will
1438    %% be killed by the exit signal.
1439    progress_report(stop, State), 
1440    do_termiante({error, econn}, State);
1441terminate(Reason, State) -> 
1442    Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]),
1443    error_logger:error_report(Report),
1444    do_termiante({error, eclosed}, State).
1445
1446do_termiante(ErrorMsg, State) ->
1447    close_data_connection(State),
1448    close_ctrl_connection(State),
1449    case State#state.client of
1450	undefined ->
1451	    ok;
1452	From ->
1453	    gen_server:reply(From, ErrorMsg)
1454    end,
1455    ok. 
1456
1457code_change(_Vsn, State1, upgrade_from_pre_5_12) ->
1458    {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, 
1459     Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1, 
1460    IpFamily = 
1461	if
1462	    (IPv6Disable =:= true) ->
1463		inet;
1464	    true ->
1465		inet6fb4
1466	end,
1467    State2 = #state{csock     = CSock,
1468		    dsock     = DSock,
1469		    verbose   = Verbose, 
1470		    ldir      = LDir,
1471		    type      = Type, 
1472		    chunk     = Chunk, 
1473		    mode      = Mode, 
1474		    timeout   = Timeout, 
1475		    data      = Data, 
1476		    ctrl_data = CtrlData, 
1477		    owner     = Owner, 
1478		    client    = Client, 
1479		    caller    = Caller, 
1480		    ipfamily  = IpFamily,
1481		    progress  = Progress}, 
1482    {ok, State2};
1483
1484code_change(_Vsn, State1, downgrade_to_pre_5_12) ->
1485    #state{csock     = CSock,
1486	   dsock     = DSock,
1487	   verbose   = Verbose, 
1488	   ldir      = LDir,
1489	   type      = Type, 
1490	   chunk     = Chunk, 
1491	   mode      = Mode, 
1492	   timeout   = Timeout, 
1493	   data      = Data, 
1494	   ctrl_d

Large files files are truncated, but you can click here to view the full file