aboutsummaryrefslogtreecommitdiff
path: root/src
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 /src
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.
Diffstat (limited to 'src')
-rw-r--r--src/buffer.c84
-rw-r--r--src/http.lua733
-rw-r--r--src/ltn12.lua5
-rw-r--r--src/mime.c28
4 files changed, 258 insertions, 592 deletions
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 }