Loading CMakeLists.txt +1 −0 Changes for CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ find_package(smtpclient REQUIRED) find_package(libuuidp REQUIRED) find_package(tinyxml2 REQUIRED) find_package(paritypp REQUIRED) find_package(ZLIB REQUIRED) if(APPLE OR ${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD") find_package(date REQUIRED) Loading editor/CMakeLists.txt +3 −0 Changes for editor/CMakeLists.txt: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ if(NOT TARGET blogidev) find_package(smtpclient REQUIRED) find_package(Brotli REQUIRED) find_package(paritypp REQUIRED) find_package(ZLIB REQUIRED) add_definitions(-D${CMAKE_HOST_SYSTEM_NAME} -DWIN32_LEAN_AND_MEAN) endif() Loading Loading @@ -57,6 +58,7 @@ if(TARGET blogidev) blogidev json-c::json-c tinyxml2::tinyxml2 ZLIB::ZLIB ) target_sources(blogi-editor PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src/plugin.cpp Loading @@ -75,6 +77,7 @@ else() uuidp::uuidp authdb::client Brotli::Brotli ZLIB::ZLIB dl ) endif() Loading editor/src/htmlimport.cpp +50 −0 Changes for editor/src/htmlimport.cpp: 50 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -41,6 +41,8 @@ #include <netinet/in.h> #include <arpa/inet.h> #include <zlib.h> using namespace blogi::htmlimport; namespace { Loading Loading @@ -123,6 +125,50 @@ bool isSafeExternalHost(const std::string &host, std::string &error) return safe; } // Best-effort gzip decompression for fetchExternalResource: plenty of real- // world servers/CDNs return a gzip-compressed body regardless of what (or // whether) the client's Accept-Encoding says -- confirmed against a live // site that ignored an explicit "Accept-Encoding: identity" -- and // libhttppp's HttpClient doesn't decompress bodies itself or expose the // Content-Encoding header, so without this every gzipped page/stylesheet/ // script would be handed to the HTML/CSS parser as opaque binary. Detected // purely by the gzip magic (1f 8b) for that reason. A no-op (returns true, // @p data untouched) when the magic isn't present. Bounded by @p maxBytes // on the DECOMPRESSED size to defend against a decompression-bomb response // (small on the wire, huge once inflated) -- returns false, leaving @p data // untouched, on any zlib error or if that cap is hit. bool gunzipIfNeeded(std::vector<std::uint8_t> &data, size_t maxBytes) { if (data.size() < 2 || data[0] != 0x1f || data[1] != 0x8b) return true; z_stream strm{}; // 16 + MAX_WBITS: gzip-header format specifically (plain MAX_WBITS // expects a zlib-wrapped stream instead, which the gzip magic above // rules out). if (inflateInit2(&strm, 16 + MAX_WBITS) != Z_OK) return false; std::vector<std::uint8_t> out; std::vector<std::uint8_t> chunk(65536); strm.next_in = data.data(); strm.avail_in = static_cast<uInt>(data.size()); int rc; do { strm.next_out = chunk.data(); strm.avail_out = static_cast<uInt>(chunk.size()); rc = inflate(&strm, Z_NO_FLUSH); if (rc != Z_OK && rc != Z_STREAM_END) { inflateEnd(&strm); return false; } size_t produced = chunk.size() - strm.avail_out; if (out.size() + produced > maxBytes) { inflateEnd(&strm); return false; } out.insert(out.end(), chunk.begin(), chunk.begin() + produced); } while (rc != Z_STREAM_END); inflateEnd(&strm); data.swap(out); return true; } } // namespace bool blogi::htmlimport::fetchExternalResource(const std::string &url, Loading Loading @@ -184,6 +230,10 @@ bool blogi::htmlimport::fetchExternalResource(const std::string &url, } out.assign(resp.begin(), resp.end()); if (!gunzipIfNeeded(out, maxBytes)) { error = "failed to decompress gzip response"; return false; } return true; } catch (const std::exception &e) { error = e.what(); Loading editor/src/htmlimport.h +4 −0 Changes for editor/src/htmlimport.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -50,6 +50,10 @@ namespace htmlimport { * streaming more than @p maxBytes before the check runs). * @p contentTypePrefix, if non-empty, rejects responses whose * Content-Type doesn't start with it (a missing header is tolerated). * A gzip-magic-prefixed body is transparently decompressed (many real * servers/CDNs send one regardless of Accept-Encoding, and the result * is re-checked against @p maxBytes to bound decompression-bomb * responses) -- see gunzipIfNeeded in htmlimport.cpp. * Returns false and sets @p error on any failure; never throws. */ bool fetchExternalResource(const std::string &url, size_t maxBytes, Loading Loading
CMakeLists.txt +1 −0 Changes for CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ find_package(smtpclient REQUIRED) find_package(libuuidp REQUIRED) find_package(tinyxml2 REQUIRED) find_package(paritypp REQUIRED) find_package(ZLIB REQUIRED) if(APPLE OR ${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD") find_package(date REQUIRED) Loading
editor/CMakeLists.txt +3 −0 Changes for editor/CMakeLists.txt: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ if(NOT TARGET blogidev) find_package(smtpclient REQUIRED) find_package(Brotli REQUIRED) find_package(paritypp REQUIRED) find_package(ZLIB REQUIRED) add_definitions(-D${CMAKE_HOST_SYSTEM_NAME} -DWIN32_LEAN_AND_MEAN) endif() Loading Loading @@ -57,6 +58,7 @@ if(TARGET blogidev) blogidev json-c::json-c tinyxml2::tinyxml2 ZLIB::ZLIB ) target_sources(blogi-editor PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src/plugin.cpp Loading @@ -75,6 +77,7 @@ else() uuidp::uuidp authdb::client Brotli::Brotli ZLIB::ZLIB dl ) endif() Loading
editor/src/htmlimport.cpp +50 −0 Changes for editor/src/htmlimport.cpp: 50 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -41,6 +41,8 @@ #include <netinet/in.h> #include <arpa/inet.h> #include <zlib.h> using namespace blogi::htmlimport; namespace { Loading Loading @@ -123,6 +125,50 @@ bool isSafeExternalHost(const std::string &host, std::string &error) return safe; } // Best-effort gzip decompression for fetchExternalResource: plenty of real- // world servers/CDNs return a gzip-compressed body regardless of what (or // whether) the client's Accept-Encoding says -- confirmed against a live // site that ignored an explicit "Accept-Encoding: identity" -- and // libhttppp's HttpClient doesn't decompress bodies itself or expose the // Content-Encoding header, so without this every gzipped page/stylesheet/ // script would be handed to the HTML/CSS parser as opaque binary. Detected // purely by the gzip magic (1f 8b) for that reason. A no-op (returns true, // @p data untouched) when the magic isn't present. Bounded by @p maxBytes // on the DECOMPRESSED size to defend against a decompression-bomb response // (small on the wire, huge once inflated) -- returns false, leaving @p data // untouched, on any zlib error or if that cap is hit. bool gunzipIfNeeded(std::vector<std::uint8_t> &data, size_t maxBytes) { if (data.size() < 2 || data[0] != 0x1f || data[1] != 0x8b) return true; z_stream strm{}; // 16 + MAX_WBITS: gzip-header format specifically (plain MAX_WBITS // expects a zlib-wrapped stream instead, which the gzip magic above // rules out). if (inflateInit2(&strm, 16 + MAX_WBITS) != Z_OK) return false; std::vector<std::uint8_t> out; std::vector<std::uint8_t> chunk(65536); strm.next_in = data.data(); strm.avail_in = static_cast<uInt>(data.size()); int rc; do { strm.next_out = chunk.data(); strm.avail_out = static_cast<uInt>(chunk.size()); rc = inflate(&strm, Z_NO_FLUSH); if (rc != Z_OK && rc != Z_STREAM_END) { inflateEnd(&strm); return false; } size_t produced = chunk.size() - strm.avail_out; if (out.size() + produced > maxBytes) { inflateEnd(&strm); return false; } out.insert(out.end(), chunk.begin(), chunk.begin() + produced); } while (rc != Z_STREAM_END); inflateEnd(&strm); data.swap(out); return true; } } // namespace bool blogi::htmlimport::fetchExternalResource(const std::string &url, Loading Loading @@ -184,6 +230,10 @@ bool blogi::htmlimport::fetchExternalResource(const std::string &url, } out.assign(resp.begin(), resp.end()); if (!gunzipIfNeeded(out, maxBytes)) { error = "failed to decompress gzip response"; return false; } return true; } catch (const std::exception &e) { error = e.what(); Loading
editor/src/htmlimport.h +4 −0 Changes for editor/src/htmlimport.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -50,6 +50,10 @@ namespace htmlimport { * streaming more than @p maxBytes before the check runs). * @p contentTypePrefix, if non-empty, rejects responses whose * Content-Type doesn't start with it (a missing header is tolerated). * A gzip-magic-prefixed body is transparently decompressed (many real * servers/CDNs send one regardless of Accept-Encoding, and the result * is re-checked against @p maxBytes to bound decompression-bomb * responses) -- see gunzipIfNeeded in htmlimport.cpp. * Returns false and sets @p error on any failure; never throws. */ bool fetchExternalResource(const std::string &url, size_t maxBytes, Loading