aboutsummaryrefslogtreecommitdiff
path: root/src/ftp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/ftp.lua')
-rw-r--r--src/ftp.lua360
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
9local Public, Private = {}, {} 9if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
10local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace 10-- get LuaSocket namespace
11socket.ftp = Public -- create ftp sub namespace 11local socket = _G[LUASOCKET_LIBNAME]
12if not socket then error('module requires LuaSocket') end
13-- create namespace inside LuaSocket namespace
14socket.ftp = socket.ftp or {}
15-- make all module globals fall into namespace
16setmetatable(socket.ftp, { __index = _G })
17setfenv(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
17Public.TIMEOUT = 60 23TIMEOUT = 60
18-- default port for ftp service 24-- default port for ftp service
19Public.PORT = 21 25PORT = 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.
22Public.EMAIL = "anonymous@anonymous.org" 28EMAIL = "anonymous@anonymous.org"
23-- block size used in transfers 29-- block size used in transfers
24Public.BLOCKSIZE = 8192 30BLOCKSIZE = 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-----------------------------------------------------------------------------
34function Private.try_receive(sock, pattern)
35 local data, err = sock:receive(pattern)
36 if not data then sock:close() end
37 return data, err
38end
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-----------------------------------------------------------------------------
47function Private.try_send(sock, data)
48 local sent, err = sock:send(data)
49 if not sent then sock:close() end
50 return err
51end
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-----------------------------------------------------------------------------
61function Private.try_sendline(sock, line)
62 return Private.try_send(sock, line .. "\r\n")
63end
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-----------------------------------------------------------------------------
73function Private.get_pasv(pasv) 40local 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)
82end 49end
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-----------------------------------------------------------------------------
93function 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)
98end
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-----------------------------------------------------------------------------
108function 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)
124end
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-----------------------------------------------------------------------------
135function 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
146end
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-----------------------------------------------------------------------------
160function 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)
164end
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-----------------------------------------------------------------------------
174function Private.greet(control) 59local 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
180end 65end
@@ -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-----------------------------------------------------------------------------
192function Private.login(control, user, password) 77local 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
198end 83end
@@ -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-----------------------------------------------------------------------------
209function Private.cwd(control, path) 94local function cwd(control, path)
210 if path then return Private.command(control, "cwd", path, {250})
211 else return 250, nil end
212end 95end
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-----------------------------------------------------------------------------
222function Private.port(control) 105local 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-----------------------------------------------------------------------------
248function Private.logout(control) 131local 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
252end 135end
@@ -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-----------------------------------------------------------------------------
262function Private.receive_indirect(data, callback) 145local 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-----------------------------------------------------------------------------
283function Private.retrieve(control, server, name, is_directory, content_cb) 166local 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})
309end
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-----------------------------------------------------------------------------
320function 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
338end 192end
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-----------------------------------------------------------------------------
351function Private.store(control, server, file, send_cb) 205local 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})
375end 229end
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-----------------------------------------------------------------------------
385function Private.change_type(control, params) 239local 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
392end 246end
@@ -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-----------------------------------------------------------------------------
402function Private.open(parsed) 256local 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
415end 284end
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-----------------------------------------------------------------------------
422function Private.close(control)
423 -- disconnect
424 Private.logout(control)
425end 290end
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-----------------------------------------------------------------------------
435function 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
441end 292end
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-----------------------------------------------------------------------------
453function Private.upload(control, request, segment) 304local 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
468end 319end
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-----------------------------------------------------------------------------
480function Private.download(control, request, segment) 331local 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
497end 348end
@@ -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-----------------------------------------------------------------------------
510function Private.parse_url(request) 361local 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-----------------------------------------------------------------------------
534function Private.parse_path(parsed_url) 384local 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-----------------------------------------------------------------------------
552function Private.build_request(data) 402local 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-----------------------------------------------------------------------------
571function Public.get_cb(request) 421function 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)
583end 433end
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-----------------------------------------------------------------------------
597function Public.put_cb(request) 447function 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
611end 461end
@@ -623,11 +473,11 @@ end
623-- Returns 473-- Returns
624-- err: error message if any 474-- err: error message if any
625----------------------------------------------------------------------------- 475-----------------------------------------------------------------------------
626function Public.put(url_or_request, content) 476function 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)
631end 481end
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-----------------------------------------------------------------------------
645function Public.get(url_or_request) 495function 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
651end 501end
652 502
653return ftp 503return socket.ftp