LCOV - code coverage report
Current view: top level - http/server - request.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 96.4 % 166 160
Test Date: 2026-11-10 10:38:07 Functions: 96.1 % 51 49

            Line data    Source code
       1              : #include "../../util/logger.hpp"
       2              : #include "../../util/compression.hpp"
       3              : #include <algorithm>
       4              : #include <cstdint>
       5              : #include <limits>
       6              : #include <utility>
       7              : 
       8              : #include "request.hpp"
       9              : #include "trusted_proxies.hpp"
      10              : #include "body_decoders.hpp"
      11              : #include "routing/route.hpp"
      12              : #include "virtual_host.hpp"
      13              : 
      14              : namespace thinger::http{
      15              : 
      16              :     using nlohmann::json;
      17              :     using std::string;
      18              : 
      19        90034 :     request::request(
      20              :         std::unique_ptr<detail::request_source> source,
      21              :         std::shared_ptr<http_stream> http_stream,
      22        90034 :         std::shared_ptr<http_request> http_request) :
      23        90034 :         http_stream_{std::move(http_stream)},
      24        90034 :         source_{std::move(source)},
      25       180068 :         http_request_{std::move(http_request)}
      26              :     {
      27        90034 :         if (http_request_) {
      28        90034 :             body_reader_.set_framing(http_request_->is_chunked_transfer(), http_request_->pending_body_size());
      29              :         }
      30        90034 :         body_reader_.set_limit(default_max_body_size);
      31        90034 :         if (source_) {
      32              :             // (capturing only `this` fits in the function, without allocating; the source's
      33              :             // coroutine is the read itself)
      34        27586 :             body_reader_.set_source([this](uint8_t* buffer, size_t size) {
      35        43903 :                 return source_->read_body_some(buffer, size);
      36              :             });
      37              :         }
      38        90034 :     }
      39              : 
      40        90034 :     request::~request()= default;
      41              : 
      42         2846 :     const string& request::operator[](const std::string& param) const{
      43         2846 :         return get_uri_parameter(param);
      44              :     }
      45              : 
      46           21 :     bool request::has(const std::string& param) const {
      47           21 :         return params_.contains(param);
      48              :     }
      49              : 
      50            2 :     bool request::erase(const std::string& param){
      51            2 :         return params_.erase(param)>0;
      52              :     }
      53              : 
      54       176752 :     std::shared_ptr<http_request> request::get_http_request(){
      55       176752 :         return http_request_;
      56              :     }
      57              : 
      58          276 :     std::shared_ptr<const http_request> request::get_http_request() const{
      59          276 :         return http_request_;
      60              :     }
      61              : 
      62        61525 :     std::shared_ptr<http_stream> request::get_http_stream() const {
      63        61525 :         return http_stream_;
      64              :     }
      65              : 
      66           28 :     void request::set_auth_user(const std::string& auth_user){
      67           28 :         auth_user_ = auth_user;
      68           28 :     }
      69              : 
      70           28 :     const std::string& request::get_auth_user() const{
      71           28 :         return auth_user_;
      72              :     }
      73              :     
      74        29370 :     void request::set_matched_route(const route* route) {
      75        29370 :         matched_route_ = route;
      76        29370 :     }
      77              :     
      78          209 :     const route* request::get_matched_route() const {
      79          209 :         return matched_route_;
      80              :     }
      81              :     
      82         5995 :     void request::set_uri_parameter(const std::string& param, const std::string& value){
      83              :         // erase all existing entries with the specified key
      84         5995 :         params_.erase(param);
      85              : 
      86              :         // insert the new key-value pair
      87         5995 :         params_.insert(std::make_pair(param, value));
      88         5995 :     }
      89              : 
      90            4 :     void request::add_uri_parameter(const std::string& param, const std::string& value){
      91            4 :         params_.insert({param, value});
      92            4 :     }
      93              : 
      94         2846 :     const std::string& request::get_uri_parameter(const std::string& param) const{
      95         2846 :         auto it = params_.find(param);
      96         2846 :         if(it!=params_.end()){
      97         2846 :             return it->second;
      98              :         }
      99            0 :         LOG_WARNING("cannot find required parameter: {}", param);
     100            0 :         static std::string empty_string;
     101            0 :         return empty_string;
     102              :     }
     103              : 
     104            0 :     const std::multimap<std::string, std::string>& request::get_uri_parameters() const{
     105            0 :         return params_;
     106              :     }
     107              : 
     108            2 :     void request::set_auth_groups(const std::set<std::string> &groups) {
     109            2 :         groups_ = groups;
     110            2 :     }
     111              : 
     112            2 :     const std::set<std::string> & request::get_auth_groups() const {
     113            2 :         return groups_;
     114              :     }
     115              : 
     116            2 :     std::string request::debug_parameters() const {
     117            2 :         std::stringstream str;
     118            6 :         for(const auto& param: params_){
     119            4 :             str << "(" << param.first << ":" << param.second << ") ";
     120              :         }
     121            4 :         return str.str();
     122            2 :     }
     123              : 
     124         2280 :     std::string request::get_peer_ip() const{
     125         2280 :         if (!peer_ip_.empty()) return peer_ip_;
     126         2238 :         return source_ ? source_->info().peer_ip : "";
     127              :     }
     128              : 
     129          243 :     connection_info request::connection() const {
     130          243 :         return source_ ? source_->info() : connection_info{};
     131              :     }
     132              : 
     133           51 :     std::optional<asio::tls_session> request::tls() const {
     134           51 :         return source_ ? source_->tls() : std::nullopt;
     135              :     }
     136              : 
     137        27301 :     bool request::misdirected() const {
     138        27301 :         if (!source_ || !http_request_ || !http_request_->is_ssl()) return false;
     139              :         // The name of the virtual host (see http_application::resolve_host())
     140         3860 :         const auto& host = http_request_->get_header(header::host);
     141         3860 :         return source_->misdirected(virtual_host::host_name(host.empty() ? http_request_->get_host() : host));
     142              :     }
     143              : 
     144          204 :     std::string request::get_request_ip() const{
     145          204 :         auto peer = get_peer_ip();
     146          204 :         if (!trusted_proxies_ || trusted_proxies_->empty() || !http_request_) return peer;
     147           81 :         return trusted_proxies_->resolve(peer, "http", *http_request_).ip;
     148          204 :     }
     149              : 
     150          111 :     std::string request::get_request_scheme() const{
     151          111 :         std::string scheme = http_request_ && http_request_->is_ssl() ? "https" : "http";
     152          111 :         if (!trusted_proxies_ || trusted_proxies_->empty() || !http_request_) return scheme;
     153           81 :         return trusted_proxies_->resolve(get_peer_ip(), scheme, *http_request_).scheme;
     154          111 :     }
     155              : 
     156            2 :     bool request::keep_alive() const{
     157            2 :         return http_request_ && http_request_->keep_alive();
     158              :     }
     159              : 
     160          357 :     cancel_reason request::cancelled() const {
     161          357 :         return http_stream_ ? http_stream_->cancelled() : cancel_reason::none;
     162              :     }
     163              : 
     164          657 :     thinger::awaitable<cancel_reason> request::client_gone() {
     165              :         if (http_stream_) co_return co_await http_stream_->wait_cancelled();
     166              :         // Never set: the wait ends when it is cancelled
     167              :         asio::async_event never(co_await boost::asio::this_coro::executor);
     168              :         co_await never.wait();
     169              :         co_return cancel_reason::none;
     170         1314 :     }
     171              : 
     172           51 :     boost::asio::cancellation_slot request::cancellation_slot() {
     173           51 :         return http_stream_ ? http_stream_->observer_slot() : boost::asio::cancellation_slot{};
     174              :     }
     175              : 
     176              :     // Convenience methods implementation
     177              : 
     178          428 :     std::string request::query(const std::string& key) const {
     179          428 :         if (http_request_ && http_request_->has_uri_parameter(key)) {
     180          160 :             return http_request_->get_uri_parameter(key);
     181              :         }
     182          536 :         return "";
     183              :     }
     184              : 
     185          130 :     std::string request::query(const std::string& key, const std::string& default_value) const {
     186          130 :         if (http_request_ && http_request_->has_uri_parameter(key)) {
     187          108 :             return http_request_->get_uri_parameter(key);
     188              :         }
     189           22 :         return default_value;
     190              :     }
     191              : 
     192          464 :     std::string request::body() const {
     193          464 :         return http_request_ ? http_request_->get_body() : "";
     194              :     }
     195              : 
     196           15 :     nlohmann::json request::json() const {
     197           15 :         if (!http_request_) {
     198            0 :             return nlohmann::json{};
     199              :         }
     200           15 :         const auto& content = http_request_->get_body();
     201           15 :         if (content.empty()) {
     202            2 :             return nlohmann::json{};
     203              :         }
     204           13 :         auto j = nlohmann::json::parse(content, nullptr, false);
     205           13 :         if (j.is_discarded()) {
     206            2 :             return nlohmann::json{};
     207              :         }
     208           11 :         return j;
     209           13 :     }
     210              : 
     211          875 :     std::string request::header(const std::string& key) const {
     212          875 :         return http_request_ ? http_request_->get_header(key) : "";
     213              :     }
     214              : 
     215         1022 :     const body_settings& request::get_body_settings() const {
     216         1022 :         return body_settings_ ? *body_settings_ : body_settings::defaults();
     217              :     }
     218              : 
     219              :     // --- Body reading ---
     220              : 
     221         4478 :     void request::set_read_ahead(const uint8_t* data, size_t size) {
     222         4478 :         body_reader_.set_read_ahead(data, size);
     223         4478 :     }
     224              : 
     225            8 :     size_t request::content_length() const {
     226            8 :         return http_request_ ? http_request_->get_content_length() : 0;
     227              :     }
     228              : 
     229          171 :     bool request::is_chunked() const {
     230          171 :         return http_request_ && http_request_->is_chunked_transfer();
     231              :     }
     232              : 
     233        17654 :     std::vector<uint8_t> request::take_read_ahead() {
     234        17654 :         return body_reader_.take_read_ahead();
     235              :     }
     236              : 
     237        43843 :     void request::note_body_read() {
     238        43843 :         if (body_reader_.has_pending() || body_reader_.error() != body_error::none) return;
     239              :         // What is left in the read-ahead follows the request (a pipelined request)
     240         2052 :         if (http_stream_) http_stream_->end_input(body_reader_.buffered() > 0);
     241              :     }
     242              : 
     243          336 :     thinger::awaitable<size_t> request::read(uint8_t* buffer, size_t size) {
     244              :         size_t bytes = co_await body_reader_.read(buffer, size);
     245              :         note_body_read();
     246              :         co_return bytes;
     247          672 :     }
     248              : 
     249        41748 :     thinger::awaitable<size_t> request::read_some(uint8_t* buffer, size_t max_size) {
     250              :         size_t bytes = co_await body_reader_.read_some(buffer, max_size);
     251              :         note_body_read();
     252              :         co_return bytes;
     253        83496 :     }
     254              : 
     255          518 :     void request::set_read_deadline(std::chrono::steady_clock::time_point deadline) {
     256          518 :         if (source_) source_->set_read_deadline(deadline);
     257          518 :     }
     258              : 
     259              :     // Decode the body in place according to its Content-Encoding (gzip, x-gzip, deflate, or
     260              :     // several of them; a body with any other coding is left to the handler, with its
     261              :     // Content-Encoding), up to `max_size` bytes once decoded
     262         1037 :     static body_error decode_content_encoding(http_request& http_request, size_t max_size) {
     263         1037 :         if (!http_request.has_header("Content-Encoding")) return body_error::none;
     264              : 
     265              :         // (the values of every Content-Encoding header make one list)
     266           80 :         std::string encoding;
     267          160 :         for (const auto& value : http_request.get_headers_with_key("Content-Encoding")) {
     268           80 :             if (!encoding.empty()) encoding += ", ";
     269           80 :             encoding += value;
     270           80 :         }
     271           80 :         switch (::thinger::util::decode_content(encoding, http_request.get_body(), max_size)) {
     272           44 :             case ::thinger::util::content_decoding::decoded:
     273           44 :                 http_request.remove_headers("Content-Encoding");
     274           44 :                 return body_error::none;
     275            6 :             case ::thinger::util::content_decoding::unsupported:
     276            6 :                 return body_error::none;
     277           15 :             case ::thinger::util::content_decoding::too_large:
     278           15 :                 LOG_ERROR("Decompressed {} request body exceeds the maximum body size", encoding);
     279           15 :                 return body_error::too_large;
     280           15 :             case ::thinger::util::content_decoding::invalid:
     281           15 :                 break;
     282              :         }
     283           15 :         LOG_ERROR("Failed to decompress {} request body", encoding);
     284           15 :         return body_error::bad_encoding;
     285           80 :     }
     286              : 
     287         2063 :     thinger::awaitable<bool> request::read_body() {
     288              :         if (!http_request_) co_return false;
     289              : 
     290              :         // Nothing left to read (already read, or no body)
     291              :         if (!body_reader_.has_pending()) co_return body_reader_.error() == body_error::none;
     292              : 
     293              :         // Held in memory: at most what a size_t counts, whatever the limit (a 32-bit build)
     294              :         auto limit = static_cast<size_t>(std::min<std::uint64_t>(body_reader_.limit(), std::numeric_limits<size_t>::max()));
     295              :         bool read = co_await body_reader_.read_all(http_request_->get_body(), limit);
     296              :         note_body_read();
     297              :         if (!read) co_return false;
     298              : 
     299              :         auto error = decode_content_encoding(*http_request_, limit);
     300              :         if (error != body_error::none) {
     301              :             body_reader_.set_error(error);
     302              :             co_return false;
     303              :         }
     304              :         co_return true;
     305         4126 :     }
     306              : 
     307        76115 :     bool request::has_pending_body() const {
     308        76115 :         return http_request_ && body_reader_.has_pending();
     309              :     }
     310              : 
     311        25374 :     body_error request::get_body_error() const {
     312        25374 :         return body_reader_.error();
     313              :     }
     314              : 
     315          102 :     thinger::awaitable<multipart_form> request::read_multipart(multipart_options options) {
     316              :         co_return co_await http::read_multipart(*this, std::move(options));
     317          204 :     }
     318              : 
     319          179 :     thinger::awaitable<bool> request::discard_body(std::uint64_t max_size) {
     320              :         bool discarded = co_await body_reader_.discard(max_size);
     321              :         note_body_read();
     322              :         co_return discarded;
     323          358 :     }
     324              : 
     325              : }
        

Generated by: LCOV version 2.0-1