/lib/inets/src/ftp/ftp.erl
Erlang | 2278 lines | 1454 code | 340 blank | 484 comment | 7 complexity | 3120cf89c88e6c05d7fe8e264c5f52ad MD5 | raw 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_data = CtrlData, 1495 owner = Owner, 1496 client = Client, 1497 caller = Caller, 1498 ipfamily = IpFamily, 1499 progress = Progress} = State1, 1500 IPv6Disable = 1501 if 1502 (IpFamily =:= inet) -> 1503 true; 1504 true -> 1505 false 1506 end, 1507 State2 = 1508 {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, 1509 Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress}, 1510 {ok, State2}; 1511 1512code_change(_Vsn, State, _Extra) -> 1513 {ok, State}. 1514 1515 1516%%%========================================================================= 1517%% Start/stop 1518%%%========================================================================= 1519%%-------------------------------------------------------------------------- 1520%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason} 1521%% 1522%% Description: Callback function for the ftp supervisor. It is called 1523%% : when start_service/1 calls ftp_sup:start_child/1 to start an 1524%% : instance of the ftp process. Also called by start_standalone/1 1525%%-------------------------------------------------------------------------- 1526start_link([Opts, GenServerOptions]) -> 1527 start_link(Opts, GenServerOptions). 1528 1529start_link(Opts, GenServerOptions) -> 1530 case lists:keysearch(client, 1, Opts) of 1531 {value, _} -> 1532 %% Via the supervisor 1533 gen_server:start_link(?MODULE, Opts, GenServerOptions); 1534 false -> 1535 Opts2 = [{client, self()} | Opts], 1536 gen_server:start_link(?MODULE, Opts2, GenServerOptions) 1537 end. 1538 1539 1540%%% Stop functionality is handled by close/1 1541 1542%%%======================================================================== 1543%%% Internal functions 1544%%%======================================================================== 1545 1546%%-------------------------------------------------------------------------- 1547%%% Help functions to handle_call and/or handle_ctrl_result 1548%%-------------------------------------------------------------------------- 1549%% User handling 1550handle_user(User, Password, Acc, State) -> 1551 send_ctrl_message(State, mk_cmd("USER ~s", [User])), 1552 activate_ctrl_connection(State), 1553 {noreply, State#state{caller = {handle_user, Password, Acc}}}. 1554 1555handle_user_passwd(Password, Acc, State) -> 1556 send_ctrl_message(State, mk_cmd("PASS ~s", [Password])), 1557 activate_ctrl_connection(State), 1558 {noreply, State#state{caller = {handle_user_passwd, Acc}}}. 1559 1560handle_user_account(Acc, State) -> 1561 send_ctrl_message(State, mk_cmd("ACCT ~s", [Acc])), 1562 activate_ctrl_connection(State), 1563 {noreply, State#state{caller = handle_user_account}}. 1564 1565 1566%%-------------------------------------------------------------------------- 1567%% handle_ctrl_result 1568%%-------------------------------------------------------------------------- 1569%%-------------------------------------------------------------------------- 1570%% Handling of control connection setup 1571handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From} 1572 = State) -> 1573 gen_server:reply(From, {ok, self()}), 1574 {noreply, State#state{client = undefined, 1575 caller = undefined }}; 1576handle_ctrl_result({_, Lines}, #state{caller = open} = State) -> 1577 ctrl_result_response(econn, State, {error, Lines}); 1578 1579%%-------------------------------------------------------------------------- 1580%% Data connection setup active mode 1581handle_ctrl_result({pos_compl, _Lines}, 1582 #state{mode = active, 1583 caller = {setup_data_connection, 1584 {LSock, Caller}}} = State) -> 1585 handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}}); 1586 1587handle_ctrl_result({Status, Lines}, 1588 #state{mode = active, 1589 caller = {setup_data_connection, {LSock, _}}} 1590 = State) -> 1591 close_connection(LSock), 1592 ctrl_result_response(Status, State, {error, Lines}); 1593 1594%% Data connection setup passive mode 1595handle_ctrl_result({pos_compl, Lines}, 1596 #state{mode = passive, 1597 ipfamily = inet6, 1598 client = From, 1599 caller = {setup_data_connection, Caller}, 1600 csock = CSock, 1601 timeout = Timeout} 1602 = State) -> 1603 [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), 1604 {ok, {IP, _}} = inet:peername(CSock), 1605 case connect(IP, list_to_integer(PortStr), Timeout, State) of 1606 {ok, _, Socket} -> 1607 handle_caller(State#state{caller = Caller, dsock = Socket}); 1608 {error, _Reason} = Error -> 1609 gen_server:reply(From, Error), 1610 {noreply, State#state{client = undefined, caller = undefined}} 1611 end; 1612 1613handle_ctrl_result({pos_compl, Lines}, 1614 #state{mode = passive, 1615 ipfamily = inet, 1616 client = From, 1617 caller = {setup_data_connection, Caller}, 1618 timeout = Timeout} = State) -> 1619 1620 {_, [?LEFT_PAREN | Rest]} = 1621 lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines), 1622 {NewPortAddr, _} = 1623 lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest), 1624 [A1, A2, A3, A4, P1, P2] = 1625 lists:map(fun(X) -> list_to_integer(X) end, 1626 string:tokens(NewPortAddr, [$,])), 1627 IP = {A1, A2, A3, A4}, 1628 Port = (P1 * 256) + P2, 1629 case connect(IP, Port, Timeout, State) of 1630 {ok, _, Socket} -> 1631 handle_caller(State#state{caller = Caller, dsock = Socket}); 1632 {error, _Reason} = Error -> 1633 gen_server:reply(From, Error), 1634 {noreply,State#state{client = undefined, caller = undefined}} 1635 end; 1636 1637%% FTP server does not support passive mode: try to fallback on active mode 1638handle_ctrl_result(_, 1639 #state{mode = passive, 1640 caller = {setup_data_connection, Caller}} = State) -> 1641 setup_data_connection(State#state{mode = active, caller = Caller}); 1642 1643 1644%%-------------------------------------------------------------------------- 1645%% User handling 1646handle_ctrl_result({pos_interm, _}, 1647 #state{caller = {handle_user, PassWord, Acc}} = State) -> 1648 handle_user_passwd(PassWord, Acc, State); 1649handle_ctrl_result({Status, _}, 1650 #state{caller = {handle_user, _, _}} = State) -> 1651 ctrl_result_response(Status, State, {error, euser}); 1652 1653%% Accounts 1654handle_ctrl_result({pos_interm_acct, _}, 1655 #state{caller = {handle_user_passwd, Acc}} = State) 1656 when Acc =/= "" -> 1657 handle_user_account(Acc, State); 1658handle_ctrl_result({Status, _}, 1659 #state{caller = {handle_user_passwd, _}} = State) -> 1660 ctrl_result_response(Status, State, {error, euser}); 1661 1662%%-------------------------------------------------------------------------- 1663%% Print current working directory 1664handle_ctrl_result({pos_compl, Lines}, 1665 #state{caller = pwd, client = From} = State) -> 1666 Dir = pwd_result(Lines), 1667 gen_server:reply(From, {ok, Dir}), 1668 {noreply, State#state{client = undefined, caller = undefined}}; 1669 1670%%-------------------------------------------------------------------------- 1671%% Directory listing 1672handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State) -> 1673 case accept_data_connection(State) of 1674 {ok, NewState} -> 1675 activate_data_connection(NewState), 1676 {noreply, NewState#state{caller = {handle_dir_result, Dir}}}; 1677 {error, _Reason} = ERROR -> 1678 case State#state.client of 1679 undefined -> 1680 {stop, ERROR, State}; 1681 From -> 1682 gen_server:reply(From, ERROR), 1683 {stop, normal, State#state{client = undefined}} 1684 end 1685 end; 1686 1687handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir, 1688 Data}, client = From} 1689 = State) -> 1690 case Dir of 1691 "" -> % Current directory 1692 gen_server:reply(From, {ok, Data}), 1693 {noreply, State#state{client = undefined, 1694 caller = undefined}}; 1695 _ -> 1696 %% <WTF> 1697 %% Dir cannot be assumed to be a dir. It is a string that 1698 %% could be a dir, but could also be a file or even a string 1699 %% containing wildcards (*). 1700 %% 1701 %% %% If there is only one line it might be a directory with one 1702 %% %% file but it might be an error message that the directory 1703 %% %% was not found. So in this case we have to endure a little 1704 %% %% overhead to be able to give a good return value. Alas not 1705 %% %% all ftp implementations behave the same and returning 1706 %% %% an error string is allowed by the FTP RFC. 1707 %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end, 1708 %% binary_to_list(Data)) of 1709 %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) -> 1710 %% send_ctrl_message(State, mk_cmd("PWD", [])), 1711 %% activate_ctrl_connection(State), 1712 %% {noreply, 1713 %% State#state{caller = {handle_dir_data, Dir, Data}}}; 1714 %% _ -> 1715 %% gen_server:reply(From, {ok, Data}), 1716 %% {noreply, State#state{client = undefined, 1717 %% caller = undefined}} 1718 %% end 1719 %% </WTF> 1720 gen_server:reply(From, {ok, Data}), 1721 {noreply, State#state{client = undefined, 1722 caller = undefined}} 1723 end; 1724 1725handle_ctrl_result({pos_compl, Lines}, 1726 #state{caller = {handle_dir_data, Dir, DirData}} = 1727 State) -> 1728 OldDir = pwd_result(Lines), 1729 send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])), 1730 activate_ctrl_connection(State), 1731 {noreply, State#state{caller = {handle_dir_data_second_phase, OldDir, 1732 DirData}}}; 1733handle_ctrl_result({Status, _}, 1734 #state{caller = {handle_dir_data, _, _}} = State) -> 1735 ctrl_result_response(Status, State, {error, epath}); 1736 1737handle_ctrl_result(S={_Status, _}, 1738 #state{caller = {handle_dir_result, _, _}} = State) -> 1739 %% OTP-5731, macosx 1740 ctrl_result_response(S, State, {error, epath}); 1741 1742handle_ctrl_result({pos_compl, _}, 1743 #state{caller = {handle_dir_data_second_phase, OldDir, 1744 DirData}} = State) -> 1745 send_ctrl_message(State, mk_cmd("CWD ~s", [OldDir])), 1746 activate_ctrl_connection(State), 1747 {noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}}; 1748handle_ctrl_result({Status, _}, 1749 #state{caller = {handle_dir_data_second_phase, _, _}} 1750 = State) -> 1751 ctrl_result_response(Status, State, {error, epath}); 1752handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData}, 1753 client = From} = State) -> 1754 gen_server:reply(From, {ok, DirData}), 1755 {noreply, State#state{client = undefined, caller = undefined}}; 1756 1757handle_ctrl_result({Status, _}, #state{caller = cd} = State) -> 1758 ctrl_result_response(Status, State, {error, epath}); 1759 1760handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) -> 1761 ctrl_result_response(Status, State, {error, epath}); 1762 1763%%-------------------------------------------------------------------------- 1764%% File renaming 1765handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}} 1766 = State) -> 1767 send_ctrl_message(State, mk_cmd("RNTO ~s", [NewFile])), 1768 activate_ctrl_connection(State), 1769 {noreply, State#state{caller = rename_second_phase}}; 1770 1771handle_ctrl_result({Status, _}, 1772 #state{caller = {rename, _}} = State) -> 1773 ctrl_result_response(Status, State, {error, epath}); 1774 1775handle_ctrl_result({Status, _}, 1776 #state{caller = rename_second_phase} = State) -> 1777 ctrl_result_response(Status, State, {error, epath}); 1778 1779%%-------------------------------------------------------------------------- 1780%% File handling - recv_bin 1781handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State) -> 1782 case accept_data_connection(State) of 1783 {ok, NewState} -> 1784 activate_data_connection(NewState), 1785 {noreply, NewState}; 1786 {error, _Reason} = ERROR -> 1787 case State#state.client of 1788 undefined -> 1789 {stop, ERROR, State}; 1790 From -> 1791 gen_server:reply(From, ERROR), 1792 {stop, normal, State#state{client = undefined}} 1793 end 1794 end; 1795 1796handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data}, 1797 client = From} = State) -> 1798 gen_server:reply(From, {ok, Data}), 1799 close_data_connection(State), 1800 {noreply, State#state{client = undefined, caller = undefined}}; 1801 1802handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) -> 1803 close_data_connection(State), 1804 ctrl_result_response(Status, State#state{dsock = undefined}, 1805 {error, epath}); 1806 1807handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) -> 1808 close_data_connection(State), 1809 ctrl_result_response(Status, State#state{dsock = undefined}, 1810 {error, epath}); 1811%%-------------------------------------------------------------------------- 1812%% File handling - start_chunk_transfer 1813handle_ctrl_result({pos_prel, _}, #state{client = From, 1814 caller = start_chunk_transfer} 1815 = State) -> 1816 case accept_data_connection(State) of 1817 {ok, NewState} -> 1818 gen_server:reply(From, ok), 1819 {noreply, NewState#state{chunk = true, client = undefined, 1820 caller = undefined}}; 1821 {error, _Reason} = ERROR -> 1822 case State#state.client of 1823 undefined -> 1824 {stop, ERROR, State}; 1825 From -> 1826 gen_server:reply(From, ERROR), 1827 {stop, normal, State#state{client = undefined}} 1828 end 1829 end; 1830 1831%%-------------------------------------------------------------------------- 1832%% File handling - recv_file 1833handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State) -> 1834 case accept_data_connection(State) of 1835 {ok, NewState} -> 1836 activate_data_connection(NewState), 1837 {noreply, NewState}; 1838 {error, _Reason} = ERROR -> 1839 case State#state.client of 1840 undefined -> 1841 {stop, ERROR, State}; 1842 From -> 1843 gen_server:reply(From, ERROR), 1844 {stop, normal, State#state{client = undefined}} 1845 end 1846 end; 1847 1848handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) -> 1849 file_close(Fd), 1850 close_data_connection(State), 1851 ctrl_result_response(Status, State#state{dsock = undefined}, 1852 {error, epath}); 1853%%-------------------------------------------------------------------------- 1854%% File handling - transfer_* 1855handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}} 1856 = State) -> 1857 case accept_data_connection(State) of 1858 {ok, NewState} -> 1859 send_file(Fd, NewState); 1860 {error, _Reason} = ERROR -> 1861 case State#state.client of 1862 undefined -> 1863 {stop, ERROR, State}; 1864 From -> 1865 gen_server:reply(From, ERROR), 1866 {stop, normal, State#state{client = undefined}} 1867 end 1868 end; 1869 1870handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} 1871 = State) -> 1872 case accept_data_connection(State) of 1873 {ok, NewState} -> 1874 send_data_message(NewState, Bin), 1875 close_data_connection(NewState), 1876 activate_ctrl_connection(NewState), 1877 {noreply, NewState#state{caller = transfer_data_second_phase, 1878 dsock = undefined}}; 1879 {error, _Reason} = ERROR -> 1880 case State#state.client of 1881 undefined -> 1882 {stop, ERROR, State}; 1883 From -> 1884 gen_server:reply(From, ERROR), 1885 {stop, normal, State#state{client = undefined}} 1886 end 1887 end; 1888 1889%%-------------------------------------------------------------------------- 1890%% Default 1891handle_ctrl_result({Status, Lines}, #state{client = From} = State) 1892 when From =/= undefined -> 1893 ctrl_result_response(Status, State, {error, Lines}). 1894 1895%%-------------------------------------------------------------------------- 1896%% Help functions to handle_ctrl_result 1897%%-------------------------------------------------------------------------- 1898ctrl_result_response(pos_compl, #state{client = From} = State, _) -> 1899 gen_server:reply(From, ok), 1900 {noreply, State#state{client = undefined, caller = undefined}}; 1901 1902ctrl_result_response(enofile, #state{client = From} = State, _) -> 1903 gen_server:reply(From, {error, enofile}), 1904 {noreply, State#state{client = undefined, caller = undefined}}; 1905 1906ctrl_result_response(Status, #state{client = From} = State, _) 1907 when (Status =:= etnospc) orelse 1908 (Status =:= epnospc) orelse 1909 (Status =:= efnamena) orelse 1910 (Status =:= econn) -> 1911%Status == etnospc; Status == epnospc; Status == econn -> 1912 gen_server:reply(From, {error, Status}), 1913%% {stop, normal, {error, Status}, State#state{client = undefined}}; 1914 {stop, normal, State#state{client = undefined}}; 1915 1916ctrl_result_response(_, #state{client = From} = State, ErrorMsg) -> 1917 gen_server:reply(From, ErrorMsg), 1918 {noreply, State#state{client = undefined, caller = undefined}}. 1919 1920%%-------------------------------------------------------------------------- 1921handle_caller(#state{caller = {dir, Dir, Len}} = State) -> 1922 Cmd = case Len of 1923 short -> "NLST"; 1924 long -> "LIST" 1925 end, 1926 case Dir of 1927 "" -> 1928 send_ctrl_message(State, mk_cmd(Cmd, "")); 1929 _ -> 1930 send_ctrl_message(State, mk_cmd(Cmd ++ " ~s", [Dir])) 1931 end, 1932 activate_ctrl_connection(State), 1933 {noreply, State#state{caller = {dir, Dir}}}; 1934 1935handle_caller(#state{caller = {recv_bin, RemoteFile}} = State) -> 1936 send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), 1937 activate_ctrl_connection(State), 1938 {noreply, State#state{caller = recv_bin}}; 1939 1940handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} = 1941 State) -> 1942 send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), 1943 activate_ctrl_connection(State), 1944 {noreply, State#state{caller = start_chunk_transfer}}; 1945 1946handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State) -> 1947 send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), 1948 activate_ctrl_connection(State), 1949 {noreply, State#state{caller = {recv_file, Fd}}}; 1950 1951handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}}, 1952 ldir = LocalDir, client = From} = State) -> 1953 case file_open(filename:absname(LocalFile, LocalDir), read) of 1954 {ok, Fd} -> 1955 send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), 1956 activate_ctrl_connection(State), 1957 {noreply, State#state{caller = {transfer_file, Fd}}}; 1958 {error, _} -> 1959 gen_server:reply(From, {error, epath}), 1960 {noreply, State#state{client = undefined, caller = undefined, 1961 dsock = undefined}} 1962 end; 1963 1964handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = 1965 State) -> 1966 send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), 1967 activate_ctrl_connection(State), 1968 {noreply, State#state{caller = {transfer_data, Bin}}}. 1969 1970%% ----------- FTP SERVER COMMUNICATION ------------------------- 1971 1972%% Connect to FTP server at Host (default is TCP port 21) 1973%% in order to establish a control connection. 1974setup_ctrl_connection(Host, Port, Timeout, State) -> 1975 MsTime = millisec_time(), 1976 case connect(Host, Port, Timeout, State) of 1977 {ok, IpFam, CSock} -> 1978 NewState = State#state{csock = CSock, ipfamily = IpFam}, 1979 activate_ctrl_connection(NewState), 1980 case Timeout - (millisec_time() - MsTime) of 1981 Timeout2 when (Timeout2 >= 0) -> 1982 {ok, NewState#state{caller = open}, Timeout2}; 1983 _ -> 1984 %% Oups: Simulate timeout 1985 {ok, NewState#state{caller = open}, 0} 1986 end; 1987 Error -> 1988 Error 1989 end. 1990 1991setup_data_connection(#state{mode = active, 1992 caller = Caller, 1993 csock = CSock} = State) -> 1994 case (catch inet:sockname(CSock)) of 1995 {ok, {{_, _, _, _, _, _, _, _} = IP, _}} -> 1996 {ok, LSock} = 1997 gen_tcp:listen(0, [{ip, IP}, {active, false}, 1998 inet6, binary, {packet, 0}]), 1999 {ok, Port} = inet:port(LSock), 2000 IpAddress = inet_parse:ntoa(IP), 2001 Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]), 2002 send_ctrl_message(State, Cmd), 2003 activate_ctrl_connection(State), 2004 {noreply, State#state{caller = {setup_data_connection, 2005 {LSock, Caller}}}}; 2006 {ok, {{_,_,_,_} = IP, _}} -> 2007 {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, 2008 binary, {packet, 0}]), 2009 {ok, Port} = inet:port(LSock), 2010 {IP1, IP2, IP3, IP4} = IP, 2011 {Port1, Port2} = {Port div 256, Port rem 256}, 2012 send_ctrl_message(State, 2013 mk_cmd("PORT ~w,~w,~w,~w,~w,~w", 2014 [IP1, IP2, IP3, IP4, Port1, Port2])), 2015 activate_ctrl_connection(State), 2016 {noreply, State#state{caller = {setup_data_connection, 2017 {LSock, Caller}}}} 2018 end; 2019 2020setup_data_connection(#state{mode = passive, ipfamily = inet6, 2021 caller = Caller} = State) -> 2022 send_ctrl_message(State, mk_cmd("EPSV", [])), 2023 activate_ctrl_connection(State), 2024 {noreply, State#state{caller = {setup_data_connection, Caller}}}; 2025 2026setup_data_connection(#state{mode = passive, ipfamily = inet, 2027 caller = Caller} = State) -> 2028 send_ctrl_message(State, mk_cmd("PASV", [])), 2029 activate_ctrl_connection(State), 2030 {noreply, State#state{caller = {setup_data_connection, Caller}}}. 2031 2032 2033%% setup_data_connection(#state{mode = passive, ip_v6_disabled = false, 2034%% caller = Caller} = State) -> 2035%% send_ctrl_message(State, mk_cmd("EPSV", [])), 2036%% activate_ctrl_connection(State), 2037%% {noreply, State#state{caller = {setup_data_connection, Caller}}}; 2038 2039%% setup_data_connection(#state{mode = passive, ip_v6_disabled = true, 2040%% caller = Caller} = State) -> 2041%% send_ctrl_message(State, mk_cmd("PASV", [])), 2042%% activate_ctrl_connection(State), 2043%% {noreply, State#state{caller = {setup_data_connection, Caller}}}. 2044 2045 2046connect(Host, Port, Timeout, #state{ipfamily = inet = IpFam}) -> 2047 connect2(Host, Port, IpFam, Timeout); 2048 2049connect(Host, Port, Timeout, #state{ipfamily = inet6 = IpFam}) -> 2050 connect2(Host, Port, IpFam, Timeout); 2051 2052connect(Host, Port, Timeout, #state{ipfamily = inet6fb4}) -> 2053 case inet:getaddr(Host, inet6) of 2054 {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} -> 2055 case inet:getaddr(Host, inet) of 2056 {ok, IPv4} -> 2057 IpFam = inet, 2058 connect2(IPv4, Port, IpFam, Timeout); 2059 2060 _ -> 2061 IpFam = inet6, 2062 connect2(IPv6, Port, IpFam, Timeout) 2063 end; 2064 2065 {ok, IPv6} -> 2066 IpFam = inet6, 2067 connect2(IPv6, Port, IpFam, Timeout); 2068 2069 _ -> 2070 case inet:getaddr(Host, inet) of 2071 {ok, IPv4} -> 2072 IpFam = inet, 2073 connect2(IPv4, Port, IpFam, Timeout); 2074 Error -> 2075 Error 2076 end 2077 end. 2078 2079connect2(Host, Port, IpFam, Timeout) -> 2080 Opts = [IpFam, binary, {packet, 0}, {active, false}], 2081 case gen_tcp:connect(Host, Port, Opts, Timeout) of 2082 {ok, Sock} -> 2083 {ok, IpFam, Sock}; 2084 Error -> 2085 Error 2086 end. 2087 2088 2089accept_data_connection(#state{mode = active, 2090 dtimeout = DTimeout, 2091 dsock = {lsock, LSock}} = State) -> 2092 case gen_tcp:accept(LSock, DTimeout) of 2093 {ok, Socket} -> 2094 gen_tcp:close(LSock), 2095 {ok, State#state{dsock = Socket}}; 2096 {error, Reason} -> 2097 {error, {data_connect_failed, Reason}} 2098 end; 2099 2100accept_data_connection(#state{mode = passive} = State) -> 2101 {ok, State}. 2102 2103send_ctrl_message(#state{csock = Socket, verbose = Verbose}, Message) -> 2104 %% io:format("send control message: ~n~p~n", [lists:flatten(Message)]), 2105 verbose(lists:flatten(Message),Verbose,send), 2106 send_message(Socket, Message). 2107 2108send_data_message(#state{dsock = Socket}, Message) -> 2109 send_message(Socket, Message). 2110 2111send_message(Socket, Message) -> 2112 case gen_tcp:send(Socket, Message) of 2113 ok -> 2114 ok; 2115 {error, Reason} -> 2116 Report = io_lib:format("gen_tcp:send/2 failed for " 2117 "reason ~p~n", [Reason]), 2118 error_logger:error_report(Report), 2119 %% If tcp does not work the only option is to terminate, 2120 %% this is the expected behavior under these circumstances. 2121 exit(normal) %% User will get error message from terminate/2 2122 end. 2123 2124activate_ctrl_connection(#state{csock = Socket, ctrl_data = {<<>>, _, _}}) -> 2125 activate_connection(Socket); 2126activate_ctrl_connection(#state{csock = Socket}) -> 2127 %% We have already received at least part of the next control message, 2128 %% that has been saved in ctrl_data, process this first. 2129 self() ! {tcp, Socket, <<>>}. 2130 2131activate_data_connection(#state{dsock = Socket}) -> 2132 activate_connection(Socket). 2133 2134activate_connection(Socket) -> 2135 inet:setopts(Socket, [{active, once}]). 2136 2137close_ctrl_connection(#state{csock = undefined}) -> 2138 ok; 2139close_ctrl_connection(#state{csock = Socket}) -> 2140 close_connection(Socket). 2141 2142close_data_connection(#state{dsock = undefined}) -> 2143 ok; 2144close_data_connection(#state{dsock = {lsock, Socket}}) -> 2145 close_connection(Socket); 2146close_data_connection(#state{dsock = Socket}) -> 2147 close_connection(Socket). 2148 2149close_connection(Socket) -> 2150 gen_tcp:close(Socket). 2151 2152%% ------------ FILE HANDELING ---------------------------------------- 2153 2154send_file(Fd, State) -> 2155 case file_read(Fd) of 2156 {ok, N, Bin} when N > 0-> 2157 send_data_message(State, Bin), 2158 progress_report({binary, Bin}, State), 2159 send_file(Fd, State); 2160 {ok, _, _} -> 2161 file_close(Fd), 2162 close_data_connection(State), 2163 progress_report({transfer_size, 0}, State), 2164 activate_ctrl_connection(State), 2165 {noreply, State#state{caller = transfer_file_second_phase, 2166 dsock = undefined}}; 2167 {error, Reason} -> 2168 gen_server:reply(State#state.client, {error, Reason}), 2169 {stop, normal, State#state{client = undefined}} 2170 end. 2171 2172file_open(File, Option) -> 2173 file:open(File, [raw, binary, Option]). 2174 2175file_close(Fd) -> 2176 file:close(Fd). 2177 2178file_read(Fd) -> 2179 case file:read(Fd, ?FILE_BUFSIZE) of 2180 {ok, Bytes} -> 2181 {ok, size(Bytes), Bytes}; 2182 eof -> 2183 {ok, 0, []}; 2184 Other -> 2185 Other 2186 end. 2187 2188file_write(Bytes, Fd) -> 2189 file:write(Fd, Bytes). 2190 2191%% -------------- MISC ---------------------------------------------- 2192 2193call(GenServer, Msg, Format) -> 2194 call(GenServer, Msg, Format, infinity). 2195call(GenServer, Msg, Format, Timeout) -> 2196 Req = {self(), Msg}, 2197 case (catch gen_server:call(GenServer, Req, Timeout)) of 2198 {ok, Bin} when is_binary(Bin) andalso (Format =:= string) -> 2199 {ok, binary_to_list(Bin)}; 2200 {'EXIT', _} -> 2201 {error, eclosed}; 2202 Result -> 2203 Result 2204 end. 2205 2206cast(GenServer, Msg) -> 2207 gen_server:cast(GenServer, {self(), Msg}). 2208 2209mk_cmd(Fmt, Args) -> 2210 [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok. 2211 2212pwd_result(Lines) -> 2213 {_, [?DOUBLE_QUOTE | Rest]} = 2214 lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines), 2215 {Dir, _} = 2216 lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest), 2217 Dir. 2218 2219%% is_verbose(Params) -> 2220%% check_param(verbose, Params). 2221 2222%% is_debug(Flags) -> 2223%% check_param(debug, Flags). 2224 2225%% is_trace(Flags) -> 2226%% check_param(trace, Flags). 2227 2228%% is_ipv6_disabled(Flags) -> 2229%% check_param(ip_v6_disabled, Flags). 2230 2231%% check_param(Param, Params) -> 2232%% lists:member(Param, Params). 2233 2234key_search(Key, List, Default) -> 2235 case lists:keysearch(Key, 1, List) of 2236 {value, {_,Val}} -> 2237 Val; 2238 false -> 2239 Default 2240 end. 2241 2242%% check_option(Pred, Value, Default) -> 2243%% case Pred(Value) of 2244%% true -> 2245%% Value; 2246%% false -> 2247%% Default 2248%% end. 2249 2250verbose(Lines, true, Direction) -> 2251 DirStr = 2252 case Direction of 2253 send -> 2254 "Sending: "; 2255 _ -> 2256 "Receiving: " 2257 end, 2258 Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR), 2259 erlang:display(DirStr++Str); 2260verbose(_, false,_) -> 2261 ok. 2262 2263progress(Options) -> 2264 ftp_progress:start_link(Options). 2265 2266progress_report(_, #state{progress = ignore}) -> 2267 ok; 2268progress_report(stop, #state{progress = ProgressPid}) -> 2269 ftp_progress:stop(ProgressPid); 2270progress_report({binary, Data}, #state{progress = ProgressPid}) -> 2271 ftp_progress:report(ProgressPid, {transfer_size, size(Data)}); 2272progress_report(Report, #state{progress = ProgressPid}) -> 2273 ftp_progress:report(ProgressPid, Report). 2274 2275 2276millisec_time() -> 2277 {A,B,C} = erlang:now(), 2278 A*1000000000+B*1000+(C div 1000).