util.httpstream: Refactored and simplified code to improve readability.
[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         
30         while true do
31                 -- read status line
32                 local status_line = readline();
33                 local method, path, httpversion = status_line:match("^(%S+)%s+(%S+)%s+HTTP/(%S+)$");
34                 if not method then coroutine.yield("invalid-status-line"); end
35                 -- TODO parse url
36                 
37                 local headers = {}; -- read headers
38                 while true do
39                         local line = readline();
40                         if line == "" then break; end -- headers done
41                         local key, val = line:match("^([^%s:]+): *(.*)$");
42                         if not key then coroutine.yield("invalid-header-line"); end -- TODO handle multi-line and invalid headers
43                         key = key:lower();
44                         headers[key] = headers[key] and headers[key]..","..val or val;
45                 end
46                 
47                 -- read body
48                 local len = tonumber(headers["content-length"]);
49                 len = len or 0; -- TODO check for invalid len
50                 local body = readlength(len);
51                 
52                 success_cb({
53                         method = method;
54                         path = path;
55                         httpversion = httpversion;
56                         headers = headers;
57                         body = body;
58                 });
59         end
60 end
61
62 function new(success_cb, error_cb)
63         local co = coroutine.create(parser);
64         return {
65                 feed = function(self, data)
66                         if not data then
67                                 co = deadroutine;
68                                 return error_cb();
69                         end
70                         local success, result = coroutine.resume(co, data, success_cb);
71                         if result then
72                                 co = deadroutine;
73                                 return error_cb(result);
74                         end
75                 end;
76         };
77 end
78
79 return _M;