aboutsummaryrefslogtreecommitdiff
path: root/vendor/luasocket/src/ftp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/luasocket/src/ftp.lua')
-rw-r--r--vendor/luasocket/src/ftp.lua329
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-----------------------------------------------------------------------------
10local base = _G
11local table = require("table")
12local string = require("string")
13local math = require("math")
14local socket = require("socket")
15local url = require("socket.url")
16local tp = require("socket.tp")
17local ltn12 = require("ltn12")
18socket.ftp = {}
19local _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
26local 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-----------------------------------------------------------------------------
35local metat = { __index = {} }
36
37function _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
43end
44
45function 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))
49end
50
51function 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))
55end
56
57function 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
65end
66
67function 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
82end
83
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)
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
116end
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
133function 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
168end
169
170function 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
191end
192
193function metat.__index:cwd(dir)
194 self.try(self.tp:command("cwd", dir))
195 self.try(self.tp:check(250))
196 return 1
197end
198
199function metat.__index:type(type)
200 self.try(self.tp:command("type", type))
201 self.try(self.tp:check(200))
202 return 1
203end
204
205function 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
209end
210
211function metat.__index:quit()
212 self.try(self.tp:command("quit"))
213 self.try(self.tp:check("2.."))
214 return 1
215end
216
217function 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()
221end
222
223-----------------------------------------------------------------------------
224-- High level FTP API
225-----------------------------------------------------------------------------
226local 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
234end
235
236local 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
248end
249
250local default = {
251 path = "/",
252 scheme = "ftp"
253}
254
255local 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
266end
267
268_M.genericform = genericform
269
270local function sput(u, body)
271 local putt = genericform(u)
272 putt.source = ltn12.source.string(body)
273 return tput(putt)
274end
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
279end)
280
281local 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()
292end
293
294local 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)
300end
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()
322end)
323
324_M.get = socket.protect(function(gett)
325 if base.type(gett) == "string" then return sget(gett)
326 else return tget(gett) end
327end)
328
329return _M