diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-05-25 05:27:44 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-05-25 05:27:44 +0000 |
commit | 888496aa821cd09d925046250ea98b1512293fd5 (patch) | |
tree | 217e2b532762a64b58c4fffcb2cba08ba2243462 /src/ftp.lua | |
parent | 4fc164b8eaa0453050a0a859321c327bb2c4f776 (diff) | |
download | luasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.gz luasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.bz2 luasocket-888496aa821cd09d925046250ea98b1512293fd5.zip |
FTP low-level working.
SMTP connection oriented working.
ltn12 improved.
Diffstat (limited to '')
-rw-r--r-- | src/ftp.lua | 557 |
1 files changed, 118 insertions, 439 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index 18dab6d..6074623 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
@@ -30,474 +30,153 @@ EMAIL = "anonymous@anonymous.org" | |||
30 | BLOCKSIZE = 2048 | 30 | BLOCKSIZE = 2048 |
31 | 31 | ||
32 | ----------------------------------------------------------------------------- | 32 | ----------------------------------------------------------------------------- |
33 | -- Gets ip and port for data connection from PASV answer | 33 | -- Low level FTP API |
34 | -- Input | ||
35 | -- pasv: PASV command answer | ||
36 | -- Returns | ||
37 | -- ip: string containing ip for data connection | ||
38 | -- port: port for data connection | ||
39 | ----------------------------------------------------------------------------- | 34 | ----------------------------------------------------------------------------- |
40 | local function get_pasv(pasv) | 35 | local metat = { __index = {} } |
41 | local a, b, c, d, p1, p2, _ | ||
42 | local ip, port | ||
43 | _,_, a, b, c, d, p1, p2 = | ||
44 | string.find(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") | ||
45 | if not (a and b and c and d and p1 and p2) then return nil, nil end | ||
46 | ip = string.format("%d.%d.%d.%d", a, b, c, d) | ||
47 | port = tonumber(p1)*256 + tonumber(p2) | ||
48 | return ip, port | ||
49 | end | ||
50 | |||
51 | ----------------------------------------------------------------------------- | ||
52 | -- Check server greeting | ||
53 | -- Input | ||
54 | -- control: control connection with server | ||
55 | -- Returns | ||
56 | -- code: nil if error | ||
57 | -- answer: server answer or error message | ||
58 | ----------------------------------------------------------------------------- | ||
59 | local function greet(control) | ||
60 | local code, answer = check_answer(control, {120, 220}) | ||
61 | if code == 120 then -- please try again, somewhat busy now... | ||
62 | return check_answer(control, {220}) | ||
63 | end | ||
64 | return code, answer | ||
65 | end | ||
66 | |||
67 | ----------------------------------------------------------------------------- | ||
68 | -- Log in on server | ||
69 | -- Input | ||
70 | -- control: control connection with server | ||
71 | -- user: user name | ||
72 | -- password: user password if any | ||
73 | -- Returns | ||
74 | -- code: nil if error | ||
75 | -- answer: server answer or error message | ||
76 | ----------------------------------------------------------------------------- | ||
77 | local function login(control, user, password) | ||
78 | local code, answer = command(control, "user", user, {230, 331}) | ||
79 | if code == 331 and password then -- need pass and we have pass | ||
80 | return command(control, "pass", password, {230, 202}) | ||
81 | end | ||
82 | return code, answer | ||
83 | end | ||
84 | |||
85 | ----------------------------------------------------------------------------- | ||
86 | -- Change to target directory | ||
87 | -- Input | ||
88 | -- control: socket for control connection with server | ||
89 | -- path: directory to change to | ||
90 | -- Returns | ||
91 | -- code: nil if error | ||
92 | -- answer: server answer or error message | ||
93 | ----------------------------------------------------------------------------- | ||
94 | local function cwd(control, path) | ||
95 | end | ||
96 | |||
97 | ----------------------------------------------------------------------------- | ||
98 | -- Change to target directory | ||
99 | -- Input | ||
100 | -- control: socket for control connection with server | ||
101 | -- Returns | ||
102 | -- server: server socket bound to local address, nil if error | ||
103 | -- answer: error message if any | ||
104 | ----------------------------------------------------------------------------- | ||
105 | local function port(control) | ||
106 | local code, answer | ||
107 | local server, ctl_ip | ||
108 | ctl_ip, answer = control:getsockname() | ||
109 | server, answer = socket.bind(ctl_ip, 0) | ||
110 | server:settimeout(TIMEOUT) | ||
111 | local ip, p, ph, pl | ||
112 | ip, p = server:getsockname() | ||
113 | pl = math.mod(p, 256) | ||
114 | ph = (p - pl)/256 | ||
115 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | ||
116 | code, answer = command(control, "port", arg, {200}) | ||
117 | if not code then | ||
118 | server:close() | ||
119 | return nil, answer | ||
120 | else return server end | ||
121 | end | ||
122 | 36 | ||
123 | ----------------------------------------------------------------------------- | 37 | function open(server, port) |
124 | -- Closes control connection with server | 38 | local tp = socket.try(socket.tp.connect(server, port or PORT)) |
125 | -- Input | 39 | return setmetatable({tp = tp}, metat) |
126 | -- control: control connection with server | ||
127 | -- Returns | ||
128 | -- code: nil if error | ||
129 | -- answer: server answer or error message | ||
130 | ----------------------------------------------------------------------------- | ||
131 | local function logout(control) | ||
132 | local code, answer = command(control, "quit", nil, {221}) | ||
133 | if code then control:close() end | ||
134 | return code, answer | ||
135 | end | 40 | end |
136 | 41 | ||
137 | ----------------------------------------------------------------------------- | 42 | local function port(portt) |
138 | -- Receives data and send it to a callback | 43 | return portt.server:accept() |
139 | -- Input | ||
140 | -- data: data connection | ||
141 | -- callback: callback to return file contents | ||
142 | -- Returns | ||
143 | -- nil if successfull, or an error message in case of error | ||
144 | ----------------------------------------------------------------------------- | ||
145 | local function receive_indirect(data, callback) | ||
146 | local chunk, err, res | ||
147 | while not err do | ||
148 | chunk, err = try_receive(data, BLOCKSIZE) | ||
149 | if err == "closed" then err = "done" end | ||
150 | res = callback(chunk, err) | ||
151 | if not res then break end | ||
152 | end | ||
153 | end | 44 | end |
154 | 45 | ||
155 | ----------------------------------------------------------------------------- | 46 | local function pasv(pasvt) |
156 | -- Retrieves file or directory listing | 47 | return socket.connect(pasvt.ip, pasvt.port) |
157 | -- Input | ||
158 | -- control: control connection with server | ||
159 | -- server: server socket bound to local address | ||
160 | -- name: file name | ||
161 | -- is_directory: is file a directory name? | ||
162 | -- content_cb: callback to receive file contents | ||
163 | -- Returns | ||
164 | -- err: error message in case of error, nil otherwise | ||
165 | ----------------------------------------------------------------------------- | ||
166 | local function retrieve(control, server, name, is_directory, content_cb) | ||
167 | local code, answer | ||
168 | local data | ||
169 | -- ask server for file or directory listing accordingly | ||
170 | if is_directory then | ||
171 | code, answer = cwd(control, name) | ||
172 | if not code then return answer end | ||
173 | code, answer = command(control, "nlst", nil, {150, 125}) | ||
174 | else | ||
175 | code, answer = command(control, "retr", name, {150, 125}) | ||
176 | end | ||
177 | if not code then return nil, answer end | ||
178 | data, answer = server:accept() | ||
179 | server:close() | ||
180 | if not data then | ||
181 | control:close() | ||
182 | return answer | ||
183 | end | ||
184 | answer = receive_indirect(data, content_cb) | ||
185 | if answer then | ||
186 | control:close() | ||
187 | return answer | ||
188 | end | ||
189 | data:close() | ||
190 | -- make sure file transfered ok | ||
191 | return check_answer(control, {226, 250}) | ||
192 | end | 48 | end |
193 | 49 | ||
194 | ----------------------------------------------------------------------------- | 50 | function metat.__index:login(user, password) |
195 | -- Stores a file | 51 | socket.try(self.tp:command("USER", user)) |
196 | -- Input | 52 | local code, reply = socket.try(self.tp:check{"2..", 331}) |
197 | -- control: control connection with server | 53 | if code == 331 then |
198 | -- server: server socket bound to local address | 54 | socket.try(password, reply) |
199 | -- file: file name under current directory | 55 | socket.try(self.tp:command("PASS", password)) |
200 | -- send_cb: callback to produce the file contents | 56 | socket.try(self.tp:check("2..")) |
201 | -- Returns | 57 | end |
202 | -- code: return code, nil if error | 58 | return 1 |
203 | -- answer: server answer or error message | 59 | end |
204 | ----------------------------------------------------------------------------- | 60 | |
205 | local function store(control, server, file, send_cb) | 61 | function metat.__index:pasv() |
206 | local data, err | 62 | socket.try(self.tp:command("PASV")) |
207 | local code, answer = command(control, "stor", file, {150, 125}) | 63 | local code, reply = socket.try(self.tp:check("2..")) |
208 | if not code then | 64 | local _, _, a, b, c, d, p1, p2 = |
209 | control:close() | 65 | string.find(reply, "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)") |
210 | return nil, answer | 66 | socket.try(a and b and c and d and p1 and p2, reply) |
211 | end | 67 | self.pasvt = { |
212 | -- start data connection | 68 | ip = string.format("%d.%d.%d.%d", a, b, c, d), |
213 | data, answer = server:accept() | 69 | port = p1*256 + p2 |
214 | server:close() | 70 | } |
215 | if not data then | 71 | if self.portt then |
216 | control:close() | 72 | self.portt.server:close() |
217 | return nil, answer | 73 | self.portt = nil |
218 | end | 74 | end |
219 | -- send whole file | 75 | return self.pasvt.ip, self.pasvt.port |
220 | err = send_indirect(data, send_cb, send_cb()) | ||
221 | if err then | ||
222 | control:close() | ||
223 | return nil, err | ||
224 | end | ||
225 | -- close connection to inform that file transmission is complete | ||
226 | data:close() | ||
227 | -- check if file was received correctly | ||
228 | return check_answer(control, {226, 250}) | ||
229 | end | ||
230 | |||
231 | ----------------------------------------------------------------------------- | ||
232 | -- Change transfer type | ||
233 | -- Input | ||
234 | -- control: control connection with server | ||
235 | -- params: "type=i" for binary or "type=a" for ascii | ||
236 | -- Returns | ||
237 | -- err: error message if any | ||
238 | ----------------------------------------------------------------------------- | ||
239 | local function change_type(control, params) | ||
240 | local type, _ | ||
241 | _, _, type = string.find(params or "", "type=(.)") | ||
242 | if type == "a" or type == "i" then | ||
243 | local code, err = command(control, "type", type, {200}) | ||
244 | if not code then return err end | ||
245 | end | ||
246 | end | 76 | end |
247 | 77 | ||
248 | ----------------------------------------------------------------------------- | 78 | function metat.__index:port(ip, port) |
249 | -- Starts a control connection, checks the greeting and log on | 79 | self.pasvt = nil |
250 | -- Input | 80 | local server |
251 | -- parsed: parsed URL components | 81 | if not ip then |
252 | -- Returns | 82 | ip, port = socket.try(self.tp:getcontrol():getsockname()) |
253 | -- control: control connection with server, or nil if error | 83 | server = socket.try(socket.bind(ip, 0)) |
254 | -- err: error message if any | 84 | ip, port = socket.try(server:getsockname()) |
255 | ----------------------------------------------------------------------------- | 85 | socket.try(server:settimeout(TIMEOUT)) |
256 | local function open(parsed) | 86 | end |
257 | local control, err = socket.tp.connect(parsed.host, parsed.port) | 87 | local pl = math.mod(port, 256) |
258 | if not control then return nil, err end | 88 | local ph = (port - pl)/256 |
89 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | ||
90 | socket.try(self.tp:command("port", arg)) | ||
91 | socket.try(self.tp:check("2..")) | ||
92 | self.portt = server and {ip = ip, port = port, server = server} | ||
93 | return 1 | ||
94 | end | ||
95 | |||
96 | function metat.__index:send(sendt) | ||
97 | local data | ||
98 | socket.try(self.pasvt or self.portt, "need port or pasv first") | ||
99 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | ||
100 | socket.try(self.tp:command(sendt.command, sendt.argument)) | ||
101 | if self.portt then data = socket.try(port(self.portt)) end | ||
102 | local step = sendt.step or ltn12.pump.step | ||
259 | local code, reply | 103 | local code, reply |
260 | -- greet | 104 | local checkstep = function(src, snk) |
261 | code, reply = control:check({120, 220}) | 105 | local readyt = socket.select(readt, nil, 0) |
262 | if code == 120 then -- busy, try again | 106 | if readyt[tp] then |
263 | code, reply = control:check(220) | 107 | code, reply = self.tp:check{"2..", "1.."} |
108 | if not code then | ||
109 | data:close() | ||
110 | return nil, reply | ||
111 | end | ||
112 | end | ||
113 | local ret, err = step(src, snk) | ||
114 | if err then data:close() end | ||
115 | return ret, err | ||
264 | end | 116 | end |
265 | -- authenticate | 117 | local sink = socket.sink("close-when-empty", data) |
266 | code, reply = control:command("user", user) | 118 | socket.try(ltn12.pump.all(sendt.source, sink, checkstep)) |
267 | code, reply = control:check({230, 331}) | 119 | if not code then code = socket.try(self.tp:check{"1..", "2.."}) end |
268 | if code == 331 and password then -- need pass and we have pass | 120 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
269 | control:command("pass", password) | 121 | return 1 |
270 | code, reply = control:check({230, 202}) | 122 | end |
123 | |||
124 | function metat.__index:receive(recvt) | ||
125 | local data | ||
126 | socket.try(self.pasvt or self.portt, "need port or pasv first") | ||
127 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | ||
128 | socket.try(self.tp:command(recvt.command, recvt.argument)) | ||
129 | if self.portt then data = socket.try(port(self.portt)) end | ||
130 | local source = socket.source("until-closed", data) | ||
131 | local step = recvt.step or ltn12.pump.step | ||
132 | local checkstep = function(src, snk) | ||
133 | local ret, err = step(src, snk) | ||
134 | if err then data:close() end | ||
135 | return ret, err | ||
271 | end | 136 | end |
272 | -- change directory | 137 | socket.try(ltn12.pump.all(source, recvt.sink, checkstep)) |
273 | local segment = parse_path(parsed) | 138 | local code = socket.try(self.tp:check{"1..", "2.."}) |
274 | for i, v in ipairs(segment) do | 139 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
275 | code, reply = control:command("cwd") | 140 | return 1 |
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 | ||
284 | end | 141 | end |
285 | 142 | ||
286 | return change_dir(control, segment) or | 143 | function metat.__index:cwd(dir) |
287 | change_type(control, parsed.params) or | 144 | socket.try(self.tp:command("CWD", dir)) |
288 | download(control, request, segment) or | 145 | socket.try(self.tp:check(250)) |
289 | close(control) | 146 | return 1 |
290 | end | 147 | end |
291 | 148 | ||
149 | function metat.__index:type(type) | ||
150 | socket.try(self.tp:command("TYPE", type)) | ||
151 | socket.try(self.tp:check(200)) | ||
152 | return 1 | ||
292 | end | 153 | end |
293 | 154 | ||
294 | ----------------------------------------------------------------------------- | 155 | function metat.__index:greet() |
295 | -- Stores a file in current directory | 156 | local code = socket.try(self.tp:check{"1..", "2.."}) |
296 | -- Input | 157 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
297 | -- control: control connection with server | 158 | return 1 |
298 | -- request: a table with the fields: | ||
299 | -- content_cb: send callback to send file contents | ||
300 | -- segment: parsed URL path segments | ||
301 | -- Returns | ||
302 | -- err: error message if any | ||
303 | ----------------------------------------------------------------------------- | ||
304 | local function upload(control, request, segment) | ||
305 | local code, name, content_cb | ||
306 | -- get remote file name | ||
307 | name = segment[table.getn(segment)] | ||
308 | if not name then | ||
309 | control:close() | ||
310 | return "Invalid file path" | ||
311 | end | ||
312 | content_cb = request.content_cb | ||
313 | -- setup passive connection | ||
314 | local server, answer = port(control) | ||
315 | if not server then return answer end | ||
316 | -- ask server to receive file | ||
317 | code, answer = store(control, server, name, content_cb) | ||
318 | if not code then return answer end | ||
319 | end | 159 | end |
320 | 160 | ||
321 | ----------------------------------------------------------------------------- | 161 | function metat.__index:quit() |
322 | -- Download a file from current directory | 162 | socket.try(self.tp:command("QUIT")) |
323 | -- Input | 163 | socket.try(self.tp:check("2..")) |
324 | -- control: control connection with server | 164 | return 1 |
325 | -- request: a table with the fields: | ||
326 | -- content_cb: receive callback to receive file contents | ||
327 | -- segment: parsed URL path segments | ||
328 | -- Returns | ||
329 | -- err: error message if any | ||
330 | ----------------------------------------------------------------------------- | ||
331 | local function download(control, request, segment) | ||
332 | local code, name, is_directory, content_cb | ||
333 | is_directory = segment.is_directory | ||
334 | content_cb = request.content_cb | ||
335 | -- get remote file name | ||
336 | name = segment[table.getn(segment)] | ||
337 | if not name and not is_directory then | ||
338 | control:close() | ||
339 | return "Invalid file path" | ||
340 | end | ||
341 | -- setup passive connection | ||
342 | local server, answer = port(control) | ||
343 | if not server then return answer end | ||
344 | -- ask server to send file or directory listing | ||
345 | code, answer = retrieve(control, server, name, | ||
346 | is_directory, content_cb) | ||
347 | if not code then return answer end | ||
348 | end | 165 | end |
349 | 166 | ||
350 | ----------------------------------------------------------------------------- | 167 | function metat.__index:close() |
351 | -- Parses the FTP URL setting default values | 168 | socket.try(self.tp:close()) |
352 | -- Input | 169 | return 1 |
353 | -- request: a table with the fields: | ||
354 | -- url: the target URL | ||
355 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
356 | -- user: account user name | ||
357 | -- password: account password | ||
358 | -- Returns | ||
359 | -- parsed: a table with parsed components | ||
360 | ----------------------------------------------------------------------------- | ||
361 | local function parse_url(request) | ||
362 | local parsed = socket.url.parse(request.url, { | ||
363 | user = "anonymous", | ||
364 | port = 21, | ||
365 | path = "/", | ||
366 | password = EMAIL, | ||
367 | scheme = "ftp" | ||
368 | }) | ||
369 | -- explicit login information overrides that given by URL | ||
370 | parsed.user = request.user or parsed.user | ||
371 | parsed.password = request.password or parsed.password | ||
372 | -- explicit representation type overrides that given by URL | ||
373 | if request.type then parsed.params = "type=" .. request.type end | ||
374 | return parsed | ||
375 | end | 170 | end |
376 | 171 | ||
377 | ----------------------------------------------------------------------------- | 172 | ----------------------------------------------------------------------------- |
378 | -- Parses the FTP URL path setting default values | 173 | -- High level FTP API |
379 | -- Input | ||
380 | -- parsed: a table with the parsed URL components | ||
381 | -- Returns | ||
382 | -- dirs: a table with parsed directory components | ||
383 | ----------------------------------------------------------------------------- | 174 | ----------------------------------------------------------------------------- |
384 | local function parse_path(parsed_url) | ||
385 | local segment = socket.url.parse_path(parsed_url.path) | ||
386 | segment.is_directory = segment.is_directory or | ||
387 | (parsed_url.params == "type=d") | ||
388 | return segment | ||
389 | end | ||
390 | 175 | ||
391 | ----------------------------------------------------------------------------- | 176 | function put(putt) |
392 | -- Builds a request table from a URL or request table | ||
393 | -- Input | ||
394 | -- url_or_request: target url or request table (a table with the fields: | ||
395 | -- url: the target URL | ||
396 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
397 | -- user: account user name | ||
398 | -- password: account password) | ||
399 | -- Returns | ||
400 | -- request: request table | ||
401 | ----------------------------------------------------------------------------- | ||
402 | local function build_request(data) | ||
403 | local request = {} | ||
404 | if type(data) == "table" then for i, v in data do request[i] = v end | ||
405 | else request.url = data end | ||
406 | return request | ||
407 | end | 177 | end |
408 | 178 | ||
409 | ----------------------------------------------------------------------------- | 179 | function get(gett) |
410 | -- Downloads a file from a FTP server | ||
411 | -- Input | ||
412 | -- request: a table with the fields: | ||
413 | -- url: the target URL | ||
414 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
415 | -- user: account user name | ||
416 | -- password: account password | ||
417 | -- content_cb: receive callback to receive file contents | ||
418 | -- Returns | ||
419 | -- err: error message if any | ||
420 | ----------------------------------------------------------------------------- | ||
421 | function get_cb(request) | ||
422 | local parsed = parse_url(request) | ||
423 | if parsed.scheme ~= "ftp" then | ||
424 | return string.format("unknown scheme '%s'", parsed.scheme) | ||
425 | end | ||
426 | local control, err = open(parsed) | ||
427 | if not control then return err end | ||
428 | local segment = parse_path(parsed) | ||
429 | return change_dir(control, segment) or | ||
430 | change_type(control, parsed.params) or | ||
431 | download(control, request, segment) or | ||
432 | close(control) | ||
433 | end | ||
434 | |||
435 | ----------------------------------------------------------------------------- | ||
436 | -- Uploads a file to a FTP server | ||
437 | -- Input | ||
438 | -- request: a table with the fields: | ||
439 | -- url: the target URL | ||
440 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
441 | -- user: account user name | ||
442 | -- password: account password | ||
443 | -- content_cb: send callback to send file contents | ||
444 | -- Returns | ||
445 | -- err: error message if any | ||
446 | ----------------------------------------------------------------------------- | ||
447 | function put_cb(request) | ||
448 | local parsed = parse_url(request) | ||
449 | if parsed.scheme ~= "ftp" then | ||
450 | return string.format("unknown scheme '%s'", parsed.scheme) | ||
451 | end | ||
452 | local control, err = open(parsed) | ||
453 | if not control then return err end | ||
454 | local segment = parse_path(parsed) | ||
455 | err = change_dir(control, segment) or | ||
456 | change_type(control, parsed.params) or | ||
457 | upload(control, request, segment) or | ||
458 | close(control) | ||
459 | if err then return nil, err | ||
460 | else return 1 end | ||
461 | end | ||
462 | |||
463 | ----------------------------------------------------------------------------- | ||
464 | -- Uploads a file to a FTP server | ||
465 | -- Input | ||
466 | -- url_or_request: target url or request table (a table with the fields: | ||
467 | -- url: the target URL | ||
468 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
469 | -- user: account user name | ||
470 | -- password: account password) | ||
471 | -- content: file contents | ||
472 | -- content: file contents | ||
473 | -- Returns | ||
474 | -- err: error message if any | ||
475 | ----------------------------------------------------------------------------- | ||
476 | function put(url_or_request, content) | ||
477 | local request = build_request(url_or_request) | ||
478 | request.content = request.content or content | ||
479 | request.content_cb = socket.callback.send_string(request.content) | ||
480 | return put_cb(request) | ||
481 | end | ||
482 | |||
483 | ----------------------------------------------------------------------------- | ||
484 | -- Retrieve a file from a ftp server | ||
485 | -- Input | ||
486 | -- url_or_request: target url or request table (a table with the fields: | ||
487 | -- url: the target URL | ||
488 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
489 | -- user: account user name | ||
490 | -- password: account password) | ||
491 | -- Returns | ||
492 | -- data: file contents as a string | ||
493 | -- err: error message in case of error, nil otherwise | ||
494 | ----------------------------------------------------------------------------- | ||
495 | function get(url_or_request) | ||
496 | local concat = socket.concat.create() | ||
497 | local request = build_request(url_or_request) | ||
498 | request.content_cb = socket.callback.receive_concat(concat) | ||
499 | local err = get_cb(request) | ||
500 | return concat:getresult(), err | ||
501 | end | 180 | end |
502 | 181 | ||
503 | return socket.ftp | 182 | return ftp |