Loading CMakeLists.txt +8 −11 Changes for CMakeLists.txt: 8 added lines, 11 removed lines. Original line number Diff line number Diff line Loading @@ -12,10 +12,18 @@ endif() # --- Mandated libraries (see docs/ARCHITECTURE.md) --- # libhttppp's own package config does not chain find_dependency() for its transitive deps, # so netplus/cmdplus must be resolved before it (see libhttppp/CMakeLists.txt for precedent). # Same reason ZLIB/brotli are resolved here too: libhttppp's own gzip/brotli support # (src/httpcompression.cpp, used by HttpProxyServer for response compression) links them # PUBLICly, so httppp::httppp's exported target requires these imported targets to already # exist in the consumer's scope even though proxyplus itself no longer calls zlib/brotli # directly. find_package(libnetplus REQUIRED) find_package(libcmdplus REQUIRED) find_package(libuuidp REQUIRED) find_package(libconfplus REQUIRED) find_package(ZLIB REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(BROTLI REQUIRED IMPORTED_TARGET libbrotlienc libbrotlidec) find_package(libhttppp REQUIRED) find_package(libsmtppp REQUIRED) find_package(paritypp REQUIRED) Loading @@ -24,14 +32,6 @@ find_package(json-c REQUIRED) find_package(Threads REQUIRED) # --- Response compression (gzip/brotli, src/http/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) # --- DKIM / DMARC (see docs/ARCHITECTURE.md §8) --- # Own RFC 6376 / RFC 7489 implementation on top of libnetplus's RSA/SHA256/ # base64 primitives and a small hand-rolled DNS TXT resolver -- no dependency Loading @@ -44,7 +44,6 @@ set(PROXYPLUS_CORE_SOURCES src/cluster/ClusterManager.cpp src/plugin/PluginManager.cpp src/http/HttpProxyServer.cpp src/http/HttpCompression.cpp src/smtp/SmtpProxyServer.cpp src/api/JsonApiServer.cpp src/security/DnsTxtResolver.cpp Loading @@ -70,8 +69,6 @@ target_link_libraries(proxyplus_core PUBLIC uuidp::uuidp json-c::json-c Threads::Threads ZLIB::ZLIB PkgConfig::BROTLI ${CMAKE_DL_LIBS} ) Loading admin/static/index.html +1 −1 Changes for admin/static/index.html: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ <input id="route-upstream" placeholder="Upstream (direkt), genau eine URL, z.B. http://10.0.0.5:8080"> <input id="route-upstream-pool" placeholder="oder Pool-ID fuer mehrere Ziele (statt Upstream)"> <input id="route-permission" placeholder="Berechtigung, z.B. http.proxy.use"> <label><input type="checkbox" id="route-tls-verify"> Upstream-TLS-Zertifikat pruefen</label> <label><input type="checkbox" id="route-tls-verify" checked> Upstream-TLS-Zertifikat pruefen</label> <input id="route-tls-pins" placeholder="Gepinnte SHA-256-Fingerprints, kommagetrennt (fuer selbstsignierte Upstreams)"> <button onclick="ProxyPlusAdmin.createRoute()">Anlegen</button> <div id="route-error" class="error"></div> Loading src/api/JsonApiServer.cpp +1 −1 Changes for src/api/JsonApiServer.cpp: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -227,7 +227,7 @@ void JsonApiController::registerRoutes() { rec.upstream = getStringField(body, "upstream"); rec.upstreamPool = getStringField(body, "upstream_pool"); rec.requiredPermission = getStringField(body, "required_permission"); rec.tlsVerifyUpstream = getBoolField(body, "tls_verify_upstream"); rec.tlsVerifyUpstream = getBoolField(body, "tls_verify_upstream", /*def=*/true); json_object *fpArr = nullptr; if (json_object_object_get_ex(body, "tls_pinned_fingerprints_sha256", &fpArr) && fpArr) { int n = json_object_array_length(fpArr); Loading src/http/HttpCompression.cppdeleted 100644 → 0 +0 −214 Changes for src/http/HttpCompression.cpp: 0 added lines, 214 removed lines. Original line number Diff line number Diff line #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 proxyplus::httpproxy::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 proxyplus::httpproxy::compression src/http/HttpCompression.hdeleted 100644 → 0 +0 −38 Changes for src/http/HttpCompression.h: 0 added lines, 38 removed lines. Original line number Diff line number Diff line #pragma once #include <string> #include <vector> namespace proxyplus::httpproxy::compression { // None: no encoding (or "identity") -- send as-is. // Gzip/Brotli: an encoding this proxy can itself produce and consume. // Other: some encoding present that isn't gzip or brotli (e.g. "deflate", "zstd", or a // comma-joined list) -- opaque to this module, callers must leave the body untouched. enum class Encoding { None, Gzip, Brotli, Other }; // Maps a single Content-Encoding token (case-insensitive) to Encoding. Encoding fromToken(const std::string &contentEncoding); // Content-Encoding token this proxy would send for enc ("gzip"/"br"). Only valid for // Gzip/Brotli. const char *toToken(Encoding enc); // Picks the best encoding this proxy can produce for a client's Accept-Encoding header, // honoring q-values (q=0 excludes) and "*" per RFC 7231 §5.3.4. Prefers Brotli over gzip // when both are acceptable with equal preference (better ratio at comparable CPU cost). // Returns Encoding::None if the header is empty/absent or nothing acceptable remains. Encoding negotiate(const std::string &acceptEncoding); // True if contentType (as returned by HttpResponse::getContentType() -- already lowercased, // parameters stripped) is worth spending CPU to compress. Binary/already-compressed formats // (images, video, archives) are excluded. bool isCompressible(const std::string &contentType); bool gzipCompress(const std::vector<char> &in, std::vector<char> &out, int level = 6); bool gzipDecompress(const std::vector<char> &in, std::vector<char> &out); bool brotliCompress(const std::vector<char> &in, std::vector<char> &out, int quality = 5); bool brotliDecompress(const std::vector<char> &in, std::vector<char> &out); } // namespace proxyplus::httpproxy::compression Loading
CMakeLists.txt +8 −11 Changes for CMakeLists.txt: 8 added lines, 11 removed lines. Original line number Diff line number Diff line Loading @@ -12,10 +12,18 @@ endif() # --- Mandated libraries (see docs/ARCHITECTURE.md) --- # libhttppp's own package config does not chain find_dependency() for its transitive deps, # so netplus/cmdplus must be resolved before it (see libhttppp/CMakeLists.txt for precedent). # Same reason ZLIB/brotli are resolved here too: libhttppp's own gzip/brotli support # (src/httpcompression.cpp, used by HttpProxyServer for response compression) links them # PUBLICly, so httppp::httppp's exported target requires these imported targets to already # exist in the consumer's scope even though proxyplus itself no longer calls zlib/brotli # directly. find_package(libnetplus REQUIRED) find_package(libcmdplus REQUIRED) find_package(libuuidp REQUIRED) find_package(libconfplus REQUIRED) find_package(ZLIB REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(BROTLI REQUIRED IMPORTED_TARGET libbrotlienc libbrotlidec) find_package(libhttppp REQUIRED) find_package(libsmtppp REQUIRED) find_package(paritypp REQUIRED) Loading @@ -24,14 +32,6 @@ find_package(json-c REQUIRED) find_package(Threads REQUIRED) # --- Response compression (gzip/brotli, src/http/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) # --- DKIM / DMARC (see docs/ARCHITECTURE.md §8) --- # Own RFC 6376 / RFC 7489 implementation on top of libnetplus's RSA/SHA256/ # base64 primitives and a small hand-rolled DNS TXT resolver -- no dependency Loading @@ -44,7 +44,6 @@ set(PROXYPLUS_CORE_SOURCES src/cluster/ClusterManager.cpp src/plugin/PluginManager.cpp src/http/HttpProxyServer.cpp src/http/HttpCompression.cpp src/smtp/SmtpProxyServer.cpp src/api/JsonApiServer.cpp src/security/DnsTxtResolver.cpp Loading @@ -70,8 +69,6 @@ target_link_libraries(proxyplus_core PUBLIC uuidp::uuidp json-c::json-c Threads::Threads ZLIB::ZLIB PkgConfig::BROTLI ${CMAKE_DL_LIBS} ) Loading
admin/static/index.html +1 −1 Changes for admin/static/index.html: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ <input id="route-upstream" placeholder="Upstream (direkt), genau eine URL, z.B. http://10.0.0.5:8080"> <input id="route-upstream-pool" placeholder="oder Pool-ID fuer mehrere Ziele (statt Upstream)"> <input id="route-permission" placeholder="Berechtigung, z.B. http.proxy.use"> <label><input type="checkbox" id="route-tls-verify"> Upstream-TLS-Zertifikat pruefen</label> <label><input type="checkbox" id="route-tls-verify" checked> Upstream-TLS-Zertifikat pruefen</label> <input id="route-tls-pins" placeholder="Gepinnte SHA-256-Fingerprints, kommagetrennt (fuer selbstsignierte Upstreams)"> <button onclick="ProxyPlusAdmin.createRoute()">Anlegen</button> <div id="route-error" class="error"></div> Loading
src/api/JsonApiServer.cpp +1 −1 Changes for src/api/JsonApiServer.cpp: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -227,7 +227,7 @@ void JsonApiController::registerRoutes() { rec.upstream = getStringField(body, "upstream"); rec.upstreamPool = getStringField(body, "upstream_pool"); rec.requiredPermission = getStringField(body, "required_permission"); rec.tlsVerifyUpstream = getBoolField(body, "tls_verify_upstream"); rec.tlsVerifyUpstream = getBoolField(body, "tls_verify_upstream", /*def=*/true); json_object *fpArr = nullptr; if (json_object_object_get_ex(body, "tls_pinned_fingerprints_sha256", &fpArr) && fpArr) { int n = json_object_array_length(fpArr); Loading
src/http/HttpCompression.cppdeleted 100644 → 0 +0 −214 Changes for src/http/HttpCompression.cpp: 0 added lines, 214 removed lines. Original line number Diff line number Diff line #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 proxyplus::httpproxy::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 proxyplus::httpproxy::compression
src/http/HttpCompression.hdeleted 100644 → 0 +0 −38 Changes for src/http/HttpCompression.h: 0 added lines, 38 removed lines. Original line number Diff line number Diff line #pragma once #include <string> #include <vector> namespace proxyplus::httpproxy::compression { // None: no encoding (or "identity") -- send as-is. // Gzip/Brotli: an encoding this proxy can itself produce and consume. // Other: some encoding present that isn't gzip or brotli (e.g. "deflate", "zstd", or a // comma-joined list) -- opaque to this module, callers must leave the body untouched. enum class Encoding { None, Gzip, Brotli, Other }; // Maps a single Content-Encoding token (case-insensitive) to Encoding. Encoding fromToken(const std::string &contentEncoding); // Content-Encoding token this proxy would send for enc ("gzip"/"br"). Only valid for // Gzip/Brotli. const char *toToken(Encoding enc); // Picks the best encoding this proxy can produce for a client's Accept-Encoding header, // honoring q-values (q=0 excludes) and "*" per RFC 7231 §5.3.4. Prefers Brotli over gzip // when both are acceptable with equal preference (better ratio at comparable CPU cost). // Returns Encoding::None if the header is empty/absent or nothing acceptable remains. Encoding negotiate(const std::string &acceptEncoding); // True if contentType (as returned by HttpResponse::getContentType() -- already lowercased, // parameters stripped) is worth spending CPU to compress. Binary/already-compressed formats // (images, video, archives) are excluded. bool isCompressible(const std::string &contentType); bool gzipCompress(const std::vector<char> &in, std::vector<char> &out, int level = 6); bool gzipDecompress(const std::vector<char> &in, std::vector<char> &out); bool brotliCompress(const std::vector<char> &in, std::vector<char> &out, int quality = 5); bool brotliDecompress(const std::vector<char> &in, std::vector<char> &out); } // namespace proxyplus::httpproxy::compression