Line data Source code
1 : #ifndef HTTP_REQUEST_PARSER_HPP
2 : #define HTTP_REQUEST_PARSER_HPP
3 :
4 : #include <iterator>
5 : #include <memory>
6 : #include <string>
7 : #include <boost/logic/tribool.hpp>
8 : #include <boost/tuple/tuple.hpp>
9 : #include <boost/lexical_cast.hpp>
10 :
11 : namespace thinger::http {
12 :
13 : class http_request;
14 :
15 : /// Parser for incoming requests.
16 : class request_factory {
17 : public:
18 : /// Limits of the header section (request line and header fields): a request over
19 : /// them fails to parse, with header_section_too_large() set
20 : static constexpr size_t max_header_lines = 100;
21 : static constexpr size_t max_header_section_size = 16 * 1024;
22 :
23 : /// Construct ready to parse the http_request method.
24 : request_factory();
25 :
26 : /// Parse some data. The tribool return value is true when a complete http_request
27 : /// has been parsed, false if the data is invalid, indeterminate when more
28 : /// data is required. The InputIterator return value indicates how much of the
29 : /// input has been consumed.
30 : template<typename InputIterator>
31 48161 : boost::tribool parse(InputIterator& begin, InputIterator end) {
32 1215325 : while (begin != end) {
33 : // The bytes of the uri, a header name or a header value are appended at once,
34 : // as consume() would one by one (up to the header section limit: the byte
35 : // over it is left to consume(), which fails)
36 1202544 : if (std::string* target = run_target()) {
37 360513 : size_t room = max_header_section_size - header_section_size_;
38 360513 : auto run_end = begin;
39 360513 : size_t size = 0;
40 2531649 : while (run_end != end && size < room && is_run_char(static_cast<char>(*run_end))) {
41 2171136 : ++run_end;
42 2171136 : ++size;
43 : }
44 360513 : if (size > 0) {
45 : if constexpr (std::contiguous_iterator<InputIterator>) {
46 : // (from a range of other than chars, append() builds a temporary string)
47 177726 : target->append(reinterpret_cast<const char*>(std::to_address(begin)), size);
48 : } else {
49 : target->append(begin, run_end);
50 : }
51 177726 : header_section_size_ += size;
52 177726 : begin = run_end;
53 177726 : continue;
54 : }
55 : }
56 1024818 : boost::tribool result = consume(*begin++);
57 : // parsed completed or parse failed
58 1024818 : if (result || !result)
59 35380 : return result;
60 : }
61 : // still not finished
62 12781 : return boost::indeterminate;
63 : }
64 :
65 23974 : void set_headers_only(bool headers_only) {
66 23974 : headers_only_ = headers_only;
67 23974 : }
68 :
69 6 : bool get_headers_only() const {
70 6 : return headers_only_;
71 : }
72 :
73 : /// Whether no part of a request was parsed since the last one
74 252 : bool idle() const {
75 252 : return state_ == method_start;
76 : }
77 :
78 : /// Whether the last parse failed for exceeding the header section limits
79 372 : bool header_section_too_large() const {
80 372 : return header_section_too_large_;
81 : }
82 :
83 : std::shared_ptr<http_request> consume_request();
84 :
85 :
86 : void on_http_method(const std::string& method);
87 :
88 : void on_http_status_code(unsigned short status_code);
89 :
90 : void on_http_uri(const std::string& uri);
91 :
92 : void on_http_major_version(uint8_t major);
93 :
94 : void on_http_minor_version(uint16_t minor);
95 :
96 : void on_http_header(const std::string& name, const std::string& value);
97 :
98 : void on_content(char content);
99 :
100 : size_t get_content_length();
101 :
102 : size_t get_content_read();
103 :
104 : private:
105 : /// Handle the next character of input.
106 : boost::tribool consume(char input);
107 :
108 : /// String a byte that consume() only appends in the current state would go to (the
109 : /// uri, a header name or a header value), null in the other states
110 1202544 : std::string* run_target() {
111 1202544 : switch (state_) {
112 71454 : case uri: return &tempString1_;
113 151224 : case header_name: return &tempString1_;
114 137835 : case header_value: return &tempString2_;
115 842031 : default: return nullptr;
116 : }
117 : }
118 :
119 : /// Whether consume() only appends `c` in the current state (see run_target)
120 2526769 : bool is_run_char(char c) const {
121 2526769 : switch (state_) {
122 571420 : case uri: return c != ' ' && is_char(c) && !is_ctl(c);
123 709725 : case header_name: return is_char(c) && !is_ctl(c) && !is_tspecial(c);
124 1245624 : case header_value: return !is_ctl(c) || c == '\t';
125 0 : default: return false;
126 : }
127 : }
128 :
129 : /// Check if a byte is optional whitespace (OWS, RFC 9110 section 5.6.3).
130 149014 : static bool is_ows(int c) {
131 149014 : return c == ' ' || c == '\t';
132 : }
133 :
134 : /// Check if a byte is an HTTP character.
135 1398226 : static bool is_char(int c) {
136 1398226 : return c >= 0 && c <= 127;
137 : }
138 :
139 : /// Check if a byte is an HTTP control character.
140 2717146 : static bool is_ctl(int c) {
141 2717146 : return (c >= 0 && c <= 31) || (c == 127);
142 : }
143 :
144 : /// Check if a byte is defined as an HTTP special character.
145 896312 : static bool is_tspecial(int c) {
146 896312 : switch (c) {
147 149724 : case '(': case ')': case '<': case '>': case '@': case ',': case ';': case ':':
148 : case '\\': case '"': case '/': case '[': case ']': case '?': case '=': case '{':
149 : case '}': case ' ': case '\t':
150 149724 : return true;
151 746588 : default:
152 746588 : return false;
153 : }
154 : }
155 :
156 : /// Check if a byte is a digit.
157 33492 : static bool is_digit(int c) {
158 33492 : return c >= '0' && c <= '9';
159 : }
160 :
161 : std::shared_ptr<http_request> req;
162 :
163 : std::string tempString1_;
164 : std::string tempString2_;
165 : size_t tempInt_;
166 : bool headers_only_ = false;
167 : size_t header_lines_ = 0;
168 : size_t header_section_size_ = 0;
169 : bool header_section_too_large_ = false;
170 : bool host_field_ = false;
171 :
172 : /// Fail the parsing for exceeding the header section limits
173 60 : bool header_section_exceeded() {
174 60 : header_section_too_large_ = true;
175 60 : return false;
176 : }
177 :
178 : /// The current state of the parser.
179 : enum state {
180 : method_start,
181 : method,
182 : uri,
183 : http_version_h,
184 : http_version_t_1,
185 : http_version_t_2,
186 : http_version_p,
187 : http_version_slash,
188 : http_version_major_start,
189 : http_version_major,
190 : http_version_minor_start,
191 : http_version_minor,
192 : expecting_newline_1,
193 : header_line_start,
194 : header_name,
195 : space_before_header_value,
196 : header_value,
197 : expecting_newline_2,
198 : expecting_newline_3,
199 : content
200 : } state_;
201 : };
202 : }
203 :
204 : #endif // HTTP_REQUEST_PARSER_HPP
|