LCOV - code coverage report
Current view: top level - http/common - http_request.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 83.7 % 264 221
Test Date: 2026-02-20 15:38:22 Functions: 82.7 % 52 43

            Line data    Source code
       1              : #include "http_request.hpp"
       2              : #include <unordered_map>
       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          956 :     method get_method(const std::string &method) {
      22              :         static std::unordered_map<std::string, http::method> methods = {
      23            0 :             {GET_STR, method::GET},
      24            0 :             {HEAD_STR, method::HEAD},
      25            0 :             {POST_STR, method::POST},
      26            0 :             {PUT_STR, method::PUT},
      27            0 :             {DELETE_STR, method::DELETE},
      28            0 :             {TRACE_STR, method::TRACE},
      29            0 :             {OPTIONS_STR, method::OPTIONS},
      30            0 :             {CONNECT_STR, method::CONNECT},
      31            0 :             {PATCH_STR, method::PATCH}
      32         3442 :         };
      33          956 :         auto it = methods.find(method);
      34          956 :         if (it != methods.end()) {
      35          953 :             return it->second;
      36              :         }
      37            3 :         return method::UNKNOWN;
      38          226 :     }
      39              : 
      40         3591 :     const std::string& get_method(http::method method)
      41              :     {
      42         3591 :         switch(method){
      43         2902 :             case method::GET:
      44         2902 :                 return GET_STR;
      45           39 :             case method::HEAD:
      46           39 :                 return HEAD_STR;
      47          390 :             case method::POST:
      48          390 :                 return POST_STR;
      49          122 :             case method::PUT:
      50          122 :                 return PUT_STR;
      51           55 :             case method::DELETE:
      52           55 :                 return DELETE_STR;
      53            3 :             case method::TRACE:
      54            3 :                 return TRACE_STR;
      55           31 :             case method::OPTIONS:
      56           31 :                 return OPTIONS_STR;
      57            3 :             case method::CONNECT:
      58            3 :                 return CONNECT_STR;
      59           43 :             case method::PATCH:
      60           43 :                 return PATCH_STR;
      61            3 :             default:
      62            3 :                 static std::string empty;
      63            3 :                 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         1775 :     void http_request::log(const char* scope, int level) const{
      80              :         // Log the request with context
      81         1775 :         LOG_INFO("[{}] {} {}", scope, http::get_method(method_), get_uri());
      82              :         
      83              :         // Log headers at debug level
      84         1775 :         LOG_DEBUG("Headers:");
      85         7578 :         for(const auto& t: headers_){
      86         5803 :             LOG_DEBUG("  {}: {}", t.first, t.second);
      87              :         }
      88         1778 :         for(const auto& t: proxy_headers_){
      89            3 :             LOG_DEBUG("  (PROXY) {}: {}", t.first, t.second);
      90              :         }
      91              :         
      92              :         // Log body if present (at trace level)
      93         1775 :         if(!content_.empty()){
      94          106 :             LOG_TRACE("Body: {} bytes", content_.size());
      95          106 :             if(content_.size() <= 500) {
      96           98 :                 std::istringstream f(content_);
      97           98 :                 std::string line;
      98          196 :                 while (std::getline(f, line)) {
      99           98 :                     LOG_TRACE("  {}", line);
     100              :                 }
     101           98 :             } else {
     102            8 :                 LOG_TRACE("  {} (truncated)", content_.substr(0, 500));
     103              :             }
     104              :         }
     105         1775 :     }
     106              : 
     107            0 :     void http_request::set_chunked_callback(std::function<void(int, const std::string&)> callback){
     108            0 :         on_chunked_ = std::move(callback);
     109            0 :     }
     110              : 
     111          864 :     std::function<void(int, const std::string&)> http_request::get_chunked_callback(){
     112          864 :         return on_chunked_;
     113              :     }
     114              : 
     115              :     std::shared_ptr<http_request>
     116            6 :     http_request::create_http_request(http::method method, const std::string& url, const std::string& unix_socket){
     117            6 :         auto request = std::make_shared<http_request>();
     118            6 :         if(request->set_url(url)){
     119            6 :             request->set_method(method);
     120           12 :             request->add_header(http::header::accept, "*/*");
     121            6 :             if(!unix_socket.empty()){
     122            0 :                 request->set_unix_socket(unix_socket);
     123              :             }
     124            6 :             return request;
     125              :         }
     126              :         // return an invalid request (something failed while parsing the URL)
     127            0 :         return std::make_shared<http_request>();
     128            6 :     }
     129              : 
     130          141 :     std::string http_request::get_base_path() const{
     131          141 :         std::string base_path = is_ssl() ? "https://" : "http://";
     132          141 :         base_path += is_default_port() ? get_host() : get_host() + ":" + get_port();
     133          141 :         return base_path;
     134            0 :     }
     135              : 
     136          141 :     std::string http_request::get_url() const{
     137          141 :         return get_base_path() + get_uri();
     138              :     }
     139              : 
     140          995 :     bool http_request::set_url(const std::string& url){
     141          995 :         static const std::regex uri_regex(R"((wss?|https?):\/\/(?:(.*):(.*)@)?([a-zA-Z\.\-0-9_]+)(?::(\d+))*(.*))");
     142          995 :         std::match_results<std::string::const_iterator> results;
     143          995 :         if (std::regex_match(url, results, uri_regex)) {
     144          995 :             std::string protocol = results[1];
     145          995 :             std::string username = results[2];
     146          995 :             std::string password = results[3];
     147          995 :             std::string host = results[4];
     148          995 :             std::string port = results[5];
     149          995 :             std::string uri = results[6];
     150          995 :             set_host(host);
     151          995 :             if(!port.empty()){
     152          869 :                 set_port(port);
     153              :             }
     154          995 :             set_protocol(protocol);
     155          995 :             set_ssl(protocol==https || protocol==wss);
     156         1049 :             set_uri(uri.empty() ? "/" : uri);
     157              : 
     158              : 
     159              :             // set upgrade headers if it is a websocket
     160          995 :             if(protocol==wss || protocol==ws){
     161           84 :                 set_header(http::header::connection, "upgrade");
     162          126 :                 set_header(http::header::upgrade, "websocket");
     163              :             }
     164              : 
     165          995 :             return true;
     166          995 :         }
     167            0 :         return false;
     168          995 :     }
     169              : 
     170            3 :     std::shared_ptr<http_request> http_request::create_http_request(const std::string& method, const std::string& url){
     171            6 :         return create_http_request(http::get_method(method), url);
     172              :     }
     173              : 
     174            3 :     bool http_request::has_query_parameters() const{
     175            3 :         return !uri_params_.empty();
     176              :     }
     177              : 
     178            0 :     bool http_request::has_resource() const{
     179            0 :         return !resource_.empty();
     180              :     }
     181              : 
     182            0 :     const std::string& http_request::get_body() const{
     183            0 :         return content_;
     184              :     }
     185              : 
     186          281 :     std::string& http_request::get_body(){
     187          281 :         return content_;
     188              :     }
     189              : 
     190            0 :     std::string& http_request::get_resource(){
     191            0 :         return resource_;
     192              :     }
     193              : 
     194            0 :     const std::string& http_request::get_resource() const{
     195            0 :         return resource_;
     196              :     }
     197              : 
     198            0 :     void http_request::set_resource(const std::string& resource){
     199              :         // update resource
     200            0 :         resource_ = resource;
     201              :         // refresh uri
     202            0 :         refresh_uri();
     203            0 :     }
     204              : 
     205            3 :     bool http_request::has_uri_parameters() const{
     206            3 :         return !uri_params_.empty();
     207              :     }
     208              : 
     209            0 :     bool http_request::has_uri_value(const std::string& key, const std::string& value) const{
     210            0 :         auto position = uri_params_.find(key);
     211            0 :         return position != uri_params_.end() && position->second == value;
     212              :     }
     213              : 
     214           30 :     bool http_request::has_uri_parameter(const std::string& key) const{
     215           30 :         return uri_params_.find(key) != uri_params_.end();
     216              :     }
     217              : 
     218            0 :     const std::multimap<std::string, std::string>& http_request::get_uri_parameters() const{
     219            0 :         return uri_params_;
     220              :     }
     221              : 
     222           22 :     const std::string& http_request::get_uri_parameter(const std::string& key) const{
     223           22 :         auto position = uri_params_.find(key);
     224           22 :         if(position != uri_params_.end()){
     225           22 :             return position->second;
     226              :         }
     227            0 :         static const std::string empty;
     228            0 :         return empty;
     229              :     }
     230              : 
     231           15 :     void http_request::add_uri_parameter(const std::string& key, const std::string& value){
     232              :         // store uri value
     233           15 :         uri_params_.emplace(key, value);
     234              :         // update uri
     235           15 :         refresh_uri();
     236           15 :     }
     237              : 
     238            9 :     bool http_request::has_content() const{
     239            9 :         return !content_.empty() || has_header(http::header::content_length);
     240              :     }
     241              : 
     242          139 :     void http_request::set_content(std::string content, std::string content_type){
     243          139 :         set_content(std::move(content));
     244          139 :         set_header(http::header::content_type, std::move(content_type));
     245          139 :     }
     246              : 
     247          148 :     void http_request::set_content(std::string content){
     248          148 :         content_ = std::move(content);
     249          148 :         set_header(http::header::content_length, boost::lexical_cast<std::string>(content_.size()));
     250          148 :     }
     251              : 
     252            6 :     const std::string& http_request::get_method_string() const{
     253            6 :         return http::get_method(method_);
     254              :     }
     255              : 
     256         2015 :     method http_request::get_method() const{
     257         2015 :         return method_;
     258              :     }
     259              : 
     260          928 :     void http_request::set_method(http::method method){
     261              :         // set method
     262          928 :         method_ = method;
     263              :         // force methods with a supposed body to force a default content length
     264          928 :         if((method_==http::method::POST || method_==http::method::PUT || method==http::method::PATCH) && !has_header(http::header::content_length)){
     265          459 :             set_header(http::header::content_length, "0");
     266              :         }
     267          928 :     }
     268              : 
     269            3 :     std::string http_request::get_query_string() const{
     270            3 :         return get_url_encoded_data(uri_params_);
     271              :     }
     272              : 
     273          942 :     std::string http_request::get_path() const {
     274          942 :         size_t query_pos = uri_.find('?');
     275          942 :         if (query_pos != std::string::npos) {
     276           91 :             return uri_.substr(0, query_pos);
     277              :         }
     278          851 :         return uri_;
     279              :     }
     280              : 
     281          923 :     void http_request::set_method(const std::string& method){
     282          923 :         method_ = http::get_method(method);
     283          923 :     }
     284              : 
     285           52 :     std::string& http_request::get_uri(){
     286           52 :         return uri_;
     287              :     }
     288              : 
     289         2785 :     const std::string& http_request::get_uri() const{
     290         2785 :         return uri_;
     291              :     }
     292              : 
     293           18 :     void http_request::refresh_uri(){
     294           18 :         if(uri_params_.empty()){
     295            0 :             uri_ = util::url::uri_path_encode(resource_);
     296              :         }else{
     297           18 :             uri_ = util::url::uri_path_encode(resource_) + "?" + get_url_encoded_data(uri_params_);
     298              :         }
     299           18 :     }
     300              : 
     301         1948 :     void http_request::set_uri(const std::string& uri){
     302         1948 :         std::smatch what;
     303         1948 :         std::string::const_iterator start = uri.begin();
     304         1948 :         std::string::const_iterator end   = uri.end();
     305              : 
     306         1948 :         static const std::regex resource_regex("(\\/[^\\?#]*)");
     307         1948 :         if(std::regex_search(start, end, what, resource_regex)){
     308         1948 :             resource_ = util::url::url_decode(std::string(what[0].first, what[1].second));
     309         1948 :             start = what[0].second;
     310              :         }
     311              : 
     312              :         // Regex will stop at ? or # or end of the uri. Check if there is parameters available for its parsing
     313         1948 :         if(start!=uri.end() && *start=='?'){
     314          158 :             ++start;
     315          158 :             parse_url_encoded_data(start, end, uri_params_);
     316              :         }
     317              : 
     318              :         // just save the original uri to avoid generating it again
     319         1948 :         uri_ = uri;
     320         1948 :     }
     321              : 
     322          836 :     const std::string& http_request::get_unix_socket(){
     323          836 :         return unix_socket_;
     324              :     }
     325              : 
     326           51 :     void http_request::set_unix_socket(const std::string& unix_socket){
     327           51 :         unix_socket_ = unix_socket;
     328           51 :     }
     329              : 
     330         1907 :     void http_request::set_ssl(bool ssl){
     331         1907 :         ssl_ = ssl;
     332         1907 :     }
     333              : 
     334         2442 :     bool http_request::is_ssl() const{
     335         2442 :         return ssl_;
     336              :     }
     337              : 
     338            6 :     const std::string& http_request::get_protocol() const{
     339            6 :         return protocol_;
     340              :     }
     341              : 
     342         1004 :     void http_request::set_protocol(std::string protocol){
     343         1004 :         protocol_ = std::move(protocol);
     344              :         // Update SSL flag based on protocol
     345         1004 :         ssl_ = (protocol_ == "https" || protocol_ == "wss");
     346         1004 :     }
     347              : 
     348          887 :     void http_request::set_port(const std::string& port){
     349          887 :         port_ = port;
     350              :         // update host header if the port is not the default one (80 or 443)
     351          887 :         if(port!=https_port && port!=http_port){
     352          875 :             set_header(http::header::host, host_ + ":" + port_);
     353              :         }
     354          887 :     }
     355              : 
     356          150 :     bool http_request::is_default_port() const{
     357          150 :         return port_.empty() ? true : ((ssl_ && https_port == port_) || http_port==port_);
     358              :     }
     359              : 
     360         7030 :     const std::string& http_request::get_port() const{
     361         7030 :         return port_.empty() ? (ssl_ ? https_port : http_port) : port_;
     362              :     }
     363              : 
     364         1930 :     void http_request::set_host(std::string host){
     365         1930 :         auto position = host.find(':');
     366         1930 :         if(position==std::string::npos){
     367         1109 :             host_ = std::move(host);
     368              :         }else{
     369          821 :             host_ = host.substr(0, position);
     370          821 :             port_ = host.substr(position+1, std::string::npos);
     371              :         }
     372              : 
     373              :         // use host always in lowercase
     374         1930 :         boost::algorithm::to_lower(host_);
     375              : 
     376              :         // update host header
     377         1930 :         if(get_port()!=https_port && get_port()!=http_port){
     378          862 :             set_header(http::header::host, host_ + ":" + port_);
     379              :         }else{
     380         1068 :             set_header(http::header::host, host_);
     381              :         }
     382         1930 :     }
     383              : 
     384         4042 :     const std::string& http_request::get_host() const{
     385         4042 :         return host_;
     386              :     }
     387              : 
     388           49 :     cookie_store& http_request::get_cookie_store(){
     389           49 :         return cookie_store_;
     390              :     }
     391              : 
     392         2982 :     void http_request::process_header(std::string key, std::string value){
     393              :         // adjust host
     394         2982 :         if(boost::iequals(key, http::header::host)){
     395          917 :             set_host(std::move(value));
     396              :         }else{
     397              :             // detect chunked transfer encoding
     398         2065 :             if(boost::iequals(key, http::header::transfer_encoding) && boost::iequals(value, "chunked")){
     399           10 :                 chunked_transfer_ = true;
     400              :             }
     401              :             // handle header by parent
     402         2065 :             headers::process_header(std::move(key), std::move(value));
     403              :         }
     404         2982 :     }
     405              : 
     406          869 :     void http_request::to_buffer(std::vector<boost::asio::const_buffer>& buffer) const{
     407          869 :         buffer.emplace_back(boost::asio::buffer(http::get_method(method_)));
     408          869 :         buffer.emplace_back(boost::asio::buffer(misc_strings::space));
     409          869 :         buffer.emplace_back(boost::asio::buffer(get_uri()));
     410          869 :         buffer.emplace_back(boost::asio::buffer(misc_strings::space));
     411          869 :         buffer.emplace_back(boost::asio::buffer(misc_strings::http_1_1));
     412          869 :         buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     413              : 
     414          869 :         std::set<std::string> proxy_headers;
     415              : 
     416              :         // replace headers with proxy headers (if any)
     417          869 :         for(const auto& [key, value] : proxy_headers_){
     418            0 :             buffer.emplace_back(boost::asio::buffer(key));
     419            0 :             buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
     420            0 :             buffer.emplace_back(boost::asio::buffer(value));
     421            0 :             buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     422            0 :             proxy_headers.emplace(key);
     423              :         }
     424              : 
     425              :         // push headers
     426         3736 :         for(const auto& [key, value] : headers_){
     427         2867 :             if(proxy_headers.find(key)==proxy_headers.end()){
     428         2867 :                 buffer.emplace_back(boost::asio::buffer(key));
     429         2867 :                 buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
     430         2867 :                 buffer.emplace_back(boost::asio::buffer(value));
     431         2867 :                 buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     432              :             }
     433              :         }
     434              : 
     435          869 :         buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
     436              : 
     437          869 :         if(!content_.empty()){
     438          106 :             buffer.emplace_back(boost::asio::buffer(content_));
     439              :         }
     440          869 :     }
     441              : 
     442            9 :     size_t http_request::get_size(){
     443              :         // Return the size of the content/payload
     444            9 :         return content_.size();
     445              :     }
     446              : 
     447            0 :     bool http_request::end_stream(){
     448            0 :         return true;
     449              :     }
     450              : 
     451              : }
        

Generated by: LCOV version 2.0-1