PageRenderTime 60ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://github.com/bsmr-erlang/otp
Erlang | 2278 lines | 1454 code | 340 blank | 484 comment | 7 complexity | 3120cf89c88e6c05d7fe8e264c5f52ad MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0
  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. -module(ftp).
  23. -behaviour(gen_server).
  24. -behaviour(inets_service).
  25. %% API - Client interface
  26. -export([cd/2, close/1, delete/2, formaterror/1,
  27. lcd/2, lpwd/1, ls/1, ls/2,
  28. mkdir/2, nlist/1, nlist/2,
  29. open/1, open/2,
  30. pwd/1, quote/2,
  31. recv/2, recv/3, recv_bin/2,
  32. recv_chunk_start/2, recv_chunk/1,
  33. rename/3, rmdir/2,
  34. send/2, send/3, send_bin/3,
  35. send_chunk_start/2, send_chunk/2, send_chunk_end/1,
  36. type/2, user/3, user/4, account/2,
  37. append/3, append/2, append_bin/3,
  38. append_chunk/2, append_chunk_end/1, append_chunk_start/2, info/1]).
  39. %% gen_server callbacks
  40. -export([init/1, handle_call/3, handle_cast/2,
  41. handle_info/2, terminate/2, code_change/3]).
  42. %% supervisor callbacks
  43. -export([start_link/1, start_link/2]).
  44. %% Behavior callbacks
  45. -export([start_standalone/1, start_service/1,
  46. stop_service/1, services/0, service_info/1]).
  47. -include("ftp_internal.hrl").
  48. %% Constante used in internal state definition
  49. -define(CONNECTION_TIMEOUT, 60*1000).
  50. -define(DATA_ACCEPT_TIMEOUT, infinity).
  51. -define(DEFAULT_MODE, passive).
  52. -define(PROGRESS_DEFAULT, ignore).
  53. %% Internal Constants
  54. -define(FTP_PORT, 21).
  55. -define(FILE_BUFSIZE, 4096).
  56. %% Internal state
  57. -record(state, {
  58. csock = undefined, % socket() - Control connection socket
  59. dsock = undefined, % socket() - Data connection socket
  60. verbose = false, % boolean()
  61. ldir = undefined, % string() - Current local directory
  62. type = ftp_server_default, % atom() - binary | ascii
  63. chunk = false, % boolean() - Receiving data chunks
  64. mode = ?DEFAULT_MODE, % passive | active
  65. timeout = ?CONNECTION_TIMEOUT, % integer()
  66. %% Data received so far on the data connection
  67. data = <<>>, % binary()
  68. %% Data received so far on the control connection
  69. %% {BinStream, AccLines}. If a binary sequence
  70. %% ends with ?CR then keep it in the binary to
  71. %% be able to detect if the next received byte is ?LF
  72. %% and hence the end of the response is reached!
  73. ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus}
  74. %% pid() - Client pid (note not the same as "From")
  75. owner = undefined,
  76. client = undefined, % "From" to be used in gen_server:reply/2
  77. %% Function that activated a connection and maybe some
  78. %% data needed further on.
  79. caller = undefined, % term()
  80. ipfamily, % inet | inet6 | inet6fb4
  81. progress = ignore, % ignore | pid()
  82. dtimeout = ?DATA_ACCEPT_TIMEOUT % non_neg_integer() | infinity
  83. }).
  84. -type shortage_reason() :: 'etnospc' | 'epnospc'.
  85. -type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'.
  86. -type common_reason() :: 'econn' | 'eclosed' | term().
  87. -type file_write_error_reason() :: term(). % See file:write for more info
  88. %%%=========================================================================
  89. %%% API - CLIENT FUNCTIONS
  90. %%%=========================================================================
  91. %%--------------------------------------------------------------------------
  92. %% open(HostOrOtpList, <Port>, <Flags>) -> {ok, Pid} | {error, ehost}
  93. %% HostOrOtpList = string() | [{option_list, Options}]
  94. %% Port = integer(),
  95. %% Flags = [Flag],
  96. %% Flag = verbose | debug | trace
  97. %%
  98. %% Description: Start an ftp client and connect to a host.
  99. %%--------------------------------------------------------------------------
  100. -spec open(Host :: string() | inet:ip_address()) ->
  101. {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
  102. %% <BACKWARD-COMPATIBILLITY>
  103. open({option_list, Options}) when is_list(Options) ->
  104. try
  105. {ok, StartOptions} = start_options(Options),
  106. {ok, OpenOptions} = open_options(Options),
  107. case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
  108. {ok, Pid} ->
  109. call(Pid, {open, ip_comm, OpenOptions}, plain);
  110. Error1 ->
  111. Error1
  112. end
  113. catch
  114. throw:Error2 ->
  115. Error2
  116. end;
  117. %% </BACKWARD-COMPATIBILLITY>
  118. open(Host) ->
  119. open(Host, []).
  120. -spec open(Host :: string() | inet:ip_address(), Opts :: list()) ->
  121. {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
  122. %% <BACKWARD-COMPATIBILLITY>
  123. open(Host, Port) when is_integer(Port) ->
  124. open(Host, [{port, Port}]);
  125. %% </BACKWARD-COMPATIBILLITY>
  126. open(Host, Opts) when is_list(Opts) ->
  127. ?fcrt("open", [{host, Host}, {opts, Opts}]),
  128. try
  129. {ok, StartOptions} = start_options(Opts),
  130. ?fcrt("open", [{start_options, StartOptions}]),
  131. {ok, OpenOptions} = open_options([{host, Host}|Opts]),
  132. ?fcrt("open", [{open_options, OpenOptions}]),
  133. case start_link(StartOptions, []) of
  134. {ok, Pid} ->
  135. ?fcrt("open - ok", [{pid, Pid}]),
  136. call(Pid, {open, ip_comm, OpenOptions}, plain);
  137. Error1 ->
  138. ?fcrt("open - error", [{error1, Error1}]),
  139. Error1
  140. end
  141. catch
  142. throw:Error2 ->
  143. ?fcrt("open - error", [{error2, Error2}]),
  144. Error2
  145. end.
  146. %%--------------------------------------------------------------------------
  147. %% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn}
  148. %% | {error, eacct}
  149. %% Pid = pid(),
  150. %% User = Pass = Acc = string()
  151. %%
  152. %% Description: Login with or without a supplied account name.
  153. %%--------------------------------------------------------------------------
  154. -spec user(Pid :: pid(),
  155. User :: string(),
  156. Pass :: string()) ->
  157. 'ok' | {'error', Reason :: 'euser' | common_reason()}.
  158. user(Pid, User, Pass) ->
  159. call(Pid, {user, User, Pass}, atom).
  160. -spec user(Pid :: pid(),
  161. User :: string(),
  162. Pass :: string(),
  163. Acc :: string()) ->
  164. 'ok' | {'error', Reason :: 'euser' | common_reason()}.
  165. user(Pid, User, Pass, Acc) ->
  166. call(Pid, {user, User, Pass, Acc}, atom).
  167. %%--------------------------------------------------------------------------
  168. %% account(Pid, Acc) -> ok | {error, eacct}
  169. %% Pid = pid()
  170. %% Acc= string()
  171. %%
  172. %% Description: Set a user Account.
  173. %%--------------------------------------------------------------------------
  174. -spec account(Pid :: pid(), Acc :: string()) ->
  175. 'ok' | {'error', Reason :: 'eacct' | common_reason()}.
  176. account(Pid, Acc) ->
  177. call(Pid, {account, Acc}, atom).
  178. %%--------------------------------------------------------------------------
  179. %% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn}
  180. %% Pid = pid()
  181. %% Dir = string()
  182. %%
  183. %% Description: Get the current working directory at remote server.
  184. %%--------------------------------------------------------------------------
  185. -spec pwd(Pid :: pid()) ->
  186. {'ok', Dir :: string()} |
  187. {'error', Reason :: restriction_reason() | common_reason()}.
  188. pwd(Pid) ->
  189. call(Pid, pwd, ctrl).
  190. %%--------------------------------------------------------------------------
  191. %% lpwd(Pid) -> {ok, Dir}
  192. %% Pid = pid()
  193. %% Dir = string()
  194. %%
  195. %% Description: Get the current working directory at local server.
  196. %%--------------------------------------------------------------------------
  197. -spec lpwd(Pid :: pid()) ->
  198. {'ok', Dir :: string()}.
  199. lpwd(Pid) ->
  200. call(Pid, lpwd, string).
  201. %%--------------------------------------------------------------------------
  202. %% cd(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
  203. %% Pid = pid()
  204. %% Dir = string()
  205. %%
  206. %% Description: Change current working directory at remote server.
  207. %%--------------------------------------------------------------------------
  208. -spec cd(Pid :: pid(), Dir :: string()) ->
  209. 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
  210. cd(Pid, Dir) ->
  211. call(Pid, {cd, Dir}, atom).
  212. %%--------------------------------------------------------------------------
  213. %% lcd(Pid, Dir) -> ok | {error, epath}
  214. %% Pid = pid()
  215. %% Dir = string()
  216. %%
  217. %% Description: Change current working directory for the local client.
  218. %%--------------------------------------------------------------------------
  219. -spec lcd(Pid :: pid(), Dir :: string()) ->
  220. 'ok' | {'error', Reason :: restriction_reason()}.
  221. lcd(Pid, Dir) ->
  222. call(Pid, {lcd, Dir}, string).
  223. %%--------------------------------------------------------------------------
  224. %% ls(Pid) -> Result
  225. %% ls(Pid, <Dir>) -> Result
  226. %%
  227. %% Pid = pid()
  228. %% Dir = string()
  229. %% Result = {ok, Listing} | {error, Reason}
  230. %% Listing = string()
  231. %% Reason = epath | elogin | econn
  232. %%
  233. %% Description: Returns a list of files in long format.
  234. %%--------------------------------------------------------------------------
  235. -spec ls(Pid :: pid()) ->
  236. {'ok', Listing :: string()} |
  237. {'error', Reason :: restriction_reason() | common_reason()}.
  238. ls(Pid) ->
  239. ls(Pid, "").
  240. -spec ls(Pid :: pid(), Dir :: string()) ->
  241. {'ok', Listing :: string()} |
  242. {'error', Reason :: restriction_reason() | common_reason()}.
  243. ls(Pid, Dir) ->
  244. call(Pid, {dir, long, Dir}, string).
  245. %%--------------------------------------------------------------------------
  246. %% nlist(Pid) -> Result
  247. %% nlist(Pid, Pathname) -> Result
  248. %%
  249. %% Pid = pid()
  250. %% Pathname = string()
  251. %% Result = {ok, Listing} | {error, Reason}
  252. %% Listing = string()
  253. %% Reason = epath | elogin | econn
  254. %%
  255. %% Description: Returns a list of files in short format
  256. %%--------------------------------------------------------------------------
  257. -spec nlist(Pid :: pid()) ->
  258. {'ok', Listing :: string()} |
  259. {'error', Reason :: restriction_reason() | common_reason()}.
  260. nlist(Pid) ->
  261. nlist(Pid, "").
  262. -spec nlist(Pid :: pid(), Pathname :: string()) ->
  263. {'ok', Listing :: string()} |
  264. {'error', Reason :: restriction_reason() | common_reason()}.
  265. nlist(Pid, Dir) ->
  266. call(Pid, {dir, short, Dir}, string).
  267. %%--------------------------------------------------------------------------
  268. %% rename(Pid, Old, New) -> ok | {error, epath} | {error, elogin}
  269. %% | {error, econn}
  270. %% Pid = pid()
  271. %% CurrFile = NewFile = string()
  272. %%
  273. %% Description: Rename a file at remote server.
  274. %%--------------------------------------------------------------------------
  275. -spec rename(Pid :: pid(), Old :: string(), New :: string()) ->
  276. 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
  277. rename(Pid, Old, New) ->
  278. call(Pid, {rename, Old, New}, string).
  279. %%--------------------------------------------------------------------------
  280. %% delete(Pid, File) -> ok | {error, epath} | {error, elogin} |
  281. %% {error, econn}
  282. %% Pid = pid()
  283. %% File = string()
  284. %%
  285. %% Description: Remove file at remote server.
  286. %%--------------------------------------------------------------------------
  287. -spec delete(Pid :: pid(), File :: string()) ->
  288. 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
  289. delete(Pid, File) ->
  290. call(Pid, {delete, File}, string).
  291. %%--------------------------------------------------------------------------
  292. %% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
  293. %% Pid = pid(),
  294. %% Dir = string()
  295. %%
  296. %% Description: Make directory at remote server.
  297. %%--------------------------------------------------------------------------
  298. -spec mkdir(Pid :: pid(), Dir :: string()) ->
  299. 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
  300. mkdir(Pid, Dir) ->
  301. call(Pid, {mkdir, Dir}, atom).
  302. %%--------------------------------------------------------------------------
  303. %% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
  304. %% Pid = pid(),
  305. %% Dir = string()
  306. %%
  307. %% Description: Remove directory at remote server.
  308. %%--------------------------------------------------------------------------
  309. -spec rmdir(Pid :: pid(), Dir :: string()) ->
  310. 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
  311. rmdir(Pid, Dir) ->
  312. call(Pid, {rmdir, Dir}, atom).
  313. %%--------------------------------------------------------------------------
  314. %% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn}
  315. %% Pid = pid()
  316. %% Type = ascii | binary
  317. %%
  318. %% Description: Set transfer type.
  319. %%--------------------------------------------------------------------------
  320. -spec type(Pid :: pid(), Type :: ascii | binary) ->
  321. 'ok' |
  322. {'error', Reason :: 'etype' | restriction_reason() | common_reason()}.
  323. type(Pid, Type) ->
  324. call(Pid, {type, Type}, atom).
  325. %%--------------------------------------------------------------------------
  326. %% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} |
  327. %% {error, elogin} | {error, econn}
  328. %% Pid = pid()
  329. %% RemoteFileName = LocalFileName = string()
  330. %%
  331. %% Description: Transfer file from remote server.
  332. %%--------------------------------------------------------------------------
  333. -spec recv(Pid :: pid(), RemoteFileName :: string()) ->
  334. 'ok' | {'error', Reason :: restriction_reason() |
  335. common_reason() |
  336. file_write_error_reason()}.
  337. recv(Pid, RemotFileName) ->
  338. recv(Pid, RemotFileName, RemotFileName).
  339. -spec recv(Pid :: pid(),
  340. RemoteFileName :: string(),
  341. LocalFileName :: string()) ->
  342. 'ok' | {'error', Reason :: term()}.
  343. recv(Pid, RemotFileName, LocalFileName) ->
  344. call(Pid, {recv, RemotFileName, LocalFileName}, atom).
  345. %%--------------------------------------------------------------------------
  346. %% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin}
  347. %% | {error, econn}
  348. %% Pid = pid()
  349. %% RemoteFile = string()
  350. %% Bin = binary()
  351. %%
  352. %% Description: Transfer file from remote server into binary.
  353. %%--------------------------------------------------------------------------
  354. -spec recv_bin(Pid :: pid(),
  355. RemoteFile :: string()) ->
  356. {'ok', Bin :: binary()} |
  357. {'error', Reason :: restriction_reason() | common_reason()}.
  358. recv_bin(Pid, RemoteFile) ->
  359. call(Pid, {recv_bin, RemoteFile}, bin).
  360. %%--------------------------------------------------------------------------
  361. %% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
  362. %% | {error, econn}
  363. %% Pid = pid()
  364. %% RemoteFile = string()
  365. %%
  366. %% Description: Start receive of chunks of remote file.
  367. %%--------------------------------------------------------------------------
  368. -spec recv_chunk_start(Pid :: pid(),
  369. RemoteFile :: string()) ->
  370. 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
  371. recv_chunk_start(Pid, RemoteFile) ->
  372. call(Pid, {recv_chunk_start, RemoteFile}, atom).
  373. %%--------------------------------------------------------------------------
  374. %% recv_chunk(Pid, RemoteFile) -> ok | {ok, Bin} | {error, Reason}
  375. %% Pid = pid()
  376. %% RemoteFile = string()
  377. %%
  378. %% Description: Transfer file from remote server into binary in chunks
  379. %%--------------------------------------------------------------------------
  380. -spec recv_chunk(Pid :: pid()) ->
  381. 'ok' |
  382. {'ok', Bin :: binary()} |
  383. {'error', Reason :: restriction_reason() | common_reason()}.
  384. recv_chunk(Pid) ->
  385. call(Pid, recv_chunk, atom).
  386. %%--------------------------------------------------------------------------
  387. %% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
  388. %% | {error, elogin}
  389. %% | {error, econn}
  390. %% Pid = pid()
  391. %% LocalFileName = RemotFileName = string()
  392. %%
  393. %% Description: Transfer file to remote server.
  394. %%--------------------------------------------------------------------------
  395. -spec send(Pid :: pid(), LocalFileName :: string()) ->
  396. 'ok' |
  397. {'error', Reason :: restriction_reason() |
  398. common_reason() |
  399. shortage_reason()}.
  400. send(Pid, LocalFileName) ->
  401. send(Pid, LocalFileName, LocalFileName).
  402. -spec send(Pid :: pid(),
  403. LocalFileName :: string(),
  404. RemoteFileName :: string()) ->
  405. 'ok' |
  406. {'error', Reason :: restriction_reason() |
  407. common_reason() |
  408. shortage_reason()}.
  409. send(Pid, LocalFileName, RemotFileName) ->
  410. call(Pid, {send, LocalFileName, RemotFileName}, atom).
  411. %%--------------------------------------------------------------------------
  412. %% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
  413. %% | {error, enotbinary} | {error, econn}
  414. %% Pid = pid()
  415. %% Bin = binary()
  416. %% RemoteFile = string()
  417. %%
  418. %% Description: Transfer a binary to a remote file.
  419. %%--------------------------------------------------------------------------
  420. -spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) ->
  421. 'ok' |
  422. {'error', Reason :: restriction_reason() |
  423. common_reason() |
  424. shortage_reason()}.
  425. send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
  426. call(Pid, {send_bin, Bin, RemoteFile}, atom);
  427. send_bin(_Pid, _Bin, _RemoteFile) ->
  428. {error, enotbinary}.
  429. %%--------------------------------------------------------------------------
  430. %% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
  431. %% | {error, econn}
  432. %% Pid = pid()
  433. %% RemoteFile = string()
  434. %%
  435. %% Description: Start transfer of chunks to remote file.
  436. %%--------------------------------------------------------------------------
  437. -spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
  438. 'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
  439. send_chunk_start(Pid, RemoteFile) ->
  440. call(Pid, {send_chunk_start, RemoteFile}, atom).
  441. %%--------------------------------------------------------------------------
  442. %% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} |
  443. %% {error, epath} | {error, econn}
  444. %% Pid = pid()
  445. %% RemoteFile = string()
  446. %%
  447. %% Description: Start append chunks of data to remote file.
  448. %%--------------------------------------------------------------------------
  449. -spec append_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
  450. 'ok' | {'error', Reason :: term()}.
  451. append_chunk_start(Pid, RemoteFile) ->
  452. call(Pid, {append_chunk_start, RemoteFile}, atom).
  453. %%--------------------------------------------------------------------------
  454. %% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
  455. %% | {error, echunk} | {error, econn}
  456. %% Pid = pid()
  457. %% Bin = binary().
  458. %%
  459. %% Purpose: Send chunk to remote file.
  460. %%--------------------------------------------------------------------------
  461. -spec send_chunk(Pid :: pid(), Bin :: binary()) ->
  462. 'ok' |
  463. {'error', Reason :: 'echunk' |
  464. restriction_reason() |
  465. common_reason()}.
  466. send_chunk(Pid, Bin) when is_binary(Bin) ->
  467. call(Pid, {transfer_chunk, Bin}, atom);
  468. send_chunk(_Pid, _Bin) ->
  469. {error, enotbinary}.
  470. %%--------------------------------------------------------------------------
  471. %% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
  472. %% | {error, echunk} | {error, econn}
  473. %% Pid = pid()
  474. %% Bin = binary()
  475. %%
  476. %% Description: Append chunk to remote file.
  477. %%--------------------------------------------------------------------------
  478. -spec append_chunk(Pid :: pid(), Bin :: binary()) ->
  479. 'ok' |
  480. {'error', Reason :: 'echunk' |
  481. restriction_reason() |
  482. common_reason()}.
  483. append_chunk(Pid, Bin) when is_binary(Bin) ->
  484. call(Pid, {transfer_chunk, Bin}, atom);
  485. append_chunk(_Pid, _Bin) ->
  486. {error, enotbinary}.
  487. %%--------------------------------------------------------------------------
  488. %% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
  489. %% | {error, econn}
  490. %% Pid = pid()
  491. %%
  492. %% Description: End sending of chunks to remote file.
  493. %%--------------------------------------------------------------------------
  494. -spec send_chunk_end(Pid :: pid()) ->
  495. 'ok' |
  496. {'error', Reason :: restriction_reason() |
  497. common_reason() |
  498. shortage_reason()}.
  499. send_chunk_end(Pid) ->
  500. call(Pid, chunk_end, atom).
  501. %%--------------------------------------------------------------------------
  502. %% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
  503. %% | {error, econn}
  504. %% Pid = pid()
  505. %%
  506. %% Description: End appending of chunks to remote file.
  507. %%--------------------------------------------------------------------------
  508. -spec append_chunk_end(Pid :: pid()) ->
  509. 'ok' |
  510. {'error', Reason :: restriction_reason() |
  511. common_reason() |
  512. shortage_reason()}.
  513. append_chunk_end(Pid) ->
  514. call(Pid, chunk_end, atom).
  515. %%--------------------------------------------------------------------------
  516. %% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
  517. %% | {error, elogin}
  518. %% | {error, econn}
  519. %% Pid = pid()
  520. %% LocalFileName = RemotFileName = string()
  521. %%
  522. %% Description: Append the local file to the remote file
  523. %%--------------------------------------------------------------------------
  524. -spec append(Pid :: pid(), LocalFileName :: string()) ->
  525. 'ok' |
  526. {'error', Reason :: 'epath' |
  527. 'elogin' |
  528. 'etnospc' |
  529. 'epnospc' |
  530. 'efnamena' | common_reason()}.
  531. append(Pid, LocalFileName) ->
  532. append(Pid, LocalFileName, LocalFileName).
  533. -spec append(Pid :: pid(),
  534. LocalFileName :: string(),
  535. RemoteFileName :: string()) ->
  536. 'ok' | {'error', Reason :: term()}.
  537. append(Pid, LocalFileName, RemotFileName) ->
  538. call(Pid, {append, LocalFileName, RemotFileName}, atom).
  539. %%--------------------------------------------------------------------------
  540. %% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
  541. %% | {error, enotbinary} | {error, econn}
  542. %% Pid = pid()
  543. %% Bin = binary()
  544. %% RemoteFile = string()
  545. %%
  546. %% Purpose: Append a binary to a remote file.
  547. %%--------------------------------------------------------------------------
  548. -spec append_bin(Pid :: pid(),
  549. Bin :: binary(),
  550. RemoteFile :: string()) ->
  551. 'ok' |
  552. {'error', Reason :: restriction_reason() |
  553. common_reason() |
  554. shortage_reason()}.
  555. append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
  556. call(Pid, {append_bin, Bin, RemoteFile}, atom);
  557. append_bin(_Pid, _Bin, _RemoteFile) ->
  558. {error, enotbinary}.
  559. %%--------------------------------------------------------------------------
  560. %% quote(Pid, Cmd) -> list()
  561. %% Pid = pid()
  562. %% Cmd = string()
  563. %%
  564. %% Description: Send arbitrary ftp command.
  565. %%--------------------------------------------------------------------------
  566. -spec quote(Pid :: pid(), Cmd :: string()) -> list().
  567. quote(Pid, Cmd) when is_list(Cmd) ->
  568. call(Pid, {quote, Cmd}, atom).
  569. %%--------------------------------------------------------------------------
  570. %% close(Pid) -> ok
  571. %% Pid = pid()
  572. %%
  573. %% Description: End the ftp session.
  574. %%--------------------------------------------------------------------------
  575. -spec close(Pid :: pid()) -> 'ok'.
  576. close(Pid) ->
  577. cast(Pid, close),
  578. ok.
  579. %%--------------------------------------------------------------------------
  580. %% formaterror(Tag) -> string()
  581. %% Tag = atom() | {error, atom()}
  582. %%
  583. %% Description: Return diagnostics.
  584. %%--------------------------------------------------------------------------
  585. -spec formaterror(Tag :: term()) -> string().
  586. formaterror(Tag) ->
  587. ftp_response:error_string(Tag).
  588. info(Pid) ->
  589. call(Pid, info, list).
  590. %%%========================================================================
  591. %%% Behavior callbacks
  592. %%%========================================================================
  593. start_standalone(Options) ->
  594. try
  595. {ok, StartOptions} = start_options(Options),
  596. {ok, OpenOptions} = open_options(Options),
  597. case start_link(StartOptions, []) of
  598. {ok, Pid} ->
  599. call(Pid, {open, ip_comm, OpenOptions}, plain);
  600. Error1 ->
  601. Error1
  602. end
  603. catch
  604. throw:Error2 ->
  605. Error2
  606. end.
  607. start_service(Options) ->
  608. try
  609. {ok, StartOptions} = start_options(Options),
  610. {ok, OpenOptions} = open_options(Options),
  611. case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
  612. {ok, Pid} ->
  613. call(Pid, {open, ip_comm, OpenOptions}, plain);
  614. Error1 ->
  615. Error1
  616. end
  617. catch
  618. throw:Error2 ->
  619. Error2
  620. end.
  621. stop_service(Pid) ->
  622. close(Pid).
  623. services() ->
  624. [{ftpc, Pid} || {_, Pid, _, _} <-
  625. supervisor:which_children(ftp_sup)].
  626. service_info(Pid) ->
  627. {ok, Info} = call(Pid, info, list),
  628. {ok, [proplists:lookup(mode, Info),
  629. proplists:lookup(local_port, Info),
  630. proplists:lookup(peer, Info),
  631. proplists:lookup(peer_port, Info)]}.
  632. %% This function extracts the start options from the
  633. %% Valid options:
  634. %% debug,
  635. %% verbose
  636. %% ipfamily
  637. %% priority
  638. %% flags (for backward compatibillity)
  639. start_options(Options) ->
  640. ?fcrt("start_options", [{options, Options}]),
  641. case lists:keysearch(flags, 1, Options) of
  642. {value, {flags, Flags}} ->
  643. Verbose = lists:member(verbose, Flags),
  644. IsTrace = lists:member(trace, Flags),
  645. IsDebug = lists:member(debug, Flags),
  646. DebugLevel =
  647. if
  648. (IsTrace =:= true) ->
  649. trace;
  650. IsDebug =:= true ->
  651. debug;
  652. true ->
  653. disable
  654. end,
  655. {ok, [{verbose, Verbose},
  656. {debug, DebugLevel},
  657. {priority, low}]};
  658. false ->
  659. ValidateVerbose =
  660. fun(true) -> true;
  661. (false) -> true;
  662. (_) -> false
  663. end,
  664. ValidateDebug =
  665. fun(trace) -> true;
  666. (debug) -> true;
  667. (disable) -> true;
  668. (_) -> false
  669. end,
  670. ValidatePriority =
  671. fun(low) -> true;
  672. (normal) -> true;
  673. (high) -> true;
  674. (_) -> false
  675. end,
  676. ValidOptions =
  677. [{verbose, ValidateVerbose, false, false},
  678. {debug, ValidateDebug, false, disable},
  679. {priority, ValidatePriority, false, low}],
  680. validate_options(Options, ValidOptions, [])
  681. end.
  682. %% This function extracts and validates the open options from the
  683. %% Valid options:
  684. %% mode
  685. %% host
  686. %% port
  687. %% timeout
  688. %% dtimeout
  689. %% progress
  690. open_options(Options) ->
  691. ?fcrt("open_options", [{options, Options}]),
  692. ValidateMode =
  693. fun(active) -> true;
  694. (passive) -> true;
  695. (_) -> false
  696. end,
  697. ValidateHost =
  698. fun(Host) when is_list(Host) ->
  699. true;
  700. (Host) when is_tuple(Host) andalso
  701. ((size(Host) =:= 4) orelse (size(Host) =:= 8)) ->
  702. true;
  703. (_) ->
  704. false
  705. end,
  706. ValidatePort =
  707. fun(Port) when is_integer(Port) andalso (Port > 0) -> true;
  708. (_) -> false
  709. end,
  710. ValidateIpFamily =
  711. fun(inet) -> true;
  712. (inet6) -> true;
  713. (inet6fb4) -> true;
  714. (_) -> false
  715. end,
  716. ValidateTimeout =
  717. fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true;
  718. (_) -> false
  719. end,
  720. ValidateDTimeout =
  721. fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true;
  722. (infinity) -> true;
  723. (_) -> false
  724. end,
  725. ValidateProgress =
  726. fun(ignore) ->
  727. true;
  728. ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso
  729. is_atom(Func) ->
  730. true;
  731. (_) ->
  732. false
  733. end,
  734. ValidOptions =
  735. [{mode, ValidateMode, false, ?DEFAULT_MODE},
  736. {host, ValidateHost, true, ehost},
  737. {port, ValidatePort, false, ?FTP_PORT},
  738. {ipfamily, ValidateIpFamily, false, inet},
  739. {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT},
  740. {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT},
  741. {progress, ValidateProgress, false, ?PROGRESS_DEFAULT}],
  742. validate_options(Options, ValidOptions, []).
  743. validate_options([], [], Acc) ->
  744. ?fcrt("validate_options -> done", [{acc, Acc}]),
  745. {ok, lists:reverse(Acc)};
  746. validate_options([], ValidOptions, Acc) ->
  747. ?fcrt("validate_options -> done",
  748. [{valid_options, ValidOptions}, {acc, Acc}]),
  749. %% Check if any mandatory options are missing!
  750. case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of
  751. [] ->
  752. Defaults =
  753. [{Key, Default} || {Key, _, _, Default} <- ValidOptions],
  754. {ok, lists:reverse(Defaults ++ Acc)};
  755. [{_, Reason}|_Missing] ->
  756. throw({error, Reason})
  757. end;
  758. validate_options([{Key, Value}|Options], ValidOptions, Acc) ->
  759. ?fcrt("validate_options -> check",
  760. [{key, Key}, {value, Value}, {acc, Acc}]),
  761. case lists:keysearch(Key, 1, ValidOptions) of
  762. {value, {Key, Validate, _, Default}} ->
  763. case (catch Validate(Value)) of
  764. true ->
  765. ?fcrt("validate_options -> check - accept", []),
  766. NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
  767. validate_options(Options, NewValidOptions,
  768. [{Key, Value} | Acc]);
  769. _ ->
  770. ?fcrt("validate_options -> check - reject",
  771. [{default, Default}]),
  772. NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
  773. validate_options(Options, NewValidOptions,
  774. [{Key, Default} | Acc])
  775. end;
  776. false ->
  777. validate_options(Options, ValidOptions, Acc)
  778. end;
  779. validate_options([_|Options], ValidOptions, Acc) ->
  780. validate_options(Options, ValidOptions, Acc).
  781. %%%========================================================================
  782. %%% gen_server callback functions
  783. %%%========================================================================
  784. %%-------------------------------------------------------------------------
  785. %% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}
  786. %% Description: Initiates the erlang process that manages a ftp connection.
  787. %%-------------------------------------------------------------------------
  788. init(Options) ->
  789. process_flag(trap_exit, true),
  790. %% Keep track of the client
  791. {value, {client, Client}} = lists:keysearch(client, 1, Options),
  792. erlang:monitor(process, Client),
  793. %% Make sure inet is started
  794. inet_db:start(),
  795. %% Where are we
  796. {ok, Dir} = file:get_cwd(),
  797. %% Maybe activate dbg
  798. case key_search(debug, Options, disable) of
  799. trace ->
  800. dbg:tracer(),
  801. dbg:p(all, [call]),
  802. dbg:tpl(ftp, [{'_', [], [{return_trace}]}]),
  803. dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]),
  804. dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]);
  805. debug ->
  806. dbg:tracer(),
  807. dbg:p(all, [call]),
  808. dbg:tp(ftp, [{'_', [], [{return_trace}]}]),
  809. dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]),
  810. dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]);
  811. _ ->
  812. %% Keep silent
  813. ok
  814. end,
  815. %% Verbose?
  816. Verbose = key_search(verbose, Options, false),
  817. %% IpFamily?
  818. IpFamily = key_search(ipfamily, Options, inet),
  819. State = #state{owner = Client,
  820. verbose = Verbose,
  821. ipfamily = IpFamily,
  822. ldir = Dir},
  823. %% Set process prio
  824. Priority = key_search(priority, Options, low),
  825. process_flag(priority, Priority),
  826. %% And we are done
  827. {ok, State}.
  828. %%--------------------------------------------------------------------------
  829. %% handle_call(Request, From, State) -> {reply, Reply, State} |
  830. %% {reply, Reply, State, Timeout} |
  831. %% {noreply, State} |
  832. %% {noreply, State, Timeout} |
  833. %% {stop, Reason, Reply, State} |
  834. %% Description: Handle incoming requests.
  835. %%-------------------------------------------------------------------------
  836. %% Anyone can ask this question
  837. handle_call({_, info}, _, #state{verbose = Verbose,
  838. mode = Mode,
  839. timeout = Timeout,
  840. ipfamily = IpFamily,
  841. csock = Socket,
  842. progress = Progress} = State) ->
  843. {ok, {_, LocalPort}} = inet:sockname(Socket),
  844. {ok, {Address, Port}} = inet:peername(Socket),
  845. Options = [{verbose, Verbose},
  846. {ipfamily, IpFamily},
  847. {mode, Mode},
  848. {peer, Address},
  849. {peer_port, Port},
  850. {local_port, LocalPort},
  851. {timeout, Timeout},
  852. {progress, Progress}],
  853. {reply, {ok, Options}, State};
  854. %% But everything else must come from the owner
  855. handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid ->
  856. {reply, {error, not_connection_owner}, State};
  857. handle_call({_, {open, ip_comm, Opts}}, From, State) ->
  858. ?fcrd("handle_call(open)", [{opts, Opts}]),
  859. case key_search(host, Opts, undefined) of
  860. undefined ->
  861. {stop, normal, {error, ehost}, State};
  862. Host ->
  863. Mode = key_search(mode, Opts, ?DEFAULT_MODE),
  864. Port = key_search(port, Opts, ?FTP_PORT),
  865. Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT),
  866. DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT),
  867. Progress = key_search(progress, Opts, ignore),
  868. IpFamily = key_search(ipfamily, Opts, inet),
  869. State2 = State#state{client = From,
  870. mode = Mode,
  871. progress = progress(Progress),
  872. ipfamily = IpFamily,
  873. dtimeout = DTimeout},
  874. ?fcrd("handle_call(open) -> setup ctrl connection with",
  875. [{host, Host}, {port, Port}, {timeout, Timeout}]),
  876. case setup_ctrl_connection(Host, Port, Timeout, State2) of
  877. {ok, State3, WaitTimeout} ->
  878. ?fcrd("handle_call(open) -> ctrl connection setup done",
  879. [{waittimeout, WaitTimeout}]),
  880. {noreply, State3, WaitTimeout};
  881. {error, Reason} ->
  882. ?fcrd("handle_call(open) -> ctrl connection setup failed",
  883. [{reason, Reason}]),
  884. gen_server:reply(From, {error, ehost}),
  885. {stop, normal, State2#state{client = undefined}}
  886. end
  887. end;
  888. handle_call({_, {open, ip_comm, Host, Opts}}, From, State) ->
  889. Mode = key_search(mode, Opts, ?DEFAULT_MODE),
  890. Port = key_search(port, Opts, ?FTP_PORT),
  891. Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT),
  892. DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT),
  893. Progress = key_search(progress, Opts, ignore),
  894. State2 = State#state{client = From,
  895. mode = Mode,
  896. progress = progress(Progress),
  897. dtimeout = DTimeout},
  898. case setup_ctrl_connection(Host, Port, Timeout, State2) of
  899. {ok, State3, WaitTimeout} ->
  900. {noreply, State3, WaitTimeout};
  901. {error, _Reason} ->
  902. gen_server:reply(From, {error, ehost}),
  903. {stop, normal, State2#state{client = undefined}}
  904. end;
  905. handle_call({_, {user, User, Password}}, From,
  906. #state{csock = CSock} = State) when (CSock =/= undefined) ->
  907. handle_user(User, Password, "", State#state{client = From});
  908. handle_call({_, {user, User, Password, Acc}}, From,
  909. #state{csock = CSock} = State) when (CSock =/= undefined) ->
  910. handle_user(User, Password, Acc, State#state{client = From});
  911. handle_call({_, {account, Acc}}, From, State)->
  912. handle_user_account(Acc, State#state{client = From});
  913. handle_call({_, pwd}, From, #state{chunk = false} = State) ->
  914. send_ctrl_message(State, mk_cmd("PWD", [])),
  915. activate_ctrl_connection(State),
  916. {noreply, State#state{client = From, caller = pwd}};
  917. handle_call({_, lpwd}, From, #state{ldir = LDir} = State) ->
  918. {reply, {ok, LDir}, State#state{client = From}};
  919. handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State) ->
  920. send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])),
  921. activate_ctrl_connection(State),
  922. {noreply, State#state{client = From, caller = cd}};
  923. handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) ->
  924. LDir = filename:absname(Dir, LDir0),
  925. case file:read_file_info(LDir) of %% FIX better check that LDir is a dir.
  926. {ok, _ } ->
  927. {reply, ok, State#state{ldir = LDir}};
  928. _ ->
  929. {reply, {error, epath}, State}
  930. end;
  931. handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From,
  932. #state{chunk = false} = State) ->
  933. setup_data_connection(State#state{caller = {dir, Dir, Len},
  934. client = From});
  935. handle_call({_, {rename, CurrFile, NewFile}}, From,
  936. #state{chunk = false} = State) ->
  937. send_ctrl_message(State, mk_cmd("RNFR ~s", [CurrFile])),
  938. activate_ctrl_connection(State),
  939. {noreply, State#state{caller = {rename, NewFile}, client = From}};
  940. handle_call({_, {delete, File}}, {_Pid, _} = From,
  941. #state{chunk = false} = State) ->
  942. send_ctrl_message(State, mk_cmd("DELE ~s", [File])),
  943. activate_ctrl_connection(State),
  944. {noreply, State#state{client = From}};
  945. handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State) ->
  946. send_ctrl_message(State, mk_cmd("MKD ~s", [Dir])),
  947. activate_ctrl_connection(State),
  948. {noreply, State#state{client = From}};
  949. handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State) ->
  950. send_ctrl_message(State, mk_cmd("RMD ~s", [Dir])),
  951. activate_ctrl_connection(State),
  952. {noreply, State#state{client = From}};
  953. handle_call({_,{type, Type}}, From, #state{chunk = false}
  954. = State) ->
  955. case Type of
  956. ascii ->
  957. send_ctrl_message(State, mk_cmd("TYPE A", [])),
  958. activate_ctrl_connection(State),
  959. {noreply, State#state{caller = type, type = ascii,
  960. client = From}};
  961. binary ->
  962. send_ctrl_message(State, mk_cmd("TYPE I", [])),
  963. activate_ctrl_connection(State),
  964. {noreply, State#state{caller = type, type = binary,
  965. client = From}};
  966. _ ->
  967. {reply, {error, etype}, State}
  968. end;
  969. handle_call({_,{recv, RemoteFile, LocalFile}}, From,
  970. #state{chunk = false, ldir = LocalDir} = State) ->
  971. progress_report({remote_file, RemoteFile}, State),
  972. NewLocalFile = filename:absname(LocalFile, LocalDir),
  973. case file_open(NewLocalFile, write) of
  974. {ok, Fd} ->
  975. setup_data_connection(State#state{client = From,
  976. caller =
  977. {recv_file,
  978. RemoteFile, Fd}});
  979. {error, _What} ->
  980. {reply, {error, epath}, State}
  981. end;
  982. handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} =
  983. State) ->
  984. setup_data_connection(State#state{caller = {recv_bin, RemoteFile},
  985. client = From});
  986. handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false}
  987. = State) ->
  988. setup_data_connection(State#state{caller = {start_chunk_transfer,
  989. "RETR", RemoteFile},
  990. client = From});
  991. handle_call({_, recv_chunk}, _, #state{chunk = false} = State) ->
  992. {reply, {error, "ftp:recv_chunk_start/2 not called"}, State};
  993. handle_call({_, recv_chunk}, From, #state{chunk = true} = State) ->
  994. activate_data_connection(State),
  995. {noreply, State#state{client = From, caller = recv_chunk}};
  996. handle_call({_, {send, LocalFile, RemoteFile}}, From,
  997. #state{chunk = false, ldir = LocalDir} = State) ->
  998. progress_report({local_file, filename:absname(LocalFile, LocalDir)},
  999. State),
  1000. setup_data_connection(State#state{caller = {transfer_file,
  1001. {"STOR",
  1002. LocalFile, RemoteFile}},
  1003. client = From});
  1004. handle_call({_, {append, LocalFile, RemoteFile}}, From,
  1005. #state{chunk = false} = State) ->
  1006. setup_data_connection(State#state{caller = {transfer_file,
  1007. {"APPE",
  1008. LocalFile, RemoteFile}},
  1009. client = From});
  1010. handle_call({_, {send_bin, Bin, RemoteFile}}, From,
  1011. #state{chunk = false} = State) ->
  1012. setup_data_connection(State#state{caller = {transfer_data,
  1013. {"STOR", Bin, RemoteFile}},
  1014. client = From});
  1015. handle_call({_,{append_bin, Bin, RemoteFile}}, From,
  1016. #state{chunk = false} = State) ->
  1017. setup_data_connection(State#state{caller = {transfer_data,
  1018. {"APPE", Bin, RemoteFile}},
  1019. client = From});
  1020. handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false}
  1021. = State) ->
  1022. setup_data_connection(State#state{caller = {start_chunk_transfer,
  1023. "STOR", RemoteFile},
  1024. client = From});
  1025. handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false}
  1026. = State) ->
  1027. setup_data_connection(State#state{caller = {start_chunk_transfer,
  1028. "APPE", RemoteFile},
  1029. client = From});
  1030. handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) ->
  1031. send_data_message(State, Bin),
  1032. {reply, ok, State};
  1033. handle_call({_, chunk_end}, From, #state{chunk = true} = State) ->
  1034. close_data_connection(State),
  1035. activate_ctrl_connection(State),
  1036. {noreply, State#state{client = From, dsock = undefined,
  1037. caller = end_chunk_transfer, chunk = false}};
  1038. handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State) ->
  1039. send_ctrl_message(State, mk_cmd(Cmd, [])),
  1040. activate_ctrl_connection(State),
  1041. {noreply, State#state{client = From, caller = quote}};
  1042. handle_call({_, _Req}, _From, #state{csock = CSock} = State)
  1043. when (CSock =:= undefined) ->
  1044. {reply, {error, not_connected}, State};
  1045. handle_call(_, _, #state{chunk = true} = State) ->
  1046. {reply, {error, echunk}, State};
  1047. %% Catch all - This can only happen if the application programmer writes
  1048. %% really bad code that violates the API.
  1049. handle_call(Request, _Timeout, State) ->
  1050. {stop, {'API_violation_connection_closed', Request},
  1051. {error, {connection_terminated, 'API_violation'}}, State}.
  1052. %%--------------------------------------------------------------------------
  1053. %% handle_cast(Request, State) -> {noreply, State} |
  1054. %% {noreply, State, Timeout} |
  1055. %% {stop, Reason, State}
  1056. %% Description: Handles cast messages.
  1057. %%-------------------------------------------------------------------------
  1058. handle_cast({Pid, close}, #state{owner = Pid} = State) ->
  1059. send_ctrl_message(State, mk_cmd("QUIT", [])),
  1060. close_ctrl_connection(State),
  1061. close_data_connection(State),
  1062. {stop, normal, State#state{csock = undefined, dsock = undefined}};
  1063. handle_cast({Pid, close}, State) ->
  1064. Report = io_lib:format("A none owner process ~p tried to close an "
  1065. "ftp connection: ~n", [Pid]),
  1066. error_logger:info_report(Report),
  1067. {noreply, State};
  1068. %% Catch all - This can oly happen if the application programmer writes
  1069. %% really bad code that violates the API.
  1070. handle_cast(Msg, State) ->
  1071. {stop, {'API_violation_connection_closed', Msg}, State}.
  1072. %%--------------------------------------------------------------------------
  1073. %% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} |
  1074. %% {stop, Reason, State}
  1075. %% Description: Handles tcp messages from the ftp-server.
  1076. %% Note: The order of the function clauses is significant.
  1077. %%--------------------------------------------------------------------------
  1078. handle_info(timeout, #state{caller = open} = State) ->
  1079. {stop, timeout, State};
  1080. handle_info(timeout, State) ->
  1081. {noreply, State};
  1082. %%% Data socket messages %%%
  1083. handle_info({tcp, Socket, Data},
  1084. #state{dsock = Socket,
  1085. caller = {recv_file, Fd}} = State) ->
  1086. file_write(binary_to_list(Data), Fd),
  1087. progress_report({binary, Data}, State),
  1088. activate_data_connection(State),
  1089. {noreply, State};
  1090. handle_info({tcp, Socket, Data}, #state{dsock = Socket, client = From,
  1091. caller = recv_chunk}
  1092. = State) ->
  1093. gen_server:reply(From, {ok, Data}),
  1094. {noreply, State#state{client = undefined, data = <<>>}};
  1095. handle_info({tcp, Socket, Data}, #state{dsock = Socket} = State) ->
  1096. activate_data_connection(State),
  1097. {noreply, State#state{data = <<(State#state.data)/binary,
  1098. Data/binary>>}};
  1099. handle_info({tcp_closed, Socket}, #state{dsock = Socket,
  1100. caller = {recv_file, Fd}}
  1101. = State) ->
  1102. file_close(Fd),
  1103. progress_report({transfer_size, 0}, State),
  1104. activate_ctrl_connection(State),
  1105. {noreply, State#state{dsock = undefined, data = <<>>}};
  1106. handle_info({tcp_closed, Socket}, #state{dsock = Socket, client = From,
  1107. caller = recv_chunk}
  1108. = State) ->
  1109. gen_server:reply(From, ok),
  1110. {noreply, State#state{dsock = undefined, client = undefined,
  1111. data = <<>>, caller = undefined,
  1112. chunk = false}};
  1113. handle_info({tcp_closed, Socket}, #state{dsock = Socket, caller = recv_bin,
  1114. data = Data} = State) ->
  1115. activate_ctrl_connection(State),
  1116. {noreply, State#state{dsock = undefined, data = <<>>,
  1117. caller = {recv_bin, Data}}};
  1118. handle_info({tcp_closed, Socket}, #state{dsock = Socket, data = Data,
  1119. caller = {handle_dir_result, Dir}}
  1120. = State) ->
  1121. activate_ctrl_connection(State),
  1122. {noreply, State#state{dsock = undefined,
  1123. caller = {handle_dir_result, Dir, Data},
  1124. % data = <<?CR,?LF>>}};
  1125. data = <<>>}};
  1126. handle_info({tcp_error, Socket, Reason}, #state{dsock = Socket,
  1127. client = From} = State) ->
  1128. gen_server:reply(From, {error, Reason}),
  1129. close_data_connection(State),
  1130. {noreply, State#state{dsock = undefined, client = undefined,
  1131. data = <<>>, caller = undefined, chunk = false}};
  1132. %%% Ctrl socket messages %%%
  1133. handle_info({tcp, Socket, Data}, #state{csock = Socket,
  1134. verbose = Verbose,
  1135. caller = Caller,
  1136. client = From,
  1137. ctrl_data = {CtrlData, AccLines,
  1138. LineStatus}}
  1139. = State) ->
  1140. case ftp_response:parse_lines(<<CtrlData/binary, Data/binary>>,
  1141. AccLines, LineStatus) of
  1142. {ok, Lines, NextMsgData} ->
  1143. verbose(Lines, Verbose, 'receive'),
  1144. CtrlResult = ftp_response:interpret(Lines),
  1145. case Caller of
  1146. quote ->
  1147. gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
  1148. {noreply, State#state{client = undefined,
  1149. caller = undefined,
  1150. ctrl_data = {NextMsgData, [],
  1151. start}}};
  1152. _ ->
  1153. handle_ctrl_result(CtrlResult,
  1154. State#state{ctrl_data =
  1155. {NextMsgData, [], start}})
  1156. end;
  1157. {continue, NewCtrlData} ->
  1158. activate_ctrl_connection(State),
  1159. {noreply, State#state{ctrl_data = NewCtrlData}}
  1160. end;
  1161. handle_info({tcp_closed, Socket}, #state{csock = Socket}) ->
  1162. %% If the server closes the control channel it is
  1163. %% the expected behavior that connection process terminates.
  1164. exit(normal); %% User will get error message from terminate/2
  1165. handle_info({tcp_error, Socket, Reason}, _) ->
  1166. Report =
  1167. io_lib:format("tcp_error on socket: ~p for reason: ~p~n",
  1168. [Socket, Reason]),
  1169. error_logger:error_report(Report),
  1170. %% If tcp does not work the only option is to terminate,
  1171. %% this is the expected behavior under these circumstances.
  1172. exit(normal); %% User will get error message from terminate/2
  1173. %% Monitor messages - if the process owning the ftp connection goes
  1174. %% down there is no point in continuing.
  1175. handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->
  1176. {stop, normal, State#state{client = undefined}};
  1177. handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
  1178. {stop, normal, State#state{client = undefined}};
  1179. handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->
  1180. {stop, normal, State#state{client = undefined}};
  1181. handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
  1182. {stop, {stopped, {'EXIT', Process, Reason}},
  1183. State#state{client = undefined}};
  1184. handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) ->
  1185. Report = io_lib:format("Progress reporting stopped for reason ~p~n",
  1186. Reason),
  1187. error_logger:info_report(Report),
  1188. {noreply, State#state{progress = ignore}};
  1189. %% Catch all - throws away unknown messages (This could happen by "accident"
  1190. %% so we do not want to crash, but we make a log entry as it is an
  1191. %% unwanted behaviour.)
  1192. handle_info(Info, State) ->
  1193. Report = io_lib:format("ftp : ~p : Unexpected message: ~p\n",
  1194. [self(), Info]),
  1195. error_logger:info_report(Report),
  1196. {noreply, State}.
  1197. %%--------------------------------------------------------------------------
  1198. %% terminate/2 and code_change/3
  1199. %%--------------------------------------------------------------------------
  1200. terminate(normal, State) ->
  1201. %% If terminate reason =/= normal the progress reporting process will
  1202. %% be killed by the exit signal.
  1203. progress_report(stop, State),
  1204. do_termiante({error, econn}, State);
  1205. terminate(Reason, State) ->
  1206. Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]),
  1207. error_logger:error_report(Report),
  1208. do_termiante({error, eclosed}, State).
  1209. do_termiante(ErrorMsg, State) ->
  1210. close_data_connection(State),
  1211. close_ctrl_connection(State),
  1212. case State#state.client of
  1213. undefined ->
  1214. ok;
  1215. From ->
  1216. gen_server:reply(From, ErrorMsg)
  1217. end,
  1218. ok.
  1219. code_change(_Vsn, State1, upgrade_from_pre_5_12) ->
  1220. {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
  1221. Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1,
  1222. IpFamily =
  1223. if
  1224. (IPv6Disable =:= true) ->
  1225. inet;
  1226. true ->
  1227. inet6fb4
  1228. end,
  1229. State2 = #state{csock = CSock,
  1230. dsock = DSock,
  1231. verbose = Verbose,
  1232. ldir = LDir,
  1233. type = Type,
  1234. chunk = Chunk,
  1235. mode = Mode,
  1236. timeout = Timeout,
  1237. data = Data,
  1238. ctrl_data = CtrlData,
  1239. owner = Owner,
  1240. client = Client,
  1241. caller = Caller,
  1242. ipfamily = IpFamily,
  1243. progress = Progress},
  1244. {ok, State2};
  1245. code_change(_Vsn, State1, downgrade_to_pre_5_12) ->
  1246. #state{csock = CSock,
  1247. dsock = DSock,
  1248. verbose = Verbose,
  1249. ldir = LDir,
  1250. type = Type,
  1251. chunk = Chunk,
  1252. mode = Mode,
  1253. timeout = Timeout,
  1254. data = Data,
  1255. ctrl_data = CtrlData,
  1256. owner = Owner,
  1257. client = Client,
  1258. caller = Caller,
  1259. ipfamily = IpFamily,
  1260. progress = Progress} = State1,
  1261. IPv6Disable =
  1262. if
  1263. (IpFamily =:= inet) ->
  1264. true;
  1265. true ->
  1266. false
  1267. end,
  1268. State2 =
  1269. {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
  1270. Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress},
  1271. {ok, State2};
  1272. code_change(_Vsn, State, _Extra) ->
  1273. {ok, State}.
  1274. %%%=========================================================================
  1275. %% Start/stop
  1276. %%%=========================================================================
  1277. %%--------------------------------------------------------------------------
  1278. %% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason}
  1279. %%
  1280. %% Description: Callback function for the ftp supervisor. It is called
  1281. %% : when start_service/1 calls ftp_sup:start_child/1 to start an
  1282. %% : instance of the ftp process. Also called by start_standalone/1
  1283. %%--------------------------------------------------------------------------
  1284. start_link([Opts, GenServerOptions]) ->
  1285. start_link(Opts, GenServerOptions).
  1286. start_link(Opts, GenServerOptions) ->
  1287. case lists:keysearch(client, 1, Opts) of
  1288. {value, _} ->
  1289. %% Via the supervisor
  1290. gen_server:start_link(?MODULE, Opts, GenServerOptions);
  1291. false ->
  1292. Opts2 = [{client, self()} | Opts],
  1293. gen_server:start_link(?MODULE, Opts2, GenServerOptions)
  1294. end.
  1295. %%% Stop functionality is handled by close/1
  1296. %%%========================================================================
  1297. %%% Internal functions
  1298. %%%========================================================================
  1299. %%--------------------------------------------------------------------------
  1300. %%% Help functions to handle_call and/or handle_ctrl_result
  1301. %%--------------------------------------------------------------------------
  1302. %% User handling
  1303. handle_user(User, Password, Acc, State) ->
  1304. send_ctrl_message(State, mk_cmd("USER ~s", [User])),
  1305. activate_ctrl_connection(State),
  1306. {noreply, State#state{caller = {handle_user, Password, Acc}}}.
  1307. handle_user_passwd(Password, Acc, State) ->
  1308. send_ctrl_message(State, mk_cmd("PASS ~s", [Password])),
  1309. activate_ctrl_connection(State),
  1310. {noreply, State#state{caller = {handle_user_passwd, Acc}}}.
  1311. handle_user_account(Acc, State) ->
  1312. send_ctrl_message(State, mk_cmd("ACCT ~s", [Acc])),
  1313. activate_ctrl_connection(State),
  1314. {noreply, State#state{caller = handle_user_account}}.
  1315. %%--------------------------------------------------------------------------
  1316. %% handle_ctrl_result
  1317. %%--------------------------------------------------------------------------
  1318. %%--------------------------------------------------------------------------
  1319. %% Handling of control connection setup
  1320. handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From}
  1321. = State) ->
  1322. gen_server:reply(From, {ok, self()}),
  1323. {noreply, State#state{client = undefined,
  1324. caller = undefined }};
  1325. handle_ctrl_result({_, Lines}, #state{caller = open} = State) ->
  1326. ctrl_result_response(econn, State, {error, Lines});
  1327. %%--------------------------------------------------------------------------
  1328. %% Data connection setup active mode
  1329. handle_ctrl_result({pos_compl, _Lines},
  1330. #state{mode = active,
  1331. caller = {setup_data_connection,
  1332. {LSock, Caller}}} = State) ->
  1333. handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}});
  1334. handle_ctrl_result({Status, Lines},
  1335. #state{mode = active,
  1336. caller = {setup_data_connection, {LSock, _}}}
  1337. = State) ->
  1338. close_connection(LSock),
  1339. ctrl_result_response(Status, State, {error, Lines});
  1340. %% Data connection setup passive mode
  1341. handle_ctrl_result({pos_compl, Lines},
  1342. #state{mode = passive,
  1343. ipfamily = inet6,
  1344. client = From,
  1345. caller = {setup_data_connection, Caller},
  1346. csock = CSock,
  1347. timeout = Timeout}
  1348. = State) ->
  1349. [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")),
  1350. {ok, {IP, _}} = inet:peername(CSock),
  1351. case connect(IP, list_to_integer(PortStr), Timeout, State) of
  1352. {ok, _, Socket} ->
  1353. handle_caller(State#state{caller = Caller, dsock = Socket});
  1354. {error, _Reason} = Error ->
  1355. gen_server:reply(From, Error),
  1356. {noreply, State#state{client = undefined, caller = undefined}}
  1357. end;
  1358. handle_ctrl_result({pos_compl, Lines},
  1359. #state{mode = passive,
  1360. ipfamily = inet,
  1361. client = From,
  1362. caller = {setup_data_connection, Caller},
  1363. timeout = Timeout} = State) ->
  1364. {_, [?LEFT_PAREN | Rest]} =
  1365. lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),
  1366. {NewPortAddr, _} =
  1367. lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),
  1368. [A1, A2, A3, A4, P1, P2] =
  1369. lists:map(fun(X) -> list_to_integer(X) end,
  1370. string:tokens(NewPortAddr, [$,])),
  1371. IP = {A1, A2, A3, A4},
  1372. Port = (P1 * 256) + P2,
  1373. case connect(IP, Port, Timeout, State) of
  1374. {ok, _, Socket} ->
  1375. handle_caller(State#state{caller = Caller, dsock = Socket});
  1376. {error, _Reason} = Error ->
  1377. gen_server:reply(From, Error),
  1378. {noreply,State#state{client = undefined, caller = undefined}}
  1379. end;
  1380. %% FTP server does not support passive mode: try to fallback on active mode
  1381. handle_ctrl_result(_,
  1382. #state{mode = passive,
  1383. caller = {setup_data_connection, Caller}} = State) ->
  1384. setup_data_connection(State#state{mode = active, caller = Caller});
  1385. %%--------------------------------------------------------------------------
  1386. %% User handling
  1387. handle_ctrl_result({pos_interm, _},
  1388. #state{caller = {handle_user, PassWord, Acc}} = State) ->
  1389. handle_user_passwd(PassWord, Acc, State);
  1390. handle_ctrl_result({Status, _},
  1391. #state{caller = {handle_user, _, _}} = State) ->
  1392. ctrl_result_response(Status, State, {error, euser});
  1393. %% Accounts
  1394. handle_ctrl_result({pos_interm_acct, _},
  1395. #state{caller = {handle_user_passwd, Acc}} = State)
  1396. when Acc =/= "" ->
  1397. handle_user_account(Acc, State);
  1398. handle_ctrl_result({Status, _},
  1399. #state{caller = {handle_user_passwd, _}} = State) ->
  1400. ctrl_result_response(Status, State, {error, euser});
  1401. %%--------------------------------------------------------------------------
  1402. %% Print current working directory
  1403. handle_ctrl_result({pos_compl, Lines},
  1404. #state{caller = pwd, client = From} = State) ->
  1405. Dir = pwd_result(Lines),
  1406. gen_server:reply(From, {ok, Dir}),
  1407. {noreply, State#state{client = undefined, caller = undefined}};
  1408. %%--------------------------------------------------------------------------
  1409. %% Directory listing
  1410. handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State) ->
  1411. case accept_data_connection(State) of
  1412. {ok, NewState} ->
  1413. activate_data_connection(NewState),
  1414. {noreply, NewState#state{caller = {handle_dir_result, Dir}}};
  1415. {error, _Reason} = ERROR ->
  1416. case State#state.client of
  1417. undefined ->
  1418. {stop, ERROR, State};
  1419. From ->
  1420. gen_server:reply(From, ERROR),
  1421. {stop, normal, State#state{client = undefined}}
  1422. end
  1423. end;
  1424. handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir,
  1425. Data}, client = From}
  1426. = State) ->
  1427. case Dir of
  1428. "" -> % Current directory
  1429. gen_server:reply(From, {ok, Data}),
  1430. {noreply, State#state{client = undefined,
  1431. caller = undefined}};
  1432. _ ->
  1433. %% <WTF>
  1434. %% Dir cannot be assumed to be a dir. It is a string that
  1435. %% could be a dir, but could also be a file or even a string
  1436. %% containing wildcards (*).
  1437. %%
  1438. %% %% If there is only one line it might be a directory with one
  1439. %% %% file but it might be an error message that the directory
  1440. %% %% was not found. So in this case we have to endure a little
  1441. %% %% overhead to be able to give a good return value. Alas not
  1442. %% %% all ftp implementations behave the same and returning
  1443. %% %% an error string is allowed by the FTP RFC.
  1444. %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end,
  1445. %% binary_to_list(Data)) of
  1446. %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) ->
  1447. %% send_ctrl_message(State, mk_cmd("PWD", [])),
  1448. %% activate_ctrl_connection(State),
  1449. %% {noreply,
  1450. %% State#state{caller = {handle_dir_data, Dir, Data}}};
  1451. %% _ ->
  1452. %% gen_server:reply(From, {ok, Data}),
  1453. %% {noreply, State#state{client = undefined,
  1454. %% caller = undefined}}
  1455. %% end
  1456. %% </WTF>
  1457. gen_server:reply(From, {ok, Data}),
  1458. {noreply, State#state{client = undefined,
  1459. caller = undefined}}
  1460. end;
  1461. handle_ctrl_result({pos_compl, Lines},
  1462. #state{caller = {handle_dir_data, Dir, DirData}} =
  1463. State) ->
  1464. OldDir = pwd_result(Lines),
  1465. send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])),
  1466. activate_ctrl_connection(State),
  1467. {noreply, State#state{caller = {handle_dir_data_second_phase, OldDir,
  1468. DirData}}};
  1469. handle_ctrl_result({Status, _},
  1470. #state{caller = {handle_dir_data, _, _}} = State) ->
  1471. ctrl_result_response(Status, State, {error, epath});
  1472. handle_ctrl_result(S={_Status, _},
  1473. #state{caller = {handle_dir_result, _, _}} = State) ->
  1474. %% OTP-5731, macosx
  1475. ctrl_result_response(S, State, {error, epath});
  1476. handle_ctrl_result({pos_compl, _},
  1477. #state{caller = {handle_dir_data_second_phase, OldDir,
  1478. DirData}} = State) ->
  1479. send_ctrl_message(State, mk_cmd("CWD ~s", [OldDir])),
  1480. activate_ctrl_connection(State),
  1481. {noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}};
  1482. handle_ctrl_result({Status, _},
  1483. #state{caller = {handle_dir_data_second_phase, _, _}}
  1484. = State) ->
  1485. ctrl_result_response(Status, State, {error, epath});
  1486. handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData},
  1487. client = From} = State) ->
  1488. gen_server:reply(From, {ok, DirData}),
  1489. {noreply, State#state{client = undefined, caller = undefined}};
  1490. handle_ctrl_result({Status, _}, #state{caller = cd} = State) ->
  1491. ctrl_result_response(Status, State, {error, epath});
  1492. handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) ->
  1493. ctrl_result_response(Status, State, {error, epath});
  1494. %%--------------------------------------------------------------------------
  1495. %% File renaming
  1496. handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}}
  1497. = State) ->
  1498. send_ctrl_message(State, mk_cmd("RNTO ~s", [NewFile])),
  1499. activate_ctrl_connection(State),
  1500. {noreply, State#state{caller = rename_second_phase}};
  1501. handle_ctrl_result({Status, _},
  1502. #state{caller = {rename, _}} = State) ->
  1503. ctrl_result_response(Status, State, {error, epath});
  1504. handle_ctrl_result({Status, _},
  1505. #state{caller = rename_second_phase} = State) ->
  1506. ctrl_result_response(Status, State, {error, epath});
  1507. %%--------------------------------------------------------------------------
  1508. %% File handling - recv_bin
  1509. handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State) ->
  1510. case accept_data_connection(State) of
  1511. {ok, NewState} ->
  1512. activate_data_connection(NewState),
  1513. {noreply, NewState};
  1514. {error, _Reason} = ERROR ->
  1515. case State#state.client of
  1516. undefined ->
  1517. {stop, ERROR, State};
  1518. From ->
  1519. gen_server:reply(From, ERROR),
  1520. {stop, normal, State#state{client = undefined}}
  1521. end
  1522. end;
  1523. handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data},
  1524. client = From} = State) ->
  1525. gen_server:reply(From, {ok, Data}),
  1526. close_data_connection(State),
  1527. {noreply, State#state{client = undefined, caller = undefined}};
  1528. handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) ->
  1529. close_data_connection(State),
  1530. ctrl_result_response(Status, State#state{dsock = undefined},
  1531. {error, epath});
  1532. handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) ->
  1533. close_data_connection(State),
  1534. ctrl_result_response(Status, State#state{dsock = undefined},
  1535. {error, epath});
  1536. %%--------------------------------------------------------------------------
  1537. %% File handling - start_chunk_transfer
  1538. handle_ctrl_result({pos_prel, _}, #state{client = From,
  1539. caller = start_chunk_transfer}
  1540. = State) ->
  1541. case accept_data_connection(State) of
  1542. {ok, NewState} ->
  1543. gen_server:reply(From, ok),
  1544. {noreply, NewState#state{chunk = true, client = undefined,
  1545. caller = undefined}};
  1546. {error, _Reason} = ERROR ->
  1547. case State#state.client of
  1548. undefined ->
  1549. {stop, ERROR, State};
  1550. From ->
  1551. gen_server:reply(From, ERROR),
  1552. {stop, normal, State#state{client = undefined}}
  1553. end
  1554. end;
  1555. %%--------------------------------------------------------------------------
  1556. %% File handling - recv_file
  1557. handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State) ->
  1558. case accept_data_connection(State) of
  1559. {ok, NewState} ->
  1560. activate_data_connection(NewState),
  1561. {noreply, NewState};
  1562. {error, _Reason} = ERROR ->
  1563. case State#state.client of
  1564. undefined ->
  1565. {stop, ERROR, State};
  1566. From ->
  1567. gen_server:reply(From, ERROR),
  1568. {stop, normal, State#state{client = undefined}}
  1569. end
  1570. end;
  1571. handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) ->
  1572. file_close(Fd),
  1573. close_data_connection(State),
  1574. ctrl_result_response(Status, State#state{dsock = undefined},
  1575. {error, epath});
  1576. %%--------------------------------------------------------------------------
  1577. %% File handling - transfer_*
  1578. handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}}
  1579. = State) ->
  1580. case accept_data_connection(State) of
  1581. {ok, NewState} ->
  1582. send_file(Fd, NewState);
  1583. {error, _Reason} = ERROR ->
  1584. case State#state.client of
  1585. undefined ->
  1586. {stop, ERROR, State};
  1587. From ->
  1588. gen_server:reply(From, ERROR),
  1589. {stop, normal, State#state{client = undefined}}
  1590. end
  1591. end;
  1592. handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}}
  1593. = State) ->
  1594. case accept_data_connection(State) of
  1595. {ok, NewState} ->
  1596. send_data_message(NewState, Bin),
  1597. close_data_connection(NewState),
  1598. activate_ctrl_connection(NewState),
  1599. {noreply, NewState#state{caller = transfer_data_second_phase,
  1600. dsock = undefined}};
  1601. {error, _Reason} = ERROR ->
  1602. case State#state.client of
  1603. undefined ->
  1604. {stop, ERROR, State};
  1605. From ->
  1606. gen_server:reply(From, ERROR),
  1607. {stop, normal, State#state{client = undefined}}
  1608. end
  1609. end;
  1610. %%--------------------------------------------------------------------------
  1611. %% Default
  1612. handle_ctrl_result({Status, Lines}, #state{client = From} = State)
  1613. when From =/= undefined ->
  1614. ctrl_result_response(Status, State, {error, Lines}).
  1615. %%--------------------------------------------------------------------------
  1616. %% Help functions to handle_ctrl_result
  1617. %%--------------------------------------------------------------------------
  1618. ctrl_result_response(pos_compl, #state{client = From} = State, _) ->
  1619. gen_server:reply(From, ok),
  1620. {noreply, State#state{client = undefined, caller = undefined}};
  1621. ctrl_result_response(enofile, #state{client = From} = State, _) ->
  1622. gen_server:reply(From, {error, enofile}),
  1623. {noreply, State#state{client = undefined, caller = undefined}};
  1624. ctrl_result_response(Status, #state{client = From} = State, _)
  1625. when (Status =:= etnospc) orelse
  1626. (Status =:= epnospc) orelse
  1627. (Status =:= efnamena) orelse
  1628. (Status =:= econn) ->
  1629. %Status == etnospc; Status == epnospc; Status == econn ->
  1630. gen_server:reply(From, {error, Status}),
  1631. %% {stop, normal, {error, Status}, State#state{client = undefined}};
  1632. {stop, normal, State#state{client = undefined}};
  1633. ctrl_result_response(_, #state{client = From} = State, ErrorMsg) ->
  1634. gen_server:reply(From, ErrorMsg),
  1635. {noreply, State#state{client = undefined, caller = undefined}}.
  1636. %%--------------------------------------------------------------------------
  1637. handle_caller(#state{caller = {dir, Dir, Len}} = State) ->
  1638. Cmd = case Len of
  1639. short -> "NLST";
  1640. long -> "LIST"
  1641. end,
  1642. case Dir of
  1643. "" ->
  1644. send_ctrl_message(State, mk_cmd(Cmd, ""));
  1645. _ ->
  1646. send_ctrl_message(State, mk_cmd(Cmd ++ " ~s", [Dir]))
  1647. end,
  1648. activate_ctrl_connection(State),
  1649. {noreply, State#state{caller = {dir, Dir}}};
  1650. handle_caller(#state{caller = {recv_bin, RemoteFile}} = State) ->
  1651. send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])),
  1652. activate_ctrl_connection(State),
  1653. {noreply, State#state{caller = recv_bin}};
  1654. handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} =
  1655. State) ->
  1656. send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])),
  1657. activate_ctrl_connection(State),
  1658. {noreply, State#state{caller = start_chunk_transfer}};
  1659. handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State) ->
  1660. send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])),
  1661. activate_ctrl_connection(State),
  1662. {noreply, State#state{caller = {recv_file, Fd}}};
  1663. handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}},
  1664. ldir = LocalDir, client = From} = State) ->
  1665. case file_open(filename:absname(LocalFile, LocalDir), read) of
  1666. {ok, Fd} ->
  1667. send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])),
  1668. activate_ctrl_connection(State),
  1669. {noreply, State#state{caller = {transfer_file, Fd}}};
  1670. {error, _} ->
  1671. gen_server:reply(From, {error, epath}),
  1672. {noreply, State#state{client = undefined, caller = undefined,
  1673. dsock = undefined}}
  1674. end;
  1675. handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} =
  1676. State) ->
  1677. send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])),
  1678. activate_ctrl_connection(State),
  1679. {noreply, State#state{caller = {transfer_data, Bin}}}.
  1680. %% ----------- FTP SERVER COMMUNICATION -------------------------
  1681. %% Connect to FTP server at Host (default is TCP port 21)
  1682. %% in order to establish a control connection.
  1683. setup_ctrl_connection(Host, Port, Timeout, State) ->
  1684. MsTime = millisec_time(),
  1685. case connect(Host, Port, Timeout, State) of
  1686. {ok, IpFam, CSock} ->
  1687. NewState = State#state{csock = CSock, ipfamily = IpFam},
  1688. activate_ctrl_connection(NewState),
  1689. case Timeout - (millisec_time() - MsTime) of
  1690. Timeout2 when (Timeout2 >= 0) ->
  1691. {ok, NewState#state{caller = open}, Timeout2};
  1692. _ ->
  1693. %% Oups: Simulate timeout
  1694. {ok, NewState#state{caller = open}, 0}
  1695. end;
  1696. Error ->
  1697. Error
  1698. end.
  1699. setup_data_connection(#state{mode = active,
  1700. caller = Caller,
  1701. csock = CSock} = State) ->
  1702. case (catch inet:sockname(CSock)) of
  1703. {ok, {{_, _, _, _, _, _, _, _} = IP, _}} ->
  1704. {ok, LSock} =
  1705. gen_tcp:listen(0, [{ip, IP}, {active, false},
  1706. inet6, binary, {packet, 0}]),
  1707. {ok, Port} = inet:port(LSock),
  1708. IpAddress = inet_parse:ntoa(IP),
  1709. Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]),
  1710. send_ctrl_message(State, Cmd),
  1711. activate_ctrl_connection(State),
  1712. {noreply, State#state{caller = {setup_data_connection,
  1713. {LSock, Caller}}}};
  1714. {ok, {{_,_,_,_} = IP, _}} ->
  1715. {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false},
  1716. binary, {packet, 0}]),
  1717. {ok, Port} = inet:port(LSock),
  1718. {IP1, IP2, IP3, IP4} = IP,
  1719. {Port1, Port2} = {Port div 256, Port rem 256},
  1720. send_ctrl_message(State,
  1721. mk_cmd("PORT ~w,~w,~w,~w,~w,~w",
  1722. [IP1, IP2, IP3, IP4, Port1, Port2])),
  1723. activate_ctrl_connection(State),
  1724. {noreply, State#state{caller = {setup_data_connection,
  1725. {LSock, Caller}}}}
  1726. end;
  1727. setup_data_connection(#state{mode = passive, ipfamily = inet6,
  1728. caller = Caller} = State) ->
  1729. send_ctrl_message(State, mk_cmd("EPSV", [])),
  1730. activate_ctrl_connection(State),
  1731. {noreply, State#state{caller = {setup_data_connection, Caller}}};
  1732. setup_data_connection(#state{mode = passive, ipfamily = inet,
  1733. caller = Caller} = State) ->
  1734. send_ctrl_message(State, mk_cmd("PASV", [])),
  1735. activate_ctrl_connection(State),
  1736. {noreply, State#state{caller = {setup_data_connection, Caller}}}.
  1737. %% setup_data_connection(#state{mode = passive, ip_v6_disabled = false,
  1738. %% caller = Caller} = State) ->
  1739. %% send_ctrl_message(State, mk_cmd("EPSV", [])),
  1740. %% activate_ctrl_connection(State),
  1741. %% {noreply, State#state{caller = {setup_data_connection, Caller}}};
  1742. %% setup_data_connection(#state{mode = passive, ip_v6_disabled = true,
  1743. %% caller = Caller} = State) ->
  1744. %% send_ctrl_message(State, mk_cmd("PASV", [])),
  1745. %% activate_ctrl_connection(State),
  1746. %% {noreply, State#state{caller = {setup_data_connection, Caller}}}.
  1747. connect(Host, Port, Timeout, #state{ipfamily = inet = IpFam}) ->
  1748. connect2(Host, Port, IpFam, Timeout);
  1749. connect(Host, Port, Timeout, #state{ipfamily = inet6 = IpFam}) ->
  1750. connect2(Host, Port, IpFam, Timeout);
  1751. connect(Host, Port, Timeout, #state{ipfamily = inet6fb4}) ->
  1752. case inet:getaddr(Host, inet6) of
  1753. {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} ->
  1754. case inet:getaddr(Host, inet) of
  1755. {ok, IPv4} ->
  1756. IpFam = inet,
  1757. connect2(IPv4, Port, IpFam, Timeout);
  1758. _ ->
  1759. IpFam = inet6,
  1760. connect2(IPv6, Port, IpFam, Timeout)
  1761. end;
  1762. {ok, IPv6} ->
  1763. IpFam = inet6,
  1764. connect2(IPv6, Port, IpFam, Timeout);
  1765. _ ->
  1766. case inet:getaddr(Host, inet) of
  1767. {ok, IPv4} ->
  1768. IpFam = inet,
  1769. connect2(IPv4, Port, IpFam, Timeout);
  1770. Error ->
  1771. Error
  1772. end
  1773. end.
  1774. connect2(Host, Port, IpFam, Timeout) ->
  1775. Opts = [IpFam, binary, {packet, 0}, {active, false}],
  1776. case gen_tcp:connect(Host, Port, Opts, Timeout) of
  1777. {ok, Sock} ->
  1778. {ok, IpFam, Sock};
  1779. Error ->
  1780. Error
  1781. end.
  1782. accept_data_connection(#state{mode = active,
  1783. dtimeout = DTimeout,
  1784. dsock = {lsock, LSock}} = State) ->
  1785. case gen_tcp:accept(LSock, DTimeout) of
  1786. {ok, Socket} ->
  1787. gen_tcp:close(LSock),
  1788. {ok, State#state{dsock = Socket}};
  1789. {error, Reason} ->
  1790. {error, {data_connect_failed, Reason}}
  1791. end;
  1792. accept_data_connection(#state{mode = passive} = State) ->
  1793. {ok, State}.
  1794. send_ctrl_message(#state{csock = Socket, verbose = Verbose}, Message) ->
  1795. %% io:format("send control message: ~n~p~n", [lists:flatten(Message)]),
  1796. verbose(lists:flatten(Message),Verbose,send),
  1797. send_message(Socket, Message).
  1798. send_data_message(#state{dsock = Socket}, Message) ->
  1799. send_message(Socket, Message).
  1800. send_message(Socket, Message) ->
  1801. case gen_tcp:send(Socket, Message) of
  1802. ok ->
  1803. ok;
  1804. {error, Reason} ->
  1805. Report = io_lib:format("gen_tcp:send/2 failed for "
  1806. "reason ~p~n", [Reason]),
  1807. error_logger:error_report(Report),
  1808. %% If tcp does not work the only option is to terminate,
  1809. %% this is the expected behavior under these circumstances.
  1810. exit(normal) %% User will get error message from terminate/2
  1811. end.
  1812. activate_ctrl_connection(#state{csock = Socket, ctrl_data = {<<>>, _, _}}) ->
  1813. activate_connection(Socket);
  1814. activate_ctrl_connection(#state{csock = Socket}) ->
  1815. %% We have already received at least part of the next control message,
  1816. %% that has been saved in ctrl_data, process this first.
  1817. self() ! {tcp, Socket, <<>>}.
  1818. activate_data_connection(#state{dsock = Socket}) ->
  1819. activate_connection(Socket).
  1820. activate_connection(Socket) ->
  1821. inet:setopts(Socket, [{active, once}]).
  1822. close_ctrl_connection(#state{csock = undefined}) ->
  1823. ok;
  1824. close_ctrl_connection(#state{csock = Socket}) ->
  1825. close_connection(Socket).
  1826. close_data_connection(#state{dsock = undefined}) ->
  1827. ok;
  1828. close_data_connection(#state{dsock = {lsock, Socket}}) ->
  1829. close_connection(Socket);
  1830. close_data_connection(#state{dsock = Socket}) ->
  1831. close_connection(Socket).
  1832. close_connection(Socket) ->
  1833. gen_tcp:close(Socket).
  1834. %% ------------ FILE HANDELING ----------------------------------------
  1835. send_file(Fd, State) ->
  1836. case file_read(Fd) of
  1837. {ok, N, Bin} when N > 0->
  1838. send_data_message(State, Bin),
  1839. progress_report({binary, Bin}, State),
  1840. send_file(Fd, State);
  1841. {ok, _, _} ->
  1842. file_close(Fd),
  1843. close_data_connection(State),
  1844. progress_report({transfer_size, 0}, State),
  1845. activate_ctrl_connection(State),
  1846. {noreply, State#state{caller = transfer_file_second_phase,
  1847. dsock = undefined}};
  1848. {error, Reason} ->
  1849. gen_server:reply(State#state.client, {error, Reason}),
  1850. {stop, normal, State#state{client = undefined}}
  1851. end.
  1852. file_open(File, Option) ->
  1853. file:open(File, [raw, binary, Option]).
  1854. file_close(Fd) ->
  1855. file:close(Fd).
  1856. file_read(Fd) ->
  1857. case file:read(Fd, ?FILE_BUFSIZE) of
  1858. {ok, Bytes} ->
  1859. {ok, size(Bytes), Bytes};
  1860. eof ->
  1861. {ok, 0, []};
  1862. Other ->
  1863. Other
  1864. end.
  1865. file_write(Bytes, Fd) ->
  1866. file:write(Fd, Bytes).
  1867. %% -------------- MISC ----------------------------------------------
  1868. call(GenServer, Msg, Format) ->
  1869. call(GenServer, Msg, Format, infinity).
  1870. call(GenServer, Msg, Format, Timeout) ->
  1871. Req = {self(), Msg},
  1872. case (catch gen_server:call(GenServer, Req, Timeout)) of
  1873. {ok, Bin} when is_binary(Bin) andalso (Format =:= string) ->
  1874. {ok, binary_to_list(Bin)};
  1875. {'EXIT', _} ->
  1876. {error, eclosed};
  1877. Result ->
  1878. Result
  1879. end.
  1880. cast(GenServer, Msg) ->
  1881. gen_server:cast(GenServer, {self(), Msg}).
  1882. mk_cmd(Fmt, Args) ->
  1883. [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok.
  1884. pwd_result(Lines) ->
  1885. {_, [?DOUBLE_QUOTE | Rest]} =
  1886. lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines),
  1887. {Dir, _} =
  1888. lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest),
  1889. Dir.
  1890. %% is_verbose(Params) ->
  1891. %% check_param(verbose, Params).
  1892. %% is_debug(Flags) ->
  1893. %% check_param(debug, Flags).
  1894. %% is_trace(Flags) ->
  1895. %% check_param(trace, Flags).
  1896. %% is_ipv6_disabled(Flags) ->
  1897. %% check_param(ip_v6_disabled, Flags).
  1898. %% check_param(Param, Params) ->
  1899. %% lists:member(Param, Params).
  1900. key_search(Key, List, Default) ->
  1901. case lists:keysearch(Key, 1, List) of
  1902. {value, {_,Val}} ->
  1903. Val;
  1904. false ->
  1905. Default
  1906. end.
  1907. %% check_option(Pred, Value, Default) ->
  1908. %% case Pred(Value) of
  1909. %% true ->
  1910. %% Value;
  1911. %% false ->
  1912. %% Default
  1913. %% end.
  1914. verbose(Lines, true, Direction) ->
  1915. DirStr =
  1916. case Direction of
  1917. send ->
  1918. "Sending: ";
  1919. _ ->
  1920. "Receiving: "
  1921. end,
  1922. Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR),
  1923. erlang:display(DirStr++Str);
  1924. verbose(_, false,_) ->
  1925. ok.
  1926. progress(Options) ->
  1927. ftp_progress:start_link(Options).
  1928. progress_report(_, #state{progress = ignore}) ->
  1929. ok;
  1930. progress_report(stop, #state{progress = ProgressPid}) ->
  1931. ftp_progress:stop(ProgressPid);
  1932. progress_report({binary, Data}, #state{progress = ProgressPid}) ->
  1933. ftp_progress:report(ProgressPid, {transfer_size, size(Data)});
  1934. progress_report(Report, #state{progress = ProgressPid}) ->
  1935. ftp_progress:report(ProgressPid, Report).
  1936. millisec_time() ->
  1937. {A,B,C} = erlang:now(),
  1938. A*1000000000+B*1000+(C div 1000).