aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2004-03-16 06:42:53 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2004-03-16 06:42:53 +0000
commitbcc0c2a9f0be2ca796ef5206a78e283fe15e6186 (patch)
tree65c269d4854aa5ff4a0b2c8eede5cdb18d716033
parentb6edaac2841137cf0ef5105f75358bbab4570d87 (diff)
downloadluasocket-bcc0c2a9f0be2ca796ef5206a78e283fe15e6186.tar.gz
luasocket-bcc0c2a9f0be2ca796ef5206a78e283fe15e6186.tar.bz2
luasocket-bcc0c2a9f0be2ca796ef5206a78e283fe15e6186.zip
New filter scheme.
ltn12 and mime updated. smtp/ftp broken.
-rw-r--r--TODO5
-rw-r--r--etc/b64.lua17
-rw-r--r--etc/get.lua47
-rw-r--r--src/auxiliar.c11
-rw-r--r--src/auxiliar.h1
-rw-r--r--src/ftp.lua360
-rw-r--r--src/http.lua219
-rw-r--r--src/ltn12.lua75
-rw-r--r--src/luasocket.c6
-rw-r--r--src/mime.c137
-rw-r--r--src/mime.lua2
-rw-r--r--src/smtp.lua2
-rw-r--r--src/tp.lua111
-rw-r--r--src/wsocket.c4
-rw-r--r--test/httptest.lua45
-rw-r--r--test/mimetest.lua31
-rw-r--r--test/stufftest.lua19
17 files changed, 565 insertions, 527 deletions
diff --git a/TODO b/TODO
index 7479cfc..3f1c71b 100644
--- a/TODO
+++ b/TODO
@@ -19,6 +19,11 @@
19* Separar as classes em arquivos 19* Separar as classes em arquivos
20* Retorno de sendto em datagram sockets pode ser refused 20* Retorno de sendto em datagram sockets pode ser refused
21 21
22colocar um userdata com gc metamethod pra chamar sock_close (WSAClose);
23sources ans sinks are always simple in http and ftp and smtp
24unify backbone of smtp and ftp
25expose encode/decode tables to provide extensibility for mime module
26use coroutines instead of fancy filters
22unify filter and send/receive callback. new sink/source/pump idea. 27unify filter and send/receive callback. new sink/source/pump idea.
23get rid of aux_optlstring 28get rid of aux_optlstring
24wrap sink and sources with a function that performs the replacement 29wrap sink and sources with a function that performs the replacement
diff --git a/etc/b64.lua b/etc/b64.lua
index de83578..ea157c4 100644
--- a/etc/b64.lua
+++ b/etc/b64.lua
@@ -1,13 +1,12 @@
1local source = ltn12.source.file(io.stdin)
2local sink = ltn12.sink.file(io.stdout)
1local convert 3local convert
2if arg and arg[1] == '-d' then 4if arg and arg[1] == '-d' then
3 convert = socket.mime.decode("base64") 5 convert = mime.decode("base64")
4else 6else
5 local base64 = socket.mime.encode("base64") 7 local base64 = mime.encode("base64")
6 local wrap = socket.mime.wrap() 8 local wrap = mime.wrap()
7 convert = socket.mime.chain(base64, wrap) 9 convert = ltn12.filter.chain(base64, wrap)
8end
9while 1 do
10 local chunk = io.read(4096)
11 io.write(convert(chunk))
12 if not chunk then break end
13end 10end
11source = ltn12.source.chain(source, convert)
12ltn12.pump(source, sink)
diff --git a/etc/get.lua b/etc/get.lua
index d6760b8..0306b54 100644
--- a/etc/get.lua
+++ b/etc/get.lua
@@ -80,39 +80,31 @@ function stats(size)
80 end 80 end
81end 81end
82 82
83-- downloads a file using the ftp protocol 83-- determines the size of a http file
84function getbyftp(url, file) 84function gethttpsize(url)
85 local save = socket.callback.receive.file(file or io.stdout) 85 local respt = socket.http.request {method = "HEAD", url = url}
86 if file then 86 if respt.code == 200 then
87 save = socket.callback.receive.chain(stats(gethttpsize(url)), save) 87 return tonumber(respt.headers["content-length"])
88 end 88 end
89 local err = socket.ftp.get_cb {
90 url = url,
91 content_cb = save,
92 type = "i"
93 }
94 if err then print(err) end
95end 89end
96 90
97-- downloads a file using the http protocol 91-- downloads a file using the http protocol
98function getbyhttp(url, file) 92function getbyhttp(url, file)
99 local save = socket.callback.receive.file(file or io.stdout) 93 local save = ltn12.sink.file(file or io.stdout)
100 if file then 94 -- only print feedback if output is not stdout
101 save = socket.callback.receive.chain(stats(gethttpsize(url)), save) 95 if file then save = ltn12.sink.chain(stats(gethttpsize(url)), save) end
102 end 96 local respt = socket.http.request_cb({url = url, sink = save})
103 local response = socket.http.request_cb({url = url}, {body_cb = save}) 97 if respt.code ~= 200 then print(respt.status or respt.error) end
104 if response.code ~= 200 then print(response.status or response.error) end
105end 98end
106 99
107-- determines the size of a http file 100-- downloads a file using the ftp protocol
108function gethttpsize(url) 101function getbyftp(url, file)
109 local response = socket.http.request { 102 local save = ltn12.sink.file(file or io.stdout)
110 method = "HEAD", 103 -- only print feedback if output is not stdout
111 url = url 104 -- and we don't know how big the file is
112 } 105 if file then save = ltn12.sink.chain(stats(), save) end
113 if response.code == 200 then 106 local ret, err = socket.ftp.get_cb {url = url, sink = save, type = "i"}
114 return tonumber(response.headers["content-length"]) 107 if err then print(err) end
115 end
116end 108end
117 109
118-- determines the scheme 110-- determines the scheme
@@ -130,7 +122,6 @@ function get(url, name)
130 if scheme == "ftp" then getbyftp(url, fout) 122 if scheme == "ftp" then getbyftp(url, fout)
131 elseif scheme == "http" then getbyhttp(url, fout) 123 elseif scheme == "http" then getbyhttp(url, fout)
132 else print("unknown scheme" .. scheme) end 124 else print("unknown scheme" .. scheme) end
133 if name then fout:close() end
134end 125end
135 126
136-- main program 127-- main program
diff --git a/src/auxiliar.c b/src/auxiliar.c
index 812d7fc..fe21d08 100644
--- a/src/auxiliar.c
+++ b/src/auxiliar.c
@@ -158,14 +158,3 @@ void *aux_getclassudata(lua_State *L, const char *classname, int objidx)
158{ 158{
159 return luaL_checkudata(L, objidx, classname); 159 return luaL_checkudata(L, objidx, classname);
160} 160}
161
162/*-------------------------------------------------------------------------*\
163* Accept "false" as nil
164\*-------------------------------------------------------------------------*/
165const char *aux_optlstring(lua_State *L, int n, const char *v, size_t *l)
166{
167 if (lua_isnil(L, n) || (lua_isboolean(L, n) && !lua_toboolean(L, n))) {
168 *l = 0;
169 return NULL;
170 } else return luaL_optlstring(L, n, v, l);
171}
diff --git a/src/auxiliar.h b/src/auxiliar.h
index ac62ecd..bc45182 100644
--- a/src/auxiliar.h
+++ b/src/auxiliar.h
@@ -49,6 +49,5 @@ void *aux_checkgroup(lua_State *L, const char *groupname, int objidx);
49void *aux_getclassudata(lua_State *L, const char *groupname, int objidx); 49void *aux_getclassudata(lua_State *L, const char *groupname, int objidx);
50void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx); 50void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx);
51int aux_checkboolean(lua_State *L, int objidx); 51int aux_checkboolean(lua_State *L, int objidx);
52const char *aux_optlstring(lua_State *L, int n, const char *v, size_t *l);
53 52
54#endif /* AUX_H */ 53#endif /* AUX_H */
diff --git a/src/ftp.lua b/src/ftp.lua
index e596416..18dab6d 100644
--- a/src/ftp.lua
+++ b/src/ftp.lua
@@ -5,62 +5,29 @@
5-- Conforming to: RFC 959, LTN7 5-- Conforming to: RFC 959, LTN7
6-- RCS ID: $Id$ 6-- RCS ID: $Id$
7----------------------------------------------------------------------------- 7-----------------------------------------------------------------------------
8 8-- make sure LuaSocket is loaded
9local Public, Private = {}, {} 9if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
10local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace 10-- get LuaSocket namespace
11socket.ftp = Public -- create ftp sub namespace 11local socket = _G[LUASOCKET_LIBNAME]
12if not socket then error('module requires LuaSocket') end
13-- create namespace inside LuaSocket namespace
14socket.ftp = socket.ftp or {}
15-- make all module globals fall into namespace
16setmetatable(socket.ftp, { __index = _G })
17setfenv(1, socket.ftp)
12 18
13----------------------------------------------------------------------------- 19-----------------------------------------------------------------------------
14-- Program constants 20-- Program constants
15----------------------------------------------------------------------------- 21-----------------------------------------------------------------------------
16-- timeout in seconds before the program gives up on a connection 22-- timeout in seconds before the program gives up on a connection
17Public.TIMEOUT = 60 23TIMEOUT = 60
18-- default port for ftp service 24-- default port for ftp service
19Public.PORT = 21 25PORT = 21
20-- this is the default anonymous password. used when no password is 26-- this is the default anonymous password. used when no password is
21-- provided in url. should be changed to your e-mail. 27-- provided in url. should be changed to your e-mail.
22Public.EMAIL = "anonymous@anonymous.org" 28EMAIL = "anonymous@anonymous.org"
23-- block size used in transfers 29-- block size used in transfers
24Public.BLOCKSIZE = 8192 30BLOCKSIZE = 2048
25
26-----------------------------------------------------------------------------
27-- Tries to get a pattern from the server and closes socket on error
28-- sock: socket connected to the server
29-- pattern: pattern to receive
30-- Returns
31-- received pattern on success
32-- nil followed by error message on error
33-----------------------------------------------------------------------------
34function Private.try_receive(sock, pattern)
35 local data, err = sock:receive(pattern)
36 if not data then sock:close() end
37 return data, err
38end
39
40-----------------------------------------------------------------------------
41-- Tries to send data to the server and closes socket on error
42-- sock: socket connected to the server
43-- data: data to send
44-- Returns
45-- err: error message if any, nil if successfull
46-----------------------------------------------------------------------------
47function Private.try_send(sock, data)
48 local sent, err = sock:send(data)
49 if not sent then sock:close() end
50 return err
51end
52
53-----------------------------------------------------------------------------
54-- Tries to send DOS mode lines. Closes socket on error.
55-- Input
56-- sock: server socket
57-- line: string to be sent
58-- Returns
59-- err: message in case of error, nil if successfull
60-----------------------------------------------------------------------------
61function Private.try_sendline(sock, line)
62 return Private.try_send(sock, line .. "\r\n")
63end
64 31
65----------------------------------------------------------------------------- 32-----------------------------------------------------------------------------
66-- Gets ip and port for data connection from PASV answer 33-- Gets ip and port for data connection from PASV answer
@@ -70,7 +37,7 @@ end
70-- ip: string containing ip for data connection 37-- ip: string containing ip for data connection
71-- port: port for data connection 38-- port: port for data connection
72----------------------------------------------------------------------------- 39-----------------------------------------------------------------------------
73function Private.get_pasv(pasv) 40local function get_pasv(pasv)
74 local a, b, c, d, p1, p2, _ 41 local a, b, c, d, p1, p2, _
75 local ip, port 42 local ip, port
76 _,_, a, b, c, d, p1, p2 = 43 _,_, a, b, c, d, p1, p2 =
@@ -82,88 +49,6 @@ function Private.get_pasv(pasv)
82end 49end
83 50
84----------------------------------------------------------------------------- 51-----------------------------------------------------------------------------
85-- Sends a FTP command through socket
86-- Input
87-- control: control connection socket
88-- cmd: command
89-- arg: command argument if any
90-- Returns
91-- error message in case of error, nil otherwise
92-----------------------------------------------------------------------------
93function Private.send_command(control, cmd, arg)
94 local line
95 if arg then line = cmd .. " " .. arg
96 else line = cmd end
97 return Private.try_sendline(control, line)
98end
99
100-----------------------------------------------------------------------------
101-- Gets FTP command answer, unfolding if neccessary
102-- Input
103-- control: control connection socket
104-- Returns
105-- answer: whole server reply, nil if error
106-- code: answer status code or error message
107-----------------------------------------------------------------------------
108function Private.get_answer(control)
109 local code, lastcode, sep, _
110 local line, err = Private.try_receive(control)
111 local answer = line
112 if err then return nil, err end
113 _,_, code, sep = string.find(line, "^(%d%d%d)(.)")
114 if not code or not sep then return nil, answer end
115 if sep == "-" then -- answer is multiline
116 repeat
117 line, err = Private.try_receive(control)
118 if err then return nil, err end
119 _,_, lastcode, sep = string.find(line, "^(%d%d%d)(.)")
120 answer = answer .. "\n" .. line
121 until code == lastcode and sep == " " -- answer ends with same code
122 end
123 return answer, tonumber(code)
124end
125
126-----------------------------------------------------------------------------
127-- Checks if a message return is correct. Closes control connection if not.
128-- Input
129-- control: control connection socket
130-- success: table with successfull reply status code
131-- Returns
132-- code: reply code or nil in case of error
133-- answer: server complete answer or system error message
134-----------------------------------------------------------------------------
135function Private.check_answer(control, success)
136 local answer, code = Private.get_answer(control)
137 if not answer then return nil, code end
138 if type(success) ~= "table" then success = {success} end
139 for _, s in ipairs(success) do
140 if code == s then
141 return code, answer
142 end
143 end
144 control:close()
145 return nil, answer
146end
147
148-----------------------------------------------------------------------------
149-- Trys a command on control socked, in case of error, the control connection
150-- is closed.
151-- Input
152-- control: control connection socket
153-- cmd: command
154-- arg: command argument or nil if no argument
155-- success: table with successfull reply status code
156-- Returns
157-- code: reply code or nil in case of error
158-- answer: server complete answer or system error message
159-----------------------------------------------------------------------------
160function Private.command(control, cmd, arg, success)
161 local err = Private.send_command(control, cmd, arg)
162 if err then return nil, err end
163 return Private.check_answer(control, success)
164end
165
166-----------------------------------------------------------------------------
167-- Check server greeting 52-- Check server greeting
168-- Input 53-- Input
169-- control: control connection with server 54-- control: control connection with server
@@ -171,10 +56,10 @@ end
171-- code: nil if error 56-- code: nil if error
172-- answer: server answer or error message 57-- answer: server answer or error message
173----------------------------------------------------------------------------- 58-----------------------------------------------------------------------------
174function Private.greet(control) 59local function greet(control)
175 local code, answer = Private.check_answer(control, {120, 220}) 60 local code, answer = check_answer(control, {120, 220})
176 if code == 120 then -- please try again, somewhat busy now... 61 if code == 120 then -- please try again, somewhat busy now...
177 return Private.check_answer(control, {220}) 62 return check_answer(control, {220})
178 end 63 end
179 return code, answer 64 return code, answer
180end 65end
@@ -189,10 +74,10 @@ end
189-- code: nil if error 74-- code: nil if error
190-- answer: server answer or error message 75-- answer: server answer or error message
191----------------------------------------------------------------------------- 76-----------------------------------------------------------------------------
192function Private.login(control, user, password) 77local function login(control, user, password)
193 local code, answer = Private.command(control, "user", user, {230, 331}) 78 local code, answer = command(control, "user", user, {230, 331})
194 if code == 331 and password then -- need pass and we have pass 79 if code == 331 and password then -- need pass and we have pass
195 return Private.command(control, "pass", password, {230, 202}) 80 return command(control, "pass", password, {230, 202})
196 end 81 end
197 return code, answer 82 return code, answer
198end 83end
@@ -206,9 +91,7 @@ end
206-- code: nil if error 91-- code: nil if error
207-- answer: server answer or error message 92-- answer: server answer or error message
208----------------------------------------------------------------------------- 93-----------------------------------------------------------------------------
209function Private.cwd(control, path) 94local function cwd(control, path)
210 if path then return Private.command(control, "cwd", path, {250})
211 else return 250, nil end
212end 95end
213 96
214----------------------------------------------------------------------------- 97-----------------------------------------------------------------------------
@@ -219,18 +102,18 @@ end
219-- server: server socket bound to local address, nil if error 102-- server: server socket bound to local address, nil if error
220-- answer: error message if any 103-- answer: error message if any
221----------------------------------------------------------------------------- 104-----------------------------------------------------------------------------
222function Private.port(control) 105local function port(control)
223 local code, answer 106 local code, answer
224 local server, ctl_ip 107 local server, ctl_ip
225 ctl_ip, answer = control:getsockname() 108 ctl_ip, answer = control:getsockname()
226 server, answer = socket.bind(ctl_ip, 0) 109 server, answer = socket.bind(ctl_ip, 0)
227 server:settimeout(Public.TIMEOUT) 110 server:settimeout(TIMEOUT)
228 local ip, p, ph, pl 111 local ip, p, ph, pl
229 ip, p = server:getsockname() 112 ip, p = server:getsockname()
230 pl = math.mod(p, 256) 113 pl = math.mod(p, 256)
231 ph = (p - pl)/256 114 ph = (p - pl)/256
232 local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") 115 local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
233 code, answer = Private.command(control, "port", arg, {200}) 116 code, answer = command(control, "port", arg, {200})
234 if not code then 117 if not code then
235 server:close() 118 server:close()
236 return nil, answer 119 return nil, answer
@@ -245,8 +128,8 @@ end
245-- code: nil if error 128-- code: nil if error
246-- answer: server answer or error message 129-- answer: server answer or error message
247----------------------------------------------------------------------------- 130-----------------------------------------------------------------------------
248function Private.logout(control) 131local function logout(control)
249 local code, answer = Private.command(control, "quit", nil, {221}) 132 local code, answer = command(control, "quit", nil, {221})
250 if code then control:close() end 133 if code then control:close() end
251 return code, answer 134 return code, answer
252end 135end
@@ -259,10 +142,10 @@ end
259-- Returns 142-- Returns
260-- nil if successfull, or an error message in case of error 143-- nil if successfull, or an error message in case of error
261----------------------------------------------------------------------------- 144-----------------------------------------------------------------------------
262function Private.receive_indirect(data, callback) 145local function receive_indirect(data, callback)
263 local chunk, err, res 146 local chunk, err, res
264 while not err do 147 while not err do
265 chunk, err = Private.try_receive(data, Public.BLOCKSIZE) 148 chunk, err = try_receive(data, BLOCKSIZE)
266 if err == "closed" then err = "done" end 149 if err == "closed" then err = "done" end
267 res = callback(chunk, err) 150 res = callback(chunk, err)
268 if not res then break end 151 if not res then break end
@@ -280,16 +163,16 @@ end
280-- Returns 163-- Returns
281-- err: error message in case of error, nil otherwise 164-- err: error message in case of error, nil otherwise
282----------------------------------------------------------------------------- 165-----------------------------------------------------------------------------
283function Private.retrieve(control, server, name, is_directory, content_cb) 166local function retrieve(control, server, name, is_directory, content_cb)
284 local code, answer 167 local code, answer
285 local data 168 local data
286 -- ask server for file or directory listing accordingly 169 -- ask server for file or directory listing accordingly
287 if is_directory then 170 if is_directory then
288 code, answer = Private.cwd(control, name) 171 code, answer = cwd(control, name)
289 if not code then return answer end 172 if not code then return answer end
290 code, answer = Private.command(control, "nlst", nil, {150, 125}) 173 code, answer = command(control, "nlst", nil, {150, 125})
291 else 174 else
292 code, answer = Private.command(control, "retr", name, {150, 125}) 175 code, answer = command(control, "retr", name, {150, 125})
293 end 176 end
294 if not code then return nil, answer end 177 if not code then return nil, answer end
295 data, answer = server:accept() 178 data, answer = server:accept()
@@ -298,43 +181,14 @@ function Private.retrieve(control, server, name, is_directory, content_cb)
298 control:close() 181 control:close()
299 return answer 182 return answer
300 end 183 end
301 answer = Private.receive_indirect(data, content_cb) 184 answer = receive_indirect(data, content_cb)
302 if answer then 185 if answer then
303 control:close() 186 control:close()
304 return answer 187 return answer
305 end 188 end
306 data:close() 189 data:close()
307 -- make sure file transfered ok 190 -- make sure file transfered ok
308 return Private.check_answer(control, {226, 250}) 191 return check_answer(control, {226, 250})
309end
310
311-----------------------------------------------------------------------------
312-- Sends data comming from a callback
313-- Input
314-- data: data connection
315-- send_cb: callback to produce file contents
316-- chunk, size: first callback return values
317-- Returns
318-- nil if successfull, or an error message in case of error
319-----------------------------------------------------------------------------
320function Private.send_indirect(data, send_cb, chunk, size)
321 local total, sent, err
322 total = 0
323 while 1 do
324 if type(chunk) ~= "string" or type(size) ~= "number" then
325 data:close()
326 if not chunk and type(size) == "string" then return size
327 else return "invalid callback return" end
328 end
329 sent, err = data:send(chunk)
330 if err then
331 data:close()
332 return err
333 end
334 total = total + sent
335 if sent >= size then break end
336 chunk, size = send_cb()
337 end
338end 192end
339 193
340----------------------------------------------------------------------------- 194-----------------------------------------------------------------------------
@@ -348,9 +202,9 @@ end
348-- code: return code, nil if error 202-- code: return code, nil if error
349-- answer: server answer or error message 203-- answer: server answer or error message
350----------------------------------------------------------------------------- 204-----------------------------------------------------------------------------
351function Private.store(control, server, file, send_cb) 205local function store(control, server, file, send_cb)
352 local data, err 206 local data, err
353 local code, answer = Private.command(control, "stor", file, {150, 125}) 207 local code, answer = command(control, "stor", file, {150, 125})
354 if not code then 208 if not code then
355 control:close() 209 control:close()
356 return nil, answer 210 return nil, answer
@@ -363,7 +217,7 @@ function Private.store(control, server, file, send_cb)
363 return nil, answer 217 return nil, answer
364 end 218 end
365 -- send whole file 219 -- send whole file
366 err = Private.send_indirect(data, send_cb, send_cb()) 220 err = send_indirect(data, send_cb, send_cb())
367 if err then 221 if err then
368 control:close() 222 control:close()
369 return nil, err 223 return nil, err
@@ -371,7 +225,7 @@ function Private.store(control, server, file, send_cb)
371 -- close connection to inform that file transmission is complete 225 -- close connection to inform that file transmission is complete
372 data:close() 226 data:close()
373 -- check if file was received correctly 227 -- check if file was received correctly
374 return Private.check_answer(control, {226, 250}) 228 return check_answer(control, {226, 250})
375end 229end
376 230
377----------------------------------------------------------------------------- 231-----------------------------------------------------------------------------
@@ -382,11 +236,11 @@ end
382-- Returns 236-- Returns
383-- err: error message if any 237-- err: error message if any
384----------------------------------------------------------------------------- 238-----------------------------------------------------------------------------
385function Private.change_type(control, params) 239local function change_type(control, params)
386 local type, _ 240 local type, _
387 _, _, type = string.find(params or "", "type=(.)") 241 _, _, type = string.find(params or "", "type=(.)")
388 if type == "a" or type == "i" then 242 if type == "a" or type == "i" then
389 local code, err = Private.command(control, "type", type, {200}) 243 local code, err = command(control, "type", type, {200})
390 if not code then return err end 244 if not code then return err end
391 end 245 end
392end 246end
@@ -399,45 +253,42 @@ end
399-- control: control connection with server, or nil if error 253-- control: control connection with server, or nil if error
400-- err: error message if any 254-- err: error message if any
401----------------------------------------------------------------------------- 255-----------------------------------------------------------------------------
402function Private.open(parsed) 256local function open(parsed)
403 -- start control connection 257 local control, err = socket.tp.connect(parsed.host, parsed.port)
404 local control, err = socket.connect(parsed.host, parsed.port)
405 if not control then return nil, err end 258 if not control then return nil, err end
406 -- make sure we don't block forever 259 local code, reply
407 control:settimeout(Public.TIMEOUT) 260 -- greet
408 -- check greeting 261 code, reply = control:check({120, 220})
409 local code, answer = Private.greet(control) 262 if code == 120 then -- busy, try again
410 if not code then return nil, answer end 263 code, reply = control:check(220)
411 -- try to log in 264 end
412 code, err = Private.login(control, parsed.user, parsed.password) 265 -- authenticate
413 if not code then return nil, err 266 code, reply = control:command("user", user)
414 else return control end 267 code, reply = control:check({230, 331})
268 if code == 331 and password then -- need pass and we have pass
269 control:command("pass", password)
270 code, reply = control:check({230, 202})
271 end
272 -- change directory
273 local segment = parse_path(parsed)
274 for i, v in ipairs(segment) do
275 code, reply = control:command("cwd")
276 code, reply = control:check(250)
277 end
278 -- change type
279 local type = string.sub(params or "", 7, 7)
280 if type == "a" or type == "i" then
281 code, reply = control:command("type", type)
282 code, reply = control:check(200)
283 end
415end 284end
416 285
417----------------------------------------------------------------------------- 286 return change_dir(control, segment) or
418-- Closes the connection with the server 287 change_type(control, parsed.params) or
419-- Input 288 download(control, request, segment) or
420-- control: control connection with server 289 close(control)
421-----------------------------------------------------------------------------
422function Private.close(control)
423 -- disconnect
424 Private.logout(control)
425end 290end
426 291
427-----------------------------------------------------------------------------
428-- Changes to the directory pointed to by URL
429-- Input
430-- control: control connection with server
431-- segment: parsed URL path segments
432-- Returns
433-- err: error message if any
434-----------------------------------------------------------------------------
435function Private.change_dir(control, segment)
436 local n = table.getn(segment)
437 for i = 1, n-1 do
438 local code, answer = Private.cwd(control, segment[i])
439 if not code then return answer end
440 end
441end 292end
442 293
443----------------------------------------------------------------------------- 294-----------------------------------------------------------------------------
@@ -450,7 +301,7 @@ end
450-- Returns 301-- Returns
451-- err: error message if any 302-- err: error message if any
452----------------------------------------------------------------------------- 303-----------------------------------------------------------------------------
453function Private.upload(control, request, segment) 304local function upload(control, request, segment)
454 local code, name, content_cb 305 local code, name, content_cb
455 -- get remote file name 306 -- get remote file name
456 name = segment[table.getn(segment)] 307 name = segment[table.getn(segment)]
@@ -460,10 +311,10 @@ function Private.upload(control, request, segment)
460 end 311 end
461 content_cb = request.content_cb 312 content_cb = request.content_cb
462 -- setup passive connection 313 -- setup passive connection
463 local server, answer = Private.port(control) 314 local server, answer = port(control)
464 if not server then return answer end 315 if not server then return answer end
465 -- ask server to receive file 316 -- ask server to receive file
466 code, answer = Private.store(control, server, name, content_cb) 317 code, answer = store(control, server, name, content_cb)
467 if not code then return answer end 318 if not code then return answer end
468end 319end
469 320
@@ -477,7 +328,7 @@ end
477-- Returns 328-- Returns
478-- err: error message if any 329-- err: error message if any
479----------------------------------------------------------------------------- 330-----------------------------------------------------------------------------
480function Private.download(control, request, segment) 331local function download(control, request, segment)
481 local code, name, is_directory, content_cb 332 local code, name, is_directory, content_cb
482 is_directory = segment.is_directory 333 is_directory = segment.is_directory
483 content_cb = request.content_cb 334 content_cb = request.content_cb
@@ -488,10 +339,10 @@ function Private.download(control, request, segment)
488 return "Invalid file path" 339 return "Invalid file path"
489 end 340 end
490 -- setup passive connection 341 -- setup passive connection
491 local server, answer = Private.port(control) 342 local server, answer = port(control)
492 if not server then return answer end 343 if not server then return answer end
493 -- ask server to send file or directory listing 344 -- ask server to send file or directory listing
494 code, answer = Private.retrieve(control, server, name, 345 code, answer = retrieve(control, server, name,
495 is_directory, content_cb) 346 is_directory, content_cb)
496 if not code then return answer end 347 if not code then return answer end
497end 348end
@@ -507,13 +358,12 @@ end
507-- Returns 358-- Returns
508-- parsed: a table with parsed components 359-- parsed: a table with parsed components
509----------------------------------------------------------------------------- 360-----------------------------------------------------------------------------
510function Private.parse_url(request) 361local function parse_url(request)
511 local parsed = socket.url.parse(request.url, { 362 local parsed = socket.url.parse(request.url, {
512 host = "",
513 user = "anonymous", 363 user = "anonymous",
514 port = 21, 364 port = 21,
515 path = "/", 365 path = "/",
516 password = Public.EMAIL, 366 password = EMAIL,
517 scheme = "ftp" 367 scheme = "ftp"
518 }) 368 })
519 -- explicit login information overrides that given by URL 369 -- explicit login information overrides that given by URL
@@ -531,7 +381,7 @@ end
531-- Returns 381-- Returns
532-- dirs: a table with parsed directory components 382-- dirs: a table with parsed directory components
533----------------------------------------------------------------------------- 383-----------------------------------------------------------------------------
534function Private.parse_path(parsed_url) 384local function parse_path(parsed_url)
535 local segment = socket.url.parse_path(parsed_url.path) 385 local segment = socket.url.parse_path(parsed_url.path)
536 segment.is_directory = segment.is_directory or 386 segment.is_directory = segment.is_directory or
537 (parsed_url.params == "type=d") 387 (parsed_url.params == "type=d")
@@ -549,7 +399,7 @@ end
549-- Returns 399-- Returns
550-- request: request table 400-- request: request table
551----------------------------------------------------------------------------- 401-----------------------------------------------------------------------------
552function Private.build_request(data) 402local function build_request(data)
553 local request = {} 403 local request = {}
554 if type(data) == "table" then for i, v in data do request[i] = v end 404 if type(data) == "table" then for i, v in data do request[i] = v end
555 else request.url = data end 405 else request.url = data end
@@ -568,18 +418,18 @@ end
568-- Returns 418-- Returns
569-- err: error message if any 419-- err: error message if any
570----------------------------------------------------------------------------- 420-----------------------------------------------------------------------------
571function Public.get_cb(request) 421function get_cb(request)
572 local parsed = Private.parse_url(request) 422 local parsed = parse_url(request)
573 if parsed.scheme ~= "ftp" then 423 if parsed.scheme ~= "ftp" then
574 return string.format("unknown scheme '%s'", parsed.scheme) 424 return string.format("unknown scheme '%s'", parsed.scheme)
575 end 425 end
576 local control, err = Private.open(parsed) 426 local control, err = open(parsed)
577 if not control then return err end 427 if not control then return err end
578 local segment = Private.parse_path(parsed) 428 local segment = parse_path(parsed)
579 return Private.change_dir(control, segment) or 429 return change_dir(control, segment) or
580 Private.change_type(control, parsed.params) or 430 change_type(control, parsed.params) or
581 Private.download(control, request, segment) or 431 download(control, request, segment) or
582 Private.close(control) 432 close(control)
583end 433end
584 434
585----------------------------------------------------------------------------- 435-----------------------------------------------------------------------------
@@ -594,18 +444,18 @@ end
594-- Returns 444-- Returns
595-- err: error message if any 445-- err: error message if any
596----------------------------------------------------------------------------- 446-----------------------------------------------------------------------------
597function Public.put_cb(request) 447function put_cb(request)
598 local parsed = Private.parse_url(request) 448 local parsed = parse_url(request)
599 if parsed.scheme ~= "ftp" then 449 if parsed.scheme ~= "ftp" then
600 return string.format("unknown scheme '%s'", parsed.scheme) 450 return string.format("unknown scheme '%s'", parsed.scheme)
601 end 451 end
602 local control, err = Private.open(parsed) 452 local control, err = open(parsed)
603 if not control then return err end 453 if not control then return err end
604 local segment = Private.parse_path(parsed) 454 local segment = parse_path(parsed)
605 err = Private.change_dir(control, segment) or 455 err = change_dir(control, segment) or
606 Private.change_type(control, parsed.params) or 456 change_type(control, parsed.params) or
607 Private.upload(control, request, segment) or 457 upload(control, request, segment) or
608 Private.close(control) 458 close(control)
609 if err then return nil, err 459 if err then return nil, err
610 else return 1 end 460 else return 1 end
611end 461end
@@ -623,11 +473,11 @@ end
623-- Returns 473-- Returns
624-- err: error message if any 474-- err: error message if any
625----------------------------------------------------------------------------- 475-----------------------------------------------------------------------------
626function Public.put(url_or_request, content) 476function put(url_or_request, content)
627 local request = Private.build_request(url_or_request) 477 local request = build_request(url_or_request)
628 request.content = request.content or content 478 request.content = request.content or content
629 request.content_cb = socket.callback.send_string(request.content) 479 request.content_cb = socket.callback.send_string(request.content)
630 return Public.put_cb(request) 480 return put_cb(request)
631end 481end
632 482
633----------------------------------------------------------------------------- 483-----------------------------------------------------------------------------
@@ -642,12 +492,12 @@ end
642-- data: file contents as a string 492-- data: file contents as a string
643-- err: error message in case of error, nil otherwise 493-- err: error message in case of error, nil otherwise
644----------------------------------------------------------------------------- 494-----------------------------------------------------------------------------
645function Public.get(url_or_request) 495function get(url_or_request)
646 local concat = socket.concat.create() 496 local concat = socket.concat.create()
647 local request = Private.build_request(url_or_request) 497 local request = build_request(url_or_request)
648 request.content_cb = socket.callback.receive_concat(concat) 498 request.content_cb = socket.callback.receive_concat(concat)
649 local err = Public.get_cb(request) 499 local err = get_cb(request)
650 return concat:getresult(), err 500 return concat:getresult(), err
651end 501end
652 502
653return ftp 503return socket.ftp
diff --git a/src/http.lua b/src/http.lua
index 74c29ba..629bf65 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -10,12 +10,11 @@ if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
10-- get LuaSocket namespace 10-- get LuaSocket namespace
11local socket = _G[LUASOCKET_LIBNAME] 11local socket = _G[LUASOCKET_LIBNAME]
12if not socket then error('module requires LuaSocket') end 12if not socket then error('module requires LuaSocket') end
13-- create smtp namespace inside LuaSocket namespace 13-- create namespace inside LuaSocket namespace
14local http = socket.http or {} 14socket.http = socket.http or {}
15socket.http = http 15-- make all module globals fall into namespace
16-- make all module globals fall into smtp namespace 16setmetatable(socket.http, { __index = _G })
17setmetatable(http, { __index = _G }) 17setfenv(1, socket.http)
18setfenv(1, http)
19 18
20----------------------------------------------------------------------------- 19-----------------------------------------------------------------------------
21-- Program constants 20-- Program constants
@@ -27,7 +26,18 @@ PORT = 80
27-- user agent field sent in request 26-- user agent field sent in request
28USERAGENT = socket.version 27USERAGENT = socket.version
29-- block size used in transfers 28-- block size used in transfers
30BLOCKSIZE = 8192 29BLOCKSIZE = 2048
30
31-----------------------------------------------------------------------------
32-- Function return value selectors
33-----------------------------------------------------------------------------
34local function second(a, b)
35 return b
36end
37
38local function third(a, b, c)
39 return c
40end
31 41
32----------------------------------------------------------------------------- 42-----------------------------------------------------------------------------
33-- Tries to get a pattern from the server and closes socket on error 43-- Tries to get a pattern from the server and closes socket on error
@@ -47,7 +57,7 @@ end
47----------------------------------------------------------------------------- 57-----------------------------------------------------------------------------
48-- Tries to send data to the server and closes socket on error 58-- Tries to send data to the server and closes socket on error
49-- sock: socket connected to the server 59-- sock: socket connected to the server
50-- data: data to send 60-- ...: data to send
51-- Returns 61-- Returns
52-- err: error message if any, nil if successfull 62-- err: error message if any, nil if successfull
53----------------------------------------------------------------------------- 63-----------------------------------------------------------------------------
@@ -68,11 +78,9 @@ end
68-- err: error message if any 78-- err: error message if any
69----------------------------------------------------------------------------- 79-----------------------------------------------------------------------------
70local function receive_status(sock) 80local function receive_status(sock)
71 local line, err 81 local line, err = try_receiving(sock)
72 line, err = try_receiving(sock)
73 if not err then 82 if not err then
74 local code, _ 83 local code = third(string.find(line, "HTTP/%d*%.%d* (%d%d%d)"))
75 _, _, code = string.find(line, "HTTP/%d*%.%d* (%d%d%d)")
76 return tonumber(code), line 84 return tonumber(code), line
77 else return nil, nil, err end 85 else return nil, nil, err end
78end 86end
@@ -121,7 +129,7 @@ local function receive_headers(sock, headers)
121end 129end
122 130
123----------------------------------------------------------------------------- 131-----------------------------------------------------------------------------
124-- Aborts a receive callback 132-- Aborts a sink with an error message
125-- Input 133-- Input
126-- cb: callback function 134-- cb: callback function
127-- err: error message to pass to callback 135-- err: error message to pass to callback
@@ -129,8 +137,8 @@ end
129-- callback return or if nil err 137-- callback return or if nil err
130----------------------------------------------------------------------------- 138-----------------------------------------------------------------------------
131local function abort(cb, err) 139local function abort(cb, err)
132 local go, err_or_f = cb(nil, err) 140 local go, cb_err = cb(nil, err)
133 return err_or_f or err 141 return cb_err or err
134end 142end
135 143
136----------------------------------------------------------------------------- 144-----------------------------------------------------------------------------
@@ -138,41 +146,36 @@ end
138-- Input 146-- Input
139-- sock: socket connected to the server 147-- sock: socket connected to the server
140-- headers: header set in which to include trailer headers 148-- headers: header set in which to include trailer headers
141-- receive_cb: function to receive chunks 149-- sink: response message body sink
142-- Returns 150-- Returns
143-- nil if successfull or an error message in case of error 151-- nil if successfull or an error message in case of error
144----------------------------------------------------------------------------- 152-----------------------------------------------------------------------------
145local function receive_body_bychunks(sock, headers, receive_cb) 153local function receive_body_bychunks(sock, headers, sink)
146 local chunk, size, line, err, go, err_or_f, _ 154 local chunk, size, line, err, go
147 while 1 do 155 while 1 do
148 -- get chunk size, skip extention 156 -- get chunk size, skip extention
149 line, err = try_receiving(sock) 157 line, err = try_receiving(sock)
150 if err then return abort(receive_cb, err) end 158 if err then return abort(sink, err) end
151 size = tonumber(string.gsub(line, ";.*", ""), 16) 159 size = tonumber(string.gsub(line, ";.*", ""), 16)
152 if not size then return abort(receive_cb, "invalid chunk size") end 160 if not size then return abort(sink, "invalid chunk size") end
153 -- was it the last chunk? 161 -- was it the last chunk?
154 if size <= 0 then break end 162 if size <= 0 then break end
155 -- get chunk 163 -- get chunk
156 chunk, err = try_receiving(sock, size) 164 chunk, err = try_receiving(sock, size)
157 if err then return abort(receive_cb, err) end 165 if err then return abort(sink, err) end
158 -- pass chunk to callback 166 -- pass chunk to callback
159 go, err_or_f = receive_cb(chunk) 167 go, err = sink(chunk)
160 -- see if callback needs to be replaced
161 receive_cb = err_or_f or receive_cb
162 -- see if callback aborted 168 -- see if callback aborted
163 if not go then return err_or_f or "aborted by callback" end 169 if not go then return err or "aborted by callback" end
164 -- skip CRLF on end of chunk 170 -- skip CRLF on end of chunk
165 _, err = try_receiving(sock) 171 err = second(try_receiving(sock))
166 if err then return abort(receive_cb, err) end 172 if err then return abort(sink, err) end
167 end 173 end
168 -- the server should not send trailer headers because we didn't send a 174 -- servers shouldn't send trailer headers, but who trusts them?
169 -- header informing it we know how to deal with them. we do not risk 175 err = second(receive_headers(sock, headers))
170 -- being caught unprepaired. 176 if err then return abort(sink, err) end
171 _, err = receive_headers(sock, headers)
172 if err then return abort(receive_cb, err) end
173 -- let callback know we are done 177 -- let callback know we are done
174 _, err_or_f = receive_cb("") 178 return second(sink(nil))
175 return err_or_f
176end 179end
177 180
178----------------------------------------------------------------------------- 181-----------------------------------------------------------------------------
@@ -180,94 +183,84 @@ end
180-- Input 183-- Input
181-- sock: socket connected to the server 184-- sock: socket connected to the server
182-- length: message body length 185-- length: message body length
183-- receive_cb: function to receive chunks 186-- sink: response message body sink
184-- Returns 187-- Returns
185-- nil if successfull or an error message in case of error 188-- nil if successfull or an error message in case of error
186----------------------------------------------------------------------------- 189-----------------------------------------------------------------------------
187local function receive_body_bylength(sock, length, receive_cb) 190local function receive_body_bylength(sock, length, sink)
188 while length > 0 do 191 while length > 0 do
189 local size = math.min(BLOCKSIZE, length) 192 local size = math.min(BLOCKSIZE, length)
190 local chunk, err = sock:receive(size) 193 local chunk, err = sock:receive(size)
191 local go, err_or_f = receive_cb(chunk) 194 local go, cb_err = sink(chunk)
192 length = length - string.len(chunk) 195 length = length - string.len(chunk)
193 -- see if callback aborted 196 -- see if callback aborted
194 if not go then return err_or_f or "aborted by callback" end 197 if not go then return cb_err or "aborted by callback" end
195 -- see if callback needs to be replaced
196 receive_cb = err_or_f or receive_cb
197 -- see if there was an error 198 -- see if there was an error
198 if err and length > 0 then return abort(receive_cb, err) end 199 if err and length > 0 then return abort(sink, err) end
199 end 200 end
200 local _, err_or_f = receive_cb("") 201 return second(sink(nil))
201 return err_or_f
202end 202end
203 203
204----------------------------------------------------------------------------- 204-----------------------------------------------------------------------------
205-- Receives a message body by content-length 205-- Receives a message body until the conection is closed
206-- Input 206-- Input
207-- sock: socket connected to the server 207-- sock: socket connected to the server
208-- receive_cb: function to receive chunks 208-- sink: response message body sink
209-- Returns 209-- Returns
210-- nil if successfull or an error message in case of error 210-- nil if successfull or an error message in case of error
211----------------------------------------------------------------------------- 211-----------------------------------------------------------------------------
212local function receive_body_untilclosed(sock, receive_cb) 212local function receive_body_untilclosed(sock, sink)
213 while 1 do 213 while 1 do
214 local chunk, err = sock:receive(BLOCKSIZE) 214 local chunk, err = sock:receive(BLOCKSIZE)
215 local go, err_or_f = receive_cb(chunk) 215 local go, cb_err = sink(chunk)
216 -- see if callback aborted 216 -- see if callback aborted
217 if not go then return err_or_f or "aborted by callback" end 217 if not go then return cb_err or "aborted by callback" end
218 -- see if callback needs to be replaced
219 receive_cb = err_or_f or receive_cb
220 -- see if we are done 218 -- see if we are done
221 if err == "closed" then 219 if err == "closed" then return chunk and second(sink(nil)) end
222 if chunk ~= "" then
223 go, err_or_f = receive_cb("")
224 return err_or_f
225 end
226 end
227 -- see if there was an error 220 -- see if there was an error
228 if err then return abort(receive_cb, err) end 221 if err then return abort(sink, err) end
229 end 222 end
230end 223end
231 224
232----------------------------------------------------------------------------- 225-----------------------------------------------------------------------------
233-- Receives HTTP response body 226-- Receives the HTTP response body
234-- Input 227-- Input
235-- sock: socket connected to the server 228-- sock: socket connected to the server
236-- headers: response header fields 229-- headers: response header fields
237-- receive_cb: function to receive chunks 230-- sink: response message body sink
238-- Returns 231-- Returns
239-- nil if successfull or an error message in case of error 232-- nil if successfull or an error message in case of error
240----------------------------------------------------------------------------- 233-----------------------------------------------------------------------------
241local function receive_body(sock, headers, receive_cb) 234local function receive_body(sock, headers, sink)
235 -- make sure sink is not fancy
236 sink = ltn12.sink.simplify(sink)
242 local te = headers["transfer-encoding"] 237 local te = headers["transfer-encoding"]
243 if te and te ~= "identity" then 238 if te and te ~= "identity" then
244 -- get by chunked transfer-coding of message body 239 -- get by chunked transfer-coding of message body
245 return receive_body_bychunks(sock, headers, receive_cb) 240 return receive_body_bychunks(sock, headers, sink)
246 elseif tonumber(headers["content-length"]) then 241 elseif tonumber(headers["content-length"]) then
247 -- get by content-length 242 -- get by content-length
248 local length = tonumber(headers["content-length"]) 243 local length = tonumber(headers["content-length"])
249 return receive_body_bylength(sock, length, receive_cb) 244 return receive_body_bylength(sock, length, sink)
250 else 245 else
251 -- get it all until connection closes 246 -- get it all until connection closes
252 return receive_body_untilclosed(sock, receive_cb) 247 return receive_body_untilclosed(sock, sink)
253 end 248 end
254end 249end
255 250
256----------------------------------------------------------------------------- 251-----------------------------------------------------------------------------
257-- Sends data comming from a callback 252-- Sends the HTTP request message body in chunks
258-- Input 253-- Input
259-- data: data connection 254-- data: data connection
260-- send_cb: callback to produce file contents 255-- source: request message body source
261-- Returns 256-- Returns
262-- nil if successfull, or an error message in case of error 257-- nil if successfull, or an error message in case of error
263----------------------------------------------------------------------------- 258-----------------------------------------------------------------------------
264local function send_body_bychunks(data, send_cb) 259local function send_body_bychunks(data, source)
265 while 1 do 260 while 1 do
266 local chunk, err_or_f = send_cb() 261 local chunk, cb_err = source()
267 -- check if callback aborted 262 -- check if callback aborted
268 if not chunk then return err_or_f or "aborted by callback" end 263 if not chunk then return cb_err or "aborted by callback" end
269 -- check if callback should be replaced
270 send_cb = err_or_f or send_cb
271 -- if we are done, send last-chunk 264 -- if we are done, send last-chunk
272 if chunk == "" then return try_sending(data, "0\r\n\r\n") end 265 if chunk == "" then return try_sending(data, "0\r\n\r\n") end
273 -- else send middle chunk 266 -- else send middle chunk
@@ -281,22 +274,18 @@ local function send_body_bychunks(data, send_cb)
281end 274end
282 275
283----------------------------------------------------------------------------- 276-----------------------------------------------------------------------------
284-- Sends data comming from a callback 277-- Sends the HTTP request message body
285-- Input 278-- Input
286-- data: data connection 279-- data: data connection
287-- send_cb: callback to produce body contents 280-- source: request message body source
288-- Returns 281-- Returns
289-- nil if successfull, or an error message in case of error 282-- nil if successfull, or an error message in case of error
290----------------------------------------------------------------------------- 283-----------------------------------------------------------------------------
291local function send_body_bylength(data, send_cb) 284local function send_body(data, source)
292 while 1 do 285 while 1 do
293 local chunk, err_or_f = send_cb() 286 local chunk, cb_err = source()
294 -- check if callback aborted
295 if not chunk then return err_or_f or "aborted by callback" end
296 -- check if callback should be replaced
297 send_cb = err_or_f or send_cb
298 -- check if callback is done 287 -- check if callback is done
299 if chunk == "" then return end 288 if not chunk then return cb_err end
300 -- send data 289 -- send data
301 local err = try_sending(data, chunk) 290 local err = try_sending(data, chunk)
302 if err then return err end 291 if err then return err end
@@ -304,10 +293,10 @@ local function send_body_bylength(data, send_cb)
304end 293end
305 294
306----------------------------------------------------------------------------- 295-----------------------------------------------------------------------------
307-- Sends mime headers 296-- Sends request headers
308-- Input 297-- Input
309-- sock: server socket 298-- sock: server socket
310-- headers: table with mime headers to be sent 299-- headers: table with headers to be sent
311-- Returns 300-- Returns
312-- err: error message if any 301-- err: error message if any
313----------------------------------------------------------------------------- 302-----------------------------------------------------------------------------
@@ -330,27 +319,29 @@ end
330-- method: request method to be used 319-- method: request method to be used
331-- uri: request uri 320-- uri: request uri
332-- headers: request headers to be sent 321-- headers: request headers to be sent
333-- body_cb: callback to send request message body 322-- source: request message body source
334-- Returns 323-- Returns
335-- err: nil in case of success, error message otherwise 324-- err: nil in case of success, error message otherwise
336----------------------------------------------------------------------------- 325-----------------------------------------------------------------------------
337local function send_request(sock, method, uri, headers, body_cb) 326local function send_request(sock, method, uri, headers, source)
338 local chunk, size, done, err 327 local chunk, size, done, err
339 -- send request line 328 -- send request line
340 err = try_sending(sock, method .. " " .. uri .. " HTTP/1.1\r\n") 329 err = try_sending(sock, method .. " " .. uri .. " HTTP/1.1\r\n")
341 if err then return err end 330 if err then return err end
342 if body_cb and not headers["content-length"] then 331 if source and not headers["content-length"] then
343 headers["transfer-encoding"] = "chunked" 332 headers["transfer-encoding"] = "chunked"
344 end 333 end
345 -- send request headers 334 -- send request headers
346 err = send_headers(sock, headers) 335 err = send_headers(sock, headers)
347 if err then return err end 336 if err then return err end
348 -- send request message body, if any 337 -- send request message body, if any
349 if body_cb then 338 if source then
350 if not headers["content-length"] then 339 -- make sure source is not fancy
351 return send_body_bychunks(sock, body_cb) 340 source = ltn12.source.simplify(source)
341 if headers["content-length"] then
342 return send_body(sock, source)
352 else 343 else
353 return send_body_bylength(sock, body_cb) 344 return send_body_bychunks(sock, source)
354 end 345 end
355 end 346 end
356end 347end
@@ -415,23 +406,23 @@ end
415-- Input 406-- Input
416-- reqt: a table with the original request information 407-- reqt: a table with the original request information
417-- parsed: parsed request URL 408-- parsed: parsed request URL
418-- respt: a table with the server response information
419-- Returns 409-- Returns
420-- respt: result of target authorization 410-- respt: result of target authorization
421----------------------------------------------------------------------------- 411-----------------------------------------------------------------------------
422local function authorize(reqt, parsed, respt) 412local function authorize(reqt, parsed)
423 reqt.headers["authorization"] = "Basic " .. 413 reqt.headers["authorization"] = "Basic " ..
424 (socket.mime.b64(parsed.user .. ":" .. parsed.password)) 414 (mime.b64(parsed.user .. ":" .. parsed.password))
425 local autht = { 415 local autht = {
426 nredirects = reqt.nredirects, 416 nredirects = reqt.nredirects,
427 method = reqt.method, 417 method = reqt.method,
428 url = reqt.url, 418 url = reqt.url,
429 body_cb = reqt.body_cb, 419 source = reqt.source,
420 sink = reqt.sink,
430 headers = reqt.headers, 421 headers = reqt.headers,
431 timeout = reqt.timeout, 422 timeout = reqt.timeout,
432 proxy = reqt.proxy, 423 proxy = reqt.proxy,
433 } 424 }
434 return request_cb(autht, respt) 425 return request_cb(autht)
435end 426end
436 427
437----------------------------------------------------------------------------- 428-----------------------------------------------------------------------------
@@ -443,8 +434,8 @@ end
443-- 1 if we should redirect, nil otherwise 434-- 1 if we should redirect, nil otherwise
444----------------------------------------------------------------------------- 435-----------------------------------------------------------------------------
445local function should_redirect(reqt, respt) 436local function should_redirect(reqt, respt)
446 return (reqt.redirect ~= false) and 437 return (reqt.redirect ~= false) and
447 (respt.code == 301 or respt.code == 302) and 438 (respt.code == 301 or respt.code == 302) and
448 (reqt.method == "GET" or reqt.method == "HEAD") and 439 (reqt.method == "GET" or reqt.method == "HEAD") and
449 not (reqt.nredirects and reqt.nredirects >= 5) 440 not (reqt.nredirects and reqt.nredirects >= 5)
450end 441end
@@ -453,8 +444,7 @@ end
453-- Returns the result of a request following a server redirect message. 444-- Returns the result of a request following a server redirect message.
454-- Input 445-- Input
455-- reqt: a table with the original request information 446-- reqt: a table with the original request information
456-- respt: a table with the following fields: 447-- respt: response table of previous attempt
457-- body_cb: response method body receive-callback
458-- Returns 448-- Returns
459-- respt: result of target redirection 449-- respt: result of target redirection
460----------------------------------------------------------------------------- 450-----------------------------------------------------------------------------
@@ -467,12 +457,13 @@ local function redirect(reqt, respt)
467 -- the RFC says the redirect URL has to be absolute, but some 457 -- the RFC says the redirect URL has to be absolute, but some
468 -- servers do not respect that 458 -- servers do not respect that
469 url = socket.url.absolute(reqt.url, respt.headers["location"]), 459 url = socket.url.absolute(reqt.url, respt.headers["location"]),
470 body_cb = reqt.body_cb, 460 source = reqt.source,
461 sink = reqt.sink,
471 headers = reqt.headers, 462 headers = reqt.headers,
472 timeout = reqt.timeout, 463 timeout = reqt.timeout,
473 proxy = reqt.proxy 464 proxy = reqt.proxy
474 } 465 }
475 respt = request_cb(redirt, respt) 466 respt = request_cb(redirt)
476 -- we pass the location header as a clue we tried to redirect 467 -- we pass the location header as a clue we tried to redirect
477 if respt.headers then respt.headers.location = redirt.url end 468 if respt.headers then respt.headers.location = redirt.url end
478 return respt 469 return respt
@@ -562,10 +553,9 @@ end
562-- url: target uniform resource locator 553-- url: target uniform resource locator
563-- user, password: authentication information 554-- user, password: authentication information
564-- headers: request headers to send, or nil if none 555-- headers: request headers to send, or nil if none
565-- body_cb: request message body send-callback, or nil if none 556-- source: request message body source, or nil if none
557-- sink: response message body sink
566-- redirect: should we refrain from following a server redirect message? 558-- redirect: should we refrain from following a server redirect message?
567-- respt: a table with the following fields:
568-- body_cb: response method body receive-callback
569-- Returns 559-- Returns
570-- respt: a table with the following fields: 560-- respt: a table with the following fields:
571-- headers: response header fields received, or nil if failed 561-- headers: response header fields received, or nil if failed
@@ -573,7 +563,7 @@ end
573-- code: server status code, or nil if failed 563-- code: server status code, or nil if failed
574-- error: error message, or nil if successfull 564-- error: error message, or nil if successfull
575----------------------------------------------------------------------------- 565-----------------------------------------------------------------------------
576function request_cb(reqt, respt) 566function request_cb(reqt)
577 local sock, ret 567 local sock, ret
578 local parsed = socket.url.parse(reqt.url, { 568 local parsed = socket.url.parse(reqt.url, {
579 host = "", 569 host = "",
@@ -581,6 +571,7 @@ function request_cb(reqt, respt)
581 path ="/", 571 path ="/",
582 scheme = "http" 572 scheme = "http"
583 }) 573 })
574 local respt = {}
584 if parsed.scheme ~= "http" then 575 if parsed.scheme ~= "http" then
585 respt.error = string.format("unknown scheme '%s'", parsed.scheme) 576 respt.error = string.format("unknown scheme '%s'", parsed.scheme)
586 return respt 577 return respt
@@ -597,7 +588,7 @@ function request_cb(reqt, respt)
597 if not sock then return respt end 588 if not sock then return respt end
598 -- send request message 589 -- send request message
599 respt.error = send_request(sock, reqt.method, 590 respt.error = send_request(sock, reqt.method,
600 request_uri(reqt, parsed), reqt.headers, reqt.body_cb) 591 request_uri(reqt, parsed), reqt.headers, reqt.source)
601 if respt.error then 592 if respt.error then
602 sock:close() 593 sock:close()
603 return respt 594 return respt
@@ -619,18 +610,18 @@ function request_cb(reqt, respt)
619 -- decide what to do based on request and response parameters 610 -- decide what to do based on request and response parameters
620 if should_redirect(reqt, respt) then 611 if should_redirect(reqt, respt) then
621 -- drop the body 612 -- drop the body
622 receive_body(sock, respt.headers, function (c, e) return 1 end) 613 receive_body(sock, respt.headers, ltn12.sink.null())
623 -- we are done with this connection 614 -- we are done with this connection
624 sock:close() 615 sock:close()
625 return redirect(reqt, respt) 616 return redirect(reqt, respt)
626 elseif should_authorize(reqt, parsed, respt) then 617 elseif should_authorize(reqt, parsed, respt) then
627 -- drop the body 618 -- drop the body
628 receive_body(sock, respt.headers, function (c, e) return 1 end) 619 receive_body(sock, respt.headers, ltn12.sink.null())
629 -- we are done with this connection 620 -- we are done with this connection
630 sock:close() 621 sock:close()
631 return authorize(reqt, parsed, respt) 622 return authorize(reqt, parsed, respt)
632 elseif should_receive_body(reqt, respt) then 623 elseif should_receive_body(reqt, respt) then
633 respt.error = receive_body(sock, respt.headers, respt.body_cb) 624 respt.error = receive_body(sock, respt.headers, reqt.sink)
634 if respt.error then return respt end 625 if respt.error then return respt end
635 sock:close() 626 sock:close()
636 return respt 627 return respt
@@ -658,13 +649,11 @@ end
658-- error: error message if any 649-- error: error message if any
659----------------------------------------------------------------------------- 650-----------------------------------------------------------------------------
660function request(reqt) 651function request(reqt)
661 local respt = {} 652 reqt.source = reqt.body and ltn12.source.string(reqt.body)
662 reqt.body_cb = socket.callback.send.string(reqt.body) 653 local t = {}
663 local concat = socket.concat.create() 654 reqt.sink = ltn12.sink.table(t)
664 respt.body_cb = socket.callback.receive.concat(concat) 655 local respt = request_cb(reqt)
665 respt = request_cb(reqt, respt) 656 if table.getn(t) > 0 then respt.body = table.concat(t) end
666 respt.body = concat:getresult()
667 respt.body_cb = nil
668 return respt 657 return respt
669end 658end
670 659
@@ -713,4 +702,4 @@ function post(url_or_request, body)
713 return respt.body, respt.headers, respt.code, respt.error 702 return respt.body, respt.headers, respt.code, respt.error
714end 703end
715 704
716return http 705return socket.http
diff --git a/src/ltn12.lua b/src/ltn12.lua
index 548588a..de7103d 100644
--- a/src/ltn12.lua
+++ b/src/ltn12.lua
@@ -1,6 +1,6 @@
1-- create code namespace inside LuaSocket namespace 1-- create module namespace
2ltn12 = ltn12 or {} 2ltn12 = ltn12 or {}
3-- make all module globals fall into mime namespace 3-- make all globals fall into ltn12 namespace
4setmetatable(ltn12, { __index = _G }) 4setmetatable(ltn12, { __index = _G })
5setfenv(1, ltn12) 5setfenv(1, ltn12)
6 6
@@ -12,6 +12,14 @@ sink = {}
12-- 2048 seems to be better in windows... 12-- 2048 seems to be better in windows...
13BLOCKSIZE = 2048 13BLOCKSIZE = 2048
14 14
15local function second(a, b)
16 return b
17end
18
19local function skip(a, b, c)
20 return b, c
21end
22
15-- returns a high level filter that cycles a cycles a low-level filter 23-- returns a high level filter that cycles a cycles a low-level filter
16function filter.cycle(low, ctx, extra) 24function filter.cycle(low, ctx, extra)
17 return function(chunk) 25 return function(chunk)
@@ -24,9 +32,7 @@ end
24-- chains two filters together 32-- chains two filters together
25local function chain2(f1, f2) 33local function chain2(f1, f2)
26 return function(chunk) 34 return function(chunk)
27 local ret = f2(f1(chunk)) 35 return f2(f1(chunk))
28 if chunk then return ret
29 else return ret .. f2() end
30 end 36 end
31end 37end
32 38
@@ -83,7 +89,6 @@ end
83-- creates rewindable source 89-- creates rewindable source
84function source.rewind(src) 90function source.rewind(src)
85 local t = {} 91 local t = {}
86 src = source.simplify(src)
87 return function(chunk) 92 return function(chunk)
88 if not chunk then 93 if not chunk then
89 chunk = table.remove(t) 94 chunk = table.remove(t)
@@ -97,13 +102,38 @@ end
97 102
98-- chains a source with a filter 103-- chains a source with a filter
99function source.chain(src, f) 104function source.chain(src, f)
100 src = source.simplify(src) 105 local co = coroutine.create(function()
101 local chain = function() 106 while true do
102 local chunk, err = src() 107 local chunk, err = src()
103 if not chunk then return f(nil), source.empty(err) 108 local filtered = f(chunk)
104 else return f(chunk) end 109 local done = chunk and ""
110 while true do
111 coroutine.yield(filtered)
112 if filtered == done then break end
113 filtered = f(done)
114 end
115 if not chunk then return nil, err end
116 end
117 end)
118 return function()
119 return skip(coroutine.resume(co))
105 end 120 end
106 return source.simplify(chain) 121end
122
123-- creates a source that produces contents of several files one after the
124-- other, as if they were concatenated
125function source.cat(...)
126 local co = coroutine.create(function()
127 local i = 1
128 while i <= table.getn(arg) do
129 local chunk = arg[i]:read(2048)
130 if chunk then coroutine.yield(chunk)
131 else i = i + 1 end
132 end
133 end)
134 return source.simplify(function()
135 return second(coroutine.resume(co))
136 end)
107end 137end
108 138
109-- creates a sink that stores into a table 139-- creates a sink that stores into a table
@@ -150,22 +180,25 @@ end
150 180
151-- chains a sink with a filter 181-- chains a sink with a filter
152function sink.chain(f, snk) 182function sink.chain(f, snk)
153 snk = sink.simplify(snk)
154 return function(chunk, err) 183 return function(chunk, err)
155 local r, e = snk(f(chunk)) 184 local filtered = f(chunk)
156 if not r then return nil, e end 185 local done = chunk and ""
157 if not chunk then return snk(nil, err) end 186 while true do
158 return 1 187 local ret, snkerr = snk(filtered, err)
188 if not ret then return nil, snkerr end
189 if filtered == done then return 1 end
190 filtered = f(done)
191 end
159 end 192 end
160end 193end
161 194
162-- pumps all data from a source to a sink 195-- pumps all data from a source to a sink
163function pump(src, snk) 196function pump(src, snk)
164 snk = sink.simplify(snk) 197 while true do
165 for chunk, src_err in source.simplify(src) do 198 local chunk, src_err = src()
166 local ret, snk_err = snk(chunk, src_err) 199 local ret, snk_err = snk(chunk, src_err)
167 if not chunk or not ret then 200 if not chunk or not ret then
168 return not src_err and not snk_err, src_err or snk_err 201 return not src_err and not snk_err, src_err or snk_err
169 end 202 end
170 end 203 end
171end 204end
diff --git a/src/luasocket.c b/src/luasocket.c
index 47696cb..5b19696 100644
--- a/src/luasocket.c
+++ b/src/luasocket.c
@@ -74,22 +74,16 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
74#ifdef LUASOCKET_COMPILED 74#ifdef LUASOCKET_COMPILED
75#include "ltn12.lch" 75#include "ltn12.lch"
76#include "auxiliar.lch" 76#include "auxiliar.lch"
77#include "concat.lch"
78#include "url.lch" 77#include "url.lch"
79#include "callback.lch"
80#include "mime.lch" 78#include "mime.lch"
81#include "smtp.lch" 79#include "smtp.lch"
82#include "ftp.lch"
83#include "http.lch" 80#include "http.lch"
84#else 81#else
85 lua_dofile(L, "ltn12.lua"); 82 lua_dofile(L, "ltn12.lua");
86 lua_dofile(L, "auxiliar.lua"); 83 lua_dofile(L, "auxiliar.lua");
87 lua_dofile(L, "concat.lua");
88 lua_dofile(L, "url.lua"); 84 lua_dofile(L, "url.lua");
89 lua_dofile(L, "callback.lua");
90 lua_dofile(L, "mime.lua"); 85 lua_dofile(L, "mime.lua");
91 lua_dofile(L, "smtp.lua"); 86 lua_dofile(L, "smtp.lua");
92 lua_dofile(L, "ftp.lua");
93 lua_dofile(L, "http.lua"); 87 lua_dofile(L, "http.lua");
94#endif 88#endif
95 return 0; 89 return 0;
diff --git a/src/mime.c b/src/mime.c
index 1a8bff4..77f3ae1 100644
--- a/src/mime.c
+++ b/src/mime.c
@@ -20,8 +20,8 @@
20#define SP 0x20 20#define SP 0x20
21 21
22typedef unsigned char UC; 22typedef unsigned char UC;
23static const char CRLF[2] = {CR, LF}; 23static const char CRLF[] = {CR, LF, 0};
24static const char EQCRLF[3] = {'=', CR, LF}; 24static const char EQCRLF[] = {'=', CR, LF, 0};
25 25
26/*=========================================================================*\ 26/*=========================================================================*\
27* Internal function prototypes. 27* Internal function prototypes.
@@ -95,7 +95,7 @@ int mime_open(lua_State *L)
95* Global Lua functions 95* Global Lua functions
96\*=========================================================================*/ 96\*=========================================================================*/
97/*-------------------------------------------------------------------------*\ 97/*-------------------------------------------------------------------------*\
98* Incrementaly breaks a string into lines 98* Incrementaly breaks a string into lines. The string can have CRLF breaks.
99* A, n = wrp(l, B, length) 99* A, n = wrp(l, B, length)
100* A is a copy of B, broken into lines of at most 'length' bytes. 100* A is a copy of B, broken into lines of at most 'length' bytes.
101* 'l' is how many bytes are left for the first line of B. 101* 'l' is how many bytes are left for the first line of B.
@@ -109,6 +109,15 @@ static int mime_global_wrp(lua_State *L)
109 const UC *last = input + size; 109 const UC *last = input + size;
110 int length = (int) luaL_optnumber(L, 3, 76); 110 int length = (int) luaL_optnumber(L, 3, 76);
111 luaL_Buffer buffer; 111 luaL_Buffer buffer;
112 /* end of input black-hole */
113 if (!input) {
114 /* if last line has not been terminated, add a line break */
115 if (left < length) lua_pushstring(L, CRLF);
116 /* otherwise, we are done */
117 else lua_pushnil(L);
118 lua_pushnumber(L, length);
119 return 2;
120 }
112 luaL_buffinit(L, &buffer); 121 luaL_buffinit(L, &buffer);
113 while (input < last) { 122 while (input < last) {
114 switch (*input) { 123 switch (*input) {
@@ -129,11 +138,6 @@ static int mime_global_wrp(lua_State *L)
129 } 138 }
130 input++; 139 input++;
131 } 140 }
132 /* if in last chunk and last line wasn't terminated, add a line-break */
133 if (!input && left < length) {
134 luaL_addstring(&buffer, CRLF);
135 left = length;
136 }
137 luaL_pushresult(&buffer); 141 luaL_pushresult(&buffer);
138 lua_pushnumber(L, left); 142 lua_pushnumber(L, left);
139 return 2; 143 return 2;
@@ -200,7 +204,6 @@ static size_t b64pad(const UC *input, size_t size,
200 code[0] = b64base[value]; 204 code[0] = b64base[value];
201 luaL_addlstring(buffer, (char *) code, 4); 205 luaL_addlstring(buffer, (char *) code, 4);
202 break; 206 break;
203 case 0: /* fall through */
204 default: 207 default:
205 break; 208 break;
206 } 209 }
@@ -250,19 +253,31 @@ static int mime_global_b64(lua_State *L)
250{ 253{
251 UC atom[3]; 254 UC atom[3];
252 size_t isize = 0, asize = 0; 255 size_t isize = 0, asize = 0;
253 const UC *input = (UC *) luaL_checklstring(L, 1, &isize); 256 const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
254 const UC *last = input + isize; 257 const UC *last = input + isize;
255 luaL_Buffer buffer; 258 luaL_Buffer buffer;
259 /* end-of-input blackhole */
260 if (!input) {
261 lua_pushnil(L);
262 lua_pushnil(L);
263 return 2;
264 }
265 /* process first part of the input */
256 luaL_buffinit(L, &buffer); 266 luaL_buffinit(L, &buffer);
257 while (input < last) 267 while (input < last)
258 asize = b64encode(*input++, atom, asize, &buffer); 268 asize = b64encode(*input++, atom, asize, &buffer);
259 input = (UC *) luaL_optlstring(L, 2, NULL, &isize); 269 input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
260 if (input) { 270 /* if second part is nil, we are done */
261 last = input + isize; 271 if (!input) {
262 while (input < last)
263 asize = b64encode(*input++, atom, asize, &buffer);
264 } else
265 asize = b64pad(atom, asize, &buffer); 272 asize = b64pad(atom, asize, &buffer);
273 luaL_pushresult(&buffer);
274 lua_pushnil(L);
275 return 2;
276 }
277 /* otherwise process the second part */
278 last = input + isize;
279 while (input < last)
280 asize = b64encode(*input++, atom, asize, &buffer);
266 luaL_pushresult(&buffer); 281 luaL_pushresult(&buffer);
267 lua_pushlstring(L, (char *) atom, asize); 282 lua_pushlstring(L, (char *) atom, asize);
268 return 2; 283 return 2;
@@ -278,20 +293,30 @@ static int mime_global_unb64(lua_State *L)
278{ 293{
279 UC atom[4]; 294 UC atom[4];
280 size_t isize = 0, asize = 0; 295 size_t isize = 0, asize = 0;
281 const UC *input = (UC *) luaL_checklstring(L, 1, &isize); 296 const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
282 const UC *last = input + isize; 297 const UC *last = input + isize;
283 luaL_Buffer buffer; 298 luaL_Buffer buffer;
299 /* end-of-input blackhole */
300 if (!input) {
301 lua_pushnil(L);
302 lua_pushnil(L);
303 return 2;
304 }
305 /* process first part of the input */
284 luaL_buffinit(L, &buffer); 306 luaL_buffinit(L, &buffer);
285 while (input < last) 307 while (input < last)
286 asize = b64decode(*input++, atom, asize, &buffer); 308 asize = b64decode(*input++, atom, asize, &buffer);
287 input = (UC *) luaL_optlstring(L, 2, NULL, &isize); 309 input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
288 if (input) { 310 /* if second is nil, we are done */
289 last = input + isize; 311 if (!input) {
290 while (input < last) 312 luaL_pushresult(&buffer);
291 asize = b64decode(*input++, atom, asize, &buffer); 313 lua_pushnil(L);
292 } 314 return 2;
293 /* if !input we are done. if atom > 0, the remaning is invalid. we just 315 }
294 * return it undecoded. */ 316 /* otherwise, process the rest of the input */
317 last = input + isize;
318 while (input < last)
319 asize = b64decode(*input++, atom, asize, &buffer);
295 luaL_pushresult(&buffer); 320 luaL_pushresult(&buffer);
296 lua_pushlstring(L, (char *) atom, asize); 321 lua_pushlstring(L, (char *) atom, asize);
297 return 2; 322 return 2;
@@ -425,16 +450,27 @@ static int mime_global_qp(lua_State *L)
425 const UC *last = input + isize; 450 const UC *last = input + isize;
426 const char *marker = luaL_optstring(L, 3, CRLF); 451 const char *marker = luaL_optstring(L, 3, CRLF);
427 luaL_Buffer buffer; 452 luaL_Buffer buffer;
453 /* end-of-input blackhole */
454 if (!input) {
455 lua_pushnil(L);
456 lua_pushnil(L);
457 return 2;
458 }
459 /* process first part of input */
428 luaL_buffinit(L, &buffer); 460 luaL_buffinit(L, &buffer);
429 while (input < last) 461 while (input < last)
430 asize = qpencode(*input++, atom, asize, marker, &buffer); 462 asize = qpencode(*input++, atom, asize, marker, &buffer);
431 input = (UC *) luaL_optlstring(L, 2, NULL, &isize); 463 input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
432 if (input) { 464 /* if second part is nil, we are done */
433 last = input + isize; 465 if (!input) {
434 while (input < last)
435 asize = qpencode(*input++, atom, asize, marker, &buffer);
436 } else
437 asize = qppad(atom, asize, &buffer); 466 asize = qppad(atom, asize, &buffer);
467 luaL_pushresult(&buffer);
468 lua_pushnil(L);
469 }
470 /* otherwise process rest of input */
471 last = input + isize;
472 while (input < last)
473 asize = qpencode(*input++, atom, asize, marker, &buffer);
438 luaL_pushresult(&buffer); 474 luaL_pushresult(&buffer);
439 lua_pushlstring(L, (char *) atom, asize); 475 lua_pushlstring(L, (char *) atom, asize);
440 return 2; 476 return 2;
@@ -487,21 +523,32 @@ static size_t qpdecode(UC c, UC *input, size_t size,
487\*-------------------------------------------------------------------------*/ 523\*-------------------------------------------------------------------------*/
488static int mime_global_unqp(lua_State *L) 524static int mime_global_unqp(lua_State *L)
489{ 525{
490
491 size_t asize = 0, isize = 0; 526 size_t asize = 0, isize = 0;
492 UC atom[3]; 527 UC atom[3];
493 const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize); 528 const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
494 const UC *last = input + isize; 529 const UC *last = input + isize;
495 luaL_Buffer buffer; 530 luaL_Buffer buffer;
531 /* end-of-input blackhole */
532 if (!input) {
533 lua_pushnil(L);
534 lua_pushnil(L);
535 return 2;
536 }
537 /* process first part of input */
496 luaL_buffinit(L, &buffer); 538 luaL_buffinit(L, &buffer);
497 while (input < last) 539 while (input < last)
498 asize = qpdecode(*input++, atom, asize, &buffer); 540 asize = qpdecode(*input++, atom, asize, &buffer);
499 input = (UC *) luaL_optlstring(L, 2, NULL, &isize); 541 input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
500 if (input) { 542 /* if second part is nil, we are done */
501 last = input + isize; 543 if (!input) {
502 while (input < last) 544 luaL_pushresult(&buffer);
503 asize = qpdecode(*input++, atom, asize, &buffer); 545 lua_pushnil(L);
546 return 2;
504 } 547 }
548 /* otherwise process rest of input */
549 last = input + isize;
550 while (input < last)
551 asize = qpdecode(*input++, atom, asize, &buffer);
505 luaL_pushresult(&buffer); 552 luaL_pushresult(&buffer);
506 lua_pushlstring(L, (char *) atom, asize); 553 lua_pushlstring(L, (char *) atom, asize);
507 return 2; 554 return 2;
@@ -524,6 +571,14 @@ static int mime_global_qpwrp(lua_State *L)
524 const UC *last = input + size; 571 const UC *last = input + size;
525 int length = (int) luaL_optnumber(L, 3, 76); 572 int length = (int) luaL_optnumber(L, 3, 76);
526 luaL_Buffer buffer; 573 luaL_Buffer buffer;
574 /* end-of-input blackhole */
575 if (!input) {
576 if (left < length) lua_pushstring(L, EQCRLF);
577 else lua_pushnil(L);
578 lua_pushnumber(L, length);
579 return 2;
580 }
581 /* process all input */
527 luaL_buffinit(L, &buffer); 582 luaL_buffinit(L, &buffer);
528 while (input < last) { 583 while (input < last) {
529 switch (*input) { 584 switch (*input) {
@@ -552,11 +607,6 @@ static int mime_global_qpwrp(lua_State *L)
552 } 607 }
553 input++; 608 input++;
554 } 609 }
555 /* if in last chunk and last line wasn't terminated, add a soft-break */
556 if (!input && left < length) {
557 luaL_addstring(&buffer, EQCRLF);
558 left = length;
559 }
560 luaL_pushresult(&buffer); 610 luaL_pushresult(&buffer);
561 lua_pushnumber(L, left); 611 lua_pushnumber(L, left);
562 return 2; 612 return 2;
@@ -609,13 +659,16 @@ static int mime_global_eol(lua_State *L)
609 const char *marker = luaL_optstring(L, 3, CRLF); 659 const char *marker = luaL_optstring(L, 3, CRLF);
610 luaL_Buffer buffer; 660 luaL_Buffer buffer;
611 luaL_buffinit(L, &buffer); 661 luaL_buffinit(L, &buffer);
612 while (input < last)
613 ctx = eolprocess(*input++, ctx, marker, &buffer);
614 /* if the last character was a candidate, we output a new line */ 662 /* if the last character was a candidate, we output a new line */
615 if (!input) { 663 if (!input) {
616 if (eolcandidate(ctx)) luaL_addstring(&buffer, marker); 664 if (eolcandidate(ctx)) lua_pushstring(L, marker);
617 ctx = 0; 665 else lua_pushnil(L);
666 lua_pushnumber(L, 0);
667 return 2;
618 } 668 }
669 /* process all input */
670 while (input < last)
671 ctx = eolprocess(*input++, ctx, marker, &buffer);
619 luaL_pushresult(&buffer); 672 luaL_pushresult(&buffer);
620 lua_pushnumber(L, ctx); 673 lua_pushnumber(L, ctx);
621 return 2; 674 return 2;
diff --git a/src/mime.lua b/src/mime.lua
index 4df0388..6db832d 100644
--- a/src/mime.lua
+++ b/src/mime.lua
@@ -1,4 +1,4 @@
1if not ltn12 then error('This module requires LTN12') end 1if not ltn12 then error('Requires LTN12 module') end
2-- create mime namespace 2-- create mime namespace
3mime = mime or {} 3mime = mime or {}
4-- make all module globals fall into mime namespace 4-- make all module globals fall into mime namespace
diff --git a/src/smtp.lua b/src/smtp.lua
index 8b65e44..6b02d14 100644
--- a/src/smtp.lua
+++ b/src/smtp.lua
@@ -19,7 +19,7 @@ DOMAIN = os.getenv("SERVER_NAME") or "localhost"
19SERVER = "localhost" 19SERVER = "localhost"
20 20
21function stuff() 21function stuff()
22 return socket.cicle(dot, 2) 22 return ltn12.filter.cycle(dot, 2)
23end 23end
24 24
25-- tries to get a pattern from the server and closes socket on error 25-- tries to get a pattern from the server and closes socket on error
diff --git a/src/tp.lua b/src/tp.lua
new file mode 100644
index 0000000..d8dabc0
--- /dev/null
+++ b/src/tp.lua
@@ -0,0 +1,111 @@
1-----------------------------------------------------------------------------
2-- Unified SMTP/FTP subsystem
3-- LuaSocket toolkit.
4-- Author: Diego Nehab
5-- Conforming to: RFC 2616, LTN7
6-- RCS ID: $Id$
7-----------------------------------------------------------------------------
8-- make sure LuaSocket is loaded
9if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
10-- get LuaSocket namespace
11local socket = _G[LUASOCKET_LIBNAME]
12if not socket then error('module requires LuaSocket') end
13-- create namespace inside LuaSocket namespace
14socket.tp = socket.tp or {}
15-- make all module globals fall into namespace
16setmetatable(socket.tp, { __index = _G })
17setfenv(1, socket.tp)
18
19TIMEOUT = 60
20
21-- tries to get a pattern from the server and closes socket on error
22local function try_receiving(sock, pattern)
23 local data, message = sock:receive(pattern)
24 if not data then sock:close() end
25 return data, message
26end
27
28-- tries to send data to server and closes socket on error
29local function try_sending(sock, data)
30 local sent, message = sock:send(data)
31 if not sent then sock:close() end
32 return sent, message
33end
34
35-- gets server reply
36local function get_reply(sock)
37 local code, current, separator, _
38 local line, message = try_receiving(sock)
39 local reply = line
40 if message then return nil, message end
41 _, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
42 if not code then return nil, "invalid server reply" end
43 if separator == "-" then -- reply is multiline
44 repeat
45 line, message = try_receiving(sock)
46 if message then return nil, message end
47 _,_, current, separator = string.find(line, "^(%d%d%d)(.)")
48 if not current or not separator then
49 return nil, "invalid server reply"
50 end
51 reply = reply .. "\n" .. line
52 -- reply ends with same code
53 until code == current and separator == " "
54 end
55 return code, reply
56end
57
58-- metatable for sock object
59local metatable = { __index = {} }
60
61-- execute the "check" instr
62function metatable.__index:check(ok)
63 local code, reply = get_reply(self.sock)
64 if not code then return nil, reply end
65 if type(ok) ~= "function" then
66 if type(ok) ~= "table" then ok = {ok} end
67 for i, v in ipairs(ok) do
68 if string.find(code, v) then return code, reply end
69 end
70 return nil, reply
71 else return ok(code, reply) end
72end
73
74function metatable.__index:cmdchk(cmd, arg, ok)
75 local code, err = self:command(cmd, arg)
76 if not code then return nil, err end
77 return self:check(ok)
78end
79
80-- execute the "command" instr
81function metatable.__index:command(cmd, arg)
82 if arg then return try_sending(self.sock, cmd .. " " .. arg.. "\r\n")
83 return try_sending(self.sock, cmd .. "\r\n") end
84end
85
86function metatable.__index:sink(snk, pat)
87 local chunk, err = sock:receive(pat)
88 return snk(chunk, err)
89end
90
91function metatable.__index:source(src, instr)
92 while true do
93 local chunk, err = src()
94 if not chunk then return not err, err end
95 local ret, err = try_sending(self.sock, chunk)
96 if not ret then return nil, err end
97 end
98end
99
100-- closes the underlying sock
101function metatable.__index:close()
102 self.sock:close()
103end
104
105-- connect with server and return sock object
106function connect(host, port)
107 local sock, err = socket.connect(host, port)
108 if not sock then return nil, message end
109 sock:settimeout(TIMEOUT)
110 return setmetatable({sock = sock}, metatable)
111end
diff --git a/src/wsocket.c b/src/wsocket.c
index 2993c35..af3f8d8 100644
--- a/src/wsocket.c
+++ b/src/wsocket.c
@@ -269,7 +269,7 @@ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout)
269 fd_set fds; 269 fd_set fds;
270 int ret; 270 int ret;
271 *got = 0; 271 *got = 0;
272 if (taken == 0) return IO_CLOSED; 272 if (taken == 0 || WSAGetLastError() != WSAEWOULDBLOCK) return IO_CLOSED;
273 FD_ZERO(&fds); 273 FD_ZERO(&fds);
274 FD_SET(sock, &fds); 274 FD_SET(sock, &fds);
275 ret = sock_select(0, &fds, NULL, NULL, timeout); 275 ret = sock_select(0, &fds, NULL, NULL, timeout);
@@ -295,7 +295,7 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got,
295 fd_set fds; 295 fd_set fds;
296 int ret; 296 int ret;
297 *got = 0; 297 *got = 0;
298 if (taken == 0) return IO_CLOSED; 298 if (taken == 0 || WSAGetLastError() != WSAEWOULDBLOCK) return IO_CLOSED;
299 FD_ZERO(&fds); 299 FD_ZERO(&fds);
300 FD_SET(sock, &fds); 300 FD_SET(sock, &fds);
301 ret = sock_select(0, &fds, NULL, NULL, timeout); 301 ret = sock_select(0, &fds, NULL, NULL, timeout);
diff --git a/test/httptest.lua b/test/httptest.lua
index c9a74a8..04c0ed0 100644
--- a/test/httptest.lua
+++ b/test/httptest.lua
@@ -12,7 +12,7 @@ socket.http.TIMEOUT = 5
12 12
13local t = socket.time() 13local t = socket.time()
14 14
15host = host or "diego-interface2.student.dyn.CS.Princeton.EDU" 15host = host or "diego.student.princeton.edu"
16proxy = proxy or "http://localhost:3128" 16proxy = proxy or "http://localhost:3128"
17prefix = prefix or "/luasocket-test" 17prefix = prefix or "/luasocket-test"
18cgiprefix = cgiprefix or "/luasocket-test-cgi" 18cgiprefix = cgiprefix or "/luasocket-test-cgi"
@@ -71,8 +71,8 @@ local check_request = function(request, expect, ignore)
71 check_result(response, expect, ignore) 71 check_result(response, expect, ignore)
72end 72end
73 73
74local check_request_cb = function(request, response, expect, ignore) 74local check_request_cb = function(request, expect, ignore)
75 local response = socket.http.request_cb(request, response) 75 local response = socket.http.request_cb(request)
76 check_result(response, expect, ignore) 76 check_result(response, expect, ignore)
77end 77end
78 78
@@ -83,7 +83,7 @@ local back, h, c, e = socket.http.get("http://" .. host .. forth)
83if not back then fail(e) end 83if not back then fail(e) end
84back = socket.url.parse(back) 84back = socket.url.parse(back)
85if similar(back.query, "this+is+the+query+string") then print("ok") 85if similar(back.query, "this+is+the+query+string") then print("ok")
86else fail() end 86else fail(back.query) end
87 87
88------------------------------------------------------------------------ 88------------------------------------------------------------------------
89io.write("testing query string correctness: ") 89io.write("testing query string correctness: ")
@@ -168,31 +168,28 @@ back = socket.http.post("http://" .. host .. cgiprefix .. "/cat", index)
168check(back == index) 168check(back == index)
169 169
170------------------------------------------------------------------------ 170------------------------------------------------------------------------
171io.write("testing send.file and receive.file callbacks: ") 171io.write("testing ltn12.(sink|source).file: ")
172request = { 172request = {
173 url = "http://" .. host .. cgiprefix .. "/cat", 173 url = "http://" .. host .. cgiprefix .. "/cat",
174 method = "POST", 174 method = "POST",
175 body_cb = socket.callback.send.file(io.open(index_file, "r")), 175 source = ltn12.source.file(io.open(index_file, "r")),
176 sink = ltn12.sink.file(io.open(index_file .. "-back", "w")),
176 headers = { ["content-length"] = string.len(index) } 177 headers = { ["content-length"] = string.len(index) }
177} 178}
178response = {
179 body_cb = socket.callback.receive.file(io.open(index_file .. "-back", "w"))
180}
181expect = { 179expect = {
182 code = 200 180 code = 200
183} 181}
184ignore = { 182ignore = {
185 body_cb = 1,
186 status = 1, 183 status = 1,
187 headers = 1 184 headers = 1
188} 185}
189check_request_cb(request, response, expect, ignore) 186check_request_cb(request, expect, ignore)
190back = readfile(index_file .. "-back") 187back = readfile(index_file .. "-back")
191check(back == index) 188check(back == index)
192os.remove(index_file .. "-back") 189os.remove(index_file .. "-back")
193 190
194------------------------------------------------------------------------ 191------------------------------------------------------------------------
195io.write("testing send.chain and receive.chain callbacks: ") 192io.write("testing ltn12.(sink|source).chain and mime.(encode|decode): ")
196 193
197local function b64length(len) 194local function b64length(len)
198 local a = math.ceil(len/3)*4 195 local a = math.ceil(len/3)*4
@@ -200,26 +197,26 @@ local function b64length(len)
200 return a + l*2 197 return a + l*2
201end 198end
202 199
203local req_cb = socket.callback.send.chain( 200local source = ltn12.source.chain(
204 socket.callback.send.file(io.open(index_file, "r")), 201 ltn12.source.file(io.open(index_file, "r")),
205 socket.mime.chain( 202 ltn12.filter.chain(
206 socket.mime.encode("base64"), 203 mime.encode("base64"),
207 socket.mime.wrap("base64") 204 mime.wrap("base64")
208 ) 205 )
209) 206)
210 207
211local resp_cb = socket.callback.receive.chain( 208local sink = ltn12.sink.chain(
212 socket.mime.decode("base64"), 209 mime.decode("base64"),
213 socket.callback.receive.file(io.open(index_file .. "-back", "w")) 210 ltn12.sink.file(io.open(index_file .. "-back", "w"))
214) 211)
215 212
216request = { 213request = {
217 url = "http://" .. host .. cgiprefix .. "/cat", 214 url = "http://" .. host .. cgiprefix .. "/cat",
218 method = "POST", 215 method = "POST",
219 body_cb = req_cb, 216 source = source,
217 sink = sink,
220 headers = { ["content-length"] = b64length(string.len(index)) } 218 headers = { ["content-length"] = b64length(string.len(index)) }
221} 219}
222response = { body_cb = resp_cb }
223expect = { 220expect = {
224 code = 200 221 code = 200
225} 222}
@@ -228,7 +225,7 @@ ignore = {
228 status = 1, 225 status = 1,
229 headers = 1 226 headers = 1
230} 227}
231check_request_cb(request, response, expect, ignore) 228check_request_cb(request, expect, ignore)
232back = readfile(index_file .. "-back") 229back = readfile(index_file .. "-back")
233check(back == index) 230check(back == index)
234os.remove(index_file .. "-back") 231os.remove(index_file .. "-back")
@@ -362,7 +359,7 @@ io.write("testing manual basic auth: ")
362request = { 359request = {
363 url = "http://" .. host .. prefix .. "/auth/index.html", 360 url = "http://" .. host .. prefix .. "/auth/index.html",
364 headers = { 361 headers = {
365 authorization = "Basic " .. (socket.mime.b64("luasocket:password")) 362 authorization = "Basic " .. (mime.b64("luasocket:password"))
366 } 363 }
367} 364}
368expect = { 365expect = {
diff --git a/test/mimetest.lua b/test/mimetest.lua
index 1a7e427..4a0a20a 100644
--- a/test/mimetest.lua
+++ b/test/mimetest.lua
@@ -31,18 +31,27 @@ local mao = [[
31 assim, nem tudo o que dava exprimia grande confiança. 31 assim, nem tudo o que dava exprimia grande confiança.
32]] 32]]
33 33
34local function random(handle, io_err)
35 if handle then
36 return function()
37 local chunk = handle:read(math.random(0, 1024))
38 if not chunk then handle:close() end
39 return chunk
40 end
41 else source.empty(io_err or "unable to open file") end
42end
43
44local what = nil
34local function transform(input, output, filter) 45local function transform(input, output, filter)
35 local fi, err = io.open(input, "rb") 46 local source = random(io.open(input, "rb"))
36 if not fi then fail(err) end 47 local sink = ltn12.sink.file(io.open(output, "wb"))
37 local fo, err = io.open(output, "wb") 48 if what then
38 if not fo then fail(err) end 49 sink = ltn12.sink.chain(filter, sink)
39 while 1 do 50 else
40 local chunk = fi:read(math.random(0, 1024)) 51 source = ltn12.source.chain(source, filter)
41 fo:write(filter(chunk)) 52 end
42 if not chunk then break end 53 --what = not what
43 end 54 ltn12.pump(source, sink)
44 fi:close()
45 fo:close()
46end 55end
47 56
48local function encode_qptest(mode) 57local function encode_qptest(mode)
diff --git a/test/stufftest.lua b/test/stufftest.lua
new file mode 100644
index 0000000..5eb8005
--- /dev/null
+++ b/test/stufftest.lua
@@ -0,0 +1,19 @@
1function test_dot(original, right)
2 local result, n = socket.smtp.dot(2, original)
3 assert(result == right, "->" .. result .. "<-")
4 print("ok")
5end
6
7function test_stuff(original, right)
8 local result, n = socket.smtp.dot(2, original)
9 assert(result == right, "->" .. result .. "<-")
10 print("ok")
11end
12
13test_dot("abc", "abc")
14test_dot("", "")
15test_dot("\r\n", "\r\n")
16test_dot("\r\n.", "\r\n..")
17test_dot(".\r\n.", "..\r\n..")
18test_dot(".\r\n.", "..\r\n..")
19test_dot("abcd.\r\n.", "abcd.\r\n..")