1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2002-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% 21 22-module(ftp). 23 24-behaviour(gen_server). 25 26-export([start/0, 27 start_service/1, 28 stop/0, 29 stop_service/1, 30 services/0, 31 service_info/1 32 ]). 33 34%% Added for backward compatibility 35-export([start_standalone/1]). 36 37-export([start_link/1, start_link/2]). 38 39%% API - Client interface 40-export([cd/2, close/1, delete/2, formaterror/1, 41 lcd/2, lpwd/1, ls/1, ls/2, 42 mkdir/2, nlist/1, nlist/2, 43 open/1, open/2, 44 pwd/1, quote/2, 45 recv/2, recv/3, recv_bin/2, 46 recv_chunk_start/2, recv_chunk/1, 47 rename/3, rmdir/2, 48 send/2, send/3, send_bin/3, 49 send_chunk_start/2, send_chunk/2, send_chunk_end/1, 50 type/2, user/3, user/4, account/2, 51 append/3, append/2, append_bin/3, 52 append_chunk/2, append_chunk_end/1, append_chunk_start/2, 53 info/1, latest_ctrl_response/1]). 54 55%% gen_server callbacks 56-export([init/1, handle_call/3, handle_cast/2, 57 handle_info/2, terminate/2, code_change/3]). 58 59-include("ftp_internal.hrl"). 60 61%% Constants used in internal state definition 62-define(CONNECTION_TIMEOUT, 60*1000). 63-define(DATA_ACCEPT_TIMEOUT, infinity). 64-define(DEFAULT_MODE, passive). 65-define(PROGRESS_DEFAULT, ignore). 66-define(FTP_EXT_DEFAULT, false). 67 68%% Internal Constants 69-define(FTP_PORT, 21). 70-define(FILE_BUFSIZE, 4096). 71 72 73%%%========================================================================= 74%%% Data Types 75%%%========================================================================= 76 77%% Internal state 78-record(state, { 79 csock = undefined, % socket() - Control connection socket 80 dsock = undefined, % socket() - Data connection socket 81 tls_options = undefined, % list() 82 verbose = false, % boolean() 83 ldir = undefined, % string() - Current local directory 84 type = ftp_server_default, % atom() - binary | ascii 85 chunk = false, % boolean() - Receiving data chunks 86 mode = ?DEFAULT_MODE, % passive | active 87 timeout = ?CONNECTION_TIMEOUT, % integer() 88 %% Data received so far on the data connection 89 data = <<>>, % binary() 90 %% Data received so far on the control connection 91 %% {BinStream, AccLines}. If a binary sequence 92 %% ends with ?CR then keep it in the binary to 93 %% be able to detect if the next received byte is ?LF 94 %% and hence the end of the response is reached! 95 ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus} 96 %% pid() - Client pid (note not the same as "From") 97 latest_ctrl_response = "", 98 owner = undefined, 99 client = undefined, % "From" to be used in gen_server:reply/2 100 %% Function that activated a connection and maybe some 101 %% data needed further on. 102 caller = undefined, % term() 103 ipfamily, % inet | inet6 | inet6fb4 104 sockopts_ctrl = [], 105 sockopts_data_passive = [], 106 sockopts_data_active = [], 107 progress = ignore, % ignore | pid() 108 dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity 109 tls_upgrading_data_connection = false, 110 ftp_extension = ?FTP_EXT_DEFAULT 111 }). 112 113-record(recv_chunk_closing, { 114 dconn_closed = false, 115 pos_compl_received = false, 116 client_called_us = false 117 }). 118 119 120-type shortage_reason() :: 'etnospc' | 'epnospc'. 121-type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'. 122-type common_reason() :: 'econn' | 'eclosed' | term(). 123-type file_write_error_reason() :: term(). % See file:write for more info 124 125-define(DBG(F,A), 'n/a'). 126%%-define(DBG(F,A), io:format(F,A)). 127%%-define(DBG(F,A), ct:pal("~p:~p " ++ if is_list(F) -> F; is_atom(F) -> atom_to_list(F) end, [?MODULE,?LINE|A])). 128 129 130%%%========================================================================= 131%%% API 132%%%========================================================================= 133 134start() -> 135 application:start(ftp). 136 137start_standalone(Options) -> 138 try 139 {ok, StartOptions} = start_options(Options), 140 {ok, OpenOptions} = open_options(Options), 141 {ok, SocketOptions} = socket_options(Options), 142 case start_link(StartOptions, []) of 143 {ok, Pid} -> 144 call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain); 145 Error1 -> 146 Error1 147 end 148 catch 149 throw:Error2 -> 150 Error2 151 end. 152 153start_service(Options) -> 154 try 155 {ok, StartOptions} = start_options(Options), 156 {ok, OpenOptions} = open_options(Options), 157 {ok, SocketOptions} = socket_options(Options), 158 case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of 159 {ok, Pid} -> 160 call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain); 161 Error1 -> 162 Error1 163 end 164 catch 165 throw:Error2 -> 166 Error2 167 end. 168 169stop() -> 170 application:stop(ftp). 171 172stop_service(Pid) -> 173 close(Pid). 174 175services() -> 176 [{ftpc, Pid} || {_, Pid, _, _} <- 177 supervisor:which_children(ftp_sup)]. 178service_info(Pid) -> 179 {ok, Info} = call(Pid, info, list), 180 {ok, [proplists:lookup(mode, Info), 181 proplists:lookup(local_port, Info), 182 proplists:lookup(peer, Info), 183 proplists:lookup(peer_port, Info)]}. 184 185 186%%%========================================================================= 187%%% API - CLIENT FUNCTIONS 188%%%========================================================================= 189 190%%-------------------------------------------------------------------------- 191%% open(HostOrOtpList, <Port>, <Flags>) -> {ok, Pid} | {error, ehost} 192%% HostOrOtpList = string() | [{option_list, Options}] 193%% Port = integer(), 194%% Flags = [Flag], 195%% Flag = verbose | debug | trace 196%% 197%% Description: Start an ftp client and connect to a host. 198%%-------------------------------------------------------------------------- 199 200-spec open(Host :: string() | inet:ip_address()) -> 201 {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}. 202 203%% <BACKWARD-COMPATIBILLITY> 204open({option_list, Options}) when is_list(Options) -> 205 try 206 {ok, StartOptions} = start_options(Options), 207 {ok, OpenOptions} = open_options(Options), 208 {ok, SockOpts} = socket_options(Options), 209 case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of 210 {ok, Pid} -> 211 call(Pid, {open, ip_comm, OpenOptions, SockOpts}, plain); 212 Error1 -> 213 Error1 214 end 215 catch 216 throw:Error2 -> 217 Error2 218 end; 219%% </BACKWARD-COMPATIBILLITY> 220 221open(Host) -> 222 open(Host, []). 223 224-spec open(Host :: string() | inet:ip_address(), Opts :: list()) -> 225 {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}. 226 227%% <BACKWARD-COMPATIBILLITY> 228open(Host, Port) when is_integer(Port) -> 229 open(Host, [{port, Port}]); 230%% </BACKWARD-COMPATIBILLITY> 231 232open(Host, Opts) when is_list(Opts) -> 233 try 234 {ok, StartOptions} = start_options(Opts), 235 {ok, OpenOptions} = open_options([{host, Host}|Opts]), 236 {ok, SocketOptions} = socket_options(Opts), 237 case start_link(StartOptions, []) of 238 {ok, Pid} -> 239 do_open(Pid, OpenOptions, SocketOptions, tls_options(Opts)); 240 Error1 -> 241 Error1 242 end 243 catch 244 throw:Error2 -> 245 Error2 246 end. 247 248do_open(Pid, OpenOptions, SocketOptions, TLSOpts) -> 249 case call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain) of 250 {ok, Pid} -> 251 maybe_tls_upgrade(Pid, TLSOpts); 252 Error -> 253 Error 254 end. 255%%-------------------------------------------------------------------------- 256%% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn} 257%% | {error, eacct} 258%% Pid = pid(), 259%% User = Pass = Acc = string() 260%% 261%% Description: Login with or without a supplied account name. 262%%-------------------------------------------------------------------------- 263-spec user(Pid :: pid(), 264 User :: string(), 265 Pass :: string()) -> 266 'ok' | {'error', Reason :: 'euser' | common_reason()}. 267 268user(Pid, User, Pass) -> 269 case {is_name_sane(User), is_name_sane(Pass)} of 270 {true, true} -> 271 call(Pid, {user, User, Pass}, atom); 272 _ -> 273 {error, euser} 274 end. 275 276-spec user(Pid :: pid(), 277 User :: string(), 278 Pass :: string(), 279 Acc :: string()) -> 280 'ok' | {'error', Reason :: 'euser' | common_reason()}. 281 282user(Pid, User, Pass, Acc) -> 283 case {is_name_sane(User), is_name_sane(Pass), is_name_sane(Acc)} of 284 {true, true, true} -> 285 call(Pid, {user, User, Pass, Acc}, atom); 286 _ -> 287 {error, euser} 288 end. 289 290 291%%-------------------------------------------------------------------------- 292%% account(Pid, Acc) -> ok | {error, eacct} 293%% Pid = pid() 294%% Acc= string() 295%% 296%% Description: Set a user Account. 297%%-------------------------------------------------------------------------- 298 299-spec account(Pid :: pid(), Acc :: string()) -> 300 'ok' | {'error', Reason :: 'eacct' | common_reason()}. 301 302account(Pid, Acc) -> 303 case is_name_sane(Acc) of 304 true -> 305 call(Pid, {account, Acc}, atom); 306 _ -> 307 {error, eacct} 308 end. 309 310 311%%-------------------------------------------------------------------------- 312%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn} 313%% Pid = pid() 314%% Dir = string() 315%% 316%% Description: Get the current working directory at remote server. 317%%-------------------------------------------------------------------------- 318 319-spec pwd(Pid :: pid()) -> 320 {'ok', Dir :: string()} | 321 {'error', Reason :: restriction_reason() | common_reason()}. 322 323pwd(Pid) -> 324 call(Pid, pwd, ctrl). 325 326 327%%-------------------------------------------------------------------------- 328%% lpwd(Pid) -> {ok, Dir} 329%% Pid = pid() 330%% Dir = string() 331%% 332%% Description: Get the current working directory at local server. 333%%-------------------------------------------------------------------------- 334 335-spec lpwd(Pid :: pid()) -> 336 {'ok', Dir :: string()}. 337 338lpwd(Pid) -> 339 call(Pid, lpwd, string). 340 341 342%%-------------------------------------------------------------------------- 343%% cd(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} 344%% Pid = pid() 345%% Dir = string() 346%% 347%% Description: Change current working directory at remote server. 348%%-------------------------------------------------------------------------- 349 350-spec cd(Pid :: pid(), Dir :: string()) -> 351 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. 352 353cd(Pid, Dir) -> 354 case is_name_sane(Dir) of 355 true -> 356 call(Pid, {cd, Dir}, atom); 357 _ -> 358 {error, efnamena} 359 end. 360 361 362%%-------------------------------------------------------------------------- 363%% lcd(Pid, Dir) -> ok | {error, epath} 364%% Pid = pid() 365%% Dir = string() 366%% 367%% Description: Change current working directory for the local client. 368%%-------------------------------------------------------------------------- 369 370-spec lcd(Pid :: pid(), Dir :: string()) -> 371 'ok' | {'error', Reason :: restriction_reason()}. 372 373lcd(Pid, Dir) -> 374 call(Pid, {lcd, Dir}, string). 375 376 377%%-------------------------------------------------------------------------- 378%% ls(Pid) -> Result 379%% ls(Pid, <Dir>) -> Result 380%% 381%% Pid = pid() 382%% Dir = string() 383%% Result = {ok, Listing} | {error, Reason} 384%% Listing = string() 385%% Reason = epath | elogin | econn 386%% 387%% Description: Returns a list of files in long format. 388%%-------------------------------------------------------------------------- 389 390-spec ls(Pid :: pid()) -> 391 {'ok', Listing :: string()} | 392 {'error', Reason :: restriction_reason() | common_reason()}. 393 394ls(Pid) -> 395 ls(Pid, ""). 396 397-spec ls(Pid :: pid(), Dir :: string()) -> 398 {'ok', Listing :: string()} | 399 {'error', Reason :: restriction_reason() | common_reason()}. 400 401ls(Pid, Dir) -> 402 case is_name_sane(Dir) of 403 true -> 404 call(Pid, {dir, long, Dir}, string); 405 _ -> 406 {error, efnamena} 407 end. 408 409 410%%-------------------------------------------------------------------------- 411%% nlist(Pid) -> Result 412%% nlist(Pid, Pathname) -> Result 413%% 414%% Pid = pid() 415%% Pathname = string() 416%% Result = {ok, Listing} | {error, Reason} 417%% Listing = string() 418%% Reason = epath | elogin | econn 419%% 420%% Description: Returns a list of files in short format 421%%-------------------------------------------------------------------------- 422 423-spec nlist(Pid :: pid()) -> 424 {'ok', Listing :: string()} | 425 {'error', Reason :: restriction_reason() | common_reason()}. 426 427nlist(Pid) -> 428 nlist(Pid, ""). 429 430-spec nlist(Pid :: pid(), Pathname :: string()) -> 431 {'ok', Listing :: string()} | 432 {'error', Reason :: restriction_reason() | common_reason()}. 433 434nlist(Pid, Dir) -> 435 case is_name_sane(Dir) of 436 true -> 437 call(Pid, {dir, short, Dir}, string); 438 _ -> 439 {error, efnamena} 440 end. 441 442 443%%-------------------------------------------------------------------------- 444%% rename(Pid, Old, New) -> ok | {error, epath} | {error, elogin} 445%% | {error, econn} 446%% Pid = pid() 447%% CurrFile = NewFile = string() 448%% 449%% Description: Rename a file at remote server. 450%%-------------------------------------------------------------------------- 451 452-spec rename(Pid :: pid(), Old :: string(), New :: string()) -> 453 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. 454 455rename(Pid, Old, New) -> 456 case {is_name_sane(Old), is_name_sane(New)} of 457 {true, true} -> 458 call(Pid, {rename, Old, New}, string); 459 _ -> 460 {error, efnamena} 461 end. 462 463 464%%-------------------------------------------------------------------------- 465%% delete(Pid, File) -> ok | {error, epath} | {error, elogin} | 466%% {error, econn} 467%% Pid = pid() 468%% File = string() 469%% 470%% Description: Remove file at remote server. 471%%-------------------------------------------------------------------------- 472 473-spec delete(Pid :: pid(), File :: string()) -> 474 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. 475 476delete(Pid, File) -> 477 case is_name_sane(File) of 478 true -> 479 call(Pid, {delete, File}, string); 480 _ -> 481 {error, efnamena} 482 end. 483 484 485%%-------------------------------------------------------------------------- 486%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} 487%% Pid = pid(), 488%% Dir = string() 489%% 490%% Description: Make directory at remote server. 491%%-------------------------------------------------------------------------- 492 493-spec mkdir(Pid :: pid(), Dir :: string()) -> 494 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. 495 496mkdir(Pid, Dir) -> 497 case is_name_sane(Dir) of 498 true -> 499 call(Pid, {mkdir, Dir}, atom); 500 _ -> 501 {error, efnamena} 502 end. 503 504 505%%-------------------------------------------------------------------------- 506%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} 507%% Pid = pid(), 508%% Dir = string() 509%% 510%% Description: Remove directory at remote server. 511%%-------------------------------------------------------------------------- 512 513-spec rmdir(Pid :: pid(), Dir :: string()) -> 514 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. 515 516rmdir(Pid, Dir) -> 517 case is_name_sane(Dir) of 518 true -> 519 call(Pid, {rmdir, Dir}, atom); 520 _ -> 521 {error, efnamena} 522 end. 523 524 525%%-------------------------------------------------------------------------- 526%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn} 527%% Pid = pid() 528%% Type = ascii | binary 529%% 530%% Description: Set transfer type. 531%%-------------------------------------------------------------------------- 532 533-spec type(Pid :: pid(), Type :: ascii | binary) -> 534 'ok' | 535 {'error', Reason :: 'etype' | restriction_reason() | common_reason()}. 536 537type(Pid, Type) -> 538 call(Pid, {type, Type}, atom). 539 540 541%%-------------------------------------------------------------------------- 542%% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} | 543%% {error, elogin} | {error, econn} 544%% Pid = pid() 545%% RemoteFileName = LocalFileName = string() 546%% 547%% Description: Transfer file from remote server. 548%%-------------------------------------------------------------------------- 549 550-spec recv(Pid :: pid(), RemoteFileName :: string()) -> 551 'ok' | {'error', Reason :: restriction_reason() | 552 common_reason() | 553 file_write_error_reason()}. 554 555recv(Pid, RemotFileName) -> 556 recv(Pid, RemotFileName, RemotFileName). 557 558-spec recv(Pid :: pid(), 559 RemoteFileName :: string(), 560 LocalFileName :: string()) -> 561 'ok' | {'error', Reason :: term()}. 562 563recv(Pid, RemotFileName, LocalFileName) -> 564 case is_name_sane(RemotFileName) of 565 true -> 566 call(Pid, {recv, RemotFileName, LocalFileName}, atom); 567 _ -> 568 {error, efnamena} 569 end. 570 571 572%%-------------------------------------------------------------------------- 573%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin} 574%% | {error, econn} 575%% Pid = pid() 576%% RemoteFile = string() 577%% Bin = binary() 578%% 579%% Description: Transfer file from remote server into binary. 580%%-------------------------------------------------------------------------- 581 582-spec recv_bin(Pid :: pid(), 583 RemoteFile :: string()) -> 584 {'ok', Bin :: binary()} | 585 {'error', Reason :: restriction_reason() | common_reason()}. 586 587recv_bin(Pid, RemoteFile) -> 588 case is_name_sane(RemoteFile) of 589 true -> 590 call(Pid, {recv_bin, RemoteFile}, bin); 591 _ -> 592 {error, efnamena} 593 end. 594 595 596%%-------------------------------------------------------------------------- 597%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} 598%% | {error, econn} 599%% Pid = pid() 600%% RemoteFile = string() 601%% 602%% Description: Start receive of chunks of remote file. 603%%-------------------------------------------------------------------------- 604 605-spec recv_chunk_start(Pid :: pid(), 606 RemoteFile :: string()) -> 607 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. 608 609recv_chunk_start(Pid, RemoteFile) -> 610 case is_name_sane(RemoteFile) of 611 true -> 612 call(Pid, {recv_chunk_start, RemoteFile}, atom); 613 _ -> 614 {error, efnamena} 615 end. 616 617 618%%-------------------------------------------------------------------------- 619%% recv_chunk(Pid, RemoteFile) -> ok | {ok, Bin} | {error, Reason} 620%% Pid = pid() 621%% RemoteFile = string() 622%% 623%% Description: Transfer file from remote server into binary in chunks 624%%-------------------------------------------------------------------------- 625 626-spec recv_chunk(Pid :: pid()) -> 627 'ok' | 628 {'ok', Bin :: binary()} | 629 {'error', Reason :: restriction_reason() | common_reason()}. 630 631recv_chunk(Pid) -> 632 call(Pid, recv_chunk, atom). 633 634 635%%-------------------------------------------------------------------------- 636%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} 637%% | {error, elogin} 638%% | {error, econn} 639%% Pid = pid() 640%% LocalFileName = RemotFileName = string() 641%% 642%% Description: Transfer file to remote server. 643%%-------------------------------------------------------------------------- 644 645-spec send(Pid :: pid(), LocalFileName :: string()) -> 646 'ok' | 647 {'error', Reason :: restriction_reason() | 648 common_reason() | 649 shortage_reason()}. 650 651send(Pid, LocalFileName) -> 652 send(Pid, LocalFileName, LocalFileName). 653 654-spec send(Pid :: pid(), 655 LocalFileName :: string(), 656 RemoteFileName :: string()) -> 657 'ok' | 658 {'error', Reason :: restriction_reason() | 659 common_reason() | 660 shortage_reason()}. 661 662send(Pid, LocalFileName, RemotFileName) -> 663 case is_name_sane(RemotFileName) of 664 true -> 665 call(Pid, {send, LocalFileName, RemotFileName}, atom); 666 _ -> 667 {error, efnamena} 668 end. 669 670 671%%-------------------------------------------------------------------------- 672%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} 673%% | {error, enotbinary} | {error, econn} 674%% Pid = pid() 675%% Bin = binary() 676%% RemoteFile = string() 677%% 678%% Description: Transfer a binary to a remote file. 679%%-------------------------------------------------------------------------- 680 681-spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) -> 682 'ok' | 683 {'error', Reason :: restriction_reason() | 684 common_reason() | 685 shortage_reason()}. 686 687send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) -> 688 case is_name_sane(RemoteFile) of 689 true -> 690 call(Pid, {send_bin, Bin, RemoteFile}, atom); 691 _ -> 692 {error, efnamena} 693 end; 694send_bin(_Pid, _Bin, _RemoteFile) -> 695 {error, enotbinary}. 696 697 698%%-------------------------------------------------------------------------- 699%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} 700%% | {error, econn} 701%% Pid = pid() 702%% RemoteFile = string() 703%% 704%% Description: Start transfer of chunks to remote file. 705%%-------------------------------------------------------------------------- 706 707-spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) -> 708 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. 709 710send_chunk_start(Pid, RemoteFile) -> 711 case is_name_sane(RemoteFile) of 712 true -> 713 call(Pid, {send_chunk_start, RemoteFile}, atom); 714 _ -> 715 {error, efnamena} 716 end. 717 718 719%%-------------------------------------------------------------------------- 720%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | 721%% {error, epath} | {error, econn} 722%% Pid = pid() 723%% RemoteFile = string() 724%% 725%% Description: Start append chunks of data to remote file. 726%%-------------------------------------------------------------------------- 727 728-spec append_chunk_start(Pid :: pid(), RemoteFile :: string()) -> 729 'ok' | {'error', Reason :: term()}. 730 731append_chunk_start(Pid, RemoteFile) -> 732 case is_name_sane(RemoteFile) of 733 true -> 734 call(Pid, {append_chunk_start, RemoteFile}, atom); 735 _ -> 736 {error, efnamena} 737 end. 738 739 740%%-------------------------------------------------------------------------- 741%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} 742%% | {error, echunk} | {error, econn} 743%% Pid = pid() 744%% Bin = binary(). 745%% 746%% Purpose: Send chunk to remote file. 747%%-------------------------------------------------------------------------- 748 749-spec send_chunk(Pid :: pid(), Bin :: binary()) -> 750 'ok' | 751 {'error', Reason :: 'echunk' | 752 restriction_reason() | 753 common_reason()}. 754 755send_chunk(Pid, Bin) when is_binary(Bin) -> 756 call(Pid, {transfer_chunk, Bin}, atom); 757send_chunk(_Pid, _Bin) -> 758 {error, enotbinary}. 759 760 761%%-------------------------------------------------------------------------- 762%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} 763%% | {error, echunk} | {error, econn} 764%% Pid = pid() 765%% Bin = binary() 766%% 767%% Description: Append chunk to remote file. 768%%-------------------------------------------------------------------------- 769 770-spec append_chunk(Pid :: pid(), Bin :: binary()) -> 771 'ok' | 772 {'error', Reason :: 'echunk' | 773 restriction_reason() | 774 common_reason()}. 775 776append_chunk(Pid, Bin) when is_binary(Bin) -> 777 call(Pid, {transfer_chunk, Bin}, atom); 778append_chunk(_Pid, _Bin) -> 779 {error, enotbinary}. 780 781 782%%-------------------------------------------------------------------------- 783%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} 784%% | {error, econn} 785%% Pid = pid() 786%% 787%% Description: End sending of chunks to remote file. 788%%-------------------------------------------------------------------------- 789 790-spec send_chunk_end(Pid :: pid()) -> 791 'ok' | 792 {'error', Reason :: restriction_reason() | 793 common_reason() | 794 shortage_reason()}. 795 796send_chunk_end(Pid) -> 797 call(Pid, chunk_end, atom). 798 799 800%%-------------------------------------------------------------------------- 801%% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} 802%% | {error, econn} 803%% Pid = pid() 804%% 805%% Description: End appending of chunks to remote file. 806%%-------------------------------------------------------------------------- 807 808-spec append_chunk_end(Pid :: pid()) -> 809 'ok' | 810 {'error', Reason :: restriction_reason() | 811 common_reason() | 812 shortage_reason()}. 813 814append_chunk_end(Pid) -> 815 call(Pid, chunk_end, atom). 816 817 818%%-------------------------------------------------------------------------- 819%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} 820%% | {error, elogin} 821%% | {error, econn} 822%% Pid = pid() 823%% LocalFileName = RemotFileName = string() 824%% 825%% Description: Append the local file to the remote file 826%%-------------------------------------------------------------------------- 827 828-spec append(Pid :: pid(), LocalFileName :: string()) -> 829 'ok' | 830 {'error', Reason :: 'epath' | 831 'elogin' | 832 'etnospc' | 833 'epnospc' | 834 'efnamena' | common_reason()}. 835 836append(Pid, LocalFileName) -> 837 append(Pid, LocalFileName, LocalFileName). 838 839-spec append(Pid :: pid(), 840 LocalFileName :: string(), 841 RemoteFileName :: string()) -> 842 'ok' | {'error', Reason :: term()}. 843 844append(Pid, LocalFileName, RemotFileName) -> 845 case is_name_sane(RemotFileName) of 846 true -> 847 call(Pid, {append, LocalFileName, RemotFileName}, atom); 848 _ -> 849 {error, efnamena} 850 end. 851 852 853%%-------------------------------------------------------------------------- 854%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} 855%% | {error, enotbinary} | {error, econn} 856%% Pid = pid() 857%% Bin = binary() 858%% RemoteFile = string() 859%% 860%% Purpose: Append a binary to a remote file. 861%%-------------------------------------------------------------------------- 862 863-spec append_bin(Pid :: pid(), 864 Bin :: binary(), 865 RemoteFile :: string()) -> 866 'ok' | 867 {'error', Reason :: restriction_reason() | 868 common_reason() | 869 shortage_reason()}. 870 871append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) -> 872 case is_name_sane(RemoteFile) of 873 true -> 874 call(Pid, {append_bin, Bin, RemoteFile}, atom); 875 _ -> 876 {error, efnamena} 877 end; 878append_bin(_Pid, _Bin, _RemoteFile) -> 879 {error, enotbinary}. 880 881 882%%-------------------------------------------------------------------------- 883%% quote(Pid, Cmd) -> list() 884%% Pid = pid() 885%% Cmd = string() 886%% 887%% Description: Send arbitrary ftp command. 888%%-------------------------------------------------------------------------- 889 890-spec quote(Pid :: pid(), Cmd :: string()) -> list(). 891 892quote(Pid, Cmd) when is_list(Cmd) -> 893 call(Pid, {quote, Cmd}, atom). 894 895 896%%-------------------------------------------------------------------------- 897%% close(Pid) -> ok 898%% Pid = pid() 899%% 900%% Description: End the ftp session. 901%%-------------------------------------------------------------------------- 902 903-spec close(Pid :: pid()) -> 'ok'. 904 905close(Pid) -> 906 cast(Pid, close), 907 ok. 908 909 910%%-------------------------------------------------------------------------- 911%% formaterror(Tag) -> string() 912%% Tag = atom() | {error, atom()} 913%% 914%% Description: Return diagnostics. 915%%-------------------------------------------------------------------------- 916 917-spec formaterror(Tag :: term()) -> string(). 918 919formaterror(Tag) -> 920 ftp_response:error_string(Tag). 921 922 923info(Pid) -> 924 call(Pid, info, list). 925 926 927%%-------------------------------------------------------------------------- 928%% latest_ctrl_response(Pid) -> string() 929%% Pid = pid() 930%% 931%% Description: The latest received response from the server 932%%-------------------------------------------------------------------------- 933 934-spec latest_ctrl_response(Pid :: pid()) -> string(). 935 936latest_ctrl_response(Pid) -> 937 call(Pid, latest_ctrl_response, string). 938 939 940%%%======================================================================== 941%%% gen_server callback functions 942%%%======================================================================== 943 944%%------------------------------------------------------------------------- 945%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} 946%% Description: Initiates the erlang process that manages a ftp connection. 947%%------------------------------------------------------------------------- 948init(Options) -> 949 process_flag(trap_exit, true), 950 951 %% Keep track of the client 952 {value, {client, Client}} = lists:keysearch(client, 1, Options), 953 erlang:monitor(process, Client), 954 955 %% Make sure inet is started 956 _ = inet_db:start(), 957 958 %% Where are we 959 {ok, Dir} = file:get_cwd(), 960 961 %% Maybe activate dbg 962 case key_search(debug, Options, disable) of 963 trace -> 964 dbg:tracer(), 965 dbg:p(all, [call]), 966 {ok, _} = dbg:tpl(ftp, [{'_', [], [{return_trace}]}]), 967 {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]), 968 {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]), 969 ok; 970 debug -> 971 dbg:tracer(), 972 dbg:p(all, [call]), 973 {ok, _} = dbg:tp(ftp, [{'_', [], [{return_trace}]}]), 974 {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]), 975 {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]), 976 ok; 977 _ -> 978 %% Keep silent 979 ok 980 end, 981 982 %% Verbose? 983 Verbose = key_search(verbose, Options, false), 984 985 %% IpFamily? 986 IpFamily = key_search(ipfamily, Options, inet), 987 988 State = #state{owner = Client, 989 verbose = Verbose, 990 ipfamily = IpFamily, 991 ldir = Dir}, 992 993 %% Set process prio 994 Priority = key_search(priority, Options, low), 995 process_flag(priority, Priority), 996 997 %% And we are done 998 {ok, State}. 999 1000 1001%%-------------------------------------------------------------------------- 1002%% handle_call(Request, From, State) -> {reply, Reply, State} | 1003%% {reply, Reply, State, Timeout} | 1004%% {noreply, State} | 1005%% {noreply, State, Timeout} | 1006%% {stop, Reason, Reply, State} | 1007%% Description: Handle incoming requests. 1008%%------------------------------------------------------------------------- 1009 1010%% Anyone can ask this question 1011handle_call({_, info}, _, #state{verbose = Verbose, 1012 mode = Mode, 1013 timeout = Timeout, 1014 ipfamily = IpFamily, 1015 csock = Socket, 1016 progress = Progress} = State) -> 1017 {ok, {_, LocalPort}} = sockname(Socket), 1018 {ok, {Address, Port}} = peername(Socket), 1019 Options = [{verbose, Verbose}, 1020 {ipfamily, IpFamily}, 1021 {mode, Mode}, 1022 {peer, Address}, 1023 {peer_port, Port}, 1024 {local_port, LocalPort}, 1025 {timeout, Timeout}, 1026 {progress, Progress}], 1027 {reply, {ok, Options}, State}; 1028 1029handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = State) -> 1030 {reply, {ok,Resp}, State}; 1031 1032%% But everything else must come from the owner 1033handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid -> 1034 {reply, {error, not_connection_owner}, State}; 1035 1036handle_call({_, {open, ip_comm, Opts, {CtrlOpts, DataPassOpts, DataActOpts}}}, From, State) -> 1037 case key_search(host, Opts, undefined) of 1038 undefined -> 1039 {stop, normal, {error, ehost}, State}; 1040 Host -> 1041 Mode = key_search(mode, Opts, ?DEFAULT_MODE), 1042 Port = key_search(port, Opts, ?FTP_PORT), 1043 Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), 1044 DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), 1045 Progress = key_search(progress, Opts, ignore), 1046 IpFamily = key_search(ipfamily, Opts, inet), 1047 FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), 1048 1049 State2 = State#state{client = From, 1050 mode = Mode, 1051 progress = progress(Progress), 1052 ipfamily = IpFamily, 1053 sockopts_ctrl = CtrlOpts, 1054 sockopts_data_passive = DataPassOpts, 1055 sockopts_data_active = DataActOpts, 1056 dtimeout = DTimeout, 1057 ftp_extension = FtpExt}, 1058 1059 case setup_ctrl_connection(Host, Port, Timeout, State2) of 1060 {ok, State3, WaitTimeout} -> 1061 {noreply, State3, WaitTimeout}; 1062 {error, _Reason} -> 1063 gen_server:reply(From, {error, ehost}), 1064 {stop, normal, State2#state{client = undefined}} 1065 end 1066 end; 1067 1068handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State0) -> 1069 _ = send_ctrl_message(State0, mk_cmd("AUTH TLS", [])), 1070 State = activate_ctrl_connection(State0), 1071 {noreply, State#state{client = From, caller = open, tls_options = TLSOptions}}; 1072 1073handle_call({_, {user, User, Password}}, From, 1074 #state{csock = CSock} = State) when (CSock =/= undefined) -> 1075 handle_user(User, Password, "", State#state{client = From}); 1076 1077handle_call({_, {user, User, Password, Acc}}, From, 1078 #state{csock = CSock} = State) when (CSock =/= undefined) -> 1079 handle_user(User, Password, Acc, State#state{client = From}); 1080 1081handle_call({_, {account, Acc}}, From, State)-> 1082 handle_user_account(Acc, State#state{client = From}); 1083 1084handle_call({_, pwd}, From, #state{chunk = false} = State0) -> 1085 _ = send_ctrl_message(State0, mk_cmd("PWD", [])), 1086 State = activate_ctrl_connection(State0), 1087 {noreply, State#state{client = From, caller = pwd}}; 1088 1089handle_call({_, lpwd}, From, #state{ldir = LDir} = State) -> 1090 {reply, {ok, LDir}, State#state{client = From}}; 1091 1092handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State0) -> 1093 _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])), 1094 State = activate_ctrl_connection(State0), 1095 {noreply, State#state{client = From, caller = cd}}; 1096 1097handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) -> 1098 LDir = filename:absname(Dir, LDir0), 1099 case file:read_file_info(LDir) of %% FIX better check that LDir is a dir. 1100 {ok, _ } -> 1101 {reply, ok, State#state{ldir = LDir}}; 1102 _ -> 1103 {reply, {error, epath}, State} 1104 end; 1105 1106handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From, 1107 #state{chunk = false} = State) -> 1108 setup_data_connection(State#state{caller = {dir, Dir, Len}, 1109 client = From}); 1110handle_call({_, {rename, CurrFile, NewFile}}, From, 1111 #state{chunk = false} = State0) -> 1112 _ = send_ctrl_message(State0, mk_cmd("RNFR ~s", [CurrFile])), 1113 State = activate_ctrl_connection(State0), 1114 {noreply, State#state{caller = {rename, NewFile}, client = From}}; 1115 1116handle_call({_, {delete, File}}, {_Pid, _} = From, 1117 #state{chunk = false} = State0) -> 1118 _ = send_ctrl_message(State0, mk_cmd("DELE ~s", [File])), 1119 State = activate_ctrl_connection(State0), 1120 {noreply, State#state{client = From}}; 1121 1122handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State0) -> 1123 _ = send_ctrl_message(State0, mk_cmd("MKD ~s", [Dir])), 1124 State = activate_ctrl_connection(State0), 1125 {noreply, State#state{client = From}}; 1126 1127handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State0) -> 1128 _ = send_ctrl_message(State0, mk_cmd("RMD ~s", [Dir])), 1129 State = activate_ctrl_connection(State0), 1130 {noreply, State#state{client = From}}; 1131 1132handle_call({_,{type, Type}}, From, #state{chunk = false} = State0) -> 1133 case Type of 1134 ascii -> 1135 _ = send_ctrl_message(State0, mk_cmd("TYPE A", [])), 1136 State = activate_ctrl_connection(State0), 1137 {noreply, State#state{caller = type, type = ascii, 1138 client = From}}; 1139 binary -> 1140 _ = send_ctrl_message(State0, mk_cmd("TYPE I", [])), 1141 State = activate_ctrl_connection(State0), 1142 {noreply, State#state{caller = type, type = binary, 1143 client = From}}; 1144 _ -> 1145 {reply, {error, etype}, State0} 1146 end; 1147 1148handle_call({_,{recv, RemoteFile, LocalFile}}, From, 1149 #state{chunk = false, ldir = LocalDir} = State) -> 1150 progress_report({remote_file, RemoteFile}, State), 1151 NewLocalFile = filename:absname(LocalFile, LocalDir), 1152 1153 case file_open(NewLocalFile, write) of 1154 {ok, Fd} -> 1155 setup_data_connection(State#state{client = From, 1156 caller = 1157 {recv_file, 1158 RemoteFile, Fd}}); 1159 {error, _What} -> 1160 {reply, {error, epath}, State} 1161 end; 1162 1163handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} = 1164 State) -> 1165 setup_data_connection(State#state{caller = {recv_bin, RemoteFile}, 1166 client = From}); 1167 1168handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false} 1169 = State) -> 1170 setup_data_connection(State#state{caller = {start_chunk_transfer, 1171 "RETR", RemoteFile}, 1172 client = From}); 1173 1174handle_call({_, recv_chunk}, _, #state{chunk = false} = State) -> 1175 {reply, {error, "ftp:recv_chunk_start/2 not called"}, State}; 1176 1177handle_call({_, recv_chunk}, _From, #state{chunk = true, 1178 caller = #recv_chunk_closing{dconn_closed = true, 1179 pos_compl_received = true 1180 } 1181 } = State0) -> 1182 %% The ftp:recv_chunk call was the last event we waited for, finnish and clean up 1183 ?DBG("recv_chunk_closing ftp:recv_chunk, last event",[]), 1184 State = activate_ctrl_connection(State0), 1185 {reply, ok, State#state{caller = undefined, 1186 chunk = false, 1187 client = undefined}}; 1188 1189handle_call({_, recv_chunk}, From, #state{chunk = true, 1190 caller = #recv_chunk_closing{} = R 1191 } = State) -> 1192 %% Waiting for more, don't care what 1193 ?DBG("recv_chunk_closing ftp:recv_chunk, get more",[]), 1194 {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}}; 1195 1196handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) -> 1197 State = activate_data_connection(State0), 1198 {noreply, State#state{client = From, caller = recv_chunk}}; 1199 1200handle_call({_, {send, LocalFile, RemoteFile}}, From, 1201 #state{chunk = false, ldir = LocalDir} = State) -> 1202 progress_report({local_file, filename:absname(LocalFile, LocalDir)}, 1203 State), 1204 setup_data_connection(State#state{caller = {transfer_file, 1205 {"STOR", 1206 LocalFile, RemoteFile}}, 1207 client = From}); 1208handle_call({_, {append, LocalFile, RemoteFile}}, From, 1209 #state{chunk = false} = State) -> 1210 setup_data_connection(State#state{caller = {transfer_file, 1211 {"APPE", 1212 LocalFile, RemoteFile}}, 1213 client = From}); 1214handle_call({_, {send_bin, Bin, RemoteFile}}, From, 1215 #state{chunk = false} = State) -> 1216 setup_data_connection(State#state{caller = {transfer_data, 1217 {"STOR", Bin, RemoteFile}}, 1218 client = From}); 1219handle_call({_,{append_bin, Bin, RemoteFile}}, From, 1220 #state{chunk = false} = State) -> 1221 setup_data_connection(State#state{caller = {transfer_data, 1222 {"APPE", Bin, RemoteFile}}, 1223 client = From}); 1224handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false} 1225 = State) -> 1226 setup_data_connection(State#state{caller = {start_chunk_transfer, 1227 "STOR", RemoteFile}, 1228 client = From}); 1229handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false} 1230 = State) -> 1231 setup_data_connection(State#state{caller = {start_chunk_transfer, 1232 "APPE", RemoteFile}, 1233 client = From}); 1234handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) -> 1235 send_data_message(State, Bin), 1236 {reply, ok, State}; 1237 1238handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) -> 1239 {reply, {error, echunk}, State}; 1240 1241handle_call({_, chunk_end}, From, #state{chunk = true} = State0) -> 1242 close_data_connection(State0), 1243 State = activate_ctrl_connection(State0), 1244 {noreply, State#state{client = From, dsock = undefined, 1245 caller = end_chunk_transfer, chunk = false}}; 1246 1247handle_call({_, chunk_end}, _, #state{chunk = false} = State) -> 1248 {reply, {error, echunk}, State}; 1249 1250handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State0) -> 1251 _ = send_ctrl_message(State0, mk_cmd(Cmd, [])), 1252 State = activate_ctrl_connection(State0), 1253 {noreply, State#state{client = From, caller = quote}}; 1254 1255handle_call({_, _Req}, _From, #state{csock = CSock} = State) 1256 when (CSock =:= undefined) -> 1257 {reply, {error, not_connected}, State}; 1258 1259handle_call(_, _, #state{chunk = true} = State) -> 1260 {reply, {error, echunk}, State}; 1261 1262%% Catch all - This can only happen if the application programmer writes 1263%% really bad code that violates the API. 1264handle_call(Request, _Timeout, State) -> 1265 {stop, {'API_violation_connection_closed', Request}, 1266 {error, {connection_terminated, 'API_violation'}}, State}. 1267 1268%%-------------------------------------------------------------------------- 1269%% handle_cast(Request, State) -> {noreply, State} | 1270%% {noreply, State, Timeout} | 1271%% {stop, Reason, State} 1272%% Description: Handles cast messages. 1273%%------------------------------------------------------------------------- 1274handle_cast({Pid, close}, #state{owner = Pid} = State) -> 1275 _ = send_ctrl_message(State, mk_cmd("QUIT", [])), 1276 close_ctrl_connection(State), 1277 close_data_connection(State), 1278 {stop, normal, State#state{csock = undefined, dsock = undefined}}; 1279 1280handle_cast({Pid, close}, State) -> 1281 Report = io_lib:format("A none owner process ~p tried to close an " 1282 "ftp connection: ~n", [Pid]), 1283 error_logger:info_report(Report), 1284 {noreply, State}; 1285 1286%% Catch all - This can oly happen if the application programmer writes 1287%% really bad code that violates the API. 1288handle_cast(Msg, State) -> 1289 {stop, {'API_violation_connection_closed', Msg}, State}. 1290 1291%%-------------------------------------------------------------------------- 1292%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} | 1293%% {stop, Reason, State} 1294%% Description: Handles tcp messages from the ftp-server. 1295%% Note: The order of the function clauses is significant. 1296%%-------------------------------------------------------------------------- 1297 1298handle_info(timeout, #state{caller = open} = State) -> 1299 {stop, timeout, State}; 1300 1301handle_info(timeout, State) -> 1302 {noreply, State}; 1303 1304%%% Data socket messages %%% 1305handle_info({Trpt, Socket, Data}, 1306 #state{dsock = {Trpt,Socket}, 1307 caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl -> 1308 ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]), 1309 ok = file_write(binary_to_list(Data), Fd), 1310 progress_report({binary, Data}, State0), 1311 State = activate_data_connection(State0), 1312 {noreply, State}; 1313 1314handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From, 1315 caller = recv_chunk} 1316 = State) when Trpt==tcp;Trpt==ssl -> 1317 ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State]), 1318 gen_server:reply(From, {ok, Data}), 1319 {noreply, State#state{client = undefined, data = <<>>}}; 1320 1321handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when Trpt==tcp;Trpt==ssl -> 1322 ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]), 1323 State = activate_data_connection(State0), 1324 {noreply, State#state{data = <<(State#state.data)/binary, 1325 Data/binary>>}}; 1326 1327handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, 1328 caller = {recv_file, Fd}} = State0) 1329 when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> 1330 file_close(Fd), 1331 progress_report({transfer_size, 0}, State0), 1332 State = activate_ctrl_connection(State0), 1333 ?DBG("Data channel close",[]), 1334 {noreply, State#state{dsock = undefined, data = <<>>}}; 1335 1336handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, 1337 client = Client, 1338 caller = recv_chunk} = State0) 1339 when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> 1340 ?DBG("Data channel close recv_chunk",[]), 1341 State = activate_ctrl_connection(State0), 1342 {noreply, State#state{dsock = undefined, 1343 caller = #recv_chunk_closing{dconn_closed = true, 1344 client_called_us = Client =/= undefined} 1345 }}; 1346 1347handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin, 1348 data = Data} = State0) 1349 when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> 1350 ?DBG("Data channel close",[]), 1351 State = activate_ctrl_connection(State0), 1352 {noreply, State#state{dsock = undefined, data = <<>>, 1353 caller = {recv_bin, Data}}}; 1354 1355handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data, 1356 caller = {handle_dir_result, Dir}} 1357 = State0) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> 1358 ?DBG("Data channel close",[]), 1359 State = activate_ctrl_connection(State0), 1360 {noreply, State#state{dsock = undefined, 1361 caller = {handle_dir_result, Dir, Data}, 1362% data = <<?CR,?LF>>}}; 1363 data = <<>>}}; 1364 1365handle_info({Err, Socket, Reason}, #state{dsock = {Trpt,Socket}, 1366 client = From} = State) 1367 when {Err,Trpt}=={tcp_error,tcp} ; {Err,Trpt}=={ssl_error,ssl} -> 1368 gen_server:reply(From, {error, Reason}), 1369 close_data_connection(State), 1370 {noreply, State#state{dsock = undefined, client = undefined, 1371 data = <<>>, caller = undefined, chunk = false}}; 1372 1373%%% Ctrl socket messages %%% 1374handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket}, 1375 verbose = Verbose, 1376 caller = Caller, 1377 client = From, 1378 ctrl_data = {BinCtrlData, AccLines, 1379 LineStatus}} 1380 = State0) -> 1381 ?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<<BinCtrlData/binary, Data/binary>>,State]), 1382 case ftp_response:parse_lines(<<BinCtrlData/binary, Data/binary>>, 1383 AccLines, LineStatus) of 1384 {ok, Lines, NextMsgData} -> 1385 verbose(Lines, Verbose, 'receive'), 1386 CtrlResult = ftp_response:interpret(Lines), 1387 case Caller of 1388 quote -> 1389 gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])), 1390 {noreply, State0#state{client = undefined, 1391 caller = undefined, 1392 latest_ctrl_response = Lines, 1393 ctrl_data = {NextMsgData, [], 1394 start}}}; 1395 _ -> 1396 ?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]), 1397 handle_ctrl_result(CtrlResult, 1398 State0#state{latest_ctrl_response = Lines, 1399 ctrl_data = 1400 {NextMsgData, [], start}}) 1401 end; 1402 {continue, CtrlData} when CtrlData =/= State0#state.ctrl_data -> 1403 ?DBG(' ...Continue... ctrl_data=~p~n',[CtrlData]), 1404 State1 = State0#state{ctrl_data = CtrlData}, 1405 State = activate_ctrl_connection(State1), 1406 {noreply, State}; 1407 {continue, CtrlData} -> 1408 ?DBG(' ...Continue... ctrl_data=~p~n',[CtrlData]), 1409 {noreply, State0} 1410 end; 1411 1412%% If the server closes the control channel it is 1413%% the expected behavior that connection process terminates. 1414handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}}) 1415 when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> 1416 exit(normal); %% User will get error message from terminate/2 1417 1418handle_info({Err, Socket, Reason}, _) when Err==tcp_error ; Err==ssl_error -> 1419 Report = 1420 io_lib:format("~p on socket: ~p for reason: ~p~n", 1421 [Err, Socket, Reason]), 1422 error_logger:error_report(Report), 1423 %% If tcp does not work the only option is to terminate, 1424 %% this is the expected behavior under these circumstances. 1425 exit(normal); %% User will get error message from terminate/2 1426 1427%% Monitor messages - if the process owning the ftp connection goes 1428%% down there is no point in continuing. 1429handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) -> 1430 {stop, normal, State#state{client = undefined}}; 1431 1432handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) -> 1433 {stop, normal, State#state{client = undefined}}; 1434 1435handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) -> 1436 {stop, normal, State#state{client = undefined}}; 1437 1438handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) -> 1439 {stop, {stopped, {'EXIT', Process, Reason}}, 1440 State#state{client = undefined}}; 1441 1442handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) -> 1443 Report = io_lib:format("Progress reporting stopped for reason ~p~n", 1444 [Reason]), 1445 error_logger:info_report(Report), 1446 {noreply, State#state{progress = ignore}}; 1447 1448%% Catch all - throws away unknown messages (This could happen by "accident" 1449%% so we do not want to crash, but we make a log entry as it is an 1450%% unwanted behaviour.) 1451handle_info(Info, State) -> 1452 Report = io_lib:format("ftp : ~p : Unexpected message: ~p~nState: ~p~n", 1453 [self(), Info, State]), 1454 error_logger:info_report(Report), 1455 {noreply, State}. 1456 1457%%-------------------------------------------------------------------------- 1458%% terminate/2 and code_change/3 1459%%-------------------------------------------------------------------------- 1460terminate(normal, State) -> 1461 %% If terminate reason =/= normal the progress reporting process will 1462 %% be killed by the exit signal. 1463 progress_report(stop, State), 1464 do_terminate({error, econn}, State); 1465terminate(Reason, State) -> 1466 Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]), 1467 error_logger:error_report(Report), 1468 do_terminate({error, eclosed}, State). 1469 1470do_terminate(ErrorMsg, State) -> 1471 close_data_connection(State), 1472 close_ctrl_connection(State), 1473 case State#state.client of 1474 undefined -> 1475 ok; 1476 From -> 1477 gen_server:reply(From, ErrorMsg) 1478 end, 1479 ok. 1480 1481code_change(_Vsn, State1, upgrade_from_pre_5_12) -> 1482 {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, 1483 Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1, 1484 IpFamily = 1485 if 1486 (IPv6Disable =:= true) -> 1487 inet; 1488 true -> 1489 inet6fb4 1490 end, 1491 State2 = #state{csock = CSock, 1492 dsock = DSock, 1493 verbose = Verbose, 1494 ldir = LDir, 1495 type = Type, 1496 chunk = Chunk, 1497 mode = Mode, 1498 timeout = Timeout, 1499 data = Data, 1500 ctrl_data = CtrlData, 1501 owner = Owner, 1502 client = Client, 1503 caller = Caller, 1504 ipfamily = IpFamily, 1505 progress = Progress}, 1506 {ok, State2}; 1507 1508code_change(_Vsn, State1, downgrade_to_pre_5_12) -> 1509 #state{csock = CSock, 1510 dsock = DSock, 1511 verbose = Verbose, 1512 ldir = LDir, 1513 type = Type, 1514 chunk = Chunk, 1515 mode = Mode, 1516 timeout = Timeout, 1517 data = Data, 1518 ctrl_data = CtrlData, 1519 owner = Owner, 1520 client = Client, 1521 caller = Caller, 1522 ipfamily = IpFamily, 1523 progress = Progress} = State1, 1524 IPv6Disable = 1525 if 1526 (IpFamily =:= inet) -> 1527 true; 1528 true -> 1529 false 1530 end, 1531 State2 = 1532 {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, 1533 Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress}, 1534 {ok, State2}; 1535 1536code_change(_Vsn, State, _Extra) -> 1537 {ok, State}. 1538 1539 1540%%%========================================================================= 1541%% Start/stop 1542%%%========================================================================= 1543%%-------------------------------------------------------------------------- 1544%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason} 1545%% 1546%% Description: Callback function for the ftp supervisor. It is called 1547%% : when start_service/1 calls ftp_sup:start_child/1 to start an 1548%% : instance of the ftp process. Also called by start_standalone/1 1549%%-------------------------------------------------------------------------- 1550start_link([Opts, GenServerOptions]) -> 1551 start_link(Opts, GenServerOptions). 1552 1553start_link(Opts, GenServerOptions) -> 1554 case lists:keysearch(client, 1, Opts) of 1555 {value, _} -> 1556 %% Via the supervisor 1557 gen_server:start_link(?MODULE, Opts, GenServerOptions); 1558 false -> 1559 Opts2 = [{client, self()} | Opts], 1560 gen_server:start_link(?MODULE, Opts2, GenServerOptions) 1561 end. 1562 1563 1564%%% Stop functionality is handled by close/1 1565 1566%%%======================================================================== 1567%%% Internal functions 1568%%%======================================================================== 1569 1570%%-------------------------------------------------------------------------- 1571%%% Help functions to handle_call and/or handle_ctrl_result 1572%%-------------------------------------------------------------------------- 1573%% User handling 1574handle_user(User, Password, Acc, State0) -> 1575 _ = send_ctrl_message(State0, mk_cmd("USER ~s", [User])), 1576 State = activate_ctrl_connection(State0), 1577 {noreply, State#state{caller = {handle_user, Password, Acc}}}. 1578 1579handle_user_passwd(Password, Acc, State0) -> 1580 _ = send_ctrl_message(State0, mk_cmd("PASS ~s", [Password])), 1581 State = activate_ctrl_connection(State0), 1582 {noreply, State#state{caller = {handle_user_passwd, Acc}}}. 1583 1584handle_user_account(Acc, State0) -> 1585 _ = send_ctrl_message(State0, mk_cmd("ACCT ~s", [Acc])), 1586 State = activate_ctrl_connection(State0), 1587 {noreply, State#state{caller = handle_user_account}}. 1588 1589 1590%%-------------------------------------------------------------------------- 1591%% handle_ctrl_result 1592%%-------------------------------------------------------------------------- 1593handle_ctrl_result({tls_upgrade, _}, #state{csock = {tcp, Socket}, 1594 tls_options = TLSOptions, 1595 timeout = Timeout, 1596 caller = open, client = From} 1597 = State0) -> 1598 ?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]), 1599 case ssl:connect(Socket, TLSOptions, Timeout) of 1600 {ok, TLSSocket} -> 1601 State1 = State0#state{csock = {ssl,TLSSocket}}, 1602 _ = send_ctrl_message(State1, mk_cmd("PBSZ 0", [])), 1603 State = activate_ctrl_connection(State1), 1604 {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}} }; 1605 {error, _} = Error -> 1606 gen_server:reply(From, {Error, self()}), 1607 {stop, normal, State0#state{client = undefined, 1608 caller = undefined, 1609 tls_upgrading_data_connection = false}} 1610 end; 1611 1612handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State0) -> 1613 _ = send_ctrl_message(State0, mk_cmd("PROT P", [])), 1614 State = activate_ctrl_connection(State0), 1615 {noreply, State#state{tls_upgrading_data_connection = {true, prot}}}; 1616 1617handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot}, 1618 client = From} = State) -> 1619 gen_server:reply(From, {ok, self()}), 1620 {noreply, State#state{client = undefined, 1621 caller = undefined, 1622 tls_upgrading_data_connection = false}}; 1623 1624handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From} 1625 = State) -> 1626 gen_server:reply(From, {ok, self()}), 1627 {noreply, State#state{client = undefined, 1628 caller = undefined }}; 1629handle_ctrl_result({_, Lines}, #state{caller = open} = State) -> 1630 ctrl_result_response(econn, State, {error, Lines}); 1631 1632%%-------------------------------------------------------------------------- 1633%% Data connection setup active mode 1634handle_ctrl_result({pos_compl, _Lines}, 1635 #state{mode = active, 1636 caller = {setup_data_connection, 1637 {LSock, Caller}}} = State) -> 1638 handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}}); 1639 1640handle_ctrl_result({Status, _Lines}, 1641 #state{mode = active, 1642 caller = {setup_data_connection, {LSock, _}}} 1643 = State) -> 1644 close_connection({tcp,LSock}), 1645 ctrl_result_response(Status, State, {error, Status}); 1646 1647%% Data connection setup passive mode 1648handle_ctrl_result({pos_compl, Lines}, 1649 #state{mode = passive, 1650 ipfamily = inet6, 1651 client = From, 1652 caller = {setup_data_connection, Caller}, 1653 csock = CSock, 1654 sockopts_data_passive = SockOpts, 1655 timeout = Timeout} 1656 = State) -> 1657 [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), 1658 {ok, {IP, _}} = peername(CSock), 1659 case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of 1660 {ok, _, Socket} -> 1661 handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); 1662 {error, _Reason} = Error -> 1663 gen_server:reply(From, Error), 1664 {noreply, State#state{client = undefined, caller = undefined}} 1665 end; 1666 1667handle_ctrl_result({pos_compl, Lines}, 1668 #state{mode = passive, 1669 ipfamily = inet, 1670 client = From, 1671 caller = {setup_data_connection, Caller}, 1672 timeout = Timeout, 1673 sockopts_data_passive = SockOpts, 1674 ftp_extension = false} = State) -> 1675 1676 {_, [?LEFT_PAREN | Rest]} = 1677 lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines), 1678 {NewPortAddr, _} = 1679 lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest), 1680 [A1, A2, A3, A4, P1, P2] = 1681 lists:map(fun(X) -> list_to_integer(X) end, 1682 string:tokens(NewPortAddr, [$,])), 1683 IP = {A1, A2, A3, A4}, 1684 Port = (P1 * 256) + P2, 1685 1686 ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]), 1687 case connect(IP, Port, SockOpts, Timeout, State) of 1688 {ok, _, Socket} -> 1689 handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}}); 1690 {error, _Reason} = Error -> 1691 gen_server:reply(From, Error), 1692 {noreply,State#state{client = undefined, caller = undefined}} 1693 end; 1694 1695handle_ctrl_result({pos_compl, Lines}, 1696 #state{mode = passive, 1697 ipfamily = inet, 1698 client = From, 1699 caller = {setup_data_connection, Caller}, 1700 csock = CSock, 1701 timeout = Timeout, 1702 sockopts_data_passive = SockOpts, 1703 ftp_extension = true} = State) -> 1704 1705 [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), 1706 {ok, {IP, _}} = peername(CSock), 1707 1708 ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]), 1709 case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of 1710 {ok, _, Socket} -> 1711 handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); 1712 {error, _Reason} = Error -> 1713 gen_server:reply(From, Error), 1714 {noreply, State#state{client = undefined, caller = undefined}} 1715 end; 1716 1717 1718%% FTP server does not support passive mode: try to fallback on active mode 1719handle_ctrl_result(_, 1720 #state{mode = passive, 1721 caller = {setup_data_connection, Caller}} = State) -> 1722 setup_data_connection(State#state{mode = active, caller = Caller}); 1723 1724 1725%%-------------------------------------------------------------------------- 1726%% User handling 1727handle_ctrl_result({pos_interm, _}, 1728 #state{caller = {handle_user, PassWord, Acc}} = State) -> 1729 handle_user_passwd(PassWord, Acc, State); 1730handle_ctrl_result({Status, _}, 1731 #state{caller = {handle_user, _, _}} = State) -> 1732 ctrl_result_response(Status, State, {error, euser}); 1733 1734%% Accounts 1735handle_ctrl_result({pos_interm_acct, _}, 1736 #state{caller = {handle_user_passwd, Acc}} = State) 1737 when Acc =/= "" -> 1738 handle_user_account(Acc, State); 1739handle_ctrl_result({Status, _}, 1740 #state{caller = {handle_user_passwd, _}} = State) -> 1741 ctrl_result_response(Status, State, {error, euser}); 1742 1743%%-------------------------------------------------------------------------- 1744%% Print current working directory 1745handle_ctrl_result({pos_compl, Lines}, 1746 #state{caller = pwd, client = From} = State) -> 1747 Dir = pwd_result(Lines), 1748 gen_server:reply(From, {ok, Dir}), 1749 {noreply, State#state{client = undefined, caller = undefined}}; 1750 1751%%-------------------------------------------------------------------------- 1752%% Directory listing 1753handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) -> 1754 case accept_data_connection(State0) of 1755 {ok, State1} -> 1756 State = activate_data_connection(State1), 1757 {noreply, State#state{caller = {handle_dir_result, Dir}}}; 1758 {error, _Reason} = ERROR -> 1759 case State0#state.client of 1760 undefined -> 1761 {stop, ERROR, State0}; 1762 From -> 1763 gen_server:reply(From, ERROR), 1764 {stop, normal, State0#state{client = undefined}} 1765 end 1766 end; 1767 1768handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir, 1769 Data}, client = From} 1770 = State) -> 1771 case Dir of 1772 "" -> % Current directory 1773 gen_server:reply(From, {ok, Data}), 1774 {noreply, State#state{client = undefined, 1775 caller = undefined}}; 1776 _ -> 1777 %% <WTF> 1778 %% Dir cannot be assumed to be a dir. It is a string that 1779 %% could be a dir, but could also be a file or even a string 1780 %% containing wildcards (*). 1781 %% 1782 %% %% If there is only one line it might be a directory with one 1783 %% %% file but it might be an error message that the directory 1784 %% %% was not found. So in this case we have to endure a little 1785 %% %% overhead to be able to give a good return value. Alas not 1786 %% %% all ftp implementations behave the same and returning 1787 %% %% an error string is allowed by the FTP RFC. 1788 %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end, 1789 %% binary_to_list(Data)) of 1790 %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) -> 1791 %% send_ctrl_message(State, mk_cmd("PWD", [])), 1792 %% activate_ctrl_connection(State), 1793 %% {noreply, 1794 %% State#state{caller = {handle_dir_data, Dir, Data}}}; 1795 %% _ -> 1796 %% gen_server:reply(From, {ok, Data}), 1797 %% {noreply, State#state{client = undefined, 1798 %% caller = undefined}} 1799 %% end 1800 %% </WTF> 1801 gen_server:reply(From, {ok, Data}), 1802 {noreply, State#state{client = undefined, 1803 caller = undefined}} 1804 end; 1805 1806handle_ctrl_result({pos_compl, Lines}, 1807 #state{caller = {handle_dir_data, Dir, DirData}} = 1808 State0) -> 1809 OldDir = pwd_result(Lines), 1810 _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])), 1811 State = activate_ctrl_connection(State0), 1812 {noreply, State#state{caller = {handle_dir_data_second_phase, OldDir, 1813 DirData}}}; 1814handle_ctrl_result({Status, _}, 1815 #state{caller = {handle_dir_data, _, _}} = State) -> 1816 ctrl_result_response(Status, State, {error, epath}); 1817 1818handle_ctrl_result(S={_Status, _}, 1819 #state{caller = {handle_dir_result, _, _}} = State) -> 1820 %% OTP-5731, macosx 1821 ctrl_result_response(S, State, {error, epath}); 1822 1823handle_ctrl_result({pos_compl, _}, 1824 #state{caller = {handle_dir_data_second_phase, OldDir, 1825 DirData}} = State0) -> 1826 _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [OldDir])), 1827 State = activate_ctrl_connection(State0), 1828 {noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}}; 1829handle_ctrl_result({Status, _}, 1830 #state{caller = {handle_dir_data_second_phase, _, _}} 1831 = State) -> 1832 ctrl_result_response(Status, State, {error, epath}); 1833handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData}, 1834 client = From} = State) -> 1835 gen_server:reply(From, {ok, DirData}), 1836 {noreply, State#state{client = undefined, caller = undefined}}; 1837 1838handle_ctrl_result({Status, _}, #state{caller = cd} = State) -> 1839 ctrl_result_response(Status, State, {error, Status}); 1840 1841handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) -> 1842 ctrl_result_response(Status, State, {error, epath}); 1843 1844%%-------------------------------------------------------------------------- 1845%% File renaming 1846handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}} 1847 = State0) -> 1848 _ = send_ctrl_message(State0, mk_cmd("RNTO ~s", [NewFile])), 1849 State = activate_ctrl_connection(State0), 1850 {noreply, State#state{caller = rename_second_phase}}; 1851 1852handle_ctrl_result({Status, _}, 1853 #state{caller = {rename, _}} = State) -> 1854 ctrl_result_response(Status, State, {error, Status}); 1855 1856handle_ctrl_result({Status, _}, 1857 #state{caller = rename_second_phase} = State) -> 1858 ctrl_result_response(Status, State, {error, Status}); 1859 1860%%-------------------------------------------------------------------------- 1861%% File handling - recv_bin 1862handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) -> 1863 case accept_data_connection(State0) of 1864 {ok, State1} -> 1865 State = activate_data_connection(State1), 1866 {noreply, State}; 1867 {error, _Reason} = ERROR -> 1868 case State0#state.client of 1869 undefined -> 1870 {stop, ERROR, State0}; 1871 From -> 1872 gen_server:reply(From, ERROR), 1873 {stop, normal, State0#state{client = undefined}} 1874 end 1875 end; 1876 1877handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data}, 1878 client = From} = State) -> 1879 gen_server:reply(From, {ok, Data}), 1880 close_data_connection(State), 1881 {noreply, State#state{client = undefined, caller = undefined}}; 1882 1883handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) -> 1884 close_data_connection(State), 1885 ctrl_result_response(Status, State#state{dsock = undefined}, 1886 {error, epath}); 1887 1888handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) -> 1889 close_data_connection(State), 1890 ctrl_result_response(Status, State#state{dsock = undefined}, 1891 {error, epath}); 1892%%-------------------------------------------------------------------------- 1893%% File handling - start_chunk_transfer 1894handle_ctrl_result({pos_prel, _}, #state{client = From, 1895 caller = start_chunk_transfer} 1896 = State0) -> 1897 case accept_data_connection(State0) of 1898 {ok, State1} -> 1899 State = start_chunk(State1), 1900 {noreply, State}; 1901 {error, _Reason} = ERROR -> 1902 case State0#state.client of 1903 undefined -> 1904 {stop, ERROR, State0}; 1905 From -> 1906 gen_server:reply(From, ERROR), 1907 {stop, normal, State0#state{client = undefined}} 1908 end 1909 end; 1910 1911%%-------------------------------------------------------------------------- 1912%% File handling - chunk_transfer complete 1913 1914handle_ctrl_result({pos_compl, _}, #state{client = From, 1915 caller = #recv_chunk_closing{dconn_closed = true, 1916 client_called_us = true, 1917 pos_compl_received = false 1918 }} 1919 = State0) when From =/= undefined -> 1920 %% The pos_compl was the last event we waited for, finnish and clean up 1921 ?DBG("recv_chunk_closing pos_compl, last event",[]), 1922 gen_server:reply(From, ok), 1923 State = activate_ctrl_connection(State0), 1924 {noreply, State#state{caller = undefined, 1925 chunk = false, 1926 client = undefined}}; 1927 1928handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R} 1929 = State0) -> 1930 %% Waiting for more, don't care what 1931 ?DBG("recv_chunk_closing pos_compl, wait more",[]), 1932 {noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}}; 1933 1934 1935%%-------------------------------------------------------------------------- 1936%% File handling - recv_file 1937handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) -> 1938 case accept_data_connection(State0) of 1939 {ok, State1} -> 1940 State = activate_data_connection(State1), 1941 {noreply, State}; 1942 {error, _Reason} = ERROR -> 1943 case State0#state.client of 1944 undefined -> 1945 {stop, ERROR, State0}; 1946 From -> 1947 gen_server:reply(From, ERROR), 1948 {stop, normal, State0#state{client = undefined}} 1949 end 1950 end; 1951 1952handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) -> 1953 file_close(Fd), 1954 close_data_connection(State), 1955 ctrl_result_response(Status, State#state{dsock = undefined}, 1956 {error, epath}); 1957%%-------------------------------------------------------------------------- 1958%% File handling - transfer_* 1959handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}} 1960 = State0) -> 1961 case accept_data_connection(State0) of 1962 {ok, State1} -> 1963 send_file(State1, Fd); 1964 {error, _Reason} = ERROR -> 1965 case State0#state.client of 1966 undefined -> 1967 {stop, ERROR, State0}; 1968 From -> 1969 gen_server:reply(From, ERROR), 1970 {stop, normal, State0#state{client = undefined}} 1971 end 1972 end; 1973 1974handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} 1975 = State0) -> 1976 case accept_data_connection(State0) of 1977 {ok, State} -> 1978 send_bin(State, Bin); 1979 {error, _Reason} = ERROR -> 1980 case State0#state.client of 1981 undefined -> 1982 {stop, ERROR, State0}; 1983 From -> 1984 gen_server:reply(From, ERROR), 1985 {stop, normal, State0#state{client = undefined}} 1986 end 1987 end; 1988 1989%%-------------------------------------------------------------------------- 1990%% Default 1991handle_ctrl_result({Status, _Lines}, #state{client = From} = State) 1992 when From =/= undefined -> 1993 ctrl_result_response(Status, State, {error, Status}). 1994 1995%%-------------------------------------------------------------------------- 1996%% Help functions to handle_ctrl_result 1997%%-------------------------------------------------------------------------- 1998ctrl_result_response(pos_compl, #state{client = From} = State, _) -> 1999 gen_server:reply(From, ok), 2000 {noreply, State#state{client = undefined, caller = undefined}}; 2001 2002ctrl_result_response(enofile, #state{client = From} = State, _) -> 2003 gen_server:reply(From, {error, enofile}), 2004 {noreply, State#state{client = undefined, caller = undefined}}; 2005 2006ctrl_result_response(Status, #state{client = From} = State, _) 2007 when (Status =:= etnospc) orelse 2008 (Status =:= epnospc) orelse 2009 (Status =:= efnamena) orelse 2010 (Status =:= econn) -> 2011 gen_server:reply(From, {error, Status}), 2012%% {stop, normal, {error, Status}, State#state{client = undefined}}; 2013 {stop, normal, State#state{client = undefined}}; 2014 2015ctrl_result_response(_, #state{client = From} = State, ErrorMsg) -> 2016 gen_server:reply(From, ErrorMsg), 2017 {noreply, State#state{client = undefined, caller = undefined}}. 2018 2019%%-------------------------------------------------------------------------- 2020handle_caller(#state{caller = {dir, Dir, Len}} = State0) -> 2021 Cmd = case Len of 2022 short -> "NLST"; 2023 long -> "LIST" 2024 end, 2025 _ = case Dir of 2026 "" -> 2027 send_ctrl_message(State0, mk_cmd(Cmd, "")); 2028 _ -> 2029 send_ctrl_message(State0, mk_cmd(Cmd ++ " ~s", [Dir])) 2030 end, 2031 State = activate_ctrl_connection(State0), 2032 {noreply, State#state{caller = {dir, Dir}}}; 2033 2034handle_caller(#state{caller = {recv_bin, RemoteFile}} = State0) -> 2035 _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])), 2036 State = activate_ctrl_connection(State0), 2037 {noreply, State#state{caller = recv_bin}}; 2038 2039handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} = 2040 State0) -> 2041 _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])), 2042 State = activate_ctrl_connection(State0), 2043 {noreply, State#state{caller = start_chunk_transfer}}; 2044 2045handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State0) -> 2046 _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])), 2047 State = activate_ctrl_connection(State0), 2048 {noreply, State#state{caller = {recv_file, Fd}}}; 2049 2050handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}}, 2051 ldir = LocalDir, client = From} = State0) -> 2052 case file_open(filename:absname(LocalFile, LocalDir), read) of 2053 {ok, Fd} -> 2054 _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])), 2055 State = activate_ctrl_connection(State0), 2056 {noreply, State#state{caller = {transfer_file, Fd}}}; 2057 {error, _} -> 2058 gen_server:reply(From, {error, epath}), 2059 {noreply, State0#state{client = undefined, caller = undefined, 2060 dsock = undefined}} 2061 end; 2062 2063handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = 2064 State0) -> 2065 _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])), 2066 State = activate_ctrl_connection(State0), 2067 {noreply, State#state{caller = {transfer_data, Bin}}}. 2068 2069%% ----------- FTP SERVER COMMUNICATION ------------------------- 2070 2071%% Connect to FTP server at Host (default is TCP port 21) 2072%% in order to establish a control connection. 2073setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State0) -> 2074 MsTime = erlang:monotonic_time(), 2075 case connect(Host, Port, SockOpts, Timeout, State0) of 2076 {ok, IpFam, CSock} -> 2077 State1 = State0#state{csock = {tcp, CSock}, ipfamily = IpFam}, 2078 State = activate_ctrl_connection(State1), 2079 case Timeout - millisec_passed(MsTime) of 2080 Timeout2 when (Timeout2 >= 0) -> 2081 {ok, State#state{caller = open}, Timeout2}; 2082 _ -> 2083 %% Oups: Simulate timeout 2084 {ok, State#state{caller = open}, 0} 2085 end; 2086 Error -> 2087 Error 2088 end. 2089 2090setup_data_connection(#state{mode = active, 2091 caller = Caller, 2092 csock = CSock, 2093 sockopts_data_active = SockOpts, 2094 ftp_extension = FtpExt} = State0) -> 2095 case (catch sockname(CSock)) of 2096 {ok, {{_, _, _, _, _, _, _, _} = IP0, _}} -> 2097 IP = proplists:get_value(ip, SockOpts, IP0), 2098 {ok, LSock} = 2099 gen_tcp:listen(0, [{ip, IP}, {active, false}, 2100 inet6, binary, {packet, 0} | 2101 lists:keydelete(ip,1,SockOpts)]), 2102 {ok, {_, Port}} = sockname({tcp,LSock}), 2103 IpAddress = inet_parse:ntoa(IP), 2104 Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]), 2105 _ = send_ctrl_message(State0, Cmd), 2106 State = activate_ctrl_connection(State0), 2107 {noreply, State#state{caller = {setup_data_connection, 2108 {LSock, Caller}}}}; 2109 {ok, {{_,_,_,_} = IP0, _}} -> 2110 IP = proplists:get_value(ip, SockOpts, IP0), 2111 {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, 2112 binary, {packet, 0} | 2113 lists:keydelete(ip,1,SockOpts)]), 2114 {ok, Port} = inet:port(LSock), 2115 _ = case FtpExt of 2116 false -> 2117 {IP1, IP2, IP3, IP4} = IP, 2118 {Port1, Port2} = {Port div 256, Port rem 256}, 2119 send_ctrl_message(State0, 2120 mk_cmd("PORT ~w,~w,~w,~w,~w,~w", 2121 [IP1, IP2, IP3, IP4, Port1, Port2])); 2122 true -> 2123 IpAddress = inet_parse:ntoa(IP), 2124 Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]), 2125 send_ctrl_message(State0, Cmd) 2126 end, 2127 State = activate_ctrl_connection(State0), 2128 {noreply, State#state{caller = {setup_data_connection, 2129 {LSock, Caller}}}} 2130 end; 2131 2132setup_data_connection(#state{mode = passive, ipfamily = inet6, 2133 caller = Caller} = State0) -> 2134 _ = send_ctrl_message(State0, mk_cmd("EPSV", [])), 2135 State = activate_ctrl_connection(State0), 2136 {noreply, State#state{caller = {setup_data_connection, Caller}}}; 2137 2138setup_data_connection(#state{mode = passive, ipfamily = inet, 2139 caller = Caller, 2140 ftp_extension = false} = State0) -> 2141 _ = send_ctrl_message(State0, mk_cmd("PASV", [])), 2142 State = activate_ctrl_connection(State0), 2143 {noreply, State#state{caller = {setup_data_connection, Caller}}}; 2144 2145setup_data_connection(#state{mode = passive, ipfamily = inet, 2146 caller = Caller, 2147 ftp_extension = true} = State0) -> 2148 _ = send_ctrl_message(State0, mk_cmd("EPSV", [])), 2149 State = activate_ctrl_connection(State0), 2150 {noreply, State#state{caller = {setup_data_connection, Caller}}}. 2151 2152connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) -> 2153 connect2(Host, Port, IpFam, SockOpts, Timeout); 2154 2155connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6 = IpFam}) -> 2156 connect2(Host, Port, IpFam, SockOpts, Timeout); 2157 2158connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) -> 2159 case inet:getaddr(Host, inet6) of 2160 {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} -> 2161 case inet:getaddr(Host, inet) of 2162 {ok, IPv4} -> 2163 IpFam = inet, 2164 connect2(IPv4, Port, IpFam, SockOpts, Timeout); 2165 2166 _ -> 2167 IpFam = inet6, 2168 connect2(IPv6, Port, IpFam, SockOpts, Timeout) 2169 end; 2170 2171 {ok, IPv6} -> 2172 IpFam = inet6, 2173 connect2(IPv6, Port, IpFam, SockOpts, Timeout); 2174 2175 _ -> 2176 case inet:getaddr(Host, inet) of 2177 {ok, IPv4} -> 2178 IpFam = inet, 2179 connect2(IPv4, Port, IpFam, SockOpts, Timeout); 2180 Error -> 2181 Error 2182 end 2183 end. 2184 2185connect2(Host, Port, IpFam, SockOpts, Timeout) -> 2186 Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts], 2187 case gen_tcp:connect(Host, Port, Opts, Timeout) of 2188 {ok, Sock} -> 2189 {ok, IpFam, Sock}; 2190 Error -> 2191 Error 2192 end. 2193 2194 2195accept_data_connection(#state{mode = active, 2196 dtimeout = DTimeout, 2197 tls_options = TLSOptions, 2198 dsock = {lsock, LSock}} = State0) -> 2199 case gen_tcp:accept(LSock, DTimeout) of 2200 {ok, Socket} when is_list(TLSOptions) -> 2201 gen_tcp:close(LSock), 2202 ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]), 2203 case ssl:connect(Socket, TLSOptions, DTimeout) of 2204 {ok, TLSSocket} -> 2205 {ok, State0#state{dsock={ssl,TLSSocket}}}; 2206 {error, Reason} -> 2207 {error, {ssl_connect_failed, Reason}} 2208 end; 2209 {ok, Socket} -> 2210 gen_tcp:close(LSock), 2211 {ok, State0#state{dsock={tcp,Socket}}}; 2212 {error, Reason} -> 2213 {error, {data_connect_failed, Reason}} 2214 end; 2215 2216accept_data_connection(#state{mode = passive, 2217 dtimeout = DTimeout, 2218 dsock = {tcp,Socket}, 2219 tls_options = TLSOptions} = State) when is_list(TLSOptions) -> 2220 ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State]), 2221 case ssl:connect(Socket, TLSOptions, DTimeout) of 2222 {ok, TLSSocket} -> 2223 {ok, State#state{dsock={ssl,TLSSocket}}}; 2224 {error, Reason} -> 2225 {error, {ssl_connect_failed, Reason}} 2226 end; 2227accept_data_connection(#state{mode = passive} = State) -> 2228 {ok,State}. 2229 2230 2231send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) -> 2232 verbose(lists:flatten(Message),Verbose,send), 2233 ?DBG('<--ctrl ~p ---- ~s~p~n',[Socket,Message,_S]), 2234 _ = send_message(Socket, Message). 2235 2236send_data_message(_S=#state{dsock = Socket}, Message) -> 2237 ?DBG('<==data ~p ==== ~s~n~p~n',[Socket,Message,_S]), 2238 case send_message(Socket, Message) of 2239 ok -> 2240 ok; 2241 {error, Reason} -> 2242 Report = io_lib:format("send/2 for socket ~p failed with " 2243 "reason ~p~n", [Socket, Reason]), 2244 error_logger:error_report(Report), 2245 %% If tcp/ssl does not work the only option is to terminate, 2246 %% this is the expected behavior under these circumstances. 2247 exit(normal) %% User will get error message from terminate/2 2248 end. 2249 2250send_message({tcp, Socket}, Message) -> 2251 gen_tcp:send(Socket, Message); 2252send_message({ssl, Socket}, Message) -> 2253 ssl:send(Socket, Message). 2254 2255activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}} = State) -> 2256 activate_connection(CSock), 2257 State; 2258activate_ctrl_connection(#state{csock = CSock} = State0) -> 2259 activate_connection(CSock), 2260 %% We have already received at least part of the next control message, 2261 %% that has been saved in ctrl_data, process this first. 2262 {noreply, State} = handle_info({socket_type(CSock), unwrap_socket(CSock), <<>>}, State0), 2263 State. 2264 2265activate_data_connection(#state{dsock = DSock} = State) -> 2266 activate_connection(DSock), 2267 State. 2268 2269activate_connection(Socket) -> 2270 ignore_return_value( 2271 case socket_type(Socket) of 2272 tcp -> inet:setopts(unwrap_socket(Socket), [{active, once}]); 2273 ssl -> ssl:setopts(unwrap_socket(Socket), [{active, once}]) 2274 end). 2275 2276 2277ignore_return_value(_) -> ok. 2278 2279unwrap_socket({tcp,Socket}) -> Socket; 2280unwrap_socket({ssl,Socket}) -> Socket. 2281 2282socket_type({tcp,_Socket}) -> tcp; 2283socket_type({ssl,_Socket}) -> ssl. 2284 2285close_ctrl_connection(#state{csock = undefined}) -> ok; 2286close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket). 2287 2288close_data_connection(#state{dsock = undefined}) -> ok; 2289close_data_connection(#state{dsock = Socket}) -> close_connection(Socket). 2290 2291close_connection({lsock,Socket}) -> ignore_return_value( gen_tcp:close(Socket) ); 2292close_connection({tcp, Socket}) -> ignore_return_value( gen_tcp:close(Socket) ); 2293close_connection({ssl, Socket}) -> ignore_return_value( ssl:close(Socket) ). 2294 2295%% ------------ FILE HANDLING ---------------------------------------- 2296send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) -> 2297 {noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}}; 2298send_file(State0, Fd) -> 2299 case file_read(Fd) of 2300 {ok, N, Bin} when N > 0 -> 2301 send_data_message(State0, Bin), 2302 progress_report({binary, Bin}, State0), 2303 send_file(State0, Fd); 2304 {ok, _, _} -> 2305 file_close(Fd), 2306 close_data_connection(State0), 2307 progress_report({transfer_size, 0}, State0), 2308 State = activate_ctrl_connection(State0), 2309 {noreply, State#state{caller = transfer_file_second_phase, 2310 dsock = undefined}}; 2311 {error, Reason} -> 2312 gen_server:reply(State0#state.client, {error, Reason}), 2313 {stop, normal, State0#state{client = undefined}} 2314 end. 2315 2316file_open(File, Option) -> 2317 file:open(File, [raw, binary, Option]). 2318 2319file_close(Fd) -> 2320 ignore_return_value( file:close(Fd) ). 2321 2322file_read(Fd) -> 2323 case file:read(Fd, ?FILE_BUFSIZE) of 2324 {ok, Bytes} -> 2325 {ok, size(Bytes), Bytes}; 2326 eof -> 2327 {ok, 0, []}; 2328 Other -> 2329 Other 2330 end. 2331 2332file_write(Bytes, Fd) -> 2333 file:write(Fd, Bytes). 2334 2335%% -------------- MISC ---------------------------------------------- 2336 2337call(GenServer, Msg, Format) -> 2338 call(GenServer, Msg, Format, infinity). 2339call(GenServer, Msg, Format, Timeout) -> 2340 Req = {self(), Msg}, 2341 case (catch gen_server:call(GenServer, Req, Timeout)) of 2342 {ok, Bin} when is_binary(Bin) andalso (Format =:= string) -> 2343 {ok, binary_to_list(Bin)}; 2344 {'EXIT', _} -> 2345 {error, eclosed}; 2346 Result -> 2347 Result 2348 end. 2349 2350cast(GenServer, Msg) -> 2351 gen_server:cast(GenServer, {self(), Msg}). 2352 2353send_bin(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Bin) -> 2354 State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_bin, Bin}}; 2355send_bin(State0, Bin) -> 2356 send_data_message(State0, Bin), 2357 close_data_connection(State0), 2358 State = activate_ctrl_connection(State0), 2359 {noreply, State#state{caller = transfer_data_second_phase, 2360 dsock = undefined}}. 2361 2362mk_cmd(Fmt, Args) -> 2363 [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok. 2364 2365is_name_sane([]) -> 2366 true; 2367is_name_sane([?CR| _]) -> 2368 false; 2369is_name_sane([?LF| _]) -> 2370 false; 2371is_name_sane([_| Rest]) -> 2372 is_name_sane(Rest). 2373 2374pwd_result(Lines) -> 2375 {_, [?DOUBLE_QUOTE | Rest]} = 2376 lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines), 2377 {Dir, _} = 2378 lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest), 2379 Dir. 2380 2381 2382key_search(Key, List, Default) -> 2383 case lists:keysearch(Key, 1, List) of 2384 {value, {_,Val}} -> 2385 Val; 2386 false -> 2387 Default 2388 end. 2389 2390verbose(Lines, true, Direction) -> 2391 DirStr = 2392 case Direction of 2393 send -> 2394 "Sending: "; 2395 _ -> 2396 "Receiving: " 2397 end, 2398 Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR), 2399 erlang:display(DirStr++Str); 2400verbose(_, false,_) -> 2401 ok. 2402 2403progress(Options) -> 2404 ftp_progress:start_link(Options). 2405 2406progress_report(_, #state{progress = ignore}) -> 2407 ok; 2408progress_report(stop, #state{progress = ProgressPid}) -> 2409 ftp_progress:stop(ProgressPid); 2410progress_report({binary, Data}, #state{progress = ProgressPid}) -> 2411 ftp_progress:report(ProgressPid, {transfer_size, size(Data)}); 2412progress_report(Report, #state{progress = ProgressPid}) -> 2413 ftp_progress:report(ProgressPid, Report). 2414 2415 2416peername({tcp, Socket}) -> inet:peername(Socket); 2417peername({ssl, Socket}) -> ssl:peername(Socket). 2418 2419sockname({tcp, Socket}) -> inet:sockname(Socket); 2420sockname({ssl, Socket}) -> ssl:sockname(Socket). 2421 2422maybe_tls_upgrade(Pid, undefined) -> 2423 {ok, Pid}; 2424maybe_tls_upgrade(Pid, TLSOptions) -> 2425 catch ssl:start(), 2426 call(Pid, {open, tls_upgrade, TLSOptions}, plain). 2427 2428start_chunk(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State) -> 2429 State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, start_chunk, undefined}}; 2430start_chunk(#state{client = From} = State) -> 2431 gen_server:reply(From, ok), 2432 State#state{chunk = true, 2433 client = undefined, 2434 caller = undefined}. 2435 2436 2437%% This function extracts the start options from the 2438%% Valid options: 2439%% debug, 2440%% verbose 2441%% ipfamily 2442%% priority 2443%% flags (for backward compatibillity) 2444start_options(Options) -> 2445 case lists:keysearch(flags, 1, Options) of 2446 {value, {flags, Flags}} -> 2447 Verbose = lists:member(verbose, Flags), 2448 IsTrace = lists:member(trace, Flags), 2449 IsDebug = lists:member(debug, Flags), 2450 DebugLevel = 2451 if 2452 (IsTrace =:= true) -> 2453 trace; 2454 IsDebug =:= true -> 2455 debug; 2456 true -> 2457 disable 2458 end, 2459 {ok, [{verbose, Verbose}, 2460 {debug, DebugLevel}, 2461 {priority, low}]}; 2462 false -> 2463 ValidateVerbose = 2464 fun(true) -> true; 2465 (false) -> true; 2466 (_) -> false 2467 end, 2468 ValidateDebug = 2469 fun(trace) -> true; 2470 (debug) -> true; 2471 (disable) -> true; 2472 (_) -> false 2473 end, 2474 ValidatePriority = 2475 fun(low) -> true; 2476 (normal) -> true; 2477 (high) -> true; 2478 (_) -> false 2479 end, 2480 ValidOptions = 2481 [{verbose, ValidateVerbose, false, false}, 2482 {debug, ValidateDebug, false, disable}, 2483 {priority, ValidatePriority, false, low}], 2484 validate_options(Options, ValidOptions, []) 2485 end. 2486 2487 2488%% This function extracts and validates the open options from the 2489%% Valid options: 2490%% mode 2491%% host 2492%% port 2493%% timeout 2494%% dtimeout 2495%% progress 2496%% ftp_extension 2497 2498open_options(Options) -> 2499 ValidateMode = 2500 fun(active) -> true; 2501 (passive) -> true; 2502 (_) -> false 2503 end, 2504 ValidateHost = 2505 fun(Host) when is_list(Host) -> 2506 true; 2507 (Host) when is_tuple(Host) andalso 2508 ((size(Host) =:= 4) orelse (size(Host) =:= 8)) -> 2509 true; 2510 (_) -> 2511 false 2512 end, 2513 ValidatePort = 2514 fun(Port) when is_integer(Port) andalso (Port > 0) -> true; 2515 (_) -> false 2516 end, 2517 ValidateIpFamily = 2518 fun(inet) -> true; 2519 (inet6) -> true; 2520 (inet6fb4) -> true; 2521 (_) -> false 2522 end, 2523 ValidateTimeout = 2524 fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true; 2525 (_) -> false 2526 end, 2527 ValidateDTimeout = 2528 fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true; 2529 (infinity) -> true; 2530 (_) -> false 2531 end, 2532 ValidateProgress = 2533 fun(ignore) -> 2534 true; 2535 ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso 2536 is_atom(Func) -> 2537 true; 2538 (_) -> 2539 false 2540 end, 2541 ValidateFtpExtension = 2542 fun(true) -> true; 2543 (false) -> true; 2544 (_) -> false 2545 end, 2546 ValidOptions = 2547 [{mode, ValidateMode, false, ?DEFAULT_MODE}, 2548 {host, ValidateHost, true, ehost}, 2549 {port, ValidatePort, false, ?FTP_PORT}, 2550 {ipfamily, ValidateIpFamily, false, inet}, 2551 {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT}, 2552 {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT}, 2553 {progress, ValidateProgress, false, ?PROGRESS_DEFAULT}, 2554 {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}], 2555 validate_options(Options, ValidOptions, []). 2556 2557socket_options(Options) -> 2558 CtrlOpts = proplists:get_value(sock_ctrl, Options, []), 2559 DataActOpts = proplists:get_value(sock_data_act, Options, CtrlOpts), 2560 DataPassOpts = proplists:get_value(sock_data_pass, Options, CtrlOpts), 2561 case [O || O <- lists:usort(CtrlOpts++DataPassOpts++DataActOpts), 2562 not valid_socket_option(O)] of 2563 [] -> 2564 {ok, {CtrlOpts, DataPassOpts, DataActOpts}}; 2565 Invalid -> 2566 throw({error,{sock_opts,Invalid}}) 2567 end. 2568 2569 2570valid_socket_option(inet ) -> false; 2571valid_socket_option(inet6 ) -> false; 2572valid_socket_option({ipv6_v6only, _}) -> false; 2573valid_socket_option({active,_} ) -> false; 2574valid_socket_option({packet,_} ) -> false; 2575valid_socket_option({mode,_} ) -> false; 2576valid_socket_option(binary ) -> false; 2577valid_socket_option(list ) -> false; 2578valid_socket_option({header,_} ) -> false; 2579valid_socket_option({packet_size,_} ) -> false; 2580valid_socket_option(_) -> true. 2581 2582 2583tls_options(Options) -> 2584 %% Options will be validated by ssl application 2585 proplists:get_value(tls, Options, undefined). 2586 2587validate_options([], [], Acc) -> 2588 {ok, lists:reverse(Acc)}; 2589validate_options([], ValidOptions, Acc) -> 2590 %% Check if any mandatory options are missing! 2591 case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of 2592 [] -> 2593 Defaults = 2594 [{Key, Default} || {Key, _, _, Default} <- ValidOptions], 2595 {ok, lists:reverse(Defaults ++ Acc)}; 2596 [{_, Reason}|_Missing] -> 2597 throw({error, Reason}) 2598 end; 2599validate_options([{Key, Value}|Options], ValidOptions, Acc) -> 2600 case lists:keysearch(Key, 1, ValidOptions) of 2601 {value, {Key, Validate, _, Default}} -> 2602 case (catch Validate(Value)) of 2603 true -> 2604 NewValidOptions = lists:keydelete(Key, 1, ValidOptions), 2605 validate_options(Options, NewValidOptions, 2606 [{Key, Value} | Acc]); 2607 _ -> 2608 NewValidOptions = lists:keydelete(Key, 1, ValidOptions), 2609 validate_options(Options, NewValidOptions, 2610 [{Key, Default} | Acc]) 2611 end; 2612 false -> 2613 validate_options(Options, ValidOptions, Acc) 2614 end; 2615validate_options([_|Options], ValidOptions, Acc) -> 2616 validate_options(Options, ValidOptions, Acc). 2617 2618%% Help function, elapsed milliseconds since T0 2619millisec_passed(T0) -> 2620 %% OTP 18 2621 erlang:convert_time_unit(erlang:monotonic_time() - T0, 2622 native, 2623 micro_seconds) div 1000. 2624