diff options
Diffstat (limited to 'vendor/luasocket/src/ftp.lua')
-rw-r--r-- | vendor/luasocket/src/ftp.lua | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/vendor/luasocket/src/ftp.lua b/vendor/luasocket/src/ftp.lua new file mode 100644 index 00000000..0ebc5086 --- /dev/null +++ b/vendor/luasocket/src/ftp.lua | |||
@@ -0,0 +1,329 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- FTP support for the Lua language | ||
3 | -- LuaSocket toolkit. | ||
4 | -- Author: Diego Nehab | ||
5 | ----------------------------------------------------------------------------- | ||
6 | |||
7 | ----------------------------------------------------------------------------- | ||
8 | -- Declare module and import dependencies | ||
9 | ----------------------------------------------------------------------------- | ||
10 | local base = _G | ||
11 | local table = require("table") | ||
12 | local string = require("string") | ||
13 | local math = require("math") | ||
14 | local socket = require("socket") | ||
15 | local url = require("socket.url") | ||
16 | local tp = require("socket.tp") | ||
17 | local ltn12 = require("ltn12") | ||
18 | socket.ftp = {} | ||
19 | local _M = socket.ftp | ||
20 | ----------------------------------------------------------------------------- | ||
21 | -- Program constants | ||
22 | ----------------------------------------------------------------------------- | ||
23 | -- timeout in seconds before the program gives up on a connection | ||
24 | _M.TIMEOUT = 60 | ||
25 | -- default port for ftp service | ||
26 | local PORT = 21 | ||
27 | -- this is the default anonymous password. used when no password is | ||
28 | -- provided in url. should be changed to your e-mail. | ||
29 | _M.USER = "ftp" | ||
30 | _M.PASSWORD = "anonymous@anonymous.org" | ||
31 | |||
32 | ----------------------------------------------------------------------------- | ||
33 | -- Low level FTP API | ||
34 | ----------------------------------------------------------------------------- | ||
35 | local metat = { __index = {} } | ||
36 | |||
37 | function _M.open(server, port, create) | ||
38 | local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) | ||
39 | local f = base.setmetatable({ tp = tp }, metat) | ||
40 | -- make sure everything gets closed in an exception | ||
41 | f.try = socket.newtry(function() f:close() end) | ||
42 | return f | ||
43 | end | ||
44 | |||
45 | function metat.__index:portconnect() | ||
46 | self.try(self.server:settimeout(_M.TIMEOUT)) | ||
47 | self.data = self.try(self.server:accept()) | ||
48 | self.try(self.data:settimeout(_M.TIMEOUT)) | ||
49 | end | ||
50 | |||
51 | function metat.__index:pasvconnect() | ||
52 | self.data = self.try(socket.tcp()) | ||
53 | self.try(self.data:settimeout(_M.TIMEOUT)) | ||
54 | self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) | ||
55 | end | ||
56 | |||
57 | function metat.__index:login(user, password) | ||
58 | self.try(self.tp:command("user", user or _M.USER)) | ||
59 | local code, _ = self.try(self.tp:check{"2..", 331}) | ||
60 | if code == 331 then | ||
61 | self.try(self.tp:command("pass", password or _M.PASSWORD)) | ||
62 | self.try(self.tp:check("2..")) | ||
63 | end | ||
64 | return 1 | ||
65 | end | ||
66 | |||
67 | function metat.__index:pasv() | ||
68 | self.try(self.tp:command("pasv")) | ||
69 | local _, reply = self.try(self.tp:check("2..")) | ||
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)) | ||
72 | self.try(a and b and c and d and p1 and p2, reply) | ||
73 | self.pasvt = { | ||
74 | address = string.format("%d.%d.%d.%d", a, b, c, d), | ||
75 | port = p1*256 + p2 | ||
76 | } | ||
77 | if self.server then | ||
78 | self.server:close() | ||
79 | self.server = nil | ||
80 | end | ||
81 | return self.pasvt.address, self.pasvt.port | ||
82 | end | ||
83 | |||
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) | ||
103 | self.pasvt = nil | ||
104 | if not address then | ||
105 | address = self.try(self.tp:getsockname()) | ||
106 | self.server = self.try(socket.bind(address, 0)) | ||
107 | address, port = self.try(self.server:getsockname()) | ||
108 | self.try(self.server:settimeout(_M.TIMEOUT)) | ||
109 | end | ||
110 | local pl = math.mod(port, 256) | ||
111 | local ph = (port - pl)/256 | ||
112 | local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") | ||
113 | self.try(self.tp:command("port", arg)) | ||
114 | self.try(self.tp:check("2..")) | ||
115 | return 1 | ||
116 | end | ||
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 | |||
133 | function metat.__index:send(sendt) | ||
134 | self.try(self.pasvt or self.server, "need port or pasv first") | ||
135 | -- if there is a pasvt table, we already sent a PASV command | ||
136 | -- we just get the data connection into self.data | ||
137 | if self.pasvt then self:pasvconnect() end | ||
138 | -- get the transfer argument and command | ||
139 | local argument = sendt.argument or | ||
140 | url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) | ||
141 | if argument == "" then argument = nil end | ||
142 | local command = sendt.command or "stor" | ||
143 | -- send the transfer command and check the reply | ||
144 | self.try(self.tp:command(command, argument)) | ||
145 | local code, _ = self.try(self.tp:check{"2..", "1.."}) | ||
146 | -- if there is not a pasvt table, then there is a server | ||
147 | -- and we already sent a PORT command | ||
148 | if not self.pasvt then self:portconnect() end | ||
149 | -- get the sink, source and step for the transfer | ||
150 | local step = sendt.step or ltn12.pump.step | ||
151 | local readt = { self.tp } | ||
152 | local checkstep = function(src, snk) | ||
153 | -- check status in control connection while downloading | ||
154 | local readyt = socket.select(readt, nil, 0) | ||
155 | if readyt[tp] then code = self.try(self.tp:check("2..")) end | ||
156 | return step(src, snk) | ||
157 | end | ||
158 | local sink = socket.sink("close-when-done", self.data) | ||
159 | -- transfer all data and check error | ||
160 | self.try(ltn12.pump.all(sendt.source, sink, checkstep)) | ||
161 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end | ||
162 | -- done with data connection | ||
163 | self.data:close() | ||
164 | -- find out how many bytes were sent | ||
165 | local sent = socket.skip(1, self.data:getstats()) | ||
166 | self.data = nil | ||
167 | return sent | ||
168 | end | ||
169 | |||
170 | function metat.__index:receive(recvt) | ||
171 | self.try(self.pasvt or self.server, "need port or pasv first") | ||
172 | if self.pasvt then self:pasvconnect() end | ||
173 | local argument = recvt.argument or | ||
174 | url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) | ||
175 | if argument == "" then argument = nil end | ||
176 | local command = recvt.command or "retr" | ||
177 | self.try(self.tp:command(command, argument)) | ||
178 | local code,reply = self.try(self.tp:check{"1..", "2.."}) | ||
179 | if (code >= 200) and (code <= 299) then | ||
180 | recvt.sink(reply) | ||
181 | return 1 | ||
182 | end | ||
183 | if not self.pasvt then self:portconnect() end | ||
184 | local source = socket.source("until-closed", self.data) | ||
185 | local step = recvt.step or ltn12.pump.step | ||
186 | self.try(ltn12.pump.all(source, recvt.sink, step)) | ||
187 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end | ||
188 | self.data:close() | ||
189 | self.data = nil | ||
190 | return 1 | ||
191 | end | ||
192 | |||
193 | function metat.__index:cwd(dir) | ||
194 | self.try(self.tp:command("cwd", dir)) | ||
195 | self.try(self.tp:check(250)) | ||
196 | return 1 | ||
197 | end | ||
198 | |||
199 | function metat.__index:type(type) | ||
200 | self.try(self.tp:command("type", type)) | ||
201 | self.try(self.tp:check(200)) | ||
202 | return 1 | ||
203 | end | ||
204 | |||
205 | function metat.__index:greet() | ||
206 | local code = self.try(self.tp:check{"1..", "2.."}) | ||
207 | if string.find(code, "1..") then self.try(self.tp:check("2..")) end | ||
208 | return 1 | ||
209 | end | ||
210 | |||
211 | function metat.__index:quit() | ||
212 | self.try(self.tp:command("quit")) | ||
213 | self.try(self.tp:check("2..")) | ||
214 | return 1 | ||
215 | end | ||
216 | |||
217 | function metat.__index:close() | ||
218 | if self.data then self.data:close() end | ||
219 | if self.server then self.server:close() end | ||
220 | return self.tp:close() | ||
221 | end | ||
222 | |||
223 | ----------------------------------------------------------------------------- | ||
224 | -- High level FTP API | ||
225 | ----------------------------------------------------------------------------- | ||
226 | local function override(t) | ||
227 | if t.url then | ||
228 | local u = url.parse(t.url) | ||
229 | for i,v in base.pairs(t) do | ||
230 | u[i] = v | ||
231 | end | ||
232 | return u | ||
233 | else return t end | ||
234 | end | ||
235 | |||
236 | local function tput(putt) | ||
237 | putt = override(putt) | ||
238 | socket.try(putt.host, "missing hostname") | ||
239 | local f = _M.open(putt.host, putt.port, putt.create) | ||
240 | f:greet() | ||
241 | f:login(putt.user, putt.password) | ||
242 | if putt.type then f:type(putt.type) end | ||
243 | f:epsv() | ||
244 | local sent = f:send(putt) | ||
245 | f:quit() | ||
246 | f:close() | ||
247 | return sent | ||
248 | end | ||
249 | |||
250 | local default = { | ||
251 | path = "/", | ||
252 | scheme = "ftp" | ||
253 | } | ||
254 | |||
255 | local function genericform(u) | ||
256 | local t = socket.try(url.parse(u, default)) | ||
257 | socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") | ||
258 | socket.try(t.host, "missing hostname") | ||
259 | local pat = "^type=(.)$" | ||
260 | if t.params then | ||
261 | t.type = socket.skip(2, string.find(t.params, pat)) | ||
262 | socket.try(t.type == "a" or t.type == "i", | ||
263 | "invalid type '" .. t.type .. "'") | ||
264 | end | ||
265 | return t | ||
266 | end | ||
267 | |||
268 | _M.genericform = genericform | ||
269 | |||
270 | local function sput(u, body) | ||
271 | local putt = genericform(u) | ||
272 | putt.source = ltn12.source.string(body) | ||
273 | return tput(putt) | ||
274 | end | ||
275 | |||
276 | _M.put = socket.protect(function(putt, body) | ||
277 | if base.type(putt) == "string" then return sput(putt, body) | ||
278 | else return tput(putt) end | ||
279 | end) | ||
280 | |||
281 | local function tget(gett) | ||
282 | gett = override(gett) | ||
283 | socket.try(gett.host, "missing hostname") | ||
284 | local f = _M.open(gett.host, gett.port, gett.create) | ||
285 | f:greet() | ||
286 | f:login(gett.user, gett.password) | ||
287 | if gett.type then f:type(gett.type) end | ||
288 | f:epsv() | ||
289 | f:receive(gett) | ||
290 | f:quit() | ||
291 | return f:close() | ||
292 | end | ||
293 | |||
294 | local function sget(u) | ||
295 | local gett = genericform(u) | ||
296 | local t = {} | ||
297 | gett.sink = ltn12.sink.table(t) | ||
298 | tget(gett) | ||
299 | return table.concat(t) | ||
300 | end | ||
301 | |||
302 | _M.command = socket.protect(function(cmdt) | ||
303 | cmdt = override(cmdt) | ||
304 | socket.try(cmdt.host, "missing hostname") | ||
305 | socket.try(cmdt.command, "missing command") | ||
306 | local f = _M.open(cmdt.host, cmdt.port, cmdt.create) | ||
307 | f:greet() | ||
308 | f:login(cmdt.user, cmdt.password) | ||
309 | if type(cmdt.command) == "table" then | ||
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 | ||
320 | f:quit() | ||
321 | return f:close() | ||
322 | end) | ||
323 | |||
324 | _M.get = socket.protect(function(gett) | ||
325 | if base.type(gett) == "string" then return sget(gett) | ||
326 | else return tget(gett) end | ||
327 | end) | ||
328 | |||
329 | return _M | ||