util.httpstream: Move HTTP header parsing into its own function.
[prosody.git] / util / httpstream.lua
1
2 local coroutine = coroutine;
3 local tonumber = tonumber;
4
5 local deadroutine = coroutine.create(function() end);
6 coroutine.resume(deadroutine);
7
8 module("httpstream")
9
10 local function parser(data, success_cb)
11         local function readline()
12                 local pos = data:find("\r\n", nil, true);
13                 while not pos do
14                         data = data..coroutine.yield();
15                         pos = data:find("\r\n", nil, true);
16                 end
17                 local r = data:sub(1, pos-1);
18                 data = data:sub(pos+2);
19                 return r;
20         end
21         local function readlength(n)
22                 while #data < n do
23                         data = data..coroutine.yield();
24                 end
25                 local r = data:sub(1, n);
26                 data = data:sub(n + 1);
27                 return r;
28         end
29         local function readheaders()
30                 local headers = {}; -- read headers
31                 while true do
32                         local line = readline();
33                         if line == "" then break; end -- headers done
34                         local key, val = line:match("^([^%s:]+): *(.*)$");
35                         if not key then coroutine.yield("invalid-header-line"); end -- TODO handle multi-line and invalid headers
36                         key = key:lower();
37                         headers[key] = headers[key] and headers[key]..","..val or val;
38                 end
39         end
40         
41         while true do
42                 -- read status line
43                 local status_line = readline();
44                 local method, path, httpversion = status_line:match("^(%S+)%s+(%S+)%s+HTTP/(%S+)$");
45                 if not method then coroutine.yield("invalid-status-line"); end
46                 -- TODO parse url
47                 local headers = readheaders();
48                 
49                 -- read body
50                 local len = tonumber(headers["content-length"]);
51                 len = len or 0; -- TODO check for invalid len
52                 local body = readlength(len);
53                 
54                 success_cb({
55                         method = method;
56                         path = path;
57                         httpversion = httpversion;
58                         headers = headers;
59                         body = body;
60                 });
61         end
62 end
63
64 function new(success_cb, error_cb)
65         local co = coroutine.create(parser);
66         return {
67                 feed = function(self, data)
68                         if not data then
69                                 co = deadroutine;
70                                 return error_cb();
71                         end
72                         local success, result = coroutine.resume(co, data, success_cb);
73                         if result then
74                                 co = deadroutine;
75                                 return error_cb(result);
76                         end
77                 end;
78         };
79 end
80
81 return _M;