Initial revision
This commit is contained in:
parent
6f9d15b660
commit
17c4d1c305
12 changed files with 1777 additions and 0 deletions
437
src/ftp.lua
Normal file
437
src/ftp.lua
Normal file
|
@ -0,0 +1,437 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Simple FTP support for the Lua language using the LuaSocket toolkit.
|
||||
-- Author: Diego Nehab
|
||||
-- Date: 26/12/2000
|
||||
-- Conforming to: RFC 959
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Program constants
|
||||
-----------------------------------------------------------------------------
|
||||
-- timeout in seconds before the program gives up on a connection
|
||||
local TIMEOUT = 60
|
||||
-- default port for ftp service
|
||||
local PORT = 21
|
||||
-- this is the default anonymous password. used when no password is
|
||||
-- provided in url. should be changed for your e-mail.
|
||||
local EMAIL = "anonymous@anonymous.org"
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Parses a url and returns its scheme, user, password, host, port
|
||||
-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
|
||||
-- of December 1994
|
||||
-- Input
|
||||
-- url: unique resource locator desired
|
||||
-- default: table containing default values to be returned
|
||||
-- Returns
|
||||
-- table with the following fields:
|
||||
-- host: host to connect
|
||||
-- path: url path
|
||||
-- port: host port to connect
|
||||
-- user: user name
|
||||
-- pass: password
|
||||
-- scheme: protocol
|
||||
-----------------------------------------------------------------------------
|
||||
local split_url = function(url, default)
|
||||
-- initialize default parameters
|
||||
local parsed = default or {}
|
||||
-- get scheme
|
||||
url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
|
||||
-- get user name and password. both can be empty!
|
||||
-- moreover, password can be ommited
|
||||
url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
|
||||
%parsed.user = u
|
||||
-- there can be an empty password, but the ':' has to be there
|
||||
-- or else there is no password
|
||||
%parsed.pass = nil -- kill default password
|
||||
if c == ":" then %parsed.pass = p end
|
||||
end)
|
||||
-- get host
|
||||
url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
|
||||
-- get port if any
|
||||
url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
|
||||
-- whatever is left is the path
|
||||
if url ~= "" then parsed.path = url end
|
||||
return parsed
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- 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
|
||||
-----------------------------------------------------------------------------
|
||||
local get_pasv = function(pasv)
|
||||
local a,b,c,d,p1,p2
|
||||
local ip, port
|
||||
_,_, a, b, c, d, p1, p2 =
|
||||
strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)")
|
||||
if not a or not b or not c or not d or not p1 or not p2 then
|
||||
return nil, nil
|
||||
end
|
||||
ip = format("%d.%d.%d.%d", a, b, c, d)
|
||||
port = tonumber(p1)*256 + tonumber(p2)
|
||||
return ip, port
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends a FTP command through socket
|
||||
-- Input
|
||||
-- control: control connection socket
|
||||
-- cmd: command
|
||||
-- arg: command argument if any
|
||||
-----------------------------------------------------------------------------
|
||||
local send_command = function(control, cmd, arg)
|
||||
local line, err
|
||||
if arg then line = cmd .. " " .. arg .. "\r\n"
|
||||
else line = cmd .. "\r\n" end
|
||||
err = control:send(line)
|
||||
return err
|
||||
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
|
||||
-----------------------------------------------------------------------------
|
||||
local get_answer = function(control)
|
||||
local code, lastcode, sep
|
||||
local line, err = control:receive()
|
||||
local answer = line
|
||||
if err then return nil, err end
|
||||
_,_, code, sep = strfind(line, "^(%d%d%d)(.)")
|
||||
if not code or not sep then return nil, answer end
|
||||
if sep == "-" then -- answer is multiline
|
||||
repeat
|
||||
line, err = control:receive()
|
||||
if err then return nil, err end
|
||||
_,_, lastcode, sep = strfind(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
|
||||
-----------------------------------------------------------------------------
|
||||
local check_answer = function(control, success)
|
||||
local answer, code = %get_answer(control)
|
||||
if not answer then
|
||||
control:close()
|
||||
return nil, code
|
||||
end
|
||||
if type(success) ~= "table" then success = {success} end
|
||||
for i = 1, getn(success) do
|
||||
if code == success[i] 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
|
||||
-----------------------------------------------------------------------------
|
||||
local try_command = function(control, cmd, arg, success)
|
||||
local err = %send_command(control, cmd, arg)
|
||||
if err then
|
||||
control:close()
|
||||
return nil, err
|
||||
end
|
||||
local code, answer = %check_answer(control, success)
|
||||
if not code then return nil, answer end
|
||||
return code, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Creates a table with all directories in path
|
||||
-- Input
|
||||
-- file: abolute path to file
|
||||
-- Returns
|
||||
-- file: filename
|
||||
-- path: table with directories to reach filename
|
||||
-- isdir: is it a directory or a file
|
||||
-----------------------------------------------------------------------------
|
||||
local split_path = function(file)
|
||||
local path = {}
|
||||
local isdir
|
||||
file = file or "/"
|
||||
-- directory ends with a '/'
|
||||
_,_, isdir = strfind(file, "([/])$")
|
||||
gsub(file, "([^/]+)", function (dir) tinsert(%path, dir) end)
|
||||
if not isdir then file = tremove(path)
|
||||
else file = nil end
|
||||
return file, path, isdir
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Check server greeting
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local check_greeting = function(control)
|
||||
local code, answer = %check_answer(control, {120, 220})
|
||||
if not code then return nil, answer end
|
||||
if code == 120 then -- please try again, somewhat busy now...
|
||||
code, answer = %check_answer(control, {220})
|
||||
end
|
||||
return code, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Log in on server
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- user: user name
|
||||
-- pass: user password if any
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local login = function(control, user, pass)
|
||||
local code, answer = %try_command(control, "user", parsed.user, {230, 331})
|
||||
if not code then return nil, answer end
|
||||
if code == 331 and parsed.pass then -- need pass and we have pass
|
||||
code, answer = %try_command(control, "pass", parsed.pass, {230, 202})
|
||||
end
|
||||
return code, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Change to target directory
|
||||
-- Input
|
||||
-- control: socket for control connection with server
|
||||
-- path: array with directories in order
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local cwd = function(control, path)
|
||||
local code, answer = 250, "Home directory used"
|
||||
for i = 1, getn(path) do
|
||||
code, answer = %try_command(control, "cwd", path[i], {250})
|
||||
if not code then return nil, answer end
|
||||
end
|
||||
return code, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Start data connection with server
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- Returns
|
||||
-- data: socket for data connection with server, nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local start_dataconnection = function(control)
|
||||
-- ask for passive data connection
|
||||
local code, answer = %try_command(control, "pasv", nil, {227})
|
||||
if not code then return nil, answer end
|
||||
-- get data connection parameters from server reply
|
||||
local host, port = %get_pasv(answer)
|
||||
if not host or not port then return nil, answer end
|
||||
-- start data connection with given parameters
|
||||
local data, err = connect(host, port)
|
||||
if not data then return nil, err end
|
||||
data:timeout(%TIMEOUT)
|
||||
return data
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Closes control connection with server
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local logout = function(control)
|
||||
local code, answer = %try_command(control, "quit", nil, {221})
|
||||
if not code then return nil, answer end
|
||||
control:close()
|
||||
return code, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Retrieves file or directory listing
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- data: data connection with server
|
||||
-- file: file name under current directory
|
||||
-- isdir: is file a directory name?
|
||||
-- Returns
|
||||
-- file: string with file contents, nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local retrieve_file = function(control, data, file, isdir)
|
||||
-- ask server for file or directory listing accordingly
|
||||
if isdir then code, answer = %try_command(control, "nlst", file, {150, 125})
|
||||
else code, answer = %try_command(control, "retr", file, {150, 125}) end
|
||||
if not code then
|
||||
control:close()
|
||||
data:close()
|
||||
return nil, answer
|
||||
end
|
||||
-- download whole file
|
||||
file, err = data:receive("*a")
|
||||
data:close()
|
||||
if err then
|
||||
control:close()
|
||||
return nil, err
|
||||
end
|
||||
-- make sure file transfered ok
|
||||
code, answer = %check_answer(control, {226, 250})
|
||||
if not code then return nil, answer
|
||||
else return file, answer end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Stores a file
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- data: data connection with server
|
||||
-- file: file name under current directory
|
||||
-- bytes: file contents in string
|
||||
-- Returns
|
||||
-- file: string with file contents, nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local store_file = function (control, data, file, bytes)
|
||||
local code, answer = %try_command(control, "stor", file, {150, 125})
|
||||
if not code then
|
||||
data:close()
|
||||
return nil, answer
|
||||
end
|
||||
-- send whole file and close connection to mark file end
|
||||
answer = data:send(bytes)
|
||||
data:close()
|
||||
if answer then
|
||||
control:close()
|
||||
return nil, answer
|
||||
end
|
||||
-- check if file was received right
|
||||
return %check_answer(control, {226, 250})
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Change transfer type
|
||||
-- Input
|
||||
-- control: control connection with server
|
||||
-- type: new transfer type
|
||||
-- Returns
|
||||
-- code: nil if error
|
||||
-- answer: server answer or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local change_type = function(control, type)
|
||||
if type == "b" then type = "i" else type = "a" end
|
||||
return %try_command(control, "type", type, {200})
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Retrieve a file from a ftp server
|
||||
-- Input
|
||||
-- url: file location
|
||||
-- type: "binary" or "ascii"
|
||||
-- Returns
|
||||
-- file: downloaded file or nil in case of error
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
function ftp_get(url, type)
|
||||
local control, data, err
|
||||
local answer, code, server, file, path
|
||||
parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL})
|
||||
-- start control connection
|
||||
control, err = connect(parsed.host, parsed.port)
|
||||
if not control then return nil, err end
|
||||
control:timeout(%TIMEOUT)
|
||||
-- get and check greeting
|
||||
code, answer = %check_greeting(control)
|
||||
if not code then return nil, answer end
|
||||
-- try to log in
|
||||
code, answer = %login(control, parsed.user, parsed.pass)
|
||||
if not code then return nil, answer end
|
||||
-- go to directory
|
||||
file, path, isdir = %split_path(parsed.path)
|
||||
code, answer = %cwd(control, path)
|
||||
if not code then return nil, answer end
|
||||
-- change to binary type?
|
||||
code, answer = %change_type(control, type)
|
||||
if not code then return nil, answer end
|
||||
-- start data connection
|
||||
data, answer = %start_dataconnection(control)
|
||||
if not data then return nil, answer end
|
||||
-- ask server to send file or directory listing
|
||||
file, answer = %retrieve_file(control, data, file, isdir)
|
||||
if not file then return nil, answer end
|
||||
-- disconnect
|
||||
%logout(control)
|
||||
-- return whatever file we received plus a possible error
|
||||
return file, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Uploads a file to a FTP server
|
||||
-- Input
|
||||
-- url: file location
|
||||
-- bytes: file contents
|
||||
-- type: "binary" or "ascii"
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
function ftp_put(url, bytes, type)
|
||||
local control, data
|
||||
local answer, code, server, file, path
|
||||
parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL})
|
||||
-- start control connection
|
||||
control, answer = connect(parsed.host, parsed.port)
|
||||
if not control then return answer end
|
||||
control:timeout(%TIMEOUT)
|
||||
-- get and check greeting
|
||||
code, answer = %check_greeting(control)
|
||||
if not code then return answer end
|
||||
-- try to log in
|
||||
code, answer = %login(control, parsed.user, parsed.pass)
|
||||
if not code then return answer end
|
||||
-- go to directory
|
||||
file, path, isdir = %split_path(parsed.path)
|
||||
code, answer = %cwd(control, path)
|
||||
if not code then return answer end
|
||||
-- change to binary type?
|
||||
code, answer = %change_type(control, type)
|
||||
if not code then return answer end
|
||||
-- start data connection
|
||||
data, answer = %start_dataconnection(control)
|
||||
if not data then return answer end
|
||||
-- ask server to send file or directory listing
|
||||
code, answer = %store_file(control, data, file, bytes)
|
||||
if not code then return answer end
|
||||
-- disconnect
|
||||
%logout(control)
|
||||
-- return whatever file we received plus a possible error
|
||||
return nil
|
||||
end
|
312
src/http.lua
Normal file
312
src/http.lua
Normal file
|
@ -0,0 +1,312 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit.
|
||||
-- Author: Diego Nehab
|
||||
-- Date: 26/12/2000
|
||||
-- Conforming to: RFC 2068
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Program constants
|
||||
-----------------------------------------------------------------------------
|
||||
-- connection timeout in seconds
|
||||
local TIMEOUT = 60
|
||||
-- default port for document retrieval
|
||||
local PORT = 80
|
||||
-- user agent field sent in request
|
||||
local USERAGENT = "LuaSocket/HTTP 1.0"
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tries to get a line from the server or close socket if error
|
||||
-- sock: socket connected to the server
|
||||
-- Returns
|
||||
-- line: line received or nil in case of error
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local try_getline = function(sock)
|
||||
line, err = sock:receive()
|
||||
if err then
|
||||
sock:close()
|
||||
return nil, err
|
||||
end
|
||||
return line
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tries to send a line to the server or close socket if error
|
||||
-- sock: socket connected to the server
|
||||
-- line: line to send
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local try_sendline = function(sock, line)
|
||||
err = sock:send(line)
|
||||
if err then sock:close() end
|
||||
return err
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Retrieves status from http reply
|
||||
-- Input
|
||||
-- reply: http reply string
|
||||
-- Returns
|
||||
-- status: integer with status code
|
||||
-----------------------------------------------------------------------------
|
||||
local get_status = function(reply)
|
||||
local _,_, status = strfind(reply, " (%d%d%d) ")
|
||||
return tonumber(status)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Receive server reply messages
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- Returns
|
||||
-- status: server reply status code or nil if error
|
||||
-- reply: full server reply
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local get_reply = function(sock)
|
||||
local reply, err
|
||||
reply, err = %try_getline(sock)
|
||||
if not err then return %get_status(reply), reply
|
||||
else return nil, nil, err end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Receive and parse mime headers
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- mime: a table that might already contain mime headers
|
||||
-- Returns
|
||||
-- mime: a table with all mime headers in the form
|
||||
-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"}
|
||||
-- all name_i are lowercase
|
||||
-- nil and error message in case of error
|
||||
-----------------------------------------------------------------------------
|
||||
local get_mime = function(sock, mime)
|
||||
local line, err
|
||||
local name, value
|
||||
-- get first line
|
||||
line, err = %try_getline(sock)
|
||||
if err then return nil, err end
|
||||
-- headers go until a blank line is found
|
||||
while line ~= "" do
|
||||
-- get field-name and value
|
||||
_,_, name, value = strfind(line, "(.-):%s*(.*)")
|
||||
name = strlower(name)
|
||||
-- get next line (value might be folded)
|
||||
line, err = %try_getline(sock)
|
||||
if err then return nil, err end
|
||||
-- unfold any folded values
|
||||
while not err and line ~= "" and (strsub(line, 1, 1) == " ") do
|
||||
value = value .. line
|
||||
line, err = %try_getline(sock)
|
||||
if err then return nil, err end
|
||||
end
|
||||
-- save pair in table
|
||||
if mime[name] then
|
||||
-- join any multiple field
|
||||
mime[name] = mime[name] .. ", " .. value
|
||||
else
|
||||
-- new field
|
||||
mime[name] = value
|
||||
end
|
||||
end
|
||||
return mime
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Receives http body
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- mime: initial mime headers
|
||||
-- Returns
|
||||
-- body: a string containing the body of the document
|
||||
-- nil and error message in case of error
|
||||
-- Obs:
|
||||
-- mime: headers might be modified by chunked transfer
|
||||
-----------------------------------------------------------------------------
|
||||
local get_body = function(sock, mime)
|
||||
local body, err
|
||||
if mime["transfer-encoding"] == "chunked" then
|
||||
local chunk_size, line
|
||||
body = ""
|
||||
repeat
|
||||
-- get chunk size, skip extention
|
||||
line, err = %try_getline(sock)
|
||||
if err then return nil, err end
|
||||
chunk_size = tonumber(gsub(line, ";.*", ""), 16)
|
||||
if not chunk_size then
|
||||
sock:close()
|
||||
return nil, "invalid chunk size"
|
||||
end
|
||||
-- get chunk
|
||||
line, err = sock:receive(chunk_size)
|
||||
if err then
|
||||
sock:close()
|
||||
return nil, err
|
||||
end
|
||||
-- concatenate new chunk
|
||||
body = body .. line
|
||||
-- skip blank line
|
||||
_, err = %try_getline(sock)
|
||||
if err then return nil, err end
|
||||
until chunk_size <= 0
|
||||
-- store extra mime headers
|
||||
--_, err = %get_mime(sock, mime)
|
||||
--if err then return nil, err end
|
||||
elseif mime["content-length"] then
|
||||
body, err = sock:receive(tonumber(mime["content-length"]))
|
||||
if err then
|
||||
sock:close()
|
||||
return nil, err
|
||||
end
|
||||
else
|
||||
-- get it all until connection closes!
|
||||
body, err = sock:receive("*a")
|
||||
if err then
|
||||
sock:close()
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
-- return whole body
|
||||
return body
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Parses a url and returns its scheme, user, password, host, port
|
||||
-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
|
||||
-- of December 1994
|
||||
-- Input
|
||||
-- url: unique resource locator desired
|
||||
-- default: table containing default values to be returned
|
||||
-- Returns
|
||||
-- table with the following fields:
|
||||
-- host: host to connect
|
||||
-- path: url path
|
||||
-- port: host port to connect
|
||||
-- user: user name
|
||||
-- pass: password
|
||||
-- scheme: protocol
|
||||
-----------------------------------------------------------------------------
|
||||
local split_url = function(url, default)
|
||||
-- initialize default parameters
|
||||
local parsed = default or {}
|
||||
-- get scheme
|
||||
url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
|
||||
-- get user name and password. both can be empty!
|
||||
-- moreover, password can be ommited
|
||||
url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
|
||||
%parsed.user = u
|
||||
-- there can be an empty password, but the ':' has to be there
|
||||
-- or else there is no password
|
||||
%parsed.pass = nil -- kill default password
|
||||
if c == ":" then %parsed.pass = p end
|
||||
end)
|
||||
-- get host
|
||||
url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
|
||||
-- get port if any
|
||||
url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
|
||||
-- whatever is left is the path
|
||||
if url ~= "" then parsed.path = url end
|
||||
return parsed
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends a GET message through socket
|
||||
-- Input
|
||||
-- socket: http connection socket
|
||||
-- path: path requested
|
||||
-- mime: mime headers to send in request
|
||||
-- Returns
|
||||
-- err: nil in case of success, error message otherwise
|
||||
-----------------------------------------------------------------------------
|
||||
local send_get = function(sock, path, mime)
|
||||
local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n")
|
||||
if err then return err end
|
||||
for i, v in mime do
|
||||
err = %try_sendline(sock, i .. ": " .. v .. "\r\n")
|
||||
if err then return err end
|
||||
end
|
||||
err = %try_sendline(sock, "\r\n")
|
||||
return err
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Converts field names to lowercase
|
||||
-- Input
|
||||
-- headers: user header fields
|
||||
-- parsed: parsed url components
|
||||
-- Returns
|
||||
-- mime: a table with the same headers, but with lowercase field names
|
||||
-----------------------------------------------------------------------------
|
||||
local fill_headers = function(headers, parsed)
|
||||
local mime = {}
|
||||
headers = headers or {}
|
||||
for i,v in headers do
|
||||
mime[strlower(i)] = v
|
||||
end
|
||||
mime["connection"] = "close"
|
||||
mime["host"] = parsed.host
|
||||
mime["user-agent"] = %USERAGENT
|
||||
if parsed.user and parsed.pass then -- Basic Authentication
|
||||
mime["authorization"] = "Basic "..
|
||||
base64(parsed.user .. ":" .. parsed.pass)
|
||||
end
|
||||
return mime
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- We need base64 convertion routines for Basic Authentication Scheme
|
||||
-----------------------------------------------------------------------------
|
||||
dofile("base64.lua")
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Downloads and receives a http url, with its mime headers
|
||||
-- Input
|
||||
-- url: unique resource locator desired
|
||||
-- headers: headers to send with request
|
||||
-- tried: is this an authentication retry?
|
||||
-- Returns
|
||||
-- body: document body, if successfull
|
||||
-- mime: headers received with document, if sucessfull
|
||||
-- reply: server reply, if successfull
|
||||
-- err: error message, if any
|
||||
-----------------------------------------------------------------------------
|
||||
function http_get(url, headers)
|
||||
local sock, err, mime, body, status, reply
|
||||
-- get url components
|
||||
local parsed = %split_url(url, {port = %PORT, path ="/"})
|
||||
-- fill default headers
|
||||
headers = %fill_headers(headers, parsed)
|
||||
-- try connection
|
||||
sock, err = connect(parsed.host, parsed.port)
|
||||
if not sock then return nil, nil, nil, err end
|
||||
-- set connection timeout
|
||||
sock:timeout(%TIMEOUT)
|
||||
-- send request
|
||||
err = %send_get(sock, parsed.path, headers)
|
||||
if err then return nil, nil, nil, err end
|
||||
-- get server message
|
||||
status, reply, err = %get_reply(sock)
|
||||
if err then return nil, nil, nil, err end
|
||||
-- get url accordingly
|
||||
if status == 200 then -- ok, go on and get it
|
||||
mime, err = %get_mime(sock, {})
|
||||
if err then return nil, nil, reply, err end
|
||||
body, err = %get_body(sock, mime)
|
||||
if err then return nil, mime, reply, err end
|
||||
sock:close()
|
||||
return body, mime, reply
|
||||
elseif status == 301 then -- moved permanently, try again
|
||||
mime = %get_mime(sock, {})
|
||||
sock:close()
|
||||
if mime["location"] then return http_get(mime["location"], headers)
|
||||
else return nil, mime, reply end
|
||||
elseif status == 401 then
|
||||
mime, err = %get_mime(sock, {})
|
||||
if err then return nil, nil, reply, err end
|
||||
return nil, mime, reply
|
||||
end
|
||||
return nil, nil, reply
|
||||
end
|
18
src/luasocket.h
Normal file
18
src/luasocket.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*=========================================================================*\
|
||||
* TCP/IP support for LUA
|
||||
* Diego Nehab
|
||||
* 9/11/1999
|
||||
\*=========================================================================*/
|
||||
|
||||
#ifndef _LUASOCKET_H_
|
||||
#define _LUASOCKET_H_
|
||||
|
||||
/*=========================================================================*\
|
||||
* Exported function declarations
|
||||
\*=========================================================================*/
|
||||
/*-------------------------------------------------------------------------*\
|
||||
* Initializes toolkit
|
||||
\*-------------------------------------------------------------------------*/
|
||||
void lua_socketlibopen(lua_State *L);
|
||||
|
||||
#endif /* _LUASOCKET_H_ */
|
338
src/smtp.lua
Normal file
338
src/smtp.lua
Normal file
|
@ -0,0 +1,338 @@
|
|||
-----------------------------------------------------------------------------
|
||||
-- Simple SMTP support for the Lua language using the LuaSocket toolkit.
|
||||
-- Author: Diego Nehab
|
||||
-- Date: 26/12/2000
|
||||
-- Conforming to: RFC 821
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Program constants
|
||||
-----------------------------------------------------------------------------
|
||||
-- timeout in secconds before we give up waiting
|
||||
local TIMEOUT = 180
|
||||
-- port used for connection
|
||||
local PORT = 25
|
||||
-- domain used in HELO command. If we are under a CGI, try to get from
|
||||
-- environment
|
||||
local DOMAIN = getenv("SERVER_NAME")
|
||||
if not DOMAIN then
|
||||
DOMAIN = "localhost"
|
||||
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
|
||||
-----------------------------------------------------------------------------
|
||||
local puts = function(sock, line)
|
||||
local err = sock:send(line .. "\r\n")
|
||||
if err then sock:close() end
|
||||
return err
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tries to receive DOS mode lines. Closes socket on error.
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- Returns
|
||||
-- line: received string if successfull, nil in case of error
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local gets = function(sock)
|
||||
local line, err = sock:receive("*l")
|
||||
if err then
|
||||
sock:close()
|
||||
return nil, err
|
||||
end
|
||||
return line
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Gets a reply from the server and close connection if it is wrong
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- accept: acceptable errorcodes
|
||||
-- Returns
|
||||
-- code: server reply code. nil if error
|
||||
-- line: complete server reply message or error message
|
||||
-----------------------------------------------------------------------------
|
||||
local get_reply = function(sock, accept)
|
||||
local line, err = %gets(sock)
|
||||
if line then
|
||||
if type(accept) ~= "table" then accept = {accept} end
|
||||
local _,_, code = strfind(line, "^(%d%d%d)")
|
||||
if not code then return nil, line end
|
||||
code = tonumber(code)
|
||||
for i = 1, getn(accept) do
|
||||
if code == accept[i] then return code, line end
|
||||
end
|
||||
sock:close()
|
||||
return nil, line
|
||||
end
|
||||
return nil, err
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends a command to the server
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- command: command to be sent
|
||||
-- param: command parameters if any
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local send_command = function(sock, command, param)
|
||||
local line
|
||||
if param then line = command .. " " .. param
|
||||
else line = command end
|
||||
return %puts(sock, line)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Gets the initial server greeting
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- Returns
|
||||
-- code: server status code, nil if error
|
||||
-- answer: complete server reply
|
||||
-----------------------------------------------------------------------------
|
||||
local get_helo = function(sock)
|
||||
return %get_reply(sock, 220)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends initial client greeting
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- Returns
|
||||
-- code: server status code, nil if error
|
||||
-- answer: complete server reply
|
||||
-----------------------------------------------------------------------------
|
||||
local send_helo = function(sock)
|
||||
local err = %send_command(sock, "HELO", %DOMAIN)
|
||||
if not err then
|
||||
return %get_reply(sock, 250)
|
||||
else return nil, err end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends mime headers
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- mime: table with mime headers to be sent
|
||||
-- Returns
|
||||
-- err: error message if any
|
||||
-----------------------------------------------------------------------------
|
||||
local send_mime = function(sock, mime)
|
||||
local err
|
||||
mime = mime or {}
|
||||
-- send all headers
|
||||
for name,value in mime do
|
||||
err = sock:send(name .. ": " .. value .. "\r\n")
|
||||
if err then
|
||||
sock:close()
|
||||
return err
|
||||
end
|
||||
end
|
||||
-- end mime part
|
||||
err = sock:send("\r\n")
|
||||
if err then sock:close() end
|
||||
return err
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends connection termination command
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- Returns
|
||||
-- code: server status code, nil if error
|
||||
-- answer: complete server reply
|
||||
-----------------------------------------------------------------------------
|
||||
local send_quit = function(sock)
|
||||
local code, answer
|
||||
local err = %send_command(sock, "QUIT")
|
||||
if not err then
|
||||
code, answer = %get_reply(sock, 221)
|
||||
sock:close()
|
||||
return code, answer
|
||||
else return nil, err end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends sender command
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- sender: e-mail of sender
|
||||
-- Returns
|
||||
-- code: server status code, nil if error
|
||||
-- answer: complete server reply
|
||||
-----------------------------------------------------------------------------
|
||||
local send_mail = function(sock, sender)
|
||||
local param = format("FROM:<%s>", sender)
|
||||
local err = %send_command(sock, "MAIL", param)
|
||||
if not err then
|
||||
return %get_reply(sock, 250)
|
||||
else return nil, err end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends message mime headers and body
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- mime: table containing all mime headers to be sent
|
||||
-- body: message body
|
||||
-- Returns
|
||||
-- code: server status code, nil if error
|
||||
-- answer: complete server reply
|
||||
-----------------------------------------------------------------------------
|
||||
local send_data = function (sock, mime, body)
|
||||
local err = %send_command(sock, "DATA")
|
||||
if not err then
|
||||
local code, answer = %get_reply(sock, 354)
|
||||
if not code then return nil, answer end
|
||||
-- avoid premature end in message body
|
||||
body = gsub(body or "", "\n%.", "\n%.%.")
|
||||
-- mark end of message body
|
||||
body = body .. "\r\n."
|
||||
err = %send_mime(sock, mime)
|
||||
if err then return nil, err end
|
||||
err = %puts(sock, body)
|
||||
return %get_reply(sock, 250)
|
||||
else return nil, err end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends recipient list command
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- rcpt: lua table with recipient list
|
||||
-- Returns
|
||||
-- code: server status code, nil if error
|
||||
-- answer: complete server reply
|
||||
-----------------------------------------------------------------------------
|
||||
local send_rcpt = function(sock, rcpt)
|
||||
local err, code, answer
|
||||
if type(rcpt) ~= "table" then rcpt = {rcpt} end
|
||||
for i = 1, getn(rcpt) do
|
||||
err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i]))
|
||||
if not err then
|
||||
code, answer = %get_reply(sock, {250, 251})
|
||||
if not code then return code, answer end
|
||||
else return nil, err end
|
||||
end
|
||||
return code, answer
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Sends verify recipient command
|
||||
-- Input
|
||||
-- sock: server socket
|
||||
-- user: user to be verified
|
||||
-- Returns
|
||||
-- code: server status code, nil if error
|
||||
-- answer: complete server reply
|
||||
-----------------------------------------------------------------------------
|
||||
local send_vrfy = function (sock, user)
|
||||
local err = %send_command(sock, "VRFY", format("<%s>", user))
|
||||
if not err then
|
||||
return %get_reply(sock, {250, 251})
|
||||
else return nil, err end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Connection oriented mail functions
|
||||
-----------------------------------------------------------------------------
|
||||
function smtp_connect(server)
|
||||
local code, answer
|
||||
-- connect to server
|
||||
local sock, err = connect(server, %PORT)
|
||||
if not sock then return nil, err end
|
||||
sock:timeout(%TIMEOUT)
|
||||
-- initial server greeting
|
||||
code, answer = %get_helo(sock)
|
||||
if not code then return nil, answer end
|
||||
-- HELO
|
||||
code, answer = %send_helo(sock)
|
||||
if not code then return nil, answer end
|
||||
return sock
|
||||
end
|
||||
|
||||
function smtp_send(sock, from, rcpt, mime, body)
|
||||
local code, answer
|
||||
-- MAIL
|
||||
code, answer = %send_mail(sock, from)
|
||||
if not code then return nil, answer end
|
||||
-- RCPT
|
||||
code, answer = %send_rcpt(sock, rcpt)
|
||||
if not code then return nil, answer end
|
||||
-- DATA
|
||||
return %send_data(sock, mime, body)
|
||||
end
|
||||
|
||||
function smtp_close(sock)
|
||||
-- QUIT
|
||||
return %send_quit(sock)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Main mail function
|
||||
-- Input
|
||||
-- from: message sender
|
||||
-- rcpt: table containing message recipients
|
||||
-- mime: table containing mime headers
|
||||
-- body: message body
|
||||
-- server: smtp server to be used
|
||||
-- Returns
|
||||
-- nil if successfull, error message in case of error
|
||||
-----------------------------------------------------------------------------
|
||||
function smtp_mail(from, rcpt, mime, body, server)
|
||||
local sock, err = smtp_connect(server)
|
||||
if not sock then return err end
|
||||
local code, answer = smtp_send(sock, from, rcpt, mime, body)
|
||||
if not code then return answer end
|
||||
code, answer = smtp_close(sock)
|
||||
if not code then return answer
|
||||
else return nil end
|
||||
end
|
||||
|
||||
--===========================================================================
|
||||
-- Compatibility functions
|
||||
--===========================================================================
|
||||
-----------------------------------------------------------------------------
|
||||
-- Converts a comma separated list into a Lua table with one entry for each
|
||||
-- list element.
|
||||
-- Input
|
||||
-- str: string containing the list to be converted
|
||||
-- tab: table to be filled with entries
|
||||
-- Returns
|
||||
-- a table t, where t.n is the number of elements with an entry t[i]
|
||||
-- for each element
|
||||
-----------------------------------------------------------------------------
|
||||
local fill = function(str, tab)
|
||||
gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end)
|
||||
return tab
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Client mail function, implementing CGILUA 3.2 interface
|
||||
-----------------------------------------------------------------------------
|
||||
function mail(msg)
|
||||
local rcpt = {}
|
||||
local mime = {}
|
||||
mime["Subject"] = msg.subject
|
||||
mime["To"] = msg.to
|
||||
mime["From"] = msg.from
|
||||
%fill(msg.to, rcpt)
|
||||
if msg.cc then
|
||||
%fill(msg.cc, rcpt)
|
||||
mime["Cc"] = msg.cc
|
||||
end
|
||||
if msg.bcc then
|
||||
%fill(msg.bcc, rcpt)
|
||||
end
|
||||
rcpt.n = nil
|
||||
return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver)
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue