diff options
Diffstat (limited to '')
-rw-r--r-- | src/ftp.lua | 92 |
1 files changed, 68 insertions, 24 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index 917cd89..0ebc508 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
@@ -23,7 +23,7 @@ local _M = socket.ftp | |||
23 | -- timeout in seconds before the program gives up on a connection | 23 | -- timeout in seconds before the program gives up on a connection |
24 | _M.TIMEOUT = 60 | 24 | _M.TIMEOUT = 60 |
25 | -- default port for ftp service | 25 | -- default port for ftp service |
26 | _M.PORT = 21 | 26 | local PORT = 21 |
27 | -- this is the default anonymous password. used when no password is | 27 | -- this is the default anonymous password. used when no password is |
28 | -- provided in url. should be changed to your e-mail. | 28 | -- provided in url. should be changed to your e-mail. |
29 | _M.USER = "ftp" | 29 | _M.USER = "ftp" |
@@ -35,7 +35,7 @@ _M.PASSWORD = "anonymous@anonymous.org" | |||
35 | local metat = { __index = {} } | 35 | local metat = { __index = {} } |
36 | 36 | ||
37 | function _M.open(server, port, create) | 37 | function _M.open(server, port, create) |
38 | local tp = socket.try(tp.connect(server, port or _M.PORT, _M.TIMEOUT, create)) | 38 | local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) |
39 | local f = base.setmetatable({ tp = tp }, metat) | 39 | local f = base.setmetatable({ tp = tp }, metat) |
40 | -- make sure everything gets closed in an exception | 40 | -- make sure everything gets closed in an exception |
41 | f.try = socket.newtry(function() f:close() end) | 41 | f.try = socket.newtry(function() f:close() end) |
@@ -51,12 +51,12 @@ end | |||
51 | function metat.__index:pasvconnect() | 51 | function metat.__index:pasvconnect() |
52 | self.data = self.try(socket.tcp()) | 52 | self.data = self.try(socket.tcp()) |
53 | self.try(self.data:settimeout(_M.TIMEOUT)) | 53 | self.try(self.data:settimeout(_M.TIMEOUT)) |
54 | self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) | 54 | self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) |
55 | end | 55 | end |
56 | 56 | ||
57 | function metat.__index:login(user, password) | 57 | function metat.__index:login(user, password) |
58 | self.try(self.tp:command("user", user or _M.USER)) | 58 | self.try(self.tp:command("user", user or _M.USER)) |
59 | local code, reply = self.try(self.tp:check{"2..", 331}) | 59 | local code, _ = self.try(self.tp:check{"2..", 331}) |
60 | if code == 331 then | 60 | if code == 331 then |
61 | self.try(self.tp:command("pass", password or _M.PASSWORD)) | 61 | self.try(self.tp:command("pass", password or _M.PASSWORD)) |
62 | self.try(self.tp:check("2..")) | 62 | self.try(self.tp:check("2..")) |
@@ -66,37 +66,70 @@ end | |||
66 | 66 | ||
67 | function metat.__index:pasv() | 67 | function metat.__index:pasv() |
68 | self.try(self.tp:command("pasv")) | 68 | self.try(self.tp:command("pasv")) |
69 | local code, reply = self.try(self.tp:check("2..")) | 69 | local _, reply = self.try(self.tp:check("2..")) |
70 | 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+)" |
71 | 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)) |
72 | self.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) |
73 | self.pasvt = { | 73 | self.pasvt = { |
74 | ip = string.format("%d.%d.%d.%d", a, b, c, d), | 74 | address = string.format("%d.%d.%d.%d", a, b, c, d), |
75 | port = p1*256 + p2 | 75 | port = p1*256 + p2 |
76 | } | 76 | } |
77 | if self.server then | 77 | if self.server then |
78 | self.server:close() | 78 | self.server:close() |
79 | self.server = nil | 79 | self.server = nil |
80 | end | 80 | end |
81 | return self.pasvt.ip, self.pasvt.port | 81 | return self.pasvt.address, self.pasvt.port |
82 | end | 82 | end |
83 | 83 | ||
84 | function metat.__index:port(ip, port) | 84 | function metat.__index:epsv() |
85 | self.try(self.tp:command("epsv")) | ||
86 | local _, reply = self.try(self.tp:check("229")) | ||
87 | local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" | ||
88 | local _, _, _, port = string.match(reply, pattern) | ||
89 | self.try(port, "invalid epsv response") | ||
90 | self.pasvt = { | ||
91 | address = self.tp:getpeername(), | ||
92 | port = port | ||
93 | } | ||
94 | if self.server then | ||
95 | self.server:close() | ||
96 | self.server = nil | ||
97 | end | ||
98 | return self.pasvt.address, self.pasvt.port | ||
99 | end | ||
100 | |||
101 | |||
102 | function metat.__index:port(address, port) | ||
85 | self.pasvt = nil | 103 | self.pasvt = nil |
86 | if not ip then | 104 | if not address then |
87 | ip, port = self.try(self.tp:getcontrol():getsockname()) | 105 | address = self.try(self.tp:getsockname()) |
88 | self.server = self.try(socket.bind(ip, 0)) | 106 | self.server = self.try(socket.bind(address, 0)) |
89 | ip, port = self.try(self.server:getsockname()) | 107 | address, port = self.try(self.server:getsockname()) |
90 | self.try(self.server:settimeout(_M.TIMEOUT)) | 108 | self.try(self.server:settimeout(_M.TIMEOUT)) |
91 | end | 109 | end |
92 | local pl = math.mod(port, 256) | 110 | local pl = math.mod(port, 256) |
93 | local ph = (port - pl)/256 | 111 | local ph = (port - pl)/256 |
94 | local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | 112 | local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") |
95 | self.try(self.tp:command("port", arg)) | 113 | self.try(self.tp:command("port", arg)) |
96 | self.try(self.tp:check("2..")) | 114 | self.try(self.tp:check("2..")) |
97 | return 1 | 115 | return 1 |
98 | end | 116 | end |
99 | 117 | ||
118 | function metat.__index:eprt(family, address, port) | ||
119 | self.pasvt = nil | ||
120 | if not address then | ||
121 | address = self.try(self.tp:getsockname()) | ||
122 | self.server = self.try(socket.bind(address, 0)) | ||
123 | address, port = self.try(self.server:getsockname()) | ||
124 | self.try(self.server:settimeout(_M.TIMEOUT)) | ||
125 | end | ||
126 | local arg = string.format("|%s|%s|%d|", family, address, port) | ||
127 | self.try(self.tp:command("eprt", arg)) | ||
128 | self.try(self.tp:check("2..")) | ||
129 | return 1 | ||
130 | end | ||
131 | |||
132 | |||
100 | function metat.__index:send(sendt) | 133 | function metat.__index:send(sendt) |
101 | self.try(self.pasvt or self.server, "need port or pasv first") | 134 | self.try(self.pasvt or self.server, "need port or pasv first") |
102 | -- if there is a pasvt table, we already sent a PASV command | 135 | -- if there is a pasvt table, we already sent a PASV command |
@@ -109,13 +142,13 @@ function metat.__index:send(sendt) | |||
109 | local command = sendt.command or "stor" | 142 | local command = sendt.command or "stor" |
110 | -- send the transfer command and check the reply | 143 | -- send the transfer command and check the reply |
111 | self.try(self.tp:command(command, argument)) | 144 | self.try(self.tp:command(command, argument)) |
112 | local code, reply = self.try(self.tp:check{"2..", "1.."}) | 145 | local code, _ = self.try(self.tp:check{"2..", "1.."}) |
113 | -- if there is not a a pasvt table, then there is a server | 146 | -- if there is not a pasvt table, then there is a server |
114 | -- and we already sent a PORT command | 147 | -- and we already sent a PORT command |
115 | if not self.pasvt then self:portconnect() end | 148 | if not self.pasvt then self:portconnect() end |
116 | -- get the sink, source and step for the transfer | 149 | -- get the sink, source and step for the transfer |
117 | local step = sendt.step or ltn12.pump.step | 150 | local step = sendt.step or ltn12.pump.step |
118 | local readt = {self.tp.c} | 151 | local readt = { self.tp } |
119 | local checkstep = function(src, snk) | 152 | local checkstep = function(src, snk) |
120 | -- check status in control connection while downloading | 153 | -- check status in control connection while downloading |
121 | local readyt = socket.select(readt, nil, 0) | 154 | local readyt = socket.select(readt, nil, 0) |
@@ -207,7 +240,7 @@ local function tput(putt) | |||
207 | f:greet() | 240 | f:greet() |
208 | f:login(putt.user, putt.password) | 241 | f:login(putt.user, putt.password) |
209 | if putt.type then f:type(putt.type) end | 242 | if putt.type then f:type(putt.type) end |
210 | f:pasv() | 243 | f:epsv() |
211 | local sent = f:send(putt) | 244 | local sent = f:send(putt) |
212 | f:quit() | 245 | f:quit() |
213 | f:close() | 246 | f:close() |
@@ -219,7 +252,7 @@ local default = { | |||
219 | scheme = "ftp" | 252 | scheme = "ftp" |
220 | } | 253 | } |
221 | 254 | ||
222 | local function parse(u) | 255 | local function genericform(u) |
223 | local t = socket.try(url.parse(u, default)) | 256 | local t = socket.try(url.parse(u, default)) |
224 | socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") | 257 | socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") |
225 | socket.try(t.host, "missing hostname") | 258 | socket.try(t.host, "missing hostname") |
@@ -232,8 +265,10 @@ local function parse(u) | |||
232 | return t | 265 | return t |
233 | end | 266 | end |
234 | 267 | ||
268 | _M.genericform = genericform | ||
269 | |||
235 | local function sput(u, body) | 270 | local function sput(u, body) |
236 | local putt = parse(u) | 271 | local putt = genericform(u) |
237 | putt.source = ltn12.source.string(body) | 272 | putt.source = ltn12.source.string(body) |
238 | return tput(putt) | 273 | return tput(putt) |
239 | end | 274 | end |
@@ -250,14 +285,14 @@ local function tget(gett) | |||
250 | f:greet() | 285 | f:greet() |
251 | f:login(gett.user, gett.password) | 286 | f:login(gett.user, gett.password) |
252 | if gett.type then f:type(gett.type) end | 287 | if gett.type then f:type(gett.type) end |
253 | f:pasv() | 288 | f:epsv() |
254 | f:receive(gett) | 289 | f:receive(gett) |
255 | f:quit() | 290 | f:quit() |
256 | return f:close() | 291 | return f:close() |
257 | end | 292 | end |
258 | 293 | ||
259 | local function sget(u) | 294 | local function sget(u) |
260 | local gett = parse(u) | 295 | local gett = genericform(u) |
261 | local t = {} | 296 | local t = {} |
262 | gett.sink = ltn12.sink.table(t) | 297 | gett.sink = ltn12.sink.table(t) |
263 | tget(gett) | 298 | tget(gett) |
@@ -271,8 +306,17 @@ _M.command = socket.protect(function(cmdt) | |||
271 | local f = _M.open(cmdt.host, cmdt.port, cmdt.create) | 306 | local f = _M.open(cmdt.host, cmdt.port, cmdt.create) |
272 | f:greet() | 307 | f:greet() |
273 | f:login(cmdt.user, cmdt.password) | 308 | f:login(cmdt.user, cmdt.password) |
274 | f.try(f.tp:command(cmdt.command, cmdt.argument)) | 309 | if type(cmdt.command) == "table" then |
275 | if cmdt.check then f.try(f.tp:check(cmdt.check)) end | 310 | local argument = cmdt.argument or {} |
311 | local check = cmdt.check or {} | ||
312 | for i,cmd in ipairs(cmdt.command) do | ||
313 | f.try(f.tp:command(cmd, argument[i])) | ||
314 | if check[i] then f.try(f.tp:check(check[i])) end | ||
315 | end | ||
316 | else | ||
317 | f.try(f.tp:command(cmdt.command, cmdt.argument)) | ||
318 | if cmdt.check then f.try(f.tp:check(cmdt.check)) end | ||
319 | end | ||
276 | f:quit() | 320 | f:quit() |
277 | return f:close() | 321 | return f:close() |
278 | end) | 322 | end) |
@@ -282,4 +326,4 @@ _M.get = socket.protect(function(gett) | |||
282 | else return tget(gett) end | 326 | else return tget(gett) end |
283 | end) | 327 | end) |
284 | 328 | ||
285 | return _M \ No newline at end of file | 329 | return _M |