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 | |
| parent | 62799a416d2b29d8058331f3d8725fb67c75d261 (diff) | |
| download | luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.gz luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.tar.bz2 luasocket-ac4aac0909da26befaaeb6b415f66cf35b6980e0.zip | |
Implemented safe exceptions. This looks preeety good.
| -rw-r--r-- | TODO | 6 | ||||
| -rw-r--r-- | doc/reference.html | 3 | ||||
| -rw-r--r-- | doc/socket.html | 66 | ||||
| -rw-r--r-- | doc/tcp.html | 28 | ||||
| -rw-r--r-- | src/except.c | 32 | ||||
| -rw-r--r-- | src/ftp.lua | 182 | ||||
| -rw-r--r-- | src/http.lua | 55 | ||||
| -rw-r--r-- | src/smtp.lua | 64 | ||||
| -rw-r--r-- | src/socket.lua | 4 | ||||
| -rw-r--r-- | test/ftptest.lua | 10 | ||||
| -rw-r--r-- | test/httptest.lua | 1 |
11 files changed, 273 insertions, 178 deletions
| @@ -1,4 +1,5 @@ | |||
| 1 | 1 | ||
| 2 | |||
| 2 | ajeitar os README.* | 3 | ajeitar os README.* |
| 3 | ajeitar as referencias a RFCS e LTNS em todos os arquivos. | 4 | ajeitar 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 | ||
| 10 | manual | 11 | manual |
| 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. | |||
| 169 | The function returns a sink with the appropriate behavior. | 169 | The function returns a sink with the appropriate behavior. |
| 170 | </p> | 170 | </p> |
| 171 | 171 | ||
| 172 | <!-- skip ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
| 173 | |||
| 174 | <p class=name id=skip> | ||
| 175 | socket.<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> | ||
| 179 | Drops 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> | ||
| 188 | The function returns <tt>ret<sub>d+1</sub></tt> to <tt>ret<sub>N</sub></tt>. | ||
| 189 | </p> | ||
| 190 | |||
| 191 | <p class=note> | ||
| 192 | Note: 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 | ||
| 197 | local code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) | ||
| 198 | </pre> | ||
| 199 | |||
| 200 | <!-- sleep ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
| 201 | |||
| 202 | <p class=name id=sleep> | ||
| 203 | socket.<b>sleep(</b>time<b>)</b> | ||
| 204 | </p> | ||
| 205 | |||
| 206 | <p class=description> | ||
| 207 | Freezes 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. | ||
| 212 | The 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. | |||
| 201 | The function returns a source with the appropriate behavior. | 244 | The function returns a source with the appropriate behavior. |
| 202 | </p> | 245 | </p> |
| 203 | 246 | ||
| 247 | <!-- time ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | ||
| 248 | |||
| 249 | <p class=name id=time> | ||
| 250 | socket.<b>time()</b> | ||
| 251 | </p> | ||
| 252 | |||
| 253 | <p class=description> | ||
| 254 | Returns the time in seconds, relative to the origin of the | ||
| 255 | universe. Only time differences are meaninful. | ||
| 256 | </p> | ||
| 257 | |||
| 258 | <p class=return> | ||
| 259 | The function returns the time as a number. | ||
| 260 | </p> | ||
| 261 | |||
| 262 | <pre class=example> | ||
| 263 | t = socket.time() | ||
| 264 | -- do stuff | ||
| 265 | print(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 |
| 216 | arguments, but are usually the return values of a function call | 280 | arguments, but are usually the return values of a function call |
| 217 | nested with <tt>try</tt>. | 281 | nested 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 |
| 279 | to support multiple patterns (but I have never seen this feature used) and | 279 | to support multiple patterns (but I have never seen this feature used) and |
| 280 | partial results used to be returned in the same way as successful results. | 280 | now it doesn't anymore. Partial results used to be returned in the same |
| 281 | This last feature violated the idea that all functions should return | 281 | way as successful results. This last feature violated the idea that all |
| 282 | <tt><b>nil</b></tt> on error. Thus the change. | 282 | functions should return <tt><b>nil</b></tt> on error. Thus it was changed |
| 283 | too. | ||
| 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> |
| 303 | The method returns the number of bytes accepted by the transport layer, | 304 | If successful, the method returns the number of bytes accepted by |
| 304 | followed by an error code. The error code is <b><tt>nil</tt></b> if the operation | 305 | the transport layer. In case of error, the method returns |
| 305 | completed with no errors, the string '<tt>closed</tt>' in case | 306 | <b><tt>nil</tt></b>, followed by an error message, followed by the |
| 307 | partial number of bytes accepted by the transport layer. | ||
| 308 | The error message can be '<tt>closed</tt>' in case | ||
| 306 | the connection was closed before the transmission was completed or the | 309 | the connection was closed before the transmission was completed or the |
| 307 | string '<tt>timeout</tt>' in case there was a timeout during the | 310 | string '<tt>timeout</tt>' in case there was a timeout during the |
| 308 | operation. | 311 | operation. |
| 309 | </p> | 312 | </p> |
| 310 | 313 | ||
| 311 | <p class=note> | 314 | <p class=note> |
| 312 | Note: The return values for the <tt>send</tt> method have been changed in | 315 | <b>Important note</b>: |
| 313 | LuaSocket 2.0! In previous versions, the method returned only the | 316 | The return values for the <tt>send</tt> method have been changed in |
| 314 | error message. Since returning <b><tt>nil</tt></b> in case of success goes | 317 | LuaSocket 2.0 alpha <b>and again</b> in the beta (sorry)! |
| 315 | against all other LuaSocket methods and functions, the | 318 | In previous versions, the method returned only the |
| 316 | <tt>send</tt> method been changed for the sake of uniformity. | 319 | error message. Since returning <b><tt>nil</tt></b> in case of success was |
| 320 | nonsense, in alpha the first return value became the number of bytes sent. | ||
| 321 | Alas, it wasn't returning <tt><b>nil</b></tt> in case of | ||
| 322 | error. 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 | \*=========================================================================*/ |
| 15 | static int global_try(lua_State *L); | 15 | static int global_try(lua_State *L); |
| 16 | static int global_protect(lua_State *L); | 16 | static int global_protect(lua_State *L); |
| 17 | static int global_newtry(lua_State *L); | ||
| 17 | static int protected(lua_State *L); | 18 | static int protected(lua_State *L); |
| 19 | static int finalize(lua_State *L); | ||
| 18 | 20 | ||
| 19 | /* except functions */ | 21 | /* except functions */ |
| 20 | static luaL_reg func[] = { | 22 | static 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 | \*-------------------------------------------------------------------------*/ |
| 29 | static int global_try(lua_State *L) { | 32 | static 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 | \*-------------------------------------------------------------------------*/ | ||
| 43 | static 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 | |||
| 53 | static int global_newtry(lua_State *L) { | ||
| 54 | lua_pushcclosure(L, finalize, 1); | ||
| 55 | return 1; | ||
| 56 | } | ||
| 57 | |||
| 58 | /*-------------------------------------------------------------------------*\ | ||
| 59 | * Protect factory | ||
| 39 | \*-------------------------------------------------------------------------*/ | 60 | \*-------------------------------------------------------------------------*/ |
| 40 | static int protected(lua_State *L) { | 61 | static 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 | ||
| 50 | static int global_protect(lua_State *L) { | 71 | static 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 | ||
| 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) |
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 | |||
| 31 | local metat = { __index = {} } | 31 | local metat = { __index = {} } |
| 32 | 32 | ||
| 33 | function open(host, port) | 33 | function 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) | ||
| 39 | end | 40 | end |
| 40 | 41 | ||
| 41 | function metat.__index:sendrequestline(method, uri) | 42 | function 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)) |
| 44 | end | 45 | end |
| 45 | 46 | ||
| 46 | function metat.__index:sendheaders(headers) | 47 | function 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 |
| 53 | end | 54 | end |
| 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)) |
| 63 | end | 64 | end |
| 64 | 65 | ||
| 65 | function metat.__index:receivestatusline() | 66 | function 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) |
| 69 | end | 70 | end |
| 70 | 71 | ||
| 71 | function metat.__index:receiveheaders() | 72 | function 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)) |
| 107 | end | 108 | end |
| 108 | 109 | ||
| 109 | function metat.__index:close() | 110 | function metat.__index:close() |
| 110 | return self.con:close() | 111 | return self.c:close() |
| 111 | end | 112 | end |
| 112 | 113 | ||
| 113 | ----------------------------------------------------------------------------- | 114 | ----------------------------------------------------------------------------- |
| @@ -204,23 +205,23 @@ end | |||
| 204 | 205 | ||
| 205 | function trequest(reqt) | 206 | function 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 |
| 225 | end | 226 | end |
| 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" | |||
| 31 | local metat = { __index = {} } | 31 | local metat = { __index = {} } |
| 32 | 32 | ||
| 33 | function metat.__index:greet(domain) | 33 | function 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.."))) |
| 37 | end | 37 | end |
| 38 | 38 | ||
| 39 | function metat.__index:mail(from) | 39 | function 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..")) |
| 42 | end | 42 | end |
| 43 | 43 | ||
| 44 | function metat.__index:rcpt(to) | 44 | function 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..")) |
| 47 | end | 47 | end |
| 48 | 48 | ||
| 49 | function metat.__index:data(src, step) | 49 | function 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..")) |
| 55 | end | 55 | end |
| 56 | 56 | ||
| 57 | function metat.__index:quit() | 57 | function 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..")) |
| 60 | end | 60 | end |
| 61 | 61 | ||
| 62 | function metat.__index:close() | 62 | function metat.__index:close() |
| 63 | return socket.try(self.tp:close()) | 63 | return self.try(self.tp:close()) |
| 64 | end | 64 | end |
| 65 | 65 | ||
| 66 | function metat.__index:login(user, password) | 66 | function 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..")) |
| 73 | end | 73 | end |
| 74 | 74 | ||
| 75 | function metat.__index:plain(user, password) | 75 | function 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..")) |
| 79 | end | 79 | end |
| 80 | 80 | ||
| 81 | function metat.__index:auth(user, password, ext) | 81 | function 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 |
| 90 | end | 90 | end |
| 91 | 91 | ||
| @@ -104,7 +104,9 @@ end | |||
| 104 | 104 | ||
| 105 | function open(server, port) | 105 | function 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) | ||
| 108 | end | 110 | end |
| 109 | 111 | ||
| 110 | --------------------------------------------------------------------------- | 112 | --------------------------------------------------------------------------- |
| @@ -222,10 +224,10 @@ end | |||
| 222 | -- High level SMTP API | 224 | -- High level SMTP API |
| 223 | ----------------------------------------------------------------------------- | 225 | ----------------------------------------------------------------------------- |
| 224 | send = socket.protect(function(mailt) | 226 | send = 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() |
| 231 | end) | 233 | end) |
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 | } |
| 87 | check(not err and back == index, err) | 87 | check(not err and back == index, err) |
| 88 | 88 | ||
| 89 | io.write("testing home directory listing: ") | ||
| 90 | expected = capture("ls -F /var/ftp | grep -v /") | ||
| 91 | back, err = socket.ftp.get("ftp://localhost/") | ||
| 92 | check(back and similar(back, expected), nil, err) | ||
| 93 | |||
| 94 | io.write("testing directory listing: ") | ||
| 95 | expected = capture("ls -F /var/ftp/pub | grep -v /") | ||
| 96 | back, err = socket.ftp.get("ftp://localhost/pub;type=d") | ||
| 97 | check(similar(back, expected)) | ||
| 98 | |||
| 99 | io.write("testing upload denial: ") | 89 | io.write("testing upload denial: ") |
| 100 | ret, err = socket.ftp.put("ftp://localhost/index.up.html;type=a", index) | 90 | ret, err = socket.ftp.put("ftp://localhost/index.up.html;type=a", index) |
| 101 | check(err, err) | 91 | check(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: ") | |||
| 70 | local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" | 70 | local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" |
| 71 | local back, c, h = http.request("http://" .. host .. forth) | 71 | local back, c, h = http.request("http://" .. host .. forth) |
| 72 | if not back then fail(c) end | 72 | if not back then fail(c) end |
| 73 | print(back) | ||
| 74 | back = url.parse(back) | 73 | back = url.parse(back) |
| 75 | if similar(back.query, "this+is+the+query+string") then print("ok") | 74 | if similar(back.query, "this+is+the+query+string") then print("ok") |
| 76 | else fail(back.query) end | 75 | else fail(back.query) end |
