2 local coroutine = coroutine;
3 local tonumber = tonumber;
5 local deadroutine = coroutine.create(function() end);
6 coroutine.resume(deadroutine);
10 local function parser(success_cb, parser_type, options_cb)
11 local data = coroutine.yield();
12 local function readline()
13 local pos = data:find("\r\n", nil, true);
15 data = data..coroutine.yield();
16 pos = data:find("\r\n", nil, true);
18 local r = data:sub(1, pos-1);
19 data = data:sub(pos+2);
22 local function readlength(n)
24 data = data..coroutine.yield();
26 local r = data:sub(1, n);
27 data = data:sub(n + 1);
30 local function readheaders()
31 local headers = {}; -- read headers
33 local line = readline();
34 if line == "" then break; end -- headers done
35 local key, val = line:match("^([^%s:]+): *(.*)$");
36 if not key then coroutine.yield("invalid-header-line"); end -- TODO handle multi-line and invalid headers
38 headers[key] = headers[key] and headers[key]..","..val or val;
43 if not parser_type or parser_type == "server" then
46 local status_line = readline();
47 local method, path, httpversion = status_line:match("^(%S+)%s+(%S+)%s+HTTP/(%S+)$");
48 if not method then coroutine.yield("invalid-status-line"); end
49 path = path:gsub("^//+", "/"); -- TODO parse url more
50 local headers = readheaders();
53 local len = tonumber(headers["content-length"]);
54 len = len or 0; -- TODO check for invalid len
55 local body = readlength(len);
60 httpversion = httpversion;
65 elseif parser_type == "client" then
68 local status_line = readline();
69 local httpversion, status_code, reason_phrase = status_line:match("^HTTP/(%S+)%s+(%d%d%d)%s+(.*)$");
70 status_code = tonumber(status_code);
71 if not status_code then coroutine.yield("invalid-status-line"); end
72 local headers = readheaders();
76 ( (options_cb and options_cb().method == "HEAD")
77 or (status_code == 204 or status_code == 304 or status_code == 301)
78 or (status_code >= 100 and status_code < 200) );
82 local len = tonumber(headers["content-length"]);
83 if headers["transfer-encoding"] == "chunked" then
86 local chunk_size = readline():match("^%x+");
87 if not chunk_size then coroutine.yield("invalid-chunk-size"); end
88 chunk_size = tonumber(chunk_size, 16)
89 if chunk_size == 0 then break; end
90 body = body..readlength(chunk_size);
91 if readline() ~= "" then coroutine.yield("invalid-chunk-ending"); end
93 local trailers = readheaders();
94 elseif len then -- TODO check for invalid len
95 body = readlength(len);
98 local newdata = coroutine.yield();
101 body, data = data, "";
107 httpversion = httpversion;
110 -- COMPAT the properties below are deprecated
111 responseversion = httpversion;
112 responseheaders = headers;
115 else coroutine.yield("unknown-parser-type"); end
118 function new(success_cb, error_cb, parser_type, options_cb)
119 local co = coroutine.create(parser);
120 coroutine.resume(co, success_cb, parser_type, options_cb)
122 feed = function(self, data)
124 if parser_type == "client" then coroutine.resume(co, ""); end
128 local success, result = coroutine.resume(co, data);
131 return error_cb(result);