New filter scheme.

ltn12 and mime updated.
smtp/ftp broken.
This commit is contained in:
Diego Nehab 2004-03-16 06:42:53 +00:00
parent b6edaac284
commit bcc0c2a9f0
17 changed files with 568 additions and 530 deletions

5
TODO
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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);
}

View File

@ -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 */

View File

@ -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})
end if code == 331 and password then -- need pass and we have pass
control:command("pass", password)
----------------------------------------------------------------------------- code, reply = control:check({230, 202})
-- Closes the connection with the server end
-- Input -- change directory
-- control: control connection with server local segment = parse_path(parsed)
----------------------------------------------------------------------------- for i, v in ipairs(segment) do
function Private.close(control) code, reply = control:command("cwd")
-- disconnect code, reply = control:check(250)
Private.logout(control)
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
-- 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
return change_dir(control, segment) or
change_type(control, parsed.params) or
download(control, request, segment) or
close(control)
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

View File

@ -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
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -443,8 +434,8 @@ end
-- 1 if we should redirect, nil otherwise -- 1 if we should redirect, nil otherwise
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local function should_redirect(reqt, respt) local function should_redirect(reqt, respt)
return (reqt.redirect ~= false) and return (reqt.redirect ~= false) and
(respt.code == 301 or respt.code == 302) and (respt.code == 301 or respt.code == 302) and
(reqt.method == "GET" or reqt.method == "HEAD") and (reqt.method == "GET" or reqt.method == "HEAD") and
not (reqt.nredirects and reqt.nredirects >= 5) not (reqt.nredirects and reqt.nredirects >= 5)
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

View File

@ -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
if not chunk then return nil, err end
end
end)
return function()
return skip(coroutine.resume(co))
end end
return source.simplify(chain) 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,22 +180,25 @@ 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
end end
end end
end end

View File

@ -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;

View File

@ -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 */
last = input + isize; if (!input) {
while (input < last)
asize = b64encode(*input++, atom, asize, &buffer);
} else
asize = b64pad(atom, asize, &buffer); asize = b64pad(atom, asize, &buffer);
luaL_pushresult(&buffer);
lua_pushnil(L);
return 2;
}
/* otherwise process the second part */
last = input + isize;
while (input < last)
asize = b64encode(*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;
@ -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 */
last = input + isize; if (!input) {
while (input < last) luaL_pushresult(&buffer);
asize = b64decode(*input++, atom, asize, &buffer); lua_pushnil(L);
} return 2;
/* if !input we are done. if atom > 0, the remaning is invalid. we just }
* return it undecoded. */ /* otherwise, process the rest of the input */
last = input + isize;
while (input < last)
asize = b64decode(*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;
@ -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 */
last = input + isize; if (!input) {
while (input < last)
asize = qpencode(*input++, atom, asize, marker, &buffer);
} else
asize = qppad(atom, asize, &buffer); asize = qppad(atom, asize, &buffer);
luaL_pushresult(&buffer);
lua_pushnil(L);
}
/* otherwise process rest of input */
last = input + isize;
while (input < last)
asize = qpencode(*input++, atom, asize, marker, &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 */
last = input + isize; if (!input) {
while (input < last) luaL_pushresult(&buffer);
asize = qpdecode(*input++, atom, asize, &buffer); lua_pushnil(L);
return 2;
} }
/* otherwise process rest of input */
last = input + isize;
while (input < last)
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;

View File

@ -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

View File

@ -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
View 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

View File

@ -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);

View File

@ -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 = {

View File

@ -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 random(handle, io_err)
if handle then
return function()
local chunk = handle:read(math.random(0, 1024))
if not chunk then handle:close() end
return chunk
end
else source.empty(io_err or "unable to open file") end
end
local what = nil
local function transform(input, output, filter) local function transform(input, output, filter)
local fi, err = io.open(input, "rb") local source = random(io.open(input, "rb"))
if not fi then fail(err) end local sink = ltn12.sink.file(io.open(output, "wb"))
local fo, err = io.open(output, "wb") if what then
if not fo then fail(err) end sink = ltn12.sink.chain(filter, sink)
while 1 do else
local chunk = fi:read(math.random(0, 1024)) source = ltn12.source.chain(source, filter)
fo:write(filter(chunk)) end
if not chunk then break end --what = not what
end ltn12.pump(source, sink)
fi:close()
fo:close()
end end
local function encode_qptest(mode) local function encode_qptest(mode)

19
test/stufftest.lua Normal file
View 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..")