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

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

https://github.com/bsmr-erlang/otp
Erlang | 2278 lines | 1454 code | 340 blank | 484 comment | 7 complexity | 3120cf89c88e6c05d7fe8e264c5f52ad MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0

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

  1. %%
  2. %% %CopyrightBegin%
  3. %%
  4. %% Copyright Ericsson AB 1997-2012. All Rights Reserved.
  5. %%
  6. %% The contents of this file are subject to the Erlang Public License,
  7. %% Version 1.1, (the "License"); you may not use this file except in
  8. %% compliance with the License. You should have received a copy of the
  9. %% Erlang Public License along with this software. If not, it can be
  10. %% retrieved online at http://www.erlang.org/.
  11. %%
  12. %% Software distributed under the License is distributed on an "AS IS"
  13. %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  14. %% the License for the specific language governing rights and limitations
  15. %% under the License.
  16. %%
  17. %% %CopyrightEnd%
  18. %%
  19. %%
  20. %% Description: This module implements an ftp client, RFC 959.
  21. %% It also supports ipv6 RFC 2428.
  22. -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_d

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