aboutsummaryrefslogtreecommitdiff
path: root/src/ftp.lua
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2004-06-18 08:02:09 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2004-06-18 08:02:09 +0000
commitac4aac0909da26befaaeb6b415f66cf35b6980e0 (patch)
tree3d3289e6192508484dcbefa10e2d862c5cc06d64 /src/ftp.lua
parent62799a416d2b29d8058331f3d8725fb67c75d261 (diff)
downloadluasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.gz
luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.bz2
luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.zip
Implemented safe exceptions. This looks preeety good.
Diffstat (limited to 'src/ftp.lua')
-rw-r--r--src/ftp.lua182
1 files changed, 96 insertions, 86 deletions
diff --git a/src/ftp.lua b/src/ftp.lua
index 3868158..ad02c72 100644
--- a/src/ftp.lua
+++ b/src/ftp.lua
@@ -32,143 +32,153 @@ local metat = { __index = {} }
32 32
33function open(server, port) 33function open(server, port)
34 local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT)) 34 local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT))
35 return setmetatable({tp = tp}, metat) 35 local f = { tp = tp }
36 -- make sure everything gets closed in an exception
37 f.try = socket.newtry(function()
38 tp:close()
39 if f.data then f.data:close() end
40 if f.server then f.server:close() end
41 end)
42 return setmetatable(f, metat)
36end 43end
37 44
38local function port(portt) 45function metat.__index:portconnect()
39 return portt.server:accept() 46 self.try(self.server:settimeout(TIMEOUT))
47 self.data = self.try(self.server:accept())
48 self.try(self.data:settimeout(TIMEOUT))
40end 49end
41 50
42local function pasv(pasvt) 51function metat.__index:pasvconnect()
43 local data = socket.try(socket.tcp()) 52 self.data = self.try(socket.tcp())
44 socket.try(data:settimeout(TIMEOUT)) 53 self.try(self.data:settimeout(TIMEOUT))
45 socket.try(data:connect(pasvt.ip, pasvt.port)) 54 self.try(self.data:connect(self.pasvt.ip, self.pasvt.port))
46 return data
47end 55end
48 56
49function metat.__index:login(user, password) 57function metat.__index:login(user, password)
50 socket.try(self.tp:command("user", user or USER)) 58 self.try(self.tp:command("user", user or USER))
51 local code, reply = socket.try(self.tp:check{"2..", 331}) 59 local code, reply = self.try(self.tp:check{"2..", 331})
52 if code == 331 then 60 if code == 331 then
53 socket.try(self.tp:command("pass", password or PASSWORD)) 61 self.try(self.tp:command("pass", password or PASSWORD))
54 socket.try(self.tp:check("2..")) 62 self.try(self.tp:check("2.."))
55 end 63 end
56 return 1 64 return 1
57end 65end
58 66
59function metat.__index:pasv() 67function metat.__index:pasv()
60 socket.try(self.tp:command("pasv")) 68 self.try(self.tp:command("pasv"))
61 local code, reply = socket.try(self.tp:check("2..")) 69 local code, reply = self.try(self.tp:check("2.."))
62 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+)"
63 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))
64 socket.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)
65 self.pasvt = { 73 self.pasvt = {
66 ip = string.format("%d.%d.%d.%d", a, b, c, d), 74 ip = string.format("%d.%d.%d.%d", a, b, c, d),
67 port = p1*256 + p2 75 port = p1*256 + p2
68 } 76 }
69 if self.portt then 77 if self.server then
70 self.portt.server:close() 78 self.server:close()
71 self.portt = nil 79 self.server = nil
72 end 80 end
73 return self.pasvt.ip, self.pasvt.port 81 return self.pasvt.ip, self.pasvt.port
74end 82end
75 83
76function metat.__index:port(ip, port) 84function metat.__index:port(ip, port)
77 self.pasvt = nil 85 self.pasvt = nil
78 local server
79 if not ip then 86 if not ip then
80 ip, port = socket.try(self.tp:getcontrol():getsockname()) 87 ip, port = self.try(self.tp:getcontrol():getsockname())
81 server = socket.try(socket.bind(ip, 0)) 88 self.server = self.try(socket.bind(ip, 0))
82 ip, port = socket.try(server:getsockname()) 89 ip, port = self.try(self.server:getsockname())
83 socket.try(server:settimeout(TIMEOUT)) 90 self.try(server:settimeout(TIMEOUT))
84 end 91 end
85 local pl = math.mod(port, 256) 92 local pl = math.mod(port, 256)
86 local ph = (port - pl)/256 93 local ph = (port - pl)/256
87 local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") 94 local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
88 socket.try(self.tp:command("port", arg)) 95 self.try(self.tp:command("port", arg))
89 socket.try(self.tp:check("2..")) 96 self.try(self.tp:check("2.."))
90 self.portt = server and {ip = ip, port = port, server = server}
91 return 1 97 return 1
92end 98end
93 99
94function metat.__index:send(sendt) 100function metat.__index:send(sendt)
95 local data 101 self.try(self.pasvt or self.server, "need port or pasv first")
96 socket.try(self.pasvt or self.portt, "need port or pasv first") 102 -- if there is a pasvt table, we already sent a PASV command
97 if self.pasvt then data = socket.try(pasv(self.pasvt)) end 103 -- we just get the data connection into self.data
104 if self.pasvt then self:pasvconnect() end
105 -- get the transfer argument and command
98 local argument = sendt.argument or string.gsub(sendt.path, "^/", "") 106 local argument = sendt.argument or string.gsub(sendt.path, "^/", "")
99 if argument == "" then argument = nil end 107 if argument == "" then argument = nil end
100 local command = sendt.command or "stor" 108 local command = sendt.command or "stor"
101 socket.try(self.tp:command(command, argument)) 109 -- send the transfer command and check the reply
102 local code, reply = socket.try(self.tp:check{"2..", "1.."}) 110 self.try(self.tp:command(command, argument))
103 if self.portt then data = socket.try(port(self.portt)) end 111 local code, reply = self.try(self.tp:check{"2..", "1.."})
112 -- if there is not a a pasvt table, then there is a server
113 -- and we already sent a PORT command
114 if not self.pasvt then self:portconnect() end
115 -- get the sink, source and step for the transfer
104 local step = sendt.step or ltn12.pump.step 116 local step = sendt.step or ltn12.pump.step
105 local checkstep = function(src, snk) 117 local checkstep = function(src, snk)
118 -- check status in control connection while downloading
106 local readyt = socket.select(readt, nil, 0) 119 local readyt = socket.select(readt, nil, 0)
107 if readyt[tp] then 120 if readyt[tp] then self.try(self.tp:check("2..")) end
108 code, reply = self.tp:check("2..") 121 return step(src, snk)
109 if not code then
110 data:close()
111 return nil, reply
112 end
113 end
114 local ret, err = step(src, snk)
115 if err then data:close() end
116 return ret, err
117 end 122 end
118 local sink = socket.sink("close-when-done", data) 123 local sink = socket.sink("close-when-done", self.data)
119 socket.try(ltn12.pump.all(sendt.source, sink, checkstep)) 124 -- transfer all data and check error
120 if string.find(code, "1..") then socket.try(self.tp:check("2..")) end 125 self.try(ltn12.pump.all(sendt.source, sink, checkstep))
126 if string.find(code, "1..") then self.try(self.tp:check("2..")) end
127 -- done with data connection
128 self.data:close()
129 self.data = nil
121 return 1 130 return 1
122end 131end
123 132
124function metat.__index:receive(recvt) 133function metat.__index:receive(recvt)
125 local data 134 self.try(self.pasvt or self.server, "need port or pasv first")
126 socket.try(self.pasvt or self.portt, "need port or pasv first") 135 if self.pasvt then self:pasvconnect() end
127 if self.pasvt then data = socket.try(pasv(self.pasvt)) end
128 local argument = recvt.argument or string.gsub(recvt.path, "^/", "") 136 local argument = recvt.argument or string.gsub(recvt.path, "^/", "")
129 if argument == "" then argument = nil end 137 if argument == "" then argument = nil end
130 local command = recvt.command or "retr" 138 local command = recvt.command or "retr"
131 socket.try(self.tp:command(command, argument)) 139 self.try(self.tp:command(command, argument))
132 local code = socket.try(self.tp:check{"1..", "2.."}) 140 local code = self.try(self.tp:check{"1..", "2.."})
133 if self.portt then data = socket.try(port(self.portt)) end 141 if not self.pasvt then self:portconnect() end
134 local source = socket.source("until-closed", data) 142 local source = socket.source("until-closed", self.data)
135 local step = recvt.step or ltn12.pump.step 143 local step = recvt.step or ltn12.pump.step
136 local checkstep = function(src, snk) 144 self.try(ltn12.pump.all(source, recvt.sink, step))
137 local ret, err = step(src, snk) 145 if string.find(code, "1..") then self.try(self.tp:check("2..")) end
138 if err then data:close() end 146 self.data:close()
139 return ret, err 147 self.data = nil
140 end
141 socket.try(ltn12.pump.all(source, recvt.sink, checkstep))
142 if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
143 return 1 148 return 1
144end 149end
145 150
146function metat.__index:cwd(dir) 151function metat.__index:cwd(dir)
147 socket.try(self.tp:command("cwd", dir)) 152 self.try(self.tp:command("cwd", dir))
148 socket.try(self.tp:check(250)) 153 self.try(self.tp:check(250))
149 return 1 154 return 1
150end 155end
151 156
152function metat.__index:type(type) 157function metat.__index:type(type)
153 socket.try(self.tp:command("type", type)) 158 self.try(self.tp:command("type", type))
154 socket.try(self.tp:check(200)) 159 self.try(self.tp:check(200))
155 return 1 160 return 1
156end 161end
157 162
158function metat.__index:greet() 163function metat.__index:greet()
159 local code = socket.try(self.tp:check{"1..", "2.."}) 164 local code = self.try(self.tp:check{"1..", "2.."})
160 if string.find(code, "1..") then socket.try(self.tp:check("2..")) end 165 if string.find(code, "1..") then self.try(self.tp:check("2..")) end
161 return 1 166 return 1
162end 167end
163 168
164function metat.__index:quit() 169function metat.__index:quit()
165 socket.try(self.tp:command("quit")) 170 self.try(self.tp:command("quit"))
166 socket.try(self.tp:check("2..")) 171 self.try(self.tp:check("2.."))
167 return 1 172 return 1
168end 173end
169 174
170function metat.__index:close() 175function metat.__index:close()
171 socket.try(self.tp:close()) 176 self.tp:close()
177 if self.data then self.data:close() end
178 if self.server then self.server:close() end
179 self.tp = nil
180 self.data = nil
181 self.server = nil
172 return 1 182 return 1
173end 183end
174 184
@@ -176,14 +186,14 @@ end
176-- High level FTP API 186-- High level FTP API
177----------------------------------------------------------------------------- 187-----------------------------------------------------------------------------
178local function tput(putt) 188local function tput(putt)
179 local con = open(putt.host, putt.port) 189 local f = open(putt.host, putt.port)
180 con:greet() 190 f:greet()
181 con:login(putt.user, putt.password) 191 f:login(putt.user, putt.password)
182 if putt.type then con:type(putt.type) end 192 if putt.type then f:type(putt.type) end
183 con:pasv() 193 f:pasv()
184 con:send(putt) 194 f:send(putt)
185 con:quit() 195 f:quit()
186 return con:close() 196 return f:close()
187end 197end
188 198
189local default = { 199local default = {
@@ -216,14 +226,14 @@ put = socket.protect(function(putt, body)
216end) 226end)
217 227
218local function tget(gett) 228local function tget(gett)
219 local con = open(gett.host, gett.port) 229 local f = open(gett.host, gett.port)
220 con:greet() 230 f:greet()
221 con:login(gett.user, gett.password) 231 f:login(gett.user, gett.password)
222 if gett.type then con:type(gett.type) end 232 if gett.type then f:type(gett.type) end
223 con:pasv() 233 f:pasv()
224 con:receive(gett) 234 f:receive(gett)
225 con:quit() 235 f:quit()
226 return con:close() 236 return f:close()
227end 237end
228 238
229local function sget(u) 239local function sget(u)