Implemented safe exceptions. This looks preeety good.

This commit is contained in:
Diego Nehab 2004-06-18 08:02:09 +00:00
parent 62799a416d
commit ac4aac0909
11 changed files with 273 additions and 178 deletions

6
TODO
View File

@ -1,4 +1,5 @@
ajeitar os README.* ajeitar os README.*
ajeitar as referencias a RFCS e LTNS em todos os arquivos. ajeitar as referencias a RFCS e LTNS em todos os arquivos.
@ -8,8 +9,9 @@ check garbage collection in test*.lua
manual manual
socket.skip socket.newtry
send return convention changed. *socket.skip
*send return convention changed.
* compatibility: select sets are associative * compatibility: select sets are associative
* add socket.connect and socket.bind to the manual * add socket.connect and socket.bind to the manual
* add shutdown * add shutdown

View File

@ -143,8 +143,9 @@
<a href="socket.html#protect">protect</a>, <a href="socket.html#protect">protect</a>,
<a href="socket.html#select">select</a>, <a href="socket.html#select">select</a>,
<a href="socket.html#sink">sink</a>, <a href="socket.html#sink">sink</a>,
<a href="socket.html#source">source</a>, <a href="socket.html#skip">skip</a>,
<a href="socket.html#sleep">sleep</a>, <a href="socket.html#sleep">sleep</a>,
<a href="socket.html#source">source</a>,
<a href="socket.html#time">time</a>, <a href="socket.html#time">time</a>,
<a href="tcp.html#tcp">tcp</a>, <a href="tcp.html#tcp">tcp</a>,
<a href="socket.html#try">try</a>, <a href="socket.html#try">try</a>,

View File

@ -169,6 +169,49 @@ socket, leaving it open when done.
The function returns a sink with the appropriate behavior. The function returns a sink with the appropriate behavior.
</p> </p>
<!-- skip ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
<p class=name id=skip>
socket.<b>skip(</b>d [, ret<sub>1</sub>, ret<sub>2</sub> ... ret<sub>N</sub>]<b>)</b>
</p>
<p class=description>
Drops a number of arguments and returns the remaining.
</p>
<p class=parameters>
<tt>D</tt> is the number of arguments to drop. <tt>Ret<sub>1</sub></tt> to
<tt>ret<sub>N</sub></tt> are the arguments.
</p>
<p class=return>
The function returns <tt>ret<sub>d+1</sub></tt> to <tt>ret<sub>N</sub></tt>.
</p>
<p class=note>
Note: This function is useful to avoid creation of dummy variables:
</p>
<pre class=example>
-- get the status code and separator from SMTP server reply
local code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)"))
</pre>
<!-- sleep ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
<p class=name id=sleep>
socket.<b>sleep(</b>time<b>)</b>
</p>
<p class=description>
Freezes the program execution during a given amount of time.
</p>
<p class=parameters>
<tt>Time</tt> is the number of seconds to sleep for.
The function truncates <tt>time</tt> to the nearest integer.
</p>
<!-- source +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- source +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
<p class=name id=source> <p class=name id=source>
@ -201,6 +244,27 @@ side closes the connection.
The function returns a source with the appropriate behavior. The function returns a source with the appropriate behavior.
</p> </p>
<!-- time ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
<p class=name id=time>
socket.<b>time()</b>
</p>
<p class=description>
Returns the time in seconds, relative to the origin of the
universe. Only time differences are meaninful.
</p>
<p class=return>
The function returns the time as a number.
</p>
<pre class=example>
t = socket.time()
-- do stuff
print(socket.time() - t .. " seconds elapsed")
</pre>
<!-- try ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- try ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
<p class=name id=try> <p class=name id=try>
@ -212,7 +276,7 @@ Throws an exception in case of error.
</p> </p>
<p class=parameters> <p class=parameters>
<tt>Ret</tt><sub>1</sub> to <tt>ret</tt><sub>N</sub> can be arbitrary <tt>Ret<sub>1</sub></tt> to <tt>ret<sub>N</sub></tt> can be arbitrary
arguments, but are usually the return values of a function call arguments, but are usually the return values of a function call
nested with <tt>try</tt>. nested with <tt>try</tt>.
</p> </p>

View File

@ -277,9 +277,10 @@ the transmission.
<p class=note> <p class=note>
<b>Important note</b>: This function was changed <em>severely</em>. It used <b>Important note</b>: This function was changed <em>severely</em>. It used
to support multiple patterns (but I have never seen this feature used) and to support multiple patterns (but I have never seen this feature used) and
partial results used to be returned in the same way as successful results. now it doesn't anymore. Partial results used to be returned in the same
This last feature violated the idea that all functions should return way as successful results. This last feature violated the idea that all
<tt><b>nil</b></tt> on error. Thus the change. functions should return <tt><b>nil</b></tt> on error. Thus it was changed
too.
</p> </p>
<!-- send +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- send +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
@ -300,20 +301,25 @@ result to LuaSocket instead of passing several independent strings.
</p> </p>
<p class=return> <p class=return>
The method returns the number of bytes accepted by the transport layer, If successful, the method returns the number of bytes accepted by
followed by an error code. The error code is <b><tt>nil</tt></b> if the operation the transport layer. In case of error, the method returns
completed with no errors, the string '<tt>closed</tt>' in case <b><tt>nil</tt></b>, followed by an error message, followed by the
partial number of bytes accepted by the transport layer.
The error message can be '<tt>closed</tt>' in case
the connection was closed before the transmission was completed or the the connection was closed before the transmission was completed or the
string '<tt>timeout</tt>' in case there was a timeout during the string '<tt>timeout</tt>' in case there was a timeout during the
operation. operation.
</p> </p>
<p class=note> <p class=note>
Note: The return values for the <tt>send</tt> method have been changed in <b>Important note</b>:
LuaSocket 2.0! In previous versions, the method returned only the The return values for the <tt>send</tt> method have been changed in
error message. Since returning <b><tt>nil</tt></b> in case of success goes LuaSocket 2.0 alpha <b>and again</b> in the beta (sorry)!
against all other LuaSocket methods and functions, the In previous versions, the method returned only the
<tt>send</tt> method been changed for the sake of uniformity. error message. Since returning <b><tt>nil</tt></b> in case of success was
nonsense, in alpha the first return value became the number of bytes sent.
Alas, it wasn't returning <tt><b>nil</b></tt> in case of
error. So it was changed again in beta.
</p> </p>
<!-- setoption ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- setoption ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->

View File

@ -14,17 +14,20 @@
\*=========================================================================*/ \*=========================================================================*/
static int global_try(lua_State *L); static int global_try(lua_State *L);
static int global_protect(lua_State *L); static int global_protect(lua_State *L);
static int global_newtry(lua_State *L);
static int protected(lua_State *L); static int protected(lua_State *L);
static int finalize(lua_State *L);
/* except functions */ /* except functions */
static luaL_reg func[] = { static luaL_reg func[] = {
{"try", global_try}, {"try", global_try},
{"protect", global_protect}, {"newtry", global_newtry},
{NULL, NULL} {"protect", global_protect},
{NULL, NULL}
}; };
/*-------------------------------------------------------------------------*\ /*-------------------------------------------------------------------------*\
* Exception handling: try method * Try method
\*-------------------------------------------------------------------------*/ \*-------------------------------------------------------------------------*/
static int global_try(lua_State *L) { static int global_try(lua_State *L) {
if (lua_isnil(L, 1) || (lua_isboolean(L, 1) && !lua_toboolean(L, 1))) { if (lua_isnil(L, 1) || (lua_isboolean(L, 1) && !lua_toboolean(L, 1))) {
@ -35,7 +38,25 @@ static int global_try(lua_State *L) {
} }
/*-------------------------------------------------------------------------*\ /*-------------------------------------------------------------------------*\
* Exception handling: protect factory * Finalizer factory
\*-------------------------------------------------------------------------*/
static int finalize(lua_State *L) {
if (lua_isnil(L, 1) || (lua_isboolean(L, 1) && !lua_toboolean(L, 1))) {
lua_pushvalue(L, lua_upvalueindex(1));
lua_pcall(L, 0, 0, 0);
lua_settop(L, 2);
lua_error(L);
return 0;
} else return lua_gettop(L);
}
static int global_newtry(lua_State *L) {
lua_pushcclosure(L, finalize, 1);
return 1;
}
/*-------------------------------------------------------------------------*\
* Protect factory
\*-------------------------------------------------------------------------*/ \*-------------------------------------------------------------------------*/
static int protected(lua_State *L) { static int protected(lua_State *L) {
lua_pushvalue(L, lua_upvalueindex(1)); lua_pushvalue(L, lua_upvalueindex(1));
@ -48,7 +69,6 @@ static int protected(lua_State *L) {
} }
static int global_protect(lua_State *L) { static int global_protect(lua_State *L) {
lua_insert(L, 1);
lua_pushcclosure(L, protected, 1); lua_pushcclosure(L, protected, 1);
return 1; return 1;
} }

View File

@ -32,143 +32,153 @@ local metat = { __index = {} }
function open(server, port) function open(server, port)
local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT)) local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT))
return setmetatable({tp = tp}, metat) local f = { tp = tp }
-- make sure everything gets closed in an exception
f.try = socket.newtry(function()
tp:close()
if f.data then f.data:close() end
if f.server then f.server:close() end
end)
return setmetatable(f, metat)
end end
local function port(portt) function metat.__index:portconnect()
return portt.server:accept() self.try(self.server:settimeout(TIMEOUT))
self.data = self.try(self.server:accept())
self.try(self.data:settimeout(TIMEOUT))
end end
local function pasv(pasvt) function metat.__index:pasvconnect()
local data = socket.try(socket.tcp()) self.data = self.try(socket.tcp())
socket.try(data:settimeout(TIMEOUT)) self.try(self.data:settimeout(TIMEOUT))
socket.try(data:connect(pasvt.ip, pasvt.port)) self.try(self.data:connect(self.pasvt.ip, self.pasvt.port))
return data
end end
function metat.__index:login(user, password) function metat.__index:login(user, password)
socket.try(self.tp:command("user", user or USER)) self.try(self.tp:command("user", user or USER))
local code, reply = socket.try(self.tp:check{"2..", 331}) local code, reply = self.try(self.tp:check{"2..", 331})
if code == 331 then if code == 331 then
socket.try(self.tp:command("pass", password or PASSWORD)) self.try(self.tp:command("pass", password or PASSWORD))
socket.try(self.tp:check("2..")) self.try(self.tp:check("2.."))
end end
return 1 return 1
end end
function metat.__index:pasv() function metat.__index:pasv()
socket.try(self.tp:command("pasv")) self.try(self.tp:command("pasv"))
local code, reply = socket.try(self.tp:check("2..")) local code, reply = self.try(self.tp:check("2.."))
local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)"
local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern))
socket.try(a and b and c and d and p1 and p2, reply) self.try(a and b and c and d and p1 and p2, reply)
self.pasvt = { self.pasvt = {
ip = string.format("%d.%d.%d.%d", a, b, c, d), ip = string.format("%d.%d.%d.%d", a, b, c, d),
port = p1*256 + p2 port = p1*256 + p2
} }
if self.portt then if self.server then
self.portt.server:close() self.server:close()
self.portt = nil self.server = nil
end end
return self.pasvt.ip, self.pasvt.port return self.pasvt.ip, self.pasvt.port
end end
function metat.__index:port(ip, port) function metat.__index:port(ip, port)
self.pasvt = nil self.pasvt = nil
local server
if not ip then if not ip then
ip, port = socket.try(self.tp:getcontrol():getsockname()) ip, port = self.try(self.tp:getcontrol():getsockname())
server = socket.try(socket.bind(ip, 0)) self.server = self.try(socket.bind(ip, 0))
ip, port = socket.try(server:getsockname()) ip, port = self.try(self.server:getsockname())
socket.try(server:settimeout(TIMEOUT)) self.try(server:settimeout(TIMEOUT))
end end
local pl = math.mod(port, 256) local pl = math.mod(port, 256)
local ph = (port - pl)/256 local ph = (port - pl)/256
local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
socket.try(self.tp:command("port", arg)) self.try(self.tp:command("port", arg))
socket.try(self.tp:check("2..")) self.try(self.tp:check("2.."))
self.portt = server and {ip = ip, port = port, server = server}
return 1 return 1
end end
function metat.__index:send(sendt) function metat.__index:send(sendt)
local data self.try(self.pasvt or self.server, "need port or pasv first")
socket.try(self.pasvt or self.portt, "need port or pasv first") -- if there is a pasvt table, we already sent a PASV command
if self.pasvt then data = socket.try(pasv(self.pasvt)) end -- we just get the data connection into self.data
if self.pasvt then self:pasvconnect() end
-- get the transfer argument and command
local argument = sendt.argument or string.gsub(sendt.path, "^/", "") local argument = sendt.argument or string.gsub(sendt.path, "^/", "")
if argument == "" then argument = nil end if argument == "" then argument = nil end
local command = sendt.command or "stor" local command = sendt.command or "stor"
socket.try(self.tp:command(command, argument)) -- send the transfer command and check the reply
local code, reply = socket.try(self.tp:check{"2..", "1.."}) self.try(self.tp:command(command, argument))
if self.portt then data = socket.try(port(self.portt)) end local code, reply = self.try(self.tp:check{"2..", "1.."})
-- if there is not a a pasvt table, then there is a server
-- and we already sent a PORT command
if not self.pasvt then self:portconnect() end
-- get the sink, source and step for the transfer
local step = sendt.step or ltn12.pump.step local step = sendt.step or ltn12.pump.step
local checkstep = function(src, snk) local checkstep = function(src, snk)
-- check status in control connection while downloading
local readyt = socket.select(readt, nil, 0) local readyt = socket.select(readt, nil, 0)
if readyt[tp] then if readyt[tp] then self.try(self.tp:check("2..")) end
code, reply = self.tp:check("2..") return step(src, snk)
if not code then
data:close()
return nil, reply
end
end
local ret, err = step(src, snk)
if err then data:close() end
return ret, err
end end
local sink = socket.sink("close-when-done", data) local sink = socket.sink("close-when-done", self.data)
socket.try(ltn12.pump.all(sendt.source, sink, checkstep)) -- transfer all data and check error
if string.find(code, "1..") then socket.try(self.tp:check("2..")) end self.try(ltn12.pump.all(sendt.source, sink, checkstep))
if string.find(code, "1..") then self.try(self.tp:check("2..")) end
-- done with data connection
self.data:close()
self.data = nil
return 1 return 1
end end
function metat.__index:receive(recvt) function metat.__index:receive(recvt)
local data self.try(self.pasvt or self.server, "need port or pasv first")
socket.try(self.pasvt or self.portt, "need port or pasv first") if self.pasvt then self:pasvconnect() end
if self.pasvt then data = socket.try(pasv(self.pasvt)) end
local argument = recvt.argument or string.gsub(recvt.path, "^/", "") local argument = recvt.argument or string.gsub(recvt.path, "^/", "")
if argument == "" then argument = nil end if argument == "" then argument = nil end
local command = recvt.command or "retr" local command = recvt.command or "retr"
socket.try(self.tp:command(command, argument)) self.try(self.tp:command(command, argument))
local code = socket.try(self.tp:check{"1..", "2.."}) local code = self.try(self.tp:check{"1..", "2.."})
if self.portt then data = socket.try(port(self.portt)) end if not self.pasvt then self:portconnect() end
local source = socket.source("until-closed", data) local source = socket.source("until-closed", self.data)
local step = recvt.step or ltn12.pump.step local step = recvt.step or ltn12.pump.step
local checkstep = function(src, snk) self.try(ltn12.pump.all(source, recvt.sink, step))
local ret, err = step(src, snk) if string.find(code, "1..") then self.try(self.tp:check("2..")) end
if err then data:close() end self.data:close()
return ret, err self.data = nil
end
socket.try(ltn12.pump.all(source, recvt.sink, checkstep))
if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
return 1 return 1
end end
function metat.__index:cwd(dir) function metat.__index:cwd(dir)
socket.try(self.tp:command("cwd", dir)) self.try(self.tp:command("cwd", dir))
socket.try(self.tp:check(250)) self.try(self.tp:check(250))
return 1 return 1
end end
function metat.__index:type(type) function metat.__index:type(type)
socket.try(self.tp:command("type", type)) self.try(self.tp:command("type", type))
socket.try(self.tp:check(200)) self.try(self.tp:check(200))
return 1 return 1
end end
function metat.__index:greet() function metat.__index:greet()
local code = socket.try(self.tp:check{"1..", "2.."}) local code = self.try(self.tp:check{"1..", "2.."})
if string.find(code, "1..") then socket.try(self.tp:check("2..")) end if string.find(code, "1..") then self.try(self.tp:check("2..")) end
return 1 return 1
end end
function metat.__index:quit() function metat.__index:quit()
socket.try(self.tp:command("quit")) self.try(self.tp:command("quit"))
socket.try(self.tp:check("2..")) self.try(self.tp:check("2.."))
return 1 return 1
end end
function metat.__index:close() function metat.__index:close()
socket.try(self.tp:close()) self.tp:close()
if self.data then self.data:close() end
if self.server then self.server:close() end
self.tp = nil
self.data = nil
self.server = nil
return 1 return 1
end end
@ -176,14 +186,14 @@ end
-- High level FTP API -- High level FTP API
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local function tput(putt) local function tput(putt)
local con = open(putt.host, putt.port) local f = open(putt.host, putt.port)
con:greet() f:greet()
con:login(putt.user, putt.password) f:login(putt.user, putt.password)
if putt.type then con:type(putt.type) end if putt.type then f:type(putt.type) end
con:pasv() f:pasv()
con:send(putt) f:send(putt)
con:quit() f:quit()
return con:close() return f:close()
end end
local default = { local default = {
@ -216,14 +226,14 @@ put = socket.protect(function(putt, body)
end) end)
local function tget(gett) local function tget(gett)
local con = open(gett.host, gett.port) local f = open(gett.host, gett.port)
con:greet() f:greet()
con:login(gett.user, gett.password) f:login(gett.user, gett.password)
if gett.type then con:type(gett.type) end if gett.type then f:type(gett.type) end
con:pasv() f:pasv()
con:receive(gett) f:receive(gett)
con:quit() f:quit()
return con:close() return f:close()
end end
local function sget(u) local function sget(u)

View File

@ -31,24 +31,25 @@ BLOCKSIZE = 2048
local metat = { __index = {} } local metat = { __index = {} }
function open(host, port) function open(host, port)
local con = socket.try(socket.tcp()) local c = socket.try(socket.tcp())
socket.try(con:settimeout(TIMEOUT)) -- make sure the connection gets closed on exception
port = port or PORT local try = socket.newtry(function() c:close() end)
socket.try(con:connect(host, port)) try(c:settimeout(TIMEOUT))
return setmetatable({ con = con }, metat) try(c:connect(host, port or PORT))
return setmetatable({ c = c, try = try }, metat)
end end
function metat.__index:sendrequestline(method, uri) function metat.__index:sendrequestline(method, uri)
local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
return socket.try(self.con:send(reqline)) return self.try(self.c:send(reqline))
end end
function metat.__index:sendheaders(headers) function metat.__index:sendheaders(headers)
for i, v in pairs(headers) do for i, v in pairs(headers) do
socket.try(self.con:send(i .. ": " .. v .. "\r\n")) self.try(self.c:send(i .. ": " .. v .. "\r\n"))
end end
-- mark end of request headers -- mark end of request headers
socket.try(self.con:send("\r\n")) self.try(self.c:send("\r\n"))
return 1 return 1
end end
@ -59,32 +60,32 @@ function metat.__index:sendbody(headers, source, step)
local mode local mode
if headers["content-length"] then mode = "keep-open" if headers["content-length"] then mode = "keep-open"
else mode = "http-chunked" end else mode = "http-chunked" end
return socket.try(ltn12.pump.all(source, socket.sink(mode, self.con), step)) return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step))
end end
function metat.__index:receivestatusline() function metat.__index:receivestatusline()
local status = socket.try(self.con:receive()) local status = self.try(self.c:receive())
local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
return socket.try(tonumber(code), status) return self.try(tonumber(code), status)
end end
function metat.__index:receiveheaders() function metat.__index:receiveheaders()
local line, name, value local line, name, value
local headers = {} local headers = {}
-- get first line -- get first line
line = socket.try(self.con:receive()) line = self.try(self.c:receive())
-- headers go until a blank line is found -- headers go until a blank line is found
while line ~= "" do while line ~= "" do
-- get field-name and value -- get field-name and value
name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
socket.try(name and value, "malformed reponse headers") self.try(name and value, "malformed reponse headers")
name = string.lower(name) name = string.lower(name)
-- get next line (value might be folded) -- get next line (value might be folded)
line = socket.try(self.con:receive()) line = self.try(self.c:receive())
-- unfold any folded values -- unfold any folded values
while string.find(line, "^%s") do while string.find(line, "^%s") do
value = value .. line value = value .. line
line = socket.try(self.con:receive()) line = self.try(self.c:receive())
end end
-- save pair in table -- save pair in table
if headers[name] then headers[name] = headers[name] .. ", " .. value if headers[name] then headers[name] = headers[name] .. ", " .. value
@ -102,12 +103,12 @@ function metat.__index:receivebody(headers, sink, step)
if TE and TE ~= "identity" then mode = "http-chunked" if TE and TE ~= "identity" then mode = "http-chunked"
elseif tonumber(headers["content-length"]) then mode = "by-length" elseif tonumber(headers["content-length"]) then mode = "by-length"
else mode = "default" end else mode = "default" end
return socket.try(ltn12.pump.all(socket.source(mode, self.con, length), return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
sink, step)) sink, step))
end end
function metat.__index:close() function metat.__index:close()
return self.con:close() return self.c:close()
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -204,23 +205,23 @@ end
function trequest(reqt) function trequest(reqt)
reqt = adjustrequest(reqt) reqt = adjustrequest(reqt)
local con = open(reqt.host, reqt.port) local h = open(reqt.host, reqt.port)
con:sendrequestline(reqt.method, reqt.uri) h:sendrequestline(reqt.method, reqt.uri)
con:sendheaders(reqt.headers) h:sendheaders(reqt.headers)
con:sendbody(reqt.headers, reqt.source, reqt.step) h:sendbody(reqt.headers, reqt.source, reqt.step)
local code, headers, status local code, headers, status
code, status = con:receivestatusline() code, status = h:receivestatusline()
headers = con:receiveheaders() headers = h:receiveheaders()
if shouldredirect(reqt, code) then if shouldredirect(reqt, code) then
con:close() h:close()
return tredirect(reqt, headers) return tredirect(reqt, headers)
elseif shouldauthorize(reqt, code) then elseif shouldauthorize(reqt, code) then
con:close() h:close()
return tauthorize(reqt) return tauthorize(reqt)
elseif shouldreceivebody(reqt, code) then elseif shouldreceivebody(reqt, code) then
con:receivebody(headers, reqt.sink, reqt.step) h:receivebody(headers, reqt.sink, reqt.step)
end end
con:close() h:close()
return 1, code, headers, status return 1, code, headers, status
end end

View File

@ -31,51 +31,51 @@ ZONE = "-0000"
local metat = { __index = {} } local metat = { __index = {} }
function metat.__index:greet(domain) function metat.__index:greet(domain)
socket.try(self.tp:check("2..")) self.try(self.tp:check("2.."))
socket.try(self.tp:command("EHLO", domain or DOMAIN)) self.try(self.tp:command("EHLO", domain or DOMAIN))
return socket.skip(1, socket.try(self.tp:check("2.."))) return socket.skip(1, self.try(self.tp:check("2..")))
end end
function metat.__index:mail(from) function metat.__index:mail(from)
socket.try(self.tp:command("MAIL", "FROM:" .. from)) self.try(self.tp:command("MAIL", "FROM:" .. from))
return socket.try(self.tp:check("2..")) return self.try(self.tp:check("2.."))
end end
function metat.__index:rcpt(to) function metat.__index:rcpt(to)
socket.try(self.tp:command("RCPT", "TO:" .. to)) self.try(self.tp:command("RCPT", "TO:" .. to))
return socket.try(self.tp:check("2..")) return self.try(self.tp:check("2.."))
end end
function metat.__index:data(src, step) function metat.__index:data(src, step)
socket.try(self.tp:command("DATA")) self.try(self.tp:command("DATA"))
socket.try(self.tp:check("3..")) self.try(self.tp:check("3.."))
socket.try(self.tp:source(src, step)) self.try(self.tp:source(src, step))
socket.try(self.tp:send("\r\n.\r\n")) self.try(self.tp:send("\r\n.\r\n"))
return socket.try(self.tp:check("2..")) return self.try(self.tp:check("2.."))
end end
function metat.__index:quit() function metat.__index:quit()
socket.try(self.tp:command("QUIT")) self.try(self.tp:command("QUIT"))
return socket.try(self.tp:check("2..")) return self.try(self.tp:check("2.."))
end end
function metat.__index:close() function metat.__index:close()
return socket.try(self.tp:close()) return self.try(self.tp:close())
end end
function metat.__index:login(user, password) function metat.__index:login(user, password)
socket.try(self.tp:command("AUTH", "LOGIN")) self.try(self.tp:command("AUTH", "LOGIN"))
socket.try(self.tp:check("3..")) self.try(self.tp:check("3.."))
socket.try(self.tp:command(mime.b64(user))) self.try(self.tp:command(mime.b64(user)))
socket.try(self.tp:check("3..")) self.try(self.tp:check("3.."))
socket.try(self.tp:command(mime.b64(password))) self.try(self.tp:command(mime.b64(password)))
return socket.try(self.tp:check("2..")) return self.try(self.tp:check("2.."))
end end
function metat.__index:plain(user, password) function metat.__index:plain(user, password)
local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password)
socket.try(self.tp:command("AUTH", auth)) self.try(self.tp:command("AUTH", auth))
return socket.try(self.tp:check("2..")) return self.try(self.tp:check("2.."))
end end
function metat.__index:auth(user, password, ext) function metat.__index:auth(user, password, ext)
@ -85,7 +85,7 @@ function metat.__index:auth(user, password, ext)
elseif string.find(ext, "AUTH[^\n]+PLAIN") then elseif string.find(ext, "AUTH[^\n]+PLAIN") then
return self:plain(user, password) return self:plain(user, password)
else else
socket.try(nil, "authentication not supported") self.try(nil, "authentication not supported")
end end
end end
@ -104,7 +104,9 @@ end
function open(server, port) function open(server, port)
local tp = socket.try(tp.connect(server or SERVER, port or PORT, TIMEOUT)) local tp = socket.try(tp.connect(server or SERVER, port or PORT, TIMEOUT))
return setmetatable({tp = tp}, metat) -- make sure tp is closed if we get an exception
local try = socket.newtry(function() tp:close() end)
return setmetatable({ tp = tp, try = try}, metat)
end end
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
@ -222,10 +224,10 @@ end
-- High level SMTP API -- High level SMTP API
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
send = socket.protect(function(mailt) send = socket.protect(function(mailt)
local con = open(mailt.server, mailt.port) local s = open(mailt.server, mailt.port)
local ext = con:greet(mailt.domain) local ext = s:greet(mailt.domain)
con:auth(mailt.user, mailt.password, ext) s:auth(mailt.user, mailt.password, ext)
con:send(mailt) s:send(mailt)
con:quit() s:quit()
return con:close() return s:close()
end) end)

View File

@ -156,8 +156,8 @@ socket.sourcet["http-chunked"] = function(sock)
else else
-- get chunk and skip terminating CRLF -- get chunk and skip terminating CRLF
local chunk, err = sock:receive(size) local chunk, err = sock:receive(size)
if err or socket.skip(2, sock:receive()) then return nil, err if chunk then sock:receive() end
else return chunk end return chunk, err
end end
end end
}) })

View File

@ -86,16 +86,6 @@ back, err = socket.ftp.get {
} }
check(not err and back == index, err) check(not err and back == index, err)
io.write("testing home directory listing: ")
expected = capture("ls -F /var/ftp | grep -v /")
back, err = socket.ftp.get("ftp://localhost/")
check(back and similar(back, expected), nil, err)
io.write("testing directory listing: ")
expected = capture("ls -F /var/ftp/pub | grep -v /")
back, err = socket.ftp.get("ftp://localhost/pub;type=d")
check(similar(back, expected))
io.write("testing upload denial: ") io.write("testing upload denial: ")
ret, err = socket.ftp.put("ftp://localhost/index.up.html;type=a", index) ret, err = socket.ftp.put("ftp://localhost/index.up.html;type=a", index)
check(err, err) check(err, err)

View File

@ -70,7 +70,6 @@ io.write("testing request uri correctness: ")
local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string"
local back, c, h = http.request("http://" .. host .. forth) local back, c, h = http.request("http://" .. host .. forth)
if not back then fail(c) end if not back then fail(c) end
print(back)
back = url.parse(back) back = url.parse(back)
if similar(back.query, "this+is+the+query+string") then print("ok") if similar(back.query, "this+is+the+query+string") then print("ok")
else fail(back.query) end else fail(back.query) end