aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README24
-rw-r--r--etc/dict.lua39
-rw-r--r--makefile.dist81
-rw-r--r--samples/README32
-rw-r--r--samples/listener.lua25
-rw-r--r--samples/talker.lua22
-rw-r--r--src/ftp.lua437
-rw-r--r--src/http.lua312
-rw-r--r--src/luasocket.h18
-rw-r--r--src/smtp.lua338
-rw-r--r--test/testclnt.lua359
-rw-r--r--test/testsrvr.lua90
12 files changed, 1777 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..c4ac6fd
--- /dev/null
+++ b/README
@@ -0,0 +1,24 @@
1This directory contains the implementation of the protocols FTP, HTTP and
2SMTP. The files provided are:
3
4 http.lua -- HTTP protocol implementation
5 base64.lua -- base64 encoding implementation
6
7The module http.lua provides functionality to download an URL from a
8HTTP server. The implementation conforms to the HTTP/1.1 standard, RFC
92068. The base64.lua module provides base64 encoding and decoding. The
10module is used for the HTTP Basic Authentication Scheme, and conforms to
11RFC 1521.
12
13 smtp.lua -- SMTP protocol implementation
14
15The module smtp.lua provides functionality to send e-mail messages to a
16SMTP mail server. The implementation conforms to RFC 821.
17
18 ftp.lua -- FTP protocol implementation
19
20The module ftp.lua provides functions to download and upload files from
21and to FTP servers. The implementation conforms to RFC 959.
22
23These implementations are supported. Please send any comments do
24diego@tecgraf.puc-rio.br.
diff --git a/etc/dict.lua b/etc/dict.lua
new file mode 100644
index 0000000..683cb45
--- /dev/null
+++ b/etc/dict.lua
@@ -0,0 +1,39 @@
1-- dict.lua
2-- simple client for DICT protocol (see http://www.dict.org/)
3-- shows definitions for each word from stdin. uses only "wn" dictionary.
4-- if a word is "=", then the rest of the line is sent verbatim as a protocol
5-- command to the server.
6
7if verbose then verbose=write else verbose=function()end end
8
9verbose(">>> connecting to server\n")
10local s,e=connect("dict.org",2628)
11assert(s,e)
12verbose(">>> connected\n")
13
14while 1 do
15 local w=read"*w"
16 if w==nil then break end
17 if w=="=" then
18 w=read"*l"
19 verbose(">>>",w,"\n")
20 send(s,w,"\r\n")
21 else
22 verbose(">>> looking up `",w,"'\n")
23 send(s,"DEFINE wn ",w,"\r\n")
24 end
25 while 1 do
26 local l=receive(s)
27 if l==nil then break end
28 if strfind(l,"^[0-9]") then
29 write("<<< ",l,"\n")
30 else
31 write(l,"\n")
32 end
33 if strfind(l,"^250") or strfind(l,"^[45]") then break end
34 end
35end
36
37send(s,"QUIT\r\n")
38verbose("<<< ",receive(s),"\n")
39close(s)
diff --git a/makefile.dist b/makefile.dist
new file mode 100644
index 0000000..19a3775
--- /dev/null
+++ b/makefile.dist
@@ -0,0 +1,81 @@
1#--------------------------------------------------------------------------
2# LuaSocket makefile
3# Test executable for socklib
4# Diego Nehab, 29/8/1999
5#--------------------------------------------------------------------------
6
7# don't echo commands
8# .SILENT:
9
10CXX = g++
11CC = gcc
12
13DIST = luasocket-1.1
14
15WARNINGS = -Wall -Wshadow -Wpointer-arith -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs
16
17CFLAGS = $(WARNINGS) -D_DEBUG -O2
18
19LUA = /home/diego/lib/lua
20LUALIB = $(LUA)/lib
21LUAINC = $(LUA)/include
22
23INC = -I$(LUAINC)
24# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lsocket -lnsl
25# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lnsl
26LIB = $(LUALIB)/liblualib.a $(LUALIB)/liblua.a -lm
27
28SRC = ~diego/tec/luasocket
29OBJ = /tmp
30DEP = /tmp
31
32# list of .cpp files
33c_sources = luasocket.c lua.c
34
35# corresponding .o files
36c_objects = $(addprefix $(OBJ)/, $(addsuffix .o, $(basename $(c_sources))))
37
38# binary depends on objects
39luasocket: $(c_objects)
40 $(CC) $(CPPFLAGS) -o $@ $(c_objects) $(LIB)
41
42# rule to create them
43$(c_objects): $(OBJ)/%.o: $(SRC)/%.c
44 $(CC) -c $(CFLAGS) $(INC) -o $@ $<
45
46# corresponding .d files
47c_deps = $(addprefix $(DEP)/, $(addsuffix .d, $(basename $(c_sources))))
48
49# makefile depend on them...
50makefile : $(c_deps)
51
52# ... since it includes them
53-include $(c_deps)
54
55# rule to create them
56$(c_deps) : $(DEP)/%.d : $(SRC)/%.c
57 $(SHELL) -ec '$(CC) -MM $(CFLAGS) $(INC) $< \
58 | sed '\''s%\($*\.o\)%$@ $(OBJ)/\1%'\'' > $@; \
59 [ -s $@ ] || rm -f $@'
60
61# clean all trash
62clean:
63 rm -f $(OBJ)/*.o
64 rm -f $(DEP)/*.d
65 rm -f luasocket core
66
67dist:
68 mkdir -p $(DIST)/lua
69 mkdir -p $(DIST)/examples
70 mkdir -p $(DIST)/html
71 cp -vf *.c $(DIST)
72 cp -vf *.h $(DIST)
73 cp -vf makefile $(DIST)
74 cp -vf README $(DIST)
75 cp -vf lua/*.lua $(DIST)/lua
76 cp -vf lua/README $(DIST)/lua
77 cp -vf examples/*.lua $(DIST)/examples
78 cp -vf examples/README $(DIST)/examples
79 cp -vf html/manual.html $(DIST)/html
80 tar -zcvf $(DIST).tar.gz $(DIST)
81 zip -r $(DIST).zip $(DIST)
diff --git a/samples/README b/samples/README
new file mode 100644
index 0000000..ef5017b
--- /dev/null
+++ b/samples/README
@@ -0,0 +1,32 @@
1This directory contains some sample programs using LuaSocket as well as
2the automatic tests used to make sure the library is working properly.
3
4The files provided are:
5
6 server.lua -- test server
7 client.lua -- test client
8 command.lua -- test command definitions
9
10The automatic tests are composed by three files: client.lua, command.lua
11and server.lua. To run the automatic tests on your system, make sure to
12compile the library with _DEBUG defined (check makefile) and then open
13two terminals. Run 'luasocket server.lua' on one of them and 'luasocket
14client.lua' on the other. The programs should start talking to each
15other.
16
17 listen.lua -- echo server
18 talk.lua -- echo tester
19
20listen.lua and talk.lua are about the simplest applications you can
21write using LuaSocket. Run 'luasocket listen.lua' and 'luasocket
22talk.lua' on different terminals. Whatever you type on talk.lua will be
23printed by listen.lua.
24
25 dict.lua -- dict client
26
27The dict.lua module is a cool simple client for the DICT protocol,
28written by Luiz Henrique Figueiredo. Just run it and enter a few words
29to see it working.
30
31Good luck,
32Diego.
diff --git a/samples/listener.lua b/samples/listener.lua
new file mode 100644
index 0000000..a47d9a3
--- /dev/null
+++ b/samples/listener.lua
@@ -0,0 +1,25 @@
1host = "localhost"
2port = 8080
3if arg then
4 host = arg[1] or host
5 port = arg[2] or port
6end
7print("Binding to host '" ..host.. "' and port " ..port.. "...")
8s, i, p, e = bind(host, port)
9if not s then
10 print(e)
11 exit()
12end
13print("Waiting connection from talker on " .. i .. ":" .. p .. "...")
14c, e = s:accept()
15if not c then
16 print(e)
17 exit()
18end
19print("Connected. Here is the stuff:")
20l, e = c:receive()
21while not e do
22 print(l)
23 l, e = c:receive()
24end
25print(e)
diff --git a/samples/talker.lua b/samples/talker.lua
new file mode 100644
index 0000000..b3313e6
--- /dev/null
+++ b/samples/talker.lua
@@ -0,0 +1,22 @@
1host = "localhost"
2port = 8080
3if arg then
4 host = arg[1] or host
5 port = arg[2] or port
6end
7print("Attempting connection to host '" ..host.. "' and port " ..port.. "...")
8c, e = connect(host, port)
9if not c then
10 print(e)
11 exit()
12end
13print("Connected! Please type stuff (empty line to stop):")
14l = read()
15while l and l ~= "" and not e do
16 e = c:send(l, "\n")
17 if e then
18 print(e)
19 exit()
20 end
21 l = read()
22end
diff --git a/src/ftp.lua b/src/ftp.lua
new file mode 100644
index 0000000..b817356
--- /dev/null
+++ b/src/ftp.lua
@@ -0,0 +1,437 @@
1-----------------------------------------------------------------------------
2-- Simple FTP support for the Lua language using the LuaSocket toolkit.
3-- Author: Diego Nehab
4-- Date: 26/12/2000
5-- Conforming to: RFC 959
6-----------------------------------------------------------------------------
7
8-----------------------------------------------------------------------------
9-- Program constants
10-----------------------------------------------------------------------------
11-- timeout in seconds before the program gives up on a connection
12local TIMEOUT = 60
13-- default port for ftp service
14local PORT = 21
15-- this is the default anonymous password. used when no password is
16-- provided in url. should be changed for your e-mail.
17local EMAIL = "anonymous@anonymous.org"
18
19-----------------------------------------------------------------------------
20-- Parses a url and returns its scheme, user, password, host, port
21-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
22-- of December 1994
23-- Input
24-- url: unique resource locator desired
25-- default: table containing default values to be returned
26-- Returns
27-- table with the following fields:
28-- host: host to connect
29-- path: url path
30-- port: host port to connect
31-- user: user name
32-- pass: password
33-- scheme: protocol
34-----------------------------------------------------------------------------
35local split_url = function(url, default)
36 -- initialize default parameters
37 local parsed = default or {}
38 -- get scheme
39 url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
40 -- get user name and password. both can be empty!
41 -- moreover, password can be ommited
42 url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
43 %parsed.user = u
44 -- there can be an empty password, but the ':' has to be there
45 -- or else there is no password
46 %parsed.pass = nil -- kill default password
47 if c == ":" then %parsed.pass = p end
48 end)
49 -- get host
50 url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
51 -- get port if any
52 url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
53 -- whatever is left is the path
54 if url ~= "" then parsed.path = url end
55 return parsed
56end
57
58-----------------------------------------------------------------------------
59-- Gets ip and port for data connection from PASV answer
60-- Input
61-- pasv: PASV command answer
62-- Returns
63-- ip: string containing ip for data connection
64-- port: port for data connection
65-----------------------------------------------------------------------------
66local get_pasv = function(pasv)
67 local a,b,c,d,p1,p2
68 local ip, port
69 _,_, a, b, c, d, p1, p2 =
70 strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)")
71 if not a or not b or not c or not d or not p1 or not p2 then
72 return nil, nil
73 end
74 ip = format("%d.%d.%d.%d", a, b, c, d)
75 port = tonumber(p1)*256 + tonumber(p2)
76 return ip, port
77end
78
79-----------------------------------------------------------------------------
80-- Sends a FTP command through socket
81-- Input
82-- control: control connection socket
83-- cmd: command
84-- arg: command argument if any
85-----------------------------------------------------------------------------
86local send_command = function(control, cmd, arg)
87 local line, err
88 if arg then line = cmd .. " " .. arg .. "\r\n"
89 else line = cmd .. "\r\n" end
90 err = control:send(line)
91 return err
92end
93
94-----------------------------------------------------------------------------
95-- Gets FTP command answer, unfolding if neccessary
96-- Input
97-- control: control connection socket
98-- Returns
99-- answer: whole server reply, nil if error
100-- code: answer status code or error message
101-----------------------------------------------------------------------------
102local get_answer = function(control)
103 local code, lastcode, sep
104 local line, err = control:receive()
105 local answer = line
106 if err then return nil, err end
107 _,_, code, sep = strfind(line, "^(%d%d%d)(.)")
108 if not code or not sep then return nil, answer end
109 if sep == "-" then -- answer is multiline
110 repeat
111 line, err = control:receive()
112 if err then return nil, err end
113 _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)")
114 answer = answer .. "\n" .. line
115 until code == lastcode and sep == " " -- answer ends with same code
116 end
117 return answer, tonumber(code)
118end
119
120-----------------------------------------------------------------------------
121-- Checks if a message return is correct. Closes control connection if not.
122-- Input
123-- control: control connection socket
124-- success: table with successfull reply status code
125-- Returns
126-- code: reply code or nil in case of error
127-- answer: server complete answer or system error message
128-----------------------------------------------------------------------------
129local check_answer = function(control, success)
130 local answer, code = %get_answer(control)
131 if not answer then
132 control:close()
133 return nil, code
134 end
135 if type(success) ~= "table" then success = {success} end
136 for i = 1, getn(success) do
137 if code == success[i] then
138 return code, answer
139 end
140 end
141 control:close()
142 return nil, answer
143end
144
145-----------------------------------------------------------------------------
146-- Trys a command on control socked, in case of error, the control connection
147-- is closed.
148-- Input
149-- control: control connection socket
150-- cmd: command
151-- arg: command argument or nil if no argument
152-- success: table with successfull reply status code
153-- Returns
154-- code: reply code or nil in case of error
155-- answer: server complete answer or system error message
156-----------------------------------------------------------------------------
157local try_command = function(control, cmd, arg, success)
158 local err = %send_command(control, cmd, arg)
159 if err then
160 control:close()
161 return nil, err
162 end
163 local code, answer = %check_answer(control, success)
164 if not code then return nil, answer end
165 return code, answer
166end
167
168-----------------------------------------------------------------------------
169-- Creates a table with all directories in path
170-- Input
171-- file: abolute path to file
172-- Returns
173-- file: filename
174-- path: table with directories to reach filename
175-- isdir: is it a directory or a file
176-----------------------------------------------------------------------------
177local split_path = function(file)
178 local path = {}
179 local isdir
180 file = file or "/"
181 -- directory ends with a '/'
182 _,_, isdir = strfind(file, "([/])$")
183 gsub(file, "([^/]+)", function (dir) tinsert(%path, dir) end)
184 if not isdir then file = tremove(path)
185 else file = nil end
186 return file, path, isdir
187end
188
189-----------------------------------------------------------------------------
190-- Check server greeting
191-- Input
192-- control: control connection with server
193-- Returns
194-- code: nil if error
195-- answer: server answer or error message
196-----------------------------------------------------------------------------
197local check_greeting = function(control)
198 local code, answer = %check_answer(control, {120, 220})
199 if not code then return nil, answer end
200 if code == 120 then -- please try again, somewhat busy now...
201 code, answer = %check_answer(control, {220})
202 end
203 return code, answer
204end
205
206-----------------------------------------------------------------------------
207-- Log in on server
208-- Input
209-- control: control connection with server
210-- user: user name
211-- pass: user password if any
212-- Returns
213-- code: nil if error
214-- answer: server answer or error message
215-----------------------------------------------------------------------------
216local login = function(control, user, pass)
217 local code, answer = %try_command(control, "user", parsed.user, {230, 331})
218 if not code then return nil, answer end
219 if code == 331 and parsed.pass then -- need pass and we have pass
220 code, answer = %try_command(control, "pass", parsed.pass, {230, 202})
221 end
222 return code, answer
223end
224
225-----------------------------------------------------------------------------
226-- Change to target directory
227-- Input
228-- control: socket for control connection with server
229-- path: array with directories in order
230-- Returns
231-- code: nil if error
232-- answer: server answer or error message
233-----------------------------------------------------------------------------
234local cwd = function(control, path)
235 local code, answer = 250, "Home directory used"
236 for i = 1, getn(path) do
237 code, answer = %try_command(control, "cwd", path[i], {250})
238 if not code then return nil, answer end
239 end
240 return code, answer
241end
242
243-----------------------------------------------------------------------------
244-- Start data connection with server
245-- Input
246-- control: control connection with server
247-- Returns
248-- data: socket for data connection with server, nil if error
249-- answer: server answer or error message
250-----------------------------------------------------------------------------
251local start_dataconnection = function(control)
252 -- ask for passive data connection
253 local code, answer = %try_command(control, "pasv", nil, {227})
254 if not code then return nil, answer end
255 -- get data connection parameters from server reply
256 local host, port = %get_pasv(answer)
257 if not host or not port then return nil, answer end
258 -- start data connection with given parameters
259 local data, err = connect(host, port)
260 if not data then return nil, err end
261 data:timeout(%TIMEOUT)
262 return data
263end
264
265-----------------------------------------------------------------------------
266-- Closes control connection with server
267-- Input
268-- control: control connection with server
269-- Returns
270-- code: nil if error
271-- answer: server answer or error message
272-----------------------------------------------------------------------------
273local logout = function(control)
274 local code, answer = %try_command(control, "quit", nil, {221})
275 if not code then return nil, answer end
276 control:close()
277 return code, answer
278end
279
280-----------------------------------------------------------------------------
281-- Retrieves file or directory listing
282-- Input
283-- control: control connection with server
284-- data: data connection with server
285-- file: file name under current directory
286-- isdir: is file a directory name?
287-- Returns
288-- file: string with file contents, nil if error
289-- answer: server answer or error message
290-----------------------------------------------------------------------------
291local retrieve_file = function(control, data, file, isdir)
292 -- ask server for file or directory listing accordingly
293 if isdir then code, answer = %try_command(control, "nlst", file, {150, 125})
294 else code, answer = %try_command(control, "retr", file, {150, 125}) end
295 if not code then
296 control:close()
297 data:close()
298 return nil, answer
299 end
300 -- download whole file
301 file, err = data:receive("*a")
302 data:close()
303 if err then
304 control:close()
305 return nil, err
306 end
307 -- make sure file transfered ok
308 code, answer = %check_answer(control, {226, 250})
309 if not code then return nil, answer
310 else return file, answer end
311end
312
313-----------------------------------------------------------------------------
314-- Stores a file
315-- Input
316-- control: control connection with server
317-- data: data connection with server
318-- file: file name under current directory
319-- bytes: file contents in string
320-- Returns
321-- file: string with file contents, nil if error
322-- answer: server answer or error message
323-----------------------------------------------------------------------------
324local store_file = function (control, data, file, bytes)
325 local code, answer = %try_command(control, "stor", file, {150, 125})
326 if not code then
327 data:close()
328 return nil, answer
329 end
330 -- send whole file and close connection to mark file end
331 answer = data:send(bytes)
332 data:close()
333 if answer then
334 control:close()
335 return nil, answer
336 end
337 -- check if file was received right
338 return %check_answer(control, {226, 250})
339end
340
341-----------------------------------------------------------------------------
342-- Change transfer type
343-- Input
344-- control: control connection with server
345-- type: new transfer type
346-- Returns
347-- code: nil if error
348-- answer: server answer or error message
349-----------------------------------------------------------------------------
350local change_type = function(control, type)
351 if type == "b" then type = "i" else type = "a" end
352 return %try_command(control, "type", type, {200})
353end
354
355-----------------------------------------------------------------------------
356-- Retrieve a file from a ftp server
357-- Input
358-- url: file location
359-- type: "binary" or "ascii"
360-- Returns
361-- file: downloaded file or nil in case of error
362-- err: error message if any
363-----------------------------------------------------------------------------
364function ftp_get(url, type)
365 local control, data, err
366 local answer, code, server, file, path
367 parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL})
368 -- start control connection
369 control, err = connect(parsed.host, parsed.port)
370 if not control then return nil, err end
371 control:timeout(%TIMEOUT)
372 -- get and check greeting
373 code, answer = %check_greeting(control)
374 if not code then return nil, answer end
375 -- try to log in
376 code, answer = %login(control, parsed.user, parsed.pass)
377 if not code then return nil, answer end
378 -- go to directory
379 file, path, isdir = %split_path(parsed.path)
380 code, answer = %cwd(control, path)
381 if not code then return nil, answer end
382 -- change to binary type?
383 code, answer = %change_type(control, type)
384 if not code then return nil, answer end
385 -- start data connection
386 data, answer = %start_dataconnection(control)
387 if not data then return nil, answer end
388 -- ask server to send file or directory listing
389 file, answer = %retrieve_file(control, data, file, isdir)
390 if not file then return nil, answer end
391 -- disconnect
392 %logout(control)
393 -- return whatever file we received plus a possible error
394 return file, answer
395end
396
397-----------------------------------------------------------------------------
398-- Uploads a file to a FTP server
399-- Input
400-- url: file location
401-- bytes: file contents
402-- type: "binary" or "ascii"
403-- Returns
404-- err: error message if any
405-----------------------------------------------------------------------------
406function ftp_put(url, bytes, type)
407 local control, data
408 local answer, code, server, file, path
409 parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL})
410 -- start control connection
411 control, answer = connect(parsed.host, parsed.port)
412 if not control then return answer end
413 control:timeout(%TIMEOUT)
414 -- get and check greeting
415 code, answer = %check_greeting(control)
416 if not code then return answer end
417 -- try to log in
418 code, answer = %login(control, parsed.user, parsed.pass)
419 if not code then return answer end
420 -- go to directory
421 file, path, isdir = %split_path(parsed.path)
422 code, answer = %cwd(control, path)
423 if not code then return answer end
424 -- change to binary type?
425 code, answer = %change_type(control, type)
426 if not code then return answer end
427 -- start data connection
428 data, answer = %start_dataconnection(control)
429 if not data then return answer end
430 -- ask server to send file or directory listing
431 code, answer = %store_file(control, data, file, bytes)
432 if not code then return answer end
433 -- disconnect
434 %logout(control)
435 -- return whatever file we received plus a possible error
436 return nil
437end
diff --git a/src/http.lua b/src/http.lua
new file mode 100644
index 0000000..8f08725
--- /dev/null
+++ b/src/http.lua
@@ -0,0 +1,312 @@
1-----------------------------------------------------------------------------
2-- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit.
3-- Author: Diego Nehab
4-- Date: 26/12/2000
5-- Conforming to: RFC 2068
6-----------------------------------------------------------------------------
7
8-----------------------------------------------------------------------------
9-- Program constants
10-----------------------------------------------------------------------------
11-- connection timeout in seconds
12local TIMEOUT = 60
13-- default port for document retrieval
14local PORT = 80
15-- user agent field sent in request
16local USERAGENT = "LuaSocket/HTTP 1.0"
17
18-----------------------------------------------------------------------------
19-- Tries to get a line from the server or close socket if error
20-- sock: socket connected to the server
21-- Returns
22-- line: line received or nil in case of error
23-- err: error message if any
24-----------------------------------------------------------------------------
25local try_getline = function(sock)
26 line, err = sock:receive()
27 if err then
28 sock:close()
29 return nil, err
30 end
31 return line
32end
33
34-----------------------------------------------------------------------------
35-- Tries to send a line to the server or close socket if error
36-- sock: socket connected to the server
37-- line: line to send
38-- Returns
39-- err: error message if any
40-----------------------------------------------------------------------------
41local try_sendline = function(sock, line)
42 err = sock:send(line)
43 if err then sock:close() end
44 return err
45end
46
47-----------------------------------------------------------------------------
48-- Retrieves status from http reply
49-- Input
50-- reply: http reply string
51-- Returns
52-- status: integer with status code
53-----------------------------------------------------------------------------
54local get_status = function(reply)
55 local _,_, status = strfind(reply, " (%d%d%d) ")
56 return tonumber(status)
57end
58
59-----------------------------------------------------------------------------
60-- Receive server reply messages
61-- Input
62-- sock: server socket
63-- Returns
64-- status: server reply status code or nil if error
65-- reply: full server reply
66-- err: error message if any
67-----------------------------------------------------------------------------
68local get_reply = function(sock)
69 local reply, err
70 reply, err = %try_getline(sock)
71 if not err then return %get_status(reply), reply
72 else return nil, nil, err end
73end
74
75-----------------------------------------------------------------------------
76-- Receive and parse mime headers
77-- Input
78-- sock: server socket
79-- mime: a table that might already contain mime headers
80-- Returns
81-- mime: a table with all mime headers in the form
82-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"}
83-- all name_i are lowercase
84-- nil and error message in case of error
85-----------------------------------------------------------------------------
86local get_mime = function(sock, mime)
87 local line, err
88 local name, value
89 -- get first line
90 line, err = %try_getline(sock)
91 if err then return nil, err end
92 -- headers go until a blank line is found
93 while line ~= "" do
94 -- get field-name and value
95 _,_, name, value = strfind(line, "(.-):%s*(.*)")
96 name = strlower(name)
97 -- get next line (value might be folded)
98 line, err = %try_getline(sock)
99 if err then return nil, err end
100 -- unfold any folded values
101 while not err and line ~= "" and (strsub(line, 1, 1) == " ") do
102 value = value .. line
103 line, err = %try_getline(sock)
104 if err then return nil, err end
105 end
106 -- save pair in table
107 if mime[name] then
108 -- join any multiple field
109 mime[name] = mime[name] .. ", " .. value
110 else
111 -- new field
112 mime[name] = value
113 end
114 end
115 return mime
116end
117
118-----------------------------------------------------------------------------
119-- Receives http body
120-- Input
121-- sock: server socket
122-- mime: initial mime headers
123-- Returns
124-- body: a string containing the body of the document
125-- nil and error message in case of error
126-- Obs:
127-- mime: headers might be modified by chunked transfer
128-----------------------------------------------------------------------------
129local get_body = function(sock, mime)
130 local body, err
131 if mime["transfer-encoding"] == "chunked" then
132 local chunk_size, line
133 body = ""
134 repeat
135 -- get chunk size, skip extention
136 line, err = %try_getline(sock)
137 if err then return nil, err end
138 chunk_size = tonumber(gsub(line, ";.*", ""), 16)
139 if not chunk_size then
140 sock:close()
141 return nil, "invalid chunk size"
142 end
143 -- get chunk
144 line, err = sock:receive(chunk_size)
145 if err then
146 sock:close()
147 return nil, err
148 end
149 -- concatenate new chunk
150 body = body .. line
151 -- skip blank line
152 _, err = %try_getline(sock)
153 if err then return nil, err end
154 until chunk_size <= 0
155 -- store extra mime headers
156 --_, err = %get_mime(sock, mime)
157 --if err then return nil, err end
158 elseif mime["content-length"] then
159 body, err = sock:receive(tonumber(mime["content-length"]))
160 if err then
161 sock:close()
162 return nil, err
163 end
164 else
165 -- get it all until connection closes!
166 body, err = sock:receive("*a")
167 if err then
168 sock:close()
169 return nil, err
170 end
171 end
172 -- return whole body
173 return body
174end
175
176-----------------------------------------------------------------------------
177-- Parses a url and returns its scheme, user, password, host, port
178-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
179-- of December 1994
180-- Input
181-- url: unique resource locator desired
182-- default: table containing default values to be returned
183-- Returns
184-- table with the following fields:
185-- host: host to connect
186-- path: url path
187-- port: host port to connect
188-- user: user name
189-- pass: password
190-- scheme: protocol
191-----------------------------------------------------------------------------
192local split_url = function(url, default)
193 -- initialize default parameters
194 local parsed = default or {}
195 -- get scheme
196 url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
197 -- get user name and password. both can be empty!
198 -- moreover, password can be ommited
199 url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
200 %parsed.user = u
201 -- there can be an empty password, but the ':' has to be there
202 -- or else there is no password
203 %parsed.pass = nil -- kill default password
204 if c == ":" then %parsed.pass = p end
205 end)
206 -- get host
207 url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
208 -- get port if any
209 url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
210 -- whatever is left is the path
211 if url ~= "" then parsed.path = url end
212 return parsed
213end
214
215-----------------------------------------------------------------------------
216-- Sends a GET message through socket
217-- Input
218-- socket: http connection socket
219-- path: path requested
220-- mime: mime headers to send in request
221-- Returns
222-- err: nil in case of success, error message otherwise
223-----------------------------------------------------------------------------
224local send_get = function(sock, path, mime)
225 local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n")
226 if err then return err end
227 for i, v in mime do
228 err = %try_sendline(sock, i .. ": " .. v .. "\r\n")
229 if err then return err end
230 end
231 err = %try_sendline(sock, "\r\n")
232 return err
233end
234
235-----------------------------------------------------------------------------
236-- Converts field names to lowercase
237-- Input
238-- headers: user header fields
239-- parsed: parsed url components
240-- Returns
241-- mime: a table with the same headers, but with lowercase field names
242-----------------------------------------------------------------------------
243local fill_headers = function(headers, parsed)
244 local mime = {}
245 headers = headers or {}
246 for i,v in headers do
247 mime[strlower(i)] = v
248 end
249 mime["connection"] = "close"
250 mime["host"] = parsed.host
251 mime["user-agent"] = %USERAGENT
252 if parsed.user and parsed.pass then -- Basic Authentication
253 mime["authorization"] = "Basic "..
254 base64(parsed.user .. ":" .. parsed.pass)
255 end
256 return mime
257end
258
259-----------------------------------------------------------------------------
260-- We need base64 convertion routines for Basic Authentication Scheme
261-----------------------------------------------------------------------------
262dofile("base64.lua")
263
264-----------------------------------------------------------------------------
265-- Downloads and receives a http url, with its mime headers
266-- Input
267-- url: unique resource locator desired
268-- headers: headers to send with request
269-- tried: is this an authentication retry?
270-- Returns
271-- body: document body, if successfull
272-- mime: headers received with document, if sucessfull
273-- reply: server reply, if successfull
274-- err: error message, if any
275-----------------------------------------------------------------------------
276function http_get(url, headers)
277 local sock, err, mime, body, status, reply
278 -- get url components
279 local parsed = %split_url(url, {port = %PORT, path ="/"})
280 -- fill default headers
281 headers = %fill_headers(headers, parsed)
282 -- try connection
283 sock, err = connect(parsed.host, parsed.port)
284 if not sock then return nil, nil, nil, err end
285 -- set connection timeout
286 sock:timeout(%TIMEOUT)
287 -- send request
288 err = %send_get(sock, parsed.path, headers)
289 if err then return nil, nil, nil, err end
290 -- get server message
291 status, reply, err = %get_reply(sock)
292 if err then return nil, nil, nil, err end
293 -- get url accordingly
294 if status == 200 then -- ok, go on and get it
295 mime, err = %get_mime(sock, {})
296 if err then return nil, nil, reply, err end
297 body, err = %get_body(sock, mime)
298 if err then return nil, mime, reply, err end
299 sock:close()
300 return body, mime, reply
301 elseif status == 301 then -- moved permanently, try again
302 mime = %get_mime(sock, {})
303 sock:close()
304 if mime["location"] then return http_get(mime["location"], headers)
305 else return nil, mime, reply end
306 elseif status == 401 then
307 mime, err = %get_mime(sock, {})
308 if err then return nil, nil, reply, err end
309 return nil, mime, reply
310 end
311 return nil, nil, reply
312end
diff --git a/src/luasocket.h b/src/luasocket.h
new file mode 100644
index 0000000..d4037cd
--- /dev/null
+++ b/src/luasocket.h
@@ -0,0 +1,18 @@
1/*=========================================================================*\
2* TCP/IP support for LUA
3* Diego Nehab
4* 9/11/1999
5\*=========================================================================*/
6
7#ifndef _LUASOCKET_H_
8#define _LUASOCKET_H_
9
10/*=========================================================================*\
11* Exported function declarations
12\*=========================================================================*/
13/*-------------------------------------------------------------------------*\
14* Initializes toolkit
15\*-------------------------------------------------------------------------*/
16void lua_socketlibopen(lua_State *L);
17
18#endif /* _LUASOCKET_H_ */
diff --git a/src/smtp.lua b/src/smtp.lua
new file mode 100644
index 0000000..f9ed64c
--- /dev/null
+++ b/src/smtp.lua
@@ -0,0 +1,338 @@
1-----------------------------------------------------------------------------
2-- Simple SMTP support for the Lua language using the LuaSocket toolkit.
3-- Author: Diego Nehab
4-- Date: 26/12/2000
5-- Conforming to: RFC 821
6-----------------------------------------------------------------------------
7
8-----------------------------------------------------------------------------
9-- Program constants
10-----------------------------------------------------------------------------
11-- timeout in secconds before we give up waiting
12local TIMEOUT = 180
13-- port used for connection
14local PORT = 25
15-- domain used in HELO command. If we are under a CGI, try to get from
16-- environment
17local DOMAIN = getenv("SERVER_NAME")
18if not DOMAIN then
19 DOMAIN = "localhost"
20end
21
22-----------------------------------------------------------------------------
23-- Tries to send DOS mode lines. Closes socket on error.
24-- Input
25-- sock: server socket
26-- line: string to be sent
27-- Returns
28-- err: message in case of error, nil if successfull
29-----------------------------------------------------------------------------
30local puts = function(sock, line)
31 local err = sock:send(line .. "\r\n")
32 if err then sock:close() end
33 return err
34end
35
36-----------------------------------------------------------------------------
37-- Tries to receive DOS mode lines. Closes socket on error.
38-- Input
39-- sock: server socket
40-- Returns
41-- line: received string if successfull, nil in case of error
42-- err: error message if any
43-----------------------------------------------------------------------------
44local gets = function(sock)
45 local line, err = sock:receive("*l")
46 if err then
47 sock:close()
48 return nil, err
49 end
50 return line
51end
52
53-----------------------------------------------------------------------------
54-- Gets a reply from the server and close connection if it is wrong
55-- Input
56-- sock: server socket
57-- accept: acceptable errorcodes
58-- Returns
59-- code: server reply code. nil if error
60-- line: complete server reply message or error message
61-----------------------------------------------------------------------------
62local get_reply = function(sock, accept)
63 local line, err = %gets(sock)
64 if line then
65 if type(accept) ~= "table" then accept = {accept} end
66 local _,_, code = strfind(line, "^(%d%d%d)")
67 if not code then return nil, line end
68 code = tonumber(code)
69 for i = 1, getn(accept) do
70 if code == accept[i] then return code, line end
71 end
72 sock:close()
73 return nil, line
74 end
75 return nil, err
76end
77
78-----------------------------------------------------------------------------
79-- Sends a command to the server
80-- Input
81-- sock: server socket
82-- command: command to be sent
83-- param: command parameters if any
84-- Returns
85-- err: error message if any
86-----------------------------------------------------------------------------
87local send_command = function(sock, command, param)
88 local line
89 if param then line = command .. " " .. param
90 else line = command end
91 return %puts(sock, line)
92end
93
94-----------------------------------------------------------------------------
95-- Gets the initial server greeting
96-- Input
97-- sock: server socket
98-- Returns
99-- code: server status code, nil if error
100-- answer: complete server reply
101-----------------------------------------------------------------------------
102local get_helo = function(sock)
103 return %get_reply(sock, 220)
104end
105
106-----------------------------------------------------------------------------
107-- Sends initial client greeting
108-- Input
109-- sock: server socket
110-- Returns
111-- code: server status code, nil if error
112-- answer: complete server reply
113-----------------------------------------------------------------------------
114local send_helo = function(sock)
115 local err = %send_command(sock, "HELO", %DOMAIN)
116 if not err then
117 return %get_reply(sock, 250)
118 else return nil, err end
119end
120
121-----------------------------------------------------------------------------
122-- Sends mime headers
123-- Input
124-- sock: server socket
125-- mime: table with mime headers to be sent
126-- Returns
127-- err: error message if any
128-----------------------------------------------------------------------------
129local send_mime = function(sock, mime)
130 local err
131 mime = mime or {}
132 -- send all headers
133 for name,value in mime do
134 err = sock:send(name .. ": " .. value .. "\r\n")
135 if err then
136 sock:close()
137 return err
138 end
139 end
140 -- end mime part
141 err = sock:send("\r\n")
142 if err then sock:close() end
143 return err
144end
145
146-----------------------------------------------------------------------------
147-- Sends connection termination command
148-- Input
149-- sock: server socket
150-- Returns
151-- code: server status code, nil if error
152-- answer: complete server reply
153-----------------------------------------------------------------------------
154local send_quit = function(sock)
155 local code, answer
156 local err = %send_command(sock, "QUIT")
157 if not err then
158 code, answer = %get_reply(sock, 221)
159 sock:close()
160 return code, answer
161 else return nil, err end
162end
163
164-----------------------------------------------------------------------------
165-- Sends sender command
166-- Input
167-- sock: server socket
168-- sender: e-mail of sender
169-- Returns
170-- code: server status code, nil if error
171-- answer: complete server reply
172-----------------------------------------------------------------------------
173local send_mail = function(sock, sender)
174 local param = format("FROM:<%s>", sender)
175 local err = %send_command(sock, "MAIL", param)
176 if not err then
177 return %get_reply(sock, 250)
178 else return nil, err end
179end
180
181-----------------------------------------------------------------------------
182-- Sends message mime headers and body
183-- Input
184-- sock: server socket
185-- mime: table containing all mime headers to be sent
186-- body: message body
187-- Returns
188-- code: server status code, nil if error
189-- answer: complete server reply
190-----------------------------------------------------------------------------
191local send_data = function (sock, mime, body)
192 local err = %send_command(sock, "DATA")
193 if not err then
194 local code, answer = %get_reply(sock, 354)
195 if not code then return nil, answer end
196 -- avoid premature end in message body
197 body = gsub(body or "", "\n%.", "\n%.%.")
198 -- mark end of message body
199 body = body .. "\r\n."
200 err = %send_mime(sock, mime)
201 if err then return nil, err end
202 err = %puts(sock, body)
203 return %get_reply(sock, 250)
204 else return nil, err end
205end
206
207-----------------------------------------------------------------------------
208-- Sends recipient list command
209-- Input
210-- sock: server socket
211-- rcpt: lua table with recipient list
212-- Returns
213-- code: server status code, nil if error
214-- answer: complete server reply
215-----------------------------------------------------------------------------
216local send_rcpt = function(sock, rcpt)
217 local err, code, answer
218 if type(rcpt) ~= "table" then rcpt = {rcpt} end
219 for i = 1, getn(rcpt) do
220 err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i]))
221 if not err then
222 code, answer = %get_reply(sock, {250, 251})
223 if not code then return code, answer end
224 else return nil, err end
225 end
226 return code, answer
227end
228
229-----------------------------------------------------------------------------
230-- Sends verify recipient command
231-- Input
232-- sock: server socket
233-- user: user to be verified
234-- Returns
235-- code: server status code, nil if error
236-- answer: complete server reply
237-----------------------------------------------------------------------------
238local send_vrfy = function (sock, user)
239 local err = %send_command(sock, "VRFY", format("<%s>", user))
240 if not err then
241 return %get_reply(sock, {250, 251})
242 else return nil, err end
243end
244
245-----------------------------------------------------------------------------
246-- Connection oriented mail functions
247-----------------------------------------------------------------------------
248function smtp_connect(server)
249 local code, answer
250 -- connect to server
251 local sock, err = connect(server, %PORT)
252 if not sock then return nil, err end
253 sock:timeout(%TIMEOUT)
254 -- initial server greeting
255 code, answer = %get_helo(sock)
256 if not code then return nil, answer end
257 -- HELO
258 code, answer = %send_helo(sock)
259 if not code then return nil, answer end
260 return sock
261end
262
263function smtp_send(sock, from, rcpt, mime, body)
264 local code, answer
265 -- MAIL
266 code, answer = %send_mail(sock, from)
267 if not code then return nil, answer end
268 -- RCPT
269 code, answer = %send_rcpt(sock, rcpt)
270 if not code then return nil, answer end
271 -- DATA
272 return %send_data(sock, mime, body)
273end
274
275function smtp_close(sock)
276 -- QUIT
277 return %send_quit(sock)
278end
279
280-----------------------------------------------------------------------------
281-- Main mail function
282-- Input
283-- from: message sender
284-- rcpt: table containing message recipients
285-- mime: table containing mime headers
286-- body: message body
287-- server: smtp server to be used
288-- Returns
289-- nil if successfull, error message in case of error
290-----------------------------------------------------------------------------
291function smtp_mail(from, rcpt, mime, body, server)
292 local sock, err = smtp_connect(server)
293 if not sock then return err end
294 local code, answer = smtp_send(sock, from, rcpt, mime, body)
295 if not code then return answer end
296 code, answer = smtp_close(sock)
297 if not code then return answer
298 else return nil end
299end
300
301--===========================================================================
302-- Compatibility functions
303--===========================================================================
304-----------------------------------------------------------------------------
305-- Converts a comma separated list into a Lua table with one entry for each
306-- list element.
307-- Input
308-- str: string containing the list to be converted
309-- tab: table to be filled with entries
310-- Returns
311-- a table t, where t.n is the number of elements with an entry t[i]
312-- for each element
313-----------------------------------------------------------------------------
314local fill = function(str, tab)
315 gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end)
316 return tab
317end
318
319-----------------------------------------------------------------------------
320-- Client mail function, implementing CGILUA 3.2 interface
321-----------------------------------------------------------------------------
322function mail(msg)
323 local rcpt = {}
324 local mime = {}
325 mime["Subject"] = msg.subject
326 mime["To"] = msg.to
327 mime["From"] = msg.from
328 %fill(msg.to, rcpt)
329 if msg.cc then
330 %fill(msg.cc, rcpt)
331 mime["Cc"] = msg.cc
332 end
333 if msg.bcc then
334 %fill(msg.bcc, rcpt)
335 end
336 rcpt.n = nil
337 return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver)
338end
diff --git a/test/testclnt.lua b/test/testclnt.lua
new file mode 100644
index 0000000..c1c22bd
--- /dev/null
+++ b/test/testclnt.lua
@@ -0,0 +1,359 @@
1-----------------------------------------------------------------------------
2-- LuaSocket automated test module
3-- client.lua
4-- This is the client module. It connects with the server module and executes
5-- all tests.
6-----------------------------------------------------------------------------
7
8-----------------------------------------------------------------------------
9-- Prints a header to separate the test phases
10-- Input
11-- test: test phase name
12-----------------------------------------------------------------------------
13function new_test(test)
14 write("----------------------------------------------\n",
15 test, "\n",
16 "----------------------------------------------\n")
17end
18
19-----------------------------------------------------------------------------
20-- Read command definitions and stablish control connection
21-----------------------------------------------------------------------------
22new_test("initializing...")
23dofile("command.lua")
24test_debug_mode()
25while control == nil do
26 print("client: trying control connection...")
27 control, err = connect(HOST, PORT)
28 if control then
29 print("client: control connection stablished!")
30 else
31 sleep(2)
32 end
33end
34
35-----------------------------------------------------------------------------
36-- Make sure server is ready for data transmission
37-----------------------------------------------------------------------------
38function sync()
39 send_command(SYNC)
40 get_command()
41end
42
43-----------------------------------------------------------------------------
44-- Close and reopen data connection, to get rid of any unread blocks
45-----------------------------------------------------------------------------
46function reconnect()
47 if data then
48 data:close()
49 send_command(CLOSE)
50 data = nil
51 end
52 while data == nil do
53 send_command(CONNECT)
54 data = connect(HOST, PORT)
55 if not data then
56 print("client: waiting for data connection.")
57 sleep(1)
58 end
59 end
60 sync()
61end
62
63-----------------------------------------------------------------------------
64-- Tests the command connection
65-----------------------------------------------------------------------------
66function test_command(cmd, par)
67 local cmd_back, par_back
68 reconnect()
69 send_command(COMMAND)
70 write("testing command ")
71 print_command(cmd, par)
72 send_command(cmd, par)
73 cmd_back, par_back = get_command()
74 if cmd_back ~= cmd or par_back ~= par then
75 fail(cmd)
76 else
77 pass()
78 end
79end
80
81-----------------------------------------------------------------------------
82-- Tests ASCII line transmission
83-- Input
84-- len: length of line to be tested
85-----------------------------------------------------------------------------
86function test_asciiline(len)
87 local str, str10, back, err
88 reconnect()
89 send_command(ECHO_LINE)
90 str = strrep("x", mod(len, 10))
91 str10 = strrep("aZb.c#dAe?", floor(len/10))
92 str = str .. str10
93 write("testing ", len, " byte(s) line\n")
94 err = data:send(str, "\n")
95 if err then fail(err) end
96 back, err = data:receive()
97 if err then fail(err) end
98 if back == str then pass("lines match")
99 else fail("lines don't match") end
100end
101
102-----------------------------------------------------------------------------
103-- Tests closed connection detection
104-----------------------------------------------------------------------------
105function test_closed()
106 local str = "This is our little test line"
107 local len = strlen(str)
108 local back, err, total
109 reconnect()
110 print("testing close while reading line")
111 send_command(ECHO_BLOCK, len)
112 data:send(str)
113 send_command(CLOSE)
114 -- try to get a line
115 back, err = data:receive()
116 if not err then fail("shold have gotten 'closed'.")
117 elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.")
118 elseif str ~= back then fail("didn't receive what i should 'closed'.")
119 else pass("rightfull 'closed' received") end
120 reconnect()
121 print("testing close while reading block")
122 send_command(ECHO_BLOCK, len)
123 data:send(str)
124 send_command(CLOSE)
125 -- try to get a line
126 back, err = data:receive(2*len)
127 if not err then fail("shold have gotten 'closed'.")
128 elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.")
129 elseif str ~= back then fail("didn't receive what I should.")
130 else pass("rightfull 'closed' received") end
131end
132
133-----------------------------------------------------------------------------
134-- Tests binary line transmission
135-- Input
136-- len: length of line to be tested
137-----------------------------------------------------------------------------
138function test_rawline(len)
139 local str, str10, back, err
140 reconnect()
141 send_command(ECHO_LINE)
142 str = strrep(strchar(47), mod(len, 10))
143 str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10))
144 str = str .. str10
145 write("testing ", len, " byte(s) line\n")
146 err = data:send(str, "\n")
147 if err then fail(err) end
148 back, err = data:receive()
149 if err then fail(err) end
150 if back == str then pass("lines match")
151 else fail("lines don't match") end
152end
153
154-----------------------------------------------------------------------------
155-- Tests block transmission
156-- Input
157-- len: length of block to be tested
158-----------------------------------------------------------------------------
159function test_block(len)
160 local half = floor(len/2)
161 local s1, s2, back, err
162 reconnect()
163 send_command(ECHO_BLOCK, len)
164 write("testing ", len, " byte(s) block\n")
165 s1 = strrep("x", half)
166 err = data:send(s1)
167 if err then fail(err) end
168 sleep(1)
169 s2 = strrep("y", len-half)
170 err = data:send(s2)
171 if err then fail(err) end
172 back, err = data:receive(len)
173 if err then fail(err) end
174 if back == s1..s2 then pass("blocks match")
175 else fail("blocks don't match") end
176end
177
178-----------------------------------------------------------------------------
179-- Tests if return-timeout was respected
180-- delta: time elapsed during transfer
181-- t: timeout value
182-- err: error code returned by I/O operation
183-----------------------------------------------------------------------------
184function returntimed_out(delta, t, err)
185 if err == "timeout" then
186 if delta + 0.1 >= t then
187 pass("got right timeout")
188 return 1
189 else
190 fail("shouldn't have gotten timeout")
191 end
192 elseif delta > t then
193 fail("should have gotten timeout")
194 end
195end
196
197-----------------------------------------------------------------------------
198-- Tests if return-timeout was respected
199-- delta: time elapsed during transfer
200-- t: timeout value
201-- err: error code returned by I/O operation
202-- o: operation being executed
203-----------------------------------------------------------------------------
204function blockedtimed_out(t, s, err, o)
205 if err == "timeout" then
206 if s >= t then
207 pass("got right forced timeout")
208 return 1
209 else
210 pass("got natural cause timeout (may be wrong)")
211 return 1
212 end
213 elseif s > t then
214 if o == "send" then
215 pass("must have been buffered (may be wrong)")
216 else
217 fail("should have gotten timeout")
218 end
219 end
220end
221
222-----------------------------------------------------------------------------
223-- Tests blocked-timeout conformance
224-- Input
225-- len: length of block to be tested
226-- t: timeout value
227-- s: server sleep between transfers
228-----------------------------------------------------------------------------
229function test_blockedtimeout(len, t, s)
230 local str, err, back, total
231 reconnect()
232 send_command(RECEIVE_BLOCK, len)
233 send_command(SLEEP, s)
234 send_command(RECEIVE_BLOCK, len)
235 write("testing ", len, " bytes, ", t,
236 "s block timeout, ", s, "s sleep\n")
237 data:timeout(t)
238 str = strrep("a", 2*len)
239 err, total = data:send(str)
240 if blockedtimed_out(t, s, err, "send") then return end
241 if err then fail(err) end
242 send_command(SEND_BLOCK)
243 send_command(SLEEP, s)
244 send_command(SEND_BLOCK)
245 back, err = data:receive(2*len)
246 if blockedtimed_out(t, s, err, "receive") then return end
247 if err then fail(err) end
248 if back == str then pass("blocks match")
249 else fail("blocks don't match") end
250end
251
252-----------------------------------------------------------------------------
253-- Tests return-timeout conformance
254-- Input
255-- len: length of block to be tested
256-- t: timeout value
257-- s: server sleep between transfers
258-----------------------------------------------------------------------------
259function test_returntimeout(len, t, s)
260 local str, err, back, delta, total
261 reconnect()
262 send_command(RECEIVE_BLOCK, len)
263 send_command(SLEEP, s)
264 send_command(RECEIVE_BLOCK, len)
265 write("testing ", len, " bytes, ", t,
266 "s return timeout, ", s, "s sleep\n")
267 data:timeout(t, "return")
268 str = strrep("a", 2*len)
269 err, total, delta = data:send(str)
270 print("delta: " .. delta)
271 if returntimed_out(delta, t, err) then return end
272 if err then fail(err) end
273 send_command(SEND_BLOCK)
274 send_command(SLEEP, s)
275 send_command(SEND_BLOCK)
276 back, err, delta = data:receive(2*len)
277 print("delta: " .. delta)
278 if returntimed_out(delta, t, err) then return end
279 if err then fail(err) end
280 if back == str then pass("blocks match")
281 else fail("blocks don't match") end
282end
283
284-----------------------------------------------------------------------------
285-- Execute all tests
286-----------------------------------------------------------------------------
287new_test("control connection test")
288test_command(EXIT)
289test_command(CONNECT)
290test_command(CLOSE)
291test_command(ECHO_BLOCK, 12234)
292test_command(SLEEP, 1111)
293test_command(ECHO_LINE)
294
295new_test("connection close test")
296test_closed()
297
298new_test("binary string test")
299test_rawline(1)
300test_rawline(17)
301test_rawline(200)
302test_rawline(3000)
303test_rawline(8000)
304test_rawline(40000)
305
306new_test("blocking transfer test")
307test_block(1)
308test_block(17)
309test_block(200)
310test_block(3000)
311test_block(80000)
312test_block(800000)
313
314new_test("non-blocking transfer test")
315-- the value is not important, we only want
316-- to test non-blockin I/O anyways
317data:timeout(200)
318test_block(1)
319test_block(17)
320test_block(200)
321test_block(3000)
322test_block(80000)
323test_block(800000)
324test_block(8000000)
325
326new_test("character string test")
327test_asciiline(1)
328test_asciiline(17)
329test_asciiline(200)
330test_asciiline(3000)
331test_asciiline(8000)
332test_asciiline(40000)
333
334new_test("return timeout test")
335test_returntimeout(80, .5, 1)
336test_returntimeout(80, 1, 0.5)
337test_returntimeout(8000, .5, 0)
338test_returntimeout(80000, .5, 0)
339test_returntimeout(800000, .5, 0)
340
341new_test("blocked timeout test")
342test_blockedtimeout(80, .5, 1)
343test_blockedtimeout(80, 1, 1)
344test_blockedtimeout(80, 1.5, 1)
345test_blockedtimeout(800, 1, 0)
346test_blockedtimeout(8000, 1, 0)
347test_blockedtimeout(80000, 1, 0)
348test_blockedtimeout(800000, 1, 0)
349
350-----------------------------------------------------------------------------
351-- Close connection and exit server. We are done.
352-----------------------------------------------------------------------------
353new_test("the library has passed all tests")
354print("client: closing connection with server")
355send_command(CLOSE)
356send_command(EXIT)
357control:close()
358print("client: exiting...")
359exit()
diff --git a/test/testsrvr.lua b/test/testsrvr.lua
new file mode 100644
index 0000000..99ecd2a
--- /dev/null
+++ b/test/testsrvr.lua
@@ -0,0 +1,90 @@
1-----------------------------------------------------------------------------
2-- LuaSocket automated test module
3-- server.lua
4-- This is the server module. It's completely controled by the client module
5-- by the use of a control connection.
6-----------------------------------------------------------------------------
7
8-----------------------------------------------------------------------------
9-- Read command definitions
10-----------------------------------------------------------------------------
11dofile("command.lua")
12test_debug_mode()
13
14-----------------------------------------------------------------------------
15-- Bind to address and wait for control connection
16-----------------------------------------------------------------------------
17server, err = bind(HOST, PORT)
18if not server then
19 print(err)
20 exit(1)
21end
22print("server: waiting for control connection...")
23control = server:accept()
24print("server: control connection stablished!")
25
26-----------------------------------------------------------------------------
27-- Executes a command, detecting any possible failures
28-- Input
29-- cmd: command to be executed
30-- par: command parameters, if needed
31-----------------------------------------------------------------------------
32function execute_command(cmd, par)
33 if cmd == CONNECT then
34 print("server: waiting for data connection...")
35 data = server:accept()
36 if not data then
37 fail("server: unable to start data connection!")
38 else
39 print("server: data connection stablished!")
40 end
41 elseif cmd == CLOSE then
42 print("server: closing connection with client...")
43 if data then
44 data:close()
45 data = nil
46 end
47 elseif cmd == ECHO_LINE then
48 str, err = data:receive()
49 if err then fail("server: " .. err) end
50 err = data:send(str, "\n")
51 if err then fail("server: " .. err) end
52 elseif cmd == ECHO_BLOCK then
53 str, err = data:receive(par)
54 if err then fail("server: " .. err) end
55 err = data:send(str)
56 if err then fail("server: " .. err) end
57 elseif cmd == RECEIVE_BLOCK then
58 str, err = data:receive(par)
59 elseif cmd == SEND_BLOCK then
60 err = data:send(str)
61 elseif cmd == ECHO_TIMEOUT then
62 str, err = data:receive(par)
63 if err then fail("server: " .. err) end
64 err = data:send(str)
65 if err then fail("server: " .. err) end
66 elseif cmd == COMMAND then
67 cmd, par = get_command()
68 send_command(cmd, par)
69 elseif cmd == EXIT then
70 print("server: exiting...")
71 exit(0)
72 elseif cmd == SYNC then
73 print("server: synchronizing...")
74 send_command(SYNC)
75 elseif cmd == SLEEP then
76 print("server: sleeping for " .. par .. " seconds...")
77 sleep(par)
78 print("server: woke up!")
79 end
80end
81
82-----------------------------------------------------------------------------
83-- Loop forever, accepting and executing commands
84-----------------------------------------------------------------------------
85while 1 do
86 cmd, par = get_command()
87 if not cmd then fail("server: " .. par) end
88 print_command(cmd, par)
89 execute_command(cmd, par)
90end