aboutsummaryrefslogtreecommitdiff
path: root/src/ftp.lua
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/ftp.lua92
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 26local 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"
35local metat = { __index = {} } 35local metat = { __index = {} }
36 36
37function _M.open(server, port, create) 37function _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
51function metat.__index:pasvconnect() 51function 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))
55end 55end
56 56
57function metat.__index:login(user, password) 57function 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
67function metat.__index:pasv() 67function 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
82end 82end
83 83
84function metat.__index:port(ip, port) 84function 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
99end
100
101
102function 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
98end 116end
99 117
118function 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
130end
131
132
100function metat.__index:send(sendt) 133function 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
222local function parse(u) 255local 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
233end 266end
234 267
268_M.genericform = genericform
269
235local function sput(u, body) 270local 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)
239end 274end
@@ -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()
257end 292end
258 293
259local function sget(u) 294local 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()
278end) 322end)
@@ -282,4 +326,4 @@ _M.get = socket.protect(function(gett)
282 else return tget(gett) end 326 else return tget(gett) end
283end) 327end)
284 328
285return _M \ No newline at end of file 329return _M