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 |
