diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-06-18 08:02:09 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-06-18 08:02:09 +0000 |
commit | ac4aac0909da26befaaeb6b415f66cf35b6980e0 (patch) | |
tree | 3d3289e6192508484dcbefa10e2d862c5cc06d64 /src/ftp.lua | |
parent | 62799a416d2b29d8058331f3d8725fb67c75d261 (diff) | |
download | luasocket-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.lua | 182 |
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 | ||
33 | function open(server, port) | 33 | function 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) | ||
36 | end | 43 | end |
37 | 44 | ||
38 | local function port(portt) | 45 | function 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)) | ||
40 | end | 49 | end |
41 | 50 | ||
42 | local function pasv(pasvt) | 51 | function 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 | ||
47 | end | 55 | end |
48 | 56 | ||
49 | function metat.__index:login(user, password) | 57 | function 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 |
57 | end | 65 | end |
58 | 66 | ||
59 | function metat.__index:pasv() | 67 | function 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 |
74 | end | 82 | end |
75 | 83 | ||
76 | function metat.__index:port(ip, port) | 84 | function 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 |
92 | end | 98 | end |
93 | 99 | ||
94 | function metat.__index:send(sendt) | 100 | function 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 |
122 | end | 131 | end |
123 | 132 | ||
124 | function metat.__index:receive(recvt) | 133 | function 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 |
144 | end | 149 | end |
145 | 150 | ||
146 | function metat.__index:cwd(dir) | 151 | function 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 |
150 | end | 155 | end |
151 | 156 | ||
152 | function metat.__index:type(type) | 157 | function 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 |
156 | end | 161 | end |
157 | 162 | ||
158 | function metat.__index:greet() | 163 | function 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 |
162 | end | 167 | end |
163 | 168 | ||
164 | function metat.__index:quit() | 169 | function 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 |
168 | end | 173 | end |
169 | 174 | ||
170 | function metat.__index:close() | 175 | function 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 |
173 | end | 183 | end |
174 | 184 | ||
@@ -176,14 +186,14 @@ end | |||
176 | -- High level FTP API | 186 | -- High level FTP API |
177 | ----------------------------------------------------------------------------- | 187 | ----------------------------------------------------------------------------- |
178 | local function tput(putt) | 188 | local 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() |
187 | end | 197 | end |
188 | 198 | ||
189 | local default = { | 199 | local default = { |
@@ -216,14 +226,14 @@ put = socket.protect(function(putt, body) | |||
216 | end) | 226 | end) |
217 | 227 | ||
218 | local function tget(gett) | 228 | local 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() |
227 | end | 237 | end |
228 | 238 | ||
229 | local function sget(u) | 239 | local function sget(u) |