diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-03-16 06:42:53 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2004-03-16 06:42:53 +0000 |
commit | bcc0c2a9f0be2ca796ef5206a78e283fe15e6186 (patch) | |
tree | 65c269d4854aa5ff4a0b2c8eede5cdb18d716033 | |
parent | b6edaac2841137cf0ef5105f75358bbab4570d87 (diff) | |
download | luasocket-bcc0c2a9f0be2ca796ef5206a78e283fe15e6186.tar.gz luasocket-bcc0c2a9f0be2ca796ef5206a78e283fe15e6186.tar.bz2 luasocket-bcc0c2a9f0be2ca796ef5206a78e283fe15e6186.zip |
New filter scheme.
ltn12 and mime updated.
smtp/ftp broken.
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | etc/b64.lua | 17 | ||||
-rw-r--r-- | etc/get.lua | 47 | ||||
-rw-r--r-- | src/auxiliar.c | 11 | ||||
-rw-r--r-- | src/auxiliar.h | 1 | ||||
-rw-r--r-- | src/ftp.lua | 360 | ||||
-rw-r--r-- | src/http.lua | 219 | ||||
-rw-r--r-- | src/ltn12.lua | 75 | ||||
-rw-r--r-- | src/luasocket.c | 6 | ||||
-rw-r--r-- | src/mime.c | 137 | ||||
-rw-r--r-- | src/mime.lua | 2 | ||||
-rw-r--r-- | src/smtp.lua | 2 | ||||
-rw-r--r-- | src/tp.lua | 111 | ||||
-rw-r--r-- | src/wsocket.c | 4 | ||||
-rw-r--r-- | test/httptest.lua | 45 | ||||
-rw-r--r-- | test/mimetest.lua | 31 | ||||
-rw-r--r-- | test/stufftest.lua | 19 |
17 files changed, 565 insertions, 527 deletions
@@ -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 | ||
22 | colocar um userdata com gc metamethod pra chamar sock_close (WSAClose); | ||
23 | sources ans sinks are always simple in http and ftp and smtp | ||
24 | unify backbone of smtp and ftp | ||
25 | expose encode/decode tables to provide extensibility for mime module | ||
26 | use coroutines instead of fancy filters | ||
22 | unify filter and send/receive callback. new sink/source/pump idea. | 27 | unify filter and send/receive callback. new sink/source/pump idea. |
23 | get rid of aux_optlstring | 28 | get rid of aux_optlstring |
24 | wrap sink and sources with a function that performs the replacement | 29 | wrap 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 @@ | |||
1 | local source = ltn12.source.file(io.stdin) | ||
2 | local sink = ltn12.sink.file(io.stdout) | ||
1 | local convert | 3 | local convert |
2 | if arg and arg[1] == '-d' then | 4 | if arg and arg[1] == '-d' then |
3 | convert = socket.mime.decode("base64") | 5 | convert = mime.decode("base64") |
4 | else | 6 | else |
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) |
8 | end | ||
9 | while 1 do | ||
10 | local chunk = io.read(4096) | ||
11 | io.write(convert(chunk)) | ||
12 | if not chunk then break end | ||
13 | end | 10 | end |
11 | source = ltn12.source.chain(source, convert) | ||
12 | ltn12.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 |
81 | end | 81 | end |
82 | 82 | ||
83 | -- downloads a file using the ftp protocol | 83 | -- determines the size of a http file |
84 | function getbyftp(url, file) | 84 | function 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 | ||
95 | end | 89 | end |
96 | 90 | ||
97 | -- downloads a file using the http protocol | 91 | -- downloads a file using the http protocol |
98 | function getbyhttp(url, file) | 92 | function 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 | ||
105 | end | 98 | end |
106 | 99 | ||
107 | -- determines the size of a http file | 100 | -- downloads a file using the ftp protocol |
108 | function gethttpsize(url) | 101 | function 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 | ||
116 | end | 108 | end |
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 | ||
134 | end | 125 | end |
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 | \*-------------------------------------------------------------------------*/ | ||
165 | const 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); | |||
49 | void *aux_getclassudata(lua_State *L, const char *groupname, int objidx); | 49 | void *aux_getclassudata(lua_State *L, const char *groupname, int objidx); |
50 | void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx); | 50 | void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx); |
51 | int aux_checkboolean(lua_State *L, int objidx); | 51 | int aux_checkboolean(lua_State *L, int objidx); |
52 | const 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 | |
9 | local Public, Private = {}, {} | 9 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end |
10 | local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace | 10 | -- get LuaSocket namespace |
11 | socket.ftp = Public -- create ftp sub namespace | 11 | local socket = _G[LUASOCKET_LIBNAME] |
12 | if not socket then error('module requires LuaSocket') end | ||
13 | -- create namespace inside LuaSocket namespace | ||
14 | socket.ftp = socket.ftp or {} | ||
15 | -- make all module globals fall into namespace | ||
16 | setmetatable(socket.ftp, { __index = _G }) | ||
17 | setfenv(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 |
17 | Public.TIMEOUT = 60 | 23 | TIMEOUT = 60 |
18 | -- default port for ftp service | 24 | -- default port for ftp service |
19 | Public.PORT = 21 | 25 | PORT = 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. |
22 | Public.EMAIL = "anonymous@anonymous.org" | 28 | EMAIL = "anonymous@anonymous.org" |
23 | -- block size used in transfers | 29 | -- block size used in transfers |
24 | Public.BLOCKSIZE = 8192 | 30 | BLOCKSIZE = 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 | ----------------------------------------------------------------------------- | ||
34 | function Private.try_receive(sock, pattern) | ||
35 | local data, err = sock:receive(pattern) | ||
36 | if not data then sock:close() end | ||
37 | return data, err | ||
38 | end | ||
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 | ----------------------------------------------------------------------------- | ||
47 | function Private.try_send(sock, data) | ||
48 | local sent, err = sock:send(data) | ||
49 | if not sent then sock:close() end | ||
50 | return err | ||
51 | end | ||
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 | ----------------------------------------------------------------------------- | ||
61 | function Private.try_sendline(sock, line) | ||
62 | return Private.try_send(sock, line .. "\r\n") | ||
63 | end | ||
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 | ----------------------------------------------------------------------------- |
73 | function Private.get_pasv(pasv) | 40 | local 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) | |||
82 | end | 49 | end |
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 | ----------------------------------------------------------------------------- | ||
93 | function 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) | ||
98 | end | ||
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 | ----------------------------------------------------------------------------- | ||
108 | function 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) | ||
124 | end | ||
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 | ----------------------------------------------------------------------------- | ||
135 | function 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 | ||
146 | end | ||
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 | ----------------------------------------------------------------------------- | ||
160 | function 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) | ||
164 | end | ||
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 | ----------------------------------------------------------------------------- |
174 | function Private.greet(control) | 59 | local 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 |
180 | end | 65 | end |
@@ -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 | ----------------------------------------------------------------------------- |
192 | function Private.login(control, user, password) | 77 | local 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 |
198 | end | 83 | end |
@@ -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 | ----------------------------------------------------------------------------- |
209 | function Private.cwd(control, path) | 94 | local function cwd(control, path) |
210 | if path then return Private.command(control, "cwd", path, {250}) | ||
211 | else return 250, nil end | ||
212 | end | 95 | end |
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 | ----------------------------------------------------------------------------- |
222 | function Private.port(control) | 105 | local 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 | ----------------------------------------------------------------------------- |
248 | function Private.logout(control) | 131 | local 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 |
252 | end | 135 | end |
@@ -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 | ----------------------------------------------------------------------------- |
262 | function Private.receive_indirect(data, callback) | 145 | local 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 | ----------------------------------------------------------------------------- |
283 | function Private.retrieve(control, server, name, is_directory, content_cb) | 166 | local 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}) |
309 | end | ||
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 | ----------------------------------------------------------------------------- | ||
320 | function 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 | ||
338 | end | 192 | end |
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 | ----------------------------------------------------------------------------- |
351 | function Private.store(control, server, file, send_cb) | 205 | local 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}) |
375 | end | 229 | end |
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 | ----------------------------------------------------------------------------- |
385 | function Private.change_type(control, params) | 239 | local 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 |
392 | end | 246 | end |
@@ -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 | ----------------------------------------------------------------------------- |
402 | function Private.open(parsed) | 256 | local 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 | ||
415 | end | 284 | end |
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 | ----------------------------------------------------------------------------- | ||
422 | function Private.close(control) | ||
423 | -- disconnect | ||
424 | Private.logout(control) | ||
425 | end | 290 | end |
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 | ----------------------------------------------------------------------------- | ||
435 | function 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 | ||
441 | end | 292 | end |
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 | ----------------------------------------------------------------------------- |
453 | function Private.upload(control, request, segment) | 304 | local 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 |
468 | end | 319 | end |
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 | ----------------------------------------------------------------------------- |
480 | function Private.download(control, request, segment) | 331 | local 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 |
497 | end | 348 | end |
@@ -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 | ----------------------------------------------------------------------------- |
510 | function Private.parse_url(request) | 361 | local 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 | ----------------------------------------------------------------------------- |
534 | function Private.parse_path(parsed_url) | 384 | local 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 | ----------------------------------------------------------------------------- |
552 | function Private.build_request(data) | 402 | local 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 | ----------------------------------------------------------------------------- |
571 | function Public.get_cb(request) | 421 | function 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) |
583 | end | 433 | end |
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 | ----------------------------------------------------------------------------- |
597 | function Public.put_cb(request) | 447 | function 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 |
611 | end | 461 | end |
@@ -623,11 +473,11 @@ end | |||
623 | -- Returns | 473 | -- Returns |
624 | -- err: error message if any | 474 | -- err: error message if any |
625 | ----------------------------------------------------------------------------- | 475 | ----------------------------------------------------------------------------- |
626 | function Public.put(url_or_request, content) | 476 | function 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) |
631 | end | 481 | end |
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 | ----------------------------------------------------------------------------- |
645 | function Public.get(url_or_request) | 495 | function 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 |
651 | end | 501 | end |
652 | 502 | ||
653 | return ftp | 503 | return 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 |
11 | local socket = _G[LUASOCKET_LIBNAME] | 11 | local socket = _G[LUASOCKET_LIBNAME] |
12 | if not socket then error('module requires LuaSocket') end | 12 | if not socket then error('module requires LuaSocket') end |
13 | -- create smtp namespace inside LuaSocket namespace | 13 | -- create namespace inside LuaSocket namespace |
14 | local http = socket.http or {} | 14 | socket.http = socket.http or {} |
15 | socket.http = http | 15 | -- make all module globals fall into namespace |
16 | -- make all module globals fall into smtp namespace | 16 | setmetatable(socket.http, { __index = _G }) |
17 | setmetatable(http, { __index = _G }) | 17 | setfenv(1, socket.http) |
18 | setfenv(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 |
28 | USERAGENT = socket.version | 27 | USERAGENT = socket.version |
29 | -- block size used in transfers | 28 | -- block size used in transfers |
30 | BLOCKSIZE = 8192 | 29 | BLOCKSIZE = 2048 |
30 | |||
31 | ----------------------------------------------------------------------------- | ||
32 | -- Function return value selectors | ||
33 | ----------------------------------------------------------------------------- | ||
34 | local function second(a, b) | ||
35 | return b | ||
36 | end | ||
37 | |||
38 | local function third(a, b, c) | ||
39 | return c | ||
40 | end | ||
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 | ----------------------------------------------------------------------------- |
70 | local function receive_status(sock) | 80 | local 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 |
78 | end | 86 | end |
@@ -121,7 +129,7 @@ local function receive_headers(sock, headers) | |||
121 | end | 129 | end |
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 | ----------------------------------------------------------------------------- |
131 | local function abort(cb, err) | 139 | local 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 |
134 | end | 142 | end |
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 | ----------------------------------------------------------------------------- |
145 | local function receive_body_bychunks(sock, headers, receive_cb) | 153 | local 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 | ||
176 | end | 179 | end |
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 | ----------------------------------------------------------------------------- |
187 | local function receive_body_bylength(sock, length, receive_cb) | 190 | local 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 | ||
202 | end | 202 | end |
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 | ----------------------------------------------------------------------------- |
212 | local function receive_body_untilclosed(sock, receive_cb) | 212 | local 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 |
230 | end | 223 | end |
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 | ----------------------------------------------------------------------------- |
241 | local function receive_body(sock, headers, receive_cb) | 234 | local 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 |
254 | end | 249 | end |
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 | ----------------------------------------------------------------------------- |
264 | local function send_body_bychunks(data, send_cb) | 259 | local 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) | |||
281 | end | 274 | end |
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 | ----------------------------------------------------------------------------- |
291 | local function send_body_bylength(data, send_cb) | 284 | local 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) | |||
304 | end | 293 | end |
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 | ----------------------------------------------------------------------------- |
337 | local function send_request(sock, method, uri, headers, body_cb) | 326 | local 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 |
356 | end | 347 | end |
@@ -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 | ----------------------------------------------------------------------------- |
422 | local function authorize(reqt, parsed, respt) | 412 | local 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) |
435 | end | 426 | end |
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 | ----------------------------------------------------------------------------- |
445 | local function should_redirect(reqt, respt) | 436 | local 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) |
450 | end | 441 | end |
@@ -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 | ----------------------------------------------------------------------------- |
576 | function request_cb(reqt, respt) | 566 | function 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 | ----------------------------------------------------------------------------- |
660 | function request(reqt) | 651 | function 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 |
669 | end | 658 | end |
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 |
714 | end | 703 | end |
715 | 704 | ||
716 | return http | 705 | return 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 |
2 | ltn12 = ltn12 or {} | 2 | ltn12 = ltn12 or {} |
3 | -- make all module globals fall into mime namespace | 3 | -- make all globals fall into ltn12 namespace |
4 | setmetatable(ltn12, { __index = _G }) | 4 | setmetatable(ltn12, { __index = _G }) |
5 | setfenv(1, ltn12) | 5 | setfenv(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... |
13 | BLOCKSIZE = 2048 | 13 | BLOCKSIZE = 2048 |
14 | 14 | ||
15 | local function second(a, b) | ||
16 | return b | ||
17 | end | ||
18 | |||
19 | local function skip(a, b, c) | ||
20 | return b, c | ||
21 | end | ||
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 |
16 | function filter.cycle(low, ctx, extra) | 24 | function 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 |
25 | local function chain2(f1, f2) | 33 | local 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 |
31 | end | 37 | end |
32 | 38 | ||
@@ -83,7 +89,6 @@ end | |||
83 | -- creates rewindable source | 89 | -- creates rewindable source |
84 | function source.rewind(src) | 90 | function 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 |
99 | function source.chain(src, f) | 104 | function 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) | 121 | end |
122 | |||
123 | -- creates a source that produces contents of several files one after the | ||
124 | -- other, as if they were concatenated | ||
125 | function 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) | ||
107 | end | 137 | end |
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 |
152 | function sink.chain(f, snk) | 182 | function 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 |
160 | end | 193 | end |
161 | 194 | ||
162 | -- pumps all data from a source to a sink | 195 | -- pumps all data from a source to a sink |
163 | function pump(src, snk) | 196 | function 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 |
171 | end | 204 | end |
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; |
@@ -20,8 +20,8 @@ | |||
20 | #define SP 0x20 | 20 | #define SP 0x20 |
21 | 21 | ||
22 | typedef unsigned char UC; | 22 | typedef unsigned char UC; |
23 | static const char CRLF[2] = {CR, LF}; | 23 | static const char CRLF[] = {CR, LF, 0}; |
24 | static const char EQCRLF[3] = {'=', CR, LF}; | 24 | static 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 | \*-------------------------------------------------------------------------*/ |
488 | static int mime_global_unqp(lua_State *L) | 524 | static 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 @@ | |||
1 | if not ltn12 then error('This module requires LTN12') end | 1 | if not ltn12 then error('Requires LTN12 module') end |
2 | -- create mime namespace | 2 | -- create mime namespace |
3 | mime = mime or {} | 3 | mime = 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" | |||
19 | SERVER = "localhost" | 19 | SERVER = "localhost" |
20 | 20 | ||
21 | function stuff() | 21 | function stuff() |
22 | return socket.cicle(dot, 2) | 22 | return ltn12.filter.cycle(dot, 2) |
23 | end | 23 | end |
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 | ||
9 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end | ||
10 | -- get LuaSocket namespace | ||
11 | local socket = _G[LUASOCKET_LIBNAME] | ||
12 | if not socket then error('module requires LuaSocket') end | ||
13 | -- create namespace inside LuaSocket namespace | ||
14 | socket.tp = socket.tp or {} | ||
15 | -- make all module globals fall into namespace | ||
16 | setmetatable(socket.tp, { __index = _G }) | ||
17 | setfenv(1, socket.tp) | ||
18 | |||
19 | TIMEOUT = 60 | ||
20 | |||
21 | -- tries to get a pattern from the server and closes socket on error | ||
22 | local function try_receiving(sock, pattern) | ||
23 | local data, message = sock:receive(pattern) | ||
24 | if not data then sock:close() end | ||
25 | return data, message | ||
26 | end | ||
27 | |||
28 | -- tries to send data to server and closes socket on error | ||
29 | local function try_sending(sock, data) | ||
30 | local sent, message = sock:send(data) | ||
31 | if not sent then sock:close() end | ||
32 | return sent, message | ||
33 | end | ||
34 | |||
35 | -- gets server reply | ||
36 | local 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 | ||
56 | end | ||
57 | |||
58 | -- metatable for sock object | ||
59 | local metatable = { __index = {} } | ||
60 | |||
61 | -- execute the "check" instr | ||
62 | function 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 | ||
72 | end | ||
73 | |||
74 | function 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) | ||
78 | end | ||
79 | |||
80 | -- execute the "command" instr | ||
81 | function 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 | ||
84 | end | ||
85 | |||
86 | function metatable.__index:sink(snk, pat) | ||
87 | local chunk, err = sock:receive(pat) | ||
88 | return snk(chunk, err) | ||
89 | end | ||
90 | |||
91 | function 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 | ||
98 | end | ||
99 | |||
100 | -- closes the underlying sock | ||
101 | function metatable.__index:close() | ||
102 | self.sock:close() | ||
103 | end | ||
104 | |||
105 | -- connect with server and return sock object | ||
106 | function 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) | ||
111 | end | ||
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 | ||
13 | local t = socket.time() | 13 | local t = socket.time() |
14 | 14 | ||
15 | host = host or "diego-interface2.student.dyn.CS.Princeton.EDU" | 15 | host = host or "diego.student.princeton.edu" |
16 | proxy = proxy or "http://localhost:3128" | 16 | proxy = proxy or "http://localhost:3128" |
17 | prefix = prefix or "/luasocket-test" | 17 | prefix = prefix or "/luasocket-test" |
18 | cgiprefix = cgiprefix or "/luasocket-test-cgi" | 18 | cgiprefix = 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) |
72 | end | 72 | end |
73 | 73 | ||
74 | local check_request_cb = function(request, response, expect, ignore) | 74 | local 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) |
77 | end | 77 | end |
78 | 78 | ||
@@ -83,7 +83,7 @@ local back, h, c, e = socket.http.get("http://" .. host .. forth) | |||
83 | if not back then fail(e) end | 83 | if not back then fail(e) end |
84 | back = socket.url.parse(back) | 84 | back = socket.url.parse(back) |
85 | if similar(back.query, "this+is+the+query+string") then print("ok") | 85 | if similar(back.query, "this+is+the+query+string") then print("ok") |
86 | else fail() end | 86 | else fail(back.query) end |
87 | 87 | ||
88 | ------------------------------------------------------------------------ | 88 | ------------------------------------------------------------------------ |
89 | io.write("testing query string correctness: ") | 89 | io.write("testing query string correctness: ") |
@@ -168,31 +168,28 @@ back = socket.http.post("http://" .. host .. cgiprefix .. "/cat", index) | |||
168 | check(back == index) | 168 | check(back == index) |
169 | 169 | ||
170 | ------------------------------------------------------------------------ | 170 | ------------------------------------------------------------------------ |
171 | io.write("testing send.file and receive.file callbacks: ") | 171 | io.write("testing ltn12.(sink|source).file: ") |
172 | request = { | 172 | request = { |
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 | } |
178 | response = { | ||
179 | body_cb = socket.callback.receive.file(io.open(index_file .. "-back", "w")) | ||
180 | } | ||
181 | expect = { | 179 | expect = { |
182 | code = 200 | 180 | code = 200 |
183 | } | 181 | } |
184 | ignore = { | 182 | ignore = { |
185 | body_cb = 1, | ||
186 | status = 1, | 183 | status = 1, |
187 | headers = 1 | 184 | headers = 1 |
188 | } | 185 | } |
189 | check_request_cb(request, response, expect, ignore) | 186 | check_request_cb(request, expect, ignore) |
190 | back = readfile(index_file .. "-back") | 187 | back = readfile(index_file .. "-back") |
191 | check(back == index) | 188 | check(back == index) |
192 | os.remove(index_file .. "-back") | 189 | os.remove(index_file .. "-back") |
193 | 190 | ||
194 | ------------------------------------------------------------------------ | 191 | ------------------------------------------------------------------------ |
195 | io.write("testing send.chain and receive.chain callbacks: ") | 192 | io.write("testing ltn12.(sink|source).chain and mime.(encode|decode): ") |
196 | 193 | ||
197 | local function b64length(len) | 194 | local 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 |
201 | end | 198 | end |
202 | 199 | ||
203 | local req_cb = socket.callback.send.chain( | 200 | local 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 | ||
211 | local resp_cb = socket.callback.receive.chain( | 208 | local 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 | ||
216 | request = { | 213 | request = { |
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 | } |
222 | response = { body_cb = resp_cb } | ||
223 | expect = { | 220 | expect = { |
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 | } |
231 | check_request_cb(request, response, expect, ignore) | 228 | check_request_cb(request, expect, ignore) |
232 | back = readfile(index_file .. "-back") | 229 | back = readfile(index_file .. "-back") |
233 | check(back == index) | 230 | check(back == index) |
234 | os.remove(index_file .. "-back") | 231 | os.remove(index_file .. "-back") |
@@ -362,7 +359,7 @@ io.write("testing manual basic auth: ") | |||
362 | request = { | 359 | request = { |
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 | } |
368 | expect = { | 365 | expect = { |
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 | ||
34 | local 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 | ||
42 | end | ||
43 | |||
44 | local what = nil | ||
34 | local function transform(input, output, filter) | 45 | local 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() | ||
46 | end | 55 | end |
47 | 56 | ||
48 | local function encode_qptest(mode) | 57 | local 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 @@ | |||
1 | function test_dot(original, right) | ||
2 | local result, n = socket.smtp.dot(2, original) | ||
3 | assert(result == right, "->" .. result .. "<-") | ||
4 | print("ok") | ||
5 | end | ||
6 | |||
7 | function test_stuff(original, right) | ||
8 | local result, n = socket.smtp.dot(2, original) | ||
9 | assert(result == right, "->" .. result .. "<-") | ||
10 | print("ok") | ||
11 | end | ||
12 | |||
13 | test_dot("abc", "abc") | ||
14 | test_dot("", "") | ||
15 | test_dot("\r\n", "\r\n") | ||
16 | test_dot("\r\n.", "\r\n..") | ||
17 | test_dot(".\r\n.", "..\r\n..") | ||
18 | test_dot(".\r\n.", "..\r\n..") | ||
19 | test_dot("abcd.\r\n.", "abcd.\r\n..") | ||