diff options
-rw-r--r-- | README | 24 | ||||
-rw-r--r-- | etc/dict.lua | 39 | ||||
-rw-r--r-- | makefile.dist | 81 | ||||
-rw-r--r-- | samples/README | 32 | ||||
-rw-r--r-- | samples/listener.lua | 25 | ||||
-rw-r--r-- | samples/talker.lua | 22 | ||||
-rw-r--r-- | src/ftp.lua | 437 | ||||
-rw-r--r-- | src/http.lua | 312 | ||||
-rw-r--r-- | src/luasocket.h | 18 | ||||
-rw-r--r-- | src/smtp.lua | 338 | ||||
-rw-r--r-- | test/testclnt.lua | 359 | ||||
-rw-r--r-- | test/testsrvr.lua | 90 |
12 files changed, 1777 insertions, 0 deletions
@@ -0,0 +1,24 @@ | |||
1 | This directory contains the implementation of the protocols FTP, HTTP and | ||
2 | SMTP. The files provided are: | ||
3 | |||
4 | http.lua -- HTTP protocol implementation | ||
5 | base64.lua -- base64 encoding implementation | ||
6 | |||
7 | The module http.lua provides functionality to download an URL from a | ||
8 | HTTP server. The implementation conforms to the HTTP/1.1 standard, RFC | ||
9 | 2068. The base64.lua module provides base64 encoding and decoding. The | ||
10 | module is used for the HTTP Basic Authentication Scheme, and conforms to | ||
11 | RFC 1521. | ||
12 | |||
13 | smtp.lua -- SMTP protocol implementation | ||
14 | |||
15 | The module smtp.lua provides functionality to send e-mail messages to a | ||
16 | SMTP mail server. The implementation conforms to RFC 821. | ||
17 | |||
18 | ftp.lua -- FTP protocol implementation | ||
19 | |||
20 | The module ftp.lua provides functions to download and upload files from | ||
21 | and to FTP servers. The implementation conforms to RFC 959. | ||
22 | |||
23 | These implementations are supported. Please send any comments do | ||
24 | diego@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 | |||
7 | if verbose then verbose=write else verbose=function()end end | ||
8 | |||
9 | verbose(">>> connecting to server\n") | ||
10 | local s,e=connect("dict.org",2628) | ||
11 | assert(s,e) | ||
12 | verbose(">>> connected\n") | ||
13 | |||
14 | while 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 | ||
35 | end | ||
36 | |||
37 | send(s,"QUIT\r\n") | ||
38 | verbose("<<< ",receive(s),"\n") | ||
39 | close(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 | |||
10 | CXX = g++ | ||
11 | CC = gcc | ||
12 | |||
13 | DIST = luasocket-1.1 | ||
14 | |||
15 | WARNINGS = -Wall -Wshadow -Wpointer-arith -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs | ||
16 | |||
17 | CFLAGS = $(WARNINGS) -D_DEBUG -O2 | ||
18 | |||
19 | LUA = /home/diego/lib/lua | ||
20 | LUALIB = $(LUA)/lib | ||
21 | LUAINC = $(LUA)/include | ||
22 | |||
23 | INC = -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 | ||
26 | LIB = $(LUALIB)/liblualib.a $(LUALIB)/liblua.a -lm | ||
27 | |||
28 | SRC = ~diego/tec/luasocket | ||
29 | OBJ = /tmp | ||
30 | DEP = /tmp | ||
31 | |||
32 | # list of .cpp files | ||
33 | c_sources = luasocket.c lua.c | ||
34 | |||
35 | # corresponding .o files | ||
36 | c_objects = $(addprefix $(OBJ)/, $(addsuffix .o, $(basename $(c_sources)))) | ||
37 | |||
38 | # binary depends on objects | ||
39 | luasocket: $(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 | ||
47 | c_deps = $(addprefix $(DEP)/, $(addsuffix .d, $(basename $(c_sources)))) | ||
48 | |||
49 | # makefile depend on them... | ||
50 | makefile : $(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 | ||
62 | clean: | ||
63 | rm -f $(OBJ)/*.o | ||
64 | rm -f $(DEP)/*.d | ||
65 | rm -f luasocket core | ||
66 | |||
67 | dist: | ||
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 @@ | |||
1 | This directory contains some sample programs using LuaSocket as well as | ||
2 | the automatic tests used to make sure the library is working properly. | ||
3 | |||
4 | The files provided are: | ||
5 | |||
6 | server.lua -- test server | ||
7 | client.lua -- test client | ||
8 | command.lua -- test command definitions | ||
9 | |||
10 | The automatic tests are composed by three files: client.lua, command.lua | ||
11 | and server.lua. To run the automatic tests on your system, make sure to | ||
12 | compile the library with _DEBUG defined (check makefile) and then open | ||
13 | two terminals. Run 'luasocket server.lua' on one of them and 'luasocket | ||
14 | client.lua' on the other. The programs should start talking to each | ||
15 | other. | ||
16 | |||
17 | listen.lua -- echo server | ||
18 | talk.lua -- echo tester | ||
19 | |||
20 | listen.lua and talk.lua are about the simplest applications you can | ||
21 | write using LuaSocket. Run 'luasocket listen.lua' and 'luasocket | ||
22 | talk.lua' on different terminals. Whatever you type on talk.lua will be | ||
23 | printed by listen.lua. | ||
24 | |||
25 | dict.lua -- dict client | ||
26 | |||
27 | The dict.lua module is a cool simple client for the DICT protocol, | ||
28 | written by Luiz Henrique Figueiredo. Just run it and enter a few words | ||
29 | to see it working. | ||
30 | |||
31 | Good luck, | ||
32 | Diego. | ||
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 @@ | |||
1 | host = "localhost" | ||
2 | port = 8080 | ||
3 | if arg then | ||
4 | host = arg[1] or host | ||
5 | port = arg[2] or port | ||
6 | end | ||
7 | print("Binding to host '" ..host.. "' and port " ..port.. "...") | ||
8 | s, i, p, e = bind(host, port) | ||
9 | if not s then | ||
10 | print(e) | ||
11 | exit() | ||
12 | end | ||
13 | print("Waiting connection from talker on " .. i .. ":" .. p .. "...") | ||
14 | c, e = s:accept() | ||
15 | if not c then | ||
16 | print(e) | ||
17 | exit() | ||
18 | end | ||
19 | print("Connected. Here is the stuff:") | ||
20 | l, e = c:receive() | ||
21 | while not e do | ||
22 | print(l) | ||
23 | l, e = c:receive() | ||
24 | end | ||
25 | print(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 @@ | |||
1 | host = "localhost" | ||
2 | port = 8080 | ||
3 | if arg then | ||
4 | host = arg[1] or host | ||
5 | port = arg[2] or port | ||
6 | end | ||
7 | print("Attempting connection to host '" ..host.. "' and port " ..port.. "...") | ||
8 | c, e = connect(host, port) | ||
9 | if not c then | ||
10 | print(e) | ||
11 | exit() | ||
12 | end | ||
13 | print("Connected! Please type stuff (empty line to stop):") | ||
14 | l = read() | ||
15 | while 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() | ||
22 | end | ||
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 | ||
12 | local TIMEOUT = 60 | ||
13 | -- default port for ftp service | ||
14 | local 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. | ||
17 | local 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 | ----------------------------------------------------------------------------- | ||
35 | local 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 | ||
56 | end | ||
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 | ----------------------------------------------------------------------------- | ||
66 | local 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 | ||
77 | end | ||
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 | ----------------------------------------------------------------------------- | ||
86 | local 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 | ||
92 | end | ||
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 | ----------------------------------------------------------------------------- | ||
102 | local 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) | ||
118 | end | ||
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 | ----------------------------------------------------------------------------- | ||
129 | local 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 | ||
143 | end | ||
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 | ----------------------------------------------------------------------------- | ||
157 | local 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 | ||
166 | end | ||
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 | ----------------------------------------------------------------------------- | ||
177 | local 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 | ||
187 | end | ||
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 | ----------------------------------------------------------------------------- | ||
197 | local 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 | ||
204 | end | ||
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 | ----------------------------------------------------------------------------- | ||
216 | local 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 | ||
223 | end | ||
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 | ----------------------------------------------------------------------------- | ||
234 | local 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 | ||
241 | end | ||
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 | ----------------------------------------------------------------------------- | ||
251 | local 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 | ||
263 | end | ||
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 | ----------------------------------------------------------------------------- | ||
273 | local 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 | ||
278 | end | ||
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 | ----------------------------------------------------------------------------- | ||
291 | local 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 | ||
311 | end | ||
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 | ----------------------------------------------------------------------------- | ||
324 | local 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}) | ||
339 | end | ||
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 | ----------------------------------------------------------------------------- | ||
350 | local change_type = function(control, type) | ||
351 | if type == "b" then type = "i" else type = "a" end | ||
352 | return %try_command(control, "type", type, {200}) | ||
353 | end | ||
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 | ----------------------------------------------------------------------------- | ||
364 | function 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 | ||
395 | end | ||
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 | ----------------------------------------------------------------------------- | ||
406 | function 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 | ||
437 | end | ||
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 | ||
12 | local TIMEOUT = 60 | ||
13 | -- default port for document retrieval | ||
14 | local PORT = 80 | ||
15 | -- user agent field sent in request | ||
16 | local 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 | ----------------------------------------------------------------------------- | ||
25 | local 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 | ||
32 | end | ||
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 | ----------------------------------------------------------------------------- | ||
41 | local try_sendline = function(sock, line) | ||
42 | err = sock:send(line) | ||
43 | if err then sock:close() end | ||
44 | return err | ||
45 | end | ||
46 | |||
47 | ----------------------------------------------------------------------------- | ||
48 | -- Retrieves status from http reply | ||
49 | -- Input | ||
50 | -- reply: http reply string | ||
51 | -- Returns | ||
52 | -- status: integer with status code | ||
53 | ----------------------------------------------------------------------------- | ||
54 | local get_status = function(reply) | ||
55 | local _,_, status = strfind(reply, " (%d%d%d) ") | ||
56 | return tonumber(status) | ||
57 | end | ||
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 | ----------------------------------------------------------------------------- | ||
68 | local 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 | ||
73 | end | ||
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 | ----------------------------------------------------------------------------- | ||
86 | local 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 | ||
116 | end | ||
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 | ----------------------------------------------------------------------------- | ||
129 | local 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 | ||
174 | end | ||
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 | ----------------------------------------------------------------------------- | ||
192 | local 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 | ||
213 | end | ||
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 | ----------------------------------------------------------------------------- | ||
224 | local 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 | ||
233 | end | ||
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 | ----------------------------------------------------------------------------- | ||
243 | local 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 | ||
257 | end | ||
258 | |||
259 | ----------------------------------------------------------------------------- | ||
260 | -- We need base64 convertion routines for Basic Authentication Scheme | ||
261 | ----------------------------------------------------------------------------- | ||
262 | dofile("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 | ----------------------------------------------------------------------------- | ||
276 | function 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 | ||
312 | end | ||
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 | \*-------------------------------------------------------------------------*/ | ||
16 | void 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 | ||
12 | local TIMEOUT = 180 | ||
13 | -- port used for connection | ||
14 | local PORT = 25 | ||
15 | -- domain used in HELO command. If we are under a CGI, try to get from | ||
16 | -- environment | ||
17 | local DOMAIN = getenv("SERVER_NAME") | ||
18 | if not DOMAIN then | ||
19 | DOMAIN = "localhost" | ||
20 | end | ||
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 | ----------------------------------------------------------------------------- | ||
30 | local puts = function(sock, line) | ||
31 | local err = sock:send(line .. "\r\n") | ||
32 | if err then sock:close() end | ||
33 | return err | ||
34 | end | ||
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 | ----------------------------------------------------------------------------- | ||
44 | local 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 | ||
51 | end | ||
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 | ----------------------------------------------------------------------------- | ||
62 | local 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 | ||
76 | end | ||
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 | ----------------------------------------------------------------------------- | ||
87 | local 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) | ||
92 | end | ||
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 | ----------------------------------------------------------------------------- | ||
102 | local get_helo = function(sock) | ||
103 | return %get_reply(sock, 220) | ||
104 | end | ||
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 | ----------------------------------------------------------------------------- | ||
114 | local 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 | ||
119 | end | ||
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 | ----------------------------------------------------------------------------- | ||
129 | local 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 | ||
144 | end | ||
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 | ----------------------------------------------------------------------------- | ||
154 | local 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 | ||
162 | end | ||
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 | ----------------------------------------------------------------------------- | ||
173 | local 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 | ||
179 | end | ||
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 | ----------------------------------------------------------------------------- | ||
191 | local 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 | ||
205 | end | ||
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 | ----------------------------------------------------------------------------- | ||
216 | local 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 | ||
227 | end | ||
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 | ----------------------------------------------------------------------------- | ||
238 | local 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 | ||
243 | end | ||
244 | |||
245 | ----------------------------------------------------------------------------- | ||
246 | -- Connection oriented mail functions | ||
247 | ----------------------------------------------------------------------------- | ||
248 | function 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 | ||
261 | end | ||
262 | |||
263 | function smtp_send(sock, from, rcpt, mime, body) | ||
264 | local code, answer | ||
265 | |||
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) | ||
273 | end | ||
274 | |||
275 | function smtp_close(sock) | ||
276 | -- QUIT | ||
277 | return %send_quit(sock) | ||
278 | end | ||
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 | ----------------------------------------------------------------------------- | ||
291 | function 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 | ||
299 | end | ||
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 | ----------------------------------------------------------------------------- | ||
314 | local fill = function(str, tab) | ||
315 | gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) | ||
316 | return tab | ||
317 | end | ||
318 | |||
319 | ----------------------------------------------------------------------------- | ||
320 | -- Client mail function, implementing CGILUA 3.2 interface | ||
321 | ----------------------------------------------------------------------------- | ||
322 | function 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) | ||
338 | end | ||
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 | ----------------------------------------------------------------------------- | ||
13 | function new_test(test) | ||
14 | write("----------------------------------------------\n", | ||
15 | test, "\n", | ||
16 | "----------------------------------------------\n") | ||
17 | end | ||
18 | |||
19 | ----------------------------------------------------------------------------- | ||
20 | -- Read command definitions and stablish control connection | ||
21 | ----------------------------------------------------------------------------- | ||
22 | new_test("initializing...") | ||
23 | dofile("command.lua") | ||
24 | test_debug_mode() | ||
25 | while 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 | ||
33 | end | ||
34 | |||
35 | ----------------------------------------------------------------------------- | ||
36 | -- Make sure server is ready for data transmission | ||
37 | ----------------------------------------------------------------------------- | ||
38 | function sync() | ||
39 | send_command(SYNC) | ||
40 | get_command() | ||
41 | end | ||
42 | |||
43 | ----------------------------------------------------------------------------- | ||
44 | -- Close and reopen data connection, to get rid of any unread blocks | ||
45 | ----------------------------------------------------------------------------- | ||
46 | function 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() | ||
61 | end | ||
62 | |||
63 | ----------------------------------------------------------------------------- | ||
64 | -- Tests the command connection | ||
65 | ----------------------------------------------------------------------------- | ||
66 | function 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 | ||
79 | end | ||
80 | |||
81 | ----------------------------------------------------------------------------- | ||
82 | -- Tests ASCII line transmission | ||
83 | -- Input | ||
84 | -- len: length of line to be tested | ||
85 | ----------------------------------------------------------------------------- | ||
86 | function 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 | ||
100 | end | ||
101 | |||
102 | ----------------------------------------------------------------------------- | ||
103 | -- Tests closed connection detection | ||
104 | ----------------------------------------------------------------------------- | ||
105 | function 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 | ||
131 | end | ||
132 | |||
133 | ----------------------------------------------------------------------------- | ||
134 | -- Tests binary line transmission | ||
135 | -- Input | ||
136 | -- len: length of line to be tested | ||
137 | ----------------------------------------------------------------------------- | ||
138 | function 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 | ||
152 | end | ||
153 | |||
154 | ----------------------------------------------------------------------------- | ||
155 | -- Tests block transmission | ||
156 | -- Input | ||
157 | -- len: length of block to be tested | ||
158 | ----------------------------------------------------------------------------- | ||
159 | function 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 | ||
176 | end | ||
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 | ----------------------------------------------------------------------------- | ||
184 | function 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 | ||
195 | end | ||
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 | ----------------------------------------------------------------------------- | ||
204 | function 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 | ||
220 | end | ||
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 | ----------------------------------------------------------------------------- | ||
229 | function 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 | ||
250 | end | ||
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 | ----------------------------------------------------------------------------- | ||
259 | function 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 | ||
282 | end | ||
283 | |||
284 | ----------------------------------------------------------------------------- | ||
285 | -- Execute all tests | ||
286 | ----------------------------------------------------------------------------- | ||
287 | new_test("control connection test") | ||
288 | test_command(EXIT) | ||
289 | test_command(CONNECT) | ||
290 | test_command(CLOSE) | ||
291 | test_command(ECHO_BLOCK, 12234) | ||
292 | test_command(SLEEP, 1111) | ||
293 | test_command(ECHO_LINE) | ||
294 | |||
295 | new_test("connection close test") | ||
296 | test_closed() | ||
297 | |||
298 | new_test("binary string test") | ||
299 | test_rawline(1) | ||
300 | test_rawline(17) | ||
301 | test_rawline(200) | ||
302 | test_rawline(3000) | ||
303 | test_rawline(8000) | ||
304 | test_rawline(40000) | ||
305 | |||
306 | new_test("blocking transfer test") | ||
307 | test_block(1) | ||
308 | test_block(17) | ||
309 | test_block(200) | ||
310 | test_block(3000) | ||
311 | test_block(80000) | ||
312 | test_block(800000) | ||
313 | |||
314 | new_test("non-blocking transfer test") | ||
315 | -- the value is not important, we only want | ||
316 | -- to test non-blockin I/O anyways | ||
317 | data:timeout(200) | ||
318 | test_block(1) | ||
319 | test_block(17) | ||
320 | test_block(200) | ||
321 | test_block(3000) | ||
322 | test_block(80000) | ||
323 | test_block(800000) | ||
324 | test_block(8000000) | ||
325 | |||
326 | new_test("character string test") | ||
327 | test_asciiline(1) | ||
328 | test_asciiline(17) | ||
329 | test_asciiline(200) | ||
330 | test_asciiline(3000) | ||
331 | test_asciiline(8000) | ||
332 | test_asciiline(40000) | ||
333 | |||
334 | new_test("return timeout test") | ||
335 | test_returntimeout(80, .5, 1) | ||
336 | test_returntimeout(80, 1, 0.5) | ||
337 | test_returntimeout(8000, .5, 0) | ||
338 | test_returntimeout(80000, .5, 0) | ||
339 | test_returntimeout(800000, .5, 0) | ||
340 | |||
341 | new_test("blocked timeout test") | ||
342 | test_blockedtimeout(80, .5, 1) | ||
343 | test_blockedtimeout(80, 1, 1) | ||
344 | test_blockedtimeout(80, 1.5, 1) | ||
345 | test_blockedtimeout(800, 1, 0) | ||
346 | test_blockedtimeout(8000, 1, 0) | ||
347 | test_blockedtimeout(80000, 1, 0) | ||
348 | test_blockedtimeout(800000, 1, 0) | ||
349 | |||
350 | ----------------------------------------------------------------------------- | ||
351 | -- Close connection and exit server. We are done. | ||
352 | ----------------------------------------------------------------------------- | ||
353 | new_test("the library has passed all tests") | ||
354 | print("client: closing connection with server") | ||
355 | send_command(CLOSE) | ||
356 | send_command(EXIT) | ||
357 | control:close() | ||
358 | print("client: exiting...") | ||
359 | exit() | ||
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 | ----------------------------------------------------------------------------- | ||
11 | dofile("command.lua") | ||
12 | test_debug_mode() | ||
13 | |||
14 | ----------------------------------------------------------------------------- | ||
15 | -- Bind to address and wait for control connection | ||
16 | ----------------------------------------------------------------------------- | ||
17 | server, err = bind(HOST, PORT) | ||
18 | if not server then | ||
19 | print(err) | ||
20 | exit(1) | ||
21 | end | ||
22 | print("server: waiting for control connection...") | ||
23 | control = server:accept() | ||
24 | print("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 | ----------------------------------------------------------------------------- | ||
32 | function 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 | ||
80 | end | ||
81 | |||
82 | ----------------------------------------------------------------------------- | ||
83 | -- Loop forever, accepting and executing commands | ||
84 | ----------------------------------------------------------------------------- | ||
85 | while 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) | ||
90 | end | ||