Learning about Erlang for one week:
1 -module(ftp_down). 2 -export([get/6,test/0,get_data/4]). 3 4 test() -> 5 get("ftp.ftpplanet.com", 21, "anonymous", "anonymous", "images/image001_42.jpg", "d:/"). 6 7 client_message(Format, Data) -> 8 io:format("C: " ++ Format ++ "~n", Data). 9 10 server_message(Format, Data) -> 11 io:format("S: " ++ Format ++ "~n", Data). 12 13 server_verify_message(Data, ExpectedCode) -> 14 {Code, Message} = Data, 15 ExpectedCode = Code, 16 io:format("S: ~s~n", [Message]), 17 ok. 18 19 recv_until_EOL(Sock, StrSoFar) -> 20 Len = string:len(StrSoFar), 21 NotALine = Len < 2 orelse not string:equal("/r/n", string:substr(StrSoFar, Len - 1, 2)), 22 if 23 NotALine -> 24 {ok, Msg} = gen_tcp:recv(Sock, 1), 25 recv_until_EOL(Sock, StrSoFar ++ Msg); 26 true -> 27 StrSoFar 28 end. 29 30 recv_until_EOL(Sock) -> 31 recv_until_EOL(Sock, ""). 32 33 recv_until_xxxend(Sock, Num, Line, LinesSoFar) -> 34 ExpectedTail = integer_to_list(Num) ++ " end", 35 ExpectedTailLen = string:len(ExpectedTail), 36 Pos = string:len(Line) - ExpectedTailLen - 1, 37 Eq = Pos > 0 andalso string:equal(ExpectedTail, string:to_lower(string:substr(Line, Pos, ExpectedTailLen))), 38 if 39 Eq -> 40 {Num, LinesSoFar ++ Line}; 41 true -> 42 recv_response(Sock, Num, LinesSoFar ++ Line) 43 end. 44 45 recv_response(Sock, LinesSoFar) -> 46 Line = recv_until_EOL(Sock), 47 {Num, [H | _]} = string:to_integer(Line), 48 if 49 H == $- -> 50 recv_until_xxxend(Sock, Num, Line, LinesSoFar); 51 true -> 52 {Num, LinesSoFar ++ Line} 53 end. 54 55 recv_response(Sock, ResponseNo, LinesSoFar) -> 56 Line = recv_until_EOL(Sock), 57 recv_until_xxxend(Sock, ResponseNo, Line, LinesSoFar). 58 59 recv_response(Sock) -> 60 recv_response(Sock, []). 61 62 send_command(Sock, Msg) -> 63 ok = gen_tcp:send(Sock, Msg ++ "/r/n"), 64 client_message("~s", [Msg]). 65 66 parse_pasv(Msg) -> 67 Index1 = string:chr(Msg, $(), 68 Index2 = string:chr(Msg, $)), 69 [A1, A2, A3, A4, B1, B2] = lists:map(fun(S) -> string:strip(S) end, string:tokens(string:substr(Msg, Index1 + 1, Index2 - Index1 -1), ",")), 70 {Nb1, _} = string:to_integer(B1), 71 {Nb2, _} = string:to_integer(B2), 72 <<Port:16>> = <<Nb1:8, Nb2:8>>, 73 {lists:append([A1, ".", A2, ".", A3, ".", A4]), Port}. 74 75 76 recv_until_transfer_complete(Sock, {ok , Msg}, MsgSoFar) -> 77 if 78 length(Msg) > 0 -> 79 recv_until_transfer_complete(Sock, gen_tcp:recv(Sock, 0), MsgSoFar ++ Msg); 80 true -> 81 receive 82 transfer_complete -> 83 MsgSoFar 84 after 200 -> 85 recv_until_transfer_complete(Sock, gen_tcp:recv(Sock, 0), MsgSoFar ++ Msg) 86 end 87 end; 88 89 90 recv_until_transfer_complete(_, {error , closed}, MsgSoFar) -> 91 MsgSoFar. 92 93 recv_until_transfer_complete(Sock) -> 94 receive 95 start_transfer -> 96 recv_until_transfer_complete(Sock, gen_tcp:recv(Sock, 0), []) 97 after 30000 -> 98 "150 timeout" 99 end. 100 101 get_data(Addr, Port, LocalFilePath, Control) -> 102 {ok, DataSock} = gen_tcp:connect(Addr, Port, [{active, false}]), 103 Data = recv_until_transfer_complete(DataSock), 104 client_message("<I GOT>: ~w Bytes~n", [length(Data)]), 105 file:write_file(LocalFilePath, list_to_binary(Data)), 106 gen_tcp:close(DataSock), 107 Control ! bye. 108 109 110 get(Host, Port, User, Pass, FilePath, LocalPath) -> 111 Slash = string:rchr(FilePath, $/), 112 Path = string:left(FilePath, Slash - 1), 113 File = string:right(FilePath, string:len(FilePath) - Slash), 114 115 {ok, Sock} = gen_tcp:connect(Host, Port, [{active, false}]), 116 client_message("connect to ~s:~w ok.", [Host, Port]), 117 server_verify_message(recv_response(Sock), 220), 118 send_command(Sock, "USER " ++ User), 119 server_verify_message(recv_response(Sock), 331), 120 send_command(Sock, "PASS " ++ Pass), 121 server_verify_message(recv_response(Sock), 230), 122 send_command(Sock, "CWD " ++ Path), 123 server_verify_message(recv_response(Sock), 250), 124 send_command(Sock, "TYPE I"), 125 server_verify_message(recv_response(Sock), 200), 126 send_command(Sock, "PASV"), 127 {_, Msg} = recv_response(Sock), 128 server_message(Msg, ""), 129 {Addr, NewPort} = parse_pasv(Msg), 130 131 DataPid = spawn(ftp_down, get_data, [Addr, NewPort, LocalPath ++ File, self()]), 132 133 send_command(Sock, "RETR " ++ File), 134 % 150 Opening BINARY mode data connection for 150 File status okay; about to open data connection. 135 % 125 Downloading in BINARY file 125 Data connection already open; transfer starting. 136 % 550 Failed to open file 137 {Num, Text} = recv_response(Sock), 138 V = (Num == 150) or (Num == 125), 139 if 140 V -> 141 server_message("~s", [Text]), 142 DataPid ! start_transfer, 143 server_verify_message(recv_response(Sock), 226), 144 DataPid ! transfer_complete, 145 receive 146 bye -> 147 ok 148 end; 149 true -> 150 if Num == 550 -> 151 server_message("~w", Text); 152 true -> 153 ok 154 end 155 end, 156 157 gen_tcp:close(Sock).