LCOV - code coverage report
Current view: top level - http/server - request.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 85.7 % 119 102
Test Date: 2026-02-20 15:38:22 Functions: 87.5 % 40 35

            Line data    Source code
       1              : #include "../../util/logger.hpp"
       2              : #include "../../util/compression.hpp"
       3              : #include <utility>
       4              : 
       5              : #include "request.hpp"
       6              : #include "routing/route.hpp"
       7              : 
       8              : namespace thinger::http{
       9              : 
      10              :     using nlohmann::json;
      11              :     using std::string;
      12              : 
      13          930 :     request::request(
      14              :         const std::shared_ptr<server_connection>& http_connection,
      15              :         const std::shared_ptr<http_stream>& http_stream,
      16          930 :         std::shared_ptr<http_request> http_request) :
      17          930 :         http_connection_{http_connection},
      18          930 :         http_stream_{http_stream},
      19         1860 :         http_request_{std::move(http_request)}
      20              :     {
      21              : 
      22          930 :     }
      23              : 
      24          930 :     request::~request()= default;
      25              : 
      26          234 :     const string& request::operator[](const std::string& param) const{
      27          234 :         return get_uri_parameter(param);
      28              :     }
      29              : 
      30           12 :     bool request::has(const std::string& param) const {
      31           12 :         return params_.contains(param);
      32              :     }
      33              : 
      34            2 :     bool request::erase(const std::string& param){
      35            2 :         return params_.erase(param)>0;
      36              :     }
      37              : 
      38         2063 :     std::shared_ptr<http_request> request::get_http_request(){
      39         2063 :         return http_request_;
      40              :     }
      41              : 
      42         1041 :     std::shared_ptr<server_connection> request::get_http_connection() const {
      43         1041 :         return http_connection_.lock();
      44              :     }
      45              :     
      46          929 :     std::shared_ptr<http_stream> request::get_http_stream() const {
      47          929 :         return http_stream_.lock();
      48              :     }
      49              : 
      50              :     /*
      51              :     void request::add_matched_param(const std::string& param){
      52              :         uri_params_.emplace_back(param);
      53              :     }
      54              :      */
      55              : 
      56              : 
      57            4 :     void request::set_auth_user(const std::string& auth_user){
      58            4 :         auth_user_ = auth_user;
      59            4 :     }
      60              : 
      61            4 :     const std::string& request::get_auth_user() const{
      62            4 :         return auth_user_;
      63              :     }
      64              :     
      65          883 :     void request::set_matched_route(const route* route) {
      66          883 :         matched_route_ = route;
      67          883 :     }
      68              :     
      69            0 :     const route* request::get_matched_route() const {
      70            0 :         return matched_route_;
      71              :     }
      72              :     
      73            0 :     auth_level request::get_required_auth_level() const {
      74            0 :         return matched_route_ ? matched_route_->get_auth_level() : auth_level::PUBLIC;
      75              :     }
      76              : 
      77          233 :     void request::set_uri_parameter(const std::string& param, const std::string& value){
      78              :         // erase all existing entries with the specified key
      79          233 :         params_.erase(param);
      80              : 
      81              :         // insert the new key-value pair
      82          233 :         params_.insert(std::make_pair(param, value));
      83          233 :     }
      84              : 
      85            4 :     void request::add_uri_parameter(const std::string& param, const std::string& value){
      86            4 :         params_.insert({param, value});
      87            4 :     }
      88              : 
      89          234 :     const std::string& request::get_uri_parameter(const std::string& param) const{
      90          234 :         auto it = params_.find(param);
      91          234 :         if(it!=params_.end()){
      92          234 :             return it->second;
      93              :         }
      94            0 :         LOG_WARNING("cannot find required parameter: {}", param);
      95            0 :         static std::string empty_string;
      96            0 :         return empty_string;
      97              :     }
      98              : 
      99            0 :     const std::multimap<std::string, std::string>& request::get_uri_parameters() const{
     100            0 :         return params_;
     101              :     }
     102              : 
     103            2 :     void request::set_auth_groups(const std::set<std::string> &groups) {
     104            2 :         groups_ = groups;
     105            2 :     }
     106              : 
     107            2 :     const std::set<std::string> & request::get_auth_groups() const {
     108            2 :         return groups_;
     109              :     }
     110              : 
     111            2 :     std::string request::debug_parameters() const {
     112            2 :         std::stringstream str;
     113            6 :         for(const auto& param: params_){
     114            4 :             str << "(" << param.first << ":" << param.second << ") ";
     115              :         }
     116            4 :         return str.str();
     117            2 :     }
     118              : 
     119            0 :     std::string request::get_request_ip() const{
     120            0 :         auto http_connection = http_connection_.lock();
     121            0 :         return http_connection ? http_connection->get_socket()->get_remote_ip() : "";
     122            0 :     }
     123              : 
     124              :     /*
     125              :     exec_result request::get_request_data() const{
     126              :         const std::string& content = http_request_->get_body();
     127              :         const std::string& content_type = http_request_->get_content_type();
     128              : 
     129              :         // set content
     130              :         if(!content.empty()){
     131              :             if(boost::istarts_with(content_type, mime_types::application_json)){
     132              :                 try{
     133              :                     return {true, nlohmann::json::parse(content)};
     134              :                 }catch(...){
     135              :                     return {false, "invalid json payload", http_response::status::bad_request};
     136              :                 }
     137              :             }else if(boost::istarts_with(content_type, mime_types::application_octect_stream)){
     138              :                 std::vector<uint8_t> data(content.begin(), content.end());
     139              :                 return {true, nlohmann::json::binary(std::move(data))};
     140              :             }else if(boost::istarts_with(content_type, mime_types::text_html) || boost::istarts_with(content_type, mime_types::text_plain)){
     141              :                 return {true, content};
     142              :             }else if(boost::istarts_with(content_type, mime_types::application_form_urlencoded)){
     143              :                 std::multimap<std::string, std::string> parameters;
     144              :                 util::url::parse_url_encoded_data(content, parameters);
     145              :                 return {true, std::move(parameters)};
     146              :             }else if(boost::istarts_with(content_type, mime_types::application_msgpack)){
     147              :                 try{
     148              :                     return {true, nlohmann::json::from_msgpack(content)};
     149              :                 }catch(...){
     150              :                     return {false, "invalid msgpack payload", http_response::status::bad_request};
     151              :                 }
     152              :             }
     153              :             else if(boost::istarts_with(content_type, mime_types::application_cbor)){
     154              :                 try{
     155              :                     return {true, nlohmann::json::from_cbor(content)};
     156              :                 }catch(...){
     157              :                     return {false, "invalid cbor payload", http_response::status::bad_request};
     158              :                 }
     159              :             }
     160              :             else if(boost::istarts_with(content_type, mime_types::application_ubjson)){
     161              :                 try{
     162              :                     return {true, nlohmann::json::from_ubjson(content)};
     163              :                 }catch(...){
     164              :                     return {false, "invalid ubjson payload", http_response::status::bad_request};
     165              :                 }
     166              :             }else{
     167              :                 // unknown content type, return as binary
     168              :                 std::vector<uint8_t> vec(content.begin(), content.end());
     169              :                 return {true, nlohmann::json::binary_t(std::move(vec))};
     170              :             }
     171              :         }
     172              : 
     173              :         return {true, nullptr};
     174              :     }*/
     175              : 
     176            2 :     bool request::keep_alive() const{
     177            2 :         return http_request_ && http_request_->keep_alive();
     178              :     }
     179              : 
     180              :     // Convenience methods implementation
     181              : 
     182            8 :     std::string request::query(const std::string& key) const {
     183            8 :         if (http_request_ && http_request_->has_uri_parameter(key)) {
     184            4 :             return http_request_->get_uri_parameter(key);
     185              :         }
     186            8 :         return "";
     187              :     }
     188              : 
     189            4 :     std::string request::query(const std::string& key, const std::string& default_value) const {
     190            4 :         if (http_request_ && http_request_->has_uri_parameter(key)) {
     191            0 :             return http_request_->get_uri_parameter(key);
     192              :         }
     193            4 :         return default_value;
     194              :     }
     195              : 
     196           41 :     std::string request::body() const {
     197           41 :         return http_request_ ? http_request_->get_body() : "";
     198              :     }
     199              : 
     200           15 :     nlohmann::json request::json() const {
     201           15 :         if (!http_request_) {
     202            0 :             return nlohmann::json{};
     203              :         }
     204           15 :         const auto& content = http_request_->get_body();
     205           15 :         if (content.empty()) {
     206            2 :             return nlohmann::json{};
     207              :         }
     208           13 :         auto j = nlohmann::json::parse(content, nullptr, false);
     209           13 :         if (j.is_discarded()) {
     210            2 :             return nlohmann::json{};
     211              :         }
     212           11 :         return j;
     213           13 :     }
     214              : 
     215           15 :     std::string request::header(const std::string& key) const {
     216           15 :         return http_request_ ? http_request_->get_header(key) : "";
     217              :     }
     218              : 
     219              :     // --- Deferred body reading support ---
     220              : 
     221          120 :     void request::set_read_ahead(const uint8_t* data, size_t size) {
     222          120 :         if (data && size > 0) {
     223          120 :             read_ahead_.assign(data, data + size);
     224          120 :             read_ahead_offset_ = 0;
     225              :         }
     226          120 :     }
     227              : 
     228          108 :     size_t request::content_length() const {
     229          108 :         return http_request_ ? http_request_->get_content_length() : 0;
     230              :     }
     231              : 
     232          498 :     bool request::is_chunked() const {
     233          498 :         return http_request_ && http_request_->is_chunked_transfer();
     234              :     }
     235              : 
     236          260 :     std::shared_ptr<asio::socket> request::get_socket() const {
     237          260 :         auto conn = http_connection_.lock();
     238          520 :         return conn ? conn->get_socket() : nullptr;
     239          260 :     }
     240              : 
     241         1306 :     size_t request::read_ahead_available() const {
     242         1306 :         return read_ahead_.size() > read_ahead_offset_ ? read_ahead_.size() - read_ahead_offset_ : 0;
     243              :     }
     244              : 
     245              :     // --- Raw I/O (bypasses chunked decoding) ---
     246              : 
     247           44 :     thinger::awaitable<size_t> request::raw_read_some(uint8_t* buffer, size_t max_size) {
     248              :         // Consume from read-ahead first (using offset, O(1) per call)
     249              :         size_t avail = read_ahead_available();
     250              :         if (avail > 0) {
     251              :             size_t from_ahead = std::min(avail, max_size);
     252              :             std::memcpy(buffer, read_ahead_.data() + read_ahead_offset_, from_ahead);
     253              :             read_ahead_offset_ += from_ahead;
     254              :             if (read_ahead_offset_ >= read_ahead_.size()) {
     255              :                 read_ahead_.clear();
     256              :                 read_ahead_offset_ = 0;
     257              :             }
     258              :             co_return from_ahead;
     259              :         }
     260              : 
     261              :         // Read from socket
     262              :         auto sock = get_socket();
     263              :         if (sock) {
     264              :             co_return co_await sock->read_some(buffer, max_size);
     265              :         }
     266              : 
     267              :         co_return 0;
     268           88 :     }
     269              : 
     270              :     // --- Chunked transfer encoding decoder ---
     271              : 
     272           60 :     static bool is_hex_char(uint8_t c) {
     273           60 :         return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
     274              :     }
     275              : 
     276           34 :     static size_t hex_value(uint8_t c) {
     277           34 :         if (c >= '0' && c <= '9') return c - '0';
     278            2 :         if (c >= 'a' && c <= 'f') return c - 'a' + 10;
     279            0 :         if (c >= 'A' && c <= 'F') return c - 'A' + 10;
     280            0 :         return 0;
     281              :     }
     282              : 
     283           18 :     thinger::awaitable<size_t> request::read_some_chunked(uint8_t* buffer, size_t max_size) {
     284              :         size_t output = 0;
     285              : 
     286              :         while (output == 0 && chunk_state_ != chunk_state::done) {
     287              :             if (chunk_state_ == chunk_state::data && chunk_remaining_ > 0) {
     288              :                 // Fast path: read data directly into user buffer (no copy overhead)
     289              :                 size_t to_read = std::min(chunk_remaining_, max_size - output);
     290              :                 size_t bytes = co_await raw_read_some(buffer + output, to_read);
     291              :                 if (bytes == 0) co_return output;
     292              :                 chunk_remaining_ -= bytes;
     293              :                 output += bytes;
     294              :                 if (chunk_remaining_ == 0) {
     295              :                     chunk_state_ = chunk_state::data_cr;
     296              :                 }
     297              :             } else {
     298              :                 // Slow path: read a batch of raw bytes for framing
     299              :                 uint8_t raw[512];
     300              :                 size_t raw_bytes = co_await raw_read_some(raw, sizeof(raw));
     301              :                 if (raw_bytes == 0) co_return output;
     302              : 
     303              :                 size_t i = 0;
     304              :                 while (i < raw_bytes && chunk_state_ != chunk_state::done && output < max_size) {
     305              :                     uint8_t byte = raw[i];
     306              : 
     307              :                     switch (chunk_state_) {
     308              :                         case chunk_state::size:
     309              :                             if (is_hex_char(byte)) {
     310              :                                 chunk_size_accum_ = chunk_size_accum_ * 16 + hex_value(byte);
     311              :                                 i++;
     312              :                             } else if (byte == '\r') {
     313              :                                 chunk_state_ = chunk_state::size_lf;
     314              :                                 i++;
     315              :                             } else {
     316              :                                 // Skip chunk extensions (e.g. ";ext=val")
     317              :                                 i++;
     318              :                             }
     319              :                             break;
     320              : 
     321              :                         case chunk_state::size_lf:
     322              :                             if (byte == '\n') {
     323              :                                 if (chunk_size_accum_ == 0) {
     324              :                                     // Last chunk — expect trailing CRLF
     325              :                                     chunk_state_ = chunk_state::trailer_lf;
     326              :                                 } else {
     327              :                                     chunk_remaining_ = chunk_size_accum_;
     328              :                                     chunk_size_accum_ = 0;
     329              :                                     chunk_state_ = chunk_state::data;
     330              :                                 }
     331              :                                 i++;
     332              : 
     333              :                                 // If entering data state and raw buffer has more bytes,
     334              :                                 // copy data directly from raw buffer (avoid another read)
     335              :                                 if (chunk_state_ == chunk_state::data && i < raw_bytes) {
     336              :                                     size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
     337              :                                     size_t to_copy = std::min(data_avail, max_size - output);
     338              :                                     std::memcpy(buffer + output, raw + i, to_copy);
     339              :                                     output += to_copy;
     340              :                                     i += to_copy;
     341              :                                     chunk_remaining_ -= to_copy;
     342              :                                     if (chunk_remaining_ == 0) {
     343              :                                         chunk_state_ = chunk_state::data_cr;
     344              :                                     }
     345              :                                 }
     346              :                             } else {
     347              :                                 i++; // malformed, skip
     348              :                             }
     349              :                             break;
     350              : 
     351              :                         case chunk_state::data:
     352              :                             // We shouldn't reach here (handled above), but safety
     353              :                             {
     354              :                                 size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
     355              :                                 size_t to_copy = std::min(data_avail, max_size - output);
     356              :                                 std::memcpy(buffer + output, raw + i, to_copy);
     357              :                                 output += to_copy;
     358              :                                 i += to_copy;
     359              :                                 chunk_remaining_ -= to_copy;
     360              :                                 if (chunk_remaining_ == 0) {
     361              :                                     chunk_state_ = chunk_state::data_cr;
     362              :                                 }
     363              :                             }
     364              :                             break;
     365              : 
     366              :                         case chunk_state::data_cr:
     367              :                             if (byte == '\r') chunk_state_ = chunk_state::data_lf;
     368              :                             i++;
     369              :                             break;
     370              : 
     371              :                         case chunk_state::data_lf:
     372              :                             if (byte == '\n') {
     373              :                                 chunk_state_ = chunk_state::size;
     374              :                                 chunk_size_accum_ = 0;
     375              :                             }
     376              :                             i++;
     377              :                             break;
     378              : 
     379              :                         case chunk_state::trailer_lf:
     380              :                             if (byte == '\r') {
     381              :                                 i++;
     382              :                                 // Expect final \n
     383              :                                 if (i < raw_bytes && raw[i] == '\n') {
     384              :                                     i++;
     385              :                                 }
     386              :                                 chunk_state_ = chunk_state::done;
     387              :                             } else {
     388              :                                 // Trailer header line — skip until we find empty CRLF
     389              :                                 i++;
     390              :                             }
     391              :                             break;
     392              : 
     393              :                         case chunk_state::done:
     394              :                             break;
     395              :                     }
     396              :                 }
     397              : 
     398              :                 // Push unconsumed raw bytes back to read-ahead for next call
     399              :                 if (i < raw_bytes) {
     400              :                     read_ahead_.assign(raw + i, raw + raw_bytes);
     401              :                     read_ahead_offset_ = 0;
     402              :                 }
     403              :             }
     404              :         }
     405              : 
     406              :         co_return output;
     407           36 :     }
     408              : 
     409              :     // --- Public read API (dispatches to raw or chunked) ---
     410              : 
     411          356 :     thinger::awaitable<size_t> request::read(uint8_t* buffer, size_t size) {
     412              :         if (is_chunked()) {
     413              :             // For chunked, read decoded data until we have `size` bytes or EOF
     414              :             size_t total = 0;
     415              :             while (total < size) {
     416              :                 size_t bytes = co_await read_some_chunked(buffer + total, size - total);
     417              :                 if (bytes == 0) break;
     418              :                 total += bytes;
     419              :             }
     420              :             co_return total;
     421              :         }
     422              : 
     423              :         // Non-chunked: read exact size
     424              :         size_t total = 0;
     425              : 
     426              :         // Consume from read-ahead first
     427              :         size_t avail = read_ahead_available();
     428              :         if (avail > 0) {
     429              :             size_t from_ahead = std::min(avail, size);
     430              :             std::memcpy(buffer, read_ahead_.data() + read_ahead_offset_, from_ahead);
     431              :             read_ahead_offset_ += from_ahead;
     432              :             total += from_ahead;
     433              :             if (read_ahead_offset_ >= read_ahead_.size()) {
     434              :                 read_ahead_.clear();
     435              :                 read_ahead_offset_ = 0;
     436              :             }
     437              :         }
     438              : 
     439              :         // Read remaining from socket
     440              :         if (total < size) {
     441              :             auto sock = get_socket();
     442              :             if (sock) {
     443              :                 size_t remaining = size - total;
     444              :                 size_t bytes = co_await sock->read(buffer + total, remaining);
     445              :                 total += bytes;
     446              :             }
     447              :         }
     448              : 
     449              :         co_return total;
     450          712 :     }
     451              : 
     452           38 :     thinger::awaitable<size_t> request::read_some(uint8_t* buffer, size_t max_size) {
     453              :         if (is_chunked()) {
     454              :             co_return co_await read_some_chunked(buffer, max_size);
     455              :         }
     456              :         co_return co_await raw_read_some(buffer, max_size);
     457           76 :     }
     458              : 
     459          104 :     thinger::awaitable<bool> request::read_body() {
     460              :         if (!http_request_) co_return false;
     461              : 
     462              :         if (is_chunked()) {
     463              :             // Chunked: read decoded chunks until EOF, respecting max_body_size
     464              :             auto& body = http_request_->get_body();
     465              :             uint8_t buf[8192];
     466              :             while (true) {
     467              :                 size_t bytes = co_await read_some_chunked(buf, sizeof(buf));
     468              :                 if (bytes == 0) break;
     469              :                 if (body.size() + bytes > max_body_size_) co_return false;
     470              :                 body.append(reinterpret_cast<char*>(buf), bytes);
     471              :             }
     472              : 
     473              :             // Decompress chunked body if Content-Encoding is set
     474              :             if (http_request_->has_header("Content-Encoding")) {
     475              :                 std::string encoding = http_request_->get_header("Content-Encoding");
     476              :                 if (encoding == "gzip") {
     477              :                     auto decompressed = ::thinger::util::gzip::decompress(body);
     478              :                     if (decompressed) {
     479              :                         body = std::move(*decompressed);
     480              :                         http_request_->remove_header("Content-Encoding");
     481              :                     } else {
     482              :                         LOG_ERROR("Failed to decompress gzip request body");
     483              :                         co_return false;
     484              :                     }
     485              :                 } else if (encoding == "deflate") {
     486              :                     auto decompressed = ::thinger::util::deflate::decompress(body);
     487              :                     if (decompressed) {
     488              :                         body = std::move(*decompressed);
     489              :                         http_request_->remove_header("Content-Encoding");
     490              :                     } else {
     491              :                         LOG_ERROR("Failed to decompress deflate request body");
     492              :                         co_return false;
     493              :                     }
     494              :                 }
     495              :             }
     496              : 
     497              :             co_return true;
     498              :         }
     499              : 
     500              :         // Content-Length based
     501              :         size_t cl = http_request_->get_content_length();
     502              :         if (cl == 0) co_return true;
     503              : 
     504              :         auto& body = http_request_->get_body();
     505              :         body.resize(cl);
     506              : 
     507              :         size_t bytes_read = co_await read(reinterpret_cast<uint8_t*>(body.data()), cl);
     508              :         if (bytes_read != cl) co_return false;
     509              : 
     510              :         // Decompress body if Content-Encoding is set
     511              :         if (http_request_->has_header("Content-Encoding")) {
     512              :             std::string encoding = http_request_->get_header("Content-Encoding");
     513              :             if (encoding == "gzip") {
     514              :                 auto decompressed = ::thinger::util::gzip::decompress(body);
     515              :                 if (decompressed) {
     516              :                     body = std::move(*decompressed);
     517              :                     http_request_->remove_header("Content-Encoding");
     518              :                 } else {
     519              :                     LOG_ERROR("Failed to decompress gzip request body");
     520              :                     co_return false;
     521              :                 }
     522              :             } else if (encoding == "deflate") {
     523              :                 auto decompressed = ::thinger::util::deflate::decompress(body);
     524              :                 if (decompressed) {
     525              :                     body = std::move(*decompressed);
     526              :                     http_request_->remove_header("Content-Encoding");
     527              :                 } else {
     528              :                     LOG_ERROR("Failed to decompress deflate request body");
     529              :                     co_return false;
     530              :                 }
     531              :             }
     532              :         }
     533              : 
     534              :         co_return true;
     535          208 :     }
     536              : 
     537              : }
        

Generated by: LCOV version 2.0-1