FTP low-level working.
SMTP connection oriented working. ltn12 improved.
This commit is contained in:
parent
4fc164b8ea
commit
888496aa82
15 changed files with 701 additions and 659 deletions
|
@ -13,27 +13,6 @@
|
|||
/*=========================================================================*\
|
||||
* Exported functions
|
||||
\*=========================================================================*/
|
||||
/*-------------------------------------------------------------------------*\
|
||||
* Prints the value of a class in a nice way
|
||||
\*-------------------------------------------------------------------------*/
|
||||
int aux_meth_tostring(lua_State *L)
|
||||
{
|
||||
char buf[32];
|
||||
if (!lua_getmetatable(L, 1)) goto error;
|
||||
lua_pushstring(L, "__index");
|
||||
lua_gettable(L, -2);
|
||||
if (!lua_istable(L, -1)) goto error;
|
||||
lua_pushstring(L, "class");
|
||||
lua_gettable(L, -2);
|
||||
if (!lua_isstring(L, -1)) goto error;
|
||||
sprintf(buf, "%p", lua_touserdata(L, 1));
|
||||
lua_pushfstring(L, "socket: %s: %s", lua_tostring(L, -1), buf);
|
||||
return 1;
|
||||
error:
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*\
|
||||
* Initializes the module
|
||||
\*-------------------------------------------------------------------------*/
|
||||
|
@ -48,23 +27,20 @@ int aux_open(lua_State *L)
|
|||
void aux_newclass(lua_State *L, const char *classname, luaL_reg *func)
|
||||
{
|
||||
luaL_newmetatable(L, classname); /* mt */
|
||||
/* set __tostring metamethod */
|
||||
lua_pushstring(L, "__tostring");
|
||||
lua_pushcfunction(L, aux_meth_tostring);
|
||||
lua_rawset(L, -3);
|
||||
/* create __index table to place methods */
|
||||
lua_pushstring(L, "__index"); /* mt,"__index" */
|
||||
lua_newtable(L); /* mt,"__index",it */
|
||||
luaL_openlib(L, NULL, func, 0);
|
||||
/* put class name into class metatable */
|
||||
lua_pushstring(L, "class"); /* mt,"__index",it,"class" */
|
||||
lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */
|
||||
lua_rawset(L, -3); /* mt,"__index",it */
|
||||
/* get __gc method from class and use it for garbage collection */
|
||||
lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc" */
|
||||
lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc","__gc" */
|
||||
lua_rawget(L, -3); /* mt,"__index",it,"__gc",fn */
|
||||
lua_rawset(L, -5); /* mt,"__index",it */
|
||||
/* pass all methods that start with _ to the metatable, and all others
|
||||
* to the index table */
|
||||
for (; func->name; func++) { /* mt,"__index",it */
|
||||
lua_pushstring(L, func->name);
|
||||
lua_pushcfunction(L, func->func);
|
||||
lua_rawset(L, func->name[0] == '_' ? -5: -3);
|
||||
}
|
||||
lua_rawset(L, -3); /* mt */
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
|
567
src/ftp.lua
567
src/ftp.lua
|
@ -30,474 +30,153 @@ EMAIL = "anonymous@anonymous.org"
|
|||
BLOCKSIZE = 2048
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Gets ip and port for data connection from PASV answer
|
||||
-- Input
|
||||
-- pasv: PASV command answer
|
||||
-- Returns
|
||||
-- ip: string containing ip for data connection
|
||||
-- port: port for data connection
|
||||
-- Low level FTP API
|
||||
-----------------------------------------------------------------------------
|
||||
local function get_pasv(pasv)
|
||||
local a, b, c, d, p1, p2, _
|
||||
local ip, port
|
||||
_,_, a, b, c, d, p1, p2 =
|
||||
string.find(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)")
|
||||
if not (a and b and c and d and p1 and p2) then return nil, nil end
|
||||
ip = string.format("%d.%d.%d.%d", a, b, c, d)
|
||||
port = tonumber(p1)*256 + tonumber(p2)
|
||||
return ip, port
|
||||
local metat = { __index = {} }
|
||||
|
||||
function open(server, port)
|
||||
local tp = socket.try(socket.tp.connect(server, port or PORT))
|
||||
return setmetatable({tp = tp}, metat)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Check server greeting
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local function greet(control)
|
||||
local code, answer = check_answer(control, {120, 220})
|
||||
if code == 120 then -- please try again, somewhat busy now...
|
||||
return check_answer(control, {220})
|
||||
end
|
||||
return code, answer
|
||||
local function port(portt)
|
||||
return portt.server:accept()
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Log in on server
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- user: user name
|
||||
-- password: user password if any
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local function login(control, user, password)
|
||||
local code, answer = command(control, "user", user, {230, 331})
|
||||
if code == 331 and password then -- need pass and we have pass
|
||||
return command(control, "pass", password, {230, 202})
|
||||
end
|
||||
return code, answer
|
||||
local function pasv(pasvt)
|
||||
return socket.connect(pasvt.ip, pasvt.port)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Change to target directory
|
||||
-- Input
|
||||
-- control: socket for control connection with server
|
||||
-- path: directory to change to
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local function cwd(control, path)
|
||||
function metat.__index:login(user, password)
|
||||
socket.try(self.tp:command("USER", user))
|
||||
local code, reply = socket.try(self.tp:check{"2..", 331})
|
||||
if code == 331 then
|
||||
socket.try(password, reply)
|
||||
socket.try(self.tp:command("PASS", password))
|
||||
socket.try(self.tp:check("2.."))
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Change to target directory
|
||||
-- Input
|
||||
-- control: socket for control connection with server
|
||||
-- Returns
|
||||
-- server: server socket bound to local address, nil if error
|
||||
-- answer: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local function port(control)
|
||||
local code, answer
|
||||
local server, ctl_ip
|
||||
ctl_ip, answer = control:getsockname()
|
||||
server, answer = socket.bind(ctl_ip, 0)
|
||||
server:settimeout(TIMEOUT)
|
||||
local ip, p, ph, pl
|
||||
ip, p = server:getsockname()
|
||||
pl = math.mod(p, 256)
|
||||
ph = (p - pl)/256
|
||||
function metat.__index:pasv()
|
||||
socket.try(self.tp:command("PASV"))
|
||||
local code, reply = socket.try(self.tp:check("2.."))
|
||||
local _, _, a, b, c, d, p1, p2 =
|
||||
string.find(reply, "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)")
|
||||
socket.try(a and b and c and d and p1 and p2, reply)
|
||||
self.pasvt = {
|
||||
ip = string.format("%d.%d.%d.%d", a, b, c, d),
|
||||
port = p1*256 + p2
|
||||
}
|
||||
if self.portt then
|
||||
self.portt.server:close()
|
||||
self.portt = nil
|
||||
end
|
||||
return self.pasvt.ip, self.pasvt.port
|
||||
end
|
||||
|
||||
function metat.__index:port(ip, port)
|
||||
self.pasvt = nil
|
||||
local server
|
||||
if not ip then
|
||||
ip, port = socket.try(self.tp:getcontrol():getsockname())
|
||||
server = socket.try(socket.bind(ip, 0))
|
||||
ip, port = socket.try(server:getsockname())
|
||||
socket.try(server:settimeout(TIMEOUT))
|
||||
end
|
||||
local pl = math.mod(port, 256)
|
||||
local ph = (port - pl)/256
|
||||
local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
|
||||
code, answer = command(control, "port", arg, {200})
|
||||
if not code then
|
||||
server:close()
|
||||
return nil, answer
|
||||
else return server end
|
||||
socket.try(self.tp:command("port", arg))
|
||||
socket.try(self.tp:check("2.."))
|
||||
self.portt = server and {ip = ip, port = port, server = server}
|
||||
return 1
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Closes control connection with server
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local function logout(control)
|
||||
local code, answer = command(control, "quit", nil, {221})
|
||||
if code then control:close() end
|
||||
return code, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Receives data and send it to a callback
|
||||
-- Input
|
||||
-- data: data connection
|
||||
-- callback: callback to return file contents
|
||||
-- Returns
|
||||
-- nil if successfull, or an error message in case of error
|
||||
-----------------------------------------------------------------------------
|
||||
local function receive_indirect(data, callback)
|
||||
local chunk, err, res
|
||||
while not err do
|
||||
chunk, err = try_receive(data, BLOCKSIZE)
|
||||
if err == "closed" then err = "done" end
|
||||
res = callback(chunk, err)
|
||||
if not res then break end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Retrieves file or directory listing
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- server: server socket bound to local address
|
||||
-- name: file name
|
||||
-- is_directory: is file a directory name?
|
||||
-- content_cb: callback to receive file contents
|
||||
-- Returns
|
||||
-- err: error message in case of error, nil otherwise
|
||||
-----------------------------------------------------------------------------
|
||||
local function retrieve(control, server, name, is_directory, content_cb)
|
||||
local code, answer
|
||||
local data
|
||||
-- ask server for file or directory listing accordingly
|
||||
if is_directory then
|
||||
code, answer = cwd(control, name)
|
||||
if not code then return answer end
|
||||
code, answer = command(control, "nlst", nil, {150, 125})
|
||||
else
|
||||
code, answer = command(control, "retr", name, {150, 125})
|
||||
end
|
||||
if not code then return nil, answer end
|
||||
data, answer = server:accept()
|
||||
server:close()
|
||||
if not data then
|
||||
control:close()
|
||||
return answer
|
||||
end
|
||||
answer = receive_indirect(data, content_cb)
|
||||
if answer then
|
||||
control:close()
|
||||
return answer
|
||||
end
|
||||
data:close()
|
||||
-- make sure file transfered ok
|
||||
return check_answer(control, {226, 250})
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Stores a file
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- server: server socket bound to local address
|
||||
-- file: file name under current directory
|
||||
-- send_cb: callback to produce the file contents
|
||||
-- Returns
|
||||
-- code: return code, nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local function store(control, server, file, send_cb)
|
||||
local data, err
|
||||
local code, answer = command(control, "stor", file, {150, 125})
|
||||
if not code then
|
||||
control:close()
|
||||
return nil, answer
|
||||
end
|
||||
-- start data connection
|
||||
data, answer = server:accept()
|
||||
server:close()
|
||||
if not data then
|
||||
control:close()
|
||||
return nil, answer
|
||||
end
|
||||
-- send whole file
|
||||
err = send_indirect(data, send_cb, send_cb())
|
||||
if err then
|
||||
control:close()
|
||||
return nil, err
|
||||
end
|
||||
-- close connection to inform that file transmission is complete
|
||||
data:close()
|
||||
-- check if file was received correctly
|
||||
return check_answer(control, {226, 250})
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Change transfer type
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- params: "type=i" for binary or "type=a" for ascii
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local function change_type(control, params)
|
||||
local type, _
|
||||
_, _, type = string.find(params or "", "type=(.)")
|
||||
if type == "a" or type == "i" then
|
||||
local code, err = command(control, "type", type, {200})
|
||||
if not code then return err end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Starts a control connection, checks the greeting and log on
|
||||
-- Input
|
||||
-- parsed: parsed URL components
|
||||
-- Returns
|
||||
-- control: control connection with server, or nil if error
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local function open(parsed)
|
||||
local control, err = socket.tp.connect(parsed.host, parsed.port)
|
||||
if not control then return nil, err end
|
||||
function metat.__index:send(sendt)
|
||||
local data
|
||||
socket.try(self.pasvt or self.portt, "need port or pasv first")
|
||||
if self.pasvt then data = socket.try(pasv(self.pasvt)) end
|
||||
socket.try(self.tp:command(sendt.command, sendt.argument))
|
||||
if self.portt then data = socket.try(port(self.portt)) end
|
||||
local step = sendt.step or ltn12.pump.step
|
||||
local code, reply
|
||||
-- greet
|
||||
code, reply = control:check({120, 220})
|
||||
if code == 120 then -- busy, try again
|
||||
code, reply = control:check(220)
|
||||
local checkstep = function(src, snk)
|
||||
local readyt = socket.select(readt, nil, 0)
|
||||
if readyt[tp] then
|
||||
code, reply = self.tp:check{"2..", "1.."}
|
||||
if not code then
|
||||
data:close()
|
||||
return nil, reply
|
||||
end
|
||||
end
|
||||
local ret, err = step(src, snk)
|
||||
if err then data:close() end
|
||||
return ret, err
|
||||
end
|
||||
-- authenticate
|
||||
code, reply = control:command("user", user)
|
||||
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})
|
||||
local sink = socket.sink("close-when-empty", data)
|
||||
socket.try(ltn12.pump.all(sendt.source, sink, checkstep))
|
||||
if not code then code = socket.try(self.tp:check{"1..", "2.."}) end
|
||||
if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
|
||||
return 1
|
||||
end
|
||||
|
||||
function metat.__index:receive(recvt)
|
||||
local data
|
||||
socket.try(self.pasvt or self.portt, "need port or pasv first")
|
||||
if self.pasvt then data = socket.try(pasv(self.pasvt)) end
|
||||
socket.try(self.tp:command(recvt.command, recvt.argument))
|
||||
if self.portt then data = socket.try(port(self.portt)) end
|
||||
local source = socket.source("until-closed", data)
|
||||
local step = recvt.step or ltn12.pump.step
|
||||
local checkstep = function(src, snk)
|
||||
local ret, err = step(src, snk)
|
||||
if err then data:close() end
|
||||
return ret, err
|
||||
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
|
||||
socket.try(ltn12.pump.all(source, recvt.sink, checkstep))
|
||||
local code = socket.try(self.tp:check{"1..", "2.."})
|
||||
if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
|
||||
return 1
|
||||
end
|
||||
|
||||
return change_dir(control, segment) or
|
||||
change_type(control, parsed.params) or
|
||||
download(control, request, segment) or
|
||||
close(control)
|
||||
function metat.__index:cwd(dir)
|
||||
socket.try(self.tp:command("CWD", dir))
|
||||
socket.try(self.tp:check(250))
|
||||
return 1
|
||||
end
|
||||
|
||||
function metat.__index:type(type)
|
||||
socket.try(self.tp:command("TYPE", type))
|
||||
socket.try(self.tp:check(200))
|
||||
return 1
|
||||
end
|
||||
|
||||
function metat.__index:greet()
|
||||
local code = socket.try(self.tp:check{"1..", "2.."})
|
||||
if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
|
||||
return 1
|
||||
end
|
||||
|
||||
function metat.__index:quit()
|
||||
socket.try(self.tp:command("QUIT"))
|
||||
socket.try(self.tp:check("2.."))
|
||||
return 1
|
||||
end
|
||||
|
||||
function metat.__index:close()
|
||||
socket.try(self.tp:close())
|
||||
return 1
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Stores a file in current directory
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- request: a table with the fields:
|
||||
-- content_cb: send callback to send file contents
|
||||
-- segment: parsed URL path segments
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-- High level FTP API
|
||||
-----------------------------------------------------------------------------
|
||||
local function upload(control, request, segment)
|
||||
local code, name, content_cb
|
||||
-- get remote file name
|
||||
name = segment[table.getn(segment)]
|
||||
if not name then
|
||||
control:close()
|
||||
return "Invalid file path"
|
||||
end
|
||||
content_cb = request.content_cb
|
||||
-- setup passive connection
|
||||
local server, answer = port(control)
|
||||
if not server then return answer end
|
||||
-- ask server to receive file
|
||||
code, answer = store(control, server, name, content_cb)
|
||||
if not code then return answer end
|
||||
|
||||
function put(putt)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Download a file from current directory
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- request: a table with the fields:
|
||||
-- content_cb: receive callback to receive file contents
|
||||
-- segment: parsed URL path segments
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local function download(control, request, segment)
|
||||
local code, name, is_directory, content_cb
|
||||
is_directory = segment.is_directory
|
||||
content_cb = request.content_cb
|
||||
-- get remote file name
|
||||
name = segment[table.getn(segment)]
|
||||
if not name and not is_directory then
|
||||
control:close()
|
||||
return "Invalid file path"
|
||||
end
|
||||
-- setup passive connection
|
||||
local server, answer = port(control)
|
||||
if not server then return answer end
|
||||
-- ask server to send file or directory listing
|
||||
code, answer = retrieve(control, server, name,
|
||||
is_directory, content_cb)
|
||||
if not code then return answer end
|
||||
function get(gett)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Parses the FTP URL setting default values
|
||||
-- Input
|
||||
-- request: a table with the fields:
|
||||
-- url: the target URL
|
||||
-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
|
||||
-- user: account user name
|
||||
-- password: account password
|
||||
-- Returns
|
||||
-- parsed: a table with parsed components
|
||||
-----------------------------------------------------------------------------
|
||||
local function parse_url(request)
|
||||
local parsed = socket.url.parse(request.url, {
|
||||
user = "anonymous",
|
||||
port = 21,
|
||||
path = "/",
|
||||
password = EMAIL,
|
||||
scheme = "ftp"
|
||||
})
|
||||
-- explicit login information overrides that given by URL
|
||||
parsed.user = request.user or parsed.user
|
||||
parsed.password = request.password or parsed.password
|
||||
-- explicit representation type overrides that given by URL
|
||||
if request.type then parsed.params = "type=" .. request.type end
|
||||
return parsed
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Parses the FTP URL path setting default values
|
||||
-- Input
|
||||
-- parsed: a table with the parsed URL components
|
||||
-- Returns
|
||||
-- dirs: a table with parsed directory components
|
||||
-----------------------------------------------------------------------------
|
||||
local function parse_path(parsed_url)
|
||||
local segment = socket.url.parse_path(parsed_url.path)
|
||||
segment.is_directory = segment.is_directory or
|
||||
(parsed_url.params == "type=d")
|
||||
return segment
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Builds a request table from a URL or request table
|
||||
-- Input
|
||||
-- url_or_request: target url or request table (a table with the fields:
|
||||
-- url: the target URL
|
||||
-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
|
||||
-- user: account user name
|
||||
-- password: account password)
|
||||
-- Returns
|
||||
-- request: request table
|
||||
-----------------------------------------------------------------------------
|
||||
local function build_request(data)
|
||||
local request = {}
|
||||
if type(data) == "table" then for i, v in data do request[i] = v end
|
||||
else request.url = data end
|
||||
return request
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Downloads a file from a FTP server
|
||||
-- Input
|
||||
-- request: a table with the fields:
|
||||
-- url: the target URL
|
||||
-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
|
||||
-- user: account user name
|
||||
-- password: account password
|
||||
-- content_cb: receive callback to receive file contents
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
function get_cb(request)
|
||||
local parsed = parse_url(request)
|
||||
if parsed.scheme ~= "ftp" then
|
||||
return string.format("unknown scheme '%s'", parsed.scheme)
|
||||
end
|
||||
local control, err = open(parsed)
|
||||
if not control then return err end
|
||||
local segment = parse_path(parsed)
|
||||
return change_dir(control, segment) or
|
||||
change_type(control, parsed.params) or
|
||||
download(control, request, segment) or
|
||||
close(control)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Uploads a file to a FTP server
|
||||
-- Input
|
||||
-- request: a table with the fields:
|
||||
-- url: the target URL
|
||||
-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
|
||||
-- user: account user name
|
||||
-- password: account password
|
||||
-- content_cb: send callback to send file contents
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
function put_cb(request)
|
||||
local parsed = parse_url(request)
|
||||
if parsed.scheme ~= "ftp" then
|
||||
return string.format("unknown scheme '%s'", parsed.scheme)
|
||||
end
|
||||
local control, err = open(parsed)
|
||||
if not control then return err end
|
||||
local segment = parse_path(parsed)
|
||||
err = change_dir(control, segment) or
|
||||
change_type(control, parsed.params) or
|
||||
upload(control, request, segment) or
|
||||
close(control)
|
||||
if err then return nil, err
|
||||
else return 1 end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Uploads a file to a FTP server
|
||||
-- Input
|
||||
-- url_or_request: target url or request table (a table with the fields:
|
||||
-- url: the target URL
|
||||
-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
|
||||
-- user: account user name
|
||||
-- password: account password)
|
||||
-- content: file contents
|
||||
-- content: file contents
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
function put(url_or_request, content)
|
||||
local request = build_request(url_or_request)
|
||||
request.content = request.content or content
|
||||
request.content_cb = socket.callback.send_string(request.content)
|
||||
return put_cb(request)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Retrieve a file from a ftp server
|
||||
-- Input
|
||||
-- url_or_request: target url or request table (a table with the fields:
|
||||
-- url: the target URL
|
||||
-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
|
||||
-- user: account user name
|
||||
-- password: account password)
|
||||
-- Returns
|
||||
-- data: file contents as a string
|
||||
-- err: error message in case of error, nil otherwise
|
||||
-----------------------------------------------------------------------------
|
||||
function get(url_or_request)
|
||||
local concat = socket.concat.create()
|
||||
local request = build_request(url_or_request)
|
||||
request.content_cb = socket.callback.receive_concat(concat)
|
||||
local err = get_cb(request)
|
||||
return concat:getresult(), err
|
||||
end
|
||||
|
||||
return socket.ftp
|
||||
return ftp
|
||||
|
|
14
src/http.lua
14
src/http.lua
|
@ -68,7 +68,7 @@ end
|
|||
|
||||
local function receive_body(reqt, respt, tmp)
|
||||
local sink = reqt.sink or ltn12.sink.null()
|
||||
local pump = reqt.pump or ltn12.pump
|
||||
local step = reqt.step or ltn12.pump.step
|
||||
local source
|
||||
local te = respt.headers["transfer-encoding"]
|
||||
if te and te ~= "identity" then
|
||||
|
@ -80,9 +80,9 @@ local function receive_body(reqt, respt, tmp)
|
|||
source = socket.source("by-length", tmp.sock, length)
|
||||
else
|
||||
-- get it all until connection closes
|
||||
source = socket.source("until-closed", tmp.sock)
|
||||
source = socket.source(tmp.sock)
|
||||
end
|
||||
socket.try(pump(source, sink))
|
||||
socket.try(ltn12.pump.all(source, sink, step))
|
||||
end
|
||||
|
||||
local function send_headers(sock, headers)
|
||||
|
@ -125,7 +125,7 @@ end
|
|||
local function send_request(reqt, respt, tmp)
|
||||
local uri = request_uri(reqt, respt, tmp)
|
||||
local headers = tmp.headers
|
||||
local pump = reqt.pump or ltn12.pump
|
||||
local step = reqt.step or ltn12.pump.step
|
||||
-- send request line
|
||||
socket.try(tmp.sock:send((reqt.method or "GET")
|
||||
.. " " .. uri .. " HTTP/1.1\r\n"))
|
||||
|
@ -136,9 +136,11 @@ local function send_request(reqt, respt, tmp)
|
|||
-- send request message body, if any
|
||||
if not reqt.source then return end
|
||||
if headers["content-length"] then
|
||||
socket.try(pump(reqt.source, socket.sink(tmp.sock)))
|
||||
socket.try(ltn12.pump.all(reqt.source,
|
||||
socket.sink(tmp.sock), step))
|
||||
else
|
||||
socket.try(pump(reqt.source, socket.sink("http-chunked", tmp.sock)))
|
||||
socket.try(ltn12.pump.all(reqt.source,
|
||||
socket.sink("http-chunked", tmp.sock), step))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ setfenv(1, ltn12)
|
|||
filter = {}
|
||||
source = {}
|
||||
sink = {}
|
||||
pump = {}
|
||||
|
||||
-- 2048 seems to be better in windows...
|
||||
BLOCKSIZE = 2048
|
||||
|
@ -22,7 +23,6 @@ end
|
|||
|
||||
-- returns a high level filter that cycles a cycles a low-level filter
|
||||
function filter.cycle(low, ctx, extra)
|
||||
if type(low) ~= 'function' then error('invalid low-level filter', 2) end
|
||||
return function(chunk)
|
||||
local ret
|
||||
ret, ctx = low(ctx, chunk, extra)
|
||||
|
@ -32,8 +32,6 @@ end
|
|||
|
||||
-- chains two filters together
|
||||
local function chain2(f1, f2)
|
||||
if type(f1) ~= 'function' then error('invalid filter', 2) end
|
||||
if type(f2) ~= 'function' then error('invalid filter', 2) end
|
||||
local co = coroutine.create(function(chunk)
|
||||
while true do
|
||||
local filtered1 = f1(chunk)
|
||||
|
@ -58,7 +56,6 @@ end
|
|||
function filter.chain(...)
|
||||
local f = arg[1]
|
||||
for i = 2, table.getn(arg) do
|
||||
if type(arg[i]) ~= 'function' then error('invalid filter', 2) end
|
||||
f = chain2(f, arg[i])
|
||||
end
|
||||
return f
|
||||
|
@ -93,7 +90,6 @@ end
|
|||
|
||||
-- turns a fancy source into a simple source
|
||||
function source.simplify(src)
|
||||
if type(src) ~= 'function' then error('invalid source', 2) end
|
||||
return function()
|
||||
local chunk, err_or_new = src()
|
||||
src = err_or_new or src
|
||||
|
@ -117,7 +113,6 @@ end
|
|||
|
||||
-- creates rewindable source
|
||||
function source.rewind(src)
|
||||
if type(src) ~= 'function' then error('invalid source', 2) end
|
||||
local t = {}
|
||||
return function(chunk)
|
||||
if not chunk then
|
||||
|
@ -132,8 +127,6 @@ end
|
|||
|
||||
-- chains a source with a filter
|
||||
function source.chain(src, f)
|
||||
if type(src) ~= 'function' then error('invalid source', 2) end
|
||||
if type(f) ~= 'function' then error('invalid filter', 2) end
|
||||
local co = coroutine.create(function()
|
||||
while true do
|
||||
local chunk, err = src()
|
||||
|
@ -152,20 +145,21 @@ function source.chain(src, f)
|
|||
end
|
||||
end
|
||||
|
||||
-- creates a source that produces contents of several files one after the
|
||||
-- creates a source that produces contents of several sources, 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)
|
||||
while i <= table.getn(arg) do
|
||||
local chunk, err = arg[i]()
|
||||
if chunk then coroutine.yield(chunk)
|
||||
else i = i + 1 end
|
||||
elseif err then return nil, err
|
||||
else i = i + 1 end
|
||||
end
|
||||
end)
|
||||
return source.simplify(function()
|
||||
return function()
|
||||
return shift(coroutine.resume(co))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- creates a sink that stores into a table
|
||||
|
@ -180,7 +174,6 @@ end
|
|||
|
||||
-- turns a fancy sink into a simple sink
|
||||
function sink.simplify(snk)
|
||||
if type(snk) ~= 'function' then error('invalid sink', 2) end
|
||||
return function(chunk, err)
|
||||
local ret, err_or_new = snk(chunk, err)
|
||||
if not ret then return nil, err_or_new end
|
||||
|
@ -219,8 +212,6 @@ end
|
|||
|
||||
-- chains a sink with a filter
|
||||
function sink.chain(f, snk)
|
||||
if type(snk) ~= 'function' then error('invalid sink', 2) end
|
||||
if type(f) ~= 'function' then error('invalid filter', 2) end
|
||||
return function(chunk, err)
|
||||
local filtered = f(chunk)
|
||||
local done = chunk and ""
|
||||
|
@ -233,15 +224,18 @@ function sink.chain(f, snk)
|
|||
end
|
||||
end
|
||||
|
||||
-- pumps all data from a source to a sink
|
||||
function pump(src, snk)
|
||||
if type(src) ~= 'function' then error('invalid source', 2) end
|
||||
if type(snk) ~= 'function' then error('invalid sink', 2) end
|
||||
-- pumps one chunk from the source to the sink
|
||||
function pump.step(src, snk)
|
||||
local chunk, src_err = src()
|
||||
local ret, snk_err = snk(chunk, src_err)
|
||||
return chunk and ret and not src_err and not snk_err, src_err or snk_err
|
||||
end
|
||||
|
||||
-- pumps all data from a source to a sink, using a step function
|
||||
function pump.all(src, snk, step)
|
||||
step = step or pump.step
|
||||
while true do
|
||||
local chunk, src_err = src()
|
||||
local ret, snk_err = snk(chunk, src_err)
|
||||
if not chunk or not ret then
|
||||
return not src_err and not snk_err, src_err or snk_err
|
||||
end
|
||||
local ret, err = step(src, snk)
|
||||
if not ret then return not err, err end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
\*=========================================================================*/
|
||||
#include "luasocket.h"
|
||||
|
||||
#include "base.h"
|
||||
#include "auxiliar.h"
|
||||
#include "timeout.h"
|
||||
#include "buffer.h"
|
||||
|
@ -39,34 +40,8 @@
|
|||
/*=========================================================================*\
|
||||
* Declarations
|
||||
\*=========================================================================*/
|
||||
static int base_open(lua_State *L);
|
||||
static int mod_open(lua_State *L, const luaL_reg *mod);
|
||||
|
||||
/*-------------------------------------------------------------------------*\
|
||||
* Setup basic stuff.
|
||||
\*-------------------------------------------------------------------------*/
|
||||
static int base_open(lua_State *L)
|
||||
{
|
||||
/* create namespace table */
|
||||
lua_pushstring(L, LUASOCKET_LIBNAME);
|
||||
lua_newtable(L);
|
||||
#ifdef LUASOCKET_DEBUG
|
||||
lua_pushstring(L, "debug");
|
||||
lua_pushnumber(L, 1);
|
||||
lua_rawset(L, -3);
|
||||
#endif
|
||||
/* make version string available so scripts */
|
||||
lua_pushstring(L, "version");
|
||||
lua_pushstring(L, LUASOCKET_VERSION);
|
||||
lua_rawset(L, -3);
|
||||
/* store namespace as global */
|
||||
lua_settable(L, LUA_GLOBALSINDEX);
|
||||
/* make sure modules know what is our namespace */
|
||||
lua_pushstring(L, "LUASOCKET_LIBNAME");
|
||||
lua_pushstring(L, LUASOCKET_LIBNAME);
|
||||
lua_settable(L, LUA_GLOBALSINDEX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_open(lua_State *L, const luaL_reg *mod)
|
||||
{
|
||||
|
@ -79,6 +54,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
|
|||
#include "tp.lch"
|
||||
#include "smtp.lch"
|
||||
#include "http.lch"
|
||||
#include "ftp.lch"
|
||||
#else
|
||||
lua_dofile(L, "ltn12.lua");
|
||||
lua_dofile(L, "auxiliar.lua");
|
||||
|
@ -87,6 +63,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
|
|||
lua_dofile(L, "tp.lua");
|
||||
lua_dofile(L, "smtp.lua");
|
||||
lua_dofile(L, "http.lua");
|
||||
lua_dofile(L, "ftp.lua");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
|
50
src/mime.c
50
src/mime.c
|
@ -14,14 +14,9 @@
|
|||
/*=========================================================================*\
|
||||
* Don't want to trust escape character constants
|
||||
\*=========================================================================*/
|
||||
#define CR 0x0D
|
||||
#define LF 0x0A
|
||||
#define HT 0x09
|
||||
#define SP 0x20
|
||||
|
||||
typedef unsigned char UC;
|
||||
static const char CRLF[] = {CR, LF, 0};
|
||||
static const char EQCRLF[] = {'=', CR, LF, 0};
|
||||
static const char CRLF[] = "\r\n";
|
||||
static const char EQCRLF[] = "=\r\n";
|
||||
|
||||
/*=========================================================================*\
|
||||
* Internal function prototypes.
|
||||
|
@ -121,9 +116,9 @@ static int mime_global_wrp(lua_State *L)
|
|||
luaL_buffinit(L, &buffer);
|
||||
while (input < last) {
|
||||
switch (*input) {
|
||||
case CR:
|
||||
case '\r':
|
||||
break;
|
||||
case LF:
|
||||
case '\n':
|
||||
luaL_addstring(&buffer, CRLF);
|
||||
left = length;
|
||||
break;
|
||||
|
@ -327,11 +322,10 @@ static int mime_global_unb64(lua_State *L)
|
|||
* all (except CRLF in text) can be =XX
|
||||
* CLRL in not text must be =XX=XX
|
||||
* 33 through 60 inclusive can be plain
|
||||
* 62 through 120 inclusive can be plain
|
||||
* 62 through 126 inclusive can be plain
|
||||
* 9 and 32 can be plain, unless in the end of a line, where must be =XX
|
||||
* encoded lines must be no longer than 76 not counting CRLF
|
||||
* soft line-break are =CRLF
|
||||
* !"#$@[\]^`{|}~ should be =XX for EBCDIC compatibility
|
||||
* To encode one byte, we need to see the next two.
|
||||
* Worst case is when we see a space, and wonder if a CRLF is comming
|
||||
\*-------------------------------------------------------------------------*/
|
||||
|
@ -344,16 +338,10 @@ static void qpsetup(UC *qpclass, UC *qpunbase)
|
|||
int i;
|
||||
for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED;
|
||||
for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN;
|
||||
for (i = 62; i <= 120; i++) qpclass[i] = QP_PLAIN;
|
||||
qpclass[HT] = QP_IF_LAST; qpclass[SP] = QP_IF_LAST;
|
||||
qpclass['!'] = QP_QUOTED; qpclass['"'] = QP_QUOTED;
|
||||
qpclass['#'] = QP_QUOTED; qpclass['$'] = QP_QUOTED;
|
||||
qpclass['@'] = QP_QUOTED; qpclass['['] = QP_QUOTED;
|
||||
qpclass['\\'] = QP_QUOTED; qpclass[']'] = QP_QUOTED;
|
||||
qpclass['^'] = QP_QUOTED; qpclass['`'] = QP_QUOTED;
|
||||
qpclass['{'] = QP_QUOTED; qpclass['|'] = QP_QUOTED;
|
||||
qpclass['}'] = QP_QUOTED; qpclass['~'] = QP_QUOTED;
|
||||
qpclass['}'] = QP_QUOTED; qpclass[CR] = QP_CR;
|
||||
for (i = 62; i <= 126; i++) qpclass[i] = QP_PLAIN;
|
||||
qpclass['\t'] = QP_IF_LAST;
|
||||
qpclass[' '] = QP_IF_LAST;
|
||||
qpclass['\r'] = QP_CR;
|
||||
for (i = 0; i < 256; i++) qpunbase[i] = 255;
|
||||
qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2;
|
||||
qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5;
|
||||
|
@ -377,7 +365,7 @@ static void qpquote(UC c, luaL_Buffer *buffer)
|
|||
|
||||
/*-------------------------------------------------------------------------*\
|
||||
* Accumulate characters until we are sure about how to deal with them.
|
||||
* Once we are sure, output the to the buffer, in the correct form.
|
||||
* Once we are sure, output to the buffer, in the correct form.
|
||||
\*-------------------------------------------------------------------------*/
|
||||
static size_t qpencode(UC c, UC *input, size_t size,
|
||||
const char *marker, luaL_Buffer *buffer)
|
||||
|
@ -389,7 +377,7 @@ static size_t qpencode(UC c, UC *input, size_t size,
|
|||
/* might be the CR of a CRLF sequence */
|
||||
case QP_CR:
|
||||
if (size < 2) return size;
|
||||
if (input[1] == LF) {
|
||||
if (input[1] == '\n') {
|
||||
luaL_addstring(buffer, marker);
|
||||
return 0;
|
||||
} else qpquote(input[0], buffer);
|
||||
|
@ -398,7 +386,7 @@ static size_t qpencode(UC c, UC *input, size_t size,
|
|||
case QP_IF_LAST:
|
||||
if (size < 3) return size;
|
||||
/* if it is the last, quote it and we are done */
|
||||
if (input[1] == CR && input[2] == LF) {
|
||||
if (input[1] == '\r' && input[2] == '\n') {
|
||||
qpquote(input[0], buffer);
|
||||
luaL_addstring(buffer, marker);
|
||||
return 0;
|
||||
|
@ -492,19 +480,19 @@ static size_t qpdecode(UC c, UC *input, size_t size,
|
|||
case '=':
|
||||
if (size < 3) return size;
|
||||
/* eliminate soft line break */
|
||||
if (input[1] == CR && input[2] == LF) return 0;
|
||||
if (input[1] == '\r' && input[2] == '\n') return 0;
|
||||
/* decode quoted representation */
|
||||
c = qpunbase[input[1]]; d = qpunbase[input[2]];
|
||||
/* if it is an invalid, do not decode */
|
||||
if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3);
|
||||
else luaL_putchar(buffer, (c << 4) + d);
|
||||
return 0;
|
||||
case CR:
|
||||
case '\r':
|
||||
if (size < 2) return size;
|
||||
if (input[1] == LF) luaL_addlstring(buffer, (char *)input, 2);
|
||||
if (input[1] == '\n') luaL_addlstring(buffer, (char *)input, 2);
|
||||
return 0;
|
||||
default:
|
||||
if (input[0] == HT || (input[0] > 31 && input[0] < 127))
|
||||
if (input[0] == '\t' || (input[0] > 31 && input[0] < 127))
|
||||
luaL_putchar(buffer, input[0]);
|
||||
return 0;
|
||||
}
|
||||
|
@ -582,9 +570,9 @@ static int mime_global_qpwrp(lua_State *L)
|
|||
luaL_buffinit(L, &buffer);
|
||||
while (input < last) {
|
||||
switch (*input) {
|
||||
case CR:
|
||||
case '\r':
|
||||
break;
|
||||
case LF:
|
||||
case '\n':
|
||||
left = length;
|
||||
luaL_addstring(&buffer, CRLF);
|
||||
break;
|
||||
|
@ -623,7 +611,7 @@ static int mime_global_qpwrp(lua_State *L)
|
|||
* c is the current character being processed
|
||||
* last is the previous character
|
||||
\*-------------------------------------------------------------------------*/
|
||||
#define eolcandidate(c) (c == CR || c == LF)
|
||||
#define eolcandidate(c) (c == '\r' || c == '\n')
|
||||
static int eolprocess(int c, int last, const char *marker,
|
||||
luaL_Buffer *buffer)
|
||||
{
|
||||
|
|
25
src/select.c
25
src/select.c
|
@ -21,7 +21,6 @@ static int meth_set(lua_State *L);
|
|||
static int meth_isset(lua_State *L);
|
||||
static int c_select(lua_State *L);
|
||||
static int global_select(lua_State *L);
|
||||
static void check_obj_tab(lua_State *L, int tabidx);
|
||||
|
||||
/* fd_set object methods */
|
||||
static luaL_reg set[] = {
|
||||
|
@ -68,9 +67,6 @@ static int global_select(lua_State *L)
|
|||
fd_set *read_fd_set, *write_fd_set;
|
||||
/* make sure we have enough arguments (nil is the default) */
|
||||
lua_settop(L, 3);
|
||||
/* check object tables */
|
||||
check_obj_tab(L, 1);
|
||||
check_obj_tab(L, 2);
|
||||
/* check timeout */
|
||||
if (!lua_isnil(L, 3) && !lua_isnumber(L, 3))
|
||||
luaL_argerror(L, 3, "number or nil expected");
|
||||
|
@ -127,24 +123,3 @@ static int c_select(lua_State *L)
|
|||
timeout < 0 ? NULL : &tv));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void check_obj_tab(lua_State *L, int tabidx)
|
||||
{
|
||||
if (tabidx < 0) tabidx = lua_gettop(L) + tabidx + 1;
|
||||
if (lua_istable(L, tabidx)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, tabidx) != 0) {
|
||||
if (aux_getgroupudata(L, "select{able}", -1) == NULL) {
|
||||
char msg[45];
|
||||
if (lua_isnumber(L, -2))
|
||||
sprintf(msg, "table entry #%g is invalid",
|
||||
lua_tonumber(L, -2));
|
||||
else
|
||||
sprintf(msg, "invalid entry found in table");
|
||||
luaL_argerror(L, tabidx, msg);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else if (!lua_isnil(L, tabidx))
|
||||
luaL_argerror(L, tabidx, "table or nil expected");
|
||||
}
|
||||
|
|
102
src/smtp.lua
102
src/smtp.lua
|
@ -20,6 +20,7 @@ DOMAIN = os.getenv("SERVER_NAME") or "localhost"
|
|||
-- default time zone (means we don't know)
|
||||
ZONE = "-0000"
|
||||
|
||||
|
||||
local function shift(a, b, c)
|
||||
return b, c
|
||||
end
|
||||
|
@ -29,31 +30,66 @@ function stuff()
|
|||
return ltn12.filter.cycle(dot, 2)
|
||||
end
|
||||
|
||||
-- send message or throw an exception
|
||||
local function send_p(control, mailt)
|
||||
socket.try(control:check("2.."))
|
||||
socket.try(control:command("EHLO", mailt.domain or DOMAIN))
|
||||
socket.try(control:check("2.."))
|
||||
socket.try(control:command("MAIL", "FROM:" .. mailt.from))
|
||||
socket.try(control:check("2.."))
|
||||
if type(mailt.rcpt) == "table" then
|
||||
for i,v in ipairs(mailt.rcpt) do
|
||||
socket.try(control:command("RCPT", "TO:" .. v))
|
||||
socket.try(control:check("2.."))
|
||||
end
|
||||
else
|
||||
socket.try(control:command("RCPT", "TO:" .. mailt.rcpt))
|
||||
socket.try(control:check("2.."))
|
||||
end
|
||||
socket.try(control:command("DATA"))
|
||||
socket.try(control:check("3.."))
|
||||
socket.try(control:source(ltn12.source.chain(mailt.source, stuff())))
|
||||
socket.try(control:send("\r\n.\r\n"))
|
||||
socket.try(control:check("2.."))
|
||||
socket.try(control:command("QUIT"))
|
||||
socket.try(control:check("2.."))
|
||||
---------------------------------------------------------------------------
|
||||
-- Low level SMTP API
|
||||
-----------------------------------------------------------------------------
|
||||
local metat = { __index = {} }
|
||||
|
||||
function metat.__index:greet(domain)
|
||||
socket.try(self.tp:check("2.."))
|
||||
socket.try(self.tp:command("EHLO", domain or DOMAIN))
|
||||
return socket.try(self.tp:check("2.."))
|
||||
end
|
||||
|
||||
function metat.__index:mail(from)
|
||||
socket.try(self.tp:command("MAIL", "FROM:" .. from))
|
||||
return socket.try(self.tp:check("2.."))
|
||||
end
|
||||
|
||||
function metat.__index:rcpt(to)
|
||||
socket.try(self.tp:command("RCPT", "TO:" .. to))
|
||||
return socket.try(self.tp:check("2.."))
|
||||
end
|
||||
|
||||
function metat.__index:data(src)
|
||||
socket.try(self.tp:command("DATA"))
|
||||
socket.try(self.tp:check("3.."))
|
||||
socket.try(self.tp:source(src))
|
||||
socket.try(self.tp:send("\r\n.\r\n"))
|
||||
return socket.try(self.tp:check("2.."))
|
||||
end
|
||||
|
||||
function metat.__index:quit()
|
||||
socket.try(self.tp:command("QUIT"))
|
||||
return socket.try(self.tp:check("2.."))
|
||||
end
|
||||
|
||||
function metat.__index:close()
|
||||
return socket.try(self.tp:close())
|
||||
end
|
||||
|
||||
-- send message or throw an exception
|
||||
function metat.__index:send(mailt)
|
||||
self:mail(mailt.from)
|
||||
if type(mailt.rcpt) == "table" then
|
||||
for i,v in ipairs(mailt.rcpt) do
|
||||
self:rcpt(v)
|
||||
end
|
||||
else
|
||||
self:rcpt(mailt.rcpt)
|
||||
end
|
||||
self:data(ltn12.source.chain(mailt.source, stuff()))
|
||||
end
|
||||
|
||||
function open(server, port)
|
||||
local tp, error = socket.tp.connect(server or SERVER, port or PORT)
|
||||
if not tp then return nil, error end
|
||||
return setmetatable({tp = tp}, metat)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- Multipart message source
|
||||
-----------------------------------------------------------------------------
|
||||
-- returns a hopefully unique mime boundary
|
||||
local seqno = 0
|
||||
local function newboundary()
|
||||
|
@ -147,13 +183,17 @@ function message(mesgt)
|
|||
return function() return shift(coroutine.resume(co)) end
|
||||
end
|
||||
|
||||
function send(mailt)
|
||||
local c, e = socket.tp.connect(mailt.server or SERVER, mailt.port or PORT)
|
||||
if not c then return nil, e end
|
||||
local s, e = pcall(send_p, c, mailt)
|
||||
c:close()
|
||||
if s then return true
|
||||
else return nil, e end
|
||||
end
|
||||
---------------------------------------------------------------------------
|
||||
-- High level SMTP API
|
||||
-----------------------------------------------------------------------------
|
||||
send = socket.protect(function(mailt)
|
||||
local server = mailt.server or SERVER
|
||||
local port = mailt.port or PORT
|
||||
local smtp = socket.try(open(server, port))
|
||||
smtp:greet(mailt.domain or DOMAIN)
|
||||
smtp:send(mailt)
|
||||
smtp:quit()
|
||||
return smtp:close()
|
||||
end)
|
||||
|
||||
return smtp
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "socket.h"
|
||||
#include "inet.h"
|
||||
#include "options.h"
|
||||
#include "base.h"
|
||||
#include "tcp.h"
|
||||
|
||||
/*=========================================================================*\
|
||||
|
@ -40,6 +41,7 @@ static int meth_dirty(lua_State *L);
|
|||
/* tcp object methods */
|
||||
static luaL_reg tcp[] = {
|
||||
{"__gc", meth_close},
|
||||
{"__tostring", base_meth_tostring},
|
||||
{"accept", meth_accept},
|
||||
{"bind", meth_bind},
|
||||
{"close", meth_close},
|
||||
|
@ -58,7 +60,6 @@ static luaL_reg tcp[] = {
|
|||
{"settimeout", meth_settimeout},
|
||||
{"shutdown", meth_shutdown},
|
||||
{NULL, NULL}
|
||||
|
||||
};
|
||||
|
||||
/* socket option handlers */
|
||||
|
|
76
src/tp.lua
76
src/tp.lua
|
@ -18,22 +18,19 @@ setfenv(1, socket.tp)
|
|||
|
||||
TIMEOUT = 60
|
||||
|
||||
-- gets server reply
|
||||
local function get_reply(sock)
|
||||
-- gets server reply (works for SMTP and FTP)
|
||||
local function get_reply(control)
|
||||
local code, current, separator, _
|
||||
local line, err = sock:receive()
|
||||
local line, err = control:receive()
|
||||
local reply = line
|
||||
if err then return nil, err 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, err = sock:receive()
|
||||
line, err = control:receive()
|
||||
if err then return nil, err end
|
||||
_,_, current, separator = string.find(line, "^(%d%d%d)(.)")
|
||||
if not current or not separator then
|
||||
return nil, "invalid server reply"
|
||||
end
|
||||
_,_, current, separator = string.find(line, "^(%d%d%d)(.?)")
|
||||
reply = reply .. "\n" .. line
|
||||
-- reply ends with same code
|
||||
until code == current and separator == " "
|
||||
|
@ -42,60 +39,73 @@ local function get_reply(sock)
|
|||
end
|
||||
|
||||
-- metatable for sock object
|
||||
local metatable = { __index = {} }
|
||||
local metat = { __index = {} }
|
||||
|
||||
function metatable.__index:check(ok)
|
||||
local code, reply = get_reply(self.sock)
|
||||
function metat.__index:check(ok)
|
||||
local code, reply = get_reply(self.control)
|
||||
if not code then return nil, reply end
|
||||
if type(ok) ~= "function" then
|
||||
if type(ok) == "table" then
|
||||
for i, v in ipairs(ok) do
|
||||
if string.find(code, v) then return code, reply end
|
||||
if string.find(code, v) then return tonumber(code), reply end
|
||||
end
|
||||
return nil, reply
|
||||
else
|
||||
if string.find(code, ok) then return code, reply
|
||||
if string.find(code, ok) then return tonumber(code), reply
|
||||
else return nil, reply end
|
||||
end
|
||||
else return ok(code, reply) end
|
||||
else return ok(tonumber(code), reply) end
|
||||
end
|
||||
|
||||
function metatable.__index:command(cmd, arg)
|
||||
if arg then return self.sock:send(cmd .. " " .. arg.. "\r\n")
|
||||
else return self.sock:send(cmd .. "\r\n") end
|
||||
function metat.__index:command(cmd, arg)
|
||||
if arg then return self.control:send(cmd .. " " .. arg.. "\r\n")
|
||||
else return self.control:send(cmd .. "\r\n") end
|
||||
end
|
||||
|
||||
function metatable.__index:sink(snk, pat)
|
||||
local chunk, err = sock:receive(pat)
|
||||
function metat.__index:sink(snk, pat)
|
||||
local chunk, err = control:receive(pat)
|
||||
return snk(chunk, err)
|
||||
end
|
||||
|
||||
function metatable.__index:send(data)
|
||||
return self.sock:send(data)
|
||||
function metat.__index:send(data)
|
||||
return self.control:send(data)
|
||||
end
|
||||
|
||||
function metatable.__index:receive(pat)
|
||||
return self.sock:receive(pat)
|
||||
function metat.__index:receive(pat)
|
||||
return self.control:receive(pat)
|
||||
end
|
||||
|
||||
function metatable.__index:source(src, instr)
|
||||
function metat.__index:getfd()
|
||||
return self.control:getfd()
|
||||
end
|
||||
|
||||
function metat.__index:dirty()
|
||||
return self.control:dirty()
|
||||
end
|
||||
|
||||
function metat.__index:getcontrol()
|
||||
return self.control
|
||||
end
|
||||
|
||||
function metat.__index:source(src, instr)
|
||||
while true do
|
||||
local chunk, err = src()
|
||||
if not chunk then return not err, err end
|
||||
local ret, err = self.sock:send(chunk)
|
||||
local ret, err = self.control:send(chunk)
|
||||
if not ret then return nil, err end
|
||||
end
|
||||
end
|
||||
|
||||
-- closes the underlying sock
|
||||
function metatable.__index:close()
|
||||
self.sock:close()
|
||||
-- closes the underlying control
|
||||
function metat.__index:close()
|
||||
self.control:close()
|
||||
return 1
|
||||
end
|
||||
|
||||
-- connect with server and return sock object
|
||||
-- connect with server and return control object
|
||||
function connect(host, port)
|
||||
local sock, err = socket.connect(host, port)
|
||||
if not sock then return nil, err end
|
||||
sock:settimeout(TIMEOUT)
|
||||
return setmetatable({sock = sock}, metatable)
|
||||
local control, err = socket.connect(host, port)
|
||||
if not control then return nil, err end
|
||||
control:settimeout(TIMEOUT)
|
||||
return setmetatable({control = control}, metat)
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "socket.h"
|
||||
#include "inet.h"
|
||||
#include "options.h"
|
||||
#include "base.h"
|
||||
#include "udp.h"
|
||||
|
||||
/*=========================================================================*\
|
||||
|
@ -50,6 +51,7 @@ static luaL_reg udp[] = {
|
|||
{"close", meth_close},
|
||||
{"setoption", meth_setoption},
|
||||
{"__gc", meth_close},
|
||||
{"__tostring", base_meth_tostring},
|
||||
{"getfd", meth_getfd},
|
||||
{"setfd", meth_setfd},
|
||||
{"dirty", meth_dirty},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue