diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/common/impl/networking_common.c | |
Add the files
Diffstat (limited to 'src/common/impl/networking_common.c')
| -rw-r--r-- | src/common/impl/networking_common.c | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/src/common/impl/networking_common.c b/src/common/impl/networking_common.c new file mode 100644 index 0000000..e63979b --- /dev/null +++ b/src/common/impl/networking_common.c @@ -0,0 +1,192 @@ +#include "fastfetch.h" +#include "common/library.h" +#include "common/networking.h" +#include "common/strutil.h" +#include "common/debug.h" + +#ifdef FF_HAVE_ZLIB + #include <zlib.h> + +struct FFZlibLibrary { + FF_LIBRARY_SYMBOL(inflateInit2_) + FF_LIBRARY_SYMBOL(inflate) + FF_LIBRARY_SYMBOL(inflateEnd) + + bool inited; +} zlibData; + +const char* ffNetworkingLoadZlibLibrary(void) { + if (!zlibData.inited) { + zlibData.inited = true; + FF_LIBRARY_LOAD_MESSAGE(zlib, + #ifdef _WIN32 + "zlib1" + #else + "libz" + #endif + FF_LIBRARY_EXTENSION, + 2) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateInit2_) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflate) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateEnd) + zlib = NULL; // don't auto dlclose + } + return zlibData.ffinflateEnd == NULL ? "Failed to load libz" : NULL; +} + +// Try to pre-read gzip header to determine uncompressed size +static uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize) { + // gzip file format: http://www.zlib.org/rfc-gzip.html + if (dataSize < 10 || ((const uint8_t*) data)[0] != 0x1f || ((const uint8_t*) data)[1] != 0x8b) { + return 0; + } + + // Uncompressed size in gzip format is stored in the last 4 bytes, but only valid if data is less than 4GB + if (dataSize > 18) { + // Get ISIZE value from the end of file (little endian) + const uint8_t* tail = (const uint8_t*) data + dataSize - 4; + uint32_t uncompressedSize = (uint32_t) tail[0] | ((uint32_t) tail[1] << 8u) | ((uint32_t) tail[2] << 16u) | ((uint32_t) tail[3] << 24u); + + // For valid gzip files, this value is the length of the uncompressed data modulo 2^32 + if (uncompressedSize > 0) { + FF_DEBUG("Read uncompressed size from GZIP trailer: %u bytes", uncompressedSize); + // Add some margin to the estimated size for safety + return uncompressedSize + 64; + } + } + + // If unable to get size from trailer or size is 0, use estimated value + // Typically, text data compression ratio is between 3-5x, we use the larger value + uint32_t estimatedSize = dataSize * 5; + FF_DEBUG("Unable to read exact uncompressed size, estimated as 5x of compressed data: %u bytes", estimatedSize); + return estimatedSize; +} + +// Decompress gzip content +bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) { + assert(headerEnd != NULL && *headerEnd == '\r'); + + // Calculate header size + uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars); + + *headerEnd = '\0'; // Replace delimiter with null character for easier processing + // Ensure Content-Encoding is in response headers, not in response body + bool hasGzip = strcasestr(buffer->chars, "\nContent-Encoding: gzip") != NULL; + *headerEnd = '\r'; // Restore delimiter + + if (!hasGzip) { + FF_DEBUG("No gzip compressed content detected, skipping decompression"); + return true; + } + + FF_DEBUG("Gzip compressed content detected, preparing for decompression"); + + const char* bodyStart = headerEnd + 4; // Skip delimiter + + if (buffer->length <= headerSize + 4) { + // No content to decompress + FF_DEBUG("Compressed content size is 0, skipping decompression"); + return true; + } + + // Calculate compressed content size + uint32_t compressedSize = buffer->length - headerSize - 4; + + // Check if content is actually in gzip format (gzip header magic is 0x1f 0x8b) + if (compressedSize < 2 || (uint8_t) bodyStart[0] != 0x1f || (uint8_t) bodyStart[1] != 0x8b) { + FF_DEBUG("Content is not valid gzip format, skipping decompression"); + return false; + } + + // Predict uncompressed size + uint32_t estimatedSize = guessGzipOutputSize(bodyStart, compressedSize); + + // Create decompression buffer with estimated size + FF_STRBUF_AUTO_DESTROY decompressedBuffer = ffStrbufCreateA(estimatedSize > 0 ? estimatedSize : compressedSize * 5); + FF_DEBUG("Created decompression buffer: %u bytes", decompressedBuffer.allocated); + + // Initialize decompression + z_stream zs = { + .zalloc = Z_NULL, + .zfree = Z_NULL, + .opaque = Z_NULL, + .avail_in = (uInt) compressedSize, + .next_in = (Bytef*) bodyStart, + .avail_out = (uInt) ffStrbufGetFree(&decompressedBuffer), + .next_out = (Bytef*) decompressedBuffer.chars, + }; + + // Initialize decompression engine + if (zlibData.ffinflateInit2_(&zs, 16 + MAX_WBITS, ZLIB_VERSION, (int) sizeof(z_stream)) != Z_OK) { + FF_DEBUG("Failed to initialize decompression engine"); + return false; + } + uInt availableOut = zs.avail_out; + + // Perform decompression + int result = zlibData.ffinflate(&zs, Z_FINISH); + + // If output buffer is insufficient, try to extend buffer + while (result == Z_BUF_ERROR || (result != Z_STREAM_END && zs.avail_out == 0)) { + FF_DEBUG("Output buffer insufficient, trying to extend"); + + // Save already decompressed data amount + uint32_t alreadyDecompressed = (uint32_t) (availableOut - zs.avail_out); + decompressedBuffer.length += alreadyDecompressed; + decompressedBuffer.chars[decompressedBuffer.length] = '\0'; + + ffStrbufEnsureFree(&decompressedBuffer, decompressedBuffer.length / 2); + + // Set output parameters to point to new buffer + zs.avail_out = (uInt) ffStrbufGetFree(&decompressedBuffer); + zs.next_out = (Bytef*) (decompressedBuffer.chars + decompressedBuffer.length); + availableOut = zs.avail_out; + + // Decompress again + result = zlibData.ffinflate(&zs, Z_FINISH); + } + + // Check for decompression errors before using result + if (result != Z_STREAM_END) { + FF_DEBUG("Decompression failed with zlib error: %d", result); + zlibData.ffinflateEnd(&zs); + return false; + } + + zlibData.ffinflateEnd(&zs); + + // Calculate decompressed size (from the last inflate call) + uint32_t decompressedSize = (uint32_t) (availableOut - zs.avail_out); + decompressedBuffer.length += decompressedSize; + decompressedBuffer.chars[decompressedBuffer.length] = '\0'; + FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length); + + // Modify Content-Length header and remove Content-Encoding header + // Use decompressedBuffer.length (total) not decompressedSize (last chunk only) + FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedBuffer.length + 64); + + char* line = NULL; + size_t len = 0; + while (ffStrbufGetline(&line, &len, buffer)) { + if (ffStrStartsWithIgnCase(line, "Content-Encoding:")) { + continue; + } else if (ffStrStartsWithIgnCase(line, "Content-Length:")) { + ffStrbufAppendF(&newBuffer, "Content-Length: %u\r\n", decompressedBuffer.length); + continue; + } else if (line[0] == '\r') { + ffStrbufAppendS(&newBuffer, "\r\n"); + ffStrbufGetlineRestore(&line, &len, buffer); + break; + } + + ffStrbufAppendS(&newBuffer, line); // Including the trailing \r + ffStrbufAppendC(&newBuffer, '\n'); + } + + ffStrbufAppend(&newBuffer, &decompressedBuffer); + ffStrbufDestroy(buffer); + ffStrbufInitMove(buffer, &newBuffer); + + return true; +} +#endif // FF_HAVE_ZLIB |