diff options
| author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-05-25 05:27:44 +0000 |
|---|---|---|
| committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-05-25 05:27:44 +0000 |
| commit | 888496aa821cd09d925046250ea98b1512293fd5 (patch) | |
| tree | 217e2b532762a64b58c4fffcb2cba08ba2243462 /src/ftp.lua | |
| parent | 4fc164b8eaa0453050a0a859321c327bb2c4f776 (diff) | |
| download | luasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.gz luasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.bz2 luasocket-888496aa821cd09d925046250ea98b1512293fd5.zip | |
FTP low-level working.
SMTP connection oriented working.
ltn12 improved.
Diffstat (limited to 'src/ftp.lua')
| -rw-r--r-- | src/ftp.lua | 557 |
1 files changed, 118 insertions, 439 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index 18dab6d..6074623 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
| @@ -30,474 +30,153 @@ EMAIL = "anonymous@anonymous.org" | |||
| 30 | BLOCKSIZE = 2048 | 30 | BLOCKSIZE = 2048 |
| 31 | 31 | ||
| 32 | ----------------------------------------------------------------------------- | 32 | ----------------------------------------------------------------------------- |
| 33 | -- Gets ip and port for data connection from PASV answer | 33 | -- Low level FTP API |
| 34 | -- Input | ||
| 35 | -- pasv: PASV command answer | ||
| 36 | -- Returns | ||
| 37 | -- ip: string containing ip for data connection | ||
| 38 | -- port: port for data connection | ||
| 39 | ----------------------------------------------------------------------------- | 34 | ----------------------------------------------------------------------------- |
| 40 | local function get_pasv(pasv) | 35 | local metat = { __index = {} } |
| 41 | local a, b, c, d, p1, p2, _ | ||
| 42 | local ip, port | ||
| 43 | _,_, a, b, c, d, p1, p2 = | ||
| 44 | string.find(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") | ||
| 45 | if not (a and b and c and d and p1 and p2) then return nil, nil end | ||
| 46 | ip = string.format("%d.%d.%d.%d", a, b, c, d) | ||
| 47 | port = tonumber(p1)*256 + tonumber(p2) | ||
| 48 | return ip, port | ||
| 49 | end | ||
| 50 | |||
| 51 | ----------------------------------------------------------------------------- | ||
| 52 | -- Check server greeting | ||
| 53 | -- Input | ||
| 54 | -- control: control connection with server | ||
| 55 | -- Returns | ||
| 56 | -- code: nil if error | ||
| 57 | -- answer: server answer or error message | ||
| 58 | ----------------------------------------------------------------------------- | ||
| 59 | local function greet(control) | ||
| 60 | local code, answer = check_answer(control, {120, 220}) | ||
| 61 | if code == 120 then -- please try again, somewhat busy now... | ||
| 62 | return check_answer(control, {220}) | ||
| 63 | end | ||
| 64 | return code, answer | ||
| 65 | end | ||
| 66 | |||
| 67 | ----------------------------------------------------------------------------- | ||
| 68 | -- Log in on server | ||
| 69 | -- Input | ||
| 70 | -- control: control connection with server | ||
| 71 | -- user: user name | ||
| 72 | -- password: user password if any | ||
| 73 | -- Returns | ||
| 74 | -- code: nil if error | ||
| 75 | -- answer: server answer or error message | ||
| 76 | ----------------------------------------------------------------------------- | ||
| 77 | local function login(control, user, password) | ||
| 78 | local code, answer = command(control, "user", user, {230, 331}) | ||
| 79 | if code == 331 and password then -- need pass and we have pass | ||
| 80 | return command(control, "pass", password, {230, 202}) | ||
| 81 | end | ||
| 82 | return code, answer | ||
| 83 | end | ||
| 84 | |||
| 85 | ----------------------------------------------------------------------------- | ||
| 86 | -- Change to target directory | ||
| 87 | -- Input | ||
| 88 | -- control: socket for control connection with server | ||
| 89 | -- path: directory to change to | ||
| 90 | -- Returns | ||
| 91 | -- code: nil if error | ||
| 92 | -- answer: server answer or error message | ||
| 93 | ----------------------------------------------------------------------------- | ||
| 94 | local function cwd(control, path) | ||
| 95 | end | ||
| 96 | |||
| 97 | ----------------------------------------------------------------------------- | ||
| 98 | -- Change to target directory | ||
| 99 | -- Input | ||
| 100 | -- control: socket for control connection with server | ||
| 101 | -- Returns | ||
| 102 | -- server: server socket bound to local address, nil if error | ||
| 103 | -- answer: error message if any | ||
| 104 | ----------------------------------------------------------------------------- | ||
| 105 | local function port(control) | ||
| 106 | local code, answer | ||
| 107 | local server, ctl_ip | ||
| 108 | ctl_ip, answer = control:getsockname() | ||
| 109 | server, answer = socket.bind(ctl_ip, 0) | ||
| 110 | server:settimeout(TIMEOUT) | ||
| 111 | local ip, p, ph, pl | ||
| 112 | ip, p = server:getsockname() | ||
| 113 | pl = math.mod(p, 256) | ||
| 114 | ph = (p - pl)/256 | ||
| 115 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | ||
| 116 | code, answer = command(control, "port", arg, {200}) | ||
| 117 | if not code then | ||
| 118 | server:close() | ||
| 119 | return nil, answer | ||
| 120 | else return server end | ||
| 121 | end | ||
| 122 | 36 | ||
| 123 | ----------------------------------------------------------------------------- | 37 | function open(server, port) |
| 124 | -- Closes control connection with server | 38 | local tp = socket.try(socket.tp.connect(server, port or PORT)) |
| 125 | -- Input | 39 | return setmetatable({tp = tp}, metat) |
| 126 | -- control: control connection with server | ||
| 127 | -- Returns | ||
| 128 | -- code: nil if error | ||
| 129 | -- answer: server answer or error message | ||
| 130 | ----------------------------------------------------------------------------- | ||
| 131 | local function logout(control) | ||
| 132 | local code, answer = command(control, "quit", nil, {221}) | ||
| 133 | if code then control:close() end | ||
| 134 | return code, answer | ||
| 135 | end | 40 | end |
| 136 | 41 | ||
| 137 | ----------------------------------------------------------------------------- | 42 | local function port(portt) |
| 138 | -- Receives data and send it to a callback | 43 | return portt.server:accept() |
| 139 | -- Input | ||
| 140 | -- data: data connection | ||
| 141 | -- callback: callback to return file contents | ||
| 142 | -- Returns | ||
| 143 | -- nil if successfull, or an error message in case of error | ||
| 144 | ----------------------------------------------------------------------------- | ||
| 145 | local function receive_indirect(data, callback) | ||
| 146 | local chunk, err, res | ||
| 147 | while not err do | ||
| 148 | chunk, err = try_receive(data, BLOCKSIZE) | ||
| 149 | if err == "closed" then err = "done" end | ||
| 150 | res = callback(chunk, err) | ||
| 151 | if not res then break end | ||
| 152 | end | ||
| 153 | end | 44 | end |
| 154 | 45 | ||
| 155 | ----------------------------------------------------------------------------- | 46 | local function pasv(pasvt) |
| 156 | -- Retrieves file or directory listing | 47 | return socket.connect(pasvt.ip, pasvt.port) |
| 157 | -- Input | ||
| 158 | -- control: control connection with server | ||
| 159 | -- server: server socket bound to local address | ||
| 160 | -- name: file name | ||
| 161 | -- is_directory: is file a directory name? | ||
| 162 | -- content_cb: callback to receive file contents | ||
| 163 | -- Returns | ||
| 164 | -- err: error message in case of error, nil otherwise | ||
| 165 | ----------------------------------------------------------------------------- | ||
| 166 | local function retrieve(control, server, name, is_directory, content_cb) | ||
| 167 | local code, answer | ||
| 168 | local data | ||
| 169 | -- ask server for file or directory listing accordingly | ||
| 170 | if is_directory then | ||
| 171 | code, answer = cwd(control, name) | ||
| 172 | if not code then return answer end | ||
| 173 | code, answer = command(control, "nlst", nil, {150, 125}) | ||
| 174 | else | ||
| 175 | code, answer = command(control, "retr", name, {150, 125}) | ||
| 176 | end | ||
| 177 | if not code then return nil, answer end | ||
| 178 | data, answer = server:accept() | ||
| 179 | server:close() | ||
| 180 | if not data then | ||
| 181 | control:close() | ||
| 182 | return answer | ||
| 183 | end | ||
| 184 | answer = receive_indirect(data, content_cb) | ||
| 185 | if answer then | ||
| 186 | control:close() | ||
| 187 | return answer | ||
| 188 | end | ||
| 189 | data:close() | ||
| 190 | -- make sure file transfered ok | ||
| 191 | return check_answer(control, {226, 250}) | ||
| 192 | end | 48 | end |
| 193 | 49 | ||
| 194 | ----------------------------------------------------------------------------- | 50 | function metat.__index:login(user, password) |
| 195 | -- Stores a file | 51 | socket.try(self.tp:command("USER", user)) |
| 196 | -- Input | 52 | local code, reply = socket.try(self.tp:check{"2..", 331}) |
| 197 | -- control: control connection with server | 53 | if code == 331 then |
| 198 | -- server: server socket bound to local address | 54 | socket.try(password, reply) |
| 199 | -- file: file name under current directory | 55 | socket.try(self.tp:command("PASS", password)) |
| 200 | -- send_cb: callback to produce the file contents | 56 | socket.try(self.tp:check("2..")) |
| 201 | -- Returns | 57 | end |
| 202 | -- code: return code, nil if error | 58 | return 1 |
| 203 | -- answer: server answer or error message | 59 | end |
| 204 | ----------------------------------------------------------------------------- | 60 | |
| 205 | local function store(control, server, file, send_cb) | 61 | function metat.__index:pasv() |
| 206 | local data, err | 62 | socket.try(self.tp:command("PASV")) |
| 207 | local code, answer = command(control, "stor", file, {150, 125}) | 63 | local code, reply = socket.try(self.tp:check("2..")) |
| 208 | if not code then | 64 | local _, _, a, b, c, d, p1, p2 = |
| 209 | control:close() | 65 | string.find(reply, "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)") |
| 210 | return nil, answer | 66 | socket.try(a and b and c and d and p1 and p2, reply) |
| 211 | end | 67 | self.pasvt = { |
| 212 | -- start data connection | 68 | ip = string.format("%d.%d.%d.%d", a, b, c, d), |
| 213 | data, answer = server:accept() | 69 | port = p1*256 + p2 |
| 214 | server:close() | 70 | } |
| 215 | if not data then | 71 | if self.portt then |
| 216 | control:close() | 72 | self.portt.server:close() |
| 217 | return nil, answer | 73 | self.portt = nil |
| 218 | end | 74 | end |
| 219 | -- send whole file | 75 | return self.pasvt.ip, self.pasvt.port |
| 220 | err = send_indirect(data, send_cb, send_cb()) | ||
| 221 | if err then | ||
| 222 | control:close() | ||
| 223 | return nil, err | ||
| 224 | end | ||
| 225 | -- close connection to inform that file transmission is complete | ||
| 226 | data:close() | ||
| 227 | -- check if file was received correctly | ||
| 228 | return check_answer(control, {226, 250}) | ||
| 229 | end | ||
| 230 | |||
| 231 | ----------------------------------------------------------------------------- | ||
| 232 | -- Change transfer type | ||
| 233 | -- Input | ||
| 234 | -- control: control connection with server | ||
| 235 | -- params: "type=i" for binary or "type=a" for ascii | ||
| 236 | -- Returns | ||
| 237 | -- err: error message if any | ||
| 238 | ----------------------------------------------------------------------------- | ||
| 239 | local function change_type(control, params) | ||
| 240 | local type, _ | ||
| 241 | _, _, type = string.find(params or "", "type=(.)") | ||
| 242 | if type == "a" or type == "i" then | ||
| 243 | local code, err = command(control, "type", type, {200}) | ||
| 244 | if not code then return err end | ||
| 245 | end | ||
| 246 | end | 76 | end |
| 247 | 77 | ||
| 248 | ----------------------------------------------------------------------------- | 78 | function metat.__index:port(ip, port) |
| 249 | -- Starts a control connection, checks the greeting and log on | 79 | self.pasvt = nil |
| 250 | -- Input | 80 | local server |
| 251 | -- parsed: parsed URL components | 81 | if not ip then |
| 252 | -- Returns | 82 | ip, port = socket.try(self.tp:getcontrol():getsockname()) |
| 253 | -- control: control connection with server, or nil if error | 83 | server = socket.try(socket.bind(ip, 0)) |
| 254 | -- err: error message if any | 84 | ip, port = socket.try(server:getsockname()) |
| 255 | ----------------------------------------------------------------------------- | 85 | socket.try(server:settimeout(TIMEOUT)) |
| 256 | local function open(parsed) | 86 | end |
| 257 | local control, err = socket.tp.connect(parsed.host, parsed.port) | 87 | local pl = math.mod(port, 256) |
| 258 | if not control then return nil, err end | 88 | local ph = (port - pl)/256 |
| 89 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | ||
| 90 | socket.try(self.tp:command("port", arg)) | ||
| 91 | socket.try(self.tp:check("2..")) | ||
| 92 | self.portt = server and {ip = ip, port = port, server = server} | ||
| 93 | return 1 | ||
| 94 | end | ||
| 95 | |||
| 96 | function metat.__index:send(sendt) | ||
| 97 | local data | ||
| 98 | socket.try(self.pasvt or self.portt, "need port or pasv first") | ||
| 99 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | ||
| 100 | socket.try(self.tp:command(sendt.command, sendt.argument)) | ||
| 101 | if self.portt then data = socket.try(port(self.portt)) end | ||
| 102 | local step = sendt.step or ltn12.pump.step | ||
| 259 | local code, reply | 103 | local code, reply |
| 260 | -- greet | 104 | local checkstep = function(src, snk) |
| 261 | code, reply = control:check({120, 220}) | 105 | local readyt = socket.select(readt, nil, 0) |
| 262 | if code == 120 then -- busy, try again | 106 | if readyt[tp] then |
| 263 | code, reply = control:check(220) | 107 | code, reply = self.tp:check{"2..", "1.."} |
| 108 | if not code then | ||
| 109 | data:close() | ||
| 110 | return nil, reply | ||
| 111 | end | ||
| 112 | end | ||
| 113 | local ret, err = step(src, snk) | ||
| 114 | if err then data:close() end | ||
| 115 | return ret, err | ||
| 264 | end | 116 | end |
| 265 | -- authenticate | 117 | local sink = socket.sink("close-when-empty", data) |
| 266 | code, reply = control:command("user", user) | 118 | socket.try(ltn12.pump.all(sendt.source, sink, checkstep)) |
| 267 | code, reply = control:check({230, 331}) | 119 | if not code then code = socket.try(self.tp:check{"1..", "2.."}) end |
| 268 | if code == 331 and password then -- need pass and we have pass | 120 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
| 269 | control:command("pass", password) | 121 | return 1 |
| 270 | code, reply = control:check({230, 202}) | 122 | end |
| 123 | |||
| 124 | function metat.__index:receive(recvt) | ||
| 125 | local data | ||
| 126 | socket.try(self.pasvt or self.portt, "need port or pasv first") | ||
| 127 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | ||
| 128 | socket.try(self.tp:command(recvt.command, recvt.argument)) | ||
| 129 | if self.portt then data = socket.try(port(self.portt)) end | ||
| 130 | local source = socket.source("until-closed", data) | ||
| 131 | local step = recvt.step or ltn12.pump.step | ||
| 132 | local checkstep = function(src, snk) | ||
| 133 | local ret, err = step(src, snk) | ||
| 134 | if err then data:close() end | ||
| 135 | return ret, err | ||
| 271 | end | 136 | end |
| 272 | -- change directory | 137 | socket.try(ltn12.pump.all(source, recvt.sink, checkstep)) |
| 273 | local segment = parse_path(parsed) | 138 | local code = socket.try(self.tp:check{"1..", "2.."}) |
| 274 | for i, v in ipairs(segment) do | 139 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
| 275 | code, reply = control:command("cwd") | 140 | return 1 |
| 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 | ||
| 284 | end | 141 | end |
| 285 | 142 | ||
| 286 | return change_dir(control, segment) or | 143 | function metat.__index:cwd(dir) |
| 287 | change_type(control, parsed.params) or | 144 | socket.try(self.tp:command("CWD", dir)) |
| 288 | download(control, request, segment) or | 145 | socket.try(self.tp:check(250)) |
| 289 | close(control) | 146 | return 1 |
| 290 | end | 147 | end |
| 291 | 148 | ||
| 149 | function metat.__index:type(type) | ||
| 150 | socket.try(self.tp:command("TYPE", type)) | ||
| 151 | socket.try(self.tp:check(200)) | ||
| 152 | return 1 | ||
| 292 | end | 153 | end |
| 293 | 154 | ||
| 294 | ----------------------------------------------------------------------------- | 155 | function metat.__index:greet() |
| 295 | -- Stores a file in current directory | 156 | local code = socket.try(self.tp:check{"1..", "2.."}) |
| 296 | -- Input | 157 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end |
| 297 | -- control: control connection with server | 158 | return 1 |
| 298 | -- request: a table with the fields: | ||
| 299 | -- content_cb: send callback to send file contents | ||
| 300 | -- segment: parsed URL path segments | ||
| 301 | -- Returns | ||
| 302 | -- err: error message if any | ||
| 303 | ----------------------------------------------------------------------------- | ||
| 304 | local function upload(control, request, segment) | ||
| 305 | local code, name, content_cb | ||
| 306 | -- get remote file name | ||
| 307 | name = segment[table.getn(segment)] | ||
| 308 | if not name then | ||
| 309 | control:close() | ||
| 310 | return "Invalid file path" | ||
| 311 | end | ||
| 312 | content_cb = request.content_cb | ||
| 313 | -- setup passive connection | ||
| 314 | local server, answer = port(control) | ||
| 315 | if not server then return answer end | ||
| 316 | -- ask server to receive file | ||
| 317 | code, answer = store(control, server, name, content_cb) | ||
| 318 | if not code then return answer end | ||
| 319 | end | 159 | end |
| 320 | 160 | ||
| 321 | ----------------------------------------------------------------------------- | 161 | function metat.__index:quit() |
| 322 | -- Download a file from current directory | 162 | socket.try(self.tp:command("QUIT")) |
| 323 | -- Input | 163 | socket.try(self.tp:check("2..")) |
| 324 | -- control: control connection with server | 164 | return 1 |
| 325 | -- request: a table with the fields: | ||
| 326 | -- content_cb: receive callback to receive file contents | ||
| 327 | -- segment: parsed URL path segments | ||
| 328 | -- Returns | ||
| 329 | -- err: error message if any | ||
| 330 | ----------------------------------------------------------------------------- | ||
| 331 | local function download(control, request, segment) | ||
| 332 | local code, name, is_directory, content_cb | ||
| 333 | is_directory = segment.is_directory | ||
| 334 | content_cb = request.content_cb | ||
| 335 | -- get remote file name | ||
| 336 | name = segment[table.getn(segment)] | ||
| 337 | if not name and not is_directory then | ||
| 338 | control:close() | ||
| 339 | return "Invalid file path" | ||
| 340 | end | ||
| 341 | -- setup passive connection | ||
| 342 | local server, answer = port(control) | ||
| 343 | if not server then return answer end | ||
| 344 | -- ask server to send file or directory listing | ||
| 345 | code, answer = retrieve(control, server, name, | ||
| 346 | is_directory, content_cb) | ||
| 347 | if not code then return answer end | ||
| 348 | end | 165 | end |
| 349 | 166 | ||
| 350 | ----------------------------------------------------------------------------- | 167 | function metat.__index:close() |
| 351 | -- Parses the FTP URL setting default values | 168 | socket.try(self.tp:close()) |
| 352 | -- Input | 169 | return 1 |
| 353 | -- request: a table with the fields: | ||
| 354 | -- url: the target URL | ||
| 355 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 356 | -- user: account user name | ||
| 357 | -- password: account password | ||
| 358 | -- Returns | ||
| 359 | -- parsed: a table with parsed components | ||
| 360 | ----------------------------------------------------------------------------- | ||
| 361 | local function parse_url(request) | ||
| 362 | local parsed = socket.url.parse(request.url, { | ||
| 363 | user = "anonymous", | ||
| 364 | port = 21, | ||
| 365 | path = "/", | ||
| 366 | password = EMAIL, | ||
| 367 | scheme = "ftp" | ||
| 368 | }) | ||
| 369 | -- explicit login information overrides that given by URL | ||
| 370 | parsed.user = request.user or parsed.user | ||
| 371 | parsed.password = request.password or parsed.password | ||
| 372 | -- explicit representation type overrides that given by URL | ||
| 373 | if request.type then parsed.params = "type=" .. request.type end | ||
| 374 | return parsed | ||
| 375 | end | 170 | end |
| 376 | 171 | ||
| 377 | ----------------------------------------------------------------------------- | 172 | ----------------------------------------------------------------------------- |
| 378 | -- Parses the FTP URL path setting default values | 173 | -- High level FTP API |
| 379 | -- Input | ||
| 380 | -- parsed: a table with the parsed URL components | ||
| 381 | -- Returns | ||
| 382 | -- dirs: a table with parsed directory components | ||
| 383 | ----------------------------------------------------------------------------- | 174 | ----------------------------------------------------------------------------- |
| 384 | local function parse_path(parsed_url) | ||
| 385 | local segment = socket.url.parse_path(parsed_url.path) | ||
| 386 | segment.is_directory = segment.is_directory or | ||
| 387 | (parsed_url.params == "type=d") | ||
| 388 | return segment | ||
| 389 | end | ||
| 390 | 175 | ||
| 391 | ----------------------------------------------------------------------------- | 176 | function put(putt) |
| 392 | -- Builds a request table from a URL or request table | ||
| 393 | -- Input | ||
| 394 | -- url_or_request: target url or request table (a table with the fields: | ||
| 395 | -- url: the target URL | ||
| 396 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 397 | -- user: account user name | ||
| 398 | -- password: account password) | ||
| 399 | -- Returns | ||
| 400 | -- request: request table | ||
| 401 | ----------------------------------------------------------------------------- | ||
| 402 | local function build_request(data) | ||
| 403 | local request = {} | ||
| 404 | if type(data) == "table" then for i, v in data do request[i] = v end | ||
| 405 | else request.url = data end | ||
| 406 | return request | ||
| 407 | end | 177 | end |
| 408 | 178 | ||
| 409 | ----------------------------------------------------------------------------- | 179 | function get(gett) |
| 410 | -- Downloads a file from a FTP server | ||
| 411 | -- Input | ||
| 412 | -- request: a table with the fields: | ||
| 413 | -- url: the target URL | ||
| 414 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 415 | -- user: account user name | ||
| 416 | -- password: account password | ||
| 417 | -- content_cb: receive callback to receive file contents | ||
| 418 | -- Returns | ||
| 419 | -- err: error message if any | ||
| 420 | ----------------------------------------------------------------------------- | ||
| 421 | function get_cb(request) | ||
| 422 | local parsed = parse_url(request) | ||
| 423 | if parsed.scheme ~= "ftp" then | ||
| 424 | return string.format("unknown scheme '%s'", parsed.scheme) | ||
| 425 | end | ||
| 426 | local control, err = open(parsed) | ||
| 427 | if not control then return err end | ||
| 428 | local segment = parse_path(parsed) | ||
| 429 | return change_dir(control, segment) or | ||
| 430 | change_type(control, parsed.params) or | ||
| 431 | download(control, request, segment) or | ||
| 432 | close(control) | ||
| 433 | end | ||
| 434 | |||
| 435 | ----------------------------------------------------------------------------- | ||
| 436 | -- Uploads a file to a FTP server | ||
| 437 | -- Input | ||
| 438 | -- request: a table with the fields: | ||
| 439 | -- url: the target URL | ||
| 440 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 441 | -- user: account user name | ||
| 442 | -- password: account password | ||
| 443 | -- content_cb: send callback to send file contents | ||
| 444 | -- Returns | ||
| 445 | -- err: error message if any | ||
| 446 | ----------------------------------------------------------------------------- | ||
| 447 | function put_cb(request) | ||
| 448 | local parsed = parse_url(request) | ||
| 449 | if parsed.scheme ~= "ftp" then | ||
| 450 | return string.format("unknown scheme '%s'", parsed.scheme) | ||
| 451 | end | ||
| 452 | local control, err = open(parsed) | ||
| 453 | if not control then return err end | ||
| 454 | local segment = parse_path(parsed) | ||
| 455 | err = change_dir(control, segment) or | ||
| 456 | change_type(control, parsed.params) or | ||
| 457 | upload(control, request, segment) or | ||
| 458 | close(control) | ||
| 459 | if err then return nil, err | ||
| 460 | else return 1 end | ||
| 461 | end | ||
| 462 | |||
| 463 | ----------------------------------------------------------------------------- | ||
| 464 | -- Uploads a file to a FTP server | ||
| 465 | -- Input | ||
| 466 | -- url_or_request: target url or request table (a table with the fields: | ||
| 467 | -- url: the target URL | ||
| 468 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 469 | -- user: account user name | ||
| 470 | -- password: account password) | ||
| 471 | -- content: file contents | ||
| 472 | -- content: file contents | ||
| 473 | -- Returns | ||
| 474 | -- err: error message if any | ||
| 475 | ----------------------------------------------------------------------------- | ||
| 476 | function put(url_or_request, content) | ||
| 477 | local request = build_request(url_or_request) | ||
| 478 | request.content = request.content or content | ||
| 479 | request.content_cb = socket.callback.send_string(request.content) | ||
| 480 | return put_cb(request) | ||
| 481 | end | ||
| 482 | |||
| 483 | ----------------------------------------------------------------------------- | ||
| 484 | -- Retrieve a file from a ftp server | ||
| 485 | -- Input | ||
| 486 | -- url_or_request: target url or request table (a table with the fields: | ||
| 487 | -- url: the target URL | ||
| 488 | -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory | ||
| 489 | -- user: account user name | ||
| 490 | -- password: account password) | ||
| 491 | -- Returns | ||
| 492 | -- data: file contents as a string | ||
| 493 | -- err: error message in case of error, nil otherwise | ||
| 494 | ----------------------------------------------------------------------------- | ||
| 495 | function get(url_or_request) | ||
| 496 | local concat = socket.concat.create() | ||
| 497 | local request = build_request(url_or_request) | ||
| 498 | request.content_cb = socket.callback.receive_concat(concat) | ||
| 499 | local err = get_cb(request) | ||
| 500 | return concat:getresult(), err | ||
| 501 | end | 180 | end |
| 502 | 181 | ||
| 503 | return socket.ftp | 182 | return ftp |
