Mercurial > luasocket
comparison src/ftp.lua @ 0:4b915342e2a8
LuaSocket 2.0.2 + CMake build description.
author | Eric Wing <ewing . public |-at-| gmail . com> |
---|---|
date | Tue, 26 Aug 2008 18:40:01 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4b915342e2a8 |
---|---|
1 ----------------------------------------------------------------------------- | |
2 -- FTP support for the Lua language | |
3 -- LuaSocket toolkit. | |
4 -- Author: Diego Nehab | |
5 -- RCS ID: $Id: ftp.lua,v 1.45 2007/07/11 19:25:47 diego Exp $ | |
6 ----------------------------------------------------------------------------- | |
7 | |
8 ----------------------------------------------------------------------------- | |
9 -- Declare module and import dependencies | |
10 ----------------------------------------------------------------------------- | |
11 local base = _G | |
12 local table = require("table") | |
13 local string = require("string") | |
14 local math = require("math") | |
15 local socket = require("socket") | |
16 local url = require("socket.url") | |
17 local tp = require("socket.tp") | |
18 local ltn12 = require("ltn12") | |
19 module("socket.ftp") | |
20 | |
21 ----------------------------------------------------------------------------- | |
22 -- Program constants | |
23 ----------------------------------------------------------------------------- | |
24 -- timeout in seconds before the program gives up on a connection | |
25 TIMEOUT = 60 | |
26 -- default port for ftp service | |
27 PORT = 21 | |
28 -- this is the default anonymous password. used when no password is | |
29 -- provided in url. should be changed to your e-mail. | |
30 USER = "ftp" | |
31 PASSWORD = "anonymous@anonymous.org" | |
32 | |
33 ----------------------------------------------------------------------------- | |
34 -- Low level FTP API | |
35 ----------------------------------------------------------------------------- | |
36 local metat = { __index = {} } | |
37 | |
38 function open(server, port, create) | |
39 local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create)) | |
40 local f = base.setmetatable({ tp = tp }, metat) | |
41 -- make sure everything gets closed in an exception | |
42 f.try = socket.newtry(function() f:close() end) | |
43 return f | |
44 end | |
45 | |
46 function metat.__index:portconnect() | |
47 self.try(self.server:settimeout(TIMEOUT)) | |
48 self.data = self.try(self.server:accept()) | |
49 self.try(self.data:settimeout(TIMEOUT)) | |
50 end | |
51 | |
52 function metat.__index:pasvconnect() | |
53 self.data = self.try(socket.tcp()) | |
54 self.try(self.data:settimeout(TIMEOUT)) | |
55 self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) | |
56 end | |
57 | |
58 function metat.__index:login(user, password) | |
59 self.try(self.tp:command("user", user or USER)) | |
60 local code, reply = self.try(self.tp:check{"2..", 331}) | |
61 if code == 331 then | |
62 self.try(self.tp:command("pass", password or PASSWORD)) | |
63 self.try(self.tp:check("2..")) | |
64 end | |
65 return 1 | |
66 end | |
67 | |
68 function metat.__index:pasv() | |
69 self.try(self.tp:command("pasv")) | |
70 local code, reply = self.try(self.tp:check("2..")) | |
71 local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" | |
72 local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) | |
73 self.try(a and b and c and d and p1 and p2, reply) | |
74 self.pasvt = { | |
75 ip = string.format("%d.%d.%d.%d", a, b, c, d), | |
76 port = p1*256 + p2 | |
77 } | |
78 if self.server then | |
79 self.server:close() | |
80 self.server = nil | |
81 end | |
82 return self.pasvt.ip, self.pasvt.port | |
83 end | |
84 | |
85 function metat.__index:port(ip, port) | |
86 self.pasvt = nil | |
87 if not ip then | |
88 ip, port = self.try(self.tp:getcontrol():getsockname()) | |
89 self.server = self.try(socket.bind(ip, 0)) | |
90 ip, port = self.try(self.server:getsockname()) | |
91 self.try(self.server:settimeout(TIMEOUT)) | |
92 end | |
93 local pl = math.mod(port, 256) | |
94 local ph = (port - pl)/256 | |
95 local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") | |
96 self.try(self.tp:command("port", arg)) | |
97 self.try(self.tp:check("2..")) | |
98 return 1 | |
99 end | |
100 | |
101 function metat.__index:send(sendt) | |
102 self.try(self.pasvt or self.server, "need port or pasv first") | |
103 -- if there is a pasvt table, we already sent a PASV command | |
104 -- we just get the data connection into self.data | |
105 if self.pasvt then self:pasvconnect() end | |
106 -- get the transfer argument and command | |
107 local argument = sendt.argument or | |
108 url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) | |
109 if argument == "" then argument = nil end | |
110 local command = sendt.command or "stor" | |
111 -- send the transfer command and check the reply | |
112 self.try(self.tp:command(command, argument)) | |
113 local code, reply = self.try(self.tp:check{"2..", "1.."}) | |
114 -- if there is not a a pasvt table, then there is a server | |
115 -- and we already sent a PORT command | |
116 if not self.pasvt then self:portconnect() end | |
117 -- get the sink, source and step for the transfer | |
118 local step = sendt.step or ltn12.pump.step | |
119 local readt = {self.tp.c} | |
120 local checkstep = function(src, snk) | |
121 -- check status in control connection while downloading | |
122 local readyt = socket.select(readt, nil, 0) | |
123 if readyt[tp] then code = self.try(self.tp:check("2..")) end | |
124 return step(src, snk) | |
125 end | |
126 local sink = socket.sink("close-when-done", self.data) | |
127 -- transfer all data and check error | |
128 self.try(ltn12.pump.all(sendt.source, sink, checkstep)) | |
129 if string.find(code, "1..") then self.try(self.tp:check("2..")) end | |
130 -- done with data connection | |
131 self.data:close() | |
132 -- find out how many bytes were sent | |
133 local sent = socket.skip(1, self.data:getstats()) | |
134 self.data = nil | |
135 return sent | |
136 end | |
137 | |
138 function metat.__index:receive(recvt) | |
139 self.try(self.pasvt or self.server, "need port or pasv first") | |
140 if self.pasvt then self:pasvconnect() end | |
141 local argument = recvt.argument or | |
142 url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) | |
143 if argument == "" then argument = nil end | |
144 local command = recvt.command or "retr" | |
145 self.try(self.tp:command(command, argument)) | |
146 local code = self.try(self.tp:check{"1..", "2.."}) | |
147 if not self.pasvt then self:portconnect() end | |
148 local source = socket.source("until-closed", self.data) | |
149 local step = recvt.step or ltn12.pump.step | |
150 self.try(ltn12.pump.all(source, recvt.sink, step)) | |
151 if string.find(code, "1..") then self.try(self.tp:check("2..")) end | |
152 self.data:close() | |
153 self.data = nil | |
154 return 1 | |
155 end | |
156 | |
157 function metat.__index:cwd(dir) | |
158 self.try(self.tp:command("cwd", dir)) | |
159 self.try(self.tp:check(250)) | |
160 return 1 | |
161 end | |
162 | |
163 function metat.__index:type(type) | |
164 self.try(self.tp:command("type", type)) | |
165 self.try(self.tp:check(200)) | |
166 return 1 | |
167 end | |
168 | |
169 function metat.__index:greet() | |
170 local code = self.try(self.tp:check{"1..", "2.."}) | |
171 if string.find(code, "1..") then self.try(self.tp:check("2..")) end | |
172 return 1 | |
173 end | |
174 | |
175 function metat.__index:quit() | |
176 self.try(self.tp:command("quit")) | |
177 self.try(self.tp:check("2..")) | |
178 return 1 | |
179 end | |
180 | |
181 function metat.__index:close() | |
182 if self.data then self.data:close() end | |
183 if self.server then self.server:close() end | |
184 return self.tp:close() | |
185 end | |
186 | |
187 ----------------------------------------------------------------------------- | |
188 -- High level FTP API | |
189 ----------------------------------------------------------------------------- | |
190 local function override(t) | |
191 if t.url then | |
192 local u = url.parse(t.url) | |
193 for i,v in base.pairs(t) do | |
194 u[i] = v | |
195 end | |
196 return u | |
197 else return t end | |
198 end | |
199 | |
200 local function tput(putt) | |
201 putt = override(putt) | |
202 socket.try(putt.host, "missing hostname") | |
203 local f = open(putt.host, putt.port, putt.create) | |
204 f:greet() | |
205 f:login(putt.user, putt.password) | |
206 if putt.type then f:type(putt.type) end | |
207 f:pasv() | |
208 local sent = f:send(putt) | |
209 f:quit() | |
210 f:close() | |
211 return sent | |
212 end | |
213 | |
214 local default = { | |
215 path = "/", | |
216 scheme = "ftp" | |
217 } | |
218 | |
219 local function parse(u) | |
220 local t = socket.try(url.parse(u, default)) | |
221 socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") | |
222 socket.try(t.host, "missing hostname") | |
223 local pat = "^type=(.)$" | |
224 if t.params then | |
225 t.type = socket.skip(2, string.find(t.params, pat)) | |
226 socket.try(t.type == "a" or t.type == "i", | |
227 "invalid type '" .. t.type .. "'") | |
228 end | |
229 return t | |
230 end | |
231 | |
232 local function sput(u, body) | |
233 local putt = parse(u) | |
234 putt.source = ltn12.source.string(body) | |
235 return tput(putt) | |
236 end | |
237 | |
238 put = socket.protect(function(putt, body) | |
239 if base.type(putt) == "string" then return sput(putt, body) | |
240 else return tput(putt) end | |
241 end) | |
242 | |
243 local function tget(gett) | |
244 gett = override(gett) | |
245 socket.try(gett.host, "missing hostname") | |
246 local f = open(gett.host, gett.port, gett.create) | |
247 f:greet() | |
248 f:login(gett.user, gett.password) | |
249 if gett.type then f:type(gett.type) end | |
250 f:pasv() | |
251 f:receive(gett) | |
252 f:quit() | |
253 return f:close() | |
254 end | |
255 | |
256 local function sget(u) | |
257 local gett = parse(u) | |
258 local t = {} | |
259 gett.sink = ltn12.sink.table(t) | |
260 tget(gett) | |
261 return table.concat(t) | |
262 end | |
263 | |
264 command = socket.protect(function(cmdt) | |
265 cmdt = override(cmdt) | |
266 socket.try(cmdt.host, "missing hostname") | |
267 socket.try(cmdt.command, "missing command") | |
268 local f = open(cmdt.host, cmdt.port, cmdt.create) | |
269 f:greet() | |
270 f:login(cmdt.user, cmdt.password) | |
271 f.try(f.tp:command(cmdt.command, cmdt.argument)) | |
272 if cmdt.check then f.try(f.tp:check(cmdt.check)) end | |
273 f:quit() | |
274 return f:close() | |
275 end) | |
276 | |
277 get = socket.protect(function(gett) | |
278 if base.type(gett) == "string" then return sget(gett) | |
279 else return tget(gett) end | |
280 end) | |
281 |