diff options
| author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-06-18 08:02:09 +0000 |
|---|---|---|
| committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-06-18 08:02:09 +0000 |
| commit | ac4aac0909da26befaaeb6b415f66cf35b6980e0 (patch) | |
| tree | 3d3289e6192508484dcbefa10e2d862c5cc06d64 /src/ftp.lua | |
| parent | 62799a416d2b29d8058331f3d8725fb67c75d261 (diff) | |
| download | luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.gz luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.bz2 luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.zip | |
Implemented safe exceptions. This looks preeety good.
Diffstat (limited to 'src/ftp.lua')
| -rw-r--r-- | src/ftp.lua | 182 |
1 files changed, 96 insertions, 86 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index 3868158..ad02c72 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
| @@ -32,143 +32,153 @@ local metat = { __index = {} } | |||
| 32 | 32 | ||
| 33 | function open(server, port) | 33 | function open(server, port) |
| 34 | local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT)) | 34 | local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT)) |
| 35 | return setmetatable({tp = tp}, metat) | 35 | local f = { tp = tp } |
| 36 | -- make sure everything gets closed in an exception | ||
| 37 | f.try = socket.newtry(function() | ||
| 38 | tp:close() | ||
| 39 | if f.data then f.data:close() end | ||
| 40 | if f.server then f.server:close() end | ||
| 41 | end) | ||
| 42 | return setmetatable(f, metat) | ||
| 36 | end | 43 | end |
| 37 | 44 | ||
| 38 | local function port(portt) | 45 | function metat.__index:portconnect() |
| 39 | return portt.server:accept() | 46 | self.try(self.server:settimeout(TIMEOUT)) |
| 47 | self.data = self.try(self.server:accept()) | ||
| 48 | self.try(self.data:settimeout(TIMEOUT)) | ||
| 40 | end | 49 | end |
| 41 | 50 | ||
| 42 | local function pasv(pasvt) | 51 | function metat.__index:pasvconnect() |
| 43 | local data = socket.try(socket.tcp()) | 52 | self.data = self.try(socket.tcp()) |
| 44 | socket.try(data:settimeout(TIMEOUT)) | 53 | self.try(self.data:settimeout(TIMEOUT)) |
| 45 | socket.try(data:connect(pasvt.ip, pasvt.port)) | 54 | self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) |
| 46 | return data | ||
| 47 | end | 55 | end |
| 48 | 56 | ||
| 49 | function metat.__index:login(user, password) | 57 | function metat.__index:login(user, password) |
| 50 | socket.try(self.tp:command("user", user or USER)) | 58 | self.try(self.tp:command("user", user or USER)) |
| 51 | local code, reply = socket.try(self.tp:check{"2..", 331}) | 59 | local code, reply = self.try(self.tp:check{"2..", 331}) |
| 52 | if code == 331 then | 60 | if code == 331 then |
| 53 | socket.try(self.tp:command("pass", password or PASSWORD)) | 61 | self.try(self.tp:command("pass", password or PASSWORD)) |
| 54 | socket.try(self.tp:check("2..")) | 62 | self.try(self.tp:check("2..")) |
| 55 | end | 63 | end |
| 56 | return 1 | 64 | return 1 |
| 57 | end | 65 | end |
| 58 | 66 | ||
| 59 | function metat.__index:pasv() | 67 | function metat.__index:pasv() |
| 60 | socket.try(self.tp:command("pasv")) | 68 | self.try(self.tp:command("pasv")) |
| 61 | local code, reply = socket.try(self.tp:check("2..")) | 69 | local code, reply = self.try(self.tp:check("2..")) |
| 62 | local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" | 70 | local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" |
| 63 | local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) | 71 | local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) |
| 64 | socket.try(a and b and c and d and p1 and p2, reply) | 72 | self.try(a and b and c and d and p1 and p2, reply) |
| 65 | self.pasvt = { | 73 | self.pasvt = { |
| 66 | ip = string.format("%d.%d.%d.%d", a, b, c, d), | 74 | ip = string.format("%d.%d.%d.%d", a, b, c, d), |
| 67 | port = p1*256 + p2 | 75 | port = p1*256 + p2 |
| 68 | } | 76 | } |
| 69 | if self.portt then | 77 | if self.server then |
| 70 | self.portt.server:close() | 78 | self.server:close() |
| 71 | self.portt = nil | 79 | self.server = nil |
| 72 | end | 80 | end |
| 73 | return self.pasvt.ip, self.pasvt.port | 81 | return self.pasvt.ip, self.pasvt.port |
| 74 | end | 82 | end |
| 75 | 83 | ||
| 76 | function metat.__index:port(ip, port) | 84 | function metat.__index:port(ip, port) |
| 77 | self.pasvt = nil | 85 | self.pasvt = nil |
| 78 | local server | ||
| 79 | if not ip then | 86 | if not ip then |
| 80 | ip, port = socket.try(self.tp:getcontrol():getsockname()) | 87 | ip, port = self.try(self.tp:getcontrol():getsockname()) |
| 81 | server = socket.try(socket.bind(ip, 0)) | 88 | self.server = self.try(socket.bind(ip, 0)) |
| 82 | ip, port = socket.try(server:getsockname()) | 89 | ip, port = self.try(self.server:getsockname()) |
| 83 | socket.try(server:settimeout(TIMEOUT)) | 90 | self.try(server:settimeout(TIMEOUT)) |
| 84 | end | 91 | end |
| 85 | local pl = math.mod(port, 256) | 92 | local pl = math.mod(port, 256) |
| 86 | local ph = (port - pl)/256 | 93 | local ph = (port - pl)/256 |
| 87 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | 94 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") |
| 88 | socket.try(self.tp:command("port", arg)) | 95 | self.try(self.tp:command("port", arg)) |
| 89 | socket.try(self.tp:check("2..")) | 96 | self.try(self.tp:check("2..")) |
| 90 | self.portt = server and {ip = ip, port = port, server = server} | ||
| 91 | return 1 | 97 | return 1 |
| 92 | end | 98 | end |
| 93 | 99 | ||
| 94 | function metat.__index:send(sendt) | 100 | function metat.__index:send(sendt) |
| 95 | local data | 101 | self.try(self.pasvt or self.server, "need port or pasv first") |
| 96 | socket.try(self.pasvt or self.portt, "need port or pasv first") | 102 | -- if there is a pasvt table, we already sent a PASV command |
| 97 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | 103 | -- we just get the data connection into self.data |
| 104 | if self.pasvt then self:pasvconnect() end | ||
| 105 | -- get the transfer argument and command | ||
| 98 | local argument = sendt.argument or string.gsub(sendt.path, "^/", "") | 106 | local argument = sendt.argument or string.gsub(sendt.path, "^/", "") |
| 99 | if argument == "" then argument = nil end | 107 | if argument == "" then argument = nil end |
| 100 | local command = sendt.command or "stor" | 108 | local command = sendt.command or "stor" |
| 101 | socket.try(self.tp:command(command, argument)) | 109 | -- send the transfer command and check the reply |
| 102 | local code, reply = socket.try(self.tp:check{"2..", "1.."}) | 110 | self.try(self.tp:command(command, argument)) |
| 103 | if self.portt then data = socket.try(port(self.portt)) end | 111 | local code, reply = self.try(self.tp:check{"2..", "1.."}) |
| 112 | -- if there is not a a pasvt table, then there is a server | ||
| 113 | -- and we already sent a PORT command | ||
| 114 | if not self.pasvt then self:portconnect() end | ||
| 115 | -- get the sink, source and step for the transfer | ||
| 104 | local step = sendt.step or ltn12.pump.step | 116 | local step = sendt.step or ltn12.pump.step |
| 105 | local checkstep = function(src, snk) | 117 | local checkstep = function(src, snk) |
| 118 | -- check status in control connection while downloading | ||
| 106 | local readyt = socket.select(readt, nil, 0) | 119 | local readyt = socket.select(readt, nil, 0) |
| 107 | if readyt[tp] then | 120 | if readyt[tp] then self.try(self.tp:check("2..")) end |
| 108 | code, reply = self.tp:check("2..") | 121 | return step(src, snk) |
| 109 | if not code then | ||
| 110 | data:close() | ||
| 111 | return nil, reply | ||
| 112 | end | ||
| 113 | end | ||
| 114 | local ret, err = step(src, snk) | ||
| 115 | if err then data:close() end | ||
| 116 | return ret, err | ||
| 117 | end | 122 | end |
| 118 | local sink = socket.sink("close-when-done", data) | 123 | local sink = socket.sink("close-when-done", self.data) |
| 119 | socket.try(ltn12.pump.all(sendt.source, sink, checkstep)) | 124 | -- transfer all data and check error |
| 120 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end | 125 | self.try(ltn12.pump.all(sendt.source, sink, checkstep)) |
| 126 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end | ||
| 127 | -- done with data connection | ||
| 128 | self.data:close() | ||
| 129 | self.data = nil | ||
| 121 | return 1 | 130 | return 1 |
| 122 | end | 131 | end |
| 123 | 132 | ||
| 124 | function metat.__index:receive(recvt) | 133 | function metat.__index:receive(recvt) |
| 125 | local data | 134 | self.try(self.pasvt or self.server, "need port or pasv first") |
| 126 | socket.try(self.pasvt or self.portt, "need port or pasv first") | 135 | if self.pasvt then self:pasvconnect() end |
| 127 | if self.pasvt then data = socket.try(pasv(self.pasvt)) end | ||
| 128 | local argument = recvt.argument or string.gsub(recvt.path, "^/", "") | 136 | local argument = recvt.argument or string.gsub(recvt.path, "^/", "") |
| 129 | if argument == "" then argument = nil end | 137 | if argument == "" then argument = nil end |
| 130 | local command = recvt.command or "retr" | 138 | local command = recvt.command or "retr" |
| 131 | socket.try(self.tp:command(command, argument)) | 139 | self.try(self.tp:command(command, argument)) |
| 132 | local code = socket.try(self.tp:check{"1..", "2.."}) | 140 | local code = self.try(self.tp:check{"1..", "2.."}) |
| 133 | if self.portt then data = socket.try(port(self.portt)) end | 141 | if not self.pasvt then self:portconnect() end |
| 134 | local source = socket.source("until-closed", data) | 142 | local source = socket.source("until-closed", self.data) |
| 135 | local step = recvt.step or ltn12.pump.step | 143 | local step = recvt.step or ltn12.pump.step |
| 136 | local checkstep = function(src, snk) | 144 | self.try(ltn12.pump.all(source, recvt.sink, step)) |
| 137 | local ret, err = step(src, snk) | 145 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end |
| 138 | if err then data:close() end | 146 | self.data:close() |
| 139 | return ret, err | 147 | self.data = nil |
| 140 | end | ||
| 141 | socket.try(ltn12.pump.all(source, recvt.sink, checkstep)) | ||
| 142 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end | ||
| 143 | return 1 | 148 | return 1 |
| 144 | end | 149 | end |
| 145 | 150 | ||
| 146 | function metat.__index:cwd(dir) | 151 | function metat.__index:cwd(dir) |
| 147 | socket.try(self.tp:command("cwd", dir)) | 152 | self.try(self.tp:command("cwd", dir)) |
| 148 | socket.try(self.tp:check(250)) | 153 | self.try(self.tp:check(250)) |
| 149 | return 1 | 154 | return 1 |
| 150 | end | 155 | end |
| 151 | 156 | ||
| 152 | function metat.__index:type(type) | 157 | function metat.__index:type(type) |
| 153 | socket.try(self.tp:command("type", type)) | 158 | self.try(self.tp:command("type", type)) |
| 154 | socket.try(self.tp:check(200)) | 159 | self.try(self.tp:check(200)) |
| 155 | return 1 | 160 | return 1 |
| 156 | end | 161 | end |
| 157 | 162 | ||
| 158 | function metat.__index:greet() | 163 | function metat.__index:greet() |
| 159 | local code = socket.try(self.tp:check{"1..", "2.."}) | 164 | local code = self.try(self.tp:check{"1..", "2.."}) |
| 160 | if string.find(code, "1..") then socket.try(self.tp:check("2..")) end | 165 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end |
| 161 | return 1 | 166 | return 1 |
| 162 | end | 167 | end |
| 163 | 168 | ||
| 164 | function metat.__index:quit() | 169 | function metat.__index:quit() |
| 165 | socket.try(self.tp:command("quit")) | 170 | self.try(self.tp:command("quit")) |
| 166 | socket.try(self.tp:check("2..")) | 171 | self.try(self.tp:check("2..")) |
| 167 | return 1 | 172 | return 1 |
| 168 | end | 173 | end |
| 169 | 174 | ||
| 170 | function metat.__index:close() | 175 | function metat.__index:close() |
| 171 | socket.try(self.tp:close()) | 176 | self.tp:close() |
| 177 | if self.data then self.data:close() end | ||
| 178 | if self.server then self.server:close() end | ||
| 179 | self.tp = nil | ||
| 180 | self.data = nil | ||
| 181 | self.server = nil | ||
| 172 | return 1 | 182 | return 1 |
| 173 | end | 183 | end |
| 174 | 184 | ||
| @@ -176,14 +186,14 @@ end | |||
| 176 | -- High level FTP API | 186 | -- High level FTP API |
| 177 | ----------------------------------------------------------------------------- | 187 | ----------------------------------------------------------------------------- |
| 178 | local function tput(putt) | 188 | local function tput(putt) |
| 179 | local con = open(putt.host, putt.port) | 189 | local f = open(putt.host, putt.port) |
| 180 | con:greet() | 190 | f:greet() |
| 181 | con:login(putt.user, putt.password) | 191 | f:login(putt.user, putt.password) |
| 182 | if putt.type then con:type(putt.type) end | 192 | if putt.type then f:type(putt.type) end |
| 183 | con:pasv() | 193 | f:pasv() |
| 184 | con:send(putt) | 194 | f:send(putt) |
| 185 | con:quit() | 195 | f:quit() |
| 186 | return con:close() | 196 | return f:close() |
| 187 | end | 197 | end |
| 188 | 198 | ||
| 189 | local default = { | 199 | local default = { |
| @@ -216,14 +226,14 @@ put = socket.protect(function(putt, body) | |||
| 216 | end) | 226 | end) |
| 217 | 227 | ||
| 218 | local function tget(gett) | 228 | local function tget(gett) |
| 219 | local con = open(gett.host, gett.port) | 229 | local f = open(gett.host, gett.port) |
| 220 | con:greet() | 230 | f:greet() |
| 221 | con:login(gett.user, gett.password) | 231 | f:login(gett.user, gett.password) |
| 222 | if gett.type then con:type(gett.type) end | 232 | if gett.type then f:type(gett.type) end |
| 223 | con:pasv() | 233 | f:pasv() |
| 224 | con:receive(gett) | 234 | f:receive(gett) |
| 225 | con:quit() | 235 | f:quit() |
| 226 | return con:close() | 236 | return f:close() |
| 227 | end | 237 | end |
| 228 | 238 | ||
| 229 | local function sget(u) | 239 | local function sget(u) |
