@@ -1478,6 +1478,9 @@ struct BodyReader {
14781478 bool has_error () const { return last_error != Error::Success; }
14791479};
14801480
1481+ // Forward declaration for compression support in StreamHandle
1482+ class decompressor ;
1483+
14811484} // namespace detail
14821485
14831486class ClientImpl {
@@ -1509,6 +1512,11 @@ class ClientImpl {
15091512 Stream *stream_ = nullptr ; // Stream for reading
15101513 detail::BodyReader body_reader_; // Body reading state
15111514
1515+ // Compression support
1516+ std::unique_ptr<detail::decompressor> decompressor_;
1517+ std::string decompress_buffer_; // Buffer for decompressed data
1518+ size_t decompress_offset_ = 0 ; // Read position in decompress_buffer_
1519+
15121520 // Default constructor
15131521 StreamHandle () = default ;
15141522
@@ -1532,35 +1540,25 @@ class ClientImpl {
15321540 bool is_socket_direct_mode () const { return stream_ != nullptr ; }
15331541
15341542 // Read up to len bytes into buf, returns number of bytes read (0 at EOF)
1535- ssize_t read (char *buf, size_t len) {
1536- if (!is_valid () || !response) { return -1 ; }
1537-
1538- if (is_socket_direct_mode ()) {
1539- // Socket direct mode: read from stream via BodyReader
1540- return body_reader_.read (buf, len);
1541- } else {
1542- // Memory buffer mode: read from pre-loaded response body
1543- const auto &body = response->body ;
1544- if (read_offset_ >= body.size ()) { return 0 ; }
1543+ // Implementation is below decompressor class definition
1544+ ssize_t read (char *buf, size_t len);
15451545
1546- auto remaining = body.size () - read_offset_;
1547- auto to_read = (std::min)(len, remaining);
1548- std::memcpy (buf, body.data () + read_offset_, to_read);
1549- read_offset_ += to_read;
1550- return static_cast <ssize_t >(to_read);
1551- }
1552- }
1546+ private:
1547+ // Read with decompression support (implemented after decompressor class)
1548+ ssize_t read_with_decompression (char *buf, size_t len);
15531549
1550+ public:
15541551 // Read all remaining content into a string
15551552 std::string read_all () {
15561553 if (!is_valid () || !response) { return {}; }
15571554
15581555 if (is_socket_direct_mode ()) {
1559- // Socket direct mode: read all from stream
1556+ // Socket direct mode: read all from stream (uses read() for
1557+ // decompression)
15601558 std::string result;
15611559 char buf[8192 ];
15621560 ssize_t n;
1563- while ((n = body_reader_. read (buf, sizeof (buf))) > 0 ) {
1561+ while ((n = read (buf, sizeof (buf))) > 0 ) {
15641562 result.append (buf, static_cast <size_t >(n));
15651563 }
15661564 return result;
@@ -2946,6 +2944,75 @@ inline bool is_field_value(const std::string &s) { return is_field_content(s); }
29462944
29472945} // namespace detail
29482946
2947+ // ----------------------------------------------------------------------------
2948+ // StreamHandle method implementations (after decompressor class definition)
2949+ // ----------------------------------------------------------------------------
2950+
2951+ inline ssize_t ClientImpl::StreamHandle::read (char *buf, size_t len) {
2952+ if (!is_valid () || !response) { return -1 ; }
2953+
2954+ if (is_socket_direct_mode ()) {
2955+ // Socket direct mode: read from stream via BodyReader
2956+ if (decompressor_) { return read_with_decompression (buf, len); }
2957+ return body_reader_.read (buf, len);
2958+ } else {
2959+ // Memory buffer mode: read from pre-loaded response body
2960+ const auto &body = response->body ;
2961+ if (read_offset_ >= body.size ()) { return 0 ; }
2962+
2963+ auto remaining = body.size () - read_offset_;
2964+ auto to_read = (std::min)(len, remaining);
2965+ std::memcpy (buf, body.data () + read_offset_, to_read);
2966+ read_offset_ += to_read;
2967+ return static_cast <ssize_t >(to_read);
2968+ }
2969+ }
2970+
2971+ inline ssize_t ClientImpl::StreamHandle::read_with_decompression (char *buf,
2972+ size_t len) {
2973+ // First, return any buffered decompressed data
2974+ if (decompress_offset_ < decompress_buffer_.size ()) {
2975+ auto available = decompress_buffer_.size () - decompress_offset_;
2976+ auto to_copy = (std::min)(len, available);
2977+ std::memcpy (buf, decompress_buffer_.data () + decompress_offset_, to_copy);
2978+ decompress_offset_ += to_copy;
2979+ return static_cast <ssize_t >(to_copy);
2980+ }
2981+
2982+ // Buffer exhausted, read more compressed data and decompress
2983+ decompress_buffer_.clear ();
2984+ decompress_offset_ = 0 ;
2985+
2986+ char compressed_buf[8192 ];
2987+ auto n = body_reader_.read (compressed_buf, sizeof (compressed_buf));
2988+
2989+ if (n <= 0 ) { return n; } // EOF or error
2990+
2991+ // Decompress the data
2992+ bool decompress_ok =
2993+ decompressor_->decompress (compressed_buf, static_cast <size_t >(n),
2994+ [this ](const char *data, size_t data_len) {
2995+ decompress_buffer_.append (data, data_len);
2996+ return true ;
2997+ });
2998+
2999+ if (!decompress_ok) {
3000+ body_reader_.last_error = Error::Read;
3001+ return -1 ;
3002+ }
3003+
3004+ if (decompress_buffer_.empty ()) {
3005+ // Decompressor needs more data, try again
3006+ return read_with_decompression (buf, len);
3007+ }
3008+
3009+ // Return from the newly decompressed buffer
3010+ auto to_copy = (std::min)(len, decompress_buffer_.size ());
3011+ std::memcpy (buf, decompress_buffer_.data (), to_copy);
3012+ decompress_offset_ = to_copy;
3013+ return static_cast <ssize_t >(to_copy);
3014+ }
3015+
29493016// ----------------------------------------------------------------------------
29503017
29513018/*
@@ -9419,6 +9486,30 @@ ClientImpl::open_stream_direct(const std::string &path,
94199486 handle.response ->get_header_value (" Transfer-Encoding" );
94209487 handle.body_reader_ .chunked = (transfer_encoding == " chunked" );
94219488
9489+ // Set up decompressor based on Content-Encoding
9490+ auto content_encoding = handle.response ->get_header_value (" Content-Encoding" );
9491+ if (!content_encoding.empty ()) {
9492+ #ifdef CPPHTTPLIB_ZLIB_SUPPORT
9493+ if (content_encoding == " gzip" || content_encoding == " deflate" ) {
9494+ handle.decompressor_ = detail::make_unique<detail::gzip_decompressor>();
9495+ } else
9496+ #endif
9497+ #ifdef CPPHTTPLIB_BROTLI_SUPPORT
9498+ if (content_encoding == " br" ) {
9499+ handle.decompressor_ = detail::make_unique<detail::brotli_decompressor>();
9500+ } else
9501+ #endif
9502+ #ifdef CPPHTTPLIB_ZSTD_SUPPORT
9503+ if (content_encoding == " zstd" ) {
9504+ handle.decompressor_ = detail::make_unique<detail::zstd_decompressor>();
9505+ } else
9506+ #endif
9507+ {
9508+ // Unsupported encoding - leave decompressor_ null
9509+ // Data will be returned as-is (compressed)
9510+ }
9511+ }
9512+
94229513 return handle;
94239514}
94249515
0 commit comments