Initial revision

master
Diego Nehab 2000-12-29 22:15:09 +00:00
parent 6f9d15b660
commit 17c4d1c305
12 changed files with 1777 additions and 0 deletions

24
README Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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