diff options
| author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2000-12-29 22:15:09 +0000 |
|---|---|---|
| committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2000-12-29 22:15:09 +0000 |
| commit | 17c4d1c30544f0ed638879835f179ada96249868 (patch) | |
| tree | 46ca8042ba8fda147f56af61e26ec2ceec41f614 /src/ftp.lua | |
| parent | 6f9d15b66027cef58441549f8ac0603ca42da0ac (diff) | |
| download | luasocket-17c4d1c30544f0ed638879835f179ada96249868.tar.gz luasocket-17c4d1c30544f0ed638879835f179ada96249868.tar.bz2 luasocket-17c4d1c30544f0ed638879835f179ada96249868.zip | |
Initial revision
Diffstat (limited to 'src/ftp.lua')
| -rw-r--r-- | src/ftp.lua | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/src/ftp.lua b/src/ftp.lua new file mode 100644 index 0000000..b817356 --- /dev/null +++ b/src/ftp.lua | |||
| @@ -0,0 +1,437 @@ | |||
| 1 | ----------------------------------------------------------------------------- | ||
| 2 | -- Simple FTP support for the Lua language using the LuaSocket toolkit. | ||
| 3 | -- Author: Diego Nehab | ||
| 4 | -- Date: 26/12/2000 | ||
| 5 | -- Conforming to: RFC 959 | ||
| 6 | ----------------------------------------------------------------------------- | ||
| 7 | |||
| 8 | ----------------------------------------------------------------------------- | ||
| 9 | -- Program constants | ||
| 10 | ----------------------------------------------------------------------------- | ||
| 11 | -- timeout in seconds before the program gives up on a connection | ||
| 12 | local TIMEOUT = 60 | ||
| 13 | -- default port for ftp service | ||
| 14 | local PORT = 21 | ||
| 15 | -- this is the default anonymous password. used when no password is | ||
| 16 | -- provided in url. should be changed for your e-mail. | ||
| 17 | local EMAIL = "anonymous@anonymous.org" | ||
| 18 | |||
| 19 | ----------------------------------------------------------------------------- | ||
| 20 | -- Parses a url and returns its scheme, user, password, host, port | ||
| 21 | -- and path components, according to RFC 1738, Uniform Resource Locators (URL), | ||
| 22 | -- of December 1994 | ||
| 23 | -- Input | ||
| 24 | -- url: unique resource locator desired | ||
| 25 | -- default: table containing default values to be returned | ||
| 26 | -- Returns | ||
| 27 | -- table with the following fields: | ||
| 28 | -- host: host to connect | ||
| 29 | -- path: url path | ||
| 30 | -- port: host port to connect | ||
| 31 | -- user: user name | ||
| 32 | -- pass: password | ||
| 33 | -- scheme: protocol | ||
| 34 | ----------------------------------------------------------------------------- | ||
| 35 | local split_url = function(url, default) | ||
| 36 | -- initialize default parameters | ||
| 37 | local parsed = default or {} | ||
| 38 | -- get scheme | ||
| 39 | url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) | ||
| 40 | -- get user name and password. both can be empty! | ||
| 41 | -- moreover, password can be ommited | ||
| 42 | url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) | ||
| 43 | %parsed.user = u | ||
| 44 | -- there can be an empty password, but the ':' has to be there | ||
| 45 | -- or else there is no password | ||
| 46 | %parsed.pass = nil -- kill default password | ||
| 47 | if c == ":" then %parsed.pass = p end | ||
| 48 | end) | ||
| 49 | -- get host | ||
| 50 | url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) | ||
| 51 | -- get port if any | ||
| 52 | url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) | ||
| 53 | -- whatever is left is the path | ||
| 54 | if url ~= "" then parsed.path = url end | ||
| 55 | return parsed | ||
| 56 | end | ||
| 57 | |||
| 58 | ----------------------------------------------------------------------------- | ||
| 59 | -- Gets ip and port for data connection from PASV answer | ||
| 60 | -- Input | ||
| 61 | -- pasv: PASV command answer | ||
| 62 | -- Returns | ||
| 63 | -- ip: string containing ip for data connection | ||
| 64 | -- port: port for data connection | ||
| 65 | ----------------------------------------------------------------------------- | ||
| 66 | local get_pasv = function(pasv) | ||
| 67 | local a,b,c,d,p1,p2 | ||
| 68 | local ip, port | ||
| 69 | _,_, a, b, c, d, p1, p2 = | ||
| 70 | 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 | ||
| 72 | return nil, nil | ||
| 73 | end | ||
| 74 | ip = format("%d.%d.%d.%d", a, b, c, d) | ||
| 75 | port = tonumber(p1)*256 + tonumber(p2) | ||
| 76 | return ip, port | ||
| 77 | end | ||
| 78 | |||
| 79 | ----------------------------------------------------------------------------- | ||
| 80 | -- Sends a FTP command through socket | ||
| 81 | -- Input | ||
| 82 | -- control: control connection socket | ||
| 83 | -- cmd: command | ||
| 84 | -- arg: command argument if any | ||
| 85 | ----------------------------------------------------------------------------- | ||
| 86 | local send_command = function(control, cmd, arg) | ||
| 87 | local line, err | ||
| 88 | if arg then line = cmd .. " " .. arg .. "\r\n" | ||
| 89 | else line = cmd .. "\r\n" end | ||
| 90 | err = control:send(line) | ||
| 91 | return err | ||
| 92 | end | ||
| 93 | |||
| 94 | ----------------------------------------------------------------------------- | ||
| 95 | -- Gets FTP command answer, unfolding if neccessary | ||
| 96 | -- Input | ||
| 97 | -- control: control connection socket | ||
| 98 | -- Returns | ||
| 99 | -- answer: whole server reply, nil if error | ||
| 100 | -- code: answer status code or error message | ||
| 101 | ----------------------------------------------------------------------------- | ||
| 102 | local get_answer = function(control) | ||
| 103 | local code, lastcode, sep | ||
| 104 | local line, err = control:receive() | ||
| 105 | local answer = line | ||
| 106 | if err then return nil, err end | ||
| 107 | _,_, code, sep = strfind(line, "^(%d%d%d)(.)") | ||
| 108 | if not code or not sep then return nil, answer end | ||
| 109 | if sep == "-" then -- answer is multiline | ||
| 110 | repeat | ||
| 111 | line, err = control:receive() | ||
| 112 | if err then return nil, err end | ||
| 113 | _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") | ||
| 114 | answer = answer .. "\n" .. line | ||
| 115 | until code == lastcode and sep == " " -- answer ends with same code | ||
| 116 | end | ||
| 117 | return answer, tonumber(code) | ||
| 118 | end | ||
| 119 | |||
| 120 | ----------------------------------------------------------------------------- | ||
| 121 | -- Checks if a message return is correct. Closes control connection if not. | ||
| 122 | -- Input | ||
| 123 | -- control: control connection socket | ||
| 124 | -- success: table with successfull reply status code | ||
| 125 | -- Returns | ||
| 126 | -- code: reply code or nil in case of error | ||
| 127 | -- answer: server complete answer or system error message | ||
| 128 | ----------------------------------------------------------------------------- | ||
| 129 | local check_answer = function(control, success) | ||
| 130 | local answer, code = %get_answer(control) | ||
| 131 | if not answer then | ||
| 132 | control:close() | ||
| 133 | return nil, code | ||
| 134 | end | ||
| 135 | if type(success) ~= "table" then success = {success} end | ||
| 136 | for i = 1, getn(success) do | ||
| 137 | if code == success[i] then | ||
| 138 | return code, answer | ||
| 139 | end | ||
| 140 | end | ||
| 141 | control:close() | ||
| 142 | return nil, answer | ||
| 143 | end | ||
| 144 | |||
| 145 | ----------------------------------------------------------------------------- | ||
| 146 | -- Trys a command on control socked, in case of error, the control connection | ||
| 147 | -- is closed. | ||
| 148 | -- Input | ||
| 149 | -- control: control connection socket | ||
| 150 | -- cmd: command | ||
| 151 | -- arg: command argument or nil if no argument | ||
| 152 | -- success: table with successfull reply status code | ||
| 153 | -- Returns | ||
| 154 | -- code: reply code or nil in case of error | ||
| 155 | -- answer: server complete answer or system error message | ||
| 156 | ----------------------------------------------------------------------------- | ||
| 157 | local try_command = function(control, cmd, arg, success) | ||
| 158 | local err = %send_command(control, cmd, arg) | ||
| 159 | if err then | ||
| 160 | control:close() | ||
| 161 | return nil, err | ||
| 162 | end | ||
| 163 | local code, answer = %check_answer(control, success) | ||
| 164 | if not code then return nil, answer end | ||
| 165 | return code, answer | ||
| 166 | end | ||
| 167 | |||
| 168 | ----------------------------------------------------------------------------- | ||
| 169 | -- Creates a table with all directories in path | ||
| 170 | -- Input | ||
| 171 | -- file: abolute path to file | ||
| 172 | -- Returns | ||
| 173 | -- file: filename | ||
| 174 | -- path: table with directories to reach filename | ||
| 175 | -- isdir: is it a directory or a file | ||
| 176 | ----------------------------------------------------------------------------- | ||
| 177 | local split_path = function(file) | ||
| 178 | local path = {} | ||
| 179 | local isdir | ||
| 180 | file = file or "/" | ||
| 181 | -- directory ends with a '/' | ||
| 182 | _,_, isdir = strfind(file, "([/])$") | ||
| 183 | gsub(file, "([^/]+)", function (dir) tinsert(%path, dir) end) | ||
| 184 | if not isdir then file = tremove(path) | ||
| 185 | else file = nil end | ||
| 186 | return file, path, isdir | ||
| 187 | end | ||
| 188 | |||
| 189 | ----------------------------------------------------------------------------- | ||
| 190 | -- Check server greeting | ||
| 191 | -- Input | ||
| 192 | -- control: control connection with server | ||
| 193 | -- Returns | ||
| 194 | -- code: nil if error | ||
| 195 | -- answer: server answer or error message | ||
| 196 | ----------------------------------------------------------------------------- | ||
| 197 | local check_greeting = function(control) | ||
| 198 | local code, answer = %check_answer(control, {120, 220}) | ||
| 199 | if not code then return nil, answer end | ||
| 200 | if code == 120 then -- please try again, somewhat busy now... | ||
| 201 | code, answer = %check_answer(control, {220}) | ||
| 202 | end | ||
| 203 | return code, answer | ||
| 204 | end | ||
| 205 | |||
| 206 | ----------------------------------------------------------------------------- | ||
| 207 | -- Log in on server | ||
| 208 | -- Input | ||
| 209 | -- control: control connection with server | ||
| 210 | -- user: user name | ||
| 211 | -- pass: user password if any | ||
| 212 | -- Returns | ||
| 213 | -- code: nil if error | ||
| 214 | -- answer: server answer or error message | ||
| 215 | ----------------------------------------------------------------------------- | ||
| 216 | local login = function(control, user, pass) | ||
| 217 | local code, answer = %try_command(control, "user", parsed.user, {230, 331}) | ||
| 218 | if not code then return nil, answer end | ||
| 219 | if code == 331 and parsed.pass then -- need pass and we have pass | ||
| 220 | code, answer = %try_command(control, "pass", parsed.pass, {230, 202}) | ||
| 221 | end | ||
| 222 | return code, answer | ||
| 223 | end | ||
| 224 | |||
| 225 | ----------------------------------------------------------------------------- | ||
| 226 | -- Change to target directory | ||
| 227 | -- Input | ||
| 228 | -- control: socket for control connection with server | ||
| 229 | -- path: array with directories in order | ||
| 230 | -- Returns | ||
| 231 | -- code: nil if error | ||
| 232 | -- answer: server answer or error message | ||
| 233 | ----------------------------------------------------------------------------- | ||
| 234 | local cwd = function(control, path) | ||
| 235 | local code, answer = 250, "Home directory used" | ||
| 236 | for i = 1, getn(path) do | ||
| 237 | code, answer = %try_command(control, "cwd", path[i], {250}) | ||
| 238 | if not code then return nil, answer end | ||
| 239 | end | ||
| 240 | return code, answer | ||
| 241 | end | ||
| 242 | |||
| 243 | ----------------------------------------------------------------------------- | ||
| 244 | -- Start data connection with server | ||
| 245 | -- Input | ||
| 246 | -- control: control connection with server | ||
| 247 | -- Returns | ||
| 248 | -- data: socket for data connection with server, nil if error | ||
| 249 | -- answer: server answer or error message | ||
| 250 | ----------------------------------------------------------------------------- | ||
| 251 | local start_dataconnection = function(control) | ||
| 252 | -- ask for passive data connection | ||
| 253 | local code, answer = %try_command(control, "pasv", nil, {227}) | ||
| 254 | if not code then return nil, answer end | ||
| 255 | -- get data connection parameters from server reply | ||
| 256 | local host, port = %get_pasv(answer) | ||
| 257 | if not host or not port then return nil, answer end | ||
| 258 | -- start data connection with given parameters | ||
| 259 | local data, err = connect(host, port) | ||
| 260 | if not data then return nil, err end | ||
| 261 | data:timeout(%TIMEOUT) | ||
| 262 | return data | ||
| 263 | end | ||
| 264 | |||
| 265 | ----------------------------------------------------------------------------- | ||
| 266 | -- Closes control connection with server | ||
| 267 | -- Input | ||
| 268 | -- control: control connection with server | ||
| 269 | -- Returns | ||
| 270 | -- code: nil if error | ||
| 271 | -- answer: server answer or error message | ||
| 272 | ----------------------------------------------------------------------------- | ||
| 273 | local logout = function(control) | ||
| 274 | local code, answer = %try_command(control, "quit", nil, {221}) | ||
| 275 | if not code then return nil, answer end | ||
| 276 | control:close() | ||
| 277 | return code, answer | ||
| 278 | end | ||
| 279 | |||
| 280 | ----------------------------------------------------------------------------- | ||
| 281 | -- Retrieves file or directory listing | ||
| 282 | -- Input | ||
| 283 | -- control: control connection with server | ||
| 284 | -- data: data connection with server | ||
| 285 | -- file: file name under current directory | ||
| 286 | -- isdir: is file a directory name? | ||
| 287 | -- Returns | ||
| 288 | -- file: string with file contents, nil if error | ||
| 289 | -- answer: server answer or error message | ||
| 290 | ----------------------------------------------------------------------------- | ||
| 291 | local retrieve_file = function(control, data, file, isdir) | ||
| 292 | -- ask server for file or directory listing accordingly | ||
| 293 | if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) | ||
| 294 | else code, answer = %try_command(control, "retr", file, {150, 125}) end | ||
| 295 | if not code then | ||
| 296 | control:close() | ||
| 297 | data:close() | ||
| 298 | return nil, answer | ||
| 299 | end | ||
| 300 | -- download whole file | ||
| 301 | file, err = data:receive("*a") | ||
| 302 | data:close() | ||
| 303 | if err then | ||
| 304 | control:close() | ||
| 305 | return nil, err | ||
| 306 | end | ||
| 307 | -- make sure file transfered ok | ||
| 308 | code, answer = %check_answer(control, {226, 250}) | ||
| 309 | if not code then return nil, answer | ||
| 310 | else return file, answer end | ||
| 311 | end | ||
| 312 | |||
| 313 | ----------------------------------------------------------------------------- | ||
| 314 | -- Stores a file | ||
| 315 | -- Input | ||
| 316 | -- control: control connection with server | ||
| 317 | -- data: data connection with server | ||
| 318 | -- file: file name under current directory | ||
| 319 | -- bytes: file contents in string | ||
| 320 | -- Returns | ||
| 321 | -- file: string with file contents, nil if error | ||
| 322 | -- answer: server answer or error message | ||
| 323 | ----------------------------------------------------------------------------- | ||
| 324 | local store_file = function (control, data, file, bytes) | ||
| 325 | local code, answer = %try_command(control, "stor", file, {150, 125}) | ||
| 326 | if not code then | ||
| 327 | data:close() | ||
| 328 | return nil, answer | ||
| 329 | end | ||
| 330 | -- send whole file and close connection to mark file end | ||
| 331 | answer = data:send(bytes) | ||
| 332 | data:close() | ||
| 333 | if answer then | ||
| 334 | control:close() | ||
| 335 | return nil, answer | ||
| 336 | end | ||
| 337 | -- check if file was received right | ||
| 338 | return %check_answer(control, {226, 250}) | ||
| 339 | end | ||
| 340 | |||
| 341 | ----------------------------------------------------------------------------- | ||
| 342 | -- Change transfer type | ||
| 343 | -- Input | ||
| 344 | -- control: control connection with server | ||
| 345 | -- type: new transfer type | ||
| 346 | -- Returns | ||
| 347 | -- code: nil if error | ||
| 348 | -- answer: server answer or error message | ||
| 349 | ----------------------------------------------------------------------------- | ||
| 350 | local change_type = function(control, type) | ||
| 351 | if type == "b" then type = "i" else type = "a" end | ||
| 352 | return %try_command(control, "type", type, {200}) | ||
| 353 | end | ||
| 354 | |||
| 355 | ----------------------------------------------------------------------------- | ||
| 356 | -- Retrieve a file from a ftp server | ||
| 357 | -- Input | ||
| 358 | -- url: file location | ||
| 359 | -- type: "binary" or "ascii" | ||
| 360 | -- Returns | ||
| 361 | -- file: downloaded file or nil in case of error | ||
| 362 | -- err: error message if any | ||
| 363 | ----------------------------------------------------------------------------- | ||
| 364 | function ftp_get(url, type) | ||
| 365 | local control, data, err | ||
| 366 | local answer, code, server, file, path | ||
| 367 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | ||
| 368 | -- start control connection | ||
| 369 | control, err = connect(parsed.host, parsed.port) | ||
| 370 | if not control then return nil, err end | ||
| 371 | control:timeout(%TIMEOUT) | ||
| 372 | -- get and check greeting | ||
| 373 | code, answer = %check_greeting(control) | ||
| 374 | if not code then return nil, answer end | ||
| 375 | -- try to log in | ||
| 376 | code, answer = %login(control, parsed.user, parsed.pass) | ||
| 377 | if not code then return nil, answer end | ||
| 378 | -- go to directory | ||
| 379 | file, path, isdir = %split_path(parsed.path) | ||
| 380 | code, answer = %cwd(control, path) | ||
| 381 | if not code then return nil, answer end | ||
| 382 | -- change to binary type? | ||
| 383 | code, answer = %change_type(control, type) | ||
| 384 | if not code then return nil, answer end | ||
| 385 | -- start data connection | ||
| 386 | data, answer = %start_dataconnection(control) | ||
| 387 | if not data then return nil, answer end | ||
| 388 | -- ask server to send file or directory listing | ||
| 389 | file, answer = %retrieve_file(control, data, file, isdir) | ||
| 390 | if not file then return nil, answer end | ||
| 391 | -- disconnect | ||
| 392 | %logout(control) | ||
| 393 | -- return whatever file we received plus a possible error | ||
| 394 | return file, answer | ||
| 395 | end | ||
| 396 | |||
| 397 | ----------------------------------------------------------------------------- | ||
| 398 | -- Uploads a file to a FTP server | ||
| 399 | -- Input | ||
| 400 | -- url: file location | ||
| 401 | -- bytes: file contents | ||
| 402 | -- type: "binary" or "ascii" | ||
| 403 | -- Returns | ||
| 404 | -- err: error message if any | ||
| 405 | ----------------------------------------------------------------------------- | ||
| 406 | function ftp_put(url, bytes, type) | ||
| 407 | local control, data | ||
| 408 | local answer, code, server, file, path | ||
| 409 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | ||
| 410 | -- start control connection | ||
| 411 | control, answer = connect(parsed.host, parsed.port) | ||
| 412 | if not control then return answer end | ||
| 413 | control:timeout(%TIMEOUT) | ||
| 414 | -- get and check greeting | ||
| 415 | code, answer = %check_greeting(control) | ||
| 416 | if not code then return answer end | ||
| 417 | -- try to log in | ||
| 418 | code, answer = %login(control, parsed.user, parsed.pass) | ||
| 419 | if not code then return answer end | ||
| 420 | -- go to directory | ||
| 421 | file, path, isdir = %split_path(parsed.path) | ||
| 422 | code, answer = %cwd(control, path) | ||
| 423 | if not code then return answer end | ||
| 424 | -- change to binary type? | ||
| 425 | code, answer = %change_type(control, type) | ||
| 426 | if not code then return answer end | ||
| 427 | -- start data connection | ||
| 428 | data, answer = %start_dataconnection(control) | ||
| 429 | if not data then return answer end | ||
| 430 | -- ask server to send file or directory listing | ||
| 431 | code, answer = %store_file(control, data, file, bytes) | ||
| 432 | if not code then return answer end | ||
| 433 | -- disconnect | ||
| 434 | %logout(control) | ||
| 435 | -- return whatever file we received plus a possible error | ||
| 436 | return nil | ||
| 437 | end | ||
