LCOV - code coverage report
Current view: top level - http/server - body_reader.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 45 45
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 15 15

            Line data    Source code
       1              : #include "body_reader.hpp"
       2              : #include <algorithm>
       3              : #include <cstring>
       4              : #include <limits>
       5              : #include <boost/asio/this_coro.hpp>
       6              : 
       7              : namespace thinger::http {
       8              : 
       9              :     namespace {
      10              : 
      11              :         // Whether the coroutine reading was cancelled (e.g. the other side of a `||` ended
      12              :         // first): its read ended without taking anything, so the body is not incomplete,
      13              :         // just not read yet
      14          319 :         thinger::awaitable<bool> cancelled() {
      15              :             auto state = co_await boost::asio::this_coro::cancellation_state;
      16              :             co_return state.cancelled() != boost::asio::cancellation_type::none;
      17          638 :         }
      18              : 
      19              :         // The first piece of a Content-Length body read whole (see read_all())
      20              :         constexpr size_t body_first_piece = 64 * 1024;
      21              : 
      22              :     }
      23              : 
      24        90361 :     void body_reader::set_framing(bool chunked, std::uint64_t content_length) {
      25        90361 :         chunked_ = chunked;
      26        90361 :         remaining_ = chunked ? 0 : content_length;
      27        90361 :         read_ = 0;
      28        90361 :         decoder_ = {};
      29        90361 :         error_ = body_error::none;
      30        90361 :     }
      31              : 
      32         4643 :     void body_reader::set_read_ahead(const uint8_t* data, size_t size) {
      33         4643 :         if (data && size > 0) {
      34         4637 :             read_ahead_.assign(data, data + size);
      35         4637 :             read_ahead_offset_ = 0;
      36              :         }
      37         4643 :     }
      38              : 
      39      4160441 :     size_t body_reader::buffered() const {
      40      4160441 :         return read_ahead_.size() > read_ahead_offset_ ? read_ahead_.size() - read_ahead_offset_ : 0;
      41              :     }
      42              : 
      43        17804 :     std::vector<uint8_t> body_reader::take_read_ahead() {
      44        35608 :         std::vector<uint8_t> data(read_ahead_.begin() + static_cast<std::ptrdiff_t>(read_ahead_.size() - buffered()),
      45        35608 :                                   read_ahead_.end());
      46        17804 :         read_ahead_.clear();
      47        17804 :         read_ahead_offset_ = 0;
      48        17804 :         return data;
      49              :     }
      50              : 
      51       122788 :     bool body_reader::has_pending() const {
      52       122788 :         if (error_ != body_error::none) return false;
      53       121285 :         return chunked_ ? !decoder_.done() : remaining_ > 0;
      54              :     }
      55              : 
      56      4134515 :     size_t body_reader::received(const io_result& read) {
      57      4134515 :         const auto& [ec, bytes] = read;
      58      4134515 :         if (bytes == 0 && ec == read_deadline_expired) [[unlikely]] error_ = body_error::timeout;
      59      4134515 :         return bytes;
      60              :     }
      61              : 
      62      4125976 :     thinger::awaitable<size_t> body_reader::read_raw(uint8_t* buffer, size_t max_size) {
      63              :         if (size_t available = buffered()) {
      64              :             size_t bytes = std::min(available, max_size);
      65              :             std::memcpy(buffer, read_ahead_.data() + read_ahead_offset_, bytes);
      66              :             read_ahead_offset_ += bytes;
      67              :             co_return bytes;
      68              :         }
      69              :         if (!source_) co_return 0;
      70              :         co_return received(co_await source_(buffer, max_size));
      71      8251952 :     }
      72              : 
      73         3589 :     thinger::awaitable<bool> body_reader::fill_read_ahead() {
      74              :         read_ahead_.resize(framing_read_size);
      75              :         read_ahead_offset_ = 0;
      76              :         size_t bytes = source_ ? received(co_await source_(read_ahead_.data(), read_ahead_.size())) : 0;
      77              :         read_ahead_.resize(bytes);
      78              :         co_return bytes > 0;
      79         7178 :     }
      80              : 
      81      4126412 :     thinger::awaitable<size_t> body_reader::read_some_length(uint8_t* buffer, size_t max_size) {
      82              :         auto to_read = static_cast<size_t>(std::min<std::uint64_t>(max_size, remaining_));
      83              :         if (to_read == 0) co_return 0;
      84              :         size_t bytes = co_await read_raw(buffer, to_read);
      85              :         if (bytes == 0) {
      86              :             if (error_ == body_error::none && !co_await cancelled()) error_ = body_error::incomplete;
      87              :             co_return 0;
      88              :         }
      89              :         remaining_ -= bytes;
      90              :         co_return bytes;
      91      8252824 :     }
      92              : 
      93         9266 :     thinger::awaitable<size_t> body_reader::read_some_chunked(uint8_t* buffer, size_t max_size) {
      94              :         while (!decoder_.done()) {
      95              :             if (buffered() == 0) {
      96              :                 // Within chunk data: read it straight into the caller buffer
      97              :                 if (size_t pending = decoder_.data_pending()) {
      98              :                     size_t bytes = source_ ? received(co_await source_(buffer, std::min(pending, max_size))) : 0;
      99              :                     if (bytes == 0) {
     100              :                         if (error_ == body_error::none && !co_await cancelled()) error_ = body_error::incomplete;
     101              :                         co_return 0;
     102              :                     }
     103              :                     decoder_.data_consumed(bytes);
     104              :                     co_return bytes;
     105              :                 }
     106              :                 // Framing: decode it from the read-ahead, where anything read past the end
     107              :                 // of the body stays for the next request
     108              :                 if (!co_await fill_read_ahead()) {
     109              :                     if (error_ == body_error::none && !co_await cancelled()) error_ = body_error::incomplete;
     110              :                     co_return 0;
     111              :                 }
     112              :             }
     113              : 
     114              :             size_t produced = 0;
     115              :             read_ahead_offset_ += decoder_.decode(read_ahead_.data() + read_ahead_offset_, buffered(),
     116              :                                                   buffer, max_size, produced);
     117              :             if (decoder_.failed()) {
     118              :                 error_ = body_error::malformed;
     119              :                 co_return 0;
     120              :             }
     121              :             if (produced > 0) co_return produced;
     122              :         }
     123              :         co_return 0;
     124        18532 :     }
     125              : 
     126      4135714 :     thinger::awaitable<size_t> body_reader::read_some(uint8_t* buffer, size_t max_size) {
     127              :         if (error_ != body_error::none || max_size == 0) co_return 0;
     128              :         // A Content-Length body over the limit fails before anything is read
     129              :         if (!chunked_ && remaining_ > 0 && (remaining_ > limit_ || read_ > limit_ - remaining_)) {
     130              :             error_ = body_error::too_large;
     131              :             co_return 0;
     132              :         }
     133              :         size_t bytes = chunked_ ? co_await read_some_chunked(buffer, max_size)
     134              :                                 : co_await read_some_length(buffer, max_size);
     135              :         read_ += bytes;
     136              :         if (read_ > limit_) {
     137              :             error_ = body_error::too_large;
     138              :             co_return 0;
     139              :         }
     140              :         co_return bytes;
     141      8271428 :     }
     142              : 
     143         2317 :     thinger::awaitable<size_t> body_reader::read(uint8_t* buffer, size_t size) {
     144              :         size_t total = 0;
     145              :         while (total < size) {
     146              :             size_t bytes = co_await read_some(buffer + total, size - total);
     147              :             if (bytes == 0) break;
     148              :             total += bytes;
     149              :         }
     150              :         co_return total;
     151         4634 :     }
     152              : 
     153         2338 :     thinger::awaitable<bool> body_reader::read_all(std::string& body, size_t max_size) {
     154              :         if (error_ != body_error::none) co_return false;
     155              : 
     156              :         if (!chunked_) {
     157              :             // Known size: reject it before allocating anything
     158              :             if (remaining_ > max_size || body.size() > max_size - remaining_) {
     159              :                 error_ = body_error::too_large;
     160              :                 co_return false;
     161              :             }
     162              :             // Grown as its bytes arrive, not to its announced size at once: a head announcing a
     163              :             // large body takes no memory for bytes that never come (as Go's io.ReadAll). The
     164              :             // first piece holds a small body whole; each next one doubles what arrived
     165              :             size_t offset = body.size();
     166              :             size_t end = offset + static_cast<size_t>(remaining_);
     167              :             while (body.size() < end) {
     168              :                 size_t size = body.size();
     169              :                 size_t piece = std::min(end - size, std::max(body_first_piece, size - offset));
     170              :                 body.resize(size + piece);
     171              :                 size_t bytes = co_await read(reinterpret_cast<uint8_t*>(body.data()) + size, piece);
     172              :                 if (bytes < piece) {
     173              :                     body.resize(size + bytes);
     174              :                     co_return false;
     175              :                 }
     176              :             }
     177              :             co_return true;
     178              :         }
     179              : 
     180              :         uint8_t buffer[8192];
     181              :         while (size_t bytes = co_await read_some(buffer, sizeof(buffer))) {
     182              :             if (bytes > max_size || body.size() > max_size - bytes) {
     183              :                 error_ = body_error::too_large;
     184              :                 co_return false;
     185              :             }
     186              :             body.append(reinterpret_cast<char*>(buffer), bytes);
     187              :         }
     188              :         co_return error_ == body_error::none;
     189         4676 :     }
     190              : 
     191          197 :     thinger::awaitable<bool> body_reader::discard(std::uint64_t max_size) {
     192              :         if (error_ != body_error::none) co_return false;
     193              :         if (!has_pending()) co_return true;
     194              :         if (!chunked_ && remaining_ > max_size) co_return false;
     195              : 
     196              :         uint8_t buffer[8192];
     197              :         std::uint64_t discarded = 0;
     198              :         while (has_pending()) {
     199              :             size_t bytes = co_await read_some(buffer, sizeof(buffer));
     200              :             if (bytes == 0) break;
     201              :             discarded += bytes;
     202              :             if (discarded > max_size) co_return false;
     203              :         }
     204              :         co_return error_ == body_error::none && !has_pending();
     205          394 :     }
     206              : 
     207              : }
        

Generated by: LCOV version 2.0-1