Loading CMakeLists.txt +8 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,14 @@ find_package(libcmdplus REQUIRED) find_package(libhtmlpp) # --- Response/request compression (src/httpcompression.cpp) --- # No CMake config package for either upstream, and no bundled FindBrotli module -- zlib gets # CMake's own FindZLIB, brotli goes through pkg-config (libbrotlienc/libbrotlidec, Debian # package libbrotli-dev). find_package(ZLIB REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(BROTLI REQUIRED IMPORTED_TARGET libbrotlienc libbrotlidec) include(CheckIncludeFileCXX) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/) Loading src/CMakeLists.txt +6 −4 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ set(libhttpSrcs httpd.cpp hpack.cpp qpack.cpp httpcompression.cpp ) add_library(httppp SHARED ${libhttpSrcs}) Loading @@ -20,11 +21,11 @@ add_library(httppp-static STATIC ${libhttpSrcs}) generate_export_header(httppp) if(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows") target_link_libraries(httppp netplus::netplus cmdplus::cmdplus) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus) target_link_libraries(httppp netplus::netplus cmdplus::cmdplus ZLIB::ZLIB PkgConfig::BROTLI) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus ZLIB::ZLIB PkgConfig::BROTLI) else() target_link_libraries(httppp netplus::netplus cmdplus::cmdplus pthread) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus pthread) target_link_libraries(httppp netplus::netplus cmdplus::cmdplus pthread ZLIB::ZLIB PkgConfig::BROTLI) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus pthread ZLIB::ZLIB PkgConfig::BROTLI) endif() set_property(TARGET httppp PROPERTY VERSION ${Upstream_VERSION}) Loading Loading @@ -53,6 +54,7 @@ install(FILES httpd.h https.h qpack.h mimetypes.h httpcompression.h "${CMAKE_BINARY_DIR}/config.h" "${CMAKE_CURRENT_BINARY_DIR}/httppp_export.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/httppp Loading src/http.cpp +10 −2 Original line number Diff line number Diff line Loading @@ -224,8 +224,11 @@ netplus::TlsSessionCache& libhttppp::HttpClient::tlsSessionCache() { return cache; } libhttppp::HttpClient::HttpClient(const HttpUrl& desturl, int vers, int timeoutSec) : _url(desturl), _vers(vers){ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl, int vers, int timeoutSec, const netplus::TlsTrustPolicy& trustPolicy) : _url(desturl), _vers(vers), _trustPolicy(trustPolicy){ if (_trustPolicy.verifyPeer && _trustPolicy.expectedHostname.empty()) _trustPolicy.expectedHostname = _url.getHost(); _recvTimeoutSec = timeoutSec; _sendTimeoutSec = timeoutSec; try { Loading Loading @@ -358,6 +361,11 @@ void libhttppp::HttpClient::resetConnection(){ std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); // Opt-in peer verification (see HttpClient's ctor doc) -- default-constructed // TlsTrustPolicy has verifyPeer=false, a no-op, so callers who never pass one get the // exact pre-existing behavior. sslsock->getTls().trust_policy = _trustPolicy; // Advertise ALPN based on version preference if (_vers >= 4) { sslsock->getTls().client_alpn_protocols = Loading src/http.h +9 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,13 @@ namespace libhttppp { // (see resetConnection()) in addition to being the initial value // setTimeout() would otherwise set afterward -- too late to affect // that first connect. HttpClient( const HttpUrl &desturl, int vers = 2, int timeoutSec = 60); // trustPolicy is opt-in (default-constructed = verify nothing, the pre-existing // behavior every caller got before this parameter existed): pass one with // verifyPeer=true to get real hostname/CA-chain/pinned-fingerprint verification of the // upstream's TLS certificate (see netplus::TlsTrustPolicy in <netplus/crypto/cert_verify.h>). // If trustPolicy.expectedHostname is left empty, it defaults to desturl's host. HttpClient( const HttpUrl &desturl, int vers = 2, int timeoutSec = 60, const netplus::TlsTrustPolicy &trustPolicy = netplus::TlsTrustPolicy()); ~HttpClient()=default; void reconnect(); void setTimeout(int timeout_sec); Loading Loading @@ -157,6 +163,8 @@ namespace libhttppp { // across reconnects to the same host. One cache per process. static netplus::TlsSessionCache& tlsSessionCache(); private: netplus::TlsTrustPolicy _trustPolicy; void _ensureConnected(); // Thin wrapper around netplus::tcp::connectTimeout() using // _recvTimeoutSec, translating its NetException into HTTPException. Loading src/httpcompression.cpp 0 → 100644 +243 −0 Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include "httpcompression.h" #include <algorithm> #include <array> #include <cctype> #include <cstdlib> #include <set> #include <zlib.h> #include <brotli/decode.h> #include <brotli/encode.h> namespace libhttppp { namespace compression { namespace { std::string toLower(const std::string &s) { std::string out(s); std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); return out; } std::string trim(const std::string &s) { size_t start = 0, end = s.size(); while (start < end && std::isspace(static_cast<unsigned char>(s[start]))) ++start; while (end > start && std::isspace(static_cast<unsigned char>(s[end - 1]))) --end; return s.substr(start, end - start); } struct Preference { double q = 1.0; bool present = false; }; } // namespace Encoding fromToken(const std::string &contentEncoding) { std::string tok = trim(toLower(contentEncoding)); if (tok.empty() || tok == "identity") return Encoding::None; if (tok == "gzip" || tok == "x-gzip") return Encoding::Gzip; if (tok == "br") return Encoding::Brotli; return Encoding::Other; } const char *toToken(Encoding enc) { switch (enc) { case Encoding::Gzip: return "gzip"; case Encoding::Brotli: return "br"; default: return ""; } } Encoding negotiate(const std::string &acceptEncoding) { if (acceptEncoding.empty()) return Encoding::None; Preference br, gzip, star; size_t pos = 0; while (pos <= acceptEncoding.size()) { size_t comma = acceptEncoding.find(',', pos); std::string part = acceptEncoding.substr(pos, comma == std::string::npos ? std::string::npos : comma - pos); pos = (comma == std::string::npos) ? acceptEncoding.size() + 1 : comma + 1; if (part.empty()) continue; size_t semi = part.find(';'); std::string name = toLower(trim(part.substr(0, semi))); double q = 1.0; if (semi != std::string::npos) { std::string qpart = trim(part.substr(semi + 1)); size_t eq = qpart.find('='); if (eq != std::string::npos) { try { q = std::stod(trim(qpart.substr(eq + 1))); } catch (const std::exception &) { q = 1.0; } } } if (name == "br") { br = {q, true}; } else if (name == "gzip" || name == "x-gzip") { gzip = {q, true}; } else if (name == "*") { star = {q, true}; } if (comma == std::string::npos) break; } // A token not explicitly listed falls back to "*"'s preference, if present, per RFC // 7231 §5.3.4. double brQ = br.present ? br.q : (star.present ? star.q : 0.0); double gzipQ = gzip.present ? gzip.q : (star.present ? star.q : 0.0); bool brOk = brQ > 0.0; bool gzipOk = gzipQ > 0.0; if (brOk && gzipOk) return brQ >= gzipQ ? Encoding::Brotli : Encoding::Gzip; if (brOk) return Encoding::Brotli; if (gzipOk) return Encoding::Gzip; return Encoding::None; } bool isCompressible(const std::string &contentType) { if (contentType.empty()) return false; if (contentType.rfind("text/", 0) == 0) return true; static const std::set<std::string> kAlso = { "application/json", "application/javascript", "application/x-javascript", "application/xml", "application/xhtml+xml", "application/rss+xml", "application/atom+xml", "image/svg+xml", }; return kAlso.count(contentType) != 0; } bool gzipCompress(const std::vector<char> &in, std::vector<char> &out, int level) { z_stream strm{}; // windowBits 15+16 asks zlib for a gzip (not raw-deflate/zlib) wrapper. if (deflateInit2(&strm, level, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) { return false; } out.resize(deflateBound(&strm, static_cast<uLong>(in.size()))); strm.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(in.data())); strm.avail_in = static_cast<uInt>(in.size()); strm.next_out = reinterpret_cast<Bytef *>(out.data()); strm.avail_out = static_cast<uInt>(out.size()); bool ok = deflate(&strm, Z_FINISH) == Z_STREAM_END; out.resize(ok ? strm.total_out : 0); deflateEnd(&strm); return ok; } bool gzipDecompress(const std::vector<char> &in, std::vector<char> &out) { z_stream strm{}; // windowBits 15+32 auto-detects a gzip or zlib header. if (inflateInit2(&strm, 15 + 32) != Z_OK) return false; strm.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(in.data())); strm.avail_in = static_cast<uInt>(in.size()); out.clear(); std::array<char, 64 * 1024> chunk; int ret = Z_OK; do { strm.next_out = reinterpret_cast<Bytef *>(chunk.data()); strm.avail_out = static_cast<uInt>(chunk.size()); ret = inflate(&strm, Z_NO_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { inflateEnd(&strm); return false; } out.insert(out.end(), chunk.data(), chunk.data() + (chunk.size() - strm.avail_out)); } while (ret != Z_STREAM_END && strm.avail_in > 0); inflateEnd(&strm); return ret == Z_STREAM_END; } bool brotliCompress(const std::vector<char> &in, std::vector<char> &out, int quality) { size_t bound = BrotliEncoderMaxCompressedSize(in.size()); if (bound == 0) bound = 64; // MaxCompressedSize documents 0 as "input too large to bound"; // never true here, but keep resize() from truncating to empty. out.resize(bound); size_t encodedSize = out.size(); BROTLI_BOOL ok = BrotliEncoderCompress( quality, BROTLI_DEFAULT_WINDOW, BROTLI_MODE_GENERIC, in.size(), reinterpret_cast<const uint8_t *>(in.data()), &encodedSize, reinterpret_cast<uint8_t *>(out.data())); if (!ok) { out.clear(); return false; } out.resize(encodedSize); return true; } bool brotliDecompress(const std::vector<char> &in, std::vector<char> &out) { BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); if (!state) return false; out.clear(); const uint8_t *nextIn = reinterpret_cast<const uint8_t *>(in.data()); size_t availIn = in.size(); std::array<uint8_t, 64 * 1024> chunk; BrotliDecoderResult result; do { uint8_t *nextOut = chunk.data(); size_t availOut = chunk.size(); result = BrotliDecoderDecompressStream(state, &availIn, &nextIn, &availOut, &nextOut, nullptr); out.insert(out.end(), reinterpret_cast<char *>(chunk.data()), reinterpret_cast<char *>(chunk.data() + (chunk.size() - availOut))); if (result == BROTLI_DECODER_RESULT_ERROR) { BrotliDecoderDestroyInstance(state); return false; } } while (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT); bool ok = result == BROTLI_DECODER_RESULT_SUCCESS; BrotliDecoderDestroyInstance(state); return ok; } } // namespace compression } // namespace libhttppp Loading
CMakeLists.txt +8 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,14 @@ find_package(libcmdplus REQUIRED) find_package(libhtmlpp) # --- Response/request compression (src/httpcompression.cpp) --- # No CMake config package for either upstream, and no bundled FindBrotli module -- zlib gets # CMake's own FindZLIB, brotli goes through pkg-config (libbrotlienc/libbrotlidec, Debian # package libbrotli-dev). find_package(ZLIB REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(BROTLI REQUIRED IMPORTED_TARGET libbrotlienc libbrotlidec) include(CheckIncludeFileCXX) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/) Loading
src/CMakeLists.txt +6 −4 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ set(libhttpSrcs httpd.cpp hpack.cpp qpack.cpp httpcompression.cpp ) add_library(httppp SHARED ${libhttpSrcs}) Loading @@ -20,11 +21,11 @@ add_library(httppp-static STATIC ${libhttpSrcs}) generate_export_header(httppp) if(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows") target_link_libraries(httppp netplus::netplus cmdplus::cmdplus) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus) target_link_libraries(httppp netplus::netplus cmdplus::cmdplus ZLIB::ZLIB PkgConfig::BROTLI) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus ZLIB::ZLIB PkgConfig::BROTLI) else() target_link_libraries(httppp netplus::netplus cmdplus::cmdplus pthread) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus pthread) target_link_libraries(httppp netplus::netplus cmdplus::cmdplus pthread ZLIB::ZLIB PkgConfig::BROTLI) target_link_libraries(httppp-static netplus::netplus-static cmdplus::cmdplus pthread ZLIB::ZLIB PkgConfig::BROTLI) endif() set_property(TARGET httppp PROPERTY VERSION ${Upstream_VERSION}) Loading Loading @@ -53,6 +54,7 @@ install(FILES httpd.h https.h qpack.h mimetypes.h httpcompression.h "${CMAKE_BINARY_DIR}/config.h" "${CMAKE_CURRENT_BINARY_DIR}/httppp_export.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/httppp Loading
src/http.cpp +10 −2 Original line number Diff line number Diff line Loading @@ -224,8 +224,11 @@ netplus::TlsSessionCache& libhttppp::HttpClient::tlsSessionCache() { return cache; } libhttppp::HttpClient::HttpClient(const HttpUrl& desturl, int vers, int timeoutSec) : _url(desturl), _vers(vers){ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl, int vers, int timeoutSec, const netplus::TlsTrustPolicy& trustPolicy) : _url(desturl), _vers(vers), _trustPolicy(trustPolicy){ if (_trustPolicy.verifyPeer && _trustPolicy.expectedHostname.empty()) _trustPolicy.expectedHostname = _url.getHost(); _recvTimeoutSec = timeoutSec; _sendTimeoutSec = timeoutSec; try { Loading Loading @@ -358,6 +361,11 @@ void libhttppp::HttpClient::resetConnection(){ std::map<std::string, netplus::ssl::CertificateBundle> certs; auto sslsock = std::make_unique<netplus::ssl>(certs,-1); // Opt-in peer verification (see HttpClient's ctor doc) -- default-constructed // TlsTrustPolicy has verifyPeer=false, a no-op, so callers who never pass one get the // exact pre-existing behavior. sslsock->getTls().trust_policy = _trustPolicy; // Advertise ALPN based on version preference if (_vers >= 4) { sslsock->getTls().client_alpn_protocols = Loading
src/http.h +9 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,13 @@ namespace libhttppp { // (see resetConnection()) in addition to being the initial value // setTimeout() would otherwise set afterward -- too late to affect // that first connect. HttpClient( const HttpUrl &desturl, int vers = 2, int timeoutSec = 60); // trustPolicy is opt-in (default-constructed = verify nothing, the pre-existing // behavior every caller got before this parameter existed): pass one with // verifyPeer=true to get real hostname/CA-chain/pinned-fingerprint verification of the // upstream's TLS certificate (see netplus::TlsTrustPolicy in <netplus/crypto/cert_verify.h>). // If trustPolicy.expectedHostname is left empty, it defaults to desturl's host. HttpClient( const HttpUrl &desturl, int vers = 2, int timeoutSec = 60, const netplus::TlsTrustPolicy &trustPolicy = netplus::TlsTrustPolicy()); ~HttpClient()=default; void reconnect(); void setTimeout(int timeout_sec); Loading Loading @@ -157,6 +163,8 @@ namespace libhttppp { // across reconnects to the same host. One cache per process. static netplus::TlsSessionCache& tlsSessionCache(); private: netplus::TlsTrustPolicy _trustPolicy; void _ensureConnected(); // Thin wrapper around netplus::tcp::connectTimeout() using // _recvTimeoutSec, translating its NetException into HTTPException. Loading
src/httpcompression.cpp 0 → 100644 +243 −0 Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include "httpcompression.h" #include <algorithm> #include <array> #include <cctype> #include <cstdlib> #include <set> #include <zlib.h> #include <brotli/decode.h> #include <brotli/encode.h> namespace libhttppp { namespace compression { namespace { std::string toLower(const std::string &s) { std::string out(s); std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); return out; } std::string trim(const std::string &s) { size_t start = 0, end = s.size(); while (start < end && std::isspace(static_cast<unsigned char>(s[start]))) ++start; while (end > start && std::isspace(static_cast<unsigned char>(s[end - 1]))) --end; return s.substr(start, end - start); } struct Preference { double q = 1.0; bool present = false; }; } // namespace Encoding fromToken(const std::string &contentEncoding) { std::string tok = trim(toLower(contentEncoding)); if (tok.empty() || tok == "identity") return Encoding::None; if (tok == "gzip" || tok == "x-gzip") return Encoding::Gzip; if (tok == "br") return Encoding::Brotli; return Encoding::Other; } const char *toToken(Encoding enc) { switch (enc) { case Encoding::Gzip: return "gzip"; case Encoding::Brotli: return "br"; default: return ""; } } Encoding negotiate(const std::string &acceptEncoding) { if (acceptEncoding.empty()) return Encoding::None; Preference br, gzip, star; size_t pos = 0; while (pos <= acceptEncoding.size()) { size_t comma = acceptEncoding.find(',', pos); std::string part = acceptEncoding.substr(pos, comma == std::string::npos ? std::string::npos : comma - pos); pos = (comma == std::string::npos) ? acceptEncoding.size() + 1 : comma + 1; if (part.empty()) continue; size_t semi = part.find(';'); std::string name = toLower(trim(part.substr(0, semi))); double q = 1.0; if (semi != std::string::npos) { std::string qpart = trim(part.substr(semi + 1)); size_t eq = qpart.find('='); if (eq != std::string::npos) { try { q = std::stod(trim(qpart.substr(eq + 1))); } catch (const std::exception &) { q = 1.0; } } } if (name == "br") { br = {q, true}; } else if (name == "gzip" || name == "x-gzip") { gzip = {q, true}; } else if (name == "*") { star = {q, true}; } if (comma == std::string::npos) break; } // A token not explicitly listed falls back to "*"'s preference, if present, per RFC // 7231 §5.3.4. double brQ = br.present ? br.q : (star.present ? star.q : 0.0); double gzipQ = gzip.present ? gzip.q : (star.present ? star.q : 0.0); bool brOk = brQ > 0.0; bool gzipOk = gzipQ > 0.0; if (brOk && gzipOk) return brQ >= gzipQ ? Encoding::Brotli : Encoding::Gzip; if (brOk) return Encoding::Brotli; if (gzipOk) return Encoding::Gzip; return Encoding::None; } bool isCompressible(const std::string &contentType) { if (contentType.empty()) return false; if (contentType.rfind("text/", 0) == 0) return true; static const std::set<std::string> kAlso = { "application/json", "application/javascript", "application/x-javascript", "application/xml", "application/xhtml+xml", "application/rss+xml", "application/atom+xml", "image/svg+xml", }; return kAlso.count(contentType) != 0; } bool gzipCompress(const std::vector<char> &in, std::vector<char> &out, int level) { z_stream strm{}; // windowBits 15+16 asks zlib for a gzip (not raw-deflate/zlib) wrapper. if (deflateInit2(&strm, level, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) { return false; } out.resize(deflateBound(&strm, static_cast<uLong>(in.size()))); strm.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(in.data())); strm.avail_in = static_cast<uInt>(in.size()); strm.next_out = reinterpret_cast<Bytef *>(out.data()); strm.avail_out = static_cast<uInt>(out.size()); bool ok = deflate(&strm, Z_FINISH) == Z_STREAM_END; out.resize(ok ? strm.total_out : 0); deflateEnd(&strm); return ok; } bool gzipDecompress(const std::vector<char> &in, std::vector<char> &out) { z_stream strm{}; // windowBits 15+32 auto-detects a gzip or zlib header. if (inflateInit2(&strm, 15 + 32) != Z_OK) return false; strm.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(in.data())); strm.avail_in = static_cast<uInt>(in.size()); out.clear(); std::array<char, 64 * 1024> chunk; int ret = Z_OK; do { strm.next_out = reinterpret_cast<Bytef *>(chunk.data()); strm.avail_out = static_cast<uInt>(chunk.size()); ret = inflate(&strm, Z_NO_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { inflateEnd(&strm); return false; } out.insert(out.end(), chunk.data(), chunk.data() + (chunk.size() - strm.avail_out)); } while (ret != Z_STREAM_END && strm.avail_in > 0); inflateEnd(&strm); return ret == Z_STREAM_END; } bool brotliCompress(const std::vector<char> &in, std::vector<char> &out, int quality) { size_t bound = BrotliEncoderMaxCompressedSize(in.size()); if (bound == 0) bound = 64; // MaxCompressedSize documents 0 as "input too large to bound"; // never true here, but keep resize() from truncating to empty. out.resize(bound); size_t encodedSize = out.size(); BROTLI_BOOL ok = BrotliEncoderCompress( quality, BROTLI_DEFAULT_WINDOW, BROTLI_MODE_GENERIC, in.size(), reinterpret_cast<const uint8_t *>(in.data()), &encodedSize, reinterpret_cast<uint8_t *>(out.data())); if (!ok) { out.clear(); return false; } out.resize(encodedSize); return true; } bool brotliDecompress(const std::vector<char> &in, std::vector<char> &out) { BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); if (!state) return false; out.clear(); const uint8_t *nextIn = reinterpret_cast<const uint8_t *>(in.data()); size_t availIn = in.size(); std::array<uint8_t, 64 * 1024> chunk; BrotliDecoderResult result; do { uint8_t *nextOut = chunk.data(); size_t availOut = chunk.size(); result = BrotliDecoderDecompressStream(state, &availIn, &nextIn, &availOut, &nextOut, nullptr); out.insert(out.end(), reinterpret_cast<char *>(chunk.data()), reinterpret_cast<char *>(chunk.data() + (chunk.size() - availOut))); if (result == BROTLI_DECODER_RESULT_ERROR) { BrotliDecoderDestroyInstance(state); return false; } } while (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT); bool ok = result == BROTLI_DECODER_RESULT_SUCCESS; BrotliDecoderDestroyInstance(state); return ok; } } // namespace compression } // namespace libhttppp