LCOV - code coverage report
Current view: top level - http/client - response_factory.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 78.7 % 300 236
Test Date: 2026-04-21 17:49:55 Functions: 100.0 % 23 23

            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       142400 :     boost::tribool response_factory::consume(char input, bool head_request) {
       9              :         // keep control of headers size
      10       142400 :         if(state_<=expecting_newline_3){
      11        94568 :             headers_size_++;
      12        94568 :             if(headers_size_>MAX_HEADERS_SIZE) return false;
      13              :         }
      14              :         // parse response
      15       142400 :         switch (state_) {
      16          926 :             case http_version_h:
      17          926 :                 if (input == 'H') {
      18          926 :                     state_ = http_version_t_1;
      19          926 :                     return boost::indeterminate;
      20              :                 }
      21            0 :                 return false;
      22          926 :             case http_version_t_1:
      23          926 :                 if (input == 'T') {
      24          926 :                     state_ = http_version_t_2;
      25          926 :                     return boost::indeterminate;
      26              :                 }
      27            0 :                 return false;
      28          926 :             case http_version_t_2:
      29          926 :                 if (input == 'T') {
      30          926 :                     state_ = http_version_p;
      31          926 :                     return boost::indeterminate;
      32              :                 }
      33            0 :                 return false;
      34          926 :             case http_version_p:
      35          926 :                 if (input == 'P') {
      36          926 :                     state_ = http_version_slash;
      37          926 :                     return boost::indeterminate;
      38              :                 }
      39            0 :                 return false;
      40          926 :             case http_version_slash:
      41          926 :                 if (input == '/') {
      42          926 :                     state_ = http_version_major_start;
      43          926 :                     return boost::indeterminate;
      44              :                 }
      45            0 :                 return false;
      46          926 :             case http_version_major_start:
      47          926 :                 if (is_digit(input)) {
      48          926 :                     tempInt_ = input - '0';
      49          926 :                     state_ = http_version_major;
      50          926 :                     return boost::indeterminate;
      51              :                 }
      52            0 :                 return false;
      53          926 :             case http_version_major:
      54          926 :                 if (input == '.') {
      55          926 :                     if(!resp) resp = std::make_shared<http_response>();
      56          926 :                     on_http_major_version(tempInt_);
      57          926 :                     state_ = http_version_minor_start;
      58          926 :                     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          926 :             case http_version_minor_start:
      66          926 :                 if (is_digit(input)) {
      67          926 :                     tempInt_ = input - '0';
      68          926 :                     state_ = http_version_minor;
      69          926 :                     return boost::indeterminate;
      70              :                 }
      71            0 :                 return false;
      72          926 :             case http_version_minor:
      73          926 :                 if (is_digit(input)) {
      74            0 :                     tempInt_ = tempInt_ * 10 + input - '0';
      75            0 :                     return boost::indeterminate;
      76              :                 }
      77          926 :                 else if(input == ' '){
      78          926 :                     on_http_minor_version(tempInt_);
      79          926 :                     tempInt_ = 0;
      80          926 :                     state_ = status_code;
      81          926 :                     return boost::indeterminate;
      82              :                 }
      83            0 :                 return false;
      84         3704 :             case status_code:
      85         3704 :                 if (is_digit(input)) {
      86         2778 :                     tempInt_ = tempInt_ * 10 + input - '0';
      87         2778 :                     return boost::indeterminate;
      88          926 :                 }else if(input == ' '){
      89          926 :                     on_http_status_code(tempInt_);
      90          926 :                     state_ = reason_phrase;
      91          926 :                     return boost::indeterminate;
      92              :                 }
      93            0 :                 return false;
      94         5541 :             case reason_phrase:
      95         5541 :                 if(input == '\r'){
      96          926 :                     on_http_reason_phrase(tempString1_);
      97          926 :                     state_ = expecting_newline_1;
      98          926 :                     return boost::indeterminate;
      99         4615 :                 }else if (is_char(input) || input == ' '){
     100         4615 :                     tempString1_.push_back(input);
     101         4615 :                     return boost::indeterminate;
     102              :                 }
     103            0 :                 return false;
     104          926 :             case expecting_newline_1:
     105          926 :                 if (input == '\n') {
     106          926 :                     state_ = header_line_start;
     107          926 :                     return boost::indeterminate;
     108              :                 }
     109            0 :                 return false;
     110         3776 :             case header_line_start:
     111         3776 :                 if (input == '\r') {
     112          926 :                     state_ = expecting_newline_3;
     113          926 :                     return boost::indeterminate;
     114              :                 }
     115         2850 :                 else if (!empty_headers() && (input == ' ' || input == '\t')) {
     116            0 :                     state_ = header_lws;
     117            0 :                     return boost::indeterminate;
     118              :                 }
     119         2850 :                 else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
     120            0 :                     return false;
     121              :                 }
     122              :                 else {
     123         2850 :                     tempString1_.clear();
     124         2850 :                     tempString1_.push_back(input);
     125         2850 :                     state_ = header_name;
     126         2850 :                     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        34290 :             case header_name:
     145        34290 :                 if (input == ':') {
     146         2850 :                     state_ = space_before_header_value;
     147         2850 :                     return boost::indeterminate;
     148              :                 }
     149        31440 :                 else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) {
     150            0 :                     return false;
     151              :                 }
     152              :                 else {
     153        31440 :                     tempString1_.push_back(input);
     154        31440 :                     return boost::indeterminate;
     155              :                 }
     156         2850 :             case space_before_header_value:
     157         2850 :                 if (input == ' ') {
     158         2850 :                     tempString2_.clear();
     159         2850 :                     state_ = header_value;
     160         2850 :                     return boost::indeterminate;
     161              :                 }
     162            0 :                 return false;
     163        31371 :             case header_value:
     164        31371 :                 if (input == '\r') {
     165         2850 :                     on_http_header(tempString1_, tempString2_);
     166         2850 :                     state_ = expecting_newline_2;
     167         2850 :                     return boost::indeterminate;
     168              :                 }
     169        28521 :                 else if (is_ctl(input)) {
     170            0 :                     return false;
     171              :                 }
     172              :                 else {
     173        28521 :                     tempString2_.push_back(input);
     174        28521 :                     return boost::indeterminate;
     175              :                 }
     176         2850 :             case expecting_newline_2:
     177         2850 :                 if (input == '\n') {
     178         2850 :                     state_ = header_line_start;
     179         2850 :                     return boost::indeterminate;
     180              :                 }
     181            0 :                 return false;
     182          926 :             case expecting_newline_3:
     183          926 :                 if (input == '\n'){
     184          926 :                     if(content_==lenght_delimited && get_content_length()>0){
     185              :                         // if it is a head request, we are not expecting a body
     186          777 :                         if(head_request) return true;
     187              : 
     188              :                         // set state to length delimited content
     189          777 :                         state_ = lenght_delimited_content;
     190              :                         // save content size to read in tempInt_
     191          777 :                         tempInt_ = get_content_length();
     192              :                         // verify we are abe to read the content size
     193          777 :                         if(on_length_delimited_content(tempInt_)){
     194          777 :                             return boost::indeterminate;
     195              :                         }else{
     196            0 :                             return false;
     197              :                         }
     198          149 :                     }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          141 :                     return true;
     211              :                 }
     212            0 :                 return false;
     213        47650 :             case lenght_delimited_content:
     214              :                 // save content
     215        47650 :                 on_content_data(input);
     216              :                 // check if we are done reading the data
     217        47650 :                 if(--tempInt_>0){
     218        46875 :                     return boost::indeterminate;
     219              :                 }else{
     220          775 :                     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        38905 :     bool response_factory::is_char(char c) {
     288        38905 :         return c >= 0 && c <= 127;
     289              :     }
     290              : 
     291        62811 :     bool response_factory::is_ctl(char c) {
     292        62811 :         return (c >= 0 && c <= 31) || (c == 127);
     293              :     }
     294              : 
     295        34290 :     bool response_factory::is_tspecial(char c) {
     296        34290 :         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        34290 :             default:
     318        34290 :                 return false;
     319              :         }
     320              :     }
     321              : 
     322         6482 :     bool response_factory::is_digit(char c) {
     323         6482 :         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          926 :     std::shared_ptr<http_response> response_factory::consume_response() {
     343          926 :         std::shared_ptr<http_response> request(resp);
     344          926 :         reset();
     345          926 :         request->log("HTTP CLIENT RESPONSE", 0);
     346          926 :         return request;
     347            0 :     }
     348              : 
     349         2015 :     void response_factory::reset() {
     350         2015 :         resp.reset();
     351         2015 :         content_ = none;
     352         2015 :         state_ = http_version_h;
     353         2015 :         tempString1_.clear();
     354         2015 :         tempString2_.clear();
     355         2015 :         headers_size_ = 0;
     356         2015 :         on_chunked_ = nullptr;
     357         2015 :         on_streaming_ = nullptr;
     358         2015 :         streaming_downloaded_ = 0;
     359         2015 :         streaming_aborted_ = false;
     360         2015 :     }
     361              : 
     362          926 :     void response_factory::on_http_status_code(unsigned short status_code){
     363          926 :         resp->set_status(status_code);
     364          926 :     }
     365              : 
     366          926 :     void response_factory::on_http_reason_phrase(const std::string& reason){
     367          926 :         resp->set_reason_phrase(reason);
     368          926 :     }
     369              : 
     370          926 :     void response_factory::on_http_major_version(uint8_t major)
     371              :     {
     372          926 :         resp->set_http_version_major(major);
     373          926 :     }
     374              : 
     375          926 :     void response_factory::on_http_minor_version(uint16_t minor){
     376          926 :         resp->set_http_version_minor(minor);
     377          926 :     }
     378              : 
     379           18 :     bool response_factory::on_chunk_read(size_t size){
     380           18 :         if(size==0) return true;
     381              :         // In streaming mode the content is handed off chunk-by-chunk and never
     382              :         // accumulated, so the max_content_size_ buffer limit does not apply.
     383           10 :         if(on_streaming_) return true;
     384           10 :         size_t expected_size = get_content_read() +  size;
     385           10 :         if(expected_size>max_content_size_){
     386            0 :             LOG_ERROR("the response size exceeds the maximum allowed file size: %zu (%zu max)", size, max_content_size_);
     387            0 :             return false;
     388              :         }
     389              :         // upgrade buffer size
     390           10 :         resp->get_content().reserve(expected_size);
     391           10 :         return true;
     392              :     }
     393              : 
     394          777 :     bool response_factory::on_length_delimited_content(size_t size){
     395              :         // In streaming mode the content is handed off chunk-by-chunk and never
     396              :         // accumulated, so the max_content_size_ buffer limit does not apply.
     397          777 :         if(on_streaming_) return true;
     398          775 :         if(size>max_content_size_){
     399            0 :             LOG_ERROR("the response size exceeds the maximum allowed file size: %zu (%zu max)", size, max_content_size_);
     400            0 :             return false;
     401              :         }
     402          775 :         resp->get_content().reserve(size);
     403          775 :         return true;
     404              :     }
     405              : 
     406         2850 :     void response_factory::on_http_header(const std::string& name, const std::string& value){
     407         2850 :         if(boost::iequals(name, http::header::transfer_encoding) && boost::iequals(value, "chunked")){
     408            8 :             content_ = chunked;
     409         2842 :         }else if(boost::iequals(name, http::header::content_length)){
     410          811 :             content_ = lenght_delimited;
     411              :         }
     412         2850 :         resp->process_header(name, value);
     413         2850 :     }
     414              : 
     415        47738 :     void response_factory::on_content_data(char content){
     416        47738 :         resp->get_content().push_back(content);
     417        47738 :     }
     418              : 
     419         1590 :     size_t response_factory::get_content_length(){
     420         1590 :         return resp->get_content_length();
     421              :     }
     422              : 
     423           10 :     size_t response_factory::get_content_read(){
     424           10 :         return resp->get_content().size();
     425              :     }
     426              : 
     427         2850 :     bool response_factory::empty_headers(){
     428         2850 :         return resp->empty_headers();
     429              :     }
     430              : 
     431            2 :     int response_factory::get_status_code() const {
     432            2 :         return resp ? resp->get_status_code() : 0;
     433              :     }
     434              : 
     435          971 :     void response_factory::setOnChunked(const std::function<void(int, const std::string&)>& onChunked){
     436          971 :         on_chunked_ = onChunked;
     437          971 :     }
     438              : 
     439            2 :     void response_factory::setOnStreaming(std::function<bool(const std::string_view&, size_t, size_t)> callback){
     440            2 :         on_streaming_ = std::move(callback);
     441            2 :         streaming_downloaded_ = 0;
     442            2 :         streaming_aborted_ = false;
     443            2 :     }
     444              : 
     445              : }
        

Generated by: LCOV version 2.0-1