diff options
Diffstat (limited to 'src/ftp.lua')
| -rw-r--r-- | src/ftp.lua | 360 |
1 files changed, 105 insertions, 255 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index e596416..18dab6d 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
| @@ -5,62 +5,29 @@ | |||
| 5 | -- Conforming to: RFC 959, LTN7 | 5 | -- Conforming to: RFC 959, LTN7 |
| 6 | -- RCS ID: $Id$ | 6 | -- RCS ID: $Id$ |
| 7 | ----------------------------------------------------------------------------- | 7 | ----------------------------------------------------------------------------- |
| 8 | 8 | -- make sure LuaSocket is loaded | |
| 9 | local Public, Private = {}, {} | 9 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end |
| 10 | local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace | 10 | -- get LuaSocket namespace |
| 11 | socket.ftp = Public -- create ftp sub namespace | 11 | local socket = _G[LUASOCKET_LIBNAME] |
| 12 | if not socket then error('module requires LuaSocket') end | ||
| 13 | -- create namespace inside LuaSocket namespace | ||
| 14 | socket.ftp = socket.ftp or {} | ||
| 15 | -- make all module globals fall into namespace | ||
| 16 | setmetatable(socket.ftp, { __index = _G }) | ||
| 17 | setfenv(1, socket.ftp) | ||
| 12 | 18 | ||
| 13 | ----------------------------------------------------------------------------- | 19 | ----------------------------------------------------------------------------- |
| 14 | -- Program constants | 20 | -- Program constants |
| 15 | ----------------------------------------------------------------------------- | 21 | ----------------------------------------------------------------------------- |
| 16 | -- timeout in seconds before the program gives up on a connection | 22 | -- timeout in seconds before the program gives up on a connection |
| 17 | Public.TIMEOUT = 60 | 23 | TIMEOUT = 60 |
| 18 | -- default port for ftp service | 24 | -- default port for ftp service |
| 19 | Public.PORT = 21 | 25 | PORT = 21 |
| 20 | -- this is the default anonymous password. used when no password is | 26 | -- this is the default anonymous password. used when no password is |
| 21 | -- provided in url. should be changed to your e-mail. | 27 | -- provided in url. should be changed to your e-mail. |
| 22 | Public.EMAIL = "anonymous@anonymous.org" | 28 | EMAIL = "anonymous@anonymous.org" |
| 23 | -- block size used in transfers | 29 | -- block size used in transfers |
| 24 | Public.BLOCKSIZE = 8192 | 30 | BLOCKSIZE = 2048 |
| 25 | |||
| 26 | ----------------------------------------------------------------------------- | ||
| 27 | -- Tries to get a pattern from the server and closes socket on error | ||
| 28 | -- sock: socket connected to the server | ||
| 29 | -- pattern: pattern to receive | ||
| 30 | -- Returns | ||
| 31 | -- received pattern on success | ||
| 32 | -- nil followed by error message on error | ||
| 33 | ----------------------------------------------------------------------------- | ||
| 34 | function Private.try_receive(sock, pattern) | ||
| 35 | local data, err = sock:receive(pattern) | ||
| 36 | if not data then sock:close() end | ||
| 37 | return data, err | ||
| 38 | end | ||
| 39 | |||
| 40 | ----------------------------------------------------------------------------- | ||
| 41 | -- Tries to send data to the server and closes socket on error | ||
| 42 | -- sock: socket connected to the server | ||
| 43 | -- data: data to send | ||
| 44 | -- Returns | ||
| 45 | -- err: error message if any, nil if successfull | ||
| 46 | ----------------------------------------------------------------------------- | ||
| 47 | function Private.try_send(sock, data) | ||
| 48 | local sent, err = sock:send(data) | ||
| 49 | if not sent then sock:close() end | ||
| 50 | return err | ||
| 51 | end | ||
| 52 | |||
| 53 | ----------------------------------------------------------------------------- | ||
| 54 | -- Tries to send DOS mode lines. Closes socket on error. | ||
| 55 | -- Input | ||
| 56 | -- sock: server socket | ||
| 57 | -- line: string to be sent | ||
| 58 | -- Returns | ||
| 59 | -- err: message in case of error, nil if successfull | ||
| 60 | ----------------------------------------------------------------------------- | ||
| 61 | function Private.try_sendline(sock, line) | ||
| 62 | return Private.try_send(sock, line .. "\r\n") | ||
| 63 | end | ||
| 64 | 31 | ||
| 65 | ----------------------------------------------------------------------------- | 32 | ----------------------------------------------------------------------------- |
| 66 | -- Gets ip and port for data connection from PASV answer | 33 | -- Gets ip and port for data connection from PASV answer |
| @@ -70,7 +37,7 @@ end | |||
| 70 | -- ip: string containing ip for data connection | 37 | -- ip: string containing ip for data connection |
| 71 | -- port: port for data connection | 38 | -- port: port for data connection |
| 72 | ----------------------------------------------------------------------------- | 39 | ----------------------------------------------------------------------------- |
| 73 | function Private.get_pasv(pasv) | 40 | local function get_pasv(pasv) |
| 74 | local a, b, c, d, p1, p2, _ | 41 | local a, b, c, d, p1, p2, _ |
| 75 | local ip, port | 42 | local ip, port |
| 76 | _,_, a, b, c, d, p1, p2 = | 43 | _,_, a, b, c, d, p1, p2 = |
| @@ -82,88 +49,6 @@ function Private.get_pasv(pasv) | |||
| 82 | end | 49 | end |
| 83 | 50 | ||
| 84 | ----------------------------------------------------------------------------- | 51 | ----------------------------------------------------------------------------- |
| 85 | -- Sends a FTP command through socket | ||
| 86 | -- Input | ||
| 87 | -- control: control connection socket | ||
| 88 | -- cmd: command | ||
| 89 | -- arg: command argument if any | ||
| 90 | -- Returns | ||
| 91 | -- error message in case of error, nil otherwise | ||
| 92 | ----------------------------------------------------------------------------- | ||
| 93 | function Private.send_command(control, cmd, arg) | ||
| 94 | local line | ||
| 95 | if arg then line = cmd .. " " .. arg | ||
| 96 | else line = cmd end | ||
| 97 | return Private.try_sendline(control, line) | ||
| 98 | end | ||
| 99 | |||
| 100 | ----------------------------------------------------------------------------- | ||
| 101 | -- Gets FTP command answer, unfolding if neccessary | ||
| 102 | -- Input | ||
| 103 | -- control: control connection socket | ||
| 104 | -- Returns | ||
| 105 | -- answer: whole server reply, nil if error | ||
| 106 | -- code: answer status code or error message | ||
| 107 | ----------------------------------------------------------------------------- | ||
| 108 | function Private.get_answer(control) | ||
| 109 | local code, lastcode, sep, _ | ||
| 110 | local line, err = Private.try_receive(control) | ||
| 111 | local answer = line | ||
| 112 | if err then return nil, err end | ||
| 113 | _,_, code, sep = string.find(line, "^(%d%d%d)(.)") | ||
| 114 | if not code or not sep then return nil, answer end | ||
| 115 | if sep == "-" then -- answer is multiline | ||
| 116 | repeat | ||
| 117 | line, err = Private.try_receive(control) | ||
| 118 | if err then return nil, err end | ||
| 119 | _,_, lastcode, sep = string.find(line, "^(%d%d%d)(.)") | ||
| 120 | answer = answer .. "\n" .. line | ||
| 121 | until code == lastcode and sep == " " -- answer ends with same code | ||
| 122 | end | ||
| 123 | return answer, tonumber(code) | ||
| 124 | end | ||
| 125 | |||
| 126 | ----------------------------------------------------------------------------- | ||
| 127 | -- Checks if a message return is correct. Closes control connection if not. | ||
| 128 | -- Input | ||
| 129 | -- control: control connection socket | ||
| 130 | -- success: table with successfull reply status code | ||
| 131 | -- Returns | ||
| 132 | -- code: reply code or nil in case of error | ||
| 133 | -- answer: server complete answer or system error message | ||
| 134 | ----------------------------------------------------------------------------- | ||
| 135 | function Private.check_answer(control, success) | ||
| 136 | local answer, code = Private.get_answer(control) | ||
| 137 | if not answer then return nil, code end | ||
| 138 | if type(success) ~= "table" then success = {success} end | ||
| 139 | for _, s in ipairs(success) do | ||
| 140 | if code == s then | ||
| 141 | return code, answer | ||
| 142 | end | ||
| 143 | end | ||
| 144 | control:close() | ||
| 145 | return nil, answer | ||
| 146 | end | ||
| 147 | |||
| 148 | ----------------------------------------------------------------------------- | ||
| 149 | -- Trys a command on control socked, in case of error, the control connection | ||
| 150 | -- is closed. | ||
| 151 | -- Input | ||
| 152 | -- control: control connection socket | ||
| 153 | -- cmd: command | ||
| 154 | -- arg: command argument or nil if no argument | ||
| 155 | -- success: table with successfull reply status code | ||
| 156 | -- Returns | ||
| 157 | -- code: reply code or nil in case of error | ||
| 158 | -- answer: server complete answer or system error message | ||
| 159 | ----------------------------------------------------------------------------- | ||
| 160 | function Private.command(control, cmd, arg, success) | ||
| 161 | local err = Private.send_command(control, cmd, arg) | ||
| 162 | if err then return nil, err end | ||
| 163 | return Private.check_answer(control, success) | ||
| 164 | end | ||
| 165 | |||
| 166 | ----------------------------------------------------------------------------- | ||
| 167 | -- Check server greeting | 52 | -- Check server greeting |
| 168 | -- Input | 53 | -- Input |
| 169 | -- control: control connection with server | 54 | -- control: control connection with server |
| @@ -171,10 +56,10 @@ end | |||
| 171 | -- code: nil if error | 56 | -- code: nil if error |
| 172 | -- answer: server answer or error message | 57 | -- answer: server answer or error message |
| 173 | ----------------------------------------------------------------------------- | 58 | ----------------------------------------------------------------------------- |
| 174 | function Private.greet(control) | 59 | local function greet(control) |
| 175 | local code, answer = Private.check_answer(control, {120, 220}) | 60 | local code, answer = check_answer(control, {120, 220}) |
| 176 | if code == 120 then -- please try again, somewhat busy now... | 61 | if code == 120 then -- please try again, somewhat busy now... |
| 177 | return Private.check_answer(control, {220}) | 62 | return check_answer(control, {220}) |
| 178 | end | 63 | end |
| 179 | return code, answer | 64 | return code, answer |
| 180 | end | 65 | end |
| @@ -189,10 +74,10 @@ end | |||
| 189 | -- code: nil if error | 74 | -- code: nil if error |
| 190 | -- answer: server answer or error message | 75 | -- answer: server answer or error message |
| 191 | ----------------------------------------------------------------------------- | 76 | ----------------------------------------------------------------------------- |
| 192 | function Private.login(control, user, password) | 77 | local function login(control, user, password) |
| 193 | local code, answer = Private.command(control, "user", user, {230, 331}) | 78 | local code, answer = command(control, "user", user, {230, 331}) |
| 194 | if code == 331 and password then -- need pass and we have pass | 79 | if code == 331 and password then -- need pass and we have pass |
| 195 | return Private.command(control, "pass", password, {230, 202}) | 80 | return command(control, "pass", password, {230, 202}) |
| 196 | end | 81 | end |
| 197 | return code, answer | 82 | return code, answer |
| 198 | end | 83 | end |
| @@ -206,9 +91,7 @@ end | |||
| 206 | -- code: nil if error | 91 | -- code: nil if error |
| 207 | -- answer: server answer or error message | 92 | -- answer: server answer or error message |
| 208 | ----------------------------------------------------------------------------- | 93 | ----------------------------------------------------------------------------- |
| 209 | function Private.cwd(control, path) | 94 | local function cwd(control, path) |
| 210 | if path then return Private.command(control, "cwd", path, {250}) | ||
| 211 | else return 250, nil end | ||
| 212 | end | 95 | end |
| 213 | 96 | ||
| 214 | ----------------------------------------------------------------------------- | 97 | ----------------------------------------------------------------------------- |
| @@ -219,18 +102,18 @@ end | |||
| 219 | -- server: server socket bound to local address, nil if error | 102 | -- server: server socket bound to local address, nil if error |
| 220 | -- answer: error message if any | 103 | -- answer: error message if any |
| 221 | ----------------------------------------------------------------------------- | 104 | ----------------------------------------------------------------------------- |
| 222 | function Private.port(control) | 105 | local function port(control) |
| 223 | local code, answer | 106 | local code, answer |
| 224 | local server, ctl_ip | 107 | local server, ctl_ip |
| 225 | ctl_ip, answer = control:getsockname() | 108 | ctl_ip, answer = control:getsockname() |
| 226 | server, answer = socket.bind(ctl_ip, 0) | 109 | server, answer = socket.bind(ctl_ip, 0) |
| 227 | server:settimeout(Public.TIMEOUT) | 110 | server:settimeout(TIMEOUT) |
| 228 | local ip, p, ph, pl | 111 | local ip, p, ph, pl |
| 229 | ip, p = server:getsockname() | 112 | ip, p = server:getsockname() |
| 230 | pl = math.mod(p, 256) | 113 | pl = math.mod(p, 256) |
| 231 | ph = (p - pl)/256 | 114 | ph = (p - pl)/256 |
| 232 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | 115 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") |
| 233 | code, answer = Private.command(control, "port", arg, {200}) | 116 | code, answer = command(control, "port", arg, {200}) |
| 234 | if not code then | 117 | if not code then |
| 235 | server:close() | 118 | server:close() |
| 236 | return nil, answer | 119 | return nil, answer |
| @@ -245,8 +128,8 @@ end | |||
| 245 | -- code: nil if error | 128 | -- code: nil if error |
| 246 | -- answer: server answer or error message | 129 | -- answer: server answer or error message |
| 247 | ----------------------------------------------------------------------------- | 130 | ----------------------------------------------------------------------------- |
| 248 | function Private.logout(control) | 131 | local function logout(control) |
| 249 | local code, answer = Private.command(control, "quit", nil, {221}) | 132 | local code, answer = command(control, "quit", nil, {221}) |
| 250 | if code then control:close() end | 133 | if code then control:close() end |
| 251 | return code, answer | 134 | return code, answer |
| 252 | end | 135 | end |
| @@ -259,10 +142,10 @@ end | |||
| 259 | -- Returns | 142 | -- Returns |
| 260 | -- nil if successfull, or an error message in case of error | 143 | -- nil if successfull, or an error message in case of error |
| 261 | ----------------------------------------------------------------------------- | 144 | ----------------------------------------------------------------------------- |
| 262 | function Private.receive_indirect(data, callback) | 145 | local function receive_indirect(data, callback) |
| 263 | local chunk, err, res | 146 | local chunk, err, res |
| 264 | while not err do | 147 | while not err do |
| 265 | chunk, err = Private.try_receive(data, Public.BLOCKSIZE) | 148 | chunk, err = try_receive(data, BLOCKSIZE) |
| 266 | if err == "closed" then err = "done" end | 149 | if err == "closed" then err = "done" end |
| 267 | res = callback(chunk, err) | 150 | res = callback(chunk, err) |
| 268 | if not res then break end | 151 | if not res then break end |
| @@ -280,16 +163,16 @@ end | |||
| 280 | -- Returns | 163 | -- Returns |
| 281 | -- err: error message in case of error, nil otherwise | 164 | -- err: error message in case of error, nil otherwise |
| 282 | ----------------------------------------------------------------------------- | 165 | ----------------------------------------------------------------------------- |
| 283 | function Private.retrieve(control, server, name, is_directory, content_cb) | 166 | local function retrieve(control, server, name, is_directory, content_cb) |
| 284 | local code, answer | 167 | local code, answer |
| 285 | local data | 168 | local data |
| 286 | -- ask server for file or directory listing accordingly | 169 | -- ask server for file or directory listing accordingly |
| 287 | if is_directory then | 170 | if is_directory then |
| 288 | code, answer = Private.cwd(control, name) | 171 | code, answer = cwd(control, name) |
| 289 | if not code then return answer end | 172 | if not code then return answer end |
| 290 | code, answer = Private.command(control, "nlst", nil, {150, 125}) | 173 | code, answer = command(control, "nlst", nil, {150, 125}) |
| 291 | else | 174 | else |
| 292 | code, answer = Private.command(control, "retr", name, {150, 125}) | 175 | code, answer = command(control, "retr", name, {150, 125}) |
| 293 | end | 176 | end |
| 294 | if not code then return nil, answer end | 177 | if not code then return nil, answer end |
| 295 | data, answer = server:accept() | 178 | data, answer = server:accept() |
| @@ -298,43 +181,14 @@ function Private.retrieve(control, server, name, is_directory, content_cb) | |||
| 298 | control:close() | 181 | control:close() |
| 299 | return answer | 182 | return answer |
| 300 | end | 183 | end |
| 301 | answer = Private.receive_indirect(data, content_cb) | 184 | answer = receive_indirect(data, content_cb) |
| 302 | if answer then | 185 | if answer then |
| 303 | control:close() | 186 | control:close() |
| 304 | return answer | 187 | return answer |
| 305 | end | 188 | end |
| 306 | data:close() | 189 | data:close() |
| 307 | -- make sure file transfered ok | 190 | -- make sure file transfered ok |
| 308 | return Private.check_answer(control, {226, 250}) | 191 | return check_answer(control, {226, 250}) |
| 309 | end | ||
| 310 | |||
| 311 | ----------------------------------------------------------------------------- | ||
| 312 | -- Sends data comming from a callback | ||
| 313 | -- Input | ||
| 314 | -- data: data connection | ||
| 315 | -- send_cb: callback to produce file contents | ||
| 316 | -- chunk, size: first callback return values | ||
| 317 | -- Returns | ||
| 318 | -- nil if successfull, or an error message in case of error | ||
| 319 | ----------------------------------------------------------------------------- | ||
| 320 | function Private.send_indirect(data, send_cb, chunk, size) | ||
| 321 | local total, sent, err | ||
| 322 | total = 0 | ||
| 323 | while 1 do | ||
| 324 | if type(chunk) ~= "string" or type(size) ~= "number" then | ||
| 325 | data:close() | ||
| 326 | if not chunk and type(size) == "string" then return size | ||
| 327 | else return "invalid callback return" end | ||
| 328 | end | ||
| 329 | sent, err = data:send(chunk) | ||
| 330 | if err then | ||
| 331 | data:close() | ||
| 332 | return err | ||
| 333 | end | ||
| 334 | total = total + sent | ||
| 335 | if sent >= size then break end | ||
| 336 | chunk, size = send_cb() | ||
| 337 | end | ||
| 338 | end | 192 | end |
| 339 | 193 | ||
| 340 | ----------------------------------------------------------------------------- | 194 | ----------------------------------------------------------------------------- |
| @@ -348,9 +202,9 @@ end | |||
| 348 | -- code: return code, nil if error | 202 | -- code: return code, nil if error |
| 349 | -- answer: server answer or error message | 203 | -- answer: server answer or error message |
| 350 | ----------------------------------------------------------------------------- | 204 | ----------------------------------------------------------------------------- |
| 351 | function Private.store(control, server, file, send_cb) | 205 | local function store(control, server, file, send_cb) |
| 352 | local data, err | 206 | local data, err |
| 353 | local code, answer = Private.command(control, "stor", file, {150, 125}) | 207 | local code, answer = command(control, "stor", file, {150, 125}) |
| 354 | if not code then | 208 | if not code then |
| 355 | control:close() | 209 | control:close() |
| 356 | return nil, answer | 210 | return nil, answer |
| @@ -363,7 +217,7 @@ function Private.store(control, server, file, send_cb) | |||
| 363 | return nil, answer | 217 | return nil, answer |
| 364 | end | 218 | end |
| 365 | -- send whole file | 219 | -- send whole file |
| 366 | err = Private.send_indirect(data, send_cb, send_cb()) | 220 | err = send_indirect(data, send_cb, send_cb()) |
| 367 | if err then | 221 | if err then |
| 368 | control:close() | 222 | control:close() |
| 369 | return nil, err | 223 | return nil, err |
| @@ -371,7 +225,7 @@ function Private.store(control, server, file, send_cb) | |||
| 371 | -- close connection to inform that file transmission is complete | 225 | -- close connection to inform that file transmission is complete |
| 372 | data:close() | 226 | data:close() |
| 373 | -- check if file was received correctly | 227 | -- check if file was received correctly |
| 374 | return Private.check_answer(control, {226, 250}) | 228 | return check_answer(control, {226, 250}) |
| 375 | end | 229 | end |
| 376 | 230 | ||
| 377 | ----------------------------------------------------------------------------- | 231 | ----------------------------------------------------------------------------- |
| @@ -382,11 +236,11 @@ end | |||
| 382 | -- Returns | 236 | -- Returns |
| 383 | -- err: error message if any | 237 | -- err: error message if any |
| 384 | ----------------------------------------------------------------------------- | 238 | ----------------------------------------------------------------------------- |
| 385 | function Private.change_type(control, params) | 239 | local function change_type(control, params) |
| 386 | local type, _ | 240 | local type, _ |
| 387 | _, _, type = string.find(params or "", "type=(.)") | 241 | _, _, type = string.find(params or "", "type=(.)") |
| 388 | if type == "a" or type == "i" then | 242 | if type == "a" or type == "i" then |
| 389 | local code, err = Private.command(control, "type", type, {200}) | 243 | local code, err = command(control, "type", type, {200}) |
| 390 | if not code then return err end | 244 | if not code then return err end |
| 391 | end | 245 | end |
| 392 | end | 246 | end |
| @@ -399,45 +253,42 @@ end | |||
| 399 | -- control: control connection with server, or nil if error | 253 | -- control: control connection with server, or nil if error |
| 400 | -- err: error message if any | 254 | -- err: error message if any |
| 401 | ----------------------------------------------------------------------------- | 255 | ----------------------------------------------------------------------------- |
| 402 | function Private.open(parsed) | 256 | local function open(parsed) |
| 403 | -- start control connection | 257 | local control, err = socket.tp.connect(parsed.host, parsed.port) |
| 404 | local control, err = socket.connect(parsed.host, parsed.port) | ||
| 405 | if not control then return nil, err end | 258 | if not control then return nil, err end |
| 406 | -- make sure we don't block forever | 259 | local code, reply |
| 407 | control:settimeout(Public.TIMEOUT) | 260 | -- greet |
| 408 | -- check greeting | 261 | code, reply = control:check({120, 220}) |
| 409 | local code, answer = Private.greet(control) | 262 | if code == 120 then -- busy, try again |
| 410 | if not code then return nil, answer end | 263 | code, reply = control:check(220) |
| 411 | -- try to log in | 264 | end |
| 412 | code, err = Private.login(control, parsed.user, parsed.password) | 265 | -- authenticate |
| 413 | if not code then return nil, err | 266 | code, reply = control:command("user", user) |
| 414 | else return control end | 267 | code, reply = control:check({230, 331}) |
| 268 | if code == 331 and password then -- need pass and we have pass | ||
| 269 | control:command("pass", password) | ||
| 270 | code, reply = control:check({230, 202}) | ||
| 271 | end | ||
| 272 | -- change directory | ||
| 273 | local segment = parse_path(parsed) | ||
| 274 | for i, v in ipairs(segment) do | ||
| 275 | code, reply = control:command("cwd") | ||
| 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 | ||
| 415 | end | 284 | end |
| 416 | 285 | ||
| 417 | ----------------------------------------------------------------------------- | 286 | return change_dir(control, segment) or |
| 418 | -- Closes the connection with the server | 287 | change_type(control, parsed.params) or |
| 419 | -- Input | 288 | download(control, request, segment) or |
| 420 | -- control: control connection with server | 289 | close(control) |
| 421 | ----------------------------------------------------------------------------- | ||
| 422 | function Private.close(control) | ||
| 423 | -- disconnect | ||
| 424 | Private.logout(control) | ||
| 425 | end | 290 | end |
| 426 | 291 | ||
| 427 | ----------------------------------------------------------------------------- | ||
| 428 | -- Changes to the directory pointed to by URL | ||
| 429 | -- Input | ||
| 430 | -- control: control connection with server | ||
| 431 | -- segment: parsed URL path segments | ||
| 432 | -- Returns | ||
| 433 | -- err: error message if any | ||
| 434 | ----------------------------------------------------------------------------- | ||
| 435 | function Private.change_dir(control, segment) | ||
| 436 | local n = table.getn(segment) | ||
| 437 | for i = 1, n-1 do | ||
| 438 | local code, answer = Private.cwd(control, segment[i]) | ||
| 439 | if not code then return answer end | ||
| 440 | end | ||
| 441 | end | 292 | end |
| 442 | 293 | ||
| 443 | ----------------------------------------------------------------------------- | 294 | ----------------------------------------------------------------------------- |
| @@ -450,7 +301,7 @@ end | |||
| 450 | -- Returns | 301 | -- Returns |
| 451 | -- err: error message if any | 302 | -- err: error message if any |
| 452 | ----------------------------------------------------------------------------- | 303 | ----------------------------------------------------------------------------- |
| 453 | function Private.upload(control, request, segment) | 304 | local function upload(control, request, segment) |
| 454 | local code, name, content_cb | 305 | local code, name, content_cb |
| 455 | -- get remote file name | 306 | -- get remote file name |
| 456 | name = segment[table.getn(segment)] | 307 | name = segment[table.getn(segment)] |
| @@ -460,10 +311,10 @@ function Private.upload(control, request, segment) | |||
| 460 | end | 311 | end |
| 461 | content_cb = request.content_cb | 312 | content_cb = request.content_cb |
| 462 | -- setup passive connection | 313 | -- setup passive connection |
| 463 | local server, answer = Private.port(control) | 314 | local server, answer = port(control) |
| 464 | if not server then return answer end | 315 | if not server then return answer end |
| 465 | -- ask server to receive file | 316 | -- ask server to receive file |
| 466 | code, answer = Private.store(control, server, name, content_cb) | 317 | code, answer = store(control, server, name, content_cb) |
| 467 | if not code then return answer end | 318 | if not code then return answer end |
| 468 | end | 319 | end |
| 469 | 320 | ||
| @@ -477,7 +328,7 @@ end | |||
| 477 | -- Returns | 328 | -- Returns |
| 478 | -- err: error message if any | 329 | -- err: error message if any |
| 479 | ----------------------------------------------------------------------------- | 330 | ----------------------------------------------------------------------------- |
| 480 | function Private.download(control, request, segment) | 331 | local function download(control, request, segment) |
| 481 | local code, name, is_directory, content_cb | 332 | local code, name, is_directory, content_cb |
| 482 | is_directory = segment.is_directory | 333 | is_directory = segment.is_directory |
| 483 | content_cb = request.content_cb | 334 | content_cb = request.content_cb |
| @@ -488,10 +339,10 @@ function Private.download(control, request, segment) | |||
| 488 | return "Invalid file path" | 339 | return "Invalid file path" |
| 489 | end | 340 | end |
| 490 | -- setup passive connection | 341 | -- setup passive connection |
| 491 | local server, answer = Private.port(control) | 342 | local server, answer = port(control) |
| 492 | if not server then return answer end | 343 | if not server then return answer end |
| 493 | -- ask server to send file or directory listing | 344 | -- ask server to send file or directory listing |
| 494 | code, answer = Private.retrieve(control, server, name, | 345 | code, answer = retrieve(control, server, name, |
| 495 | is_directory, content_cb) | 346 | is_directory, content_cb) |
| 496 | if not code then return answer end | 347 | if not code then return answer end |
| 497 | end | 348 | end |
| @@ -507,13 +358,12 @@ end | |||
| 507 | -- Returns | 358 | -- Returns |
| 508 | -- parsed: a table with parsed components | 359 | -- parsed: a table with parsed components |
| 509 | ----------------------------------------------------------------------------- | 360 | ----------------------------------------------------------------------------- |
| 510 | function Private.parse_url(request) | 361 | local function parse_url(request) |
| 511 | local parsed = socket.url.parse(request.url, { | 362 | local parsed = socket.url.parse(request.url, { |
| 512 | host = "", | ||
| 513 | user = "anonymous", | 363 | user = "anonymous", |
| 514 | port = 21, | 364 | port = 21, |
| 515 | path = "/", | 365 | path = "/", |
| 516 | password = Public.EMAIL, | 366 | password = EMAIL, |
| 517 | scheme = "ftp" | 367 | scheme = "ftp" |
| 518 | }) | 368 | }) |
| 519 | -- explicit login information overrides that given by URL | 369 | -- explicit login information overrides that given by URL |
| @@ -531,7 +381,7 @@ end | |||
| 531 | -- Returns | 381 | -- Returns |
| 532 | -- dirs: a table with parsed directory components | 382 | -- dirs: a table with parsed directory components |
| 533 | ----------------------------------------------------------------------------- | 383 | ----------------------------------------------------------------------------- |
| 534 | function Private.parse_path(parsed_url) | 384 | local function parse_path(parsed_url) |
| 535 | local segment = socket.url.parse_path(parsed_url.path) | 385 | local segment = socket.url.parse_path(parsed_url.path) |
| 536 | segment.is_directory = segment.is_directory or | 386 | segment.is_directory = segment.is_directory or |
| 537 | (parsed_url.params == "type=d") | 387 | (parsed_url.params == "type=d") |
| @@ -549,7 +399,7 @@ end | |||
| 549 | -- Returns | 399 | -- Returns |
| 550 | -- request: request table | 400 | -- request: request table |
| 551 | ----------------------------------------------------------------------------- | 401 | ----------------------------------------------------------------------------- |
| 552 | function Private.build_request(data) | 402 | local function build_request(data) |
| 553 | local request = {} | 403 | local request = {} |
| 554 | if type(data) == "table" then for i, v in data do request[i] = v end | 404 | if type(data) == "table" then for i, v in data do request[i] = v end |
| 555 | else request.url = data end | 405 | else request.url = data end |
| @@ -568,18 +418,18 @@ end | |||
| 568 | -- Returns | 418 | -- Returns |
| 569 | -- err: error message if any | 419 | -- err: error message if any |
| 570 | ----------------------------------------------------------------------------- | 420 | ----------------------------------------------------------------------------- |
| 571 | function Public.get_cb(request) | 421 | function get_cb(request) |
| 572 | local parsed = Private.parse_url(request) | 422 | local parsed = parse_url(request) |
| 573 | if parsed.scheme ~= "ftp" then | 423 | if parsed.scheme ~= "ftp" then |
| 574 | return string.format("unknown scheme '%s'", parsed.scheme) | 424 | return string.format("unknown scheme '%s'", parsed.scheme) |
| 575 | end | 425 | end |
| 576 | local control, err = Private.open(parsed) | 426 | local control, err = open(parsed) |
| 577 | if not control then return err end | 427 | if not control then return err end |
| 578 | local segment = Private.parse_path(parsed) | 428 | local segment = parse_path(parsed) |
| 579 | return Private.change_dir(control, segment) or | 429 | return change_dir(control, segment) or |
| 580 | Private.change_type(control, parsed.params) or | 430 | change_type(control, parsed.params) or |
| 581 | Private.download(control, request, segment) or | 431 | download(control, request, segment) or |
| 582 | Private.close(control) | 432 | close(control) |
| 583 | end | 433 | end |
| 584 | 434 | ||
| 585 | ----------------------------------------------------------------------------- | 435 | ----------------------------------------------------------------------------- |
| @@ -594,18 +444,18 @@ end | |||
| 594 | -- Returns | 444 | -- Returns |
| 595 | -- err: error message if any | 445 | -- err: error message if any |
| 596 | ----------------------------------------------------------------------------- | 446 | ----------------------------------------------------------------------------- |
| 597 | function Public.put_cb(request) | 447 | function put_cb(request) |
| 598 | local parsed = Private.parse_url(request) | 448 | local parsed = parse_url(request) |
| 599 | if parsed.scheme ~= "ftp" then | 449 | if parsed.scheme ~= "ftp" then |
| 600 | return string.format("unknown scheme '%s'", parsed.scheme) | 450 | return string.format("unknown scheme '%s'", parsed.scheme) |
| 601 | end | 451 | end |
| 602 | local control, err = Private.open(parsed) | 452 | local control, err = open(parsed) |
| 603 | if not control then return err end | 453 | if not control then return err end |
| 604 | local segment = Private.parse_path(parsed) | 454 | local segment = parse_path(parsed) |
| 605 | err = Private.change_dir(control, segment) or | 455 | err = change_dir(control, segment) or |
| 606 | Private.change_type(control, parsed.params) or | 456 | change_type(control, parsed.params) or |
| 607 | Private.upload(control, request, segment) or | 457 | upload(control, request, segment) or |
| 608 | Private.close(control) | 458 | close(control) |
| 609 | if err then return nil, err | 459 | if err then return nil, err |
| 610 | else return 1 end | 460 | else return 1 end |
| 611 | end | 461 | end |
| @@ -623,11 +473,11 @@ end | |||
| 623 | -- Returns | 473 | -- Returns |
| 624 | -- err: error message if any | 474 | -- err: error message if any |
| 625 | ----------------------------------------------------------------------------- | 475 | ----------------------------------------------------------------------------- |
| 626 | function Public.put(url_or_request, content) | 476 | function put(url_or_request, content) |
| 627 | local request = Private.build_request(url_or_request) | 477 | local request = build_request(url_or_request) |
| 628 | request.content = request.content or content | 478 | request.content = request.content or content |
| 629 | request.content_cb = socket.callback.send_string(request.content) | 479 | request.content_cb = socket.callback.send_string(request.content) |
| 630 | return Public.put_cb(request) | 480 | return put_cb(request) |
| 631 | end | 481 | end |
| 632 | 482 | ||
| 633 | ----------------------------------------------------------------------------- | 483 | ----------------------------------------------------------------------------- |
| @@ -642,12 +492,12 @@ end | |||
| 642 | -- data: file contents as a string | 492 | -- data: file contents as a string |
| 643 | -- err: error message in case of error, nil otherwise | 493 | -- err: error message in case of error, nil otherwise |
| 644 | ----------------------------------------------------------------------------- | 494 | ----------------------------------------------------------------------------- |
| 645 | function Public.get(url_or_request) | 495 | function get(url_or_request) |
| 646 | local concat = socket.concat.create() | 496 | local concat = socket.concat.create() |
| 647 | local request = Private.build_request(url_or_request) | 497 | local request = build_request(url_or_request) |
| 648 | request.content_cb = socket.callback.receive_concat(concat) | 498 | request.content_cb = socket.callback.receive_concat(concat) |
| 649 | local err = Public.get_cb(request) | 499 | local err = get_cb(request) |
| 650 | return concat:getresult(), err | 500 | return concat:getresult(), err |
| 651 | end | 501 | end |
| 652 | 502 | ||
| 653 | return ftp | 503 | return socket.ftp |
