Line data Source code
1 : #include <algorithm>
2 : #include <cctype>
3 : #include <sstream>
4 : #include <string_view>
5 : #include "request_factory.hpp"
6 : #include "../common/http_request.hpp"
7 : #include "../util/url.hpp"
8 : #include "../../util/ascii.hpp"
9 : #include "../../util/logger.hpp"
10 :
11 : namespace thinger::http {
12 :
13 : namespace {
14 :
15 1663 : bool is_percent_encoded(std::string_view text) {
16 2813 : for (auto percent = text.find('%'); percent != std::string_view::npos; percent = text.find('%', percent + 3)) {
17 2730 : if (percent + 2 >= text.size() || !std::isxdigit(static_cast<unsigned char>(text[percent + 1])) ||
18 1150 : !std::isxdigit(static_cast<unsigned char>(text[percent + 2]))) {
19 430 : return false;
20 : }
21 : }
22 1233 : return true;
23 : }
24 :
25 34630 : bool has_dot_dot_segment(std::string_view path) {
26 99098 : for (size_t start = 0; start < path.size();) {
27 65590 : auto end = std::min(path.find('/', start), path.size());
28 65590 : if (end - start == 2 && path[start] == '.' && path[start + 1] == '.') return true;
29 64468 : start = end + 1;
30 : }
31 33508 : return false;
32 : }
33 :
34 : // A target in origin form is accepted when it starts with "/" as sent (RFC 9112 section
35 : // 3.2.1: an encoded "%2F" is not one), its percent-encoding is valid, and no segment of
36 : // its decoded path is "..". The path ends at the first "?" as sent (an encoded "%3F" is
37 : // part of it) and the query is not checked for "..", as "?range=1..5" or
38 : // "/v1..v2.txt" are no traversal
39 35319 : bool is_accepted_target(std::string_view target) {
40 35319 : if (target.empty() || target[0] != '/') return false;
41 35300 : auto query = target.find('?');
42 35300 : if (query != std::string_view::npos && !is_percent_encoded(target.substr(query + 1))) return false;
43 34870 : auto path = target.substr(0, query);
44 : // without escapes, the path is checked as sent
45 34870 : if (path.find('%') == std::string_view::npos) return !has_dot_dot_segment(path);
46 1206 : std::string decoded;
47 1206 : return util::url::detail::path_decode(path, decoded) && !has_dot_dot_segment(decoded);
48 1206 : }
49 :
50 : }
51 :
52 35288 : request_factory::request_factory() : state_(method_start) {
53 35288 : }
54 :
55 1024818 : boost::tribool request_factory::consume(char input) {
56 : // bound the request line and headers, as they are buffered whole
57 1024818 : if (state_ != content && ++header_section_size_ > max_header_section_size) {
58 38 : return header_section_exceeded();
59 : }
60 1024780 : switch (state_) {
61 35392 : case method_start:
62 35392 : if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
63 0 : return false;
64 : }
65 : else {
66 35392 : state_ = method;
67 35392 : tempString1_.push_back(input);
68 35392 : return boost::indeterminate;
69 : }
70 111424 : case method:
71 111424 : if (input == ' ') {
72 : // initialize a new http request if necessary
73 35392 : if(!req){
74 35392 : req = std::make_shared<http_request>();
75 : // room for the headers of most requests, without growing
76 35392 : req->get_headers().reserve(16);
77 : }
78 : // store read method
79 35392 : on_http_method(tempString1_);
80 35392 : tempString1_.clear();
81 : // CONNECT asks for a tunnel to the authority in its target (authority-form,
82 : // RFC 9112 section 3.2.3), which is not served. What follows its header
83 : // section is tunnel data, not content nor another request (RFC 9110 section
84 : // 9.3.6): reading it as HTTP would frame the stream unlike a proxy does
85 35392 : if (req->get_method() == method::CONNECT) {
86 22 : return false;
87 : }
88 35370 : state_ = uri;
89 35370 : return boost::indeterminate;
90 : }
91 76032 : else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
92 0 : return false;
93 : }
94 : else {
95 76032 : tempString1_.push_back(input);
96 76032 : return boost::indeterminate;
97 : }
98 35352 : case uri:
99 35352 : if (input == ' ') {
100 35319 : if (!is_accepted_target(tempString1_)) {
101 1811 : tempString1_.clear();
102 1811 : return false;
103 : }
104 33508 : on_http_uri(tempString1_);
105 33508 : tempString1_.clear();
106 : // next step reading http version
107 33508 : state_ = http_version_h;
108 33508 : return boost::indeterminate;
109 : }
110 : // The request-target is ASCII (RFC 9112 section 3.2, RFC 3986): other bytes must
111 : // be percent-encoded. An invalid request line is rejected, not autocorrected, as
112 : // it may be crafted to bypass filters along the request chain (RFC 9112 section 3)
113 33 : else if (!is_char(input) || is_ctl(input)) {
114 33 : return false;
115 : }
116 : else {
117 0 : tempString1_.push_back(input);
118 0 : return boost::indeterminate;
119 : }
120 33508 : case http_version_h:
121 33508 : if (input == 'H') {
122 33508 : state_ = http_version_t_1;
123 33508 : return boost::indeterminate;
124 : }
125 0 : return false;
126 33508 : case http_version_t_1:
127 33508 : if (input == 'T') {
128 33508 : state_ = http_version_t_2;
129 33508 : return boost::indeterminate;
130 : }
131 0 : return false;
132 33508 : case http_version_t_2:
133 33508 : if (input == 'T') {
134 33508 : state_ = http_version_p;
135 33508 : return boost::indeterminate;
136 : }
137 0 : return false;
138 33508 : case http_version_p:
139 33508 : if (input == 'P') {
140 33508 : state_ = http_version_slash;
141 33508 : return boost::indeterminate;
142 : }
143 0 : return false;
144 33508 : case http_version_slash:
145 33508 : if (input == '/') {
146 33508 : state_ = http_version_major_start;
147 33508 : return boost::indeterminate;
148 : }
149 0 : return false;
150 : // HTTP-version = HTTP-name "/" DIGIT "." DIGIT (RFC 9112, section 2.3): a single
151 : // digit each, as longer numbers would be truncated (e.g. "HTTP/1.10" as 1.1). Only
152 : // major version 1 uses this message syntax; a higher minor version is handled as
153 : // 1.1 (RFC 9110, section 2.5)
154 33508 : case http_version_major_start:
155 33508 : if (input == '1') {
156 33496 : tempInt_ = input - '0';
157 33496 : state_ = http_version_major;
158 33496 : return boost::indeterminate;
159 : }
160 12 : return false;
161 33496 : case http_version_major:
162 33496 : if (input == '.') {
163 33492 : on_http_major_version(tempInt_);
164 33492 : state_ = http_version_minor_start;
165 33492 : return boost::indeterminate;
166 : }
167 4 : return false;
168 33492 : case http_version_minor_start:
169 33492 : if (is_digit(input)) {
170 33490 : tempInt_ = input - '0';
171 33490 : state_ = http_version_minor;
172 33490 : return boost::indeterminate;
173 : }
174 2 : return false;
175 33490 : case http_version_minor:
176 33490 : if (input == '\r') {
177 33484 : on_http_minor_version(tempInt_);
178 33484 : tempInt_ = -1; // reserve temp int for storing content-lenght value
179 33484 : state_ = expecting_newline_1;
180 33484 : return boost::indeterminate;
181 : }
182 6 : return false;
183 33484 : case expecting_newline_1:
184 33484 : if (input == '\n') {
185 33484 : state_ = header_line_start;
186 33484 : return boost::indeterminate;
187 : }
188 0 : return false;
189 105717 : case header_line_start:
190 105717 : if (input == '\r') {
191 30249 : state_ = expecting_newline_3;
192 30249 : return boost::indeterminate;
193 : }
194 : // A line starting with whitespace (obsolete line folding, RFC 9112 section 5.2)
195 : // is rejected, as is any other byte that cannot start a header name
196 75468 : else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
197 495 : return false;
198 : }
199 : else {
200 74973 : if (++header_lines_ > max_header_lines) {
201 22 : return header_section_exceeded();
202 : }
203 74951 : tempString1_.clear();
204 74951 : tempString1_.push_back(input);
205 74951 : state_ = header_name;
206 74951 : return boost::indeterminate;
207 : }
208 : // field-line = field-name ":" OWS field-value OWS (RFC 9112 section 5): no whitespace
209 : // between the name and the colon (a header name is a token, so it fails here)
210 74951 : case header_name:
211 74951 : if (input == ':') {
212 74419 : tempString2_.clear();
213 74419 : state_ = space_before_header_value;
214 74419 : return boost::indeterminate;
215 : }
216 532 : else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
217 532 : return false;
218 : }
219 : else {
220 0 : tempString1_.push_back(input);
221 0 : return boost::indeterminate;
222 : }
223 : // Any SP and HTAB before the value are skipped, as Node, Go and Beast do; the value
224 : // may be empty
225 149014 : case space_before_header_value:
226 149014 : if (is_ows(input)) {
227 74595 : return boost::indeterminate;
228 : }
229 74419 : state_ = header_value;
230 : [[fallthrough]];
231 : // Trailing SP and HTAB are not part of the value (RFC 9110 section 5.5): trimmed
232 : // before the field is stored, so framing fields are validated without them
233 146876 : case header_value:
234 146876 : if (input == '\r') {
235 73502 : tempString2_.erase(tempString2_.find_last_not_of(" \t") + 1);
236 : // A single Host field (RFC 9110 section 7.2), whatever the values: two may
237 : // route differently along a proxy chain (Go rejects them too; Node keeps the
238 : // first)
239 73502 : if (::thinger::util::ascii::iequals(tempString1_, header::host)) {
240 33949 : if (host_field_) return false;
241 32772 : host_field_ = true;
242 : }
243 72325 : on_http_header(tempString1_, tempString2_);
244 72325 : state_ = expecting_newline_2;
245 72325 : return boost::indeterminate;
246 : }
247 73374 : else if (is_ctl(input) && input != '\t') {
248 896 : return false;
249 : }
250 : else {
251 72478 : tempString2_.push_back(input);
252 72478 : return boost::indeterminate;
253 : }
254 72325 : case expecting_newline_2:
255 72325 : if (input == '\n') {
256 72233 : state_ = header_line_start;
257 72233 : return boost::indeterminate;
258 : }
259 92 : return false;
260 30238 : case expecting_newline_3:
261 30238 : if (input == '\n'){
262 : // An HTTP/1.1 request carries a Host field, maybe empty (RFC 9110 section
263 : // 7.2), as Node and Go require; an HTTP/1.0 one may omit it
264 30238 : if (!host_field_ && req->get_http_version_minor() >= 1) {
265 388 : return false;
266 : }
267 29850 : if(headers_only_ || get_content_length() == 0){
268 29270 : return true;
269 : }
270 580 : state_ = content;
271 580 : return boost::indeterminate;
272 : }
273 0 : return false;
274 2900 : case content:
275 2900 : on_content(input);
276 2900 : if(get_content_read()<get_content_length()){
277 2320 : return boost::indeterminate;
278 : }
279 580 : return true;
280 0 : default:
281 0 : return false;
282 : }
283 : }
284 :
285 29836 : std::shared_ptr<http_request> request_factory::consume_request() {
286 29836 : std::shared_ptr<http_request> request(req);
287 29836 : req.reset();
288 29836 : state_ = method_start;
289 29836 : tempString1_.clear();
290 29836 : tempString2_.clear();
291 29836 : header_lines_ = 0;
292 29836 : header_section_size_ = 0;
293 29836 : header_section_too_large_ = false;
294 29836 : host_field_ = false;
295 29836 : return request;
296 : }
297 :
298 35392 : void request_factory::on_http_method(const std::string& method){
299 35392 : req->set_method(method);
300 35392 : }
301 :
302 0 : void request_factory::on_http_status_code(unsigned short status_code){
303 :
304 0 : }
305 :
306 33508 : void request_factory::on_http_uri(const std::string& uri){
307 33508 : req->set_uri(uri);
308 33508 : }
309 :
310 33492 : void request_factory::on_http_major_version(uint8_t major)
311 : {
312 33492 : req->set_http_version_major(major);
313 33492 : }
314 :
315 33484 : void request_factory::on_http_minor_version(uint16_t minor){
316 33484 : req->set_http_version_minor(minor);
317 33484 : }
318 :
319 72325 : void request_factory::on_http_header(const std::string& name, const std::string& value){
320 72325 : req->process_header(name, value);
321 72325 : }
322 :
323 2900 : void request_factory::on_content(char content){
324 2900 : req->get_body().push_back(content);
325 2900 : }
326 :
327 9122 : size_t request_factory::get_content_length(){
328 9122 : return req->get_content_length();
329 : }
330 :
331 2900 : size_t request_factory::get_content_read(){
332 2900 : return req->get_body().size();
333 : }
334 : }
|