diff options
Diffstat (limited to 'src/ftp.lua')
-rw-r--r-- | src/ftp.lua | 360 |
1 files changed, 105 insertions, 255 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index e596416..18dab6d 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
@@ -5,62 +5,29 @@ | |||
5 | -- Conforming to: RFC 959, LTN7 | 5 | -- Conforming to: RFC 959, LTN7 |
6 | -- RCS ID: $Id$ | 6 | -- RCS ID: $Id$ |
7 | ----------------------------------------------------------------------------- | 7 | ----------------------------------------------------------------------------- |
8 | 8 | -- make sure LuaSocket is loaded | |
9 | local Public, Private = {}, {} | 9 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end |
10 | local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace | 10 | -- get LuaSocket namespace |
11 | socket.ftp = Public -- create ftp sub namespace | 11 | local socket = _G[LUASOCKET_LIBNAME] |
12 | if not socket then error('module requires LuaSocket') end | ||
13 | -- create namespace inside LuaSocket namespace | ||
14 | socket.ftp = socket.ftp or {} | ||
15 | -- make all module globals fall into namespace | ||
16 | setmetatable(socket.ftp, { __index = _G }) | ||
17 | setfenv(1, socket.ftp) | ||
12 | 18 | ||
13 | ----------------------------------------------------------------------------- | 19 | ----------------------------------------------------------------------------- |
14 | -- Program constants | 20 | -- Program constants |
15 | ----------------------------------------------------------------------------- | 21 | ----------------------------------------------------------------------------- |
16 | -- timeout in seconds before the program gives up on a connection | 22 | -- timeout in seconds before the program gives up on a connection |
17 | Public.TIMEOUT = 60 | 23 | TIMEOUT = 60 |
18 | -- default port for ftp service | 24 | -- default port for ftp service |
19 | Public.PORT = 21 | 25 | PORT = 21 |
20 | -- this is the default anonymous password. used when no password is | 26 | -- this is the default anonymous password. used when no password is |
21 | -- provided in url. should be changed to your e-mail. | 27 | -- provided in url. should be changed to your e-mail. |
22 | Public.EMAIL = "anonymous@anonymous.org" | 28 | EMAIL = "anonymous@anonymous.org" |
23 | -- block size used in transfers | 29 | -- block size used in transfers |
24 | Public.BLOCKSIZE = 8192 | 30 | BLOCKSIZE = 2048 |
25 | |||
26 | ----------------------------------------------------------------------------- | ||
27 | -- Tries to get a pattern from the server and closes socket on error | ||
28 | -- sock: socket connected to the server | ||
29 | -- pattern: pattern to receive | ||
30 | -- Returns | ||
31 | -- received pattern on success | ||
32 | -- nil followed by error message on error | ||
33 | ----------------------------------------------------------------------------- | ||
34 | function Private.try_receive(sock, pattern) | ||
35 | local data, err = sock:receive(pattern) | ||
36 | if not data then sock:close() end | ||
37 | return data, err | ||
38 | end | ||
39 | |||
40 | ----------------------------------------------------------------------------- | ||
41 | -- Tries to send data to the server and closes socket on error | ||
42 | -- sock: socket connected to the server | ||
43 | -- data: data to send | ||
44 | -- Returns | ||
45 | -- err: error message if any, nil if successfull | ||
46 | ----------------------------------------------------------------------------- | ||
47 | function Private.try_send(sock, data) | ||
48 | local sent, err = sock:send(data) | ||
49 | if not sent then sock:close() end | ||
50 | return err | ||
51 | end | ||
52 | |||
53 | ----------------------------------------------------------------------------- | ||
54 | -- Tries to send DOS mode lines. Closes socket on error. | ||
55 | -- Input | ||
56 | -- sock: server socket | ||
57 | -- line: string to be sent | ||
58 | -- Returns | ||
59 | -- err: message in case of error, nil if successfull | ||
60 | ----------------------------------------------------------------------------- | ||
61 | function Private.try_sendline(sock, line) | ||
62 | return Private.try_send(sock, line .. "\r\n") | ||
63 | end | ||
64 | 31 | ||
65 | ----------------------------------------------------------------------------- | 32 | ----------------------------------------------------------------------------- |
66 | -- Gets ip and port for data connection from PASV answer | 33 | -- Gets ip and port for data connection from PASV answer |
@@ -70,7 +37,7 @@ end | |||
70 | -- ip: string containing ip for data connection | 37 | -- ip: string containing ip for data connection |
71 | -- port: port for data connection | 38 | -- port: port for data connection |
72 | ----------------------------------------------------------------------------- | 39 | ----------------------------------------------------------------------------- |
73 | function Private.get_pasv(pasv) | 40 | local function get_pasv(pasv) |
74 | local a, b, c, d, p1, p2, _ | 41 | local a, b, c, d, p1, p2, _ |
75 | local ip, port | 42 | local ip, port |
76 | _,_, a, b, c, d, p1, p2 = | 43 | _,_, a, b, c, d, p1, p2 = |
@@ -82,88 +49,6 @@ function Private.get_pasv(pasv) | |||
82 | end | 49 | end |
83 | 50 | ||
84 | ----------------------------------------------------------------------------- | 51 | ----------------------------------------------------------------------------- |
85 | -- Sends a FTP command through socket | ||
86 | -- Input | ||
87 | -- control: control connection socket | ||
88 | -- cmd: command | ||
89 | -- arg: command argument if any | ||
90 | -- Returns | ||
91 | -- error message in case of error, nil otherwise | ||
92 | ----------------------------------------------------------------------------- | ||
93 | function Private.send_command(control, cmd, arg) | ||
94 | local line | ||
95 | if arg then line = cmd .. " " .. arg | ||
96 | else line = cmd end | ||
97 | return Private.try_sendline(control, line) | ||
98 | end | ||
99 | |||
100 | ----------------------------------------------------------------------------- | ||
101 | -- Gets FTP command answer, unfolding if neccessary | ||
102 | -- Input | ||
103 | -- control: control connection socket | ||
104 | -- Returns | ||
105 | -- answer: whole server reply, nil if error | ||
106 | -- code: answer status code or error message | ||
107 | ----------------------------------------------------------------------------- | ||
108 | function Private.get_answer(control) | ||
109 | local code, lastcode, sep, _ | ||
110 | local line, err = Private.try_receive(control) | ||
111 | local answer = line | ||
112 | if err then return nil, err end | ||
113 | _,_, code, sep = string.find(line, "^(%d%d%d)(.)") | ||
114 | if not code or not sep then return nil, answer end | ||
115 | if sep == "-" then -- answer is multiline | ||
116 | repeat | ||
117 | line, err = Private.try_receive(control) | ||
118 | if err then return nil, err end | ||
119 | _,_, lastcode, sep = string.find(line, "^(%d%d%d)(.)") | ||
120 | answer = answer .. "\n" .. line | ||
121 | until code == lastcode and sep == " " -- answer ends with same code | ||
122 | end | ||
123 | return answer, tonumber(code) | ||
124 | end | ||
125 | |||
126 | ----------------------------------------------------------------------------- | ||
127 | -- Checks if a message return is correct. Closes control connection if not. | ||
128 | -- Input | ||
129 | -- control: control connection socket | ||
130 | -- success: table with successfull reply status code | ||
131 | -- Returns | ||
132 | -- code: reply code or nil in case of error | ||
133 | -- answer: server complete answer or system error message | ||
134 | ----------------------------------------------------------------------------- | ||
135 | function Private.check_answer(control, success) | ||
136 | local answer, code = Private.get_answer(control) | ||
137 | if not answer then return nil, code end | ||
138 | if type(success) ~= "table" then success = {success} end | ||
139 | for _, s in ipairs(success) do | ||
140 | if code == s then | ||
141 | return code, answer | ||
142 | end | ||
143 | end | ||
144 | control:close() | ||
145 | return nil, answer | ||
146 | end | ||
147 | |||
148 | ----------------------------------------------------------------------------- | ||
149 | -- Trys a command on control socked, in case of error, the control connection | ||
150 | -- is closed. | ||
151 | -- Input | ||
152 | -- control: control connection socket | ||
153 | -- cmd: command | ||
154 | -- arg: command argument or nil if no argument | ||
155 | -- success: table with successfull reply status code | ||
156 | -- Returns | ||
157 | -- code: reply code or nil in case of error | ||
158 | -- answer: server complete answer or system error message | ||
159 | ----------------------------------------------------------------------------- | ||
160 | function Private.command(control, cmd, arg, success) | ||
161 | local err = Private.send_command(control, cmd, arg) | ||
162 | if err then return nil, err end | ||
163 | return Private.check_answer(control, success) | ||
164 | end | ||
165 | |||
166 | ----------------------------------------------------------------------------- | ||
167 | -- Check server greeting | 52 | -- Check server greeting |
168 | -- Input | 53 | -- Input |
169 | -- control: control connection with server | 54 | -- control: control connection with server |
@@ -171,10 +56,10 @@ end | |||
171 | -- code: nil if error | 56 | -- code: nil if error |
172 | -- answer: server answer or error message | 57 | -- answer: server answer or error message |
173 | ----------------------------------------------------------------------------- | 58 | ----------------------------------------------------------------------------- |
174 | function Private.greet(control) | 59 | local function greet(control) |
175 | local code, answer = Private.check_answer(control, {120, 220}) | 60 | local code, answer = check_answer(control, {120, 220}) |
176 | if code == 120 then -- please try again, somewhat busy now... | 61 | if code == 120 then -- please try again, somewhat busy now... |
177 | return Private.check_answer(control, {220}) | 62 | return check_answer(control, {220}) |
178 | end | 63 | end |
179 | return code, answer | 64 | return code, answer |
180 | end | 65 | end |
@@ -189,10 +74,10 @@ end | |||
189 | -- code: nil if error | 74 | -- code: nil if error |
190 | -- answer: server answer or error message | 75 | -- answer: server answer or error message |
191 | ----------------------------------------------------------------------------- | 76 | ----------------------------------------------------------------------------- |
192 | function Private.login(control, user, password) | 77 | local function login(control, user, password) |
193 | local code, answer = Private.command(control, "user", user, {230, 331}) | 78 | local code, answer = command(control, "user", user, {230, 331}) |
194 | if code == 331 and password then -- need pass and we have pass | 79 | if code == 331 and password then -- need pass and we have pass |
195 | return Private.command(control, "pass", password, {230, 202}) | 80 | return command(control, "pass", password, {230, 202}) |
196 | end | 81 | end |
197 | return code, answer | 82 | return code, answer |
198 | end | 83 | end |
@@ -206,9 +91,7 @@ end | |||
206 | -- code: nil if error | 91 | -- code: nil if error |
207 | -- answer: server answer or error message | 92 | -- answer: server answer or error message |
208 | ----------------------------------------------------------------------------- | 93 | ----------------------------------------------------------------------------- |
209 | function Private.cwd(control, path) | 94 | local function cwd(control, path) |
210 | if path then return Private.command(control, "cwd", path, {250}) | ||
211 | else return 250, nil end | ||
212 | end | 95 | end |
213 | 96 | ||
214 | ----------------------------------------------------------------------------- | 97 | ----------------------------------------------------------------------------- |
@@ -219,18 +102,18 @@ end | |||
219 | -- server: server socket bound to local address, nil if error | 102 | -- server: server socket bound to local address, nil if error |
220 | -- answer: error message if any | 103 | -- answer: error message if any |
221 | ----------------------------------------------------------------------------- | 104 | ----------------------------------------------------------------------------- |
222 | function Private.port(control) | 105 | local function port(control) |
223 | local code, answer | 106 | local code, answer |
224 | local server, ctl_ip | 107 | local server, ctl_ip |
225 | ctl_ip, answer = control:getsockname() | 108 | ctl_ip, answer = control:getsockname() |
226 | server, answer = socket.bind(ctl_ip, 0) | 109 | server, answer = socket.bind(ctl_ip, 0) |
227 | server:settimeout(Public.TIMEOUT) | 110 | server:settimeout(TIMEOUT) |
228 | local ip, p, ph, pl | 111 | local ip, p, ph, pl |
229 | ip, p = server:getsockname() | 112 | ip, p = server:getsockname() |
230 | pl = math.mod(p, 256) | 113 | pl = math.mod(p, 256) |
231 | ph = (p - pl)/256 | 114 | ph = (p - pl)/256 |
232 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | 115 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") |
233 | code, answer = Private.command(control, "port", arg, {200}) | 116 | code, answer = command(control, "port", arg, {200}) |
234 | if not code then | 117 | if not code then |
235 | server:close() | 118 | server:close() |
236 | return nil, answer | 119 | return nil, answer |
@@ -245,8 +128,8 @@ end | |||
245 | -- code: nil if error | 128 | -- code: nil if error |
246 | -- answer: server answer or error message | 129 | -- answer: server answer or error message |
247 | ----------------------------------------------------------------------------- | 130 | ----------------------------------------------------------------------------- |
248 | function Private.logout(control) | 131 | local function logout(control) |
249 | local code, answer = Private.command(control, "quit", nil, {221}) | 132 | local code, answer = command(control, "quit", nil, {221}) |
250 | if code then control:close() end | 133 | if code then control:close() end |
251 | return code, answer | 134 | return code, answer |
252 | end | 135 | end |
@@ -259,10 +142,10 @@ end | |||
259 | -- Returns | 142 | -- Returns |
260 | -- nil if successfull, or an error message in case of error | 143 | -- nil if successfull, or an error message in case of error |
261 | ----------------------------------------------------------------------------- | 144 | ----------------------------------------------------------------------------- |
262 | function Private.receive_indirect(data, callback) | 145 | local function receive_indirect(data, callback) |
263 | local chunk, err, res | 146 | local chunk, err, res |
264 | while not err do | 147 | while not err do |
265 | chunk, err = Private.try_receive(data, Public.BLOCKSIZE) | 148 | chunk, err = try_receive(data, BLOCKSIZE) |
266 | if err == "closed" then err = "done" end | 149 | if err == "closed" then err = "done" end |
267 | res = callback(chunk, err) | 150 | res = callback(chunk, err) |
268 | if not res then break end | 151 | if not res then break end |
@@ -280,16 +163,16 @@ end | |||
280 | -- Returns | 163 | -- Returns |
281 | -- err: error message in case of error, nil otherwise | 164 | -- err: error message in case of error, nil otherwise |
282 | ----------------------------------------------------------------------------- | 165 | ----------------------------------------------------------------------------- |
283 | function Private.retrieve(control, server, name, is_directory, content_cb) | 166 | local function retrieve(control, server, name, is_directory, content_cb) |
284 | local code, answer | 167 | local code, answer |
285 | local data | 168 | local data |
286 | -- ask server for file or directory listing accordingly | 169 | -- ask server for file or directory listing accordingly |
287 | if is_directory then | 170 | if is_directory then |
288 | code, answer = Private.cwd(control, name) | 171 | code, answer = cwd(control, name) |
289 | if not code then return answer end | 172 | if not code then return answer end |
290 | code, answer = Private.command(control, "nlst", nil, {150, 125}) | 173 | code, answer = command(control, "nlst", nil, {150, 125}) |
291 | else | 174 | else |
292 | code, answer = Private.command(control, "retr", name, {150, 125}) | 175 | code, answer = command(control, "retr", name, {150, 125}) |
293 | end | 176 | end |
294 | if not code then return nil, answer end | 177 | if not code then return nil, answer end |
295 | data, answer = server:accept() | 178 | data, answer = server:accept() |
@@ -298,43 +181,14 @@ function Private.retrieve(control, server, name, is_directory, content_cb) | |||
298 | control:close() | 181 | control:close() |
299 | return answer | 182 | return answer |
300 | end | 183 | end |
301 | answer = Private.receive_indirect(data, content_cb) | 184 | answer = receive_indirect(data, content_cb) |
302 | if answer then | 185 | if answer then |
303 | control:close() | 186 | control:close() |
304 | return answer | 187 | return answer |
305 | end | 188 | end |
306 | data:close() | 189 | data:close() |
307 | -- make sure file transfered ok | 190 | -- make sure file transfered ok |
308 | return Private.check_answer(control, {226, 250}) | 191 | return check_answer(control, {226, 250}) |
309 | end | ||
310 | |||
311 | ----------------------------------------------------------------------------- | ||
312 | -- Sends data comming from a callback | ||
313 | -- Input | ||
314 | -- data: data connection | ||
315 | -- send_cb: callback to produce file contents | ||
316 | -- chunk, size: first callback return values | ||
317 | -- Returns | ||
318 | -- nil if successfull, or an error message in case of error | ||
319 | ----------------------------------------------------------------------------- | ||
320 | function Private.send_indirect(data, send_cb, chunk, size) | ||
321 | local total, sent, err | ||
322 | total = 0 | ||
323 | while 1 do | ||
324 | if type(chunk) ~= "string" or type(size) ~= "number" then | ||
325 | data:close() | ||
326 | if not chunk and type(size) == "string" then return size | ||
327 | else return "invalid callback return" end | ||
328 | end | ||
329 | sent, err = data:send(chunk) | ||
330 | if err then | ||
331 | data:close() | ||
332 | return err | ||
333 | end | ||
334 | total = total + sent | ||
335 | if sent >= size then break end | ||
336 | chunk, size = send_cb() | ||
337 | end | ||
338 | end | 192 | end |
339 | 193 | ||
340 | ----------------------------------------------------------------------------- | 194 | ----------------------------------------------------------------------------- |
@@ -348,9 +202,9 @@ end | |||
348 | -- code: return code, nil if error | 202 | -- code: return code, nil if error |
349 | -- answer: server answer or error message | 203 | -- answer: server answer or error message |
350 | ----------------------------------------------------------------------------- | 204 | ----------------------------------------------------------------------------- |
351 | function Private.store(control, server, file, send_cb) | 205 | local function store(control, server, file, send_cb) |
352 | local data, err | 206 | local data, err |
353 | local code, answer = Private.command(control, "stor", file, {150, 125}) | 207 | local code, answer = command(control, "stor", file, {150, 125}) |
354 | if not code then | 208 | if not code then |
355 | control:close() | 209 | control:close() |
356 | return nil, answer | 210 | return nil, answer |
@@ -363,7 +217,7 @@ function Private.store(control, server, file, send_cb) | |||
363 | return nil, answer | 217 | return nil, answer |
364 | end | 218 | end |
365 | -- send whole file | 219 | -- send whole file |
366 | err = Private.send_indirect(data, send_cb, send_cb()) | 220 | err = send_indirect(data, send_cb, send_cb()) |
367 | if err then | 221 | if err then |
368 | control:close() | 222 | control:close() |
369 | return nil, err | 223 | return nil, err |
@@ -371,7 +225,7 @@ function Private.store(control, server, file, send_cb) | |||
371 | -- close connection to inform that file transmission is complete | 225 | -- close connection to inform that file transmission is complete |
372 | data:close() | 226 | data:close() |
373 | -- check if file was received correctly | 227 | -- check if file was received correctly |
374 | return Private.check_answer(control, {226, 250}) | 228 | return check_answer(control, {226, 250}) |
375 | end | 229 | end |
376 | 230 | ||
377 | ----------------------------------------------------------------------------- | 231 | ----------------------------------------------------------------------------- |
@@ -382,11 +236,11 @@ end | |||
382 | -- Returns | 236 | -- Returns |
383 | -- err: error message if any | 237 | -- err: error message if any |
384 | ----------------------------------------------------------------------------- | 238 | ----------------------------------------------------------------------------- |
385 | function Private.change_type(control, params) | 239 | local function change_type(control, params) |
386 | local type, _ | 240 | local type, _ |
387 | _, _, type = string.find(params or "", "type=(.)") | 241 | _, _, type = string.find(params or "", "type=(.)") |
388 | if type == "a" or type == "i" then | 242 | if type == "a" or type == "i" then |
389 | local code, err = Private.command(control, "type", type, {200}) | 243 | local code, err = command(control, "type", type, {200}) |
390 | if not code then return err end | 244 | if not code then return err end |
391 | end | 245 | end |
392 | end | 246 | end |
@@ -399,45 +253,42 @@ end | |||
399 | -- control: control connection with server, or nil if error | 253 | -- control: control connection with server, or nil if error |
400 | -- err: error message if any | 254 | -- err: error message if any |
401 | ----------------------------------------------------------------------------- | 255 | ----------------------------------------------------------------------------- |
402 | function Private.open(parsed) | 256 | local function open(parsed) |
403 | -- start control connection | 257 | local control, err = socket.tp.connect(parsed.host, parsed.port) |
404 | local control, err = socket.connect(parsed.host, parsed.port) | ||
405 | if not control then return nil, err end | 258 | if not control then return nil, err end |
406 | -- make sure we don't block forever | 259 | local code, reply |
407 | control:settimeout(Public.TIMEOUT) | 260 | -- greet |
408 | -- check greeting | 261 | code, reply = control:check({120, 220}) |
409 | local code, answer = Private.greet(control) | 262 | if code == 120 then -- busy, try again |
410 | if not code then return nil, answer end | 263 | code, reply = control:check(220) |
411 | -- try to log in | 264 | end |
412 | code, err = Private.login(control, parsed.user, parsed.password) | 265 | -- authenticate |
413 | if not code then return nil, err | 266 | code, reply = control:command("user", user) |
414 | else return control end | 267 | code, reply = control:check({230, 331}) |
268 | if code == 331 and password then -- need pass and we have pass | ||
269 | control:command("pass", password) | ||
270 | code, reply = control:check({230, 202}) | ||
271 | end | ||
272 | -- change directory | ||
273 | local segment = parse_path(parsed) | ||
274 | for i, v in ipairs(segment) do | ||
275 | code, reply = control:command("cwd") | ||
276 | code, reply = control:check(250) | ||
277 | end | ||
278 | -- change type | ||
279 | local type = string.sub(params or "", 7, 7) | ||
280 | if type == "a" or type == "i" then | ||
281 | code, reply = control:command("type", type) | ||
282 | code, reply = control:check(200) | ||
283 | end | ||
415 | end | 284 | end |
416 | 285 | ||
417 | ----------------------------------------------------------------------------- | 286 | return change_dir(control, segment) or |
418 | -- Closes the connection with the server | 287 | change_type(control, parsed.params) or |
419 | -- Input | 288 | download(control, request, segment) or |
420 | -- control: control connection with server | 289 | close(control) |
421 | ----------------------------------------------------------------------------- | ||
422 | function Private.close(control) | ||
423 | -- disconnect | ||
424 | Private.logout(control) | ||
425 | end | 290 | end |
426 | 291 | ||
427 | ----------------------------------------------------------------------------- | ||
428 | -- Changes to the directory pointed to by URL | ||
429 | -- Input | ||
430 | -- control: control connection with server | ||
431 | -- segment: parsed URL path segments | ||
432 | -- Returns | ||
433 | -- err: error message if any | ||
434 | ----------------------------------------------------------------------------- | ||
435 | function Private.change_dir(control, segment) | ||
436 | local n = table.getn(segment) | ||
437 | for i = 1, n-1 do | ||
438 | local code, answer = Private.cwd(control, segment[i]) | ||
439 | if not code then return answer end | ||
440 | end | ||
441 | end | 292 | end |
442 | 293 | ||
443 | ----------------------------------------------------------------------------- | 294 | ----------------------------------------------------------------------------- |
@@ -450,7 +301,7 @@ end | |||
450 | -- Returns | 301 | -- Returns |
451 | -- err: error message if any | 302 | -- err: error message if any |
452 | ----------------------------------------------------------------------------- | 303 | ----------------------------------------------------------------------------- |
453 | function Private.upload(control, request, segment) | 304 | local function upload(control, request, segment) |
454 | local code, name, content_cb | 305 | local code, name, content_cb |
455 | -- get remote file name | 306 | -- get remote file name |
456 | name = segment[table.getn(segment)] | 307 | name = segment[table.getn(segment)] |
@@ -460,10 +311,10 @@ function Private.upload(control, request, segment) | |||
460 | end | 311 | end |
461 | content_cb = request.content_cb | 312 | content_cb = request.content_cb |
462 | -- setup passive connection | 313 | -- setup passive connection |
463 | local server, answer = Private.port(control) | 314 | local server, answer = port(control) |
464 | if not server then return answer end | 315 | if not server then return answer end |
465 | -- ask server to receive file | 316 | -- ask server to receive file |
466 | code, answer = Private.store(control, server, name, content_cb) | 317 | code, answer = store(control, server, name, content_cb) |
467 | if not code then return answer end | 318 | if not code then return answer end |
468 | end | 319 | end |
469 | 320 | ||
@@ -477,7 +328,7 @@ end | |||
477 | -- Returns | 328 | -- Returns |
478 | -- err: error message if any | 329 | -- err: error message if any |
479 | ----------------------------------------------------------------------------- | 330 | ----------------------------------------------------------------------------- |
480 | function Private.download(control, request, segment) | 331 | local function download(control, request, segment) |
481 | local code, name, is_directory, content_cb | 332 | local code, name, is_directory, content_cb |
482 | is_directory = segment.is_directory | 333 | is_directory = segment.is_directory |
483 | content_cb = request.content_cb | 334 | content_cb = request.content_cb |
@@ -488,10 +339,10 @@ function Private.download(control, request, segment) | |||
488 | return "Invalid file path" | 339 | return "Invalid file path" |
489 | end | 340 | end |
490 | -- setup passive connection | 341 | -- setup passive connection |
491 | local server, answer = Private.port(control) | 342 | local server, answer = port(control) |
492 | if not server then return answer end | 343 | if not server then return answer end |
493 | -- ask server to send file or directory listing | 344 | -- ask server to send file or directory listing |
494 | code, answer = Private.retrieve(control, server, name, | 345 | code, answer = retrieve(control, server, name, |
495 | is_directory, content_cb) | 346 | is_directory, content_cb) |
496 | if not code then return answer end | 347 | if not code then return answer end |
497 | end | 348 | end |
@@ -507,13 +358,12 @@ end | |||
507 | -- Returns | 358 | -- Returns |
508 | -- parsed: a table with parsed components | 359 | -- parsed: a table with parsed components |
509 | ----------------------------------------------------------------------------- | 360 | ----------------------------------------------------------------------------- |
510 | function Private.parse_url(request) | 361 | local function parse_url(request) |
511 | local parsed = socket.url.parse(request.url, { | 362 | local parsed = socket.url.parse(request.url, { |
512 | host = "", | ||
513 | user = "anonymous", | 363 | user = "anonymous", |
514 | port = 21, | 364 | port = 21, |
515 | path = "/", | 365 | path = "/", |
516 | password = Public.EMAIL, | 366 | password = EMAIL, |
517 | scheme = "ftp" | 367 | scheme = "ftp" |
518 | }) | 368 | }) |
519 | -- explicit login information overrides that given by URL | 369 | -- explicit login information overrides that given by URL |
@@ -531,7 +381,7 @@ end | |||
531 | -- Returns | 381 | -- Returns |
532 | -- dirs: a table with parsed directory components | 382 | -- dirs: a table with parsed directory components |
533 | ----------------------------------------------------------------------------- | 383 | ----------------------------------------------------------------------------- |
534 | function Private.parse_path(parsed_url) | 384 | local function parse_path(parsed_url) |
535 | local segment = socket.url.parse_path(parsed_url.path) | 385 | local segment = socket.url.parse_path(parsed_url.path) |
536 | segment.is_directory = segment.is_directory or | 386 | segment.is_directory = segment.is_directory or |
537 | (parsed_url.params == "type=d") | 387 | (parsed_url.params == "type=d") |
@@ -549,7 +399,7 @@ end | |||
549 | -- Returns | 399 | -- Returns |
550 | -- request: request table | 400 | -- request: request table |
551 | ----------------------------------------------------------------------------- | 401 | ----------------------------------------------------------------------------- |
552 | function Private.build_request(data) | 402 | local function build_request(data) |
553 | local request = {} | 403 | local request = {} |
554 | if type(data) == "table" then for i, v in data do request[i] = v end | 404 | if type(data) == "table" then for i, v in data do request[i] = v end |
555 | else request.url = data end | 405 | else request.url = data end |
@@ -568,18 +418,18 @@ end | |||
568 | -- Returns | 418 | -- Returns |
569 | -- err: error message if any | 419 | -- err: error message if any |
570 | ----------------------------------------------------------------------------- | 420 | ----------------------------------------------------------------------------- |
571 | function Public.get_cb(request) | 421 | function get_cb(request) |
572 | local parsed = Private.parse_url(request) | 422 | local parsed = parse_url(request) |
573 | if parsed.scheme ~= "ftp" then | 423 | if parsed.scheme ~= "ftp" then |
574 | return string.format("unknown scheme '%s'", parsed.scheme) | 424 | return string.format("unknown scheme '%s'", parsed.scheme) |
575 | end | 425 | end |
576 | local control, err = Private.open(parsed) | 426 | local control, err = open(parsed) |
577 | if not control then return err end | 427 | if not control then return err end |
578 | local segment = Private.parse_path(parsed) | 428 | local segment = parse_path(parsed) |
579 | return Private.change_dir(control, segment) or | 429 | return change_dir(control, segment) or |
580 | Private.change_type(control, parsed.params) or | 430 | change_type(control, parsed.params) or |
581 | Private.download(control, request, segment) or | 431 | download(control, request, segment) or |
582 | Private.close(control) | 432 | close(control) |
583 | end | 433 | end |
584 | 434 | ||
585 | ----------------------------------------------------------------------------- | 435 | ----------------------------------------------------------------------------- |
@@ -594,18 +444,18 @@ end | |||
594 | -- Returns | 444 | -- Returns |
595 | -- err: error message if any | 445 | -- err: error message if any |
596 | ----------------------------------------------------------------------------- | 446 | ----------------------------------------------------------------------------- |
597 | function Public.put_cb(request) | 447 | function put_cb(request) |
598 | local parsed = Private.parse_url(request) | 448 | local parsed = parse_url(request) |
599 | if parsed.scheme ~= "ftp" then | 449 | if parsed.scheme ~= "ftp" then |
600 | return string.format("unknown scheme '%s'", parsed.scheme) | 450 | return string.format("unknown scheme '%s'", parsed.scheme) |
601 | end | 451 | end |
602 | local control, err = Private.open(parsed) | 452 | local control, err = open(parsed) |
603 | if not control then return err end | 453 | if not control then return err end |
604 | local segment = Private.parse_path(parsed) | 454 | local segment = parse_path(parsed) |
605 | err = Private.change_dir(control, segment) or | 455 | err = change_dir(control, segment) or |
606 | Private.change_type(control, parsed.params) or | 456 | change_type(control, parsed.params) or |
607 | Private.upload(control, request, segment) or | 457 | upload(control, request, segment) or |
608 | Private.close(control) | 458 | close(control) |
609 | if err then return nil, err | 459 | if err then return nil, err |
610 | else return 1 end | 460 | else return 1 end |
611 | end | 461 | end |
@@ -623,11 +473,11 @@ end | |||
623 | -- Returns | 473 | -- Returns |
624 | -- err: error message if any | 474 | -- err: error message if any |
625 | ----------------------------------------------------------------------------- | 475 | ----------------------------------------------------------------------------- |
626 | function Public.put(url_or_request, content) | 476 | function put(url_or_request, content) |
627 | local request = Private.build_request(url_or_request) | 477 | local request = build_request(url_or_request) |
628 | request.content = request.content or content | 478 | request.content = request.content or content |
629 | request.content_cb = socket.callback.send_string(request.content) | 479 | request.content_cb = socket.callback.send_string(request.content) |
630 | return Public.put_cb(request) | 480 | return put_cb(request) |
631 | end | 481 | end |
632 | 482 | ||
633 | ----------------------------------------------------------------------------- | 483 | ----------------------------------------------------------------------------- |
@@ -642,12 +492,12 @@ end | |||
642 | -- data: file contents as a string | 492 | -- data: file contents as a string |
643 | -- err: error message in case of error, nil otherwise | 493 | -- err: error message in case of error, nil otherwise |
644 | ----------------------------------------------------------------------------- | 494 | ----------------------------------------------------------------------------- |
645 | function Public.get(url_or_request) | 495 | function get(url_or_request) |
646 | local concat = socket.concat.create() | 496 | local concat = socket.concat.create() |
647 | local request = Private.build_request(url_or_request) | 497 | local request = build_request(url_or_request) |
648 | request.content_cb = socket.callback.receive_concat(concat) | 498 | request.content_cb = socket.callback.receive_concat(concat) |
649 | local err = Public.get_cb(request) | 499 | local err = get_cb(request) |
650 | return concat:getresult(), err | 500 | return concat:getresult(), err |
651 | end | 501 | end |
652 | 502 | ||
653 | return ftp | 503 | return socket.ftp |