mirror of
https://github.com/lunarmodules/luasocket.git
synced 2024-12-25 12:08:21 +01:00
Initial revision
This commit is contained in:
parent
6f9d15b660
commit
17c4d1c305
24
README
Normal file
24
README
Normal file
@ -0,0 +1,24 @@
|
||||
This directory contains the implementation of the protocols FTP, HTTP and
|
||||
SMTP. The files provided are:
|
||||
|
||||
http.lua -- HTTP protocol implementation
|
||||
base64.lua -- base64 encoding implementation
|
||||
|
||||
The module http.lua provides functionality to download an URL from a
|
||||
HTTP server. The implementation conforms to the HTTP/1.1 standard, RFC
|
||||
2068. The base64.lua module provides base64 encoding and decoding. The
|
||||
module is used for the HTTP Basic Authentication Scheme, and conforms to
|
||||
RFC 1521.
|
||||
|
||||
smtp.lua -- SMTP protocol implementation
|
||||
|
||||
The module smtp.lua provides functionality to send e-mail messages to a
|
||||
SMTP mail server. The implementation conforms to RFC 821.
|
||||
|
||||
ftp.lua -- FTP protocol implementation
|
||||
|
||||
The module ftp.lua provides functions to download and upload files from
|
||||
and to FTP servers. The implementation conforms to RFC 959.
|
||||
|
||||
These implementations are supported. Please send any comments do
|
||||
diego@tecgraf.puc-rio.br.
|
39
etc/dict.lua
Normal file
39
etc/dict.lua
Normal file
@ -0,0 +1,39 @@
|
||||
-- dict.lua
|
||||
-- simple client for DICT protocol (see http://www.dict.org/)
|
||||
-- shows definitions for each word from stdin. uses only "wn" dictionary.
|
||||
-- if a word is "=", then the rest of the line is sent verbatim as a protocol
|
||||
-- command to the server.
|
||||
|
||||
if verbose then verbose=write else verbose=function()end end
|
||||
|
||||
verbose(">>> connecting to server\n")
|
||||
local s,e=connect("dict.org",2628)
|
||||
assert(s,e)
|
||||
verbose(">>> connected\n")
|
||||
|
||||
while 1 do
|
||||
local w=read"*w"
|
||||
if w==nil then break end
|
||||
if w=="=" then
|
||||
w=read"*l"
|
||||
verbose(">>>",w,"\n")
|
||||
send(s,w,"\r\n")
|
||||
else
|
||||
verbose(">>> looking up `",w,"'\n")
|
||||
send(s,"DEFINE wn ",w,"\r\n")
|
||||
end
|
||||
while 1 do
|
||||
local l=receive(s)
|
||||
if l==nil then break end
|
||||
if strfind(l,"^[0-9]") then
|
||||
write("<<< ",l,"\n")
|
||||
else
|
||||
write(l,"\n")
|
||||
end
|
||||
if strfind(l,"^250") or strfind(l,"^[45]") then break end
|
||||
end
|
||||
end
|
||||
|
||||
send(s,"QUIT\r\n")
|
||||
verbose("<<< ",receive(s),"\n")
|
||||
close(s)
|
81
makefile.dist
Normal file
81
makefile.dist
Normal file
@ -0,0 +1,81 @@
|
||||
#--------------------------------------------------------------------------
|
||||
# LuaSocket makefile
|
||||
# Test executable for socklib
|
||||
# Diego Nehab, 29/8/1999
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
# don't echo commands
|
||||
# .SILENT:
|
||||
|
||||
CXX = g++
|
||||
CC = gcc
|
||||
|
||||
DIST = luasocket-1.1
|
||||
|
||||
WARNINGS = -Wall -Wshadow -Wpointer-arith -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs
|
||||
|
||||
CFLAGS = $(WARNINGS) -D_DEBUG -O2
|
||||
|
||||
LUA = /home/diego/lib/lua
|
||||
LUALIB = $(LUA)/lib
|
||||
LUAINC = $(LUA)/include
|
||||
|
||||
INC = -I$(LUAINC)
|
||||
# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lsocket -lnsl
|
||||
# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lnsl
|
||||
LIB = $(LUALIB)/liblualib.a $(LUALIB)/liblua.a -lm
|
||||
|
||||
SRC = ~diego/tec/luasocket
|
||||
OBJ = /tmp
|
||||
DEP = /tmp
|
||||
|
||||
# list of .cpp files
|
||||
c_sources = luasocket.c lua.c
|
||||
|
||||
# corresponding .o files
|
||||
c_objects = $(addprefix $(OBJ)/, $(addsuffix .o, $(basename $(c_sources))))
|
||||
|
||||
# binary depends on objects
|
||||
luasocket: $(c_objects)
|
||||
$(CC) $(CPPFLAGS) -o $@ $(c_objects) $(LIB)
|
||||
|
||||
# rule to create them
|
||||
$(c_objects): $(OBJ)/%.o: $(SRC)/%.c
|
||||
$(CC) -c $(CFLAGS) $(INC) -o $@ $<
|
||||
|
||||
# corresponding .d files
|
||||
c_deps = $(addprefix $(DEP)/, $(addsuffix .d, $(basename $(c_sources))))
|
||||
|
||||
# makefile depend on them...
|
||||
makefile : $(c_deps)
|
||||
|
||||
# ... since it includes them
|
||||
-include $(c_deps)
|
||||
|
||||
# rule to create them
|
||||
$(c_deps) : $(DEP)/%.d : $(SRC)/%.c
|
||||
$(SHELL) -ec '$(CC) -MM $(CFLAGS) $(INC) $< \
|
||||
| sed '\''s%\($*\.o\)%$@ $(OBJ)/\1%'\'' > $@; \
|
||||
[ -s $@ ] || rm -f $@'
|
||||
|
||||
# clean all trash
|
||||
clean:
|
||||
rm -f $(OBJ)/*.o
|
||||
rm -f $(DEP)/*.d
|
||||
rm -f luasocket core
|
||||
|
||||
dist:
|
||||
mkdir -p $(DIST)/lua
|
||||
mkdir -p $(DIST)/examples
|
||||
mkdir -p $(DIST)/html
|
||||
cp -vf *.c $(DIST)
|
||||
cp -vf *.h $(DIST)
|
||||
cp -vf makefile $(DIST)
|
||||
cp -vf README $(DIST)
|
||||
cp -vf lua/*.lua $(DIST)/lua
|
||||
cp -vf lua/README $(DIST)/lua
|
||||
cp -vf examples/*.lua $(DIST)/examples
|
||||
cp -vf examples/README $(DIST)/examples
|
||||
cp -vf html/manual.html $(DIST)/html
|
||||
tar -zcvf $(DIST).tar.gz $(DIST)
|
||||
zip -r $(DIST).zip $(DIST)
|
32
samples/README
Normal file
32
samples/README
Normal file
@ -0,0 +1,32 @@
|
||||
This directory contains some sample programs using LuaSocket as well as
|
||||
the automatic tests used to make sure the library is working properly.
|
||||
|
||||
The files provided are:
|
||||
|
||||
server.lua -- test server
|
||||
client.lua -- test client
|
||||
command.lua -- test command definitions
|
||||
|
||||
The automatic tests are composed by three files: client.lua, command.lua
|
||||
and server.lua. To run the automatic tests on your system, make sure to
|
||||
compile the library with _DEBUG defined (check makefile) and then open
|
||||
two terminals. Run 'luasocket server.lua' on one of them and 'luasocket
|
||||
client.lua' on the other. The programs should start talking to each
|
||||
other.
|
||||
|
||||
listen.lua -- echo server
|
||||
talk.lua -- echo tester
|
||||
|
||||
listen.lua and talk.lua are about the simplest applications you can
|
||||
write using LuaSocket. Run 'luasocket listen.lua' and 'luasocket
|
||||
talk.lua' on different terminals. Whatever you type on talk.lua will be
|
||||
printed by listen.lua.
|
||||
|
||||
dict.lua -- dict client
|
||||
|
||||
The dict.lua module is a cool simple client for the DICT protocol,
|
||||
written by Luiz Henrique Figueiredo. Just run it and enter a few words
|
||||
to see it working.
|
||||
|
||||
Good luck,
|
||||
Diego.
|
25
samples/listener.lua
Normal file
25
samples/listener.lua
Normal file
@ -0,0 +1,25 @@
|
||||
host = "localhost"
|
||||
port = 8080
|
||||
if arg then
|
||||
host = arg[1] or host
|
||||
port = arg[2] or port
|
||||
end
|
||||
print("Binding to host '" ..host.. "' and port " ..port.. "...")
|
||||
s, i, p, e = bind(host, port)
|
||||
if not s then
|
||||
print(e)
|
||||
exit()
|
||||
end
|
||||
print("Waiting connection from talker on " .. i .. ":" .. p .. "...")
|
||||
c, e = s:accept()
|
||||
if not c then
|
||||
print(e)
|
||||
exit()
|
||||
end
|
||||
print("Connected. Here is the stuff:")
|
||||
l, e = c:receive()
|
||||
while not e do
|
||||
print(l)
|
||||
l, e = c:receive()
|
||||
end
|
||||
print(e)
|
22
samples/talker.lua
Normal file
22
samples/talker.lua
Normal file
@ -0,0 +1,22 @@
|
||||
host = "localhost"
|
||||
port = 8080
|
||||
if arg then
|
||||
host = arg[1] or host
|
||||
port = arg[2] or port
|
||||
end
|
||||
print("Attempting connection to host '" ..host.. "' and port " ..port.. "...")
|
||||
c, e = connect(host, port)
|
||||
if not c then
|
||||
print(e)
|
||||
exit()
|
||||
end
|
||||
print("Connected! Please type stuff (empty line to stop):")
|
||||
l = read()
|
||||
while l and l ~= "" and not e do
|
||||
e = c:send(l, "\n")
|
||||
if e then
|
||||
print(e)
|
||||
exit()
|
||||
end
|
||||
l = read()
|
||||
end
|
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
|
359
test/testclnt.lua
Normal file
359
test/testclnt.lua
Normal file
@ -0,0 +1,359 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- LuaSocket automated test module
|
||||
-- client.lua
|
||||
-- This is the client module. It connects with the server module and executes
|
||||
-- all tests.
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Prints a header to separate the test phases
|
||||
-- Input
|
||||
-- test: test phase name
|
||||
-----------------------------------------------------------------------------
|
||||
function new_test(test)
|
||||
write("----------------------------------------------\n",
|
||||
test, "\n",
|
||||
"----------------------------------------------\n")
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Read command definitions and stablish control connection
|
||||
-----------------------------------------------------------------------------
|
||||
new_test("initializing...")
|
||||
dofile("command.lua")
|
||||
test_debug_mode()
|
||||
while control == nil do
|
||||
print("client: trying control connection...")
|
||||
control, err = connect(HOST, PORT)
|
||||
if control then
|
||||
print("client: control connection stablished!")
|
||||
else
|
||||
sleep(2)
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Make sure server is ready for data transmission
|
||||
-----------------------------------------------------------------------------
|
||||
function sync()
|
||||
send_command(SYNC)
|
||||
get_command()
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Close and reopen data connection, to get rid of any unread blocks
|
||||
-----------------------------------------------------------------------------
|
||||
function reconnect()
|
||||
if data then
|
||||
data:close()
|
||||
send_command(CLOSE)
|
||||
data = nil
|
||||
end
|
||||
while data == nil do
|
||||
send_command(CONNECT)
|
||||
data = connect(HOST, PORT)
|
||||
if not data then
|
||||
print("client: waiting for data connection.")
|
||||
sleep(1)
|
||||
end
|
||||
end
|
||||
sync()
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests the command connection
|
||||
-----------------------------------------------------------------------------
|
||||
function test_command(cmd, par)
|
||||
local cmd_back, par_back
|
||||
reconnect()
|
||||
send_command(COMMAND)
|
||||
write("testing command ")
|
||||
print_command(cmd, par)
|
||||
send_command(cmd, par)
|
||||
cmd_back, par_back = get_command()
|
||||
if cmd_back ~= cmd or par_back ~= par then
|
||||
fail(cmd)
|
||||
else
|
||||
pass()
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests ASCII line transmission
|
||||
-- Input
|
||||
-- len: length of line to be tested
|
||||
-----------------------------------------------------------------------------
|
||||
function test_asciiline(len)
|
||||
local str, str10, back, err
|
||||
reconnect()
|
||||
send_command(ECHO_LINE)
|
||||
str = strrep("x", mod(len, 10))
|
||||
str10 = strrep("aZb.c#dAe?", floor(len/10))
|
||||
str = str .. str10
|
||||
write("testing ", len, " byte(s) line\n")
|
||||
err = data:send(str, "\n")
|
||||
if err then fail(err) end
|
||||
back, err = data:receive()
|
||||
if err then fail(err) end
|
||||
if back == str then pass("lines match")
|
||||
else fail("lines don't match") end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests closed connection detection
|
||||
-----------------------------------------------------------------------------
|
||||
function test_closed()
|
||||
local str = "This is our little test line"
|
||||
local len = strlen(str)
|
||||
local back, err, total
|
||||
reconnect()
|
||||
print("testing close while reading line")
|
||||
send_command(ECHO_BLOCK, len)
|
||||
data:send(str)
|
||||
send_command(CLOSE)
|
||||
-- try to get a line
|
||||
back, err = data:receive()
|
||||
if not err then fail("shold have gotten 'closed'.")
|
||||
elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.")
|
||||
elseif str ~= back then fail("didn't receive what i should 'closed'.")
|
||||
else pass("rightfull 'closed' received") end
|
||||
reconnect()
|
||||
print("testing close while reading block")
|
||||
send_command(ECHO_BLOCK, len)
|
||||
data:send(str)
|
||||
send_command(CLOSE)
|
||||
-- try to get a line
|
||||
back, err = data:receive(2*len)
|
||||
if not err then fail("shold have gotten 'closed'.")
|
||||
elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.")
|
||||
elseif str ~= back then fail("didn't receive what I should.")
|
||||
else pass("rightfull 'closed' received") end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests binary line transmission
|
||||
-- Input
|
||||
-- len: length of line to be tested
|
||||
-----------------------------------------------------------------------------
|
||||
function test_rawline(len)
|
||||
local str, str10, back, err
|
||||
reconnect()
|
||||
send_command(ECHO_LINE)
|
||||
str = strrep(strchar(47), mod(len, 10))
|
||||
str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10))
|
||||
str = str .. str10
|
||||
write("testing ", len, " byte(s) line\n")
|
||||
err = data:send(str, "\n")
|
||||
if err then fail(err) end
|
||||
back, err = data:receive()
|
||||
if err then fail(err) end
|
||||
if back == str then pass("lines match")
|
||||
else fail("lines don't match") end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests block transmission
|
||||
-- Input
|
||||
-- len: length of block to be tested
|
||||
-----------------------------------------------------------------------------
|
||||
function test_block(len)
|
||||
local half = floor(len/2)
|
||||
local s1, s2, back, err
|
||||
reconnect()
|
||||
send_command(ECHO_BLOCK, len)
|
||||
write("testing ", len, " byte(s) block\n")
|
||||
s1 = strrep("x", half)
|
||||
err = data:send(s1)
|
||||
if err then fail(err) end
|
||||
sleep(1)
|
||||
s2 = strrep("y", len-half)
|
||||
err = data:send(s2)
|
||||
if err then fail(err) end
|
||||
back, err = data:receive(len)
|
||||
if err then fail(err) end
|
||||
if back == s1..s2 then pass("blocks match")
|
||||
else fail("blocks don't match") end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests if return-timeout was respected
|
||||
-- delta: time elapsed during transfer
|
||||
-- t: timeout value
|
||||
-- err: error code returned by I/O operation
|
||||
-----------------------------------------------------------------------------
|
||||
function returntimed_out(delta, t, err)
|
||||
if err == "timeout" then
|
||||
if delta + 0.1 >= t then
|
||||
pass("got right timeout")
|
||||
return 1
|
||||
else
|
||||
fail("shouldn't have gotten timeout")
|
||||
end
|
||||
elseif delta > t then
|
||||
fail("should have gotten timeout")
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests if return-timeout was respected
|
||||
-- delta: time elapsed during transfer
|
||||
-- t: timeout value
|
||||
-- err: error code returned by I/O operation
|
||||
-- o: operation being executed
|
||||
-----------------------------------------------------------------------------
|
||||
function blockedtimed_out(t, s, err, o)
|
||||
if err == "timeout" then
|
||||
if s >= t then
|
||||
pass("got right forced timeout")
|
||||
return 1
|
||||
else
|
||||
pass("got natural cause timeout (may be wrong)")
|
||||
return 1
|
||||
end
|
||||
elseif s > t then
|
||||
if o == "send" then
|
||||
pass("must have been buffered (may be wrong)")
|
||||
else
|
||||
fail("should have gotten timeout")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests blocked-timeout conformance
|
||||
-- Input
|
||||
-- len: length of block to be tested
|
||||
-- t: timeout value
|
||||
-- s: server sleep between transfers
|
||||
-----------------------------------------------------------------------------
|
||||
function test_blockedtimeout(len, t, s)
|
||||
local str, err, back, total
|
||||
reconnect()
|
||||
send_command(RECEIVE_BLOCK, len)
|
||||
send_command(SLEEP, s)
|
||||
send_command(RECEIVE_BLOCK, len)
|
||||
write("testing ", len, " bytes, ", t,
|
||||
"s block timeout, ", s, "s sleep\n")
|
||||
data:timeout(t)
|
||||
str = strrep("a", 2*len)
|
||||
err, total = data:send(str)
|
||||
if blockedtimed_out(t, s, err, "send") then return end
|
||||
if err then fail(err) end
|
||||
send_command(SEND_BLOCK)
|
||||
send_command(SLEEP, s)
|
||||
send_command(SEND_BLOCK)
|
||||
back, err = data:receive(2*len)
|
||||
if blockedtimed_out(t, s, err, "receive") then return end
|
||||
if err then fail(err) end
|
||||
if back == str then pass("blocks match")
|
||||
else fail("blocks don't match") end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Tests return-timeout conformance
|
||||
-- Input
|
||||
-- len: length of block to be tested
|
||||
-- t: timeout value
|
||||
-- s: server sleep between transfers
|
||||
-----------------------------------------------------------------------------
|
||||
function test_returntimeout(len, t, s)
|
||||
local str, err, back, delta, total
|
||||
reconnect()
|
||||
send_command(RECEIVE_BLOCK, len)
|
||||
send_command(SLEEP, s)
|
||||
send_command(RECEIVE_BLOCK, len)
|
||||
write("testing ", len, " bytes, ", t,
|
||||
"s return timeout, ", s, "s sleep\n")
|
||||
data:timeout(t, "return")
|
||||
str = strrep("a", 2*len)
|
||||
err, total, delta = data:send(str)
|
||||
print("delta: " .. delta)
|
||||
if returntimed_out(delta, t, err) then return end
|
||||
if err then fail(err) end
|
||||
send_command(SEND_BLOCK)
|
||||
send_command(SLEEP, s)
|
||||
send_command(SEND_BLOCK)
|
||||
back, err, delta = data:receive(2*len)
|
||||
print("delta: " .. delta)
|
||||
if returntimed_out(delta, t, err) then return end
|
||||
if err then fail(err) end
|
||||
if back == str then pass("blocks match")
|
||||
else fail("blocks don't match") end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Execute all tests
|
||||
-----------------------------------------------------------------------------
|
||||
new_test("control connection test")
|
||||
test_command(EXIT)
|
||||
test_command(CONNECT)
|
||||
test_command(CLOSE)
|
||||
test_command(ECHO_BLOCK, 12234)
|
||||
test_command(SLEEP, 1111)
|
||||
test_command(ECHO_LINE)
|
||||
|
||||
new_test("connection close test")
|
||||
test_closed()
|
||||
|
||||
new_test("binary string test")
|
||||
test_rawline(1)
|
||||
test_rawline(17)
|
||||
test_rawline(200)
|
||||
test_rawline(3000)
|
||||
test_rawline(8000)
|
||||
test_rawline(40000)
|
||||
|
||||
new_test("blocking transfer test")
|
||||
test_block(1)
|
||||
test_block(17)
|
||||
test_block(200)
|
||||
test_block(3000)
|
||||
test_block(80000)
|
||||
test_block(800000)
|
||||
|
||||
new_test("non-blocking transfer test")
|
||||
-- the value is not important, we only want
|
||||
-- to test non-blockin I/O anyways
|
||||
data:timeout(200)
|
||||
test_block(1)
|
||||
test_block(17)
|
||||
test_block(200)
|
||||
test_block(3000)
|
||||
test_block(80000)
|
||||
test_block(800000)
|
||||
test_block(8000000)
|
||||
|
||||
new_test("character string test")
|
||||
test_asciiline(1)
|
||||
test_asciiline(17)
|
||||
test_asciiline(200)
|
||||
test_asciiline(3000)
|
||||
test_asciiline(8000)
|
||||
test_asciiline(40000)
|
||||
|
||||
new_test("return timeout test")
|
||||
test_returntimeout(80, .5, 1)
|
||||
test_returntimeout(80, 1, 0.5)
|
||||
test_returntimeout(8000, .5, 0)
|
||||
test_returntimeout(80000, .5, 0)
|
||||
test_returntimeout(800000, .5, 0)
|
||||
|
||||
new_test("blocked timeout test")
|
||||
test_blockedtimeout(80, .5, 1)
|
||||
test_blockedtimeout(80, 1, 1)
|
||||
test_blockedtimeout(80, 1.5, 1)
|
||||
test_blockedtimeout(800, 1, 0)
|
||||
test_blockedtimeout(8000, 1, 0)
|
||||
test_blockedtimeout(80000, 1, 0)
|
||||
test_blockedtimeout(800000, 1, 0)
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Close connection and exit server. We are done.
|
||||
-----------------------------------------------------------------------------
|
||||
new_test("the library has passed all tests")
|
||||
print("client: closing connection with server")
|
||||
send_command(CLOSE)
|
||||
send_command(EXIT)
|
||||
control:close()
|
||||
print("client: exiting...")
|
||||
exit()
|
90
test/testsrvr.lua
Normal file
90
test/testsrvr.lua
Normal file
@ -0,0 +1,90 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- LuaSocket automated test module
|
||||
-- server.lua
|
||||
-- This is the server module. It's completely controled by the client module
|
||||
-- by the use of a control connection.
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Read command definitions
|
||||
-----------------------------------------------------------------------------
|
||||
dofile("command.lua")
|
||||
test_debug_mode()
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Bind to address and wait for control connection
|
||||
-----------------------------------------------------------------------------
|
||||
server, err = bind(HOST, PORT)
|
||||
if not server then
|
||||
print(err)
|
||||
exit(1)
|
||||
end
|
||||
print("server: waiting for control connection...")
|
||||
control = server:accept()
|
||||
print("server: control connection stablished!")
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Executes a command, detecting any possible failures
|
||||
-- Input
|
||||
-- cmd: command to be executed
|
||||
-- par: command parameters, if needed
|
||||
-----------------------------------------------------------------------------
|
||||
function execute_command(cmd, par)
|
||||
if cmd == CONNECT then
|
||||
print("server: waiting for data connection...")
|
||||
data = server:accept()
|
||||
if not data then
|
||||
fail("server: unable to start data connection!")
|
||||
else
|
||||
print("server: data connection stablished!")
|
||||
end
|
||||
elseif cmd == CLOSE then
|
||||
print("server: closing connection with client...")
|
||||
if data then
|
||||
data:close()
|
||||
data = nil
|
||||
end
|
||||
elseif cmd == ECHO_LINE then
|
||||
str, err = data:receive()
|
||||
if err then fail("server: " .. err) end
|
||||
err = data:send(str, "\n")
|
||||
if err then fail("server: " .. err) end
|
||||
elseif cmd == ECHO_BLOCK then
|
||||
str, err = data:receive(par)
|
||||
if err then fail("server: " .. err) end
|
||||
err = data:send(str)
|
||||
if err then fail("server: " .. err) end
|
||||
elseif cmd == RECEIVE_BLOCK then
|
||||
str, err = data:receive(par)
|
||||
elseif cmd == SEND_BLOCK then
|
||||
err = data:send(str)
|
||||
elseif cmd == ECHO_TIMEOUT then
|
||||
str, err = data:receive(par)
|
||||
if err then fail("server: " .. err) end
|
||||
err = data:send(str)
|
||||
if err then fail("server: " .. err) end
|
||||
elseif cmd == COMMAND then
|
||||
cmd, par = get_command()
|
||||
send_command(cmd, par)
|
||||
elseif cmd == EXIT then
|
||||
print("server: exiting...")
|
||||
exit(0)
|
||||
elseif cmd == SYNC then
|
||||
print("server: synchronizing...")
|
||||
send_command(SYNC)
|
||||
elseif cmd == SLEEP then
|
||||
print("server: sleeping for " .. par .. " seconds...")
|
||||
sleep(par)
|
||||
print("server: woke up!")
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- Loop forever, accepting and executing commands
|
||||
-----------------------------------------------------------------------------
|
||||
while 1 do
|
||||
cmd, par = get_command()
|
||||
if not cmd then fail("server: " .. par) end
|
||||
print_command(cmd, par)
|
||||
execute_command(cmd, par)
|
||||
end
|
Loading…
Reference in New Issue
Block a user