Software /
code /
prosody
Comparison
net/http/parser.lua @ 4631:fc5d3b053454
net.http.{server|codes|parser}: Initial commit.
author | Waqas Hussain <waqas20@gmail.com> |
---|---|
date | Sun, 08 Apr 2012 04:09:33 +0500 |
child | 4712:4fc99f1b7570 |
comparison
equal
deleted
inserted
replaced
4630:9502c0224caf | 4631:fc5d3b053454 |
---|---|
1 | |
2 local tonumber = tonumber; | |
3 local assert = assert; | |
4 | |
5 local httpstream = {}; | |
6 | |
7 function httpstream.new(success_cb, error_cb, parser_type, options_cb) | |
8 local client = true; | |
9 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end | |
10 local buf = ""; | |
11 local chunked; | |
12 local state = nil; | |
13 local packet; | |
14 local len; | |
15 local have_body; | |
16 local error; | |
17 return { | |
18 feed = function(self, data) | |
19 if error then return nil, "parse has failed"; end | |
20 if not data then -- EOF | |
21 if state and client and not len then -- reading client body until EOF | |
22 packet.body = buf; | |
23 success_cb(packet); | |
24 elseif buf ~= "" then -- unexpected EOF | |
25 error = true; return error_cb(); | |
26 end | |
27 return; | |
28 end | |
29 buf = buf..data; | |
30 while #buf > 0 do | |
31 if state == nil then -- read request | |
32 local index = buf:find("\r\n\r\n", nil, true); | |
33 if not index then return; end -- not enough data | |
34 local method, path, httpversion, status_code, reason_phrase; | |
35 local first_line; | |
36 local headers = {}; | |
37 for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request | |
38 if first_line then | |
39 local key, val = line:match("^([^%s:]+): *(.*)$"); | |
40 if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers | |
41 key = key:lower(); | |
42 headers[key] = headers[key] and headers[key]..","..val or val; | |
43 else | |
44 first_line = line; | |
45 if client then | |
46 httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$"); | |
47 if not status_code then error = true; return error_cb("invalid-status-line"); end | |
48 have_body = not | |
49 ( (options_cb and options_cb().method == "HEAD") | |
50 or (status_code == 204 or status_code == 304 or status_code == 301) | |
51 or (status_code >= 100 and status_code < 200) ); | |
52 chunked = have_body and headers["transfer-encoding"] == "chunked"; | |
53 else | |
54 method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$"); | |
55 if not method then error = true; return error_cb("invalid-status-line"); end | |
56 path = path:gsub("^//+", "/"); -- TODO parse url more | |
57 end | |
58 end | |
59 end | |
60 len = tonumber(headers["content-length"]); -- TODO check for invalid len | |
61 if client then | |
62 -- FIXME handle '100 Continue' response (by skipping it) | |
63 if not have_body then len = 0; end | |
64 packet = { | |
65 code = status_code; | |
66 httpversion = httpversion; | |
67 headers = headers; | |
68 body = have_body and "" or nil; | |
69 -- COMPAT the properties below are deprecated | |
70 responseversion = httpversion; | |
71 responseheaders = headers; | |
72 }; | |
73 else | |
74 len = len or 0; | |
75 packet = { | |
76 method = method; | |
77 path = path; | |
78 httpversion = httpversion; | |
79 headers = headers; | |
80 body = nil; | |
81 }; | |
82 end | |
83 buf = buf:sub(index + 4); | |
84 state = true; | |
85 end | |
86 if state then -- read body | |
87 if client then | |
88 if chunked then | |
89 local index = buf:find("\r\n", nil, true); | |
90 if not index then return; end -- not enough data | |
91 local chunk_size = buf:match("^%x+"); | |
92 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end | |
93 chunk_size = tonumber(chunk_size, 16); | |
94 index = index + 2; | |
95 if chunk_size == 0 then | |
96 state = nil; success_cb(packet); | |
97 elseif #buf - index + 1 >= chunk_size then -- we have a chunk | |
98 packet.body = packet.body..buf:sub(index, index + chunk_size - 1); | |
99 buf = buf:sub(index + chunk_size); | |
100 end | |
101 error("trailers"); -- FIXME MUST read trailers | |
102 elseif len and #buf >= len then | |
103 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); | |
104 state = nil; success_cb(packet); | |
105 end | |
106 elseif #buf >= len then | |
107 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); | |
108 state = nil; success_cb(packet); | |
109 end | |
110 end | |
111 end | |
112 end; | |
113 }; | |
114 end | |
115 | |
116 return httpstream; |