LCOV - code coverage report
Current view: top level - http/common - http_request.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 92.9 % 268 249
Test Date: 2026-11-10 10:38:07 Functions: 90.7 % 54 49

            Line data    Source code
       1              : #include "http_request.hpp"
       2              : #include <string_view>
       3              : #include <set>
       4              : #include "../../util/logger.hpp"
       5              : 
       6              : namespace thinger::http {
       7              : 
       8              :     using namespace util::url;
       9              : 
      10              : 
      11              :     static const std::string GET_STR = "GET";
      12              :     static const std::string HEAD_STR = "HEAD";
      13              :     static const std::string POST_STR = "POST";
      14              :     static const std::string PUT_STR = "PUT";
      15              :     static const std::string DELETE_STR = "DELETE";
      16              :     static const std::string TRACE_STR = "TRACE";
      17              :     static const std::string OPTIONS_STR = "OPTIONS";
      18              :     static const std::string CONNECT_STR = "CONNECT";
      19              :     static const std::string PATCH_STR = "PATCH";
      20              : 
      21        35434 :     method get_method(const std::string &method) {
      22              :         // a few short names: comparing them is cheaper than hashing
      23              :         static constexpr std::pair<std::string_view, http::method> methods[] = {
      24              :             {"GET", method::GET},
      25              :             {"POST", method::POST},
      26              :             {"PUT", method::PUT},
      27              :             {"DELETE", method::DELETE},
      28              :             {"PATCH", method::PATCH},
      29              :             {"HEAD", method::HEAD},
      30              :             {"OPTIONS", method::OPTIONS},
      31              :             {"TRACE", method::TRACE},
      32              :             {"CONNECT", method::CONNECT}
      33              :         };
      34        42637 :         for (const auto& [name, value] : methods) {
      35        42631 :             if (name == method) return value;
      36              :         }
      37            6 :         return method::UNKNOWN;
      38              :     }
      39              : 
      40       196403 :     const std::string& get_method(http::method method)
      41              :     {
      42       196403 :         switch(method){
      43       182081 :             case method::GET:
      44       182081 :                 return GET_STR;
      45          660 :             case method::HEAD:
      46          660 :                 return HEAD_STR;
      47        10770 :             case method::POST:
      48        10770 :                 return POST_STR;
      49          972 :             case method::PUT:
      50          972 :                 return PUT_STR;
      51          676 :             case method::DELETE:
      52          676 :                 return DELETE_STR;
      53            3 :             case method::TRACE:
      54            3 :                 return TRACE_STR;
      55          516 :             case method::OPTIONS:
      56          516 :                 return OPTIONS_STR;
      57           51 :             case method::CONNECT:
      58           51 :                 return CONNECT_STR;
      59          674 :             case method::PATCH:
      60          674 :                 return PATCH_STR;
      61            0 :             default:
      62            0 :                 static std::string empty;
      63            0 :                 return empty;
      64              :         }
      65              :     }
      66              : 
      67              :     /*
      68              :     void http_request::debug(std::ostream& os) const{
      69              :         os << "[HTTP REQUEST] " + get_url() << std::endl;
      70              :         os << " > HOST " << get_host() << ":" << get_port() <<  (is_ssl() ? " (SSL)" : " (NO SSL)") << std::endl;
      71              :         os << " > " << http::get_method(method_) << " " << get_uri() << std::endl;
      72              :         debug_headers(os);
      73              :         if(!content_.empty()){
      74              :             os << std::endl << content_;
      75              :         }
      76              :     }
      77              :      */
      78              : 
      79        31919 :     void http_request::log(const char* scope, const body_logging& options) const{
      80        31919 :         if(!LOG_INFO_ENABLED()) return;
      81          348 :         LOG_INFO("[{}] {} {}", scope, http::get_method(method_), loggable_uri(get_uri(), options.redact));
      82          348 :         if(LOG_DEBUG_ENABLED()){
      83          336 :             LOG_DEBUG("Headers:");
      84          336 :             log_headers(options);
      85              :         }
      86              :         // The body, unless it is not read yet: the size it announces
      87          348 :         log_body(content_, content_.empty() ? get_content_length() : content_.size(), options);
      88              :     }
      89              : 
      90            6 :     void http_request::set_chunked_callback(std::function<void(int, const std::string&)> callback){
      91            6 :         on_chunked_ = std::move(callback);
      92            6 :     }
      93              : 
      94         8406 :     std::function<void(int, const std::string&)> http_request::get_chunked_callback(){
      95         8406 :         return on_chunked_;
      96              :     }
      97              : 
      98              :     std::shared_ptr<http_request>
      99         4205 :     http_request::create_http_request(http::method method, const std::string& url, const std::string& unix_socket){
     100         4205 :         auto request = std::make_shared<http_request>();
     101         4205 :         if(request->set_url(url)){
     102         4205 :             request->set_method(method);
     103         8410 :             request->add_header(http::header::accept, "*/*");
     104         4205 :             if(!unix_socket.empty()){
     105            0 :                 request->set_unix_socket(unix_socket);
     106              :             }
     107         4205 :             return request;
     108              :         }
     109              :         // return an invalid request (something failed while parsing the URL)
     110            0 :         return std::make_shared<http_request>();
     111         4205 :     }
     112              : 
     113          582 :     std::string http_request::get_base_path() const{
     114          582 :         std::string base_path = is_ssl() ? "https://" : "http://";
     115          582 :         base_path += is_default_port() ? get_host() : get_host() + ":" + get_port();
     116          582 :         return base_path;
     117            0 :     }
     118              : 
     119          465 :     std::string http_request::get_url() const{
     120          465 :         return get_base_path() + get_uri();
     121              :     }
     122              : 
     123        13257 :     bool http_request::set_url(const std::string& url){
     124              :         // Longer URLs are refused before the regex, whose matching recurses per character in
     125              :         // libstdc++ (a Location comes from the peer)
     126        13257 :         if(url.size() > 8192) return false;
     127        13245 :         static const std::regex uri_regex(R"((wss?|https?):\/\/(?:(.*):(.*)@)?([a-zA-Z\.\-0-9_]+)(?::(\d+))*(.*))");
     128        13245 :         std::match_results<std::string::const_iterator> results;
     129        13245 :         if (std::regex_match(url, results, uri_regex)) {
     130        13230 :             std::string protocol = results[1];
     131        13230 :             std::string username = results[2];
     132        13230 :             std::string password = results[3];
     133        13230 :             std::string host = results[4];
     134        13230 :             std::string port = results[5];
     135        13230 :             std::string uri = results[6];
     136        13230 :             set_host(host);
     137        13230 :             if(!port.empty()){
     138         8281 :                 set_port(port);
     139              :             }
     140        13230 :             set_protocol(protocol);
     141        13230 :             set_ssl(protocol==https || protocol==wss);
     142        13284 :             set_uri(uri.empty() ? "/" : uri);
     143              : 
     144              : 
     145              :             // set upgrade headers if it is a websocket
     146        13230 :             if(protocol==wss || protocol==ws){
     147          530 :                 set_header(http::header::connection, "upgrade");
     148          795 :                 set_header(http::header::upgrade, "websocket");
     149              :             }
     150              : 
     151        13230 :             return true;
     152        13230 :         }
     153           15 :         return false;
     154        13245 :     }
     155              : 
     156            3 :     std::shared_ptr<http_request> http_request::create_http_request(const std::string& method, const std::string& url){
     157            6 :         return create_http_request(http::get_method(method), url);
     158              :     }
     159              : 
     160            3 :     bool http_request::has_query_parameters() const{
     161            3 :         return !uri_params_.empty();
     162              :     }
     163              : 
     164            0 :     bool http_request::has_resource() const{
     165            0 :         return !resource_.empty();
     166              :     }
     167              : 
     168            0 :     const std::string& http_request::get_body() const{
     169            0 :         return content_;
     170              :     }
     171              : 
     172        29925 :     std::string& http_request::get_body(){
     173        29925 :         return content_;
     174              :     }
     175              : 
     176       120338 :     std::string& http_request::get_resource(){
     177       120338 :         return resource_;
     178              :     }
     179              : 
     180            0 :     const std::string& http_request::get_resource() const{
     181            0 :         return resource_;
     182              :     }
     183              : 
     184        60153 :     void http_request::set_resource(const std::string& resource){
     185              :         // update resource
     186        60153 :         resource_ = resource;
     187              :         // refresh uri
     188        60153 :         refresh_uri();
     189        60153 :     }
     190              : 
     191            3 :     bool http_request::has_uri_parameters() const{
     192            3 :         return !uri_params_.empty();
     193              :     }
     194              : 
     195            0 :     bool http_request::has_uri_value(const std::string& key, const std::string& value) const{
     196            0 :         auto position = uri_params_.find(key);
     197            0 :         return position != uri_params_.end() && position->second == value;
     198              :     }
     199              : 
     200         1068 :     bool http_request::has_uri_parameter(const std::string& key) const{
     201         1068 :         return uri_params_.find(key) != uri_params_.end();
     202              :     }
     203              : 
     204       120313 :     const std::multimap<std::string, std::string>& http_request::get_uri_parameters() const{
     205       120313 :         return uri_params_;
     206              :     }
     207              : 
     208          298 :     const std::string& http_request::get_uri_parameter(const std::string& key) const{
     209          298 :         auto position = uri_params_.find(key);
     210          298 :         if(position != uri_params_.end()){
     211          298 :             return position->second;
     212              :         }
     213            0 :         static const std::string empty;
     214            0 :         return empty;
     215              :     }
     216              : 
     217        60171 :     void http_request::add_uri_parameter(const std::string& key, const std::string& value){
     218              :         // store uri value
     219        60171 :         uri_params_.emplace(key, value);
     220              :         // update uri
     221        60171 :         refresh_uri();
     222        60171 :     }
     223              : 
     224            9 :     bool http_request::has_content() const{
     225            9 :         return !content_.empty() || has_header(http::header::content_length);
     226              :     }
     227              : 
     228          979 :     void http_request::set_content(std::string content, std::string content_type){
     229          979 :         set_content(std::move(content));
     230          979 :         set_header(http::header::content_type, std::move(content_type));
     231          979 :     }
     232              : 
     233         1009 :     void http_request::set_content(std::string content){
     234         1009 :         content_ = std::move(content);
     235         1009 :         replace_header(std::string(http::header::content_length), boost::lexical_cast<std::string>(content_.size()));
     236         1009 :     }
     237              : 
     238            6 :     const std::string& http_request::get_method_string() const{
     239            6 :         return http::get_method(method_);
     240              :     }
     241              : 
     242        76834 :     void http_request::set_method(http::method method){
     243              :         // set method
     244        76834 :         method_ = method;
     245              :         // force methods with a supposed body to force a default content length
     246        76834 :         if((method_==http::method::POST || method_==http::method::PUT || method==http::method::PATCH) && !has_header(http::header::content_length)){
     247         3735 :             replace_header(std::string(http::header::content_length), "0");
     248              :         }
     249        76834 :     }
     250              : 
     251            3 :     std::string http_request::get_query_string() const{
     252            3 :         return get_url_encoded_data(uri_params_);
     253              :     }
     254              : 
     255         4019 :     std::string http_request::get_path() const {
     256         4019 :         size_t query_pos = uri_.find('?');
     257         4019 :         if (query_pos != std::string::npos) {
     258           47 :             return uri_.substr(0, query_pos);
     259              :         }
     260         3972 :         return uri_;
     261              :     }
     262              : 
     263        35401 :     void http_request::set_method(const std::string& method){
     264        35401 :         method_ = http::get_method(method);
     265        35401 :     }
     266              : 
     267       211139 :     std::string& http_request::get_uri(){
     268       211139 :         return uri_;
     269              :     }
     270              : 
     271        10627 :     const std::string& http_request::get_uri() const{
     272        10627 :         return uri_;
     273              :     }
     274              : 
     275       120330 :     void http_request::refresh_uri(){
     276       120330 :         if(uri_params_.empty()){
     277        60153 :             uri_ = util::url::uri_path_encode(resource_);
     278              :         }else{
     279        60177 :             uri_ = util::url::uri_path_encode(resource_) + "?" + get_url_encoded_data(uri_params_);
     280              :         }
     281       120330 :     }
     282              : 
     283       231160 :     void http_request::set_uri(const std::string& uri){
     284       231160 :         std::string::const_iterator start = uri.begin();
     285       231160 :         std::string::const_iterator end   = uri.end();
     286              : 
     287              :         // The resource goes from the first '/' (wherever it is) up to the first '?' or '#'
     288              :         // after it, or the end. Without any '/', it is left as is
     289       231160 :         if(auto slash = uri.find('/'); slash != std::string::npos){
     290       176713 :             auto resource_end = std::min(uri.find_first_of("?#", slash), uri.size());
     291              :             // "+" is literal in a path (a space only in the query); empty on an invalid escape.
     292              :             // Decoded apart, as `uri` may be the resource itself
     293       176713 :             std::string resource;
     294       176713 :             if (!util::url::detail::path_decode(std::string_view(uri).substr(slash, resource_end - slash), resource)) {
     295        14745 :                 resource.clear();
     296              :             }
     297       176713 :             resource_ = std::move(resource);
     298       176713 :             start = uri.begin() + static_cast<std::ptrdiff_t>(resource_end);
     299       176713 :         }
     300              : 
     301              :         // Query parameters follow the resource (or start the uri, if it has no resource)
     302       231160 :         if(start!=uri.end() && *start=='?'){
     303        26064 :             ++start;
     304        26064 :             parse_url_encoded_data(start, end, uri_params_);
     305              :         }
     306              : 
     307              :         // just save the original uri to avoid generating it again
     308       231160 :         uri_ = uri;
     309       231160 :     }
     310              : 
     311        24851 :     const std::string& http_request::get_unix_socket() const{
     312        24851 :         return unix_socket_;
     313              :     }
     314              : 
     315          859 :     void http_request::set_unix_socket(const std::string& unix_socket){
     316          859 :         unix_socket_ = unix_socket;
     317          859 :     }
     318              : 
     319        36748 :     void http_request::set_ssl(bool ssl){
     320        36748 :         ssl_ = ssl;
     321        36748 :     }
     322              : 
     323        52121 :     bool http_request::is_ssl() const{
     324        52121 :         return ssl_;
     325              :     }
     326              : 
     327            6 :     const std::string& http_request::get_protocol() const{
     328            6 :         return protocol_;
     329              :     }
     330              : 
     331        13239 :     void http_request::set_protocol(std::string protocol){
     332        13239 :         protocol_ = std::move(protocol);
     333              :         // Update SSL flag based on protocol
     334        13239 :         ssl_ = (protocol_ == "https" || protocol_ == "wss");
     335        13239 :     }
     336              : 
     337         8302 :     void http_request::set_port(const std::string& port){
     338         8302 :         port_ = port;
     339              :         // update host header if the port is not the default one (80 or 443)
     340         8302 :         if(port!=https_port && port!=http_port){
     341         8248 :             set_header(http::header::host, host_ + ":" + port_);
     342              :         }
     343         8302 :     }
     344              : 
     345          591 :     bool http_request::is_default_port() const{
     346          591 :         return port_.empty() ? true : ((ssl_ && https_port == port_) || http_port==port_);
     347              :     }
     348              : 
     349       237970 :     const std::string& http_request::get_port() const{
     350       237970 :         return port_.empty() ? (ssl_ ? https_port : http_port) : port_;
     351              :     }
     352              : 
     353        75696 :     void http_request::set_host(std::string host){
     354        75696 :         set_header(http::header::host, parse_host(std::move(host)));
     355        75696 :     }
     356              : 
     357       108477 :     std::string http_request::parse_host(std::string host){
     358              :         // A bracketed IPv6 literal ("[::1]:8080") keeps its brackets, as WHATWG URL.host does:
     359              :         // its port follows the "]". Without a "]" it is the host whole
     360       108477 :         auto position = host.find(':');
     361       108477 :         if(!host.empty() && host[0] == '['){
     362           24 :             auto bracket = host.find(']');
     363           24 :             position = bracket != std::string::npos && bracket + 1 < host.size() && host[bracket + 1] == ':' ?
     364              :                        bracket + 1 : std::string::npos;
     365              :         }
     366       108477 :         if(position==std::string::npos){
     367       105519 :             host_ = std::move(host);
     368              :         }else{
     369         2958 :             host_ = host.substr(0, position);
     370         2958 :             port_ = host.substr(position+1, std::string::npos);
     371              :         }
     372              : 
     373              :         // use host always in lowercase
     374       108477 :         ::thinger::util::ascii::to_lower(host_);
     375              : 
     376              :         // the value of the Host header
     377       108477 :         if(get_port()!=https_port && get_port()!=http_port){
     378         6458 :             return host_ + ":" + port_;
     379              :         }
     380       105248 :         return host_;
     381              :     }
     382              : 
     383        25847 :     const std::string& http_request::get_host() const{
     384        25847 :         return host_;
     385              :     }
     386              : 
     387          226 :     cookie_store& http_request::get_cookie_store(){
     388          226 :         return cookie_store_;
     389              :     }
     390              : 
     391       316582 :     void http_request::process_header(std::string key, std::string value){
     392              :         // adjust host
     393       316582 :         if(::thinger::util::ascii::iequals(key, http::header::host)){
     394              :             // parsed: already checked
     395        32781 :             replace_header(std::string(http::header::host), parse_host(std::move(value)));
     396              :         }else{
     397       283801 :             if(::thinger::util::ascii::iequals(key, "Expect")){
     398          360 :                 process_expect_header(value);
     399              :             }
     400              :             // handle header by parent
     401       283801 :             headers::process_header(std::move(key), std::move(value));
     402              :         }
     403       316582 :     }
     404              : 
     405          360 :     void http_request::process_expect_header(std::string_view value){
     406              :         // A list of expectations, case-insensitive: 100-continue is the only one defined.
     407              :         // Ignored in HTTP/1.0 requests (they predate it), and empty values, as Go does
     408          360 :         if(http_version_major_ == 1 && http_version_minor_ == 0) return;
     409              :         static constexpr std::string_view whitespace = " \t";
     410          351 :         while(!value.empty()){
     411          339 :             auto comma = value.find(',');
     412          339 :             auto item = value.substr(0, comma);
     413          339 :             auto first = item.find_first_not_of(whitespace);
     414          339 :             if(first != std::string_view::npos){
     415          333 :                 item = item.substr(first, item.find_last_not_of(whitespace) - first + 1);
     416          333 :                 if(::thinger::util::ascii::iequals(item, "100-continue")){
     417          273 :                     expectation_ = expectation::continue_100;
     418           60 :                 }else if(expectation_ == expectation::none){
     419           48 :                     expectation_ = expectation::unsupported;
     420              :                 }
     421              :             }
     422          339 :             if(comma == std::string_view::npos) break;
     423           15 :             value.remove_prefix(comma + 1);
     424              :         }
     425              :     }
     426              : 
     427         8389 :     void http_request::to_buffer(std::vector<boost::asio::const_buffer>& buffer) const{
     428         8389 :         to_buffer(buffer, get_uri(), {});
     429         8389 :     }
     430              : 
     431         8455 :     void http_request::to_buffer(std::vector<boost::asio::const_buffer>& buffer, std::string_view request_target,
     432              :                                  std::string_view extra_headers) const{
     433         8455 :         buffer.emplace_back(boost::asio::buffer(http::get_method(method_)));
     434         8455 :         buffer.emplace_back(boost::asio::buffer(misc_strings::space));
     435         8455 :         buffer.emplace_back(boost::asio::buffer(request_target.data(), request_target.size()));
     436         8455 :         buffer.emplace_back(boost::asio::buffer(misc_strings::space));
     437         8455 :         buffer.emplace_back(boost::asio::buffer(misc_strings::http_1_1));
     438         8455 :         buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     439              : 
     440         8455 :         std::set<std::string> proxy_headers;
     441              : 
     442              :         // replace headers with proxy headers (if any)
     443         8458 :         for(const auto& [key, value] : proxy_headers_){
     444            3 :             buffer.emplace_back(boost::asio::buffer(key));
     445            3 :             buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
     446            3 :             buffer.emplace_back(boost::asio::buffer(value));
     447            3 :             buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     448            3 :             proxy_headers.emplace(key);
     449              :         }
     450              : 
     451              :         // push headers
     452        36027 :         for(const auto& [key, value] : headers_){
     453        27572 :             if(proxy_headers.find(key)==proxy_headers.end()){
     454        27572 :                 buffer.emplace_back(boost::asio::buffer(key));
     455        27572 :                 buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
     456        27572 :                 buffer.emplace_back(boost::asio::buffer(value));
     457        27572 :                 buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     458              :             }
     459              :         }
     460              : 
     461         8455 :         if(!extra_headers.empty()){
     462            9 :             buffer.emplace_back(boost::asio::buffer(extra_headers.data(), extra_headers.size()));
     463              :         }
     464              : 
     465         8455 :         buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     466              : 
     467         8455 :         if(!content_.empty()){
     468          553 :             buffer.emplace_back(boost::asio::buffer(content_));
     469              :         }
     470         8455 :     }
     471              : 
     472            9 :     size_t http_request::get_size(){
     473              :         // Return the size of the content/payload
     474            9 :         return content_.size();
     475              :     }
     476              : 
     477            0 :     bool http_request::end_stream(){
     478            0 :         return true;
     479              :     }
     480              : 
     481              : }
        

Generated by: LCOV version 2.0-1