Line data Source code
1 : #include <sstream>
2 : #include "response_factory.hpp"
3 : #include "../../util/logger.hpp"
4 : #include "../common/http_response.hpp"
5 :
6 : namespace thinger::http{
7 :
8 127263 : boost::tribool response_factory::consume(char input, bool head_request) {
9 : // keep control of headers size
10 127263 : if(state_<=expecting_newline_3){
11 83492 : headers_size_++;
12 83492 : if(headers_size_>MAX_HEADERS_SIZE) return false;
13 : }
14 : // parse response
15 127263 : switch (state_) {
16 819 : case http_version_h:
17 819 : if (input == 'H') {
18 819 : state_ = http_version_t_1;
19 819 : return boost::indeterminate;
20 : }
21 0 : return false;
22 819 : case http_version_t_1:
23 819 : if (input == 'T') {
24 819 : state_ = http_version_t_2;
25 819 : return boost::indeterminate;
26 : }
27 0 : return false;
28 819 : case http_version_t_2:
29 819 : if (input == 'T') {
30 819 : state_ = http_version_p;
31 819 : return boost::indeterminate;
32 : }
33 0 : return false;
34 819 : case http_version_p:
35 819 : if (input == 'P') {
36 819 : state_ = http_version_slash;
37 819 : return boost::indeterminate;
38 : }
39 0 : return false;
40 819 : case http_version_slash:
41 819 : if (input == '/') {
42 819 : state_ = http_version_major_start;
43 819 : return boost::indeterminate;
44 : }
45 0 : return false;
46 819 : case http_version_major_start:
47 819 : if (is_digit(input)) {
48 819 : tempInt_ = input - '0';
49 819 : state_ = http_version_major;
50 819 : return boost::indeterminate;
51 : }
52 0 : return false;
53 819 : case http_version_major:
54 819 : if (input == '.') {
55 819 : if(!resp) resp = std::make_shared<http_response>();
56 819 : on_http_major_version(tempInt_);
57 819 : state_ = http_version_minor_start;
58 819 : return boost::indeterminate;
59 : }
60 0 : else if (is_digit(input)) {
61 0 : tempInt_ = tempInt_ * 10 + input - '0';
62 0 : return boost::indeterminate;
63 : }
64 0 : return false;
65 819 : case http_version_minor_start:
66 819 : if (is_digit(input)) {
67 819 : tempInt_ = input - '0';
68 819 : state_ = http_version_minor;
69 819 : return boost::indeterminate;
70 : }
71 0 : return false;
72 819 : case http_version_minor:
73 819 : if (is_digit(input)) {
74 0 : tempInt_ = tempInt_ * 10 + input - '0';
75 0 : return boost::indeterminate;
76 : }
77 819 : else if(input == ' '){
78 819 : on_http_minor_version(tempInt_);
79 819 : tempInt_ = 0;
80 819 : state_ = status_code;
81 819 : return boost::indeterminate;
82 : }
83 0 : return false;
84 3276 : case status_code:
85 3276 : if (is_digit(input)) {
86 2457 : tempInt_ = tempInt_ * 10 + input - '0';
87 2457 : return boost::indeterminate;
88 819 : }else if(input == ' '){
89 819 : on_http_status_code(tempInt_);
90 819 : state_ = reason_phrase;
91 819 : return boost::indeterminate;
92 : }
93 0 : return false;
94 4796 : case reason_phrase:
95 4796 : if(input == '\r'){
96 819 : on_http_reason_phrase(tempString1_);
97 819 : state_ = expecting_newline_1;
98 819 : return boost::indeterminate;
99 3977 : }else if (is_char(input) || input == ' '){
100 3977 : tempString1_.push_back(input);
101 3977 : return boost::indeterminate;
102 : }
103 0 : return false;
104 819 : case expecting_newline_1:
105 819 : if (input == '\n') {
106 819 : state_ = header_line_start;
107 819 : return boost::indeterminate;
108 : }
109 0 : return false;
110 3334 : case header_line_start:
111 3334 : if (input == '\r') {
112 819 : state_ = expecting_newline_3;
113 819 : return boost::indeterminate;
114 : }
115 2515 : else if (!empty_headers() && (input == ' ' || input == '\t')) {
116 0 : state_ = header_lws;
117 0 : return boost::indeterminate;
118 : }
119 2515 : else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
120 0 : return false;
121 : }
122 : else {
123 2515 : tempString1_.clear();
124 2515 : tempString1_.push_back(input);
125 2515 : state_ = header_name;
126 2515 : return boost::indeterminate;
127 : }
128 0 : case header_lws:
129 0 : if (input == '\r') {
130 0 : state_ = expecting_newline_2;
131 0 : return boost::indeterminate;
132 : }
133 0 : else if (input == ' ' || input == '\t') {
134 0 : return boost::indeterminate;
135 : }
136 0 : else if (is_ctl(input)) {
137 0 : return false;
138 : }
139 : else {
140 0 : state_ = header_value;
141 0 : tempString1_.push_back(input);
142 0 : return boost::indeterminate;
143 : }
144 30271 : case header_name:
145 30271 : if (input == ':') {
146 2515 : state_ = space_before_header_value;
147 2515 : return boost::indeterminate;
148 : }
149 27756 : else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
150 0 : return false;
151 : }
152 : else {
153 27756 : tempString1_.push_back(input);
154 27756 : return boost::indeterminate;
155 : }
156 2515 : case space_before_header_value:
157 2515 : if (input == ' ') {
158 2515 : tempString2_.clear();
159 2515 : state_ = header_value;
160 2515 : return boost::indeterminate;
161 : }
162 0 : return false;
163 27776 : case header_value:
164 27776 : if (input == '\r') {
165 2515 : on_http_header(tempString1_, tempString2_);
166 2515 : state_ = expecting_newline_2;
167 2515 : return boost::indeterminate;
168 : }
169 25261 : else if (is_ctl(input)) {
170 0 : return false;
171 : }
172 : else {
173 25261 : tempString2_.push_back(input);
174 25261 : return boost::indeterminate;
175 : }
176 2515 : case expecting_newline_2:
177 2515 : if (input == '\n') {
178 2515 : state_ = header_line_start;
179 2515 : return boost::indeterminate;
180 : }
181 0 : return false;
182 819 : case expecting_newline_3:
183 819 : if (input == '\n'){
184 819 : if(content_==lenght_delimited && get_content_length()>0){
185 : // if it is a head request, we are not expecting a body
186 678 : if(head_request) return true;
187 :
188 : // set state to length delimited content
189 678 : state_ = lenght_delimited_content;
190 : // save content size to read in tempInt_
191 678 : tempInt_ = get_content_length();
192 : // verify we are abe to read the content size
193 678 : if(on_length_delimited_content(tempInt_)){
194 678 : return boost::indeterminate;
195 : }else{
196 0 : return false;
197 : }
198 141 : }else if(content_==chunked){
199 : // if it is a head request, we are not expecting a body
200 8 : if(head_request) return true;
201 :
202 8 : tempInt_ = 0;
203 8 : lastChunk_ = false;
204 8 : state_ = chunked_content_size;
205 8 : if(on_chunked_){
206 0 : on_chunked_(0, std::string());
207 : }
208 8 : return boost::indeterminate;
209 : }
210 133 : return true;
211 : }
212 0 : return false;
213 43589 : case lenght_delimited_content:
214 : // save content
215 43589 : on_content_data(input);
216 : // check if we are done reading the data
217 43589 : if(--tempInt_>0){
218 42913 : return boost::indeterminate;
219 : }else{
220 676 : return true;
221 : }
222 40 : case chunked_content_size:
223 : // read chunked content size
224 40 : if(is_hexadecimal(input)){
225 22 : tempInt_ = tempInt_ * 16 + get_hex_value(input);
226 22 : return boost::indeterminate;
227 18 : }else if(input == '\r'){
228 18 : if(tempInt_==0) lastChunk_ = true;
229 18 : state_ = chunked_content_size_expecting_n;
230 18 : return boost::indeterminate;
231 : }
232 0 : return false;
233 18 : case chunked_content_size_expecting_n:
234 18 : if(input=='\n'){
235 : // check we are able to read the expected size
236 18 : if(on_chunk_read(tempInt_)){
237 18 : state_ = chunked_content;
238 18 : return boost::indeterminate;
239 : }else{
240 0 : return false;
241 : }
242 : }
243 0 : return false;
244 106 : case chunked_content:
245 106 : if(tempInt_>0){
246 88 : on_content_data(input);
247 88 : tempInt_--;
248 88 : return boost::indeterminate;
249 18 : }else if(input=='\r'){
250 18 : state_ = chunked_content_expecting_n;
251 18 : return boost::indeterminate;
252 : }
253 0 : return false;
254 18 : case chunked_content_expecting_n:
255 18 : if(input=='\n'){
256 18 : if(lastChunk_){
257 8 : if(on_chunked_) on_chunked_(2, std::string());
258 8 : return true;
259 : }else{
260 10 : tempInt_ = 0;
261 10 : state_ = chunked_content_size;
262 :
263 : // Call streaming callback if set
264 10 : if(on_streaming_ && !resp->get_content().empty()){
265 0 : streaming_downloaded_ += resp->get_content().size();
266 : // For chunked, total is 0 (unknown)
267 0 : if(!on_streaming_(resp->get_content(), streaming_downloaded_, 0)){
268 0 : streaming_aborted_ = true;
269 0 : return false;
270 : }
271 0 : resp->get_content().clear();
272 : }
273 : // Legacy chunked callback
274 10 : else if(on_chunked_){
275 0 : on_chunked_(1, resp->get_content());
276 0 : resp->get_content().clear();
277 : }
278 10 : return boost::indeterminate;
279 : }
280 : }
281 0 : return false;
282 0 : default:
283 0 : return false;
284 : }
285 : }
286 :
287 34248 : bool response_factory::is_char(char c) {
288 34248 : return c >= 0 && c <= 127;
289 : }
290 :
291 55532 : bool response_factory::is_ctl(char c) {
292 55532 : return (c >= 0 && c <= 31) || (c == 127);
293 : }
294 :
295 30271 : bool response_factory::is_tspecial(char c) {
296 30271 : switch (c) {
297 0 : case '(':
298 : case ')':
299 : case '<':
300 : case '>':
301 : case '@':
302 : case ',':
303 : case ';':
304 : case ':':
305 : case '\\':
306 : case '"':
307 : case '/':
308 : case '[':
309 : case ']':
310 : case '?':
311 : case '=':
312 : case '{':
313 : case '}':
314 : case ' ':
315 : case '\t':
316 0 : return true;
317 30271 : default:
318 30271 : return false;
319 : }
320 : }
321 :
322 5733 : bool response_factory::is_digit(char c) {
323 5733 : return c >= '0' && c <= '9';
324 : }
325 :
326 40 : bool response_factory::is_hexadecimal(char c){
327 40 : return (c >= '0' && c <= '9') || (c >= 'a' && c<='f') || (c>='A' && c <= 'F');
328 : }
329 :
330 22 : uint8_t response_factory::get_hex_value(char c)
331 : {
332 22 : if(c <= '9'){
333 22 : return c - '0';
334 0 : }else if(c<='F'){
335 0 : return c - 55;
336 0 : }else if(c<='f'){
337 0 : return c - 87;
338 : }
339 0 : return 0;
340 : }
341 :
342 819 : std::shared_ptr<http_response> response_factory::consume_response() {
343 819 : std::shared_ptr<http_response> request(resp);
344 819 : reset();
345 819 : request->log("HTTP CLIENT RESPONSE", 0);
346 819 : return request;
347 0 : }
348 :
349 1800 : void response_factory::reset() {
350 1800 : resp.reset();
351 1800 : content_ = none;
352 1800 : state_ = http_version_h;
353 1800 : tempString1_.clear();
354 1800 : tempString2_.clear();
355 1800 : headers_size_ = 0;
356 1800 : on_chunked_ = nullptr;
357 1800 : on_streaming_ = nullptr;
358 1800 : streaming_downloaded_ = 0;
359 1800 : streaming_aborted_ = false;
360 1800 : }
361 :
362 819 : void response_factory::on_http_status_code(unsigned short status_code){
363 819 : resp->set_status(status_code);
364 819 : }
365 :
366 819 : void response_factory::on_http_reason_phrase(const std::string& reason){
367 819 : resp->set_reason_phrase(reason);
368 819 : }
369 :
370 819 : void response_factory::on_http_major_version(uint8_t major)
371 : {
372 819 : resp->set_http_version_major(major);
373 819 : }
374 :
375 819 : void response_factory::on_http_minor_version(uint16_t minor){
376 819 : resp->set_http_version_minor(minor);
377 819 : }
378 :
379 18 : bool response_factory::on_chunk_read(size_t size){
380 18 : if(size==0) return true;
381 : // check that the size meets MAX_CONTENT_SIZE
382 10 : size_t expected_size = get_content_read() + size;
383 10 : if(expected_size>MAX_CONTENT_SIZE){
384 0 : LOG_ERROR("the response size exceeds the maximum allowed file size: %zu (%zu max)", size, MAX_CONTENT_SIZE);
385 0 : return false;
386 : }
387 : // upgrade buffer size
388 10 : resp->get_content().reserve(expected_size);
389 10 : return true;
390 : }
391 :
392 678 : bool response_factory::on_length_delimited_content(size_t size){
393 678 : if(size>MAX_CONTENT_SIZE){
394 0 : LOG_ERROR("the response size exceeds the maximum allowed file size: %zu (%zu max)", size, MAX_CONTENT_SIZE);
395 0 : return false;
396 : }
397 678 : resp->get_content().reserve(size);
398 678 : return true;
399 : }
400 :
401 2515 : void response_factory::on_http_header(const std::string& name, const std::string& value){
402 2515 : if(boost::iequals(name, http::header::transfer_encoding) && boost::iequals(value, "chunked")){
403 8 : content_ = chunked;
404 2507 : }else if(boost::iequals(name, http::header::content_length)){
405 704 : content_ = lenght_delimited;
406 : }
407 2515 : resp->process_header(name, value);
408 2515 : }
409 :
410 43677 : void response_factory::on_content_data(char content){
411 43677 : resp->get_content().push_back(content);
412 43677 : }
413 :
414 1384 : size_t response_factory::get_content_length(){
415 1384 : return resp->get_content_length();
416 : }
417 :
418 10 : size_t response_factory::get_content_read(){
419 10 : return resp->get_content().size();
420 : }
421 :
422 2515 : bool response_factory::empty_headers(){
423 2515 : return resp->empty_headers();
424 : }
425 :
426 2 : int response_factory::get_status_code() const {
427 2 : return resp ? resp->get_status_code() : 0;
428 : }
429 :
430 864 : void response_factory::setOnChunked(const std::function<void(int, const std::string&)>& onChunked){
431 864 : on_chunked_ = onChunked;
432 864 : }
433 :
434 2 : void response_factory::setOnStreaming(std::function<bool(const std::string_view&, size_t, size_t)> callback){
435 2 : on_streaming_ = std::move(callback);
436 2 : streaming_downloaded_ = 0;
437 2 : streaming_aborted_ = false;
438 2 : }
439 :
440 : }
|