]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // request_parser.cpp | |
3 | // ~~~~~~~~~~~~~~~~~~ | |
4 | // | |
b32b8144 | 5 | // Copyright (c) 2003-2017 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
7c673cae FG |
6 | // |
7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
9 | // | |
10 | ||
11 | #include "request_parser.hpp" | |
12 | #include <algorithm> | |
13 | #include <cctype> | |
14 | #include <boost/lexical_cast.hpp> | |
15 | #include "request.hpp" | |
16 | ||
17 | namespace http { | |
18 | namespace server4 { | |
19 | ||
20 | // Enable the pseudo-keywords reenter, yield and fork. | |
21 | #include <boost/asio/yield.hpp> | |
22 | ||
23 | std::string request_parser::content_length_name_ = "Content-Length"; | |
24 | ||
25 | boost::tribool request_parser::consume(request& req, char c) | |
26 | { | |
27 | reenter (this) | |
28 | { | |
29 | req.method.clear(); | |
30 | req.uri.clear(); | |
31 | req.http_version_major = 0; | |
32 | req.http_version_minor = 0; | |
33 | req.headers.clear(); | |
34 | req.content.clear(); | |
35 | content_length_ = 0; | |
36 | ||
37 | // Request method. | |
38 | while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ' ') | |
39 | { | |
40 | req.method.push_back(c); | |
41 | yield return boost::indeterminate; | |
42 | } | |
43 | if (req.method.empty()) | |
44 | return false; | |
45 | ||
46 | // Space. | |
47 | if (c != ' ') return false; | |
48 | yield return boost::indeterminate; | |
49 | ||
50 | // URI. | |
51 | while (!is_ctl(c) && c != ' ') | |
52 | { | |
53 | req.uri.push_back(c); | |
54 | yield return boost::indeterminate; | |
55 | } | |
56 | if (req.uri.empty()) return false; | |
57 | ||
58 | // Space. | |
59 | if (c != ' ') return false; | |
60 | yield return boost::indeterminate; | |
61 | ||
62 | // HTTP protocol identifier. | |
63 | if (c != 'H') return false; | |
64 | yield return boost::indeterminate; | |
65 | if (c != 'T') return false; | |
66 | yield return boost::indeterminate; | |
67 | if (c != 'T') return false; | |
68 | yield return boost::indeterminate; | |
69 | if (c != 'P') return false; | |
70 | yield return boost::indeterminate; | |
71 | ||
72 | // Slash. | |
73 | if (c != '/') return false; | |
74 | yield return boost::indeterminate; | |
75 | ||
76 | // Major version number. | |
77 | if (!is_digit(c)) return false; | |
78 | while (is_digit(c)) | |
79 | { | |
80 | req.http_version_major = req.http_version_major * 10 + c - '0'; | |
81 | yield return boost::indeterminate; | |
82 | } | |
83 | ||
84 | // Dot. | |
85 | if (c != '.') return false; | |
86 | yield return boost::indeterminate; | |
87 | ||
88 | // Minor version number. | |
89 | if (!is_digit(c)) return false; | |
90 | while (is_digit(c)) | |
91 | { | |
92 | req.http_version_minor = req.http_version_minor * 10 + c - '0'; | |
93 | yield return boost::indeterminate; | |
94 | } | |
95 | ||
96 | // CRLF. | |
97 | if (c != '\r') return false; | |
98 | yield return boost::indeterminate; | |
99 | if (c != '\n') return false; | |
100 | yield return boost::indeterminate; | |
101 | ||
102 | // Headers. | |
103 | while ((is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != '\r') | |
104 | || (c == ' ' || c == '\t')) | |
105 | { | |
106 | if (c == ' ' || c == '\t') | |
107 | { | |
108 | // Leading whitespace. Must be continuation of previous header's value. | |
109 | if (req.headers.empty()) return false; | |
110 | while (c == ' ' || c == '\t') | |
111 | yield return boost::indeterminate; | |
112 | } | |
113 | else | |
114 | { | |
115 | // Start the next header. | |
116 | req.headers.push_back(header()); | |
117 | ||
118 | // Header name. | |
119 | while (is_char(c) && !is_ctl(c) && !is_tspecial(c) && c != ':') | |
120 | { | |
121 | req.headers.back().name.push_back(c); | |
122 | yield return boost::indeterminate; | |
123 | } | |
124 | ||
125 | // Colon and space separates the header name from the header value. | |
126 | if (c != ':') return false; | |
127 | yield return boost::indeterminate; | |
128 | if (c != ' ') return false; | |
129 | yield return boost::indeterminate; | |
130 | } | |
131 | ||
132 | // Header value. | |
133 | while (is_char(c) && !is_ctl(c) && c != '\r') | |
134 | { | |
135 | req.headers.back().value.push_back(c); | |
136 | yield return boost::indeterminate; | |
137 | } | |
138 | ||
139 | // CRLF. | |
140 | if (c != '\r') return false; | |
141 | yield return boost::indeterminate; | |
142 | if (c != '\n') return false; | |
143 | yield return boost::indeterminate; | |
144 | } | |
145 | ||
146 | // CRLF. | |
147 | if (c != '\r') return false; | |
148 | yield return boost::indeterminate; | |
149 | if (c != '\n') return false; | |
150 | ||
151 | // Check for optional Content-Length header. | |
152 | for (std::size_t i = 0; i < req.headers.size(); ++i) | |
153 | { | |
154 | if (headers_equal(req.headers[i].name, content_length_name_)) | |
155 | { | |
156 | try | |
157 | { | |
158 | content_length_ = | |
159 | boost::lexical_cast<std::size_t>(req.headers[i].value); | |
160 | } | |
161 | catch (boost::bad_lexical_cast&) | |
162 | { | |
163 | return false; | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
168 | // Content. | |
169 | while (req.content.size() < content_length_) | |
170 | { | |
171 | yield return boost::indeterminate; | |
172 | req.content.push_back(c); | |
173 | } | |
174 | } | |
175 | ||
176 | return true; | |
177 | } | |
178 | ||
179 | // Disable the pseudo-keywords reenter, yield and fork. | |
180 | #include <boost/asio/unyield.hpp> | |
181 | ||
182 | bool request_parser::is_char(int c) | |
183 | { | |
184 | return c >= 0 && c <= 127; | |
185 | } | |
186 | ||
187 | bool request_parser::is_ctl(int c) | |
188 | { | |
189 | return (c >= 0 && c <= 31) || (c == 127); | |
190 | } | |
191 | ||
192 | bool request_parser::is_tspecial(int c) | |
193 | { | |
194 | switch (c) | |
195 | { | |
196 | case '(': case ')': case '<': case '>': case '@': | |
197 | case ',': case ';': case ':': case '\\': case '"': | |
198 | case '/': case '[': case ']': case '?': case '=': | |
199 | case '{': case '}': case ' ': case '\t': | |
200 | return true; | |
201 | default: | |
202 | return false; | |
203 | } | |
204 | } | |
205 | ||
206 | bool request_parser::is_digit(int c) | |
207 | { | |
208 | return c >= '0' && c <= '9'; | |
209 | } | |
210 | ||
211 | bool request_parser::tolower_compare(char a, char b) | |
212 | { | |
213 | return std::tolower(a) == std::tolower(b); | |
214 | } | |
215 | ||
216 | bool request_parser::headers_equal(const std::string& a, const std::string& b) | |
217 | { | |
218 | if (a.length() != b.length()) | |
219 | return false; | |
220 | ||
221 | return std::equal(a.begin(), a.end(), b.begin(), | |
222 | &request_parser::tolower_compare); | |
223 | } | |
224 | ||
225 | } // namespace server4 | |
226 | } // namespace http |