mirror of
https://github.com/lunarmodules/luasocket.git
synced 2024-12-26 12:28: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