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

            Line data    Source code
       1              : #ifndef THINGER_HTTP_SERVER_BODY_READER_HPP
       2              : #define THINGER_HTTP_SERVER_BODY_READER_HPP
       3              : 
       4              : #include <cstdint>
       5              : #include <functional>
       6              : #include <limits>
       7              : #include <string>
       8              : #include <vector>
       9              : #include "../common/chunked_decoder.hpp"
      10              : #include "../../util/types.hpp"
      11              : 
      12              : namespace thinger::http {
      13              : 
      14              :     // Why reading a request body failed
      15              :     enum class body_error {
      16              :         none,
      17              :         too_large,      // larger than the allowed size
      18              :         malformed,      // invalid chunked framing
      19              :         incomplete,     // the connection ended before the body did
      20              :         bad_encoding,   // the Content-Encoding (gzip, deflate) could not be decoded
      21              :         timeout         // the read deadline of the request passed (see request::set_read_deadline())
      22              :     };
      23              : 
      24              :     // The error of a read its source ended at the read deadline of the request: ETIME, which no
      25              :     // socket reports, not to take it for a TCP connection that timed out (ETIMEDOUT)
      26              :     inline constexpr auto read_deadline_expired = boost::system::errc::stream_timeout;
      27              : 
      28              :     /**
      29              :      * Reads a request body framed by Content-Length or by the chunked transfer coding: first
      30              :      * from the bytes already read past the request headers (read-ahead), then from the
      31              :      * connection. Reads never go past the end of the body, so any data following it (the
      32              :      * next pipelined request) is kept in the read-ahead for whoever reads next.
      33              :      */
      34              :     class body_reader {
      35              :     public:
      36              :         /// Reads up to `size` bytes from the connection: 0 on end of stream or error (see
      37              :         /// detail::request_source::read_body_some()), read_deadline_expired at the read deadline
      38              :         using source = std::function<thinger::awaitable<io_result>(uint8_t* buffer, size_t size)>;
      39              : 
      40              :         /// Bytes read from the connection at once while decoding chunked framing (never more
      41              :         /// than the connection read buffer, as the excess is handed to the next request)
      42              :         static constexpr size_t framing_read_size = 4096;
      43              : 
      44        90361 :         body_reader() = default;
      45              : 
      46              :         /// Body framing: chunked, or `content_length` bytes
      47              :         void set_framing(bool chunked, std::uint64_t content_length);
      48              : 
      49              :         /// Connection to read from once the read-ahead is exhausted (none: the body is only
      50              :         /// what is in the read-ahead)
      51        27892 :         void set_source(source src) { source_ = std::move(src); }
      52              : 
      53              :         /// Maximum body size (none by default): reading a larger body fails (too_large), a
      54              :         /// Content-Length one at its first read, before reading anything, and a chunked one
      55              :         /// once it exceeds the limit
      56       117944 :         void set_limit(std::uint64_t limit) { limit_ = limit; }
      57         2056 :         std::uint64_t limit() const { return limit_; }
      58              : 
      59              :         void set_read_ahead(const uint8_t* data, size_t size);
      60              : 
      61              :         /// Read-ahead bytes not consumed yet
      62              :         size_t buffered() const;
      63              : 
      64              :         /// Remove and return the unconsumed read-ahead bytes (data following the body)
      65              :         std::vector<uint8_t> take_read_ahead();
      66              : 
      67              :         /// Read exactly `size` body bytes, or less at the end of the body or on error
      68              :         thinger::awaitable<size_t> read(uint8_t* buffer, size_t size);
      69              : 
      70              :         /// Read up to `max_size` body bytes; 0 at the end of the body or on error, a read past
      71              :         /// the read deadline included (timeout). A read cancelled (through the cancellation
      72              :         /// slot of its coroutine) returns 0 too, leaving the body pending, without an error
      73              :         thinger::awaitable<size_t> read_some(uint8_t* buffer, size_t max_size);
      74              : 
      75              :         /// Append the rest of the body to `body`, failing (too_large) if `body` would exceed
      76              :         /// `max_size` bytes. A Content-Length body is checked before reading anything, and
      77              :         /// `body` grows as its bytes arrive, not to the announced length at once.
      78              :         thinger::awaitable<bool> read_all(std::string& body, size_t max_size);
      79              : 
      80              :         /// Read and drop the rest of the body; false if it exceeds `max_size` bytes or fails
      81              :         thinger::awaitable<bool> discard(std::uint64_t max_size);
      82              : 
      83              :         /// Whether part of the body is still unread (false once reading failed)
      84              :         bool has_pending() const;
      85              : 
      86              :         bool is_chunked() const { return chunked_; }
      87              : 
      88        28336 :         body_error error() const { return error_; }
      89           30 :         void set_error(body_error error) { error_ = error; }
      90              : 
      91              :     private:
      92              :         source source_;
      93              :         bool chunked_ = false;
      94              :         std::uint64_t remaining_ = 0;   // unread bytes of a Content-Length body
      95              :         std::uint64_t read_ = 0;        // body bytes read
      96              :         std::uint64_t limit_ = std::numeric_limits<std::uint64_t>::max();
      97              :         chunked_decoder decoder_;
      98              :         body_error error_ = body_error::none;
      99              : 
     100              :         std::vector<uint8_t> read_ahead_;
     101              :         size_t read_ahead_offset_ = 0;
     102              : 
     103              :         /// Read raw bytes: from the read-ahead first, then from the source
     104              :         thinger::awaitable<size_t> read_raw(uint8_t* buffer, size_t max_size);
     105              : 
     106              :         /// Refill the (empty) read-ahead from the source; false on end of stream
     107              :         thinger::awaitable<bool> fill_read_ahead();
     108              : 
     109              :         /// The bytes a read from the source got: none past the read deadline (timeout)
     110              :         size_t received(const io_result& read);
     111              : 
     112              :         thinger::awaitable<size_t> read_some_length(uint8_t* buffer, size_t max_size);
     113              :         thinger::awaitable<size_t> read_some_chunked(uint8_t* buffer, size_t max_size);
     114              :     };
     115              : 
     116              : }
     117              : 
     118              : #endif
        

Generated by: LCOV version 2.0-1