diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-01-25 22:03:16 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-01-25 22:03:16 +0000 |
commit | a466bd5d4266047e3a54387a76ad5665055e583a (patch) | |
tree | afe41b02beebe3b8f334cdc13299c111d24b853a /src | |
parent | 273fd0964e4d6fd3f5457d090633596feb6d582a (diff) | |
download | luasocket-a466bd5d4266047e3a54387a76ad5665055e583a.tar.gz luasocket-a466bd5d4266047e3a54387a76ad5665055e583a.tar.bz2 luasocket-a466bd5d4266047e3a54387a76ad5665055e583a.zip |
Data connection is now passive. Even minimum FTP servers are usable.
Diffstat (limited to 'src')
-rw-r--r-- | src/ftp.lua | 135 |
1 files changed, 73 insertions, 62 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index b817356..a9dac71 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
@@ -1,5 +1,5 @@ | |||
1 | ----------------------------------------------------------------------------- | 1 | ----------------------------------------------------------------------------- |
2 | -- Simple FTP support for the Lua language using the LuaSocket toolkit. | 2 | -- Simple FTP support for the Lua language using the LuaSocket 1.2 toolkit. |
3 | -- Author: Diego Nehab | 3 | -- Author: Diego Nehab |
4 | -- Date: 26/12/2000 | 4 | -- Date: 26/12/2000 |
5 | -- Conforming to: RFC 959 | 5 | -- Conforming to: RFC 959 |
@@ -170,20 +170,22 @@ end | |||
170 | -- Input | 170 | -- Input |
171 | -- file: abolute path to file | 171 | -- file: abolute path to file |
172 | -- Returns | 172 | -- Returns |
173 | -- file: filename | 173 | -- a table with the following fields |
174 | -- path: table with directories to reach filename | 174 | -- name: filename |
175 | -- isdir: is it a directory or a file | 175 | -- path: directory to file |
176 | -- isdir: is it a directory? | ||
176 | ----------------------------------------------------------------------------- | 177 | ----------------------------------------------------------------------------- |
177 | local split_path = function(file) | 178 | local split_path = function(file) |
178 | local path = {} | 179 | local parsed = {} |
179 | local isdir | 180 | file = gsub(file, "(/)$", function(i) %parsed.isdir = i end) |
180 | file = file or "/" | 181 | if not parsed.isdir then |
181 | -- directory ends with a '/' | 182 | file = gsub(file, "([^/]+)$", function(n) %parsed.name = n end) |
182 | _,_, isdir = strfind(file, "([/])$") | 183 | end |
183 | gsub(file, "([^/]+)", function (dir) tinsert(%path, dir) end) | 184 | file = gsub(file, "/$", "") |
184 | if not isdir then file = tremove(path) | 185 | file = gsub(file, "^/", "") |
185 | else file = nil end | 186 | if file == "" then file = nil end |
186 | return file, path, isdir | 187 | parsed.path = file |
188 | if parsed.path or parsed.name or parsed.isdir then return parsed end | ||
187 | end | 189 | end |
188 | 190 | ||
189 | ----------------------------------------------------------------------------- | 191 | ----------------------------------------------------------------------------- |
@@ -226,40 +228,44 @@ end | |||
226 | -- Change to target directory | 228 | -- Change to target directory |
227 | -- Input | 229 | -- Input |
228 | -- control: socket for control connection with server | 230 | -- control: socket for control connection with server |
229 | -- path: array with directories in order | 231 | -- path: directory to change to |
230 | -- Returns | 232 | -- Returns |
231 | -- code: nil if error | 233 | -- code: nil if error |
232 | -- answer: server answer or error message | 234 | -- answer: server answer or error message |
233 | ----------------------------------------------------------------------------- | 235 | ----------------------------------------------------------------------------- |
234 | local cwd = function(control, path) | 236 | local cwd = function(control, path) |
235 | local code, answer = 250, "Home directory used" | 237 | local code, answer = 250, "Home directory used" |
236 | for i = 1, getn(path) do | 238 | if path then |
237 | code, answer = %try_command(control, "cwd", path[i], {250}) | 239 | code, answer = %try_command(control, "cwd", path, {250}) |
238 | if not code then return nil, answer end | ||
239 | end | 240 | end |
240 | return code, answer | 241 | return code, answer |
241 | end | 242 | end |
242 | 243 | ||
243 | ----------------------------------------------------------------------------- | 244 | ----------------------------------------------------------------------------- |
244 | -- Start data connection with server | 245 | -- Change to target directory |
245 | -- Input | 246 | -- Input |
246 | -- control: control connection with server | 247 | -- control: socket for control connection with server |
247 | -- Returns | 248 | -- Returns |
248 | -- data: socket for data connection with server, nil if error | 249 | -- server: server socket bound to local address, nil if error |
249 | -- answer: server answer or error message | 250 | -- answer: error message if any |
250 | ----------------------------------------------------------------------------- | 251 | ----------------------------------------------------------------------------- |
251 | local start_dataconnection = function(control) | 252 | local port = function(control) |
252 | -- ask for passive data connection | 253 | local code, answer |
253 | local code, answer = %try_command(control, "pasv", nil, {227}) | 254 | local server, ctl_ip |
254 | if not code then return nil, answer end | 255 | ctl_ip, answer = control:getsockname() |
255 | -- get data connection parameters from server reply | 256 | server, answer = bind(ctl_ip, 0) |
256 | local host, port = %get_pasv(answer) | 257 | server:timeout(%TIMEOUT) |
257 | if not host or not port then return nil, answer end | 258 | local ip, p, ph, pl |
258 | -- start data connection with given parameters | 259 | ip, p = server:getsockname() |
259 | local data, err = connect(host, port) | 260 | pl = mod(p, 256) |
260 | if not data then return nil, err end | 261 | ph = (p - pl)/256 |
261 | data:timeout(%TIMEOUT) | 262 | local arg = gsub(format("%s,%d,%d", ip, ph, pl), "%.", ",") |
262 | return data | 263 | code, answer = %try_command(control, "port", arg, {200}) |
264 | if not code then | ||
265 | control:close() | ||
266 | server:close() | ||
267 | return nil, answer | ||
268 | else return server end | ||
263 | end | 269 | end |
264 | 270 | ||
265 | ----------------------------------------------------------------------------- | 271 | ----------------------------------------------------------------------------- |
@@ -281,22 +287,21 @@ end | |||
281 | -- Retrieves file or directory listing | 287 | -- Retrieves file or directory listing |
282 | -- Input | 288 | -- Input |
283 | -- control: control connection with server | 289 | -- control: control connection with server |
284 | -- data: data connection with server | 290 | -- server: server socket bound to local address |
285 | -- file: file name under current directory | 291 | -- file: file name under current directory |
286 | -- isdir: is file a directory name? | 292 | -- isdir: is file a directory name? |
287 | -- Returns | 293 | -- Returns |
288 | -- file: string with file contents, nil if error | 294 | -- file: string with file contents, nil if error |
289 | -- answer: server answer or error message | 295 | -- answer: server answer or error message |
290 | ----------------------------------------------------------------------------- | 296 | ----------------------------------------------------------------------------- |
291 | local retrieve_file = function(control, data, file, isdir) | 297 | local retrieve_file = function(control, server, file, isdir) |
298 | local data | ||
292 | -- ask server for file or directory listing accordingly | 299 | -- ask server for file or directory listing accordingly |
293 | if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) | 300 | if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) |
294 | else code, answer = %try_command(control, "retr", file, {150, 125}) end | 301 | else code, answer = %try_command(control, "retr", file, {150, 125}) end |
295 | if not code then | 302 | data, answer = server:accept() |
296 | control:close() | 303 | server:close() |
297 | data:close() | 304 | if not data then return nil, answer end |
298 | return nil, answer | ||
299 | end | ||
300 | -- download whole file | 305 | -- download whole file |
301 | file, err = data:receive("*a") | 306 | file, err = data:receive("*a") |
302 | data:close() | 307 | data:close() |
@@ -314,19 +319,23 @@ end | |||
314 | -- Stores a file | 319 | -- Stores a file |
315 | -- Input | 320 | -- Input |
316 | -- control: control connection with server | 321 | -- control: control connection with server |
317 | -- data: data connection with server | 322 | -- server: server socket bound to local address |
318 | -- file: file name under current directory | 323 | -- file: file name under current directory |
319 | -- bytes: file contents in string | 324 | -- bytes: file contents in string |
320 | -- Returns | 325 | -- Returns |
321 | -- file: string with file contents, nil if error | 326 | -- code: return code, nil if error |
322 | -- answer: server answer or error message | 327 | -- answer: server answer or error message |
323 | ----------------------------------------------------------------------------- | 328 | ----------------------------------------------------------------------------- |
324 | local store_file = function (control, data, file, bytes) | 329 | local store_file = function (control, server, file, bytes) |
330 | local data | ||
325 | local code, answer = %try_command(control, "stor", file, {150, 125}) | 331 | local code, answer = %try_command(control, "stor", file, {150, 125}) |
326 | if not code then | 332 | if not code then |
327 | data:close() | 333 | data:close() |
328 | return nil, answer | 334 | return nil, answer |
329 | end | 335 | end |
336 | data, answer = server:accept() | ||
337 | server:close() | ||
338 | if not data then return nil, answer end | ||
330 | -- send whole file and close connection to mark file end | 339 | -- send whole file and close connection to mark file end |
331 | answer = data:send(bytes) | 340 | answer = data:send(bytes) |
332 | data:close() | 341 | data:close() |
@@ -362,8 +371,8 @@ end | |||
362 | -- err: error message if any | 371 | -- err: error message if any |
363 | ----------------------------------------------------------------------------- | 372 | ----------------------------------------------------------------------------- |
364 | function ftp_get(url, type) | 373 | function ftp_get(url, type) |
365 | local control, data, err | 374 | local control, server, data, err |
366 | local answer, code, server, file, path | 375 | local answer, code, server, pfile, file |
367 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | 376 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) |
368 | -- start control connection | 377 | -- start control connection |
369 | control, err = connect(parsed.host, parsed.port) | 378 | control, err = connect(parsed.host, parsed.port) |
@@ -376,21 +385,22 @@ function ftp_get(url, type) | |||
376 | code, answer = %login(control, parsed.user, parsed.pass) | 385 | code, answer = %login(control, parsed.user, parsed.pass) |
377 | if not code then return nil, answer end | 386 | if not code then return nil, answer end |
378 | -- go to directory | 387 | -- go to directory |
379 | file, path, isdir = %split_path(parsed.path) | 388 | pfile = %split_path(parsed.path) |
380 | code, answer = %cwd(control, path) | 389 | if not pfile then return nil, "invalid path" end |
390 | code, answer = %cwd(control, pfile.path) | ||
381 | if not code then return nil, answer end | 391 | if not code then return nil, answer end |
382 | -- change to binary type? | 392 | -- change to binary type? |
383 | code, answer = %change_type(control, type) | 393 | code, answer = %change_type(control, type) |
384 | if not code then return nil, answer end | 394 | if not code then return nil, answer end |
385 | -- start data connection | 395 | -- setup passive connection |
386 | data, answer = %start_dataconnection(control) | 396 | server, answer = %port(control) |
387 | if not data then return nil, answer end | 397 | if not server then return nil, answer end |
388 | -- ask server to send file or directory listing | 398 | -- ask server to send file or directory listing |
389 | file, answer = %retrieve_file(control, data, file, isdir) | 399 | file, answer = %retrieve_file(control, server, pfile.name, pfile.isdir) |
390 | if not file then return nil, answer end | 400 | if not file then return nil, answer end |
391 | -- disconnect | 401 | -- disconnect |
392 | %logout(control) | 402 | %logout(control) |
393 | -- return whatever file we received plus a possible error | 403 | -- return whatever file we received plus a possible error message |
394 | return file, answer | 404 | return file, answer |
395 | end | 405 | end |
396 | 406 | ||
@@ -405,7 +415,7 @@ end | |||
405 | ----------------------------------------------------------------------------- | 415 | ----------------------------------------------------------------------------- |
406 | function ftp_put(url, bytes, type) | 416 | function ftp_put(url, bytes, type) |
407 | local control, data | 417 | local control, data |
408 | local answer, code, server, file, path | 418 | local answer, code, server, file, pfile |
409 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | 419 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) |
410 | -- start control connection | 420 | -- start control connection |
411 | control, answer = connect(parsed.host, parsed.port) | 421 | control, answer = connect(parsed.host, parsed.port) |
@@ -418,20 +428,21 @@ function ftp_put(url, bytes, type) | |||
418 | code, answer = %login(control, parsed.user, parsed.pass) | 428 | code, answer = %login(control, parsed.user, parsed.pass) |
419 | if not code then return answer end | 429 | if not code then return answer end |
420 | -- go to directory | 430 | -- go to directory |
421 | file, path, isdir = %split_path(parsed.path) | 431 | pfile = %split_path(parsed.path) |
422 | code, answer = %cwd(control, path) | 432 | if not pfile or pfile.isdir then return "invalid path" end |
433 | code, answer = %cwd(control, pfile.path) | ||
423 | if not code then return answer end | 434 | if not code then return answer end |
424 | -- change to binary type? | 435 | -- change to binary type? |
425 | code, answer = %change_type(control, type) | 436 | code, answer = %change_type(control, type) |
426 | if not code then return answer end | 437 | if not code then return answer end |
427 | -- start data connection | 438 | -- setup passive connection |
428 | data, answer = %start_dataconnection(control) | 439 | server, answer = %port(control) |
429 | if not data then return answer end | 440 | if not server then return answer end |
430 | -- ask server to send file or directory listing | 441 | -- ask server to send file |
431 | code, answer = %store_file(control, data, file, bytes) | 442 | code, answer = %store_file(control, server, pfile.name, bytes) |
432 | if not code then return answer end | 443 | if not code then return answer end |
433 | -- disconnect | 444 | -- disconnect |
434 | %logout(control) | 445 | %logout(control) |
435 | -- return whatever file we received plus a possible error | 446 | -- no errors |
436 | return nil | 447 | return nil |
437 | end | 448 | end |