LCOV - code coverage report
Current view: top level - http/client - response_factory.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 98.6 % 220 217
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 23 23

            Line data    Source code
       1              : #include "response_factory.hpp"
       2              : #include "../common/http_response.hpp"
       3              : #include "../common/syntax.hpp"
       4              : #include "../../util/logger.hpp"
       5              : #include <algorithm>
       6              : #include <cstring>
       7              : #include <utility>
       8              : 
       9              : namespace thinger::http {
      10              : 
      11              :     namespace {
      12              : 
      13              :         // Start of every status line: HTTP-version = "HTTP/1." DIGIT, then SP (RFC 9112,
      14              :         // section 2.3). Only major version 1 uses this message syntax; a higher minor version
      15              :         // is handled as 1.1 (RFC 9110, section 2.5)
      16              :         constexpr std::string_view version_prefix = "HTTP/1.";
      17              :         constexpr size_t status_prefix_size = 9;    // "HTTP/1.1 "
      18              : 
      19        42346 :         bool is_digit(char c) {
      20        42346 :             return c >= '0' && c <= '9';
      21              :         }
      22              : 
      23              :         // Whether the byte at `position` of a status line can be there, as far as the
      24              :         // version and the space after it are concerned
      25        76860 :         bool valid_status_prefix_char(size_t position, char c) {
      26        76860 :             if (position < version_prefix.size()) return c == version_prefix[position];
      27        17002 :             if (position == version_prefix.size()) return is_digit(c);
      28         8498 :             return position != status_prefix_size - 1 || c == ' ';
      29              :         }
      30              : 
      31              :         // Body bytes reserved at once for a Content-Length body (it grows past it as read)
      32              :         constexpr size_t max_reserve = 1024 * 1024;
      33              : 
      34              :     }
      35              : 
      36       190983 :     boost::tribool response_factory::parse_buffer(const char* data, size_t size, size_t& consumed) {
      37       190983 :         consumed = 0;
      38              :         while (true) {
      39       454933 :             if (state_ == state::done) return true;
      40       447116 :             if (state_ == state::error) return false;
      41       446483 :             if (consumed == size) return boost::indeterminate;
      42              : 
      43       263950 :             const char* begin = data + consumed;
      44       263950 :             size_t available = size - consumed;
      45       263950 :             switch (state_) {
      46       212740 :                 case state::status_line:
      47              :                 case state::header_lines:
      48       212740 :                     consumed += read_head(begin, available);
      49       212740 :                     break;
      50         9061 :                 case state::body_length:
      51         9061 :                     consumed += read_length(begin, available);
      52         9061 :                     break;
      53        42134 :                 case state::body_chunked:
      54        42134 :                     consumed += read_chunked(begin, available);
      55        42134 :                     break;
      56           15 :                 case state::body_until_close:
      57           15 :                     if (add_body(begin, available)) consumed += available;
      58           15 :                     break;
      59            0 :                 default:
      60            0 :                     break;
      61              :             }
      62       263950 :         }
      63              :     }
      64              : 
      65       212740 :     size_t response_factory::read_head(const char* data, size_t size) {
      66              :         // Up to the end of the line (LF), if it is there
      67       212740 :         auto newline = static_cast<const char*>(std::memchr(data, '\n', size));
      68       212740 :         size_t bytes = newline ? static_cast<size_t>(newline - data) + 1 : size;
      69              : 
      70              :         // Not a response from the first bytes (e.g. another protocol): fail right away
      71              :         // instead of waiting for the end of a line that may never come
      72       212740 :         if (state_ == state::status_line) {
      73        91007 :             for (size_t position = line_.size(); position < status_prefix_size && position - line_.size() < bytes; ++position) {
      74        76860 :                 if (!valid_status_prefix_char(position, data[position - line_.size()])) {
      75           81 :                     fail(client_error::invalid_status_line);
      76           81 :                     return 0;
      77              :                 }
      78              :             }
      79              :         }
      80              : 
      81              :         // The head is buffered whole: bound it
      82       212659 :         if (bytes > max_header_section_size - header_section_size_) {
      83           12 :             fail(client_error::header_section_too_large);
      84           12 :             return 0;
      85              :         }
      86       212647 :         header_section_size_ += bytes;
      87              : 
      88       212647 :         if (!newline) {
      89       177113 :             line_.append(data, bytes);
      90       177113 :             return bytes;
      91              :         }
      92        35534 :         if (line_.empty()) {
      93        33105 :             process_line({data, bytes});
      94              :         } else {
      95         2429 :             line_.append(data, bytes);
      96         2429 :             process_line(line_);
      97              :         }
      98        35534 :         line_.clear();
      99        35534 :         return bytes;
     100              :     }
     101              : 
     102        35534 :     void response_factory::process_line(std::string_view line) {
     103              :         // Every line ends with CRLF: a bare LF is not a line terminator (and a bare CR, a
     104              :         // control character, is never valid within a line)
     105        35534 :         if (line.size() < 2 || line[line.size() - 2] != '\r') {
     106           27 :             fail(state_ == state::status_line ? client_error::invalid_status_line : client_error::invalid_header);
     107           27 :             return;
     108              :         }
     109        35507 :         line.remove_suffix(2);
     110              : 
     111        35507 :         if (state_ == state::status_line) {
     112         8471 :             process_status_line(line);
     113        27036 :         } else if (line.empty()) {
     114         8267 :             process_head();
     115              :         } else {
     116        18769 :             process_header_line(line);
     117              :         }
     118              :     }
     119              : 
     120         8471 :     void response_factory::process_status_line(std::string_view line) {
     121              :         // status-line = HTTP-version SP status-code SP [ reason-phrase ] (RFC 9112, section
     122              :         // 4), with a three-digit status code from 100 to 599 (RFC 9110, section 15). As in
     123              :         // llhttp, the space after the status code may be missing when there is no reason
     124        16942 :         if (line.size() < status_prefix_size + 3 || !line.starts_with(version_prefix) || !is_digit(line[7]) || line[8] != ' ' ||
     125        16942 :             !is_digit(line[9]) || !is_digit(line[10]) || !is_digit(line[11])) {
     126           33 :             fail(client_error::invalid_status_line);
     127           93 :             return;
     128              :         }
     129         8438 :         int status = (line[9] - '0') * 100 + (line[10] - '0') * 10 + (line[11] - '0');
     130         8438 :         std::string_view reason;
     131         8438 :         if (line.size() > 12) {
     132         8429 :             if (line[12] != ' ') {
     133           24 :                 fail(client_error::invalid_status_line);
     134           24 :                 return;
     135              :             }
     136         8405 :             reason = line.substr(13);
     137              :         }
     138         8414 :         if (status < 100 || status > 599 || !syntax::is_field_value(reason)) {
     139           36 :             fail(client_error::invalid_status_line);
     140           36 :             return;
     141              :         }
     142              : 
     143         8378 :         resp_ = std::make_shared<http_response>();
     144         8378 :         resp_->set_http_version_major(1);
     145         8378 :         resp_->set_http_version_minor(static_cast<uint8_t>(line[7] - '0'));
     146         8378 :         resp_->set_status(static_cast<uint16_t>(status));
     147        16756 :         resp_->set_reason_phrase(std::string(reason));
     148         8378 :         state_ = state::header_lines;
     149              :     }
     150              : 
     151        18769 :     void response_factory::process_header_line(std::string_view line) {
     152              :         // field-line = field-name ":" OWS field-value OWS (RFC 9112, section 5). A line
     153              :         // starting with whitespace (obsolete line folding, section 5.2) has no valid name
     154        18769 :         auto colon = line.find(':');
     155        18769 :         if (colon == std::string_view::npos || !syntax::is_token(line.substr(0, colon))) {
     156           57 :             fail(client_error::invalid_header);
     157           81 :             return;
     158              :         }
     159        18712 :         auto value = syntax::trim(line.substr(colon + 1));
     160        18712 :         if (!syntax::is_field_value(value)) {
     161           18 :             fail(client_error::invalid_header);
     162           18 :             return;
     163              :         }
     164        18694 :         if (++header_lines_ > max_header_lines) {
     165            6 :             fail(client_error::header_section_too_large);
     166            6 :             return;
     167              :         }
     168        74752 :         resp_->process_header(std::string(line.substr(0, colon)), std::string(value));
     169              :     }
     170              : 
     171         8267 :     void response_factory::process_head() {
     172              :         // Ambiguous framing fails whatever the status: the server is not doing what it says
     173         8267 :         if (!resp_->has_valid_framing()) {
     174          225 :             fail(client_error::invalid_framing);
     175          225 :             return;
     176              :         }
     177              : 
     178         8042 :         int status = resp_->get_status_code();
     179         8042 :         if (status < 200 && status != 101 && !headers_only_) {
     180              :             // 100 Continue, 103 Early Hints...: the final response follows
     181           42 :             start_response();
     182           42 :             return;
     183              :         }
     184              : 
     185         8000 :         framing_ = body_framing_of(status);
     186         8000 :         keep_alive_ = !switches_protocols(status) && framing_ != body_framing::until_close && resp_->keep_alive();
     187         8000 :         if (headers_only_) {
     188          567 :             state_ = state::done;
     189              :         } else {
     190         7433 :             start_body();
     191              :         }
     192              :     }
     193              : 
     194        15654 :     bool response_factory::switches_protocols(int status) const {
     195        15654 :         return status == 101 || (request_method_ == method::CONNECT && status >= 200 && status < 300);
     196              :     }
     197              : 
     198              :     // RFC 9112, section 6.3
     199         8000 :     response_factory::body_framing response_factory::body_framing_of(int status) const {
     200         8000 :         if (status < 200 || switches_protocols(status) || request_method_ == method::HEAD || status == 204 || status == 304) {
     201          477 :             return body_framing::none;
     202              :         }
     203         7523 :         if (resp_->is_chunked_transfer()) return body_framing::chunked;
     204         7297 :         if (resp_->has_content_length_header()) return body_framing::content_length;
     205           42 :         return body_framing::until_close;
     206              :     }
     207              : 
     208         7433 :     void response_factory::start_body() {
     209         7433 :         switch (framing_) {
     210          399 :             case body_framing::none:
     211          399 :                 state_ = state::done;
     212          399 :                 break;
     213         6853 :             case body_framing::content_length:
     214         6853 :                 remaining_ = resp_->get_content_length();
     215         6853 :                 if (remaining_ == 0) {
     216          396 :                     state_ = state::done;
     217         6457 :                 } else if (!on_streaming_ && remaining_ > max_content_size_) {
     218            3 :                     LOG_ERROR("the response size exceeds the maximum allowed size: {} ({} max)", remaining_, max_content_size_);
     219            3 :                     fail(client_error::body_too_large);
     220              :                 } else {
     221         6454 :                     if (!on_streaming_) resp_->get_content().reserve(std::min(remaining_, max_reserve));
     222         6454 :                     state_ = state::body_length;
     223              :                 }
     224         6853 :                 break;
     225          163 :             case body_framing::chunked:
     226          163 :                 decoder_ = {};
     227          163 :                 if (on_chunked_ && !on_streaming_) on_chunked_(0, std::string());
     228          163 :                 state_ = state::body_chunked;
     229          163 :                 break;
     230           18 :             case body_framing::until_close:
     231           18 :                 state_ = state::body_until_close;
     232           18 :                 break;
     233              :         }
     234         7433 :     }
     235              : 
     236         9061 :     size_t response_factory::read_length(const char* data, size_t size) {
     237         9061 :         size_t bytes = std::min(size, remaining_);
     238         9061 :         if (!add_body(data, bytes)) return 0;
     239         9046 :         remaining_ -= bytes;
     240         9046 :         if (remaining_ == 0) state_ = state::done;
     241         9046 :         return bytes;
     242              :     }
     243              : 
     244        42134 :     size_t response_factory::read_chunked(const char* data, size_t size) {
     245              :         // Chunk data is taken straight from the input, the framing goes through the decoder
     246        42134 :         if (size_t pending = decoder_.data_pending()) {
     247        20867 :             size_t bytes = std::min(size, pending);
     248        41722 :             if (!add_body(data, bytes)) return 0;
     249        20855 :             decoder_.data_consumed(bytes);
     250        20855 :             if (on_chunked_ && !on_streaming_ && decoder_.data_pending() == 0) {
     251           24 :                 on_chunked_(1, resp_->get_content());
     252           24 :                 resp_->get_content().clear();
     253              :             }
     254        20855 :             return bytes;
     255              :         }
     256              : 
     257        21267 :         size_t produced = 0;
     258        21267 :         size_t bytes = decoder_.decode(reinterpret_cast<const uint8_t*>(data), size, nullptr, 0, produced);
     259        21267 :         if (decoder_.failed()) {
     260           81 :             fail(client_error::malformed_body);
     261        21186 :         } else if (decoder_.done()) {
     262           58 :             if (on_chunked_ && !on_streaming_) on_chunked_(2, std::string());
     263           58 :             state_ = state::done;
     264              :         }
     265        21267 :         return bytes;
     266              :     }
     267              : 
     268        29943 :     bool response_factory::add_body(const char* data, size_t size) {
     269        29943 :         if (on_streaming_) {
     270        20852 :             streaming_downloaded_ += size;
     271        20852 :             size_t total = framing_ == body_framing::content_length ? resp_->get_content_length() : 0;
     272        20852 :             if (!on_streaming_(std::string_view(data, size), streaming_downloaded_, total)) {
     273           24 :                 fail(client_error::aborted);
     274           24 :                 return false;
     275              :             }
     276        20828 :             return true;
     277              :         }
     278         9091 :         auto& content = resp_->get_content();
     279         9091 :         if (size > max_content_size_ - std::min(content.size(), max_content_size_)) {
     280            6 :             LOG_ERROR("the response size exceeds the maximum allowed size: {} max", max_content_size_);
     281            6 :             fail(client_error::body_too_large);
     282            6 :             return false;
     283              :         }
     284         9085 :         content.append(data, size);
     285         9085 :         return true;
     286              :     }
     287              : 
     288          123 :     bool response_factory::end_of_stream() {
     289          123 :         if (state_ == state::body_until_close) state_ = state::done;
     290          123 :         if (state_ == state::done) return true;
     291          108 :         if (state_ != state::error) fail(client_error::incomplete_response);
     292          108 :         return false;
     293              :     }
     294              : 
     295         7679 :     bool response_factory::keep_alive() const {
     296         7679 :         return state_ == state::done && keep_alive_;
     297              :     }
     298              : 
     299          741 :     void response_factory::fail(client_error error) {
     300          741 :         error_ = error;
     301          741 :         state_ = state::error;
     302          741 :     }
     303              : 
     304        19064 :     void response_factory::start_response() {
     305        19064 :         resp_.reset();
     306        19064 :         state_ = state::status_line;
     307        19064 :         framing_ = body_framing::none;
     308        19064 :         keep_alive_ = false;
     309        19064 :         line_.clear();
     310        19064 :         header_section_size_ = 0;
     311        19064 :         header_lines_ = 0;
     312        19064 :         remaining_ = 0;
     313        19064 :     }
     314              : 
     315         7832 :     std::shared_ptr<http_response> response_factory::consume_response() {
     316         7832 :         auto response = std::exchange(resp_, nullptr);
     317         7832 :         if (response) response->log("HTTP CLIENT RESPONSE", body_logging_);
     318         7832 :         return response;
     319            0 :     }
     320              : 
     321        19022 :     void response_factory::reset() {
     322        19022 :         start_response();
     323        19022 :         request_method_ = method::GET;
     324        19022 :         error_ = client_error::none;
     325        19022 :         on_chunked_ = nullptr;
     326        19022 :         on_streaming_ = nullptr;
     327        19022 :         streaming_downloaded_ = 0;
     328        19022 :     }
     329              : 
     330           86 :     int response_factory::get_status_code() const {
     331           86 :         return resp_ ? resp_->get_status_code() : 0;
     332              :     }
     333              : 
     334         8409 :     void response_factory::setOnChunked(const std::function<void(int, const std::string&)>& onChunked) {
     335         8409 :         on_chunked_ = onChunked;
     336         8409 :     }
     337              : 
     338          122 :     void response_factory::setOnStreaming(std::function<bool(const std::string_view&, size_t, size_t)> callback) {
     339          122 :         on_streaming_ = std::move(callback);
     340          122 :         streaming_downloaded_ = 0;
     341          122 :     }
     342              : 
     343              : }
        

Generated by: LCOV version 2.0-1