LCOV - code coverage report
Current view: top level - http/client - response_factory.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 78.5 % 298 234
Test Date: 2026-02-20 15:38:22 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       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              : }
        

Generated by: LCOV version 2.0-1