diff options
| author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-06-06 20:55:45 +0000 |
|---|---|---|
| committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-06-06 20:55:45 +0000 |
| commit | c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea (patch) | |
| tree | 07d34dbcd03c4ac81353fb716ea4e7c914e0e633 /src/ftp.lua | |
| parent | 77090c53fe558c48680516410a928bece08dd0d5 (diff) | |
| download | luasocket-c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea.tar.gz luasocket-c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea.tar.bz2 luasocket-c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea.zip | |
Streaming by callbacks implemented.
Diffstat (limited to 'src/ftp.lua')
| -rw-r--r-- | src/ftp.lua | 173 |
1 files changed, 135 insertions, 38 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index a9dac71..229bbc6 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
| @@ -15,6 +15,8 @@ local PORT = 21 | |||
| 15 | -- this is the default anonymous password. used when no password is | 15 | -- this is the default anonymous password. used when no password is |
| 16 | -- provided in url. should be changed for your e-mail. | 16 | -- provided in url. should be changed for your e-mail. |
| 17 | local EMAIL = "anonymous@anonymous.org" | 17 | local EMAIL = "anonymous@anonymous.org" |
| 18 | -- block size used in transfers | ||
| 19 | local BLOCKSIZE = 4096 | ||
| 18 | 20 | ||
| 19 | ----------------------------------------------------------------------------- | 21 | ----------------------------------------------------------------------------- |
| 20 | -- Parses a url and returns its scheme, user, password, host, port | 22 | -- Parses a url and returns its scheme, user, password, host, port |
| @@ -68,7 +70,7 @@ local get_pasv = function(pasv) | |||
| 68 | local ip, port | 70 | local ip, port |
| 69 | _,_, a, b, c, d, p1, p2 = | 71 | _,_, a, b, c, d, p1, p2 = |
| 70 | strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") | 72 | strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") |
| 71 | if not a or not b or not c or not d or not p1 or not p2 then | 73 | if not (a and b and c and d and p1 and p2) then |
| 72 | return nil, nil | 74 | return nil, nil |
| 73 | end | 75 | end |
| 74 | ip = format("%d.%d.%d.%d", a, b, c, d) | 76 | ip = format("%d.%d.%d.%d", a, b, c, d) |
| @@ -82,6 +84,8 @@ end | |||
| 82 | -- control: control connection socket | 84 | -- control: control connection socket |
| 83 | -- cmd: command | 85 | -- cmd: command |
| 84 | -- arg: command argument if any | 86 | -- arg: command argument if any |
| 87 | -- Returns | ||
| 88 | -- error message in case of error, nil otherwise | ||
| 85 | ----------------------------------------------------------------------------- | 89 | ----------------------------------------------------------------------------- |
| 86 | local send_command = function(control, cmd, arg) | 90 | local send_command = function(control, cmd, arg) |
| 87 | local line, err | 91 | local line, err |
| @@ -284,35 +288,84 @@ local logout = function(control) | |||
| 284 | end | 288 | end |
| 285 | 289 | ||
| 286 | ----------------------------------------------------------------------------- | 290 | ----------------------------------------------------------------------------- |
| 291 | -- Receives data and send it to a callback | ||
| 292 | -- Input | ||
| 293 | -- data: data connection | ||
| 294 | -- callback: callback to return file contents | ||
| 295 | -- Returns | ||
| 296 | -- nil if successfull, or an error message in case of error | ||
| 297 | ----------------------------------------------------------------------------- | ||
| 298 | local receive_indirect = function(data, callback) | ||
| 299 | local chunk, err, res | ||
| 300 | while not err do | ||
| 301 | chunk, err = data:receive(%BLOCKSIZE) | ||
| 302 | if err == "closed" then err = "done" end | ||
| 303 | res = callback(chunk, err) | ||
| 304 | if not res then break end | ||
| 305 | end | ||
| 306 | end | ||
| 307 | |||
| 308 | ----------------------------------------------------------------------------- | ||
| 287 | -- Retrieves file or directory listing | 309 | -- Retrieves file or directory listing |
| 288 | -- Input | 310 | -- Input |
| 289 | -- control: control connection with server | 311 | -- control: control connection with server |
| 290 | -- server: server socket bound to local address | 312 | -- server: server socket bound to local address |
| 291 | -- file: file name under current directory | 313 | -- file: file name under current directory |
| 292 | -- isdir: is file a directory name? | 314 | -- isdir: is file a directory name? |
| 315 | -- callback: callback to receive file contents | ||
| 293 | -- Returns | 316 | -- Returns |
| 294 | -- file: string with file contents, nil if error | 317 | -- err: error message in case of error, nil otherwise |
| 295 | -- answer: server answer or error message | ||
| 296 | ----------------------------------------------------------------------------- | 318 | ----------------------------------------------------------------------------- |
| 297 | local retrieve_file = function(control, server, file, isdir) | 319 | local retrieve = function(control, server, file, isdir, callback) |
| 320 | local code, answer | ||
| 298 | local data | 321 | local data |
| 299 | -- ask server for file or directory listing accordingly | 322 | -- ask server for file or directory listing accordingly |
| 300 | if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) | 323 | if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) |
| 301 | else code, answer = %try_command(control, "retr", file, {150, 125}) end | 324 | else code, answer = %try_command(control, "retr", file, {150, 125}) end |
| 302 | data, answer = server:accept() | 325 | data, answer = server:accept() |
| 303 | server:close() | 326 | server:close() |
| 304 | if not data then return nil, answer end | 327 | if not data then |
| 305 | -- download whole file | ||
| 306 | file, err = data:receive("*a") | ||
| 307 | data:close() | ||
| 308 | if err then | ||
| 309 | control:close() | 328 | control:close() |
| 310 | return nil, err | 329 | return answer |
| 311 | end | 330 | end |
| 331 | answer = %receive_indirect(data, callback) | ||
| 332 | if answer then | ||
| 333 | control:close() | ||
| 334 | return answer | ||
| 335 | end | ||
| 336 | data:close() | ||
| 312 | -- make sure file transfered ok | 337 | -- make sure file transfered ok |
| 313 | code, answer = %check_answer(control, {226, 250}) | 338 | code, answer = %check_answer(control, {226, 250}) |
| 314 | if not code then return nil, answer | 339 | if not code then return answer end |
| 315 | else return file, answer end | 340 | end |
| 341 | |||
| 342 | ----------------------------------------------------------------------------- | ||
| 343 | -- Sends data comming from a callback | ||
| 344 | -- Input | ||
| 345 | -- data: data connection | ||
| 346 | -- send_cb: callback to produce file contents | ||
| 347 | -- chunk, size: first callback results | ||
| 348 | -- Returns | ||
| 349 | -- nil if successfull, or an error message in case of error | ||
| 350 | ----------------------------------------------------------------------------- | ||
| 351 | local try_sendindirect = function(data, send_cb, chunk, size) | ||
| 352 | local sent, err | ||
| 353 | sent = 0 | ||
| 354 | while 1 do | ||
| 355 | if type(chunk) ~= "string" or type(size) ~= "number" then | ||
| 356 | data:close() | ||
| 357 | if not chunk and type(size) == "string" then return size | ||
| 358 | else return "invalid callback return" end | ||
| 359 | end | ||
| 360 | err = data:send(chunk) | ||
| 361 | if err then | ||
| 362 | data:close() | ||
| 363 | return err | ||
| 364 | end | ||
| 365 | sent = sent + strlen(chunk) | ||
| 366 | if sent >= size then break end | ||
| 367 | chunk, size = send_cb() | ||
| 368 | end | ||
| 316 | end | 369 | end |
| 317 | 370 | ||
| 318 | ----------------------------------------------------------------------------- | 371 | ----------------------------------------------------------------------------- |
| @@ -321,29 +374,34 @@ end | |||
| 321 | -- control: control connection with server | 374 | -- control: control connection with server |
| 322 | -- server: server socket bound to local address | 375 | -- server: server socket bound to local address |
| 323 | -- file: file name under current directory | 376 | -- file: file name under current directory |
| 324 | -- bytes: file contents in string | 377 | -- send_cb: callback to produce the file contents |
| 325 | -- Returns | 378 | -- Returns |
| 326 | -- code: return code, nil if error | 379 | -- code: return code, nil if error |
| 327 | -- answer: server answer or error message | 380 | -- answer: server answer or error message |
| 328 | ----------------------------------------------------------------------------- | 381 | ----------------------------------------------------------------------------- |
| 329 | local store_file = function (control, server, file, bytes) | 382 | local store = function(control, server, file, send_cb) |
| 330 | local data | 383 | local data |
| 331 | local code, answer = %try_command(control, "stor", file, {150, 125}) | 384 | local code, answer = %try_command(control, "stor", file, {150, 125}) |
| 332 | if not code then | 385 | if not code then |
| 333 | data:close() | 386 | control:close() |
| 334 | return nil, answer | 387 | return nil, answer |
| 335 | end | 388 | end |
| 389 | -- start data connection | ||
| 336 | data, answer = server:accept() | 390 | data, answer = server:accept() |
| 337 | server:close() | 391 | server:close() |
| 338 | if not data then return nil, answer end | 392 | if not data then |
| 339 | -- send whole file and close connection to mark file end | ||
| 340 | answer = data:send(bytes) | ||
| 341 | data:close() | ||
| 342 | if answer then | ||
| 343 | control:close() | 393 | control:close() |
| 344 | return nil, answer | 394 | return nil, answer |
| 395 | end | ||
| 396 | -- send whole file | ||
| 397 | err = %try_sendindirect(data, send_cb, send_cb()) | ||
| 398 | if err then | ||
| 399 | control:close() | ||
| 400 | return nil, err | ||
| 345 | end | 401 | end |
| 346 | -- check if file was received right | 402 | -- close connection to inform that file transmission is complete |
| 403 | data:close() | ||
| 404 | -- check if file was received correctly | ||
| 347 | return %check_answer(control, {226, 250}) | 405 | return %check_answer(control, {226, 250}) |
| 348 | end | 406 | end |
| 349 | 407 | ||
| @@ -365,55 +423,53 @@ end | |||
| 365 | -- Retrieve a file from a ftp server | 423 | -- Retrieve a file from a ftp server |
| 366 | -- Input | 424 | -- Input |
| 367 | -- url: file location | 425 | -- url: file location |
| 426 | -- receive_cb: callback to receive file contents | ||
| 368 | -- type: "binary" or "ascii" | 427 | -- type: "binary" or "ascii" |
| 369 | -- Returns | 428 | -- Returns |
| 370 | -- file: downloaded file or nil in case of error | ||
| 371 | -- err: error message if any | 429 | -- err: error message if any |
| 372 | ----------------------------------------------------------------------------- | 430 | ----------------------------------------------------------------------------- |
| 373 | function ftp_get(url, type) | 431 | function ftp_getindirect(url, receive_cb, type) |
| 374 | local control, server, data, err | 432 | local control, server, data, err |
| 375 | local answer, code, server, pfile, file | 433 | local answer, code, server, pfile, file |
| 376 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | 434 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) |
| 377 | -- start control connection | 435 | -- start control connection |
| 378 | control, err = connect(parsed.host, parsed.port) | 436 | control, err = connect(parsed.host, parsed.port) |
| 379 | if not control then return nil, err end | 437 | if not control then return err end |
| 380 | control:timeout(%TIMEOUT) | 438 | control:timeout(%TIMEOUT) |
| 381 | -- get and check greeting | 439 | -- get and check greeting |
| 382 | code, answer = %check_greeting(control) | 440 | code, answer = %check_greeting(control) |
| 383 | if not code then return nil, answer end | 441 | if not code then return answer end |
| 384 | -- try to log in | 442 | -- try to log in |
| 385 | code, answer = %login(control, parsed.user, parsed.pass) | 443 | code, answer = %login(control, parsed.user, parsed.pass) |
| 386 | if not code then return nil, answer end | 444 | if not code then return answer end |
| 387 | -- go to directory | 445 | -- go to directory |
| 388 | pfile = %split_path(parsed.path) | 446 | pfile = %split_path(parsed.path) |
| 389 | if not pfile then return nil, "invalid path" end | 447 | if not pfile then return "invalid path" end |
| 390 | code, answer = %cwd(control, pfile.path) | 448 | code, answer = %cwd(control, pfile.path) |
| 391 | if not code then return nil, answer end | 449 | if not code then return answer end |
| 392 | -- change to binary type? | 450 | -- change to binary type? |
| 393 | code, answer = %change_type(control, type) | 451 | code, answer = %change_type(control, type) |
| 394 | if not code then return nil, answer end | 452 | if not code then return answer end |
| 395 | -- setup passive connection | 453 | -- setup passive connection |
| 396 | server, answer = %port(control) | 454 | server, answer = %port(control) |
| 397 | if not server then return nil, answer end | 455 | if not server then return answer end |
| 398 | -- ask server to send file or directory listing | 456 | -- ask server to send file or directory listing |
| 399 | file, answer = %retrieve_file(control, server, pfile.name, pfile.isdir) | 457 | err = %retrieve(control, server, pfile.name, pfile.isdir, receive_cb) |
| 400 | if not file then return nil, answer end | 458 | if err then return err end |
| 401 | -- disconnect | 459 | -- disconnect |
| 402 | %logout(control) | 460 | %logout(control) |
| 403 | -- return whatever file we received plus a possible error message | ||
| 404 | return file, answer | ||
| 405 | end | 461 | end |
| 406 | 462 | ||
| 407 | ----------------------------------------------------------------------------- | 463 | ----------------------------------------------------------------------------- |
| 408 | -- Uploads a file to a FTP server | 464 | -- Uploads a file to a FTP server |
| 409 | -- Input | 465 | -- Input |
| 410 | -- url: file location | 466 | -- url: file location |
| 411 | -- bytes: file contents | 467 | -- send_cb: callback to produce the file contents |
| 412 | -- type: "binary" or "ascii" | 468 | -- type: "binary" or "ascii" |
| 413 | -- Returns | 469 | -- Returns |
| 414 | -- err: error message if any | 470 | -- err: error message if any |
| 415 | ----------------------------------------------------------------------------- | 471 | ----------------------------------------------------------------------------- |
| 416 | function ftp_put(url, bytes, type) | 472 | function ftp_putindirect(url, send_cb, type) |
| 417 | local control, data | 473 | local control, data |
| 418 | local answer, code, server, file, pfile | 474 | local answer, code, server, file, pfile |
| 419 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | 475 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) |
| @@ -439,10 +495,51 @@ function ftp_put(url, bytes, type) | |||
| 439 | server, answer = %port(control) | 495 | server, answer = %port(control) |
| 440 | if not server then return answer end | 496 | if not server then return answer end |
| 441 | -- ask server to send file | 497 | -- ask server to send file |
| 442 | code, answer = %store_file(control, server, pfile.name, bytes) | 498 | code, answer = %store(control, server, pfile.name, send_cb) |
| 443 | if not code then return answer end | 499 | if not code then return answer end |
| 444 | -- disconnect | 500 | -- disconnect |
| 445 | %logout(control) | 501 | %logout(control) |
| 446 | -- no errors | 502 | -- no errors |
| 447 | return nil | 503 | return nil |
| 448 | end | 504 | end |
| 505 | |||
| 506 | ----------------------------------------------------------------------------- | ||
| 507 | -- Uploads a file to a FTP server | ||
| 508 | -- Input | ||
| 509 | -- url: file location | ||
| 510 | -- bytes: file contents | ||
| 511 | -- type: "binary" or "ascii" | ||
| 512 | -- Returns | ||
| 513 | -- err: error message if any | ||
| 514 | ----------------------------------------------------------------------------- | ||
| 515 | function ftp_put(url, bytes, type) | ||
| 516 | local send_cb = function() | ||
| 517 | return %bytes, strlen(%bytes) | ||
| 518 | end | ||
| 519 | return ftp_putindirect(url, send_cb, type) | ||
| 520 | end | ||
| 521 | |||
| 522 | ----------------------------------------------------------------------------- | ||
| 523 | -- We need fast concatenation routines for direct requests | ||
| 524 | ----------------------------------------------------------------------------- | ||
| 525 | dofile("buffer.lua") | ||
| 526 | |||
| 527 | ----------------------------------------------------------------------------- | ||
| 528 | -- Retrieve a file from a ftp server | ||
| 529 | -- Input | ||
| 530 | -- url: file location | ||
| 531 | -- type: "binary" or "ascii" | ||
| 532 | -- Returns | ||
| 533 | -- data: file contents as a string | ||
| 534 | -- err: error message in case of error, nil otherwise | ||
| 535 | ----------------------------------------------------------------------------- | ||
| 536 | function ftp_get(url, type) | ||
| 537 | local bytes = { buf = buf_create() } | ||
| 538 | local receive_cb = function(chunk, err) | ||
| 539 | if not chunk then %bytes.buf = nil end | ||
| 540 | buf_addstring(%bytes.buf, chunk) | ||
| 541 | return 1 | ||
| 542 | end | ||
| 543 | err = ftp_getindirect(url, receive_cb, type) | ||
| 544 | return buf_getresult(bytes.buf), err | ||
| 545 | end | ||
