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-04-21 17:49:55 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         1076 :     request::request(
      14              :         const std::shared_ptr<server_connection>& http_connection,
      15              :         const std::shared_ptr<http_stream>& http_stream,
      16         1076 :         std::shared_ptr<http_request> http_request) :
      17         1076 :         http_connection_{http_connection},
      18         1076 :         http_stream_{http_stream},
      19         2152 :         http_request_{std::move(http_request)}
      20              :     {
      21              : 
      22         1076 :     }
      23              : 
      24         1076 :     request::~request()= default;
      25              : 
      26          242 :     const string& request::operator[](const std::string& param) const{
      27          242 :         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         2459 :     std::shared_ptr<http_request> request::get_http_request(){
      39         2459 :         return http_request_;
      40              :     }
      41              : 
      42         1215 :     std::shared_ptr<server_connection> request::get_http_connection() const {
      43         1215 :         return http_connection_.lock();
      44              :     }
      45              :     
      46         1079 :     std::shared_ptr<http_stream> request::get_http_stream() const {
      47         1079 :         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           16 :     void request::set_auth_user(const std::string& auth_user){
      58           16 :         auth_user_ = auth_user;
      59           16 :     }
      60              : 
      61           16 :     const std::string& request::get_auth_user() const{
      62           16 :         return auth_user_;
      63              :     }
      64              :     
      65         1025 :     void request::set_matched_route(const route* route) {
      66         1025 :         matched_route_ = route;
      67         1025 :     }
      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          253 :     void request::set_uri_parameter(const std::string& param, const std::string& value){
      78              :         // erase all existing entries with the specified key
      79          253 :         params_.erase(param);
      80              : 
      81              :         // insert the new key-value pair
      82          253 :         params_.insert(std::make_pair(param, value));
      83          253 :     }
      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          242 :     const std::string& request::get_uri_parameter(const std::string& param) const{
      90          242 :         auto it = params_.find(param);
      91          242 :         if(it!=params_.end()){
      92          242 :             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           44 :     std::string request::body() const {
     197           44 :         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          174 :     void request::set_read_ahead(const uint8_t* data, size_t size) {
     222          174 :         if (data && size > 0) {
     223          174 :             read_ahead_.assign(data, data + size);
     224          174 :             read_ahead_offset_ = 0;
     225              :         }
     226          174 :     }
     227              : 
     228          162 :     size_t request::content_length() const {
     229          162 :         return http_request_ ? http_request_->get_content_length() : 0;
     230              :     }
     231              : 
     232          606 :     bool request::is_chunked() const {
     233          606 :         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         1506 :     size_t request::read_ahead_available() const {
     242         1506 :         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              :             auto [ec, bytes] = co_await sock->read_some(buffer, max_size);
     265              :             co_return bytes;
     266              :         }
     267              : 
     268              :         co_return 0;
     269           88 :     }
     270              : 
     271              :     // --- Chunked transfer encoding decoder ---
     272              : 
     273           60 :     static bool is_hex_char(uint8_t c) {
     274           60 :         return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
     275              :     }
     276              : 
     277           34 :     static size_t hex_value(uint8_t c) {
     278           34 :         if (c >= '0' && c <= '9') return c - '0';
     279            2 :         if (c >= 'a' && c <= 'f') return c - 'a' + 10;
     280            0 :         if (c >= 'A' && c <= 'F') return c - 'A' + 10;
     281            0 :         return 0;
     282              :     }
     283              : 
     284           18 :     thinger::awaitable<size_t> request::read_some_chunked(uint8_t* buffer, size_t max_size) {
     285              :         size_t output = 0;
     286              : 
     287              :         while (output == 0 && chunk_state_ != chunk_state::done) {
     288              :             if (chunk_state_ == chunk_state::data && chunk_remaining_ > 0) {
     289              :                 // Fast path: read data directly into user buffer (no copy overhead)
     290              :                 size_t to_read = std::min(chunk_remaining_, max_size - output);
     291              :                 size_t bytes = co_await raw_read_some(buffer + output, to_read);
     292              :                 if (bytes == 0) co_return output;
     293              :                 chunk_remaining_ -= bytes;
     294              :                 output += bytes;
     295              :                 if (chunk_remaining_ == 0) {
     296              :                     chunk_state_ = chunk_state::data_cr;
     297              :                 }
     298              :             } else {
     299              :                 // Slow path: read a batch of raw bytes for framing
     300              :                 uint8_t raw[512];
     301              :                 size_t raw_bytes = co_await raw_read_some(raw, sizeof(raw));
     302              :                 if (raw_bytes == 0) co_return output;
     303              : 
     304              :                 size_t i = 0;
     305              :                 while (i < raw_bytes && chunk_state_ != chunk_state::done && output < max_size) {
     306              :                     uint8_t byte = raw[i];
     307              : 
     308              :                     switch (chunk_state_) {
     309              :                         case chunk_state::size:
     310              :                             if (is_hex_char(byte)) {
     311              :                                 chunk_size_accum_ = chunk_size_accum_ * 16 + hex_value(byte);
     312              :                                 i++;
     313              :                             } else if (byte == '\r') {
     314              :                                 chunk_state_ = chunk_state::size_lf;
     315              :                                 i++;
     316              :                             } else {
     317              :                                 // Skip chunk extensions (e.g. ";ext=val")
     318              :                                 i++;
     319              :                             }
     320              :                             break;
     321              : 
     322              :                         case chunk_state::size_lf:
     323              :                             if (byte == '\n') {
     324              :                                 if (chunk_size_accum_ == 0) {
     325              :                                     // Last chunk — expect trailing CRLF
     326              :                                     chunk_state_ = chunk_state::trailer_lf;
     327              :                                 } else {
     328              :                                     chunk_remaining_ = chunk_size_accum_;
     329              :                                     chunk_size_accum_ = 0;
     330              :                                     chunk_state_ = chunk_state::data;
     331              :                                 }
     332              :                                 i++;
     333              : 
     334              :                                 // If entering data state and raw buffer has more bytes,
     335              :                                 // copy data directly from raw buffer (avoid another read)
     336              :                                 if (chunk_state_ == chunk_state::data && i < raw_bytes) {
     337              :                                     size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
     338              :                                     size_t to_copy = std::min(data_avail, max_size - output);
     339              :                                     std::memcpy(buffer + output, raw + i, to_copy);
     340              :                                     output += to_copy;
     341              :                                     i += to_copy;
     342              :                                     chunk_remaining_ -= to_copy;
     343              :                                     if (chunk_remaining_ == 0) {
     344              :                                         chunk_state_ = chunk_state::data_cr;
     345              :                                     }
     346              :                                 }
     347              :                             } else {
     348              :                                 i++; // malformed, skip
     349              :                             }
     350              :                             break;
     351              : 
     352              :                         case chunk_state::data:
     353              :                             // We shouldn't reach here (handled above), but safety
     354              :                             {
     355              :                                 size_t data_avail = std::min(chunk_remaining_, raw_bytes - i);
     356              :                                 size_t to_copy = std::min(data_avail, max_size - output);
     357              :                                 std::memcpy(buffer + output, raw + i, to_copy);
     358              :                                 output += to_copy;
     359              :                                 i += to_copy;
     360              :                                 chunk_remaining_ -= to_copy;
     361              :                                 if (chunk_remaining_ == 0) {
     362              :                                     chunk_state_ = chunk_state::data_cr;
     363              :                                 }
     364              :                             }
     365              :                             break;
     366              : 
     367              :                         case chunk_state::data_cr:
     368              :                             if (byte == '\r') chunk_state_ = chunk_state::data_lf;
     369              :                             i++;
     370              :                             break;
     371              : 
     372              :                         case chunk_state::data_lf:
     373              :                             if (byte == '\n') {
     374              :                                 chunk_state_ = chunk_state::size;
     375              :                                 chunk_size_accum_ = 0;
     376              :                             }
     377              :                             i++;
     378              :                             break;
     379              : 
     380              :                         case chunk_state::trailer_lf:
     381              :                             if (byte == '\r') {
     382              :                                 i++;
     383              :                                 // Expect final \n
     384              :                                 if (i < raw_bytes && raw[i] == '\n') {
     385              :                                     i++;
     386              :                                 }
     387              :                                 chunk_state_ = chunk_state::done;
     388              :                             } else {
     389              :                                 // Trailer header line — skip until we find empty CRLF
     390              :                                 i++;
     391              :                             }
     392              :                             break;
     393              : 
     394              :                         case chunk_state::done:
     395              :                             break;
     396              :                     }
     397              :                 }
     398              : 
     399              :                 // Push unconsumed raw bytes back to read-ahead for next call
     400              :                 if (i < raw_bytes) {
     401              :                     read_ahead_.assign(raw + i, raw + raw_bytes);
     402              :                     read_ahead_offset_ = 0;
     403              :                 }
     404              :             }
     405              :         }
     406              : 
     407              :         co_return output;
     408           36 :     }
     409              : 
     410              :     // --- Public read API (dispatches to raw or chunked) ---
     411              : 
     412          410 :     thinger::awaitable<size_t> request::read(uint8_t* buffer, size_t size) {
     413              :         if (is_chunked()) {
     414              :             // For chunked, read decoded data until we have `size` bytes or EOF
     415              :             size_t total = 0;
     416              :             while (total < size) {
     417              :                 size_t bytes = co_await read_some_chunked(buffer + total, size - total);
     418              :                 if (bytes == 0) break;
     419              :                 total += bytes;
     420              :             }
     421              :             co_return total;
     422              :         }
     423              : 
     424              :         // Non-chunked: read exact size
     425              :         size_t total = 0;
     426              : 
     427              :         // Consume from read-ahead first
     428              :         size_t avail = read_ahead_available();
     429              :         if (avail > 0) {
     430              :             size_t from_ahead = std::min(avail, size);
     431              :             std::memcpy(buffer, read_ahead_.data() + read_ahead_offset_, from_ahead);
     432              :             read_ahead_offset_ += from_ahead;
     433              :             total += from_ahead;
     434              :             if (read_ahead_offset_ >= read_ahead_.size()) {
     435              :                 read_ahead_.clear();
     436              :                 read_ahead_offset_ = 0;
     437              :             }
     438              :         }
     439              : 
     440              :         // Read remaining from socket
     441              :         if (total < size) {
     442              :             auto sock = get_socket();
     443              :             if (sock) {
     444              :                 size_t remaining = size - total;
     445              :                 auto [ec, bytes] = co_await sock->read(buffer + total, remaining);
     446              :                 total += bytes;
     447              :             }
     448              :         }
     449              : 
     450              :         co_return total;
     451          820 :     }
     452              : 
     453           38 :     thinger::awaitable<size_t> request::read_some(uint8_t* buffer, size_t max_size) {
     454              :         if (is_chunked()) {
     455              :             co_return co_await read_some_chunked(buffer, max_size);
     456              :         }
     457              :         co_return co_await raw_read_some(buffer, max_size);
     458           76 :     }
     459              : 
     460          158 :     thinger::awaitable<bool> request::read_body() {
     461              :         if (!http_request_) co_return false;
     462              : 
     463              :         if (is_chunked()) {
     464              :             // Chunked: read decoded chunks until EOF, respecting max_body_size
     465              :             auto& body = http_request_->get_body();
     466              :             uint8_t buf[8192];
     467              :             while (true) {
     468              :                 size_t bytes = co_await read_some_chunked(buf, sizeof(buf));
     469              :                 if (bytes == 0) break;
     470              :                 if (body.size() + bytes > max_body_size_) co_return false;
     471              :                 body.append(reinterpret_cast<char*>(buf), bytes);
     472              :             }
     473              : 
     474              :             // Decompress chunked body if Content-Encoding is set
     475              :             if (http_request_->has_header("Content-Encoding")) {
     476              :                 std::string encoding = http_request_->get_header("Content-Encoding");
     477              :                 if (encoding == "gzip") {
     478              :                     auto decompressed = ::thinger::util::gzip::decompress(body);
     479              :                     if (decompressed) {
     480              :                         body = std::move(*decompressed);
     481              :                         http_request_->remove_header("Content-Encoding");
     482              :                     } else {
     483              :                         LOG_ERROR("Failed to decompress gzip request body");
     484              :                         co_return false;
     485              :                     }
     486              :                 } else if (encoding == "deflate") {
     487              :                     auto decompressed = ::thinger::util::deflate::decompress(body);
     488              :                     if (decompressed) {
     489              :                         body = std::move(*decompressed);
     490              :                         http_request_->remove_header("Content-Encoding");
     491              :                     } else {
     492              :                         LOG_ERROR("Failed to decompress deflate request body");
     493              :                         co_return false;
     494              :                     }
     495              :                 }
     496              :             }
     497              : 
     498              :             co_return true;
     499              :         }
     500              : 
     501              :         // Content-Length based
     502              :         size_t cl = http_request_->get_content_length();
     503              :         if (cl == 0) co_return true;
     504              : 
     505              :         auto& body = http_request_->get_body();
     506              :         body.resize(cl);
     507              : 
     508              :         size_t bytes_read = co_await read(reinterpret_cast<uint8_t*>(body.data()), cl);
     509              :         if (bytes_read != cl) co_return false;
     510              : 
     511              :         // Decompress body if Content-Encoding is set
     512              :         if (http_request_->has_header("Content-Encoding")) {
     513              :             std::string encoding = http_request_->get_header("Content-Encoding");
     514              :             if (encoding == "gzip") {
     515              :                 auto decompressed = ::thinger::util::gzip::decompress(body);
     516              :                 if (decompressed) {
     517              :                     body = std::move(*decompressed);
     518              :                     http_request_->remove_header("Content-Encoding");
     519              :                 } else {
     520              :                     LOG_ERROR("Failed to decompress gzip request body");
     521              :                     co_return false;
     522              :                 }
     523              :             } else if (encoding == "deflate") {
     524              :                 auto decompressed = ::thinger::util::deflate::decompress(body);
     525              :                 if (decompressed) {
     526              :                     body = std::move(*decompressed);
     527              :                     http_request_->remove_header("Content-Encoding");
     528              :                 } else {
     529              :                     LOG_ERROR("Failed to decompress deflate request body");
     530              :                     co_return false;
     531              :                 }
     532              :             }
     533              :         }
     534              : 
     535              :         co_return true;
     536          316 :     }
     537              : 
     538              : }
        

Generated by: LCOV version 2.0-1