mirror of
https://github.com/lunarmodules/luasocket.git
synced 2024-11-16 02:08:21 +01:00
New filter scheme.
ltn12 and mime updated. smtp/ftp broken.
This commit is contained in:
parent
b6edaac284
commit
bcc0c2a9f0
5
TODO
5
TODO
@ -19,6 +19,11 @@
|
|||||||
* Separar as classes em arquivos
|
* Separar as classes em arquivos
|
||||||
* Retorno de sendto em datagram sockets pode ser refused
|
* Retorno de sendto em datagram sockets pode ser refused
|
||||||
|
|
||||||
|
colocar um userdata com gc metamethod pra chamar sock_close (WSAClose);
|
||||||
|
sources ans sinks are always simple in http and ftp and smtp
|
||||||
|
unify backbone of smtp and ftp
|
||||||
|
expose encode/decode tables to provide extensibility for mime module
|
||||||
|
use coroutines instead of fancy filters
|
||||||
unify filter and send/receive callback. new sink/source/pump idea.
|
unify filter and send/receive callback. new sink/source/pump idea.
|
||||||
get rid of aux_optlstring
|
get rid of aux_optlstring
|
||||||
wrap sink and sources with a function that performs the replacement
|
wrap sink and sources with a function that performs the replacement
|
||||||
|
17
etc/b64.lua
17
etc/b64.lua
@ -1,13 +1,12 @@
|
|||||||
|
local source = ltn12.source.file(io.stdin)
|
||||||
|
local sink = ltn12.sink.file(io.stdout)
|
||||||
local convert
|
local convert
|
||||||
if arg and arg[1] == '-d' then
|
if arg and arg[1] == '-d' then
|
||||||
convert = socket.mime.decode("base64")
|
convert = mime.decode("base64")
|
||||||
else
|
else
|
||||||
local base64 = socket.mime.encode("base64")
|
local base64 = mime.encode("base64")
|
||||||
local wrap = socket.mime.wrap()
|
local wrap = mime.wrap()
|
||||||
convert = socket.mime.chain(base64, wrap)
|
convert = ltn12.filter.chain(base64, wrap)
|
||||||
end
|
|
||||||
while 1 do
|
|
||||||
local chunk = io.read(4096)
|
|
||||||
io.write(convert(chunk))
|
|
||||||
if not chunk then break end
|
|
||||||
end
|
end
|
||||||
|
source = ltn12.source.chain(source, convert)
|
||||||
|
ltn12.pump(source, sink)
|
||||||
|
45
etc/get.lua
45
etc/get.lua
@ -80,39 +80,31 @@ function stats(size)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- downloads a file using the ftp protocol
|
-- determines the size of a http file
|
||||||
function getbyftp(url, file)
|
function gethttpsize(url)
|
||||||
local save = socket.callback.receive.file(file or io.stdout)
|
local respt = socket.http.request {method = "HEAD", url = url}
|
||||||
if file then
|
if respt.code == 200 then
|
||||||
save = socket.callback.receive.chain(stats(gethttpsize(url)), save)
|
return tonumber(respt.headers["content-length"])
|
||||||
end
|
end
|
||||||
local err = socket.ftp.get_cb {
|
|
||||||
url = url,
|
|
||||||
content_cb = save,
|
|
||||||
type = "i"
|
|
||||||
}
|
|
||||||
if err then print(err) end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- downloads a file using the http protocol
|
-- downloads a file using the http protocol
|
||||||
function getbyhttp(url, file)
|
function getbyhttp(url, file)
|
||||||
local save = socket.callback.receive.file(file or io.stdout)
|
local save = ltn12.sink.file(file or io.stdout)
|
||||||
if file then
|
-- only print feedback if output is not stdout
|
||||||
save = socket.callback.receive.chain(stats(gethttpsize(url)), save)
|
if file then save = ltn12.sink.chain(stats(gethttpsize(url)), save) end
|
||||||
end
|
local respt = socket.http.request_cb({url = url, sink = save})
|
||||||
local response = socket.http.request_cb({url = url}, {body_cb = save})
|
if respt.code ~= 200 then print(respt.status or respt.error) end
|
||||||
if response.code ~= 200 then print(response.status or response.error) end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- determines the size of a http file
|
-- downloads a file using the ftp protocol
|
||||||
function gethttpsize(url)
|
function getbyftp(url, file)
|
||||||
local response = socket.http.request {
|
local save = ltn12.sink.file(file or io.stdout)
|
||||||
method = "HEAD",
|
-- only print feedback if output is not stdout
|
||||||
url = url
|
-- and we don't know how big the file is
|
||||||
}
|
if file then save = ltn12.sink.chain(stats(), save) end
|
||||||
if response.code == 200 then
|
local ret, err = socket.ftp.get_cb {url = url, sink = save, type = "i"}
|
||||||
return tonumber(response.headers["content-length"])
|
if err then print(err) end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- determines the scheme
|
-- determines the scheme
|
||||||
@ -130,7 +122,6 @@ function get(url, name)
|
|||||||
if scheme == "ftp" then getbyftp(url, fout)
|
if scheme == "ftp" then getbyftp(url, fout)
|
||||||
elseif scheme == "http" then getbyhttp(url, fout)
|
elseif scheme == "http" then getbyhttp(url, fout)
|
||||||
else print("unknown scheme" .. scheme) end
|
else print("unknown scheme" .. scheme) end
|
||||||
if name then fout:close() end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- main program
|
-- main program
|
||||||
|
@ -158,14 +158,3 @@ void *aux_getclassudata(lua_State *L, const char *classname, int objidx)
|
|||||||
{
|
{
|
||||||
return luaL_checkudata(L, objidx, classname);
|
return luaL_checkudata(L, objidx, classname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------*\
|
|
||||||
* Accept "false" as nil
|
|
||||||
\*-------------------------------------------------------------------------*/
|
|
||||||
const char *aux_optlstring(lua_State *L, int n, const char *v, size_t *l)
|
|
||||||
{
|
|
||||||
if (lua_isnil(L, n) || (lua_isboolean(L, n) && !lua_toboolean(L, n))) {
|
|
||||||
*l = 0;
|
|
||||||
return NULL;
|
|
||||||
} else return luaL_optlstring(L, n, v, l);
|
|
||||||
}
|
|
||||||
|
@ -49,6 +49,5 @@ void *aux_checkgroup(lua_State *L, const char *groupname, int objidx);
|
|||||||
void *aux_getclassudata(lua_State *L, const char *groupname, int objidx);
|
void *aux_getclassudata(lua_State *L, const char *groupname, int objidx);
|
||||||
void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx);
|
void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx);
|
||||||
int aux_checkboolean(lua_State *L, int objidx);
|
int aux_checkboolean(lua_State *L, int objidx);
|
||||||
const char *aux_optlstring(lua_State *L, int n, const char *v, size_t *l);
|
|
||||||
|
|
||||||
#endif /* AUX_H */
|
#endif /* AUX_H */
|
||||||
|
360
src/ftp.lua
360
src/ftp.lua
@ -5,62 +5,29 @@
|
|||||||
-- Conforming to: RFC 959, LTN7
|
-- Conforming to: RFC 959, LTN7
|
||||||
-- RCS ID: $Id$
|
-- RCS ID: $Id$
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
|
-- make sure LuaSocket is loaded
|
||||||
local Public, Private = {}, {}
|
if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
|
||||||
local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace
|
-- get LuaSocket namespace
|
||||||
socket.ftp = Public -- create ftp sub namespace
|
local socket = _G[LUASOCKET_LIBNAME]
|
||||||
|
if not socket then error('module requires LuaSocket') end
|
||||||
|
-- create namespace inside LuaSocket namespace
|
||||||
|
socket.ftp = socket.ftp or {}
|
||||||
|
-- make all module globals fall into namespace
|
||||||
|
setmetatable(socket.ftp, { __index = _G })
|
||||||
|
setfenv(1, socket.ftp)
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Program constants
|
-- Program constants
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- timeout in seconds before the program gives up on a connection
|
-- timeout in seconds before the program gives up on a connection
|
||||||
Public.TIMEOUT = 60
|
TIMEOUT = 60
|
||||||
-- default port for ftp service
|
-- default port for ftp service
|
||||||
Public.PORT = 21
|
PORT = 21
|
||||||
-- this is the default anonymous password. used when no password is
|
-- this is the default anonymous password. used when no password is
|
||||||
-- provided in url. should be changed to your e-mail.
|
-- provided in url. should be changed to your e-mail.
|
||||||
Public.EMAIL = "anonymous@anonymous.org"
|
EMAIL = "anonymous@anonymous.org"
|
||||||
-- block size used in transfers
|
-- block size used in transfers
|
||||||
Public.BLOCKSIZE = 8192
|
BLOCKSIZE = 2048
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Tries to get a pattern from the server and closes socket on error
|
|
||||||
-- sock: socket connected to the server
|
|
||||||
-- pattern: pattern to receive
|
|
||||||
-- Returns
|
|
||||||
-- received pattern on success
|
|
||||||
-- nil followed by error message on error
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.try_receive(sock, pattern)
|
|
||||||
local data, err = sock:receive(pattern)
|
|
||||||
if not data then sock:close() end
|
|
||||||
return data, err
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Tries to send data to the server and closes socket on error
|
|
||||||
-- sock: socket connected to the server
|
|
||||||
-- data: data to send
|
|
||||||
-- Returns
|
|
||||||
-- err: error message if any, nil if successfull
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.try_send(sock, data)
|
|
||||||
local sent, err = sock:send(data)
|
|
||||||
if not sent then sock:close() end
|
|
||||||
return err
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Tries to send DOS mode lines. Closes socket on error.
|
|
||||||
-- Input
|
|
||||||
-- sock: server socket
|
|
||||||
-- line: string to be sent
|
|
||||||
-- Returns
|
|
||||||
-- err: message in case of error, nil if successfull
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.try_sendline(sock, line)
|
|
||||||
return Private.try_send(sock, line .. "\r\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Gets ip and port for data connection from PASV answer
|
-- Gets ip and port for data connection from PASV answer
|
||||||
@ -70,7 +37,7 @@ end
|
|||||||
-- ip: string containing ip for data connection
|
-- ip: string containing ip for data connection
|
||||||
-- port: port for data connection
|
-- port: port for data connection
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.get_pasv(pasv)
|
local function get_pasv(pasv)
|
||||||
local a, b, c, d, p1, p2, _
|
local a, b, c, d, p1, p2, _
|
||||||
local ip, port
|
local ip, port
|
||||||
_,_, a, b, c, d, p1, p2 =
|
_,_, a, b, c, d, p1, p2 =
|
||||||
@ -81,88 +48,6 @@ function Private.get_pasv(pasv)
|
|||||||
return ip, port
|
return ip, port
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Sends a FTP command through socket
|
|
||||||
-- Input
|
|
||||||
-- control: control connection socket
|
|
||||||
-- cmd: command
|
|
||||||
-- arg: command argument if any
|
|
||||||
-- Returns
|
|
||||||
-- error message in case of error, nil otherwise
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.send_command(control, cmd, arg)
|
|
||||||
local line
|
|
||||||
if arg then line = cmd .. " " .. arg
|
|
||||||
else line = cmd end
|
|
||||||
return Private.try_sendline(control, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Gets FTP command answer, unfolding if neccessary
|
|
||||||
-- Input
|
|
||||||
-- control: control connection socket
|
|
||||||
-- Returns
|
|
||||||
-- answer: whole server reply, nil if error
|
|
||||||
-- code: answer status code or error message
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.get_answer(control)
|
|
||||||
local code, lastcode, sep, _
|
|
||||||
local line, err = Private.try_receive(control)
|
|
||||||
local answer = line
|
|
||||||
if err then return nil, err end
|
|
||||||
_,_, code, sep = string.find(line, "^(%d%d%d)(.)")
|
|
||||||
if not code or not sep then return nil, answer end
|
|
||||||
if sep == "-" then -- answer is multiline
|
|
||||||
repeat
|
|
||||||
line, err = Private.try_receive(control)
|
|
||||||
if err then return nil, err end
|
|
||||||
_,_, lastcode, sep = string.find(line, "^(%d%d%d)(.)")
|
|
||||||
answer = answer .. "\n" .. line
|
|
||||||
until code == lastcode and sep == " " -- answer ends with same code
|
|
||||||
end
|
|
||||||
return answer, tonumber(code)
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Checks if a message return is correct. Closes control connection if not.
|
|
||||||
-- Input
|
|
||||||
-- control: control connection socket
|
|
||||||
-- success: table with successfull reply status code
|
|
||||||
-- Returns
|
|
||||||
-- code: reply code or nil in case of error
|
|
||||||
-- answer: server complete answer or system error message
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.check_answer(control, success)
|
|
||||||
local answer, code = Private.get_answer(control)
|
|
||||||
if not answer then return nil, code end
|
|
||||||
if type(success) ~= "table" then success = {success} end
|
|
||||||
for _, s in ipairs(success) do
|
|
||||||
if code == s then
|
|
||||||
return code, answer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
control:close()
|
|
||||||
return nil, answer
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Trys a command on control socked, in case of error, the control connection
|
|
||||||
-- is closed.
|
|
||||||
-- Input
|
|
||||||
-- control: control connection socket
|
|
||||||
-- cmd: command
|
|
||||||
-- arg: command argument or nil if no argument
|
|
||||||
-- success: table with successfull reply status code
|
|
||||||
-- Returns
|
|
||||||
-- code: reply code or nil in case of error
|
|
||||||
-- answer: server complete answer or system error message
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.command(control, cmd, arg, success)
|
|
||||||
local err = Private.send_command(control, cmd, arg)
|
|
||||||
if err then return nil, err end
|
|
||||||
return Private.check_answer(control, success)
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Check server greeting
|
-- Check server greeting
|
||||||
-- Input
|
-- Input
|
||||||
@ -171,10 +56,10 @@ end
|
|||||||
-- code: nil if error
|
-- code: nil if error
|
||||||
-- answer: server answer or error message
|
-- answer: server answer or error message
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.greet(control)
|
local function greet(control)
|
||||||
local code, answer = Private.check_answer(control, {120, 220})
|
local code, answer = check_answer(control, {120, 220})
|
||||||
if code == 120 then -- please try again, somewhat busy now...
|
if code == 120 then -- please try again, somewhat busy now...
|
||||||
return Private.check_answer(control, {220})
|
return check_answer(control, {220})
|
||||||
end
|
end
|
||||||
return code, answer
|
return code, answer
|
||||||
end
|
end
|
||||||
@ -189,10 +74,10 @@ end
|
|||||||
-- code: nil if error
|
-- code: nil if error
|
||||||
-- answer: server answer or error message
|
-- answer: server answer or error message
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.login(control, user, password)
|
local function login(control, user, password)
|
||||||
local code, answer = Private.command(control, "user", user, {230, 331})
|
local code, answer = command(control, "user", user, {230, 331})
|
||||||
if code == 331 and password then -- need pass and we have pass
|
if code == 331 and password then -- need pass and we have pass
|
||||||
return Private.command(control, "pass", password, {230, 202})
|
return command(control, "pass", password, {230, 202})
|
||||||
end
|
end
|
||||||
return code, answer
|
return code, answer
|
||||||
end
|
end
|
||||||
@ -206,9 +91,7 @@ end
|
|||||||
-- code: nil if error
|
-- code: nil if error
|
||||||
-- answer: server answer or error message
|
-- answer: server answer or error message
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.cwd(control, path)
|
local function cwd(control, path)
|
||||||
if path then return Private.command(control, "cwd", path, {250})
|
|
||||||
else return 250, nil end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -219,18 +102,18 @@ end
|
|||||||
-- server: server socket bound to local address, nil if error
|
-- server: server socket bound to local address, nil if error
|
||||||
-- answer: error message if any
|
-- answer: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.port(control)
|
local function port(control)
|
||||||
local code, answer
|
local code, answer
|
||||||
local server, ctl_ip
|
local server, ctl_ip
|
||||||
ctl_ip, answer = control:getsockname()
|
ctl_ip, answer = control:getsockname()
|
||||||
server, answer = socket.bind(ctl_ip, 0)
|
server, answer = socket.bind(ctl_ip, 0)
|
||||||
server:settimeout(Public.TIMEOUT)
|
server:settimeout(TIMEOUT)
|
||||||
local ip, p, ph, pl
|
local ip, p, ph, pl
|
||||||
ip, p = server:getsockname()
|
ip, p = server:getsockname()
|
||||||
pl = math.mod(p, 256)
|
pl = math.mod(p, 256)
|
||||||
ph = (p - pl)/256
|
ph = (p - 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), "%.", ",")
|
||||||
code, answer = Private.command(control, "port", arg, {200})
|
code, answer = command(control, "port", arg, {200})
|
||||||
if not code then
|
if not code then
|
||||||
server:close()
|
server:close()
|
||||||
return nil, answer
|
return nil, answer
|
||||||
@ -245,8 +128,8 @@ end
|
|||||||
-- code: nil if error
|
-- code: nil if error
|
||||||
-- answer: server answer or error message
|
-- answer: server answer or error message
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.logout(control)
|
local function logout(control)
|
||||||
local code, answer = Private.command(control, "quit", nil, {221})
|
local code, answer = command(control, "quit", nil, {221})
|
||||||
if code then control:close() end
|
if code then control:close() end
|
||||||
return code, answer
|
return code, answer
|
||||||
end
|
end
|
||||||
@ -259,10 +142,10 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- nil if successfull, or an error message in case of error
|
-- nil if successfull, or an error message in case of error
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.receive_indirect(data, callback)
|
local function receive_indirect(data, callback)
|
||||||
local chunk, err, res
|
local chunk, err, res
|
||||||
while not err do
|
while not err do
|
||||||
chunk, err = Private.try_receive(data, Public.BLOCKSIZE)
|
chunk, err = try_receive(data, BLOCKSIZE)
|
||||||
if err == "closed" then err = "done" end
|
if err == "closed" then err = "done" end
|
||||||
res = callback(chunk, err)
|
res = callback(chunk, err)
|
||||||
if not res then break end
|
if not res then break end
|
||||||
@ -280,16 +163,16 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message in case of error, nil otherwise
|
-- err: error message in case of error, nil otherwise
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.retrieve(control, server, name, is_directory, content_cb)
|
local function retrieve(control, server, name, is_directory, content_cb)
|
||||||
local code, answer
|
local code, answer
|
||||||
local data
|
local data
|
||||||
-- ask server for file or directory listing accordingly
|
-- ask server for file or directory listing accordingly
|
||||||
if is_directory then
|
if is_directory then
|
||||||
code, answer = Private.cwd(control, name)
|
code, answer = cwd(control, name)
|
||||||
if not code then return answer end
|
if not code then return answer end
|
||||||
code, answer = Private.command(control, "nlst", nil, {150, 125})
|
code, answer = command(control, "nlst", nil, {150, 125})
|
||||||
else
|
else
|
||||||
code, answer = Private.command(control, "retr", name, {150, 125})
|
code, answer = command(control, "retr", name, {150, 125})
|
||||||
end
|
end
|
||||||
if not code then return nil, answer end
|
if not code then return nil, answer end
|
||||||
data, answer = server:accept()
|
data, answer = server:accept()
|
||||||
@ -298,43 +181,14 @@ function Private.retrieve(control, server, name, is_directory, content_cb)
|
|||||||
control:close()
|
control:close()
|
||||||
return answer
|
return answer
|
||||||
end
|
end
|
||||||
answer = Private.receive_indirect(data, content_cb)
|
answer = receive_indirect(data, content_cb)
|
||||||
if answer then
|
if answer then
|
||||||
control:close()
|
control:close()
|
||||||
return answer
|
return answer
|
||||||
end
|
end
|
||||||
data:close()
|
data:close()
|
||||||
-- make sure file transfered ok
|
-- make sure file transfered ok
|
||||||
return Private.check_answer(control, {226, 250})
|
return check_answer(control, {226, 250})
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Sends data comming from a callback
|
|
||||||
-- Input
|
|
||||||
-- data: data connection
|
|
||||||
-- send_cb: callback to produce file contents
|
|
||||||
-- chunk, size: first callback return values
|
|
||||||
-- Returns
|
|
||||||
-- nil if successfull, or an error message in case of error
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.send_indirect(data, send_cb, chunk, size)
|
|
||||||
local total, sent, err
|
|
||||||
total = 0
|
|
||||||
while 1 do
|
|
||||||
if type(chunk) ~= "string" or type(size) ~= "number" then
|
|
||||||
data:close()
|
|
||||||
if not chunk and type(size) == "string" then return size
|
|
||||||
else return "invalid callback return" end
|
|
||||||
end
|
|
||||||
sent, err = data:send(chunk)
|
|
||||||
if err then
|
|
||||||
data:close()
|
|
||||||
return err
|
|
||||||
end
|
|
||||||
total = total + sent
|
|
||||||
if sent >= size then break end
|
|
||||||
chunk, size = send_cb()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -348,9 +202,9 @@ end
|
|||||||
-- code: return code, nil if error
|
-- code: return code, nil if error
|
||||||
-- answer: server answer or error message
|
-- answer: server answer or error message
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.store(control, server, file, send_cb)
|
local function store(control, server, file, send_cb)
|
||||||
local data, err
|
local data, err
|
||||||
local code, answer = Private.command(control, "stor", file, {150, 125})
|
local code, answer = command(control, "stor", file, {150, 125})
|
||||||
if not code then
|
if not code then
|
||||||
control:close()
|
control:close()
|
||||||
return nil, answer
|
return nil, answer
|
||||||
@ -363,7 +217,7 @@ function Private.store(control, server, file, send_cb)
|
|||||||
return nil, answer
|
return nil, answer
|
||||||
end
|
end
|
||||||
-- send whole file
|
-- send whole file
|
||||||
err = Private.send_indirect(data, send_cb, send_cb())
|
err = send_indirect(data, send_cb, send_cb())
|
||||||
if err then
|
if err then
|
||||||
control:close()
|
control:close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -371,7 +225,7 @@ function Private.store(control, server, file, send_cb)
|
|||||||
-- close connection to inform that file transmission is complete
|
-- close connection to inform that file transmission is complete
|
||||||
data:close()
|
data:close()
|
||||||
-- check if file was received correctly
|
-- check if file was received correctly
|
||||||
return Private.check_answer(control, {226, 250})
|
return check_answer(control, {226, 250})
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -382,11 +236,11 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.change_type(control, params)
|
local function change_type(control, params)
|
||||||
local type, _
|
local type, _
|
||||||
_, _, type = string.find(params or "", "type=(.)")
|
_, _, type = string.find(params or "", "type=(.)")
|
||||||
if type == "a" or type == "i" then
|
if type == "a" or type == "i" then
|
||||||
local code, err = Private.command(control, "type", type, {200})
|
local code, err = command(control, "type", type, {200})
|
||||||
if not code then return err end
|
if not code then return err end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -399,45 +253,42 @@ end
|
|||||||
-- control: control connection with server, or nil if error
|
-- control: control connection with server, or nil if error
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.open(parsed)
|
local function open(parsed)
|
||||||
-- start control connection
|
local control, err = socket.tp.connect(parsed.host, parsed.port)
|
||||||
local control, err = socket.connect(parsed.host, parsed.port)
|
|
||||||
if not control then return nil, err end
|
if not control then return nil, err end
|
||||||
-- make sure we don't block forever
|
local code, reply
|
||||||
control:settimeout(Public.TIMEOUT)
|
-- greet
|
||||||
-- check greeting
|
code, reply = control:check({120, 220})
|
||||||
local code, answer = Private.greet(control)
|
if code == 120 then -- busy, try again
|
||||||
if not code then return nil, answer end
|
code, reply = control:check(220)
|
||||||
-- try to log in
|
end
|
||||||
code, err = Private.login(control, parsed.user, parsed.password)
|
-- authenticate
|
||||||
if not code then return nil, err
|
code, reply = control:command("user", user)
|
||||||
else return control end
|
code, reply = control:check({230, 331})
|
||||||
|
if code == 331 and password then -- need pass and we have pass
|
||||||
|
control:command("pass", password)
|
||||||
|
code, reply = control:check({230, 202})
|
||||||
|
end
|
||||||
|
-- change directory
|
||||||
|
local segment = parse_path(parsed)
|
||||||
|
for i, v in ipairs(segment) do
|
||||||
|
code, reply = control:command("cwd")
|
||||||
|
code, reply = control:check(250)
|
||||||
|
end
|
||||||
|
-- change type
|
||||||
|
local type = string.sub(params or "", 7, 7)
|
||||||
|
if type == "a" or type == "i" then
|
||||||
|
code, reply = control:command("type", type)
|
||||||
|
code, reply = control:check(200)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
return change_dir(control, segment) or
|
||||||
-- Closes the connection with the server
|
change_type(control, parsed.params) or
|
||||||
-- Input
|
download(control, request, segment) or
|
||||||
-- control: control connection with server
|
close(control)
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.close(control)
|
|
||||||
-- disconnect
|
|
||||||
Private.logout(control)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
-- Changes to the directory pointed to by URL
|
|
||||||
-- Input
|
|
||||||
-- control: control connection with server
|
|
||||||
-- segment: parsed URL path segments
|
|
||||||
-- Returns
|
|
||||||
-- err: error message if any
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
function Private.change_dir(control, segment)
|
|
||||||
local n = table.getn(segment)
|
|
||||||
for i = 1, n-1 do
|
|
||||||
local code, answer = Private.cwd(control, segment[i])
|
|
||||||
if not code then return answer end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -450,7 +301,7 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.upload(control, request, segment)
|
local function upload(control, request, segment)
|
||||||
local code, name, content_cb
|
local code, name, content_cb
|
||||||
-- get remote file name
|
-- get remote file name
|
||||||
name = segment[table.getn(segment)]
|
name = segment[table.getn(segment)]
|
||||||
@ -460,10 +311,10 @@ function Private.upload(control, request, segment)
|
|||||||
end
|
end
|
||||||
content_cb = request.content_cb
|
content_cb = request.content_cb
|
||||||
-- setup passive connection
|
-- setup passive connection
|
||||||
local server, answer = Private.port(control)
|
local server, answer = port(control)
|
||||||
if not server then return answer end
|
if not server then return answer end
|
||||||
-- ask server to receive file
|
-- ask server to receive file
|
||||||
code, answer = Private.store(control, server, name, content_cb)
|
code, answer = store(control, server, name, content_cb)
|
||||||
if not code then return answer end
|
if not code then return answer end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -477,7 +328,7 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.download(control, request, segment)
|
local function download(control, request, segment)
|
||||||
local code, name, is_directory, content_cb
|
local code, name, is_directory, content_cb
|
||||||
is_directory = segment.is_directory
|
is_directory = segment.is_directory
|
||||||
content_cb = request.content_cb
|
content_cb = request.content_cb
|
||||||
@ -488,10 +339,10 @@ function Private.download(control, request, segment)
|
|||||||
return "Invalid file path"
|
return "Invalid file path"
|
||||||
end
|
end
|
||||||
-- setup passive connection
|
-- setup passive connection
|
||||||
local server, answer = Private.port(control)
|
local server, answer = port(control)
|
||||||
if not server then return answer end
|
if not server then return answer end
|
||||||
-- ask server to send file or directory listing
|
-- ask server to send file or directory listing
|
||||||
code, answer = Private.retrieve(control, server, name,
|
code, answer = retrieve(control, server, name,
|
||||||
is_directory, content_cb)
|
is_directory, content_cb)
|
||||||
if not code then return answer end
|
if not code then return answer end
|
||||||
end
|
end
|
||||||
@ -507,13 +358,12 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- parsed: a table with parsed components
|
-- parsed: a table with parsed components
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.parse_url(request)
|
local function parse_url(request)
|
||||||
local parsed = socket.url.parse(request.url, {
|
local parsed = socket.url.parse(request.url, {
|
||||||
host = "",
|
|
||||||
user = "anonymous",
|
user = "anonymous",
|
||||||
port = 21,
|
port = 21,
|
||||||
path = "/",
|
path = "/",
|
||||||
password = Public.EMAIL,
|
password = EMAIL,
|
||||||
scheme = "ftp"
|
scheme = "ftp"
|
||||||
})
|
})
|
||||||
-- explicit login information overrides that given by URL
|
-- explicit login information overrides that given by URL
|
||||||
@ -531,7 +381,7 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- dirs: a table with parsed directory components
|
-- dirs: a table with parsed directory components
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.parse_path(parsed_url)
|
local function parse_path(parsed_url)
|
||||||
local segment = socket.url.parse_path(parsed_url.path)
|
local segment = socket.url.parse_path(parsed_url.path)
|
||||||
segment.is_directory = segment.is_directory or
|
segment.is_directory = segment.is_directory or
|
||||||
(parsed_url.params == "type=d")
|
(parsed_url.params == "type=d")
|
||||||
@ -549,7 +399,7 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- request: request table
|
-- request: request table
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Private.build_request(data)
|
local function build_request(data)
|
||||||
local request = {}
|
local request = {}
|
||||||
if type(data) == "table" then for i, v in data do request[i] = v end
|
if type(data) == "table" then for i, v in data do request[i] = v end
|
||||||
else request.url = data end
|
else request.url = data end
|
||||||
@ -568,18 +418,18 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Public.get_cb(request)
|
function get_cb(request)
|
||||||
local parsed = Private.parse_url(request)
|
local parsed = parse_url(request)
|
||||||
if parsed.scheme ~= "ftp" then
|
if parsed.scheme ~= "ftp" then
|
||||||
return string.format("unknown scheme '%s'", parsed.scheme)
|
return string.format("unknown scheme '%s'", parsed.scheme)
|
||||||
end
|
end
|
||||||
local control, err = Private.open(parsed)
|
local control, err = open(parsed)
|
||||||
if not control then return err end
|
if not control then return err end
|
||||||
local segment = Private.parse_path(parsed)
|
local segment = parse_path(parsed)
|
||||||
return Private.change_dir(control, segment) or
|
return change_dir(control, segment) or
|
||||||
Private.change_type(control, parsed.params) or
|
change_type(control, parsed.params) or
|
||||||
Private.download(control, request, segment) or
|
download(control, request, segment) or
|
||||||
Private.close(control)
|
close(control)
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -594,18 +444,18 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Public.put_cb(request)
|
function put_cb(request)
|
||||||
local parsed = Private.parse_url(request)
|
local parsed = parse_url(request)
|
||||||
if parsed.scheme ~= "ftp" then
|
if parsed.scheme ~= "ftp" then
|
||||||
return string.format("unknown scheme '%s'", parsed.scheme)
|
return string.format("unknown scheme '%s'", parsed.scheme)
|
||||||
end
|
end
|
||||||
local control, err = Private.open(parsed)
|
local control, err = open(parsed)
|
||||||
if not control then return err end
|
if not control then return err end
|
||||||
local segment = Private.parse_path(parsed)
|
local segment = parse_path(parsed)
|
||||||
err = Private.change_dir(control, segment) or
|
err = change_dir(control, segment) or
|
||||||
Private.change_type(control, parsed.params) or
|
change_type(control, parsed.params) or
|
||||||
Private.upload(control, request, segment) or
|
upload(control, request, segment) or
|
||||||
Private.close(control)
|
close(control)
|
||||||
if err then return nil, err
|
if err then return nil, err
|
||||||
else return 1 end
|
else return 1 end
|
||||||
end
|
end
|
||||||
@ -623,11 +473,11 @@ end
|
|||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Public.put(url_or_request, content)
|
function put(url_or_request, content)
|
||||||
local request = Private.build_request(url_or_request)
|
local request = build_request(url_or_request)
|
||||||
request.content = request.content or content
|
request.content = request.content or content
|
||||||
request.content_cb = socket.callback.send_string(request.content)
|
request.content_cb = socket.callback.send_string(request.content)
|
||||||
return Public.put_cb(request)
|
return put_cb(request)
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -642,12 +492,12 @@ end
|
|||||||
-- data: file contents as a string
|
-- data: file contents as a string
|
||||||
-- err: error message in case of error, nil otherwise
|
-- err: error message in case of error, nil otherwise
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function Public.get(url_or_request)
|
function get(url_or_request)
|
||||||
local concat = socket.concat.create()
|
local concat = socket.concat.create()
|
||||||
local request = Private.build_request(url_or_request)
|
local request = build_request(url_or_request)
|
||||||
request.content_cb = socket.callback.receive_concat(concat)
|
request.content_cb = socket.callback.receive_concat(concat)
|
||||||
local err = Public.get_cb(request)
|
local err = get_cb(request)
|
||||||
return concat:getresult(), err
|
return concat:getresult(), err
|
||||||
end
|
end
|
||||||
|
|
||||||
return ftp
|
return socket.ftp
|
||||||
|
215
src/http.lua
215
src/http.lua
@ -10,12 +10,11 @@ if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
|
|||||||
-- get LuaSocket namespace
|
-- get LuaSocket namespace
|
||||||
local socket = _G[LUASOCKET_LIBNAME]
|
local socket = _G[LUASOCKET_LIBNAME]
|
||||||
if not socket then error('module requires LuaSocket') end
|
if not socket then error('module requires LuaSocket') end
|
||||||
-- create smtp namespace inside LuaSocket namespace
|
-- create namespace inside LuaSocket namespace
|
||||||
local http = socket.http or {}
|
socket.http = socket.http or {}
|
||||||
socket.http = http
|
-- make all module globals fall into namespace
|
||||||
-- make all module globals fall into smtp namespace
|
setmetatable(socket.http, { __index = _G })
|
||||||
setmetatable(http, { __index = _G })
|
setfenv(1, socket.http)
|
||||||
setfenv(1, http)
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Program constants
|
-- Program constants
|
||||||
@ -27,7 +26,18 @@ PORT = 80
|
|||||||
-- user agent field sent in request
|
-- user agent field sent in request
|
||||||
USERAGENT = socket.version
|
USERAGENT = socket.version
|
||||||
-- block size used in transfers
|
-- block size used in transfers
|
||||||
BLOCKSIZE = 8192
|
BLOCKSIZE = 2048
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- Function return value selectors
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
local function second(a, b)
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
|
||||||
|
local function third(a, b, c)
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Tries to get a pattern from the server and closes socket on error
|
-- Tries to get a pattern from the server and closes socket on error
|
||||||
@ -47,7 +57,7 @@ end
|
|||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Tries to send data to the server and closes socket on error
|
-- Tries to send data to the server and closes socket on error
|
||||||
-- sock: socket connected to the server
|
-- sock: socket connected to the server
|
||||||
-- data: data to send
|
-- ...: data to send
|
||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any, nil if successfull
|
-- err: error message if any, nil if successfull
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -68,11 +78,9 @@ end
|
|||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function receive_status(sock)
|
local function receive_status(sock)
|
||||||
local line, err
|
local line, err = try_receiving(sock)
|
||||||
line, err = try_receiving(sock)
|
|
||||||
if not err then
|
if not err then
|
||||||
local code, _
|
local code = third(string.find(line, "HTTP/%d*%.%d* (%d%d%d)"))
|
||||||
_, _, code = string.find(line, "HTTP/%d*%.%d* (%d%d%d)")
|
|
||||||
return tonumber(code), line
|
return tonumber(code), line
|
||||||
else return nil, nil, err end
|
else return nil, nil, err end
|
||||||
end
|
end
|
||||||
@ -121,7 +129,7 @@ local function receive_headers(sock, headers)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Aborts a receive callback
|
-- Aborts a sink with an error message
|
||||||
-- Input
|
-- Input
|
||||||
-- cb: callback function
|
-- cb: callback function
|
||||||
-- err: error message to pass to callback
|
-- err: error message to pass to callback
|
||||||
@ -129,8 +137,8 @@ end
|
|||||||
-- callback return or if nil err
|
-- callback return or if nil err
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function abort(cb, err)
|
local function abort(cb, err)
|
||||||
local go, err_or_f = cb(nil, err)
|
local go, cb_err = cb(nil, err)
|
||||||
return err_or_f or err
|
return cb_err or err
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -138,41 +146,36 @@ end
|
|||||||
-- Input
|
-- Input
|
||||||
-- sock: socket connected to the server
|
-- sock: socket connected to the server
|
||||||
-- headers: header set in which to include trailer headers
|
-- headers: header set in which to include trailer headers
|
||||||
-- receive_cb: function to receive chunks
|
-- sink: response message body sink
|
||||||
-- Returns
|
-- Returns
|
||||||
-- nil if successfull or an error message in case of error
|
-- nil if successfull or an error message in case of error
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function receive_body_bychunks(sock, headers, receive_cb)
|
local function receive_body_bychunks(sock, headers, sink)
|
||||||
local chunk, size, line, err, go, err_or_f, _
|
local chunk, size, line, err, go
|
||||||
while 1 do
|
while 1 do
|
||||||
-- get chunk size, skip extention
|
-- get chunk size, skip extention
|
||||||
line, err = try_receiving(sock)
|
line, err = try_receiving(sock)
|
||||||
if err then return abort(receive_cb, err) end
|
if err then return abort(sink, err) end
|
||||||
size = tonumber(string.gsub(line, ";.*", ""), 16)
|
size = tonumber(string.gsub(line, ";.*", ""), 16)
|
||||||
if not size then return abort(receive_cb, "invalid chunk size") end
|
if not size then return abort(sink, "invalid chunk size") end
|
||||||
-- was it the last chunk?
|
-- was it the last chunk?
|
||||||
if size <= 0 then break end
|
if size <= 0 then break end
|
||||||
-- get chunk
|
-- get chunk
|
||||||
chunk, err = try_receiving(sock, size)
|
chunk, err = try_receiving(sock, size)
|
||||||
if err then return abort(receive_cb, err) end
|
if err then return abort(sink, err) end
|
||||||
-- pass chunk to callback
|
-- pass chunk to callback
|
||||||
go, err_or_f = receive_cb(chunk)
|
go, err = sink(chunk)
|
||||||
-- see if callback needs to be replaced
|
|
||||||
receive_cb = err_or_f or receive_cb
|
|
||||||
-- see if callback aborted
|
-- see if callback aborted
|
||||||
if not go then return err_or_f or "aborted by callback" end
|
if not go then return err or "aborted by callback" end
|
||||||
-- skip CRLF on end of chunk
|
-- skip CRLF on end of chunk
|
||||||
_, err = try_receiving(sock)
|
err = second(try_receiving(sock))
|
||||||
if err then return abort(receive_cb, err) end
|
if err then return abort(sink, err) end
|
||||||
end
|
end
|
||||||
-- the server should not send trailer headers because we didn't send a
|
-- servers shouldn't send trailer headers, but who trusts them?
|
||||||
-- header informing it we know how to deal with them. we do not risk
|
err = second(receive_headers(sock, headers))
|
||||||
-- being caught unprepaired.
|
if err then return abort(sink, err) end
|
||||||
_, err = receive_headers(sock, headers)
|
|
||||||
if err then return abort(receive_cb, err) end
|
|
||||||
-- let callback know we are done
|
-- let callback know we are done
|
||||||
_, err_or_f = receive_cb("")
|
return second(sink(nil))
|
||||||
return err_or_f
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -180,94 +183,84 @@ end
|
|||||||
-- Input
|
-- Input
|
||||||
-- sock: socket connected to the server
|
-- sock: socket connected to the server
|
||||||
-- length: message body length
|
-- length: message body length
|
||||||
-- receive_cb: function to receive chunks
|
-- sink: response message body sink
|
||||||
-- Returns
|
-- Returns
|
||||||
-- nil if successfull or an error message in case of error
|
-- nil if successfull or an error message in case of error
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function receive_body_bylength(sock, length, receive_cb)
|
local function receive_body_bylength(sock, length, sink)
|
||||||
while length > 0 do
|
while length > 0 do
|
||||||
local size = math.min(BLOCKSIZE, length)
|
local size = math.min(BLOCKSIZE, length)
|
||||||
local chunk, err = sock:receive(size)
|
local chunk, err = sock:receive(size)
|
||||||
local go, err_or_f = receive_cb(chunk)
|
local go, cb_err = sink(chunk)
|
||||||
length = length - string.len(chunk)
|
length = length - string.len(chunk)
|
||||||
-- see if callback aborted
|
-- see if callback aborted
|
||||||
if not go then return err_or_f or "aborted by callback" end
|
if not go then return cb_err or "aborted by callback" end
|
||||||
-- see if callback needs to be replaced
|
|
||||||
receive_cb = err_or_f or receive_cb
|
|
||||||
-- see if there was an error
|
-- see if there was an error
|
||||||
if err and length > 0 then return abort(receive_cb, err) end
|
if err and length > 0 then return abort(sink, err) end
|
||||||
end
|
end
|
||||||
local _, err_or_f = receive_cb("")
|
return second(sink(nil))
|
||||||
return err_or_f
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Receives a message body by content-length
|
-- Receives a message body until the conection is closed
|
||||||
-- Input
|
-- Input
|
||||||
-- sock: socket connected to the server
|
-- sock: socket connected to the server
|
||||||
-- receive_cb: function to receive chunks
|
-- sink: response message body sink
|
||||||
-- Returns
|
-- Returns
|
||||||
-- nil if successfull or an error message in case of error
|
-- nil if successfull or an error message in case of error
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function receive_body_untilclosed(sock, receive_cb)
|
local function receive_body_untilclosed(sock, sink)
|
||||||
while 1 do
|
while 1 do
|
||||||
local chunk, err = sock:receive(BLOCKSIZE)
|
local chunk, err = sock:receive(BLOCKSIZE)
|
||||||
local go, err_or_f = receive_cb(chunk)
|
local go, cb_err = sink(chunk)
|
||||||
-- see if callback aborted
|
-- see if callback aborted
|
||||||
if not go then return err_or_f or "aborted by callback" end
|
if not go then return cb_err or "aborted by callback" end
|
||||||
-- see if callback needs to be replaced
|
|
||||||
receive_cb = err_or_f or receive_cb
|
|
||||||
-- see if we are done
|
-- see if we are done
|
||||||
if err == "closed" then
|
if err == "closed" then return chunk and second(sink(nil)) end
|
||||||
if chunk ~= "" then
|
|
||||||
go, err_or_f = receive_cb("")
|
|
||||||
return err_or_f
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- see if there was an error
|
-- see if there was an error
|
||||||
if err then return abort(receive_cb, err) end
|
if err then return abort(sink, err) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Receives HTTP response body
|
-- Receives the HTTP response body
|
||||||
-- Input
|
-- Input
|
||||||
-- sock: socket connected to the server
|
-- sock: socket connected to the server
|
||||||
-- headers: response header fields
|
-- headers: response header fields
|
||||||
-- receive_cb: function to receive chunks
|
-- sink: response message body sink
|
||||||
-- Returns
|
-- Returns
|
||||||
-- nil if successfull or an error message in case of error
|
-- nil if successfull or an error message in case of error
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function receive_body(sock, headers, receive_cb)
|
local function receive_body(sock, headers, sink)
|
||||||
|
-- make sure sink is not fancy
|
||||||
|
sink = ltn12.sink.simplify(sink)
|
||||||
local te = headers["transfer-encoding"]
|
local te = headers["transfer-encoding"]
|
||||||
if te and te ~= "identity" then
|
if te and te ~= "identity" then
|
||||||
-- get by chunked transfer-coding of message body
|
-- get by chunked transfer-coding of message body
|
||||||
return receive_body_bychunks(sock, headers, receive_cb)
|
return receive_body_bychunks(sock, headers, sink)
|
||||||
elseif tonumber(headers["content-length"]) then
|
elseif tonumber(headers["content-length"]) then
|
||||||
-- get by content-length
|
-- get by content-length
|
||||||
local length = tonumber(headers["content-length"])
|
local length = tonumber(headers["content-length"])
|
||||||
return receive_body_bylength(sock, length, receive_cb)
|
return receive_body_bylength(sock, length, sink)
|
||||||
else
|
else
|
||||||
-- get it all until connection closes
|
-- get it all until connection closes
|
||||||
return receive_body_untilclosed(sock, receive_cb)
|
return receive_body_untilclosed(sock, sink)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Sends data comming from a callback
|
-- Sends the HTTP request message body in chunks
|
||||||
-- Input
|
-- Input
|
||||||
-- data: data connection
|
-- data: data connection
|
||||||
-- send_cb: callback to produce file contents
|
-- source: request message body source
|
||||||
-- Returns
|
-- Returns
|
||||||
-- nil if successfull, or an error message in case of error
|
-- nil if successfull, or an error message in case of error
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function send_body_bychunks(data, send_cb)
|
local function send_body_bychunks(data, source)
|
||||||
while 1 do
|
while 1 do
|
||||||
local chunk, err_or_f = send_cb()
|
local chunk, cb_err = source()
|
||||||
-- check if callback aborted
|
-- check if callback aborted
|
||||||
if not chunk then return err_or_f or "aborted by callback" end
|
if not chunk then return cb_err or "aborted by callback" end
|
||||||
-- check if callback should be replaced
|
|
||||||
send_cb = err_or_f or send_cb
|
|
||||||
-- if we are done, send last-chunk
|
-- if we are done, send last-chunk
|
||||||
if chunk == "" then return try_sending(data, "0\r\n\r\n") end
|
if chunk == "" then return try_sending(data, "0\r\n\r\n") end
|
||||||
-- else send middle chunk
|
-- else send middle chunk
|
||||||
@ -281,22 +274,18 @@ local function send_body_bychunks(data, send_cb)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Sends data comming from a callback
|
-- Sends the HTTP request message body
|
||||||
-- Input
|
-- Input
|
||||||
-- data: data connection
|
-- data: data connection
|
||||||
-- send_cb: callback to produce body contents
|
-- source: request message body source
|
||||||
-- Returns
|
-- Returns
|
||||||
-- nil if successfull, or an error message in case of error
|
-- nil if successfull, or an error message in case of error
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function send_body_bylength(data, send_cb)
|
local function send_body(data, source)
|
||||||
while 1 do
|
while 1 do
|
||||||
local chunk, err_or_f = send_cb()
|
local chunk, cb_err = source()
|
||||||
-- check if callback aborted
|
|
||||||
if not chunk then return err_or_f or "aborted by callback" end
|
|
||||||
-- check if callback should be replaced
|
|
||||||
send_cb = err_or_f or send_cb
|
|
||||||
-- check if callback is done
|
-- check if callback is done
|
||||||
if chunk == "" then return end
|
if not chunk then return cb_err end
|
||||||
-- send data
|
-- send data
|
||||||
local err = try_sending(data, chunk)
|
local err = try_sending(data, chunk)
|
||||||
if err then return err end
|
if err then return err end
|
||||||
@ -304,10 +293,10 @@ local function send_body_bylength(data, send_cb)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- Sends mime headers
|
-- Sends request headers
|
||||||
-- Input
|
-- Input
|
||||||
-- sock: server socket
|
-- sock: server socket
|
||||||
-- headers: table with mime headers to be sent
|
-- headers: table with headers to be sent
|
||||||
-- Returns
|
-- Returns
|
||||||
-- err: error message if any
|
-- err: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -330,27 +319,29 @@ end
|
|||||||
-- method: request method to be used
|
-- method: request method to be used
|
||||||
-- uri: request uri
|
-- uri: request uri
|
||||||
-- headers: request headers to be sent
|
-- headers: request headers to be sent
|
||||||
-- body_cb: callback to send request message body
|
-- source: request message body source
|
||||||
-- Returns
|
-- Returns
|
||||||
-- err: nil in case of success, error message otherwise
|
-- err: nil in case of success, error message otherwise
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function send_request(sock, method, uri, headers, body_cb)
|
local function send_request(sock, method, uri, headers, source)
|
||||||
local chunk, size, done, err
|
local chunk, size, done, err
|
||||||
-- send request line
|
-- send request line
|
||||||
err = try_sending(sock, method .. " " .. uri .. " HTTP/1.1\r\n")
|
err = try_sending(sock, method .. " " .. uri .. " HTTP/1.1\r\n")
|
||||||
if err then return err end
|
if err then return err end
|
||||||
if body_cb and not headers["content-length"] then
|
if source and not headers["content-length"] then
|
||||||
headers["transfer-encoding"] = "chunked"
|
headers["transfer-encoding"] = "chunked"
|
||||||
end
|
end
|
||||||
-- send request headers
|
-- send request headers
|
||||||
err = send_headers(sock, headers)
|
err = send_headers(sock, headers)
|
||||||
if err then return err end
|
if err then return err end
|
||||||
-- send request message body, if any
|
-- send request message body, if any
|
||||||
if body_cb then
|
if source then
|
||||||
if not headers["content-length"] then
|
-- make sure source is not fancy
|
||||||
return send_body_bychunks(sock, body_cb)
|
source = ltn12.source.simplify(source)
|
||||||
|
if headers["content-length"] then
|
||||||
|
return send_body(sock, source)
|
||||||
else
|
else
|
||||||
return send_body_bylength(sock, body_cb)
|
return send_body_bychunks(sock, source)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -415,23 +406,23 @@ end
|
|||||||
-- Input
|
-- Input
|
||||||
-- reqt: a table with the original request information
|
-- reqt: a table with the original request information
|
||||||
-- parsed: parsed request URL
|
-- parsed: parsed request URL
|
||||||
-- respt: a table with the server response information
|
|
||||||
-- Returns
|
-- Returns
|
||||||
-- respt: result of target authorization
|
-- respt: result of target authorization
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
local function authorize(reqt, parsed, respt)
|
local function authorize(reqt, parsed)
|
||||||
reqt.headers["authorization"] = "Basic " ..
|
reqt.headers["authorization"] = "Basic " ..
|
||||||
(socket.mime.b64(parsed.user .. ":" .. parsed.password))
|
(mime.b64(parsed.user .. ":" .. parsed.password))
|
||||||
local autht = {
|
local autht = {
|
||||||
nredirects = reqt.nredirects,
|
nredirects = reqt.nredirects,
|
||||||
method = reqt.method,
|
method = reqt.method,
|
||||||
url = reqt.url,
|
url = reqt.url,
|
||||||
body_cb = reqt.body_cb,
|
source = reqt.source,
|
||||||
|
sink = reqt.sink,
|
||||||
headers = reqt.headers,
|
headers = reqt.headers,
|
||||||
timeout = reqt.timeout,
|
timeout = reqt.timeout,
|
||||||
proxy = reqt.proxy,
|
proxy = reqt.proxy,
|
||||||
}
|
}
|
||||||
return request_cb(autht, respt)
|
return request_cb(autht)
|
||||||
end
|
end
|
||||||
|
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -453,8 +444,7 @@ end
|
|||||||
-- Returns the result of a request following a server redirect message.
|
-- Returns the result of a request following a server redirect message.
|
||||||
-- Input
|
-- Input
|
||||||
-- reqt: a table with the original request information
|
-- reqt: a table with the original request information
|
||||||
-- respt: a table with the following fields:
|
-- respt: response table of previous attempt
|
||||||
-- body_cb: response method body receive-callback
|
|
||||||
-- Returns
|
-- Returns
|
||||||
-- respt: result of target redirection
|
-- respt: result of target redirection
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
@ -467,12 +457,13 @@ local function redirect(reqt, respt)
|
|||||||
-- the RFC says the redirect URL has to be absolute, but some
|
-- the RFC says the redirect URL has to be absolute, but some
|
||||||
-- servers do not respect that
|
-- servers do not respect that
|
||||||
url = socket.url.absolute(reqt.url, respt.headers["location"]),
|
url = socket.url.absolute(reqt.url, respt.headers["location"]),
|
||||||
body_cb = reqt.body_cb,
|
source = reqt.source,
|
||||||
|
sink = reqt.sink,
|
||||||
headers = reqt.headers,
|
headers = reqt.headers,
|
||||||
timeout = reqt.timeout,
|
timeout = reqt.timeout,
|
||||||
proxy = reqt.proxy
|
proxy = reqt.proxy
|
||||||
}
|
}
|
||||||
respt = request_cb(redirt, respt)
|
respt = request_cb(redirt)
|
||||||
-- we pass the location header as a clue we tried to redirect
|
-- we pass the location header as a clue we tried to redirect
|
||||||
if respt.headers then respt.headers.location = redirt.url end
|
if respt.headers then respt.headers.location = redirt.url end
|
||||||
return respt
|
return respt
|
||||||
@ -562,10 +553,9 @@ end
|
|||||||
-- url: target uniform resource locator
|
-- url: target uniform resource locator
|
||||||
-- user, password: authentication information
|
-- user, password: authentication information
|
||||||
-- headers: request headers to send, or nil if none
|
-- headers: request headers to send, or nil if none
|
||||||
-- body_cb: request message body send-callback, or nil if none
|
-- source: request message body source, or nil if none
|
||||||
|
-- sink: response message body sink
|
||||||
-- redirect: should we refrain from following a server redirect message?
|
-- redirect: should we refrain from following a server redirect message?
|
||||||
-- respt: a table with the following fields:
|
|
||||||
-- body_cb: response method body receive-callback
|
|
||||||
-- Returns
|
-- Returns
|
||||||
-- respt: a table with the following fields:
|
-- respt: a table with the following fields:
|
||||||
-- headers: response header fields received, or nil if failed
|
-- headers: response header fields received, or nil if failed
|
||||||
@ -573,7 +563,7 @@ end
|
|||||||
-- code: server status code, or nil if failed
|
-- code: server status code, or nil if failed
|
||||||
-- error: error message, or nil if successfull
|
-- error: error message, or nil if successfull
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function request_cb(reqt, respt)
|
function request_cb(reqt)
|
||||||
local sock, ret
|
local sock, ret
|
||||||
local parsed = socket.url.parse(reqt.url, {
|
local parsed = socket.url.parse(reqt.url, {
|
||||||
host = "",
|
host = "",
|
||||||
@ -581,6 +571,7 @@ function request_cb(reqt, respt)
|
|||||||
path ="/",
|
path ="/",
|
||||||
scheme = "http"
|
scheme = "http"
|
||||||
})
|
})
|
||||||
|
local respt = {}
|
||||||
if parsed.scheme ~= "http" then
|
if parsed.scheme ~= "http" then
|
||||||
respt.error = string.format("unknown scheme '%s'", parsed.scheme)
|
respt.error = string.format("unknown scheme '%s'", parsed.scheme)
|
||||||
return respt
|
return respt
|
||||||
@ -597,7 +588,7 @@ function request_cb(reqt, respt)
|
|||||||
if not sock then return respt end
|
if not sock then return respt end
|
||||||
-- send request message
|
-- send request message
|
||||||
respt.error = send_request(sock, reqt.method,
|
respt.error = send_request(sock, reqt.method,
|
||||||
request_uri(reqt, parsed), reqt.headers, reqt.body_cb)
|
request_uri(reqt, parsed), reqt.headers, reqt.source)
|
||||||
if respt.error then
|
if respt.error then
|
||||||
sock:close()
|
sock:close()
|
||||||
return respt
|
return respt
|
||||||
@ -619,18 +610,18 @@ function request_cb(reqt, respt)
|
|||||||
-- decide what to do based on request and response parameters
|
-- decide what to do based on request and response parameters
|
||||||
if should_redirect(reqt, respt) then
|
if should_redirect(reqt, respt) then
|
||||||
-- drop the body
|
-- drop the body
|
||||||
receive_body(sock, respt.headers, function (c, e) return 1 end)
|
receive_body(sock, respt.headers, ltn12.sink.null())
|
||||||
-- we are done with this connection
|
-- we are done with this connection
|
||||||
sock:close()
|
sock:close()
|
||||||
return redirect(reqt, respt)
|
return redirect(reqt, respt)
|
||||||
elseif should_authorize(reqt, parsed, respt) then
|
elseif should_authorize(reqt, parsed, respt) then
|
||||||
-- drop the body
|
-- drop the body
|
||||||
receive_body(sock, respt.headers, function (c, e) return 1 end)
|
receive_body(sock, respt.headers, ltn12.sink.null())
|
||||||
-- we are done with this connection
|
-- we are done with this connection
|
||||||
sock:close()
|
sock:close()
|
||||||
return authorize(reqt, parsed, respt)
|
return authorize(reqt, parsed, respt)
|
||||||
elseif should_receive_body(reqt, respt) then
|
elseif should_receive_body(reqt, respt) then
|
||||||
respt.error = receive_body(sock, respt.headers, respt.body_cb)
|
respt.error = receive_body(sock, respt.headers, reqt.sink)
|
||||||
if respt.error then return respt end
|
if respt.error then return respt end
|
||||||
sock:close()
|
sock:close()
|
||||||
return respt
|
return respt
|
||||||
@ -658,13 +649,11 @@ end
|
|||||||
-- error: error message if any
|
-- error: error message if any
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
function request(reqt)
|
function request(reqt)
|
||||||
local respt = {}
|
reqt.source = reqt.body and ltn12.source.string(reqt.body)
|
||||||
reqt.body_cb = socket.callback.send.string(reqt.body)
|
local t = {}
|
||||||
local concat = socket.concat.create()
|
reqt.sink = ltn12.sink.table(t)
|
||||||
respt.body_cb = socket.callback.receive.concat(concat)
|
local respt = request_cb(reqt)
|
||||||
respt = request_cb(reqt, respt)
|
if table.getn(t) > 0 then respt.body = table.concat(t) end
|
||||||
respt.body = concat:getresult()
|
|
||||||
respt.body_cb = nil
|
|
||||||
return respt
|
return respt
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -713,4 +702,4 @@ function post(url_or_request, body)
|
|||||||
return respt.body, respt.headers, respt.code, respt.error
|
return respt.body, respt.headers, respt.code, respt.error
|
||||||
end
|
end
|
||||||
|
|
||||||
return http
|
return socket.http
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
-- create code namespace inside LuaSocket namespace
|
-- create module namespace
|
||||||
ltn12 = ltn12 or {}
|
ltn12 = ltn12 or {}
|
||||||
-- make all module globals fall into mime namespace
|
-- make all globals fall into ltn12 namespace
|
||||||
setmetatable(ltn12, { __index = _G })
|
setmetatable(ltn12, { __index = _G })
|
||||||
setfenv(1, ltn12)
|
setfenv(1, ltn12)
|
||||||
|
|
||||||
@ -12,6 +12,14 @@ sink = {}
|
|||||||
-- 2048 seems to be better in windows...
|
-- 2048 seems to be better in windows...
|
||||||
BLOCKSIZE = 2048
|
BLOCKSIZE = 2048
|
||||||
|
|
||||||
|
local function second(a, b)
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
|
||||||
|
local function skip(a, b, c)
|
||||||
|
return b, c
|
||||||
|
end
|
||||||
|
|
||||||
-- returns a high level filter that cycles a cycles a low-level filter
|
-- returns a high level filter that cycles a cycles a low-level filter
|
||||||
function filter.cycle(low, ctx, extra)
|
function filter.cycle(low, ctx, extra)
|
||||||
return function(chunk)
|
return function(chunk)
|
||||||
@ -24,9 +32,7 @@ end
|
|||||||
-- chains two filters together
|
-- chains two filters together
|
||||||
local function chain2(f1, f2)
|
local function chain2(f1, f2)
|
||||||
return function(chunk)
|
return function(chunk)
|
||||||
local ret = f2(f1(chunk))
|
return f2(f1(chunk))
|
||||||
if chunk then return ret
|
|
||||||
else return ret .. f2() end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -83,7 +89,6 @@ end
|
|||||||
-- creates rewindable source
|
-- creates rewindable source
|
||||||
function source.rewind(src)
|
function source.rewind(src)
|
||||||
local t = {}
|
local t = {}
|
||||||
src = source.simplify(src)
|
|
||||||
return function(chunk)
|
return function(chunk)
|
||||||
if not chunk then
|
if not chunk then
|
||||||
chunk = table.remove(t)
|
chunk = table.remove(t)
|
||||||
@ -97,13 +102,38 @@ end
|
|||||||
|
|
||||||
-- chains a source with a filter
|
-- chains a source with a filter
|
||||||
function source.chain(src, f)
|
function source.chain(src, f)
|
||||||
src = source.simplify(src)
|
local co = coroutine.create(function()
|
||||||
local chain = function()
|
while true do
|
||||||
local chunk, err = src()
|
local chunk, err = src()
|
||||||
if not chunk then return f(nil), source.empty(err)
|
local filtered = f(chunk)
|
||||||
else return f(chunk) end
|
local done = chunk and ""
|
||||||
|
while true do
|
||||||
|
coroutine.yield(filtered)
|
||||||
|
if filtered == done then break end
|
||||||
|
filtered = f(done)
|
||||||
end
|
end
|
||||||
return source.simplify(chain)
|
if not chunk then return nil, err end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return function()
|
||||||
|
return skip(coroutine.resume(co))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- creates a source that produces contents of several files one after the
|
||||||
|
-- other, as if they were concatenated
|
||||||
|
function source.cat(...)
|
||||||
|
local co = coroutine.create(function()
|
||||||
|
local i = 1
|
||||||
|
while i <= table.getn(arg) do
|
||||||
|
local chunk = arg[i]:read(2048)
|
||||||
|
if chunk then coroutine.yield(chunk)
|
||||||
|
else i = i + 1 end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return source.simplify(function()
|
||||||
|
return second(coroutine.resume(co))
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- creates a sink that stores into a table
|
-- creates a sink that stores into a table
|
||||||
@ -150,19 +180,22 @@ end
|
|||||||
|
|
||||||
-- chains a sink with a filter
|
-- chains a sink with a filter
|
||||||
function sink.chain(f, snk)
|
function sink.chain(f, snk)
|
||||||
snk = sink.simplify(snk)
|
|
||||||
return function(chunk, err)
|
return function(chunk, err)
|
||||||
local r, e = snk(f(chunk))
|
local filtered = f(chunk)
|
||||||
if not r then return nil, e end
|
local done = chunk and ""
|
||||||
if not chunk then return snk(nil, err) end
|
while true do
|
||||||
return 1
|
local ret, snkerr = snk(filtered, err)
|
||||||
|
if not ret then return nil, snkerr end
|
||||||
|
if filtered == done then return 1 end
|
||||||
|
filtered = f(done)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- pumps all data from a source to a sink
|
-- pumps all data from a source to a sink
|
||||||
function pump(src, snk)
|
function pump(src, snk)
|
||||||
snk = sink.simplify(snk)
|
while true do
|
||||||
for chunk, src_err in source.simplify(src) do
|
local chunk, src_err = src()
|
||||||
local ret, snk_err = snk(chunk, src_err)
|
local ret, snk_err = snk(chunk, src_err)
|
||||||
if not chunk or not ret then
|
if not chunk or not ret then
|
||||||
return not src_err and not snk_err, src_err or snk_err
|
return not src_err and not snk_err, src_err or snk_err
|
||||||
|
@ -74,22 +74,16 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
|
|||||||
#ifdef LUASOCKET_COMPILED
|
#ifdef LUASOCKET_COMPILED
|
||||||
#include "ltn12.lch"
|
#include "ltn12.lch"
|
||||||
#include "auxiliar.lch"
|
#include "auxiliar.lch"
|
||||||
#include "concat.lch"
|
|
||||||
#include "url.lch"
|
#include "url.lch"
|
||||||
#include "callback.lch"
|
|
||||||
#include "mime.lch"
|
#include "mime.lch"
|
||||||
#include "smtp.lch"
|
#include "smtp.lch"
|
||||||
#include "ftp.lch"
|
|
||||||
#include "http.lch"
|
#include "http.lch"
|
||||||
#else
|
#else
|
||||||
lua_dofile(L, "ltn12.lua");
|
lua_dofile(L, "ltn12.lua");
|
||||||
lua_dofile(L, "auxiliar.lua");
|
lua_dofile(L, "auxiliar.lua");
|
||||||
lua_dofile(L, "concat.lua");
|
|
||||||
lua_dofile(L, "url.lua");
|
lua_dofile(L, "url.lua");
|
||||||
lua_dofile(L, "callback.lua");
|
|
||||||
lua_dofile(L, "mime.lua");
|
lua_dofile(L, "mime.lua");
|
||||||
lua_dofile(L, "smtp.lua");
|
lua_dofile(L, "smtp.lua");
|
||||||
lua_dofile(L, "ftp.lua");
|
|
||||||
lua_dofile(L, "http.lua");
|
lua_dofile(L, "http.lua");
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
|
119
src/mime.c
119
src/mime.c
@ -20,8 +20,8 @@
|
|||||||
#define SP 0x20
|
#define SP 0x20
|
||||||
|
|
||||||
typedef unsigned char UC;
|
typedef unsigned char UC;
|
||||||
static const char CRLF[2] = {CR, LF};
|
static const char CRLF[] = {CR, LF, 0};
|
||||||
static const char EQCRLF[3] = {'=', CR, LF};
|
static const char EQCRLF[] = {'=', CR, LF, 0};
|
||||||
|
|
||||||
/*=========================================================================*\
|
/*=========================================================================*\
|
||||||
* Internal function prototypes.
|
* Internal function prototypes.
|
||||||
@ -95,7 +95,7 @@ int mime_open(lua_State *L)
|
|||||||
* Global Lua functions
|
* Global Lua functions
|
||||||
\*=========================================================================*/
|
\*=========================================================================*/
|
||||||
/*-------------------------------------------------------------------------*\
|
/*-------------------------------------------------------------------------*\
|
||||||
* Incrementaly breaks a string into lines
|
* Incrementaly breaks a string into lines. The string can have CRLF breaks.
|
||||||
* A, n = wrp(l, B, length)
|
* A, n = wrp(l, B, length)
|
||||||
* A is a copy of B, broken into lines of at most 'length' bytes.
|
* A is a copy of B, broken into lines of at most 'length' bytes.
|
||||||
* 'l' is how many bytes are left for the first line of B.
|
* 'l' is how many bytes are left for the first line of B.
|
||||||
@ -109,6 +109,15 @@ static int mime_global_wrp(lua_State *L)
|
|||||||
const UC *last = input + size;
|
const UC *last = input + size;
|
||||||
int length = (int) luaL_optnumber(L, 3, 76);
|
int length = (int) luaL_optnumber(L, 3, 76);
|
||||||
luaL_Buffer buffer;
|
luaL_Buffer buffer;
|
||||||
|
/* end of input black-hole */
|
||||||
|
if (!input) {
|
||||||
|
/* if last line has not been terminated, add a line break */
|
||||||
|
if (left < length) lua_pushstring(L, CRLF);
|
||||||
|
/* otherwise, we are done */
|
||||||
|
else lua_pushnil(L);
|
||||||
|
lua_pushnumber(L, length);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
luaL_buffinit(L, &buffer);
|
luaL_buffinit(L, &buffer);
|
||||||
while (input < last) {
|
while (input < last) {
|
||||||
switch (*input) {
|
switch (*input) {
|
||||||
@ -129,11 +138,6 @@ static int mime_global_wrp(lua_State *L)
|
|||||||
}
|
}
|
||||||
input++;
|
input++;
|
||||||
}
|
}
|
||||||
/* if in last chunk and last line wasn't terminated, add a line-break */
|
|
||||||
if (!input && left < length) {
|
|
||||||
luaL_addstring(&buffer, CRLF);
|
|
||||||
left = length;
|
|
||||||
}
|
|
||||||
luaL_pushresult(&buffer);
|
luaL_pushresult(&buffer);
|
||||||
lua_pushnumber(L, left);
|
lua_pushnumber(L, left);
|
||||||
return 2;
|
return 2;
|
||||||
@ -200,7 +204,6 @@ static size_t b64pad(const UC *input, size_t size,
|
|||||||
code[0] = b64base[value];
|
code[0] = b64base[value];
|
||||||
luaL_addlstring(buffer, (char *) code, 4);
|
luaL_addlstring(buffer, (char *) code, 4);
|
||||||
break;
|
break;
|
||||||
case 0: /* fall through */
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -250,19 +253,31 @@ static int mime_global_b64(lua_State *L)
|
|||||||
{
|
{
|
||||||
UC atom[3];
|
UC atom[3];
|
||||||
size_t isize = 0, asize = 0;
|
size_t isize = 0, asize = 0;
|
||||||
const UC *input = (UC *) luaL_checklstring(L, 1, &isize);
|
const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
|
||||||
const UC *last = input + isize;
|
const UC *last = input + isize;
|
||||||
luaL_Buffer buffer;
|
luaL_Buffer buffer;
|
||||||
|
/* end-of-input blackhole */
|
||||||
|
if (!input) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* process first part of the input */
|
||||||
luaL_buffinit(L, &buffer);
|
luaL_buffinit(L, &buffer);
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = b64encode(*input++, atom, asize, &buffer);
|
asize = b64encode(*input++, atom, asize, &buffer);
|
||||||
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
||||||
if (input) {
|
/* if second part is nil, we are done */
|
||||||
|
if (!input) {
|
||||||
|
asize = b64pad(atom, asize, &buffer);
|
||||||
|
luaL_pushresult(&buffer);
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* otherwise process the second part */
|
||||||
last = input + isize;
|
last = input + isize;
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = b64encode(*input++, atom, asize, &buffer);
|
asize = b64encode(*input++, atom, asize, &buffer);
|
||||||
} else
|
|
||||||
asize = b64pad(atom, asize, &buffer);
|
|
||||||
luaL_pushresult(&buffer);
|
luaL_pushresult(&buffer);
|
||||||
lua_pushlstring(L, (char *) atom, asize);
|
lua_pushlstring(L, (char *) atom, asize);
|
||||||
return 2;
|
return 2;
|
||||||
@ -278,20 +293,30 @@ static int mime_global_unb64(lua_State *L)
|
|||||||
{
|
{
|
||||||
UC atom[4];
|
UC atom[4];
|
||||||
size_t isize = 0, asize = 0;
|
size_t isize = 0, asize = 0;
|
||||||
const UC *input = (UC *) luaL_checklstring(L, 1, &isize);
|
const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
|
||||||
const UC *last = input + isize;
|
const UC *last = input + isize;
|
||||||
luaL_Buffer buffer;
|
luaL_Buffer buffer;
|
||||||
|
/* end-of-input blackhole */
|
||||||
|
if (!input) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* process first part of the input */
|
||||||
luaL_buffinit(L, &buffer);
|
luaL_buffinit(L, &buffer);
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = b64decode(*input++, atom, asize, &buffer);
|
asize = b64decode(*input++, atom, asize, &buffer);
|
||||||
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
||||||
if (input) {
|
/* if second is nil, we are done */
|
||||||
|
if (!input) {
|
||||||
|
luaL_pushresult(&buffer);
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* otherwise, process the rest of the input */
|
||||||
last = input + isize;
|
last = input + isize;
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = b64decode(*input++, atom, asize, &buffer);
|
asize = b64decode(*input++, atom, asize, &buffer);
|
||||||
}
|
|
||||||
/* if !input we are done. if atom > 0, the remaning is invalid. we just
|
|
||||||
* return it undecoded. */
|
|
||||||
luaL_pushresult(&buffer);
|
luaL_pushresult(&buffer);
|
||||||
lua_pushlstring(L, (char *) atom, asize);
|
lua_pushlstring(L, (char *) atom, asize);
|
||||||
return 2;
|
return 2;
|
||||||
@ -425,16 +450,27 @@ static int mime_global_qp(lua_State *L)
|
|||||||
const UC *last = input + isize;
|
const UC *last = input + isize;
|
||||||
const char *marker = luaL_optstring(L, 3, CRLF);
|
const char *marker = luaL_optstring(L, 3, CRLF);
|
||||||
luaL_Buffer buffer;
|
luaL_Buffer buffer;
|
||||||
|
/* end-of-input blackhole */
|
||||||
|
if (!input) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* process first part of input */
|
||||||
luaL_buffinit(L, &buffer);
|
luaL_buffinit(L, &buffer);
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = qpencode(*input++, atom, asize, marker, &buffer);
|
asize = qpencode(*input++, atom, asize, marker, &buffer);
|
||||||
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
||||||
if (input) {
|
/* if second part is nil, we are done */
|
||||||
|
if (!input) {
|
||||||
|
asize = qppad(atom, asize, &buffer);
|
||||||
|
luaL_pushresult(&buffer);
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
/* otherwise process rest of input */
|
||||||
last = input + isize;
|
last = input + isize;
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = qpencode(*input++, atom, asize, marker, &buffer);
|
asize = qpencode(*input++, atom, asize, marker, &buffer);
|
||||||
} else
|
|
||||||
asize = qppad(atom, asize, &buffer);
|
|
||||||
luaL_pushresult(&buffer);
|
luaL_pushresult(&buffer);
|
||||||
lua_pushlstring(L, (char *) atom, asize);
|
lua_pushlstring(L, (char *) atom, asize);
|
||||||
return 2;
|
return 2;
|
||||||
@ -487,21 +523,32 @@ static size_t qpdecode(UC c, UC *input, size_t size,
|
|||||||
\*-------------------------------------------------------------------------*/
|
\*-------------------------------------------------------------------------*/
|
||||||
static int mime_global_unqp(lua_State *L)
|
static int mime_global_unqp(lua_State *L)
|
||||||
{
|
{
|
||||||
|
|
||||||
size_t asize = 0, isize = 0;
|
size_t asize = 0, isize = 0;
|
||||||
UC atom[3];
|
UC atom[3];
|
||||||
const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
|
const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
|
||||||
const UC *last = input + isize;
|
const UC *last = input + isize;
|
||||||
luaL_Buffer buffer;
|
luaL_Buffer buffer;
|
||||||
|
/* end-of-input blackhole */
|
||||||
|
if (!input) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* process first part of input */
|
||||||
luaL_buffinit(L, &buffer);
|
luaL_buffinit(L, &buffer);
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = qpdecode(*input++, atom, asize, &buffer);
|
asize = qpdecode(*input++, atom, asize, &buffer);
|
||||||
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
|
||||||
if (input) {
|
/* if second part is nil, we are done */
|
||||||
|
if (!input) {
|
||||||
|
luaL_pushresult(&buffer);
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* otherwise process rest of input */
|
||||||
last = input + isize;
|
last = input + isize;
|
||||||
while (input < last)
|
while (input < last)
|
||||||
asize = qpdecode(*input++, atom, asize, &buffer);
|
asize = qpdecode(*input++, atom, asize, &buffer);
|
||||||
}
|
|
||||||
luaL_pushresult(&buffer);
|
luaL_pushresult(&buffer);
|
||||||
lua_pushlstring(L, (char *) atom, asize);
|
lua_pushlstring(L, (char *) atom, asize);
|
||||||
return 2;
|
return 2;
|
||||||
@ -524,6 +571,14 @@ static int mime_global_qpwrp(lua_State *L)
|
|||||||
const UC *last = input + size;
|
const UC *last = input + size;
|
||||||
int length = (int) luaL_optnumber(L, 3, 76);
|
int length = (int) luaL_optnumber(L, 3, 76);
|
||||||
luaL_Buffer buffer;
|
luaL_Buffer buffer;
|
||||||
|
/* end-of-input blackhole */
|
||||||
|
if (!input) {
|
||||||
|
if (left < length) lua_pushstring(L, EQCRLF);
|
||||||
|
else lua_pushnil(L);
|
||||||
|
lua_pushnumber(L, length);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
/* process all input */
|
||||||
luaL_buffinit(L, &buffer);
|
luaL_buffinit(L, &buffer);
|
||||||
while (input < last) {
|
while (input < last) {
|
||||||
switch (*input) {
|
switch (*input) {
|
||||||
@ -552,11 +607,6 @@ static int mime_global_qpwrp(lua_State *L)
|
|||||||
}
|
}
|
||||||
input++;
|
input++;
|
||||||
}
|
}
|
||||||
/* if in last chunk and last line wasn't terminated, add a soft-break */
|
|
||||||
if (!input && left < length) {
|
|
||||||
luaL_addstring(&buffer, EQCRLF);
|
|
||||||
left = length;
|
|
||||||
}
|
|
||||||
luaL_pushresult(&buffer);
|
luaL_pushresult(&buffer);
|
||||||
lua_pushnumber(L, left);
|
lua_pushnumber(L, left);
|
||||||
return 2;
|
return 2;
|
||||||
@ -609,13 +659,16 @@ static int mime_global_eol(lua_State *L)
|
|||||||
const char *marker = luaL_optstring(L, 3, CRLF);
|
const char *marker = luaL_optstring(L, 3, CRLF);
|
||||||
luaL_Buffer buffer;
|
luaL_Buffer buffer;
|
||||||
luaL_buffinit(L, &buffer);
|
luaL_buffinit(L, &buffer);
|
||||||
while (input < last)
|
|
||||||
ctx = eolprocess(*input++, ctx, marker, &buffer);
|
|
||||||
/* if the last character was a candidate, we output a new line */
|
/* if the last character was a candidate, we output a new line */
|
||||||
if (!input) {
|
if (!input) {
|
||||||
if (eolcandidate(ctx)) luaL_addstring(&buffer, marker);
|
if (eolcandidate(ctx)) lua_pushstring(L, marker);
|
||||||
ctx = 0;
|
else lua_pushnil(L);
|
||||||
|
lua_pushnumber(L, 0);
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
|
/* process all input */
|
||||||
|
while (input < last)
|
||||||
|
ctx = eolprocess(*input++, ctx, marker, &buffer);
|
||||||
luaL_pushresult(&buffer);
|
luaL_pushresult(&buffer);
|
||||||
lua_pushnumber(L, ctx);
|
lua_pushnumber(L, ctx);
|
||||||
return 2;
|
return 2;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
if not ltn12 then error('This module requires LTN12') end
|
if not ltn12 then error('Requires LTN12 module') end
|
||||||
-- create mime namespace
|
-- create mime namespace
|
||||||
mime = mime or {}
|
mime = mime or {}
|
||||||
-- make all module globals fall into mime namespace
|
-- make all module globals fall into mime namespace
|
||||||
|
@ -19,7 +19,7 @@ DOMAIN = os.getenv("SERVER_NAME") or "localhost"
|
|||||||
SERVER = "localhost"
|
SERVER = "localhost"
|
||||||
|
|
||||||
function stuff()
|
function stuff()
|
||||||
return socket.cicle(dot, 2)
|
return ltn12.filter.cycle(dot, 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- tries to get a pattern from the server and closes socket on error
|
-- tries to get a pattern from the server and closes socket on error
|
||||||
|
111
src/tp.lua
Normal file
111
src/tp.lua
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- Unified SMTP/FTP subsystem
|
||||||
|
-- LuaSocket toolkit.
|
||||||
|
-- Author: Diego Nehab
|
||||||
|
-- Conforming to: RFC 2616, LTN7
|
||||||
|
-- RCS ID: $Id$
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- make sure LuaSocket is loaded
|
||||||
|
if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
|
||||||
|
-- get LuaSocket namespace
|
||||||
|
local socket = _G[LUASOCKET_LIBNAME]
|
||||||
|
if not socket then error('module requires LuaSocket') end
|
||||||
|
-- create namespace inside LuaSocket namespace
|
||||||
|
socket.tp = socket.tp or {}
|
||||||
|
-- make all module globals fall into namespace
|
||||||
|
setmetatable(socket.tp, { __index = _G })
|
||||||
|
setfenv(1, socket.tp)
|
||||||
|
|
||||||
|
TIMEOUT = 60
|
||||||
|
|
||||||
|
-- tries to get a pattern from the server and closes socket on error
|
||||||
|
local function try_receiving(sock, pattern)
|
||||||
|
local data, message = sock:receive(pattern)
|
||||||
|
if not data then sock:close() end
|
||||||
|
return data, message
|
||||||
|
end
|
||||||
|
|
||||||
|
-- tries to send data to server and closes socket on error
|
||||||
|
local function try_sending(sock, data)
|
||||||
|
local sent, message = sock:send(data)
|
||||||
|
if not sent then sock:close() end
|
||||||
|
return sent, message
|
||||||
|
end
|
||||||
|
|
||||||
|
-- gets server reply
|
||||||
|
local function get_reply(sock)
|
||||||
|
local code, current, separator, _
|
||||||
|
local line, message = try_receiving(sock)
|
||||||
|
local reply = line
|
||||||
|
if message then return nil, message end
|
||||||
|
_, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
|
||||||
|
if not code then return nil, "invalid server reply" end
|
||||||
|
if separator == "-" then -- reply is multiline
|
||||||
|
repeat
|
||||||
|
line, message = try_receiving(sock)
|
||||||
|
if message then return nil, message end
|
||||||
|
_,_, current, separator = string.find(line, "^(%d%d%d)(.)")
|
||||||
|
if not current or not separator then
|
||||||
|
return nil, "invalid server reply"
|
||||||
|
end
|
||||||
|
reply = reply .. "\n" .. line
|
||||||
|
-- reply ends with same code
|
||||||
|
until code == current and separator == " "
|
||||||
|
end
|
||||||
|
return code, reply
|
||||||
|
end
|
||||||
|
|
||||||
|
-- metatable for sock object
|
||||||
|
local metatable = { __index = {} }
|
||||||
|
|
||||||
|
-- execute the "check" instr
|
||||||
|
function metatable.__index:check(ok)
|
||||||
|
local code, reply = get_reply(self.sock)
|
||||||
|
if not code then return nil, reply end
|
||||||
|
if type(ok) ~= "function" then
|
||||||
|
if type(ok) ~= "table" then ok = {ok} end
|
||||||
|
for i, v in ipairs(ok) do
|
||||||
|
if string.find(code, v) then return code, reply end
|
||||||
|
end
|
||||||
|
return nil, reply
|
||||||
|
else return ok(code, reply) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function metatable.__index:cmdchk(cmd, arg, ok)
|
||||||
|
local code, err = self:command(cmd, arg)
|
||||||
|
if not code then return nil, err end
|
||||||
|
return self:check(ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- execute the "command" instr
|
||||||
|
function metatable.__index:command(cmd, arg)
|
||||||
|
if arg then return try_sending(self.sock, cmd .. " " .. arg.. "\r\n")
|
||||||
|
return try_sending(self.sock, cmd .. "\r\n") end
|
||||||
|
end
|
||||||
|
|
||||||
|
function metatable.__index:sink(snk, pat)
|
||||||
|
local chunk, err = sock:receive(pat)
|
||||||
|
return snk(chunk, err)
|
||||||
|
end
|
||||||
|
|
||||||
|
function metatable.__index:source(src, instr)
|
||||||
|
while true do
|
||||||
|
local chunk, err = src()
|
||||||
|
if not chunk then return not err, err end
|
||||||
|
local ret, err = try_sending(self.sock, chunk)
|
||||||
|
if not ret then return nil, err end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- closes the underlying sock
|
||||||
|
function metatable.__index:close()
|
||||||
|
self.sock:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- connect with server and return sock object
|
||||||
|
function connect(host, port)
|
||||||
|
local sock, err = socket.connect(host, port)
|
||||||
|
if not sock then return nil, message end
|
||||||
|
sock:settimeout(TIMEOUT)
|
||||||
|
return setmetatable({sock = sock}, metatable)
|
||||||
|
end
|
@ -269,7 +269,7 @@ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout)
|
|||||||
fd_set fds;
|
fd_set fds;
|
||||||
int ret;
|
int ret;
|
||||||
*got = 0;
|
*got = 0;
|
||||||
if (taken == 0) return IO_CLOSED;
|
if (taken == 0 || WSAGetLastError() != WSAEWOULDBLOCK) return IO_CLOSED;
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(sock, &fds);
|
FD_SET(sock, &fds);
|
||||||
ret = sock_select(0, &fds, NULL, NULL, timeout);
|
ret = sock_select(0, &fds, NULL, NULL, timeout);
|
||||||
@ -295,7 +295,7 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got,
|
|||||||
fd_set fds;
|
fd_set fds;
|
||||||
int ret;
|
int ret;
|
||||||
*got = 0;
|
*got = 0;
|
||||||
if (taken == 0) return IO_CLOSED;
|
if (taken == 0 || WSAGetLastError() != WSAEWOULDBLOCK) return IO_CLOSED;
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(sock, &fds);
|
FD_SET(sock, &fds);
|
||||||
ret = sock_select(0, &fds, NULL, NULL, timeout);
|
ret = sock_select(0, &fds, NULL, NULL, timeout);
|
||||||
|
@ -12,7 +12,7 @@ socket.http.TIMEOUT = 5
|
|||||||
|
|
||||||
local t = socket.time()
|
local t = socket.time()
|
||||||
|
|
||||||
host = host or "diego-interface2.student.dyn.CS.Princeton.EDU"
|
host = host or "diego.student.princeton.edu"
|
||||||
proxy = proxy or "http://localhost:3128"
|
proxy = proxy or "http://localhost:3128"
|
||||||
prefix = prefix or "/luasocket-test"
|
prefix = prefix or "/luasocket-test"
|
||||||
cgiprefix = cgiprefix or "/luasocket-test-cgi"
|
cgiprefix = cgiprefix or "/luasocket-test-cgi"
|
||||||
@ -71,8 +71,8 @@ local check_request = function(request, expect, ignore)
|
|||||||
check_result(response, expect, ignore)
|
check_result(response, expect, ignore)
|
||||||
end
|
end
|
||||||
|
|
||||||
local check_request_cb = function(request, response, expect, ignore)
|
local check_request_cb = function(request, expect, ignore)
|
||||||
local response = socket.http.request_cb(request, response)
|
local response = socket.http.request_cb(request)
|
||||||
check_result(response, expect, ignore)
|
check_result(response, expect, ignore)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ local back, h, c, e = socket.http.get("http://" .. host .. forth)
|
|||||||
if not back then fail(e) end
|
if not back then fail(e) end
|
||||||
back = socket.url.parse(back)
|
back = socket.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() end
|
else fail(back.query) end
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
io.write("testing query string correctness: ")
|
io.write("testing query string correctness: ")
|
||||||
@ -168,31 +168,28 @@ back = socket.http.post("http://" .. host .. cgiprefix .. "/cat", index)
|
|||||||
check(back == index)
|
check(back == index)
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
io.write("testing send.file and receive.file callbacks: ")
|
io.write("testing ltn12.(sink|source).file: ")
|
||||||
request = {
|
request = {
|
||||||
url = "http://" .. host .. cgiprefix .. "/cat",
|
url = "http://" .. host .. cgiprefix .. "/cat",
|
||||||
method = "POST",
|
method = "POST",
|
||||||
body_cb = socket.callback.send.file(io.open(index_file, "r")),
|
source = ltn12.source.file(io.open(index_file, "r")),
|
||||||
|
sink = ltn12.sink.file(io.open(index_file .. "-back", "w")),
|
||||||
headers = { ["content-length"] = string.len(index) }
|
headers = { ["content-length"] = string.len(index) }
|
||||||
}
|
}
|
||||||
response = {
|
|
||||||
body_cb = socket.callback.receive.file(io.open(index_file .. "-back", "w"))
|
|
||||||
}
|
|
||||||
expect = {
|
expect = {
|
||||||
code = 200
|
code = 200
|
||||||
}
|
}
|
||||||
ignore = {
|
ignore = {
|
||||||
body_cb = 1,
|
|
||||||
status = 1,
|
status = 1,
|
||||||
headers = 1
|
headers = 1
|
||||||
}
|
}
|
||||||
check_request_cb(request, response, expect, ignore)
|
check_request_cb(request, expect, ignore)
|
||||||
back = readfile(index_file .. "-back")
|
back = readfile(index_file .. "-back")
|
||||||
check(back == index)
|
check(back == index)
|
||||||
os.remove(index_file .. "-back")
|
os.remove(index_file .. "-back")
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
io.write("testing send.chain and receive.chain callbacks: ")
|
io.write("testing ltn12.(sink|source).chain and mime.(encode|decode): ")
|
||||||
|
|
||||||
local function b64length(len)
|
local function b64length(len)
|
||||||
local a = math.ceil(len/3)*4
|
local a = math.ceil(len/3)*4
|
||||||
@ -200,26 +197,26 @@ local function b64length(len)
|
|||||||
return a + l*2
|
return a + l*2
|
||||||
end
|
end
|
||||||
|
|
||||||
local req_cb = socket.callback.send.chain(
|
local source = ltn12.source.chain(
|
||||||
socket.callback.send.file(io.open(index_file, "r")),
|
ltn12.source.file(io.open(index_file, "r")),
|
||||||
socket.mime.chain(
|
ltn12.filter.chain(
|
||||||
socket.mime.encode("base64"),
|
mime.encode("base64"),
|
||||||
socket.mime.wrap("base64")
|
mime.wrap("base64")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
local resp_cb = socket.callback.receive.chain(
|
local sink = ltn12.sink.chain(
|
||||||
socket.mime.decode("base64"),
|
mime.decode("base64"),
|
||||||
socket.callback.receive.file(io.open(index_file .. "-back", "w"))
|
ltn12.sink.file(io.open(index_file .. "-back", "w"))
|
||||||
)
|
)
|
||||||
|
|
||||||
request = {
|
request = {
|
||||||
url = "http://" .. host .. cgiprefix .. "/cat",
|
url = "http://" .. host .. cgiprefix .. "/cat",
|
||||||
method = "POST",
|
method = "POST",
|
||||||
body_cb = req_cb,
|
source = source,
|
||||||
|
sink = sink,
|
||||||
headers = { ["content-length"] = b64length(string.len(index)) }
|
headers = { ["content-length"] = b64length(string.len(index)) }
|
||||||
}
|
}
|
||||||
response = { body_cb = resp_cb }
|
|
||||||
expect = {
|
expect = {
|
||||||
code = 200
|
code = 200
|
||||||
}
|
}
|
||||||
@ -228,7 +225,7 @@ ignore = {
|
|||||||
status = 1,
|
status = 1,
|
||||||
headers = 1
|
headers = 1
|
||||||
}
|
}
|
||||||
check_request_cb(request, response, expect, ignore)
|
check_request_cb(request, expect, ignore)
|
||||||
back = readfile(index_file .. "-back")
|
back = readfile(index_file .. "-back")
|
||||||
check(back == index)
|
check(back == index)
|
||||||
os.remove(index_file .. "-back")
|
os.remove(index_file .. "-back")
|
||||||
@ -362,7 +359,7 @@ io.write("testing manual basic auth: ")
|
|||||||
request = {
|
request = {
|
||||||
url = "http://" .. host .. prefix .. "/auth/index.html",
|
url = "http://" .. host .. prefix .. "/auth/index.html",
|
||||||
headers = {
|
headers = {
|
||||||
authorization = "Basic " .. (socket.mime.b64("luasocket:password"))
|
authorization = "Basic " .. (mime.b64("luasocket:password"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect = {
|
expect = {
|
||||||
|
@ -31,18 +31,27 @@ local mao = [[
|
|||||||
assim, nem tudo o que dava exprimia grande confiança.
|
assim, nem tudo o que dava exprimia grande confiança.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local function transform(input, output, filter)
|
local function random(handle, io_err)
|
||||||
local fi, err = io.open(input, "rb")
|
if handle then
|
||||||
if not fi then fail(err) end
|
return function()
|
||||||
local fo, err = io.open(output, "wb")
|
local chunk = handle:read(math.random(0, 1024))
|
||||||
if not fo then fail(err) end
|
if not chunk then handle:close() end
|
||||||
while 1 do
|
return chunk
|
||||||
local chunk = fi:read(math.random(0, 1024))
|
|
||||||
fo:write(filter(chunk))
|
|
||||||
if not chunk then break end
|
|
||||||
end
|
end
|
||||||
fi:close()
|
else source.empty(io_err or "unable to open file") end
|
||||||
fo:close()
|
end
|
||||||
|
|
||||||
|
local what = nil
|
||||||
|
local function transform(input, output, filter)
|
||||||
|
local source = random(io.open(input, "rb"))
|
||||||
|
local sink = ltn12.sink.file(io.open(output, "wb"))
|
||||||
|
if what then
|
||||||
|
sink = ltn12.sink.chain(filter, sink)
|
||||||
|
else
|
||||||
|
source = ltn12.source.chain(source, filter)
|
||||||
|
end
|
||||||
|
--what = not what
|
||||||
|
ltn12.pump(source, sink)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function encode_qptest(mode)
|
local function encode_qptest(mode)
|
||||||
|
19
test/stufftest.lua
Normal file
19
test/stufftest.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
function test_dot(original, right)
|
||||||
|
local result, n = socket.smtp.dot(2, original)
|
||||||
|
assert(result == right, "->" .. result .. "<-")
|
||||||
|
print("ok")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_stuff(original, right)
|
||||||
|
local result, n = socket.smtp.dot(2, original)
|
||||||
|
assert(result == right, "->" .. result .. "<-")
|
||||||
|
print("ok")
|
||||||
|
end
|
||||||
|
|
||||||
|
test_dot("abc", "abc")
|
||||||
|
test_dot("", "")
|
||||||
|
test_dot("\r\n", "\r\n")
|
||||||
|
test_dot("\r\n.", "\r\n..")
|
||||||
|
test_dot(".\r\n.", "..\r\n..")
|
||||||
|
test_dot(".\r\n.", "..\r\n..")
|
||||||
|
test_dot("abcd.\r\n.", "abcd.\r\n..")
|
Loading…
Reference in New Issue
Block a user