aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2001-09-12 18:27:55 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2001-09-12 18:27:55 +0000
commit9546cd10ab0f9e51123e3111548549d36f10d473 (patch)
tree331c78a46fecebece37a1bdc5a3fa96fc0146344 /src
parent5c622071f95b833727767219cdee31b1419d6cc4 (diff)
downloadluasocket-9546cd10ab0f9e51123e3111548549d36f10d473.tar.gz
luasocket-9546cd10ab0f9e51123e3111548549d36f10d473.tar.bz2
luasocket-9546cd10ab0f9e51123e3111548549d36f10d473.zip
Updated for LuaSocket 1.4.
Better tested, some name changes.
Diffstat (limited to 'src')
-rw-r--r--src/ftp.lua551
1 files changed, 324 insertions, 227 deletions
diff --git a/src/ftp.lua b/src/ftp.lua
index bbb672b..98b08eb 100644
--- a/src/ftp.lua
+++ b/src/ftp.lua
@@ -1,60 +1,62 @@
1----------------------------------------------------------------------------- 1-----------------------------------------------------------------------------
2-- Simple FTP support for the Lua language using the LuaSocket 1.3b toolkit. 2-- FTP support for the Lua language
3-- LuaSocket 1.4 toolkit.
3-- Author: Diego Nehab 4-- Author: Diego Nehab
4-- Date: 26/12/2000 5-- Date: 26/12/2000
5-- Conforming to: RFC 959 6-- Conforming to: RFC 959, LTN7
7-- RCS ID: $Id$
6----------------------------------------------------------------------------- 8-----------------------------------------------------------------------------
7 9
10local Public, Private = {}, {}
11FTP = Public
12
8----------------------------------------------------------------------------- 13-----------------------------------------------------------------------------
9-- Program constants 14-- Program constants
10----------------------------------------------------------------------------- 15-----------------------------------------------------------------------------
11-- timeout in seconds before the program gives up on a connection 16-- timeout in seconds before the program gives up on a connection
12local TIMEOUT = 60 17Public.TIMEOUT = 60
13-- default port for ftp service 18-- default port for ftp service
14local PORT = 21 19Public.PORT = 21
15-- this is the default anonymous password. used when no password is 20-- this is the default anonymous password. used when no password is
16-- provided in url. should be changed for your e-mail. 21-- provided in url. should be changed to your e-mail.
17local EMAIL = "anonymous@anonymous.org" 22Public.EMAIL = "anonymous@anonymous.org"
18-- block size used in transfers 23-- block size used in transfers
19local BLOCKSIZE = 4096 24Public.BLOCKSIZE = 8192
25
26-----------------------------------------------------------------------------
27-- Required libraries
28-----------------------------------------------------------------------------
29dofile "concat.lua"
30dofile "url.lua"
31dofile "code.lua"
20 32
21----------------------------------------------------------------------------- 33-----------------------------------------------------------------------------
22-- Parses a url and returns its scheme, user, password, host, port 34-- Tries to send DOS mode lines. Closes socket on error.
23-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
24-- of December 1994
25-- Input 35-- Input
26-- url: unique resource locator desired 36-- sock: server socket
27-- default: table containing default values to be returned 37-- line: string to be sent
28-- Returns 38-- Returns
29-- table with the following fields: 39-- err: message in case of error, nil if successfull
30-- host: host to connect 40-----------------------------------------------------------------------------
31-- path: url path 41function Private.try_sendline(sock, line)
32-- port: host port to connect 42 local err = sock:send(line .. "\r\n")
33-- user: user name 43 if err then sock:close() end
34-- pass: password 44 return err
35-- scheme: protocol 45end
36----------------------------------------------------------------------------- 46
37local split_url = function(url, default) 47-----------------------------------------------------------------------------
38 -- initialize default parameters 48-- Tries to get a pattern from the server and closes socket on error
39 local parsed = default or {} 49-- sock: socket connected to the server
40 -- get scheme 50-- ...: pattern to receive
41 url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) 51-- Returns
42 -- get user name and password. both can be empty! 52-- ...: received pattern
43 -- moreover, password can be ommited 53-- err: error message if any
44 url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) 54-----------------------------------------------------------------------------
45 %parsed.user = u 55function Private.try_receive(...)
46 -- there can be an empty password, but the ':' has to be there 56 local sock = arg[1]
47 -- or else there is no password 57 local data, err = call(sock.receive, arg)
48 %parsed.pass = nil -- kill default password 58 if err then sock:close() end
49 if c == ":" then %parsed.pass = p end 59 return data, err
50 end)
51 -- get host
52 url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
53 -- get port if any
54 url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
55 -- whatever is left is the path
56 if url ~= "" then parsed.path = url end
57 return parsed
58end 60end
59 61
60----------------------------------------------------------------------------- 62-----------------------------------------------------------------------------
@@ -65,14 +67,12 @@ end
65-- ip: string containing ip for data connection 67-- ip: string containing ip for data connection
66-- port: port for data connection 68-- port: port for data connection
67----------------------------------------------------------------------------- 69-----------------------------------------------------------------------------
68local get_pasv = function(pasv) 70function Private.get_pasv(pasv)
69 local a,b,c,d,p1,p2 71 local a, b, c, d, p1, p2, _
70 local ip, port 72 local ip, port
71 _,_, a, b, c, d, p1, p2 = 73 _,_, a, b, c, d, p1, p2 =
72 strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") 74 strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)")
73 if not (a and b and c and d and p1 and p2) then 75 if not (a and b and c and d and p1 and p2) then return nil, nil end
74 return nil, nil
75 end
76 ip = format("%d.%d.%d.%d", a, b, c, d) 76 ip = format("%d.%d.%d.%d", a, b, c, d)
77 port = tonumber(p1)*256 + tonumber(p2) 77 port = tonumber(p1)*256 + tonumber(p2)
78 return ip, port 78 return ip, port
@@ -87,12 +87,11 @@ end
87-- Returns 87-- Returns
88-- error message in case of error, nil otherwise 88-- error message in case of error, nil otherwise
89----------------------------------------------------------------------------- 89-----------------------------------------------------------------------------
90local send_command = function(control, cmd, arg) 90function Private.send_command(control, cmd, arg)
91 local line, err 91 local line
92 if arg then line = cmd .. " " .. arg .. "\r\n" 92 if arg then line = cmd .. " " .. arg
93 else line = cmd .. "\r\n" end 93 else line = cmd end
94 err = control:send(line) 94 return %Private.try_sendline(control, line)
95 return err
96end 95end
97 96
98----------------------------------------------------------------------------- 97-----------------------------------------------------------------------------
@@ -103,16 +102,16 @@ end
103-- answer: whole server reply, nil if error 102-- answer: whole server reply, nil if error
104-- code: answer status code or error message 103-- code: answer status code or error message
105----------------------------------------------------------------------------- 104-----------------------------------------------------------------------------
106local get_answer = function(control) 105function Private.get_answer(control)
107 local code, lastcode, sep 106 local code, lastcode, sep, _
108 local line, err = control:receive() 107 local line, err = %Private.try_receive(control)
109 local answer = line 108 local answer = line
110 if err then return nil, err end 109 if err then return nil, err end
111 _,_, code, sep = strfind(line, "^(%d%d%d)(.)") 110 _,_, code, sep = strfind(line, "^(%d%d%d)(.)")
112 if not code or not sep then return nil, answer end 111 if not code or not sep then return nil, answer end
113 if sep == "-" then -- answer is multiline 112 if sep == "-" then -- answer is multiline
114 repeat 113 repeat
115 line, err = control:receive() 114 line, err = %Private.try_receive(control)
116 if err then return nil, err end 115 if err then return nil, err end
117 _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") 116 _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)")
118 answer = answer .. "\n" .. line 117 answer = answer .. "\n" .. line
@@ -130,12 +129,9 @@ end
130-- code: reply code or nil in case of error 129-- code: reply code or nil in case of error
131-- answer: server complete answer or system error message 130-- answer: server complete answer or system error message
132----------------------------------------------------------------------------- 131-----------------------------------------------------------------------------
133local check_answer = function(control, success) 132function Private.check_answer(control, success)
134 local answer, code = %get_answer(control) 133 local answer, code = %Private.get_answer(control)
135 if not answer then 134 if not answer then return nil, code end
136 control:close()
137 return nil, code
138 end
139 if type(success) ~= "table" then success = {success} end 135 if type(success) ~= "table" then success = {success} end
140 for i = 1, getn(success) do 136 for i = 1, getn(success) do
141 if code == success[i] then 137 if code == success[i] then
@@ -158,38 +154,10 @@ end
158-- code: reply code or nil in case of error 154-- code: reply code or nil in case of error
159-- answer: server complete answer or system error message 155-- answer: server complete answer or system error message
160----------------------------------------------------------------------------- 156-----------------------------------------------------------------------------
161local try_command = function(control, cmd, arg, success) 157function Private.command(control, cmd, arg, success)
162 local err = %send_command(control, cmd, arg) 158 local err = %Private.send_command(control, cmd, arg)
163 if err then 159 if err then return nil, err end
164 control:close() 160 return %Private.check_answer(control, success)
165 return nil, err
166 end
167 local code, answer = %check_answer(control, success)
168 if not code then return nil, answer end
169 return code, answer
170end
171
172-----------------------------------------------------------------------------
173-- Creates a table with all directories in path
174-- Input
175-- file: abolute path to file
176-- Returns
177-- a table with the following fields
178-- name: filename
179-- path: directory to file
180-- isdir: is it a directory?
181-----------------------------------------------------------------------------
182local split_path = function(file)
183 local parsed = {}
184 file = gsub(file, "(/)$", function(i) %parsed.isdir = i end)
185 if not parsed.isdir then
186 file = gsub(file, "([^/]+)$", function(n) %parsed.name = n end)
187 end
188 file = gsub(file, "/$", "")
189 file = gsub(file, "^/", "")
190 if file == "" then file = nil end
191 parsed.path = file
192 if parsed.path or parsed.name or parsed.isdir then return parsed end
193end 161end
194 162
195----------------------------------------------------------------------------- 163-----------------------------------------------------------------------------
@@ -200,11 +168,10 @@ end
200-- code: nil if error 168-- code: nil if error
201-- answer: server answer or error message 169-- answer: server answer or error message
202----------------------------------------------------------------------------- 170-----------------------------------------------------------------------------
203local check_greeting = function(control) 171function Private.greet(control)
204 local code, answer = %check_answer(control, {120, 220}) 172 local code, answer = %Private.check_answer(control, {120, 220})
205 if not code then return nil, answer end
206 if code == 120 then -- please try again, somewhat busy now... 173 if code == 120 then -- please try again, somewhat busy now...
207 code, answer = %check_answer(control, {220}) 174 return %Private.check_answer(control, {220})
208 end 175 end
209 return code, answer 176 return code, answer
210end 177end
@@ -214,16 +181,15 @@ end
214-- Input 181-- Input
215-- control: control connection with server 182-- control: control connection with server
216-- user: user name 183-- user: user name
217-- pass: user password if any 184-- password: user password if any
218-- Returns 185-- Returns
219-- code: nil if error 186-- code: nil if error
220-- answer: server answer or error message 187-- answer: server answer or error message
221----------------------------------------------------------------------------- 188-----------------------------------------------------------------------------
222local login = function(control, user, pass) 189function Private.login(control, user, password)
223 local code, answer = %try_command(control, "user", parsed.user, {230, 331}) 190 local code, answer = %Private.command(control, "user", user, {230, 331})
224 if not code then return nil, answer end 191 if code == 331 and password then -- need pass and we have pass
225 if code == 331 and parsed.pass then -- need pass and we have pass 192 return %Private.command(control, "pass", password, {230, 202})
226 code, answer = %try_command(control, "pass", parsed.pass, {230, 202})
227 end 193 end
228 return code, answer 194 return code, answer
229end 195end
@@ -237,12 +203,9 @@ end
237-- code: nil if error 203-- code: nil if error
238-- answer: server answer or error message 204-- answer: server answer or error message
239----------------------------------------------------------------------------- 205-----------------------------------------------------------------------------
240local cwd = function(control, path) 206function Private.cwd(control, path)
241 local code, answer = 250, "Home directory used" 207 if path then return %Private.command(control, "cwd", path, {250})
242 if path then 208 else return 250, nil end
243 code, answer = %try_command(control, "cwd", path, {250})
244 end
245 return code, answer
246end 209end
247 210
248----------------------------------------------------------------------------- 211-----------------------------------------------------------------------------
@@ -253,20 +216,19 @@ end
253-- server: server socket bound to local address, nil if error 216-- server: server socket bound to local address, nil if error
254-- answer: error message if any 217-- answer: error message if any
255----------------------------------------------------------------------------- 218-----------------------------------------------------------------------------
256local port = function(control) 219function Private.port(control)
257 local code, answer 220 local code, answer
258 local server, ctl_ip 221 local server, ctl_ip
259 ctl_ip, answer = control:getsockname() 222 ctl_ip, answer = control:getsockname()
260 server, answer = bind(ctl_ip, 0) 223 server, answer = bind(ctl_ip, 0)
261 server:timeout(%TIMEOUT) 224 server:timeout(%Public.TIMEOUT)
262 local ip, p, ph, pl 225 local ip, p, ph, pl
263 ip, p = server:getsockname() 226 ip, p = server:getsockname()
264 pl = mod(p, 256) 227 pl = mod(p, 256)
265 ph = (p - pl)/256 228 ph = (p - pl)/256
266 local arg = gsub(format("%s,%d,%d", ip, ph, pl), "%.", ",") 229 local arg = gsub(format("%s,%d,%d", ip, ph, pl), "%.", ",")
267 code, answer = %try_command(control, "port", arg, {200}) 230 code, answer = %Private.command(control, "port", arg, {200})
268 if not code then 231 if not code then
269 control:close()
270 server:close() 232 server:close()
271 return nil, answer 233 return nil, answer
272 else return server end 234 else return server end
@@ -280,10 +242,9 @@ end
280-- code: nil if error 242-- code: nil if error
281-- answer: server answer or error message 243-- answer: server answer or error message
282----------------------------------------------------------------------------- 244-----------------------------------------------------------------------------
283local logout = function(control) 245function Private.logout(control)
284 local code, answer = %try_command(control, "quit", nil, {221}) 246 local code, answer = %Private.command(control, "quit", nil, {221})
285 if not code then return nil, answer end 247 if code then control:close() end
286 control:close()
287 return code, answer 248 return code, answer
288end 249end
289 250
@@ -295,10 +256,10 @@ end
295-- Returns 256-- Returns
296-- nil if successfull, or an error message in case of error 257-- nil if successfull, or an error message in case of error
297----------------------------------------------------------------------------- 258-----------------------------------------------------------------------------
298local receive_indirect = function(data, callback) 259function Private.receive_indirect(data, callback)
299 local chunk, err, res 260 local chunk, err, res
300 while not err do 261 while not err do
301 chunk, err = data:receive(%BLOCKSIZE) 262 chunk, err = %Private.try_receive(data, %Public.BLOCKSIZE)
302 if err == "closed" then err = "done" end 263 if err == "closed" then err = "done" end
303 res = callback(chunk, err) 264 res = callback(chunk, err)
304 if not res then break end 265 if not res then break end
@@ -310,33 +271,38 @@ end
310-- Input 271-- Input
311-- control: control connection with server 272-- control: control connection with server
312-- server: server socket bound to local address 273-- server: server socket bound to local address
313-- file: file name under current directory 274-- name: file name
314-- isdir: is file a directory name? 275-- is_directory: is file a directory name?
315-- callback: callback to receive file contents 276-- download_cb: callback to receive file contents
316-- Returns 277-- Returns
317-- err: error message in case of error, nil otherwise 278-- err: error message in case of error, nil otherwise
318----------------------------------------------------------------------------- 279-----------------------------------------------------------------------------
319local retrieve = function(control, server, file, isdir, callback) 280function Private.retrieve(control, server, name, is_directory, download_cb)
320 local code, answer 281 local code, answer
321 local data 282 local data
322 -- ask server for file or directory listing accordingly 283 -- ask server for file or directory listing accordingly
323 if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) 284 if is_directory then
324 else code, answer = %try_command(control, "retr", file, {150, 125}) end 285 code, answer = %Private.cwd(control, name)
286 if not code then return answer end
287 code, answer = %Private.command(control, "nlst", nil, {150, 125})
288 else
289 code, answer = %Private.command(control, "retr", name, {150, 125})
290 end
291 if not code then return nil, answer end
325 data, answer = server:accept() 292 data, answer = server:accept()
326 server:close() 293 server:close()
327 if not data then 294 if not data then
328 control:close() 295 control:close()
329 return answer 296 return answer
330 end 297 end
331 answer = %receive_indirect(data, callback) 298 answer = %Private.receive_indirect(data, download_cb)
332 if answer then 299 if answer then
333 control:close() 300 control:close()
334 return answer 301 return answer
335 end 302 end
336 data:close() 303 data:close()
337 -- make sure file transfered ok 304 -- make sure file transfered ok
338 code, answer = %check_answer(control, {226, 250}) 305 return %Private.check_answer(control, {226, 250})
339 if not code then return answer end
340end 306end
341 307
342----------------------------------------------------------------------------- 308-----------------------------------------------------------------------------
@@ -344,11 +310,11 @@ end
344-- Input 310-- Input
345-- data: data connection 311-- data: data connection
346-- send_cb: callback to produce file contents 312-- send_cb: callback to produce file contents
347-- chunk, size: first callback results 313-- chunk, size: first callback return values
348-- Returns 314-- Returns
349-- nil if successfull, or an error message in case of error 315-- nil if successfull, or an error message in case of error
350----------------------------------------------------------------------------- 316-----------------------------------------------------------------------------
351local try_sendindirect = function(data, send_cb, chunk, size) 317function Private.send_indirect(data, send_cb, chunk, size)
352 local sent, err 318 local sent, err
353 sent = 0 319 sent = 0
354 while 1 do 320 while 1 do
@@ -358,9 +324,9 @@ local try_sendindirect = function(data, send_cb, chunk, size)
358 else return "invalid callback return" end 324 else return "invalid callback return" end
359 end 325 end
360 err = data:send(chunk) 326 err = data:send(chunk)
361 if err then 327 if err then
362 data:close() 328 data:close()
363 return err 329 return err
364 end 330 end
365 sent = sent + strlen(chunk) 331 sent = sent + strlen(chunk)
366 if sent >= size then break end 332 if sent >= size then break end
@@ -379,9 +345,9 @@ end
379-- code: return code, nil if error 345-- code: return code, nil if error
380-- answer: server answer or error message 346-- answer: server answer or error message
381----------------------------------------------------------------------------- 347-----------------------------------------------------------------------------
382local store = function(control, server, file, send_cb) 348function Private.store(control, server, file, send_cb)
383 local data 349 local data, err
384 local code, answer = %try_command(control, "stor", file, {150, 125}) 350 local code, answer = %Private.command(control, "stor", file, {150, 125})
385 if not code then 351 if not code then
386 control:close() 352 control:close()
387 return nil, answer 353 return nil, answer
@@ -394,7 +360,7 @@ local store = function(control, server, file, send_cb)
394 return nil, answer 360 return nil, answer
395 end 361 end
396 -- send whole file 362 -- send whole file
397 err = %try_sendindirect(data, send_cb, send_cb()) 363 err = %Private.send_indirect(data, send_cb, send_cb())
398 if err then 364 if err then
399 control:close() 365 control:close()
400 return nil, err 366 return nil, err
@@ -402,144 +368,275 @@ local store = function(control, server, file, send_cb)
402 -- close connection to inform that file transmission is complete 368 -- close connection to inform that file transmission is complete
403 data:close() 369 data:close()
404 -- check if file was received correctly 370 -- check if file was received correctly
405 return %check_answer(control, {226, 250}) 371 return %Private.check_answer(control, {226, 250})
406end 372end
407 373
408----------------------------------------------------------------------------- 374-----------------------------------------------------------------------------
409-- Change transfer type 375-- Change transfer type
410-- Input 376-- Input
411-- control: control connection with server 377-- control: control connection with server
412-- type: new transfer type 378-- params: "type=i" for binary or "type=a" for ascii
413-- Returns 379-- Returns
414-- code: nil if error 380-- err: error message if any
415-- answer: server answer or error message
416----------------------------------------------------------------------------- 381-----------------------------------------------------------------------------
417local change_type = function(control, type) 382function Private.change_type(control, params)
418 if type == "b" then type = "i" else type = "a" end 383 local type
419 return %try_command(control, "type", type, {200}) 384 if params == "type=i" then type = "i"
385 elseif params == "type=a" then type = "a" end
386 if type then
387 local code, err = %Private.command(control, "type", type, {200})
388 if not code then return err end
389 end
420end 390end
421 391
422----------------------------------------------------------------------------- 392-----------------------------------------------------------------------------
423-- Retrieve a file from a ftp server 393-- Starts a control connection, checks the greeting and log on
424-- Input 394-- Input
425-- url: file location 395-- parsed: parsed URL components
426-- receive_cb: callback to receive file contents
427-- type: "binary" or "ascii"
428-- Returns 396-- Returns
397-- control: control connection with server, or nil if error
429-- err: error message if any 398-- err: error message if any
430----------------------------------------------------------------------------- 399-----------------------------------------------------------------------------
431function ftp_getindirect(url, receive_cb, type) 400function Private.open(parsed)
432 local control, server, data, err
433 local answer, code, server, pfile, file
434 parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL})
435 -- start control connection 401 -- start control connection
436 control, err = connect(parsed.host, parsed.port) 402 local control, err = connect(parsed.host, parsed.port)
437 if not control then return err end 403 if not control then return nil, err end
438 control:timeout(%TIMEOUT) 404 -- make sure we don't block forever
439 -- get and check greeting 405 control:timeout(%Public.TIMEOUT)
440 code, answer = %check_greeting(control) 406 -- check greeting
441 if not code then return answer end 407 local code, answer = %Private.greet(control)
408 if not code then return nil, answer end
442 -- try to log in 409 -- try to log in
443 code, answer = %login(control, parsed.user, parsed.pass) 410 code, err = %Private.login(control, parsed.user, parsed.password)
444 if not code then return answer end 411 if not code then return nil, err
445 -- go to directory 412 else return control end
446 pfile = %split_path(parsed.path) 413end
447 if not pfile then return "invalid path" end 414
448 code, answer = %cwd(control, pfile.path) 415-----------------------------------------------------------------------------
449 if not code then return answer end 416-- Closes the connection with the server
450 -- change to binary type? 417-- Input
451 code, answer = %change_type(control, type) 418-- control: control connection with server
452 if not code then return answer end 419-----------------------------------------------------------------------------
453 -- setup passive connection 420function Private.close(control)
454 server, answer = %port(control)
455 if not server then return answer end
456 -- ask server to send file or directory listing
457 err = %retrieve(control, server, pfile.name, pfile.isdir, receive_cb)
458 if err then return err end
459 -- disconnect 421 -- disconnect
460 %logout(control) 422 %Private.logout(control)
461end 423end
462 424
463----------------------------------------------------------------------------- 425-----------------------------------------------------------------------------
464-- Uploads a file to a FTP server 426-- Changes to the directory pointed to by URL
465-- Input 427-- Input
466-- url: file location 428-- control: control connection with server
467-- send_cb: callback to produce the file contents 429-- segment: parsed URL path segments
468-- type: "binary" or "ascii"
469-- Returns 430-- Returns
470-- err: error message if any 431-- err: error message if any
471----------------------------------------------------------------------------- 432-----------------------------------------------------------------------------
472function ftp_putindirect(url, send_cb, type) 433function Private.change_dir(control, segment)
473 local control, data 434 local n = getn(segment)
474 local answer, code, server, file, pfile 435 for i = 1, n-1 do
475 parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) 436 local code, answer = %Private.cwd(control, segment[i])
476 -- start control connection 437 if not code then return answer end
477 control, answer = connect(parsed.host, parsed.port) 438 end
478 if not control then return answer end 439end
479 control:timeout(%TIMEOUT) 440
480 -- get and check greeting 441-----------------------------------------------------------------------------
481 code, answer = %check_greeting(control) 442-- Stores a file in current directory
482 if not code then return answer end 443-- Input
483 -- try to log in 444-- control: control connection with server
484 code, answer = %login(control, parsed.user, parsed.pass) 445-- request: a table with the fields:
485 if not code then return answer end 446-- upload_cb: send callback to send file contents
486 -- go to directory 447-- segment: parsed URL path segments
487 pfile = %split_path(parsed.path) 448-- Returns
488 if not pfile or pfile.isdir then return "invalid path" end 449-- err: error message if any
489 code, answer = %cwd(control, pfile.path) 450-----------------------------------------------------------------------------
490 if not code then return answer end 451function Private.upload(control, request, segment)
491 -- change to binary type? 452 local code, name, upload_cb
492 code, answer = %change_type(control, type) 453 -- get remote file name
454 name = segment[getn(segment)]
455 if not name then
456 control:close()
457 return "Invalid file path"
458 end
459 upload_cb = request.upload_cb
460 -- setup passive connection
461 local server, answer = %Private.port(control)
462 if not server then return answer end
463 -- ask server to receive file
464 code, answer = %Private.store(control, server, name, upload_cb)
493 if not code then return answer end 465 if not code then return answer end
466end
467
468-----------------------------------------------------------------------------
469-- Download a file from current directory
470-- Input
471-- control: control connection with server
472-- request: a table with the fields:
473-- download_cb: receive callback to receive file contents
474-- segment: parsed URL path segments
475-- Returns
476-- err: error message if any
477-----------------------------------------------------------------------------
478function Private.download(control, request, segment)
479 local code, name, is_directory, download_cb
480 is_directory = segment.is_directory
481 download_cb = request.download_cb
482 -- get remote file name
483 name = segment[getn(segment)]
484 if not name and not is_directory then
485 control:close()
486 return "Invalid file path"
487 end
494 -- setup passive connection 488 -- setup passive connection
495 server, answer = %port(control) 489 local server, answer = %Private.port(control)
496 if not server then return answer end 490 if not server then return answer end
497 -- ask server to send file 491 -- ask server to send file or directory listing
498 code, answer = %store(control, server, pfile.name, send_cb) 492 code, answer = %Private.retrieve(control, server, name,
493 is_directory, download_cb)
499 if not code then return answer end 494 if not code then return answer end
500 -- disconnect 495end
501 %logout(control) 496
502 -- no errors 497-----------------------------------------------------------------------------
503 return nil 498-- Parses the FTP URL setting default values
499-- Input
500-- request: a table with the fields:
501-- url: the target URL
502-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
503-- user: account user name
504-- password: account password
505-- Returns
506-- parsed: a table with parsed components
507-----------------------------------------------------------------------------
508function Private.parse_url(request)
509 local parsed = URL.parse_url(request.url, {
510 host = "",
511 user = "anonymous",
512 port = 21,
513 path = "/",
514 password = %Public.EMAIL
515 })
516 -- explicit login information overrides that given by URL
517 parsed.user = request.user or parsed.user
518 parsed.password = request.password or parsed.password
519 -- explicit representation type overrides that given by URL
520 if request.type then parsed.params = "type=" .. request.type end
521 return parsed
522end
523
524-----------------------------------------------------------------------------
525-- Parses the FTP URL path setting default values
526-- Input
527-- parsed: a table with the parsed URL components
528-- Returns
529-- dirs: a table with parsed directory components
530-----------------------------------------------------------------------------
531function Private.parse_path(parsed)
532 local segment = URL.parse_path(parsed.path)
533 segment.is_directory = segment.is_directory or (parsed.params == "type=d")
534 return segment
535end
536
537-----------------------------------------------------------------------------
538-- Builds a request table from a URL or request table
539-- Input
540-- url_or_request: target url or request table (a table with the fields:
541-- url: the target URL
542-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
543-- user: account user name
544-- password: account password)
545-- Returns
546-- request: request table
547-----------------------------------------------------------------------------
548function Private.build_request(data)
549 local request = {}
550 if type(data) == "table" then for i, v in data do request[i] = v end
551 else request.url = data end
552 return request
553end
554
555-----------------------------------------------------------------------------
556-- Downloads a file from a FTP server
557-- Input
558-- request: a table with the fields:
559-- url: the target URL
560-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
561-- user: account user name
562-- password: account password
563-- download_cb: receive callback to receive file contents
564-- Returns
565-- err: error message if any
566-----------------------------------------------------------------------------
567function Public.get_cb(request)
568 local parsed = %Private.parse_url(request)
569 local control, err = %Private.open(parsed)
570 if not control then return err end
571 local segment = %Private.parse_path(parsed)
572 return %Private.change_dir(control, segment) or
573 %Private.change_type(control, parsed.params) or
574 %Private.download(control, request, segment) or
575 %Private.close(control)
504end 576end
505 577
506----------------------------------------------------------------------------- 578-----------------------------------------------------------------------------
507-- Uploads a file to a FTP server 579-- Uploads a file to a FTP server
508-- Input 580-- Input
509-- url: file location 581-- request: a table with the fields:
510-- bytes: file contents 582-- url: the target URL
511-- type: "binary" or "ascii" 583-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
584-- user: account user name
585-- password: account password
586-- upload_cb: send callback to send file contents
512-- Returns 587-- Returns
513-- err: error message if any 588-- err: error message if any
514----------------------------------------------------------------------------- 589-----------------------------------------------------------------------------
515function ftp_put(url, bytes, type) 590function Public.put_cb(request)
516 local send_cb = function() 591 local parsed = %Private.parse_url(request)
517 return %bytes, strlen(%bytes) 592 local control, err = %Private.open(parsed)
518 end 593 if not control then return err end
519 return ftp_putindirect(url, send_cb, type) 594 local segment = %Private.parse_path(parsed)
595 return %Private.change_dir(control, segment) or
596 %Private.change_type(control, parsed.params) or
597 %Private.upload(control, request, segment) or
598 %Private.close(control)
520end 599end
521 600
522----------------------------------------------------------------------------- 601-----------------------------------------------------------------------------
523-- We need fast concatenation routines for direct requests 602-- Uploads a file to a FTP server
603-- Input
604-- url_or_request: target url or request table (a table with the fields:
605-- url: the target URL
606-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
607-- user: account user name
608-- password: account password)
609-- content: file contents
610-- Returns
611-- err: error message if any
524----------------------------------------------------------------------------- 612-----------------------------------------------------------------------------
525dofile("buffer.lua") 613function Public.put(url_or_request, content)
614 local request = %Private.build_request(url_or_request)
615 request.upload_cb = function()
616 return %content, strlen(%content)
617 end
618 return %Public.put_cb(request)
619end
526 620
527----------------------------------------------------------------------------- 621-----------------------------------------------------------------------------
528-- Retrieve a file from a ftp server 622-- Retrieve a file from a ftp server
529-- Input 623-- Input
530-- url: file location 624-- url_or_request: target url or request table (a table with the fields:
531-- type: "binary" or "ascii" 625-- url: the target URL
626-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
627-- user: account user name
628-- password: account password)
532-- Returns 629-- Returns
533-- data: file contents as a string 630-- data: file contents as a string
534-- err: error message in case of error, nil otherwise 631-- err: error message in case of error, nil otherwise
535----------------------------------------------------------------------------- 632-----------------------------------------------------------------------------
536function ftp_get(url, type) 633function Public.get(url_or_request)
537 local bytes = { buf = buf_create() } 634 local cat = Concat.create()
538 local receive_cb = function(chunk, err) 635 local request = %Private.build_request(url_or_request)
539 if not chunk then %bytes.buf = nil end 636 request.download_cb = function(chunk, err)
540 buf_addstring(%bytes.buf, chunk) 637 if chunk then %cat:addstring(chunk) end
541 return 1 638 return 1
542 end 639 end
543 err = ftp_getindirect(url, receive_cb, type) 640 local err = %Public.get_cb(request)
544 return buf_getresult(bytes.buf), err 641 return cat:getresult(), err
545end 642end