aboutsummaryrefslogtreecommitdiff
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
parent62799a416d2b29d8058331f3d8725fb67c75d261 (diff)
downloadluasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.gz
luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.bz2
luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.zip
Implemented safe exceptions. This looks preeety good.
-rw-r--r--TODO6
-rw-r--r--doc/reference.html3
-rw-r--r--doc/socket.html66
-rw-r--r--doc/tcp.html28
-rw-r--r--src/except.c32
-rw-r--r--src/ftp.lua182
-rw-r--r--src/http.lua55
-rw-r--r--src/smtp.lua64
-rw-r--r--src/socket.lua4
-rw-r--r--test/ftptest.lua10
-rw-r--r--test/httptest.lua1
11 files changed, 273 insertions, 178 deletions
diff --git a/TODO b/TODO
index 860a676..7a45569 100644
--- a/TODO
+++ b/TODO
@@ -1,4 +1,5 @@
1 1
2
2ajeitar os README.* 3ajeitar os README.*
3ajeitar as referencias a RFCS e LTNS em todos os arquivos. 4ajeitar as referencias a RFCS e LTNS em todos os arquivos.
4 5
@@ -8,8 +9,9 @@ check garbage collection in test*.lua
8 9
9 10
10manual 11manual
11 socket.skip 12 socket.newtry
12 send return convention changed. 13 *socket.skip
14 *send return convention changed.
13 * compatibility: select sets are associative 15 * compatibility: select sets are associative
14 * add socket.connect and socket.bind to the manual 16 * add socket.connect and socket.bind to the manual
15 * add shutdown 17 * add shutdown
diff --git a/doc/reference.html b/doc/reference.html
index 607958b..f130d7b 100644
--- a/doc/reference.html
+++ b/doc/reference.html
@@ -143,8 +143,9 @@
143<a href="socket.html#protect">protect</a>, 143<a href="socket.html#protect">protect</a>,
144<a href="socket.html#select">select</a>, 144<a href="socket.html#select">select</a>,
145<a href="socket.html#sink">sink</a>, 145<a href="socket.html#sink">sink</a>,
146<a href="socket.html#source">source</a>, 146<a href="socket.html#skip">skip</a>,
147<a href="socket.html#sleep">sleep</a>, 147<a href="socket.html#sleep">sleep</a>,
148<a href="socket.html#source">source</a>,
148<a href="socket.html#time">time</a>, 149<a href="socket.html#time">time</a>,
149<a href="tcp.html#tcp">tcp</a>, 150<a href="tcp.html#tcp">tcp</a>,
150<a href="socket.html#try">try</a>, 151<a href="socket.html#try">try</a>,
diff --git a/doc/socket.html b/doc/socket.html
index d7739f8..06296a3 100644
--- a/doc/socket.html
+++ b/doc/socket.html
@@ -169,6 +169,49 @@ socket, leaving it open when done.
169The function returns a sink with the appropriate behavior. 169The function returns a sink with the appropriate behavior.
170</p> 170</p>
171 171
172<!-- skip ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
173
174<p class=name id=skip>
175socket.<b>skip(</b>d [, ret<sub>1</sub>, ret<sub>2</sub> ... ret<sub>N</sub>]<b>)</b>
176</p>
177
178<p class=description>
179Drops a number of arguments and returns the remaining.
180</p>
181
182<p class=parameters>
183<tt>D</tt> is the number of arguments to drop. <tt>Ret<sub>1</sub></tt> to
184<tt>ret<sub>N</sub></tt> are the arguments.
185</p>
186
187<p class=return>
188The function returns <tt>ret<sub>d+1</sub></tt> to <tt>ret<sub>N</sub></tt>.
189</p>
190
191<p class=note>
192Note: This function is useful to avoid creation of dummy variables:
193</p>
194
195<pre class=example>
196-- get the status code and separator from SMTP server reply
197local code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)"))
198</pre>
199
200<!-- sleep ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
201
202<p class=name id=sleep>
203socket.<b>sleep(</b>time<b>)</b>
204</p>
205
206<p class=description>
207Freezes the program execution during a given amount of time.
208</p>
209
210<p class=parameters>
211<tt>Time</tt> is the number of seconds to sleep for.
212The function truncates <tt>time</tt> to the nearest integer.
213</p>
214
172<!-- source +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 215<!-- source +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
173 216
174<p class=name id=source> 217<p class=name id=source>
@@ -201,6 +244,27 @@ side closes the connection.
201The function returns a source with the appropriate behavior. 244The function returns a source with the appropriate behavior.
202</p> 245</p>
203 246
247<!-- time ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
248
249<p class=name id=time>
250socket.<b>time()</b>
251</p>
252
253<p class=description>
254Returns the time in seconds, relative to the origin of the
255universe. Only time differences are meaninful.
256</p>
257
258<p class=return>
259The function returns the time as a number.
260</p>
261
262<pre class=example>
263t = socket.time()
264-- do stuff
265print(socket.time() - t .. " seconds elapsed")
266</pre>
267
204<!-- try ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 268<!-- try ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
205 269
206<p class=name id=try> 270<p class=name id=try>
@@ -212,7 +276,7 @@ Throws an exception in case of error.
212</p> 276</p>
213 277
214<p class=parameters> 278<p class=parameters>
215<tt>Ret</tt><sub>1</sub> to <tt>ret</tt><sub>N</sub> can be arbitrary 279<tt>Ret<sub>1</sub></tt> to <tt>ret<sub>N</sub></tt> can be arbitrary
216arguments, but are usually the return values of a function call 280arguments, but are usually the return values of a function call
217nested with <tt>try</tt>. 281nested with <tt>try</tt>.
218</p> 282</p>
diff --git a/doc/tcp.html b/doc/tcp.html
index 781ec6f..cd417a5 100644
--- a/doc/tcp.html
+++ b/doc/tcp.html
@@ -277,9 +277,10 @@ the transmission.
277<p class=note> 277<p class=note>
278<b>Important note</b>: This function was changed <em>severely</em>. It used 278<b>Important note</b>: This function was changed <em>severely</em>. It used
279to support multiple patterns (but I have never seen this feature used) and 279to support multiple patterns (but I have never seen this feature used) and
280partial results used to be returned in the same way as successful results. 280now it doesn't anymore. Partial results used to be returned in the same
281This last feature violated the idea that all functions should return 281way as successful results. This last feature violated the idea that all
282<tt><b>nil</b></tt> on error. Thus the change. 282functions should return <tt><b>nil</b></tt> on error. Thus it was changed
283too.
283</p> 284</p>
284 285
285<!-- send +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 286<!-- send +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
@@ -300,20 +301,25 @@ result to LuaSocket instead of passing several independent strings.
300</p> 301</p>
301 302
302<p class=return> 303<p class=return>
303The method returns the number of bytes accepted by the transport layer, 304If successful, the method returns the number of bytes accepted by
304followed by an error code. The error code is <b><tt>nil</tt></b> if the operation 305the transport layer. In case of error, the method returns
305completed with no errors, the string '<tt>closed</tt>' in case 306<b><tt>nil</tt></b>, followed by an error message, followed by the
307partial number of bytes accepted by the transport layer.
308The error message can be '<tt>closed</tt>' in case
306the connection was closed before the transmission was completed or the 309the connection was closed before the transmission was completed or the
307string '<tt>timeout</tt>' in case there was a timeout during the 310string '<tt>timeout</tt>' in case there was a timeout during the
308operation. 311operation.
309</p> 312</p>
310 313
311<p class=note> 314<p class=note>
312Note: The return values for the <tt>send</tt> method have been changed in 315<b>Important note</b>:
313LuaSocket 2.0! In previous versions, the method returned only the 316The return values for the <tt>send</tt> method have been changed in
314error message. Since returning <b><tt>nil</tt></b> in case of success goes 317LuaSocket 2.0 alpha <b>and again</b> in the beta (sorry)!
315against all other LuaSocket methods and functions, the 318In previous versions, the method returned only the
316<tt>send</tt> method been changed for the sake of uniformity. 319error message. Since returning <b><tt>nil</tt></b> in case of success was
320nonsense, in alpha the first return value became the number of bytes sent.
321Alas, it wasn't returning <tt><b>nil</b></tt> in case of
322error. So it was changed again in beta.
317</p> 323</p>
318 324
319<!-- setoption ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 325<!-- setoption ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
diff --git a/src/except.c b/src/except.c
index 0482e1c..53a65ac 100644
--- a/src/except.c
+++ b/src/except.c
@@ -14,17 +14,20 @@
14\*=========================================================================*/ 14\*=========================================================================*/
15static int global_try(lua_State *L); 15static int global_try(lua_State *L);
16static int global_protect(lua_State *L); 16static int global_protect(lua_State *L);
17static int global_newtry(lua_State *L);
17static int protected(lua_State *L); 18static int protected(lua_State *L);
19static int finalize(lua_State *L);
18 20
19/* except functions */ 21/* except functions */
20static luaL_reg func[] = { 22static luaL_reg func[] = {
21 {"try", global_try}, 23 {"try", global_try},
22 {"protect", global_protect}, 24 {"newtry", global_newtry},
23 {NULL, NULL} 25 {"protect", global_protect},
26 {NULL, NULL}
24}; 27};
25 28
26/*-------------------------------------------------------------------------*\ 29/*-------------------------------------------------------------------------*\
27* Exception handling: try method 30* Try method
28\*-------------------------------------------------------------------------*/ 31\*-------------------------------------------------------------------------*/
29static int global_try(lua_State *L) { 32static int global_try(lua_State *L) {
30 if (lua_isnil(L, 1) || (lua_isboolean(L, 1) && !lua_toboolean(L, 1))) { 33 if (lua_isnil(L, 1) || (lua_isboolean(L, 1) && !lua_toboolean(L, 1))) {
@@ -35,7 +38,25 @@ static int global_try(lua_State *L) {
35} 38}
36 39
37/*-------------------------------------------------------------------------*\ 40/*-------------------------------------------------------------------------*\
38* Exception handling: protect factory 41* Finalizer factory
42\*-------------------------------------------------------------------------*/
43static int finalize(lua_State *L) {
44 if (lua_isnil(L, 1) || (lua_isboolean(L, 1) && !lua_toboolean(L, 1))) {
45 lua_pushvalue(L, lua_upvalueindex(1));
46 lua_pcall(L, 0, 0, 0);
47 lua_settop(L, 2);
48 lua_error(L);
49 return 0;
50 } else return lua_gettop(L);
51}
52
53static int global_newtry(lua_State *L) {
54 lua_pushcclosure(L, finalize, 1);
55 return 1;
56}
57
58/*-------------------------------------------------------------------------*\
59* Protect factory
39\*-------------------------------------------------------------------------*/ 60\*-------------------------------------------------------------------------*/
40static int protected(lua_State *L) { 61static int protected(lua_State *L) {
41 lua_pushvalue(L, lua_upvalueindex(1)); 62 lua_pushvalue(L, lua_upvalueindex(1));
@@ -48,7 +69,6 @@ static int protected(lua_State *L) {
48} 69}
49 70
50static int global_protect(lua_State *L) { 71static int global_protect(lua_State *L) {
51 lua_insert(L, 1);
52 lua_pushcclosure(L, protected, 1); 72 lua_pushcclosure(L, protected, 1);
53 return 1; 73 return 1;
54} 74}
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)
diff --git a/src/http.lua b/src/http.lua
index 3bd4d6a..d8889e1 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -31,24 +31,25 @@ BLOCKSIZE = 2048
31local metat = { __index = {} } 31local metat = { __index = {} }
32 32
33function open(host, port) 33function open(host, port)
34 local con = socket.try(socket.tcp()) 34 local c = socket.try(socket.tcp())
35 socket.try(con:settimeout(TIMEOUT)) 35 -- make sure the connection gets closed on exception
36 port = port or PORT 36 local try = socket.newtry(function() c:close() end)
37 socket.try(con:connect(host, port)) 37 try(c:settimeout(TIMEOUT))
38 return setmetatable({ con = con }, metat) 38 try(c:connect(host, port or PORT))
39 return setmetatable({ c = c, try = try }, metat)
39end 40end
40 41
41function metat.__index:sendrequestline(method, uri) 42function metat.__index:sendrequestline(method, uri)
42 local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) 43 local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
43 return socket.try(self.con:send(reqline)) 44 return self.try(self.c:send(reqline))
44end 45end
45 46
46function metat.__index:sendheaders(headers) 47function metat.__index:sendheaders(headers)
47 for i, v in pairs(headers) do 48 for i, v in pairs(headers) do
48 socket.try(self.con:send(i .. ": " .. v .. "\r\n")) 49 self.try(self.c:send(i .. ": " .. v .. "\r\n"))
49 end 50 end
50 -- mark end of request headers 51 -- mark end of request headers
51 socket.try(self.con:send("\r\n")) 52 self.try(self.c:send("\r\n"))
52 return 1 53 return 1
53end 54end
54 55
@@ -59,32 +60,32 @@ function metat.__index:sendbody(headers, source, step)
59 local mode 60 local mode
60 if headers["content-length"] then mode = "keep-open" 61 if headers["content-length"] then mode = "keep-open"
61 else mode = "http-chunked" end 62 else mode = "http-chunked" end
62 return socket.try(ltn12.pump.all(source, socket.sink(mode, self.con), step)) 63 return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step))
63end 64end
64 65
65function metat.__index:receivestatusline() 66function metat.__index:receivestatusline()
66 local status = socket.try(self.con:receive()) 67 local status = self.try(self.c:receive())
67 local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) 68 local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
68 return socket.try(tonumber(code), status) 69 return self.try(tonumber(code), status)
69end 70end
70 71
71function metat.__index:receiveheaders() 72function metat.__index:receiveheaders()
72 local line, name, value 73 local line, name, value
73 local headers = {} 74 local headers = {}
74 -- get first line 75 -- get first line
75 line = socket.try(self.con:receive()) 76 line = self.try(self.c:receive())
76 -- headers go until a blank line is found 77 -- headers go until a blank line is found
77 while line ~= "" do 78 while line ~= "" do
78 -- get field-name and value 79 -- get field-name and value
79 name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) 80 name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
80 socket.try(name and value, "malformed reponse headers") 81 self.try(name and value, "malformed reponse headers")
81 name = string.lower(name) 82 name = string.lower(name)
82 -- get next line (value might be folded) 83 -- get next line (value might be folded)
83 line = socket.try(self.con:receive()) 84 line = self.try(self.c:receive())
84 -- unfold any folded values 85 -- unfold any folded values
85 while string.find(line, "^%s") do 86 while string.find(line, "^%s") do
86 value = value .. line 87 value = value .. line
87 line = socket.try(self.con:receive()) 88 line = self.try(self.c:receive())
88 end 89 end
89 -- save pair in table 90 -- save pair in table
90 if headers[name] then headers[name] = headers[name] .. ", " .. value 91 if headers[name] then headers[name] = headers[name] .. ", " .. value
@@ -102,12 +103,12 @@ function metat.__index:receivebody(headers, sink, step)
102 if TE and TE ~= "identity" then mode = "http-chunked" 103 if TE and TE ~= "identity" then mode = "http-chunked"
103 elseif tonumber(headers["content-length"]) then mode = "by-length" 104 elseif tonumber(headers["content-length"]) then mode = "by-length"
104 else mode = "default" end 105 else mode = "default" end
105 return socket.try(ltn12.pump.all(socket.source(mode, self.con, length), 106 return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
106 sink, step)) 107 sink, step))
107end 108end
108 109
109function metat.__index:close() 110function metat.__index:close()
110 return self.con:close() 111 return self.c:close()
111end 112end
112 113
113----------------------------------------------------------------------------- 114-----------------------------------------------------------------------------
@@ -204,23 +205,23 @@ end
204 205
205function trequest(reqt) 206function trequest(reqt)
206 reqt = adjustrequest(reqt) 207 reqt = adjustrequest(reqt)
207 local con = open(reqt.host, reqt.port) 208 local h = open(reqt.host, reqt.port)
208 con:sendrequestline(reqt.method, reqt.uri) 209 h:sendrequestline(reqt.method, reqt.uri)
209 con:sendheaders(reqt.headers) 210 h:sendheaders(reqt.headers)
210 con:sendbody(reqt.headers, reqt.source, reqt.step) 211 h:sendbody(reqt.headers, reqt.source, reqt.step)
211 local code, headers, status 212 local code, headers, status
212 code, status = con:receivestatusline() 213 code, status = h:receivestatusline()
213 headers = con:receiveheaders() 214 headers = h:receiveheaders()
214 if shouldredirect(reqt, code) then 215 if shouldredirect(reqt, code) then
215 con:close() 216 h:close()
216 return tredirect(reqt, headers) 217 return tredirect(reqt, headers)
217 elseif shouldauthorize(reqt, code) then 218 elseif shouldauthorize(reqt, code) then
218 con:close() 219 h:close()
219 return tauthorize(reqt) 220 return tauthorize(reqt)
220 elseif shouldreceivebody(reqt, code) then 221 elseif shouldreceivebody(reqt, code) then
221 con:receivebody(headers, reqt.sink, reqt.step) 222 h:receivebody(headers, reqt.sink, reqt.step)
222 end 223 end
223 con:close() 224 h:close()
224 return 1, code, headers, status 225 return 1, code, headers, status
225end 226end
226 227
diff --git a/src/smtp.lua b/src/smtp.lua
index 1708053..d6357d2 100644
--- a/src/smtp.lua
+++ b/src/smtp.lua
@@ -31,51 +31,51 @@ ZONE = "-0000"
31local metat = { __index = {} } 31local metat = { __index = {} }
32 32
33function metat.__index:greet(domain) 33function metat.__index:greet(domain)
34 socket.try(self.tp:check("2..")) 34 self.try(self.tp:check("2.."))
35 socket.try(self.tp:command("EHLO", domain or DOMAIN)) 35 self.try(self.tp:command("EHLO", domain or DOMAIN))
36 return socket.skip(1, socket.try(self.tp:check("2.."))) 36 return socket.skip(1, self.try(self.tp:check("2..")))
37end 37end
38 38
39function metat.__index:mail(from) 39function metat.__index:mail(from)
40 socket.try(self.tp:command("MAIL", "FROM:" .. from)) 40 self.try(self.tp:command("MAIL", "FROM:" .. from))
41 return socket.try(self.tp:check("2..")) 41 return self.try(self.tp:check("2.."))
42end 42end
43 43
44function metat.__index:rcpt(to) 44function metat.__index:rcpt(to)
45 socket.try(self.tp:command("RCPT", "TO:" .. to)) 45 self.try(self.tp:command("RCPT", "TO:" .. to))
46 return socket.try(self.tp:check("2..")) 46 return self.try(self.tp:check("2.."))
47end 47end
48 48
49function metat.__index:data(src, step) 49function metat.__index:data(src, step)
50 socket.try(self.tp:command("DATA")) 50 self.try(self.tp:command("DATA"))
51 socket.try(self.tp:check("3..")) 51 self.try(self.tp:check("3.."))
52 socket.try(self.tp:source(src, step)) 52 self.try(self.tp:source(src, step))
53 socket.try(self.tp:send("\r\n.\r\n")) 53 self.try(self.tp:send("\r\n.\r\n"))
54 return socket.try(self.tp:check("2..")) 54 return self.try(self.tp:check("2.."))
55end 55end
56 56
57function metat.__index:quit() 57function metat.__index:quit()
58 socket.try(self.tp:command("QUIT")) 58 self.try(self.tp:command("QUIT"))
59 return socket.try(self.tp:check("2..")) 59 return self.try(self.tp:check("2.."))
60end 60end
61 61
62function metat.__index:close() 62function metat.__index:close()
63 return socket.try(self.tp:close()) 63 return self.try(self.tp:close())
64end 64end
65 65
66function metat.__index:login(user, password) 66function metat.__index:login(user, password)
67 socket.try(self.tp:command("AUTH", "LOGIN")) 67 self.try(self.tp:command("AUTH", "LOGIN"))
68 socket.try(self.tp:check("3..")) 68 self.try(self.tp:check("3.."))
69 socket.try(self.tp:command(mime.b64(user))) 69 self.try(self.tp:command(mime.b64(user)))
70 socket.try(self.tp:check("3..")) 70 self.try(self.tp:check("3.."))
71 socket.try(self.tp:command(mime.b64(password))) 71 self.try(self.tp:command(mime.b64(password)))
72 return socket.try(self.tp:check("2..")) 72 return self.try(self.tp:check("2.."))
73end 73end
74 74
75function metat.__index:plain(user, password) 75function metat.__index:plain(user, password)
76 local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) 76 local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password)
77 socket.try(self.tp:command("AUTH", auth)) 77 self.try(self.tp:command("AUTH", auth))
78 return socket.try(self.tp:check("2..")) 78 return self.try(self.tp:check("2.."))
79end 79end
80 80
81function metat.__index:auth(user, password, ext) 81function metat.__index:auth(user, password, ext)
@@ -85,7 +85,7 @@ function metat.__index:auth(user, password, ext)
85 elseif string.find(ext, "AUTH[^\n]+PLAIN") then 85 elseif string.find(ext, "AUTH[^\n]+PLAIN") then
86 return self:plain(user, password) 86 return self:plain(user, password)
87 else 87 else
88 socket.try(nil, "authentication not supported") 88 self.try(nil, "authentication not supported")
89 end 89 end
90end 90end
91 91
@@ -104,7 +104,9 @@ end
104 104
105function open(server, port) 105function open(server, port)
106 local tp = socket.try(tp.connect(server or SERVER, port or PORT, TIMEOUT)) 106 local tp = socket.try(tp.connect(server or SERVER, port or PORT, TIMEOUT))
107 return setmetatable({tp = tp}, metat) 107 -- make sure tp is closed if we get an exception
108 local try = socket.newtry(function() tp:close() end)
109 return setmetatable({ tp = tp, try = try}, metat)
108end 110end
109 111
110--------------------------------------------------------------------------- 112---------------------------------------------------------------------------
@@ -222,10 +224,10 @@ end
222-- High level SMTP API 224-- High level SMTP API
223----------------------------------------------------------------------------- 225-----------------------------------------------------------------------------
224send = socket.protect(function(mailt) 226send = socket.protect(function(mailt)
225 local con = open(mailt.server, mailt.port) 227 local s = open(mailt.server, mailt.port)
226 local ext = con:greet(mailt.domain) 228 local ext = s:greet(mailt.domain)
227 con:auth(mailt.user, mailt.password, ext) 229 s:auth(mailt.user, mailt.password, ext)
228 con:send(mailt) 230 s:send(mailt)
229 con:quit() 231 s:quit()
230 return con:close() 232 return s:close()
231end) 233end)
diff --git a/src/socket.lua b/src/socket.lua
index f73d167..0a681bf 100644
--- a/src/socket.lua
+++ b/src/socket.lua
@@ -156,8 +156,8 @@ socket.sourcet["http-chunked"] = function(sock)
156 else 156 else
157 -- get chunk and skip terminating CRLF 157 -- get chunk and skip terminating CRLF
158 local chunk, err = sock:receive(size) 158 local chunk, err = sock:receive(size)
159 if err or socket.skip(2, sock:receive()) then return nil, err 159 if chunk then sock:receive() end
160 else return chunk end 160 return chunk, err
161 end 161 end
162 end 162 end
163 }) 163 })
diff --git a/test/ftptest.lua b/test/ftptest.lua
index 37e3edc..f578c82 100644
--- a/test/ftptest.lua
+++ b/test/ftptest.lua
@@ -86,16 +86,6 @@ back, err = socket.ftp.get {
86} 86}
87check(not err and back == index, err) 87check(not err and back == index, err)
88 88
89io.write("testing home directory listing: ")
90expected = capture("ls -F /var/ftp | grep -v /")
91back, err = socket.ftp.get("ftp://localhost/")
92check(back and similar(back, expected), nil, err)
93
94io.write("testing directory listing: ")
95expected = capture("ls -F /var/ftp/pub | grep -v /")
96back, err = socket.ftp.get("ftp://localhost/pub;type=d")
97check(similar(back, expected))
98
99io.write("testing upload denial: ") 89io.write("testing upload denial: ")
100ret, err = socket.ftp.put("ftp://localhost/index.up.html;type=a", index) 90ret, err = socket.ftp.put("ftp://localhost/index.up.html;type=a", index)
101check(err, err) 91check(err, err)
diff --git a/test/httptest.lua b/test/httptest.lua
index d1f2b88..45d7e8d 100644
--- a/test/httptest.lua
+++ b/test/httptest.lua
@@ -70,7 +70,6 @@ io.write("testing request uri correctness: ")
70local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" 70local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string"
71local back, c, h = http.request("http://" .. host .. forth) 71local back, c, h = http.request("http://" .. host .. forth)
72if not back then fail(c) end 72if not back then fail(c) end
73print(back)
74back = url.parse(back) 73back = url.parse(back)
75if similar(back.query, "this+is+the+query+string") then print("ok") 74if similar(back.query, "this+is+the+query+string") then print("ok")
76else fail(back.query) end 75else fail(back.query) end