1
2
3
4local setmetatable , type , next = setmetatable , type , next
5local find , format , gsub , match = string . find , string . format , string . gsub , string . match
6local concat = table . concat
7local mod = math . mod
8
9local socket = socket or require ( " socket " )
10local url = socket . url or require ( " socket.url " )
11local tp = socket . tp or require ( " socket.tp " )
12local ltn12 = ltn12 or require ( " ltn12 " )
13
14local tcpsocket = socket . tcp
15local trysocket = socket . try
16local skipsocket = socket . skip
17local sinksocket = socket . sink
18local selectsocket = socket . select
19local bindsocket = socket . bind
20local newtrysocket = socket . newtry
21local sourcesocket = socket . source
22local protectsocket = socket . protect
23
24local parseurl = url . parse
25local unescapeurl = url . unescape
26
27local pumpall = ltn12 . pump . all
28local pumpstep = ltn12 . pump . step
29local sourcestring = ltn12 . source . string
30local sinktable = ltn12 . sink . table
31
32local ftp = {
33 TIMEOUT = 60 ,
34 USER = " ftp " ,
35 PASSWORD = " anonymous@anonymous.org " ,
36}
37
38socket . ftp = ftp
39
40local PORT = 21
41
42local methods = { }
43local mt = { __index = methods }
44
45function ftp . open ( server , port , create )
46 local tp = trysocket ( tp . connect ( server , port or PORT , ftp . TIMEOUT , create ) )
47 local f = setmetatable ( { tp = tp } , metat )
48 f . try = newtrysocket ( function ( ) f : close ( ) end )
49 return f
50end
51
52function methods . portconnect ( self )
53 local try = self . try
54 local server = self . server
55 try ( server : settimeout ( ftp . TIMEOUT ) )
56 self . data = try ( server : accept ( ) )
57 try ( self . data : settimeout ( ftp . TIMEOUT ) )
58end
59
60function methods . pasvconnect ( self )
61 local try = self . try
62 self . data = try ( tcpsocket ( ) )
63 self ( self . data : settimeout ( ftp . TIMEOUT ) )
64 self ( self . data : connect ( self . pasvt . address , self . pasvt . port ) )
65end
66
67function methods . login ( self , user , password )
68 local try = self . try
69 local tp = self . tp
70 try ( tp : command ( " user " , user or ftp . USER ) )
71 local code , reply = try ( tp : check { " 2.. " , 331 } )
72 if code = = 331 then
73 try ( tp : command ( " pass " , password or ftp . PASSWORD ) )
74 try ( tp : check ( " 2.. " ) )
75 end
76 return 1
77end
78
79function methods . pasv ( self )
80 local try = self . try
81 local tp = self . tp
82 try ( tp : command ( " pasv " ) )
83 local code , reply = try ( self . tp : check ( " 2.. " ) )
84 local pattern = " (%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+) "
85 local a , b , c , d , p1 , p2 = skipsocket ( 2 , find ( reply , pattern ) )
86 try ( a and b and c and d and p1 and p2 , reply )
87 local address = format ( " %d.%d.%d.%d " , a , b , c , d )
88 local port = p1 * 256 + p2
89 local server = self . server
90 self . pasvt = {
91 address = address ,
92 port = port ,
93 }
94 if server then
95 server : close ( )
96 self . server = nil
97 end
98 return address , port
99end
100
101function methods . epsv ( self )
102 local try = self . try
103 local tp = self . tp
104 try ( tp : command ( " epsv " ) )
105 local code , reply = try ( tp : check ( " 229 " ) )
106 local pattern = " %((.)(.-)%1(.-)%1(.-)%1%) "
107 local d , prt , address , port = match ( reply , pattern )
108 try ( port , " invalid epsv response " )
109 local address = tp : getpeername ( )
110 local server = self . server
111 self . pasvt = {
112 address = address ,
113 port = port ,
114 }
115 if self . server then
116 server : close ( )
117 self . server = nil
118 end
119 return address , port
120end
121
122function methods . port ( self , address , port )
123 local try = self . try
124 local tp = self . tp
125 self . pasvt = nil
126 if not address then
127 address , port = try ( tp : getsockname ( ) )
128 self . server = try ( bindsocket ( address , 0 ) )
129 address , port = try ( self . server : getsockname ( ) )
130 try ( self . server : settimeout ( ftp . TIMEOUT ) )
131 end
132 local pl = mod ( port , 256 )
133 local ph = ( port - pl ) / 256
134 local arg = gsub ( format ( " %s,%d,%d " , address , ph , pl ) , " %. " , " , " )
135 try ( tp : command ( " port " , arg ) )
136 try ( tp : check ( " 2.. " ) )
137 return 1
138end
139
140function methods . eprt ( self , family , address , port )
141 local try = self . try
142 local tp = self . tp
143 self . pasvt = nil
144 if not address then
145 address , port = try ( tp : getsockname ( ) )
146 self . server = try ( bindsocket ( address , 0 ) )
147 address , port = try ( self . server : getsockname ( ) )
148 try ( self . server : settimeout ( ftp . TIMEOUT ) )
149 end
150 local arg = format ( " |%s|%s|%d| " , family , address , port )
151 try ( tp : command ( " eprt " , arg ) )
152 try ( tp : check ( " 2.. " ) )
153 return 1
154end
155
156function methods . send ( self , sendt )
157 local try = self . try
158 local tp = self . tp
159
160 try ( self . pasvt or self . server , " need port or pasv first " )
161 if self . pasvt then
162 self : pasvconnect ( )
163 end
164 local argument = sendt . argument or unescapeurl ( gsub ( sendt . path or " " , " ^[/\\] " , " " ) )
165 if argument = = " " then
166 argument = nil
167 end
168 local command = sendt . command or " stor "
169 try ( tp : command ( command , argument ) )
170 local code , reply = try ( tp : check { " 2.. " , " 1.. " } )
171 if not self . pasvt then
172 self : portconnect ( )
173 end
174 local step = sendt . step or pumpstep
175 local readt = { tp }
176 local checkstep = function ( src , snk )
177 local readyt = selectsocket ( readt , nil , 0 )
178 if readyt [ tp ] then
179 code = try ( tp : check ( " 2.. " ) )
180 end
181 return step ( src , snk )
182 end
183 local sink = sinksocket ( " close-when-done " , self . data )
184 try ( pumpall ( sendt . source , sink , checkstep ) )
185 if find ( code , " 1.. " ) then
186 try ( tp : check ( " 2.. " ) )
187 end
188 self . data : close ( )
189 local sent = skipsocket ( 1 , self . data : getstats ( ) )
190 self . data = nil
191 return sent
192end
193
194function methods . receive ( self , recvt )
195 local try = self . try
196 local tp = self . tp
197 try ( self . pasvt or self . server , " need port or pasv first " )
198 if self . pasvt then self : pasvconnect ( ) end
199 local argument = recvt . argument or unescapeurl ( gsub ( recvt . path or " " , " ^[/\\] " , " " ) )
200 if argument = = " " then
201 argument = nil
202 end
203 local command = recvt . command or " retr "
204 try ( tp : command ( command , argument ) )
205 local code , reply = try ( tp : check { " 1.. " , " 2.. " } )
206 if code > = 200 and code < = 299 then
207 recvt . sink ( reply )
208 return 1
209 end
210 if not self . pasvt then
211 self : portconnect ( )
212 end
213 local source = sourcesocket ( " until-closed " , self . data )
214 local step = recvt . step or pumpstep
215 try ( pumpall ( source , recvt . sink , step ) )
216 if find ( code , " 1.. " ) then
217 try ( tp : check ( " 2.. " ) )
218 end
219 self . data : close ( )
220 self . data = nil
221 return 1
222end
223
224function methods . cwd ( self , dir )
225 local try = self . try
226 local tp = self . tp
227 try ( tp : command ( " cwd " , dir ) )
228 try ( tp : check ( 250 ) )
229 return 1
230end
231
232function methods . type ( self , typ )
233 local try = self . try
234 local tp = self . tp
235 try ( tp : command ( " type " , typ ) )
236 try ( tp : check ( 200 ) )
237 return 1
238end
239
240function methods . greet ( self )
241 local try = self . try
242 local tp = self . tp
243 local code = try ( tp : check { " 1.. " , " 2.. " } )
244 if find ( code , " 1.. " ) then
245 try ( tp : check ( " 2.. " ) )
246 end
247 return 1
248end
249
250function methods . quit ( self )
251 local try = self . try
252 try ( self . tp : command ( " quit " ) )
253 try ( self . tp : check ( " 2.. " ) )
254 return 1
255end
256
257function methods . close ( self )
258 local data = self . data
259 if data then
260 data : close ( )
261 end
262 local server = self . server
263 if server then
264 server : close ( )
265 end
266 local tp = self . tp
267 if tp then
268 tp : close ( )
269 end
270end
271
272local function override ( t )
273 if t . url then
274 local u = parseurl ( t . url )
275 for k , v in next , t do
276 u [ k ] = v
277 end
278 return u
279 else
280 return t
281 end
282end
283
284local function tput ( putt )
285 putt = override ( putt )
286 local host = putt . host
287 trysocket ( host , " missing hostname " )
288 local f = ftp . open ( host , putt . port , putt . create )
289 f : greet ( )
290 f : login ( putt . user , putt . password )
291 local typ = putt . type
292 if typ then
293 f : type ( typ )
294 end
295 f : epsv ( )
296 local sent = f : send ( putt )
297 f : quit ( )
298 f : close ( )
299 return sent
300end
301
302local default = {
303 path = " / " ,
304 scheme = " ftp " ,
305}
306
307local function genericform ( u )
308 local t = trysocket ( parseurl ( u , default ) )
309 trysocket ( t . scheme = = " ftp " , " wrong scheme ' " . . t . scheme . . " ' " )
310 trysocket ( t . host , " missing hostname " )
311 local pat = " ^type=(.)$ "
312 if t . params then
313 local typ = skipsocket ( 2 , find ( t . params , pat ) )
314 t . type = typ
315 trysocket ( typ = = " a " or typ = = " i " , " invalid type ' " . . typ . . " ' " )
316 end
317 return t
318end
319
320ftp . genericform = genericform
321
322local function sput ( u , body )
323 local putt = genericform ( u )
324 putt . source = sourcestring ( body )
325 return tput ( putt )
326end
327
328ftp . put = protectsocket ( function ( putt , body )
329 if type ( putt ) = = " string " then
330 return sput ( putt , body )
331 else
332 return tput ( putt )
333 end
334end )
335
336local function tget ( gett )
337 gett = override ( gett )
338 local host = gett . host
339 trysocket ( host , " missing hostname " )
340 local f = ftp . open ( host , gett . port , gett . create )
341 f : greet ( )
342 f : login ( gett . user , gett . password )
343 if gett . type then
344 f : type ( gett . type )
345 end
346 f : epsv ( )
347 f : receive ( gett )
348 f : quit ( )
349 return f : close ( )
350end
351
352local function sget ( u )
353 local gett = genericform ( u )
354 local t = { }
355 gett . sink = sinktable ( t )
356 tget ( gett )
357 return concat ( t )
358end
359
360ftp . command = protectsocket ( function ( cmdt )
361 cmdt = override ( cmdt )
362 local command = cmdt . command
363 local argument = cmdt . argument
364 local check = cmdt . check
365 local host = cmdt . host
366 trysocket ( host , " missing hostname " )
367 trysocket ( command , " missing command " )
368 local f = ftp . open ( host , cmdt . port , cmdt . create )
369 local try = f . try
370 local tp = f . tp
371 f : greet ( )
372 f : login ( cmdt . user , cmdt . password )
373 if type ( command ) = = " table " then
374 local argument = argument or { }
375 for i = 1 , # command do
376 local cmd = command [ i ]
377 try ( tp : command ( cmd , argument [ i ] ) )
378 if check and check [ i ] then
379 try ( tp : check ( check [ i ] ) )
380 end
381 end
382 else
383 try ( tp : command ( command , argument ) )
384 if check then
385 try ( tp : check ( check ) )
386 end
387 end
388 f : quit ( )
389 return f : close ( )
390end )
391
392ftp . get = protectsocket ( function ( gett )
393 if type ( gett ) = = " string " then
394 return sget ( gett )
395 else
396 return tget ( gett )
397 end
398end )
399
400package . loaded [ " socket.ftp " ] = ftp
401
402return ftp
403 |