aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2004-03-21 07:50:15 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2004-03-21 07:50:15 +0000
commit4919a83d2271a9e43b83c7d488e3f94c850681e3 (patch)
treea2ae9a27ead4cb85cbbd857ef74bc39c01b9bf64
parent2a14ac4fe4bb4dd6d7a7ec5195c15a4e3f783ad5 (diff)
downloadluasocket-4919a83d2271a9e43b83c7d488e3f94c850681e3.tar.gz
luasocket-4919a83d2271a9e43b83c7d488e3f94c850681e3.tar.bz2
luasocket-4919a83d2271a9e43b83c7d488e3f94c850681e3.zip
Changed receive function. Now uniform with all other functions. Returns nil
on error, return partial result in the end. http.lua rewritten.
-rw-r--r--TODO4
-rw-r--r--src/buffer.c84
-rw-r--r--src/http.lua733
-rw-r--r--src/ltn12.lua5
-rw-r--r--src/mime.c28
-rw-r--r--test/httptest.lua43
-rw-r--r--test/testclnt.lua34
-rw-r--r--test/testmesg.lua8
-rw-r--r--test/testsrvr.lua2
9 files changed, 303 insertions, 638 deletions
diff --git a/TODO b/TODO
index 3f1c71b..110a78c 100644
--- a/TODO
+++ b/TODO
@@ -19,6 +19,10 @@
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
22change mime.eol to output marker on detection of first candidate, instead
23of on the second. that way it works in one pass for strings that end with
24one candidate.
25
22colocar um userdata com gc metamethod pra chamar sock_close (WSAClose); 26colocar um userdata com gc metamethod pra chamar sock_close (WSAClose);
23sources ans sinks are always simple in http and ftp and smtp 27sources ans sinks are always simple in http and ftp and smtp
24unify backbone of smtp and ftp 28unify backbone of smtp and ftp
diff --git a/src/buffer.c b/src/buffer.c
index d9ba779..4bcfa1a 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -13,9 +13,9 @@
13/*=========================================================================*\ 13/*=========================================================================*\
14* Internal function prototypes 14* Internal function prototypes
15\*=========================================================================*/ 15\*=========================================================================*/
16static int recvraw(lua_State *L, p_buf buf, size_t wanted); 16static int recvraw(p_buf buf, size_t wanted, luaL_Buffer *b);
17static int recvline(lua_State *L, p_buf buf); 17static int recvline(p_buf buf, luaL_Buffer *b);
18static int recvall(lua_State *L, p_buf buf); 18static int recvall(p_buf buf, luaL_Buffer *b);
19static int buf_get(p_buf buf, const char **data, size_t *count); 19static int buf_get(p_buf buf, const char **data, size_t *count);
20static void buf_skip(p_buf buf, size_t count); 20static void buf_skip(p_buf buf, size_t count);
21static int sendraw(p_buf buf, const char *data, size_t count, size_t *sent); 21static int sendraw(p_buf buf, const char *data, size_t count, size_t *sent);
@@ -73,42 +73,34 @@ int buf_meth_send(lua_State *L, p_buf buf)
73\*-------------------------------------------------------------------------*/ 73\*-------------------------------------------------------------------------*/
74int buf_meth_receive(lua_State *L, p_buf buf) 74int buf_meth_receive(lua_State *L, p_buf buf)
75{ 75{
76 int top = lua_gettop(L); 76 int err = IO_DONE, top = lua_gettop(L);
77 int arg, err = IO_DONE;
78 p_tm tm = buf->tm; 77 p_tm tm = buf->tm;
78 luaL_Buffer b;
79 luaL_buffinit(L, &b);
79 tm_markstart(tm); 80 tm_markstart(tm);
80 /* push default pattern if need be */
81 if (top < 2) {
82 lua_pushstring(L, "*l");
83 top++;
84 }
85 /* make sure we have enough stack space for all returns */
86 luaL_checkstack(L, top+LUA_MINSTACK, "too many arguments");
87 /* receive all patterns */ 81 /* receive all patterns */
88 for (arg = 2; arg <= top && err == IO_DONE; arg++) { 82 if (!lua_isnumber(L, 2)) {
89 if (!lua_isnumber(L, arg)) { 83 static const char *patternnames[] = {"*l", "*a", NULL};
90 static const char *patternnames[] = {"*l", "*a", NULL}; 84 const char *pattern = luaL_optstring(L, 2, "*l");
91 const char *pattern = lua_isnil(L, arg) ? 85 /* get next pattern */
92 "*l" : luaL_checkstring(L, arg); 86 int p = luaL_findstring(pattern, patternnames);
93 /* get next pattern */ 87 if (p == 0) err = recvline(buf, &b);
94 switch (luaL_findstring(pattern, patternnames)) { 88 else if (p == 1) err = recvall(buf, &b);
95 case 0: /* line pattern */ 89 else luaL_argcheck(L, 0, 2, "invalid receive pattern");
96 err = recvline(L, buf); break;
97 case 1: /* until closed pattern */
98 err = recvall(L, buf);
99 if (err == IO_CLOSED) err = IO_DONE;
100 break;
101 default: /* else it is an error */
102 luaL_argcheck(L, 0, arg, "invalid receive pattern");
103 break;
104 }
105 /* get a fixed number of bytes */ 90 /* get a fixed number of bytes */
106 } else err = recvraw(L, buf, (size_t) lua_tonumber(L, arg)); 91 } else err = recvraw(buf, (size_t) lua_tonumber(L, 2), &b);
92 /* check if there was an error */
93 if (err != IO_DONE) {
94 luaL_pushresult(&b);
95 io_pusherror(L, err);
96 lua_pushvalue(L, -2);
97 lua_pushnil(L);
98 lua_replace(L, -4);
99 } else {
100 luaL_pushresult(&b);
101 lua_pushnil(L);
102 lua_pushnil(L);
107 } 103 }
108 /* push nil for each pattern after an error */
109 for ( ; arg <= top; arg++) lua_pushnil(L);
110 /* last return is an error code */
111 io_pusherror(L, err);
112#ifdef LUASOCKET_DEBUG 104#ifdef LUASOCKET_DEBUG
113 /* push time elapsed during operation as the last return value */ 105 /* push time elapsed during operation as the last return value */
114 lua_pushnumber(L, (tm_gettime() - tm_getstart(tm))/1000.0); 106 lua_pushnumber(L, (tm_gettime() - tm_getstart(tm))/1000.0);
@@ -150,21 +142,18 @@ int sendraw(p_buf buf, const char *data, size_t count, size_t *sent)
150* Reads a fixed number of bytes (buffered) 142* Reads a fixed number of bytes (buffered)
151\*-------------------------------------------------------------------------*/ 143\*-------------------------------------------------------------------------*/
152static 144static
153int recvraw(lua_State *L, p_buf buf, size_t wanted) 145int recvraw(p_buf buf, size_t wanted, luaL_Buffer *b)
154{ 146{
155 int err = IO_DONE; 147 int err = IO_DONE;
156 size_t total = 0; 148 size_t total = 0;
157 luaL_Buffer b;
158 luaL_buffinit(L, &b);
159 while (total < wanted && (err == IO_DONE || err == IO_RETRY)) { 149 while (total < wanted && (err == IO_DONE || err == IO_RETRY)) {
160 size_t count; const char *data; 150 size_t count; const char *data;
161 err = buf_get(buf, &data, &count); 151 err = buf_get(buf, &data, &count);
162 count = MIN(count, wanted - total); 152 count = MIN(count, wanted - total);
163 luaL_addlstring(&b, data, count); 153 luaL_addlstring(b, data, count);
164 buf_skip(buf, count); 154 buf_skip(buf, count);
165 total += count; 155 total += count;
166 } 156 }
167 luaL_pushresult(&b);
168 return err; 157 return err;
169} 158}
170 159
@@ -172,19 +161,17 @@ int recvraw(lua_State *L, p_buf buf, size_t wanted)
172* Reads everything until the connection is closed (buffered) 161* Reads everything until the connection is closed (buffered)
173\*-------------------------------------------------------------------------*/ 162\*-------------------------------------------------------------------------*/
174static 163static
175int recvall(lua_State *L, p_buf buf) 164int recvall(p_buf buf, luaL_Buffer *b)
176{ 165{
177 int err = IO_DONE; 166 int err = IO_DONE;
178 luaL_Buffer b;
179 luaL_buffinit(L, &b);
180 while (err == IO_DONE || err == IO_RETRY) { 167 while (err == IO_DONE || err == IO_RETRY) {
181 const char *data; size_t count; 168 const char *data; size_t count;
182 err = buf_get(buf, &data, &count); 169 err = buf_get(buf, &data, &count);
183 luaL_addlstring(&b, data, count); 170 luaL_addlstring(b, data, count);
184 buf_skip(buf, count); 171 buf_skip(buf, count);
185 } 172 }
186 luaL_pushresult(&b); 173 if (err == IO_CLOSED) return IO_DONE;
187 return err; 174 else return err;
188} 175}
189 176
190/*-------------------------------------------------------------------------*\ 177/*-------------------------------------------------------------------------*\
@@ -192,18 +179,16 @@ int recvall(lua_State *L, p_buf buf)
192* are not returned by the function and are discarded from the buffer 179* are not returned by the function and are discarded from the buffer
193\*-------------------------------------------------------------------------*/ 180\*-------------------------------------------------------------------------*/
194static 181static
195int recvline(lua_State *L, p_buf buf) 182int recvline(p_buf buf, luaL_Buffer *b)
196{ 183{
197 int err = IO_DONE; 184 int err = IO_DONE;
198 luaL_Buffer b;
199 luaL_buffinit(L, &b);
200 while (err == IO_DONE || err == IO_RETRY) { 185 while (err == IO_DONE || err == IO_RETRY) {
201 size_t count, pos; const char *data; 186 size_t count, pos; const char *data;
202 err = buf_get(buf, &data, &count); 187 err = buf_get(buf, &data, &count);
203 pos = 0; 188 pos = 0;
204 while (pos < count && data[pos] != '\n') { 189 while (pos < count && data[pos] != '\n') {
205 /* we ignore all \r's */ 190 /* we ignore all \r's */
206 if (data[pos] != '\r') luaL_putchar(&b, data[pos]); 191 if (data[pos] != '\r') luaL_putchar(b, data[pos]);
207 pos++; 192 pos++;
208 } 193 }
209 if (pos < count) { /* found '\n' */ 194 if (pos < count) { /* found '\n' */
@@ -212,7 +197,6 @@ int recvline(lua_State *L, p_buf buf)
212 } else /* reached the end of the buffer */ 197 } else /* reached the end of the buffer */
213 buf_skip(buf, pos); 198 buf_skip(buf, pos);
214 } 199 }
215 luaL_pushresult(&b);
216 return err; 200 return err;
217} 201}
218 202
diff --git a/src/http.lua b/src/http.lua
index 629bf65..a10cf50 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -39,423 +39,293 @@ local function third(a, b, c)
39 return c 39 return c
40end 40end
41 41
42----------------------------------------------------------------------------- 42local function shift(a, b, c, d)
43-- Tries to get a pattern from the server and closes socket on error 43 return c, d
44-- sock: socket connected to the server
45-- pattern: pattern to receive
46-- Returns
47-- received pattern on success
48-- nil followed by error message on error
49-----------------------------------------------------------------------------
50local function try_receiving(sock, pattern)
51 local data, err = sock:receive(pattern)
52 if not data then sock:close() end
53--print(data)
54 return data, err
55end
56
57-----------------------------------------------------------------------------
58-- Tries to send data to the server and closes socket on error
59-- sock: socket connected to the server
60-- ...: data to send
61-- Returns
62-- err: error message if any, nil if successfull
63-----------------------------------------------------------------------------
64local function try_sending(sock, ...)
65 local sent, err = sock:send(unpack(arg))
66 if not sent then sock:close() end
67--io.write(unpack(arg))
68 return err
69end 44end
70 45
71----------------------------------------------------------------------------- 46-- resquest_p forward declaration
72-- Receive server reply messages, parsing for status code 47local request_p
73-- Input
74-- sock: socket connected to the server
75-- Returns
76-- code: server status code or nil if error
77-- line: full HTTP status line
78-- err: error message if any
79-----------------------------------------------------------------------------
80local function receive_status(sock)
81 local line, err = try_receiving(sock)
82 if not err then
83 local code = third(string.find(line, "HTTP/%d*%.%d* (%d%d%d)"))
84 return tonumber(code), line
85 else return nil, nil, err end
86end
87 48
88-----------------------------------------------------------------------------
89-- Receive and parse response header fields
90-- Input
91-- sock: socket connected to the server
92-- headers: a table that might already contain headers
93-- Returns
94-- headers: a table with all headers fields in the form
95-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"}
96-- all name_i are lowercase
97-- nil and error message in case of error
98-----------------------------------------------------------------------------
99local function receive_headers(sock, headers) 49local function receive_headers(sock, headers)
100 local line, err 50 local line, name, value
101 local name, value, _
102 headers = headers or {}
103 -- get first line 51 -- get first line
104 line, err = try_receiving(sock) 52 line = socket.try(sock:receive())
105 if err then return nil, err end
106 -- headers go until a blank line is found 53 -- headers go until a blank line is found
107 while line ~= "" do 54 while line ~= "" do
108 -- get field-name and value 55 -- get field-name and value
109 _,_, name, value = string.find(line, "^(.-):%s*(.*)") 56 name, value = shift(string.find(line, "^(.-):%s*(.*)"))
110 if not name or not value then 57 assert(name and value, "malformed reponse headers")
111 sock:close()
112 return nil, "malformed reponse headers"
113 end
114 name = string.lower(name) 58 name = string.lower(name)
115 -- get next line (value might be folded) 59 -- get next line (value might be folded)
116 line, err = try_receiving(sock) 60 line = socket.try(sock:receive())
117 if err then return nil, err end
118 -- unfold any folded values 61 -- unfold any folded values
119 while not err and string.find(line, "^%s") do 62 while string.find(line, "^%s") do
120 value = value .. line 63 value = value .. line
121 line, err = try_receiving(sock) 64 line = socket.try(sock:receive())
122 if err then return nil, err end
123 end 65 end
124 -- save pair in table 66 -- save pair in table
125 if headers[name] then headers[name] = headers[name] .. ", " .. value 67 if headers[name] then headers[name] = headers[name] .. ", " .. value
126 else headers[name] = value end 68 else headers[name] = value end
127 end 69 end
128 return headers
129end 70end
130 71
131-----------------------------------------------------------------------------
132-- Aborts a sink with an error message
133-- Input
134-- cb: callback function
135-- err: error message to pass to callback
136-- Returns
137-- callback return or if nil err
138-----------------------------------------------------------------------------
139local function abort(cb, err) 72local function abort(cb, err)
140 local go, cb_err = cb(nil, err) 73 local go, cb_err = cb(nil, err)
141 return cb_err or err 74 error(cb_err or err)
142end 75end
143 76
144----------------------------------------------------------------------------- 77local function hand(cb, chunk)
145-- Receives a chunked message body 78 local go, cb_err = cb(chunk)
146-- Input 79 assert(go, cb_err or "aborted by callback")
147-- sock: socket connected to the server 80end
148-- headers: header set in which to include trailer headers 81
149-- sink: response message body sink 82local function receive_body_bychunks(sock, sink)
150-- Returns
151-- nil if successfull or an error message in case of error
152-----------------------------------------------------------------------------
153local function receive_body_bychunks(sock, headers, sink)
154 local chunk, size, line, err, go
155 while 1 do 83 while 1 do
156 -- get chunk size, skip extention 84 -- get chunk size, skip extention
157 line, err = try_receiving(sock) 85 local line, err = sock:receive()
158 if err then return abort(sink, err) end 86 if err then abort(sink, err) end
159 size = tonumber(string.gsub(line, ";.*", ""), 16) 87 local size = tonumber(string.gsub(line, ";.*", ""), 16)
160 if not size then return abort(sink, "invalid chunk size") end 88 if not size then abort(sink, "invalid chunk size") end
161 -- was it the last chunk? 89 -- was it the last chunk?
162 if size <= 0 then break end 90 if size <= 0 then break end
163 -- get chunk 91 -- get chunk
164 chunk, err = try_receiving(sock, size) 92 local chunk, err = sock:receive(size)
165 if err then return abort(sink, err) end 93 if err then abort(sink, err) end
166 -- pass chunk to callback 94 -- pass chunk to callback
167 go, err = sink(chunk) 95 hand(sink, chunk)
168 -- see if callback aborted
169 if not go then return err or "aborted by callback" end
170 -- skip CRLF on end of chunk 96 -- skip CRLF on end of chunk
171 err = second(try_receiving(sock)) 97 err = second(sock:receive())
172 if err then return abort(sink, err) end 98 if err then abort(sink, err) end
173 end 99 end
174 -- servers shouldn't send trailer headers, but who trusts them?
175 err = second(receive_headers(sock, headers))
176 if err then return abort(sink, err) end
177 -- let callback know we are done 100 -- let callback know we are done
178 return second(sink(nil)) 101 hand(sink, nil)
102 -- servers shouldn't send trailer headers, but who trusts them?
103 receive_headers(sock, {})
179end 104end
180 105
181-----------------------------------------------------------------------------
182-- Receives a message body by content-length
183-- Input
184-- sock: socket connected to the server
185-- length: message body length
186-- sink: response message body sink
187-- Returns
188-- nil if successfull or an error message in case of error
189-----------------------------------------------------------------------------
190local function receive_body_bylength(sock, length, sink) 106local function receive_body_bylength(sock, length, sink)
191 while length > 0 do 107 while length > 0 do
192 local size = math.min(BLOCKSIZE, length) 108 local size = math.min(BLOCKSIZE, length)
193 local chunk, err = sock:receive(size) 109 local chunk, err = sock:receive(size)
194 local go, cb_err = sink(chunk) 110 if err then abort(sink, err) end
195 length = length - string.len(chunk) 111 length = length - string.len(chunk)
196 -- see if callback aborted
197 if not go then return cb_err or "aborted by callback" end
198 -- see if there was an error 112 -- see if there was an error
199 if err and length > 0 then return abort(sink, err) end 113 hand(sink, chunk)
200 end 114 end
201 return second(sink(nil)) 115 -- let callback know we are done
116 hand(sink, nil)
202end 117end
203 118
204-----------------------------------------------------------------------------
205-- Receives a message body until the conection is closed
206-- Input
207-- sock: socket connected to the server
208-- sink: response message body sink
209-- Returns
210-- nil if successfull or an error message in case of error
211-----------------------------------------------------------------------------
212local function receive_body_untilclosed(sock, sink) 119local function receive_body_untilclosed(sock, sink)
213 while 1 do 120 while true do
214 local chunk, err = sock:receive(BLOCKSIZE) 121 local chunk, err, partial = sock:receive(BLOCKSIZE)
215 local go, cb_err = sink(chunk)
216 -- see if callback aborted
217 if not go then return cb_err or "aborted by callback" end
218 -- see if we are done 122 -- see if we are done
219 if err == "closed" then return chunk and second(sink(nil)) end 123 if err == "closed" then
124 hand(sink, partial)
125 break
126 end
127 hand(sink, chunk)
220 -- see if there was an error 128 -- see if there was an error
221 if err then return abort(sink, err) end 129 if err then abort(sink, err) end
222 end 130 end
131 -- let callback know we are done
132 hand(sink, nil)
223end 133end
224 134
225----------------------------------------------------------------------------- 135local function receive_body(reqt, respt)
226-- Receives the HTTP response body 136 local sink = reqt.sink or ltn12.sink.null()
227-- Input 137 local headers = respt.headers
228-- sock: socket connected to the server 138 local sock = respt.tmp.sock
229-- headers: response header fields
230-- sink: response message body sink
231-- Returns
232-- nil if successfull or an error message in case of error
233-----------------------------------------------------------------------------
234local function receive_body(sock, headers, sink)
235 -- make sure sink is not fancy
236 sink = ltn12.sink.simplify(sink)
237 local te = headers["transfer-encoding"] 139 local te = headers["transfer-encoding"]
238 if te and te ~= "identity" then 140 if te and te ~= "identity" then
239 -- get by chunked transfer-coding of message body 141 -- get by chunked transfer-coding of message body
240 return receive_body_bychunks(sock, headers, sink) 142 receive_body_bychunks(sock, sink)
241 elseif tonumber(headers["content-length"]) then 143 elseif tonumber(headers["content-length"]) then
242 -- get by content-length 144 -- get by content-length
243 local length = tonumber(headers["content-length"]) 145 local length = tonumber(headers["content-length"])
244 return receive_body_bylength(sock, length, sink) 146 receive_body_bylength(sock, length, sink)
245 else 147 else
246 -- get it all until connection closes 148 -- get it all until connection closes
247 return receive_body_untilclosed(sock, sink) 149 receive_body_untilclosed(sock, sink)
248 end 150 end
249end 151end
250 152
251-----------------------------------------------------------------------------
252-- Sends the HTTP request message body in chunks
253-- Input
254-- data: data connection
255-- source: request message body source
256-- Returns
257-- nil if successfull, or an error message in case of error
258-----------------------------------------------------------------------------
259local function send_body_bychunks(data, source) 153local function send_body_bychunks(data, source)
260 while 1 do 154 while true do
261 local chunk, cb_err = source() 155 local chunk, err = source()
262 -- check if callback aborted 156 assert(chunk or not err, err)
263 if not chunk then return cb_err or "aborted by callback" end 157 if not chunk then break end
264 -- if we are done, send last-chunk 158 socket.try(data:send(string.format("%X\r\n", string.len(chunk))))
265 if chunk == "" then return try_sending(data, "0\r\n\r\n") end 159 socket.try(data:send(chunk, "\r\n"))
266 -- else send middle chunk
267 local err = try_sending(data,
268 string.format("%X\r\n", string.len(chunk)),
269 chunk,
270 "\r\n"
271 )
272 if err then return err end
273 end 160 end
161 socket.try(data:send("0\r\n\r\n"))
274end 162end
275 163
276-----------------------------------------------------------------------------
277-- Sends the HTTP request message body
278-- Input
279-- data: data connection
280-- source: request message body source
281-- Returns
282-- nil if successfull, or an error message in case of error
283-----------------------------------------------------------------------------
284local function send_body(data, source) 164local function send_body(data, source)
285 while 1 do 165 while true do
286 local chunk, cb_err = source() 166 local chunk, err = source()
287 -- check if callback is done 167 assert(chunk or not err, err)
288 if not chunk then return cb_err end 168 if not chunk then break end
289 -- send data 169 socket.try(data:send(chunk))
290 local err = try_sending(data, chunk)
291 if err then return err end
292 end 170 end
293end 171end
294 172
295-----------------------------------------------------------------------------
296-- Sends request headers
297-- Input
298-- sock: server socket
299-- headers: table with headers to be sent
300-- Returns
301-- err: error message if any
302-----------------------------------------------------------------------------
303local function send_headers(sock, headers) 173local function send_headers(sock, headers)
304 local err
305 headers = headers or {}
306 -- send request headers 174 -- send request headers
307 for i, v in headers do 175 for i, v in pairs(headers) do
308 err = try_sending(sock, i .. ": " .. v .. "\r\n") 176 socket.try(sock:send(i .. ": " .. v .. "\r\n"))
309 if err then return err end
310 end 177 end
311 -- mark end of request headers 178 -- mark end of request headers
312 return try_sending(sock, "\r\n") 179 socket.try(sock:send("\r\n"))
313end 180end
314 181
315----------------------------------------------------------------------------- 182local function should_receive_body(reqt, respt)
316-- Sends a HTTP request message through socket 183 if reqt.method == "HEAD" then return nil end
317-- Input 184 if respt.code == 204 or respt.code == 304 then return nil end
318-- sock: socket connected to the server 185 if respt.code >= 100 and respt.code < 200 then return nil end
319-- method: request method to be used 186 return 1
320-- uri: request uri 187end
321-- headers: request headers to be sent 188
322-- source: request message body source 189local function receive_status(reqt, respt)
323-- Returns 190 local sock = respt.tmp.sock
324-- err: nil in case of success, error message otherwise 191 local status = socket.try(sock:receive())
325----------------------------------------------------------------------------- 192 local code = third(string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
326local function send_request(sock, method, uri, headers, source) 193 -- store results
327 local chunk, size, done, err 194 respt.code, respt.status = tonumber(code), status
195end
196
197local function request_uri(reqt, respt)
198 local url
199 local parsed = respt.tmp.parsed
200 if not reqt.proxy then
201 url = {
202 path = parsed.path,
203 params = parsed.params,
204 query = parsed.query,
205 fragment = parsed.fragment
206 }
207 else url = respt.tmp.parsed end
208 return socket.url.build(url)
209end
210
211local function send_request(reqt, respt)
212 local uri = request_uri(reqt, respt)
213 local sock = respt.tmp.sock
214 local headers = respt.tmp.headers
328 -- send request line 215 -- send request line
329 err = try_sending(sock, method .. " " .. uri .. " HTTP/1.1\r\n") 216 socket.try(sock:send((reqt.method or "GET")
330 if err then return err end 217 .. " " .. uri .. " HTTP/1.1\r\n"))
331 if source and not headers["content-length"] then 218 -- send request headers headeres
219 if reqt.source and not headers["content-length"] then
332 headers["transfer-encoding"] = "chunked" 220 headers["transfer-encoding"] = "chunked"
333 end 221 end
334 -- send request headers 222 send_headers(sock, headers)
335 err = send_headers(sock, headers)
336 if err then return err end
337 -- send request message body, if any 223 -- send request message body, if any
338 if source then 224 if reqt.source then
339 -- make sure source is not fancy 225 if headers["content-length"] then send_body(sock, reqt.source)
340 source = ltn12.source.simplify(source) 226 else send_body_bychunks(sock, reqt.source) end
341 if headers["content-length"] then
342 return send_body(sock, source)
343 else
344 return send_body_bychunks(sock, source)
345 end
346 end 227 end
347end 228end
348 229
349----------------------------------------------------------------------------- 230local function open(reqt, respt)
350-- Determines if we should read a message body from the server response 231 local parsed = respt.tmp.parsed
351-- Input 232 local proxy = reqt.proxy or PROXY
352-- reqt: a table with the original request information 233 local host, port
353-- respt: a table with the server response information 234 if proxy then
354-- Returns 235 local pproxy = socket.url.parse(proxy)
355-- 1 if a message body should be processed, nil otherwise 236 assert(pproxy.port and pproxy.host, "invalid proxy")
356----------------------------------------------------------------------------- 237 host, port = pproxy.host, pproxy.port
357local function should_receive_body(reqt, respt) 238 else
358 if reqt.method == "HEAD" then return nil end 239 host, port = parsed.host, parsed.port
359 if respt.code == 204 or respt.code == 304 then return nil end 240 end
360 if respt.code >= 100 and respt.code < 200 then return nil end 241 local sock = socket.try(socket.tcp())
361 return 1 242 -- store results
243 respt.tmp.sock = sock
244 sock:settimeout(reqt.timeout or TIMEOUT)
245 socket.try(sock:connect(host, port))
362end 246end
363 247
364----------------------------------------------------------------------------- 248function adjust_headers(reqt, respt)
365-- Converts field names to lowercase and adds a few needed headers
366-- Input
367-- headers: request header fields
368-- parsed: parsed request URL
369-- Returns
370-- lower: a table with the same headers, but with lowercase field names
371-----------------------------------------------------------------------------
372local function fill_headers(headers, parsed)
373 local lower = {} 249 local lower = {}
374 headers = headers or {} 250 local headers = reqt.headers or {}
375 -- set default headers 251 -- set default headers
376 lower["user-agent"] = USERAGENT 252 lower["user-agent"] = USERAGENT
377 -- override with user values 253 -- override with user values
378 for i,v in headers do 254 for i,v in headers do
379 lower[string.lower(i)] = v 255 lower[string.lower(i)] = v
380 end 256 end
381 lower["host"] = parsed.host 257 lower["host"] = respt.tmp.parsed.host
382 -- this cannot be overriden 258 -- this cannot be overriden
383 lower["connection"] = "close" 259 lower["connection"] = "close"
384 return lower 260 -- store results
261 respt.tmp.headers = lower
385end 262end
386 263
387----------------------------------------------------------------------------- 264function parse_url(reqt, respt)
388-- Decides wether we should follow retry with authorization formation 265 -- parse url with default fields
389-- Input 266 local parsed = socket.url.parse(reqt.url, {
390-- reqt: a table with the original request information 267 host = "",
391-- parsed: parsed request URL 268 port = PORT,
392-- respt: a table with the server response information 269 path ="/",
393-- Returns 270 scheme = "http"
394-- 1 if we should retry, nil otherwise 271 })
395----------------------------------------------------------------------------- 272 -- scheme has to be http
396local function should_authorize(reqt, parsed, respt) 273 if parsed.scheme ~= "http" then
274 error(string.format("unknown scheme '%s'", parsed.scheme))
275 end
276 -- explicit authentication info overrides that given by the URL
277 parsed.user = reqt.user or parsed.user
278 parsed.password = reqt.password or parsed.password
279 -- store results
280 respt.tmp.parsed = parsed
281end
282
283local function should_authorize(reqt, respt)
397 -- if there has been an authorization attempt, it must have failed 284 -- if there has been an authorization attempt, it must have failed
398 if reqt.headers["authorization"] then return nil end 285 if reqt.headers and reqt.headers["authorization"] then return nil end
399 -- if we don't have authorization information, we can't retry 286 -- if we don't have authorization information, we can't retry
400 if parsed.user and parsed.password then return 1 287 return respt.tmp.parsed.user and respt.tmp.parsed.password
401 else return nil end
402end 288end
403 289
404----------------------------------------------------------------------------- 290local function clone(headers)
405-- Returns the result of retrying a request with authorization information 291 if not headers then return nil end
406-- Input 292 local copy = {}
407-- reqt: a table with the original request information 293 for i,v in pairs(headers) do
408-- parsed: parsed request URL 294 copy[i] = v
409-- Returns 295 end
410-- respt: result of target authorization 296 return copy
411----------------------------------------------------------------------------- 297end
412local function authorize(reqt, parsed) 298
413 reqt.headers["authorization"] = "Basic " .. 299local function authorize(reqt, respt)
300 local headers = clone(reqt.headers) or {}
301 local parsed = respt.tmp.parsed
302 headers["authorization"] = "Basic " ..
414 (mime.b64(parsed.user .. ":" .. parsed.password)) 303 (mime.b64(parsed.user .. ":" .. parsed.password))
415 local autht = { 304 local autht = {
416 nredirects = reqt.nredirects,
417 method = reqt.method, 305 method = reqt.method,
418 url = reqt.url, 306 url = reqt.url,
419 source = reqt.source, 307 source = reqt.source,
420 sink = reqt.sink, 308 sink = reqt.sink,
421 headers = reqt.headers, 309 headers = headers,
422 timeout = reqt.timeout, 310 timeout = reqt.timeout,
423 proxy = reqt.proxy, 311 proxy = reqt.proxy,
424 } 312 }
425 return request_cb(autht) 313 request_p(autht, respt)
426end 314end
427 315
428-----------------------------------------------------------------------------
429-- Decides wether we should follow a server redirect message
430-- Input
431-- reqt: a table with the original request information
432-- respt: a table with the server response information
433-- Returns
434-- 1 if we should redirect, nil otherwise
435-----------------------------------------------------------------------------
436local function should_redirect(reqt, respt) 316local function should_redirect(reqt, respt)
437 return (reqt.redirect ~= false) and 317 return (reqt.redirect ~= false) and
438 (respt.code == 301 or respt.code == 302) and 318 (respt.code == 301 or respt.code == 302) and
439 (reqt.method == "GET" or reqt.method == "HEAD") and 319 (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD")
440 not (reqt.nredirects and reqt.nredirects >= 5) 320 and (not respt.tmp.nredirects or respt.tmp.nredirects < 5)
441end 321end
442 322
443-----------------------------------------------------------------------------
444-- Returns the result of a request following a server redirect message.
445-- Input
446-- reqt: a table with the original request information
447-- respt: response table of previous attempt
448-- Returns
449-- respt: result of target redirection
450-----------------------------------------------------------------------------
451local function redirect(reqt, respt) 323local function redirect(reqt, respt)
452 local nredirects = reqt.nredirects or 0 324 respt.tmp.nredirects = (respt.tmp.nredirects or 0) + 1
453 nredirects = nredirects + 1
454 local redirt = { 325 local redirt = {
455 nredirects = nredirects,
456 method = reqt.method, 326 method = reqt.method,
457 -- the RFC says the redirect URL has to be absolute, but some 327 -- the RFC says the redirect URL has to be absolute, but some
458 -- servers do not respect that 328 -- servers do not respect that
459 url = socket.url.absolute(reqt.url, respt.headers["location"]), 329 url = socket.url.absolute(reqt.url, respt.headers["location"]),
460 source = reqt.source, 330 source = reqt.source,
461 sink = reqt.sink, 331 sink = reqt.sink,
@@ -463,243 +333,58 @@ local function redirect(reqt, respt)
463 timeout = reqt.timeout, 333 timeout = reqt.timeout,
464 proxy = reqt.proxy 334 proxy = reqt.proxy
465 } 335 }
466 respt = request_cb(redirt) 336 request_p(redirt, respt)
467 -- we pass the location header as a clue we tried to redirect 337 -- we pass the location header as a clue we redirected
468 if respt.headers then respt.headers.location = redirt.url end 338 if respt.headers then respt.headers.location = redirt.url end
469 return respt
470end
471
472-----------------------------------------------------------------------------
473-- Computes the request URI from the parsed request URL
474-- If we are using a proxy, we use the absoluteURI format.
475-- Otherwise, we use the abs_path format.
476-- Input
477-- parsed: parsed URL
478-- Returns
479-- uri: request URI for parsed URL
480-----------------------------------------------------------------------------
481local function request_uri(reqt, parsed)
482 local url
483 if not reqt.proxy then
484 url = {
485 path = parsed.path,
486 params = parsed.params,
487 query = parsed.query,
488 fragment = parsed.fragment
489 }
490 else url = parsed end
491 return socket.url.build(url)
492end
493
494-----------------------------------------------------------------------------
495-- Builds a request table from a URL or request table
496-- Input
497-- url_or_request: target url or request table (a table with the fields:
498-- url: the target URL
499-- user: account user name
500-- password: account password)
501-- Returns
502-- reqt: request table
503-----------------------------------------------------------------------------
504local function build_request(data)
505 local reqt = {}
506 if type(data) == "table" then
507 for i, v in data
508 do reqt[i] = v
509 end
510 else reqt.url = data end
511 return reqt
512end 339end
513 340
514----------------------------------------------------------------------------- 341function request_p(reqt, respt)
515-- Connects to a server, be it a proxy or not 342 parse_url(reqt, respt)
516-- Input 343 adjust_headers(reqt, respt)
517-- reqt: the request table 344 open(reqt, respt)
518-- parsed: the parsed request url 345 send_request(reqt, respt)
519-- Returns 346 receive_status(reqt, respt)
520-- sock: connection socket, or nil in case of error 347 respt.headers = {}
521-- err: error message 348 receive_headers(respt.tmp.sock, respt.headers)
522-----------------------------------------------------------------------------
523local function try_connect(reqt, parsed)
524 reqt.proxy = reqt.proxy or PROXY
525 local host, port
526 if reqt.proxy then
527 local pproxy = socket.url.parse(reqt.proxy)
528 if not pproxy.port or not pproxy.host then
529 return nil, "invalid proxy"
530 end
531 host, port = pproxy.host, pproxy.port
532 else
533 host, port = parsed.host, parsed.port
534 end
535 local sock, ret, err
536 sock, err = socket.tcp()
537 if not sock then return nil, err end
538 sock:settimeout(reqt.timeout or TIMEOUT)
539 ret, err = sock:connect(host, port)
540 if not ret then
541 sock:close()
542 return nil, err
543 end
544 return sock
545end
546
547-----------------------------------------------------------------------------
548-- Sends a HTTP request and retrieves the server reply using callbacks to
549-- send the request body and receive the response body
550-- Input
551-- reqt: a table with the following fields
552-- method: "GET", "PUT", "POST" etc (defaults to "GET")
553-- url: target uniform resource locator
554-- user, password: authentication information
555-- headers: request headers to send, or nil if none
556-- source: request message body source, or nil if none
557-- sink: response message body sink
558-- redirect: should we refrain from following a server redirect message?
559-- Returns
560-- respt: a table with the following fields:
561-- headers: response header fields received, or nil if failed
562-- status: server response status line, or nil if failed
563-- code: server status code, or nil if failed
564-- error: error message, or nil if successfull
565-----------------------------------------------------------------------------
566function request_cb(reqt)
567 local sock, ret
568 local parsed = socket.url.parse(reqt.url, {
569 host = "",
570 port = PORT,
571 path ="/",
572 scheme = "http"
573 })
574 local respt = {}
575 if parsed.scheme ~= "http" then
576 respt.error = string.format("unknown scheme '%s'", parsed.scheme)
577 return respt
578 end
579 -- explicit authentication info overrides that given by the URL
580 parsed.user = reqt.user or parsed.user
581 parsed.password = reqt.password or parsed.password
582 -- default method
583 reqt.method = reqt.method or "GET"
584 -- fill default headers
585 reqt.headers = fill_headers(reqt.headers, parsed)
586 -- try to connect to server
587 sock, respt.error = try_connect(reqt, parsed)
588 if not sock then return respt end
589 -- send request message
590 respt.error = send_request(sock, reqt.method,
591 request_uri(reqt, parsed), reqt.headers, reqt.source)
592 if respt.error then
593 sock:close()
594 return respt
595 end
596 -- get server response message
597 respt.code, respt.status, respt.error = receive_status(sock)
598 if respt.error then return respt end
599 -- deal with continue 100
600 -- servers should not send them, but some do!
601 if respt.code == 100 then
602 respt.headers, respt.error = receive_headers(sock, {})
603 if respt.error then return respt end
604 respt.code, respt.status, respt.error = receive_status(sock)
605 if respt.error then return respt end
606 end
607 -- receive all headers
608 respt.headers, respt.error = receive_headers(sock, {})
609 if respt.error then return respt end
610 -- decide what to do based on request and response parameters
611 if should_redirect(reqt, respt) then 349 if should_redirect(reqt, respt) then
612 -- drop the body 350 respt.tmp.sock:close()
613 receive_body(sock, respt.headers, ltn12.sink.null()) 351 redirect(reqt, respt)
614 -- we are done with this connection 352 elseif should_authorize(reqt, respt) then
615 sock:close() 353 respt.tmp.sock:close()
616 return redirect(reqt, respt) 354 authorize(reqt, respt)
617 elseif should_authorize(reqt, parsed, respt) then
618 -- drop the body
619 receive_body(sock, respt.headers, ltn12.sink.null())
620 -- we are done with this connection
621 sock:close()
622 return authorize(reqt, parsed, respt)
623 elseif should_receive_body(reqt, respt) then 355 elseif should_receive_body(reqt, respt) then
624 respt.error = receive_body(sock, respt.headers, reqt.sink) 356 receive_body(reqt, respt)
625 if respt.error then return respt end
626 sock:close()
627 return respt
628 end 357 end
629 sock:close()
630 return respt
631end 358end
632 359
633-----------------------------------------------------------------------------
634-- Sends a HTTP request and retrieves the server reply
635-- Input
636-- reqt: a table with the following fields
637-- method: "GET", "PUT", "POST" etc (defaults to "GET")
638-- url: request URL, i.e. the document to be retrieved
639-- user, password: authentication information
640-- headers: request header fields, or nil if none
641-- body: request message body as a string, or nil if none
642-- redirect: should we refrain from following a server redirect message?
643-- Returns
644-- respt: a table with the following fields:
645-- body: response message body, or nil if failed
646-- headers: response header fields, or nil if failed
647-- status: server response status line, or nil if failed
648-- code: server response status code, or nil if failed
649-- error: error message if any
650-----------------------------------------------------------------------------
651function request(reqt) 360function request(reqt)
652 reqt.source = reqt.body and ltn12.source.string(reqt.body) 361 local respt = { tmp = {} }
653 local t = {} 362 local s, e = pcall(request_p, reqt, respt)
654 reqt.sink = ltn12.sink.table(t) 363 if not s then respt.error = e end
655 local respt = request_cb(reqt) 364 if respt.tmp.sock then respt.tmp.sock:close() end
656 if table.getn(t) > 0 then respt.body = table.concat(t) end 365 respt.tmp = nil
657 return respt 366 return respt
658end 367end
659 368
660----------------------------------------------------------------------------- 369function get(url)
661-- Retrieves a URL by the method "GET" 370 local t = {}
662-- Input 371 respt = request {
663-- url_or_request: target url or request table (a table with the fields: 372 url = url,
664-- url: the target URL 373 sink = ltn12.sink.table(t)
665-- user: account user name 374 }
666-- password: account password) 375 return table.getn(t) > 0 and table.concat(t), respt.headers,
667-- Returns 376 respt.code, respt.error
668-- body: response message body, or nil if failed
669-- headers: response header fields received, or nil if failed
670-- code: server response status code, or nil if failed
671-- error: error message if any
672-----------------------------------------------------------------------------
673function get(url_or_request)
674 local reqt = build_request(url_or_request)
675 reqt.method = "GET"
676 local respt = request(reqt)
677 return respt.body, respt.headers, respt.code, respt.error
678end 377end
679 378
680----------------------------------------------------------------------------- 379function post(url, body)
681-- Retrieves a URL by the method "POST" 380 local t = {}
682-- Input 381 respt = request {
683-- url_or_request: target url or request table (a table with the fields: 382 url = url,
684-- url: the target URL 383 method = "POST",
685-- body: request message body 384 source = ltn12.source.string(body),
686-- user: account user name 385 sink = ltn12.sink.table(t),
687-- password: account password) 386 headers = { ["content-length"] = string.len(body) }
688-- body: request message body, or nil if none 387 }
689-- Returns 388 return table.getn(t) > 0 and table.concat(t),
690-- body: response message body, or nil if failed 389 respt.headers, respt.code, respt.error
691-- headers: response header fields received, or nil if failed
692-- code: server response status code, or nil if failed
693-- error: error message, or nil if successfull
694-----------------------------------------------------------------------------
695function post(url_or_request, body)
696 local reqt = build_request(url_or_request)
697 reqt.method = "POST"
698 reqt.body = reqt.body or body
699 reqt.headers = reqt.headers or
700 { ["content-length"] = string.len(reqt.body) }
701 local respt = request(reqt)
702 return respt.body, respt.headers, respt.code, respt.error
703end 390end
704
705return socket.http
diff --git a/src/ltn12.lua b/src/ltn12.lua
index ef6247d..dc49d80 100644
--- a/src/ltn12.lua
+++ b/src/ltn12.lua
@@ -171,9 +171,8 @@ function sink.file(handle, io_err)
171 return function(chunk, err) 171 return function(chunk, err)
172 if not chunk then 172 if not chunk then
173 handle:close() 173 handle:close()
174 return nil, err 174 return 1
175 end 175 else return handle:write(chunk) end
176 return handle:write(chunk)
177 end 176 end
178 else return sink.error(io_err or "unable to open file") end 177 else return sink.error(io_err or "unable to open file") end
179end 178end
diff --git a/src/mime.c b/src/mime.c
index 77f3ae1..7bfa6aa 100644
--- a/src/mime.c
+++ b/src/mime.c
@@ -619,28 +619,27 @@ static int mime_global_qpwrp(lua_State *L)
619* end of line markers each, but \r\n, \n\r etc will only issue *one* 619* end of line markers each, but \r\n, \n\r etc will only issue *one*
620* marker. This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as 620* marker. This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as
621* probably other more obscure conventions. 621* probably other more obscure conventions.
622*
623* c is the current character being processed
624* last is the previous character
622\*-------------------------------------------------------------------------*/ 625\*-------------------------------------------------------------------------*/
623#define eolcandidate(c) (c == CR || c == LF) 626#define eolcandidate(c) (c == CR || c == LF)
624static size_t eolprocess(int c, int ctx, const char *marker, 627static int eolprocess(int c, int last, const char *marker,
625 luaL_Buffer *buffer) 628 luaL_Buffer *buffer)
626{ 629{
627 if (eolcandidate(ctx)) { 630 if (eolcandidate(c)) {
628 luaL_addstring(buffer, marker); 631 if (eolcandidate(last)) {
629 if (eolcandidate(c)) { 632 if (c == last) luaL_addstring(buffer, marker);
630 if (c == ctx)
631 luaL_addstring(buffer, marker);
632 return 0; 633 return 0;
633 } else { 634 } else {
634 luaL_putchar(buffer, c); 635 luaL_addstring(buffer, marker);
635 return 0; 636 return c;
636 } 637 }
637 } else { 638 } else {
638 if (!eolcandidate(c)) { 639 luaL_putchar(buffer, c);
639 luaL_putchar(buffer, c); 640 return 0;
640 return 0;
641 } else
642 return c;
643 } 641 }
642
644} 643}
645 644
646/*-------------------------------------------------------------------------*\ 645/*-------------------------------------------------------------------------*\
@@ -661,8 +660,7 @@ static int mime_global_eol(lua_State *L)
661 luaL_buffinit(L, &buffer); 660 luaL_buffinit(L, &buffer);
662 /* if the last character was a candidate, we output a new line */ 661 /* if the last character was a candidate, we output a new line */
663 if (!input) { 662 if (!input) {
664 if (eolcandidate(ctx)) lua_pushstring(L, marker); 663 lua_pushnil(L);
665 else lua_pushnil(L);
666 lua_pushnumber(L, 0); 664 lua_pushnumber(L, 0);
667 return 2; 665 return 2;
668 } 666 }
diff --git a/test/httptest.lua b/test/httptest.lua
index 04c0ed0..ddeea50 100644
--- a/test/httptest.lua
+++ b/test/httptest.lua
@@ -8,7 +8,7 @@ dofile("noglobals.lua")
8local host, proxy, request, response, index_file 8local host, proxy, request, response, index_file
9local ignore, expect, index, prefix, cgiprefix, index_crlf 9local ignore, expect, index, prefix, cgiprefix, index_crlf
10 10
11socket.http.TIMEOUT = 5 11socket.http.TIMEOUT = 10
12 12
13local t = socket.time() 13local t = socket.time()
14 14
@@ -49,7 +49,9 @@ local check_result = function(response, expect, ignore)
49 for i,v in response do 49 for i,v in response do
50 if not ignore[i] then 50 if not ignore[i] then
51 if v ~= expect[i] then 51 if v ~= expect[i] then
52 print(string.sub(tostring(v), 1, 70)) 52 local f = io.open("err", "w")
53 f:write(tostring(v), "\n\n versus\n\n", tostring(expect[i]))
54 f:close()
53 fail(i .. " differs!") 55 fail(i .. " differs!")
54 end 56 end
55 end 57 end
@@ -57,8 +59,10 @@ local check_result = function(response, expect, ignore)
57 for i,v in expect do 59 for i,v in expect do
58 if not ignore[i] then 60 if not ignore[i] then
59 if v ~= response[i] then 61 if v ~= response[i] then
62 local f = io.open("err", "w")
63 f:write(tostring(response[i]), "\n\n versus\n\n", tostring(v))
60 v = string.sub(type(v) == "string" and v or "", 1, 70) 64 v = string.sub(type(v) == "string" and v or "", 1, 70)
61 print(string.sub(tostring(v), 1, 70)) 65 f:close()
62 fail(i .. " differs!") 66 fail(i .. " differs!")
63 end 67 end
64 end 68 end
@@ -67,12 +71,14 @@ local check_result = function(response, expect, ignore)
67end 71end
68 72
69local check_request = function(request, expect, ignore) 73local check_request = function(request, expect, ignore)
74 local t
75 if not request.sink then
76 request.sink, t = ltn12.sink.table(t)
77 end
78 request.source = request.source or
79 (request.body and ltn12.source.string(request.body))
70 local response = socket.http.request(request) 80 local response = socket.http.request(request)
71 check_result(response, expect, ignore) 81 if t and table.getn(t) > 0 then response.body = table.concat(t) end
72end
73
74local check_request_cb = function(request, expect, ignore)
75 local response = socket.http.request_cb(request)
76 check_result(response, expect, ignore) 82 check_result(response, expect, ignore)
77end 83end
78 84
@@ -183,7 +189,7 @@ ignore = {
183 status = 1, 189 status = 1,
184 headers = 1 190 headers = 1
185} 191}
186check_request_cb(request, expect, ignore) 192check_request(request, expect, ignore)
187back = readfile(index_file .. "-back") 193back = readfile(index_file .. "-back")
188check(back == index) 194check(back == index)
189os.remove(index_file .. "-back") 195os.remove(index_file .. "-back")
@@ -225,20 +231,12 @@ ignore = {
225 status = 1, 231 status = 1,
226 headers = 1 232 headers = 1
227} 233}
228check_request_cb(request, expect, ignore) 234check_request(request, expect, ignore)
229back = readfile(index_file .. "-back") 235back = readfile(index_file .. "-back")
230check(back == index) 236check(back == index)
231os.remove(index_file .. "-back") 237os.remove(index_file .. "-back")
232 238
233------------------------------------------------------------------------ 239------------------------------------------------------------------------
234io.write("testing simple post function with table args: ")
235back = socket.http.post {
236 url = "http://" .. host .. cgiprefix .. "/cat",
237 body = index
238}
239check(back == index)
240
241------------------------------------------------------------------------
242io.write("testing http redirection: ") 240io.write("testing http redirection: ")
243request = { 241request = {
244 url = "http://" .. host .. prefix 242 url = "http://" .. host .. prefix
@@ -439,15 +437,6 @@ body = socket.http.get("http://" .. host .. prefix .. "/index.html")
439check(body == index) 437check(body == index)
440 438
441------------------------------------------------------------------------ 439------------------------------------------------------------------------
442io.write("testing simple get function with table args: ")
443body = socket.http.get {
444 url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html",
445 user = "luasocket",
446 password = "password"
447}
448check(body == index)
449
450------------------------------------------------------------------------
451io.write("testing HEAD method: ") 440io.write("testing HEAD method: ")
452socket.http.TIMEOUT = 1 441socket.http.TIMEOUT = 1
453response = socket.http.request { 442response = socket.http.request {
diff --git a/test/testclnt.lua b/test/testclnt.lua
index 1b64abd..ecf419b 100644
--- a/test/testclnt.lua
+++ b/test/testclnt.lua
@@ -17,14 +17,12 @@ function warn(...)
17 io.stderr:write("WARNING: ", s, "\n") 17 io.stderr:write("WARNING: ", s, "\n")
18end 18end
19 19
20pad = string.rep(" ", 8192)
21
22function remote(...) 20function remote(...)
23 local s = string.format(unpack(arg)) 21 local s = string.format(unpack(arg))
24 s = string.gsub(s, "\n", ";") 22 s = string.gsub(s, "\n", ";")
25 s = string.gsub(s, "%s+", " ") 23 s = string.gsub(s, "%s+", " ")
26 s = string.gsub(s, "^%s*", "") 24 s = string.gsub(s, "^%s*", "")
27 control:send(pad, s, "\n") 25 control:send(s, "\n")
28 control:receive() 26 control:receive()
29end 27end
30 28
@@ -122,7 +120,13 @@ remote (string.format("str = data:receive(%d)",
122 sent, err = data:send(p1, p2, p3, p4) 120 sent, err = data:send(p1, p2, p3, p4)
123 if err then fail(err) end 121 if err then fail(err) end
124remote "data:send(str); data:close()" 122remote "data:send(str); data:close()"
125 bp1, bp2, bp3, bp4, err = data:receive("*l", "*l", string.len(p3), "*a") 123 bp1, err = data:receive()
124 if err then fail(err) end
125 bp2, err = data:receive()
126 if err then fail(err) end
127 bp3, err = data:receive(string.len(p3))
128 if err then fail(err) end
129 bp4, err = data:receive("*a")
126 if err then fail(err) end 130 if err then fail(err) end
127 if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 and bp4 == p4 then 131 if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 and bp4 == p4 then
128 pass("patterns match") 132 pass("patterns match")
@@ -186,7 +190,7 @@ end
186------------------------------------------------------------------------ 190------------------------------------------------------------------------
187function test_totaltimeoutreceive(len, tm, sl) 191function test_totaltimeoutreceive(len, tm, sl)
188 reconnect() 192 reconnect()
189 local str, err, total 193 local str, err, partial
190 pass("%d bytes, %ds total timeout, %ds pause", len, tm, sl) 194 pass("%d bytes, %ds total timeout, %ds pause", len, tm, sl)
191 remote (string.format ([[ 195 remote (string.format ([[
192 data:settimeout(%d) 196 data:settimeout(%d)
@@ -198,9 +202,9 @@ function test_totaltimeoutreceive(len, tm, sl)
198 data:send(str) 202 data:send(str)
199 ]], 2*tm, len, sl, sl)) 203 ]], 2*tm, len, sl, sl))
200 data:settimeout(tm, "total") 204 data:settimeout(tm, "total")
201 str, err, elapsed = data:receive(2*len) 205 str, err, partial, elapsed = data:receive(2*len)
202 check_timeout(tm, sl, elapsed, err, "receive", "total", 206 check_timeout(tm, sl, elapsed, err, "receive", "total",
203 string.len(str) == 2*len) 207 string.len(str or partial) == 2*len)
204end 208end
205 209
206------------------------------------------------------------------------ 210------------------------------------------------------------------------
@@ -226,7 +230,7 @@ end
226------------------------------------------------------------------------ 230------------------------------------------------------------------------
227function test_blockingtimeoutreceive(len, tm, sl) 231function test_blockingtimeoutreceive(len, tm, sl)
228 reconnect() 232 reconnect()
229 local str, err, total 233 local str, err, partial
230 pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) 234 pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl)
231 remote (string.format ([[ 235 remote (string.format ([[
232 data:settimeout(%d) 236 data:settimeout(%d)
@@ -238,9 +242,9 @@ function test_blockingtimeoutreceive(len, tm, sl)
238 data:send(str) 242 data:send(str)
239 ]], 2*tm, len, sl, sl)) 243 ]], 2*tm, len, sl, sl))
240 data:settimeout(tm) 244 data:settimeout(tm)
241 str, err, elapsed = data:receive(2*len) 245 str, err, partial, elapsed = data:receive(2*len)
242 check_timeout(tm, sl, elapsed, err, "receive", "blocking", 246 check_timeout(tm, sl, elapsed, err, "receive", "blocking",
243 string.len(str) == 2*len) 247 string.len(str or partial) == 2*len)
244end 248end
245 249
246------------------------------------------------------------------------ 250------------------------------------------------------------------------
@@ -298,7 +302,7 @@ end
298 302
299------------------------------------------------------------------------ 303------------------------------------------------------------------------
300function test_closed() 304function test_closed()
301 local back, err 305 local back, partial, err
302 local str = 'little string' 306 local str = 'little string'
303 reconnect() 307 reconnect()
304 pass("trying read detection") 308 pass("trying read detection")
@@ -308,10 +312,10 @@ function test_closed()
308 data = nil 312 data = nil
309 ]], str)) 313 ]], str))
310 -- try to get a line 314 -- try to get a line
311 back, err = data:receive() 315 back, err, partial = data:receive()
312 if not err then fail("shold have gotten 'closed'.") 316 if not err then fail("should have gotten 'closed'.")
313 elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") 317 elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.")
314 elseif str ~= back then fail("didn't receive partial result.") 318 elseif str ~= partial then fail("didn't receive partial result.")
315 else pass("graceful 'closed' received") end 319 else pass("graceful 'closed' received") end
316 reconnect() 320 reconnect()
317 pass("trying write detection") 321 pass("trying write detection")
@@ -456,7 +460,6 @@ test_methods(socket.udp(), {
456 "setpeername", 460 "setpeername",
457 "setsockname", 461 "setsockname",
458 "settimeout", 462 "settimeout",
459 "shutdown",
460}) 463})
461 464
462test("select function") 465test("select function")
@@ -481,6 +484,7 @@ accept_timeout()
481accept_errors() 484accept_errors()
482 485
483 486
487
484test("mixed patterns") 488test("mixed patterns")
485test_mixed(1) 489test_mixed(1)
486test_mixed(17) 490test_mixed(17)
diff --git a/test/testmesg.lua b/test/testmesg.lua
index 228bbe4..8b33133 100644
--- a/test/testmesg.lua
+++ b/test/testmesg.lua
@@ -6,7 +6,7 @@ mesgt = {
6 body = { 6 body = {
7 preamble = "Some attatched stuff", 7 preamble = "Some attatched stuff",
8 [1] = { 8 [1] = {
9 body = "Testing stuffing.\r\n.\r\nGot you.\r\n.Hehehe.\r\n" 9 body = mime.eol(0, "Testing stuffing.\n.\nGot you.\n.Hehehe.\n")
10 }, 10 },
11 [2] = { 11 [2] = {
12 headers = { 12 headers = {
@@ -29,7 +29,7 @@ mesgt = {
29 ["content-transfer-encoding"] = "QUOTED-PRINTABLE" 29 ["content-transfer-encoding"] = "QUOTED-PRINTABLE"
30 }, 30 },
31 body = ltn12.source.chain( 31 body = ltn12.source.chain(
32 ltn12.source.file(io.open("message.lua", "rb")), 32 ltn12.source.file(io.open("testmesg.lua", "rb")),
33 ltn12.filter.chain( 33 ltn12.filter.chain(
34 mime.normalize(), 34 mime.normalize(),
35 mime.encode("quoted-printable"), 35 mime.encode("quoted-printable"),
@@ -46,8 +46,8 @@ mesgt = {
46-- ltn12.pump(source, sink) 46-- ltn12.pump(source, sink)
47 47
48print(socket.smtp.send { 48print(socket.smtp.send {
49 rcpt = {"<db@werx4.com>", "<diego@cs.princeton.edu>"}, 49 rcpt = "<diego@cs.princeton.edu>",
50 from = "<diego@cs.princeton.edu>", 50 from = "<diego@cs.princeton.edu>",
51 source = socket.smtp.message(mesgt), 51 source = socket.smtp.message(mesgt),
52 server = "smtp.princeton.edu" 52 server = "mail.cs.princeton.edu"
53}) 53})
diff --git a/test/testsrvr.lua b/test/testsrvr.lua
index 99b54e5..5c05239 100644
--- a/test/testsrvr.lua
+++ b/test/testsrvr.lua
@@ -22,6 +22,8 @@ while 1 do
22 print("server: closing connection...") 22 print("server: closing connection...")
23 break 23 break
24 end 24 end
25print(command);
26
25 (loadstring(command))() 27 (loadstring(command))()
26 end 28 end
27end 29end