mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-27 23:30:47 +01:00
Networking: removes compression support
This commit is contained in:
@@ -4078,7 +4078,7 @@
|
|||||||
"const": "publicip"
|
"const": "publicip"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"description": "URL of the public IP detection server to use. Only HTTP is supported\nIf empty, `http://ipinfo.io/json` is requested (`http://v6.ipinfo.io/json` when `ipv6` is set), with HTTP compression and TCP Fast Open enabled. A non-empty URL disables both and makes `ipv6` have no effect",
|
"description": "URL of the public IP detection server to use. Only HTTP is supported\nIf empty, `http://ipinfo.io/json` is requested (`http://v6.ipinfo.io/json` when `ipv6` is set), with TCP Fast Open enabled. A non-empty URL disables both and makes `ipv6` have no effect",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "url"
|
"format": "url"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,197 +1,7 @@
|
|||||||
#include "fastfetch.h"
|
|
||||||
#include "common/library.h"
|
|
||||||
#include "common/networking.h"
|
#include "common/networking.h"
|
||||||
#include "common/strutil.h"
|
#include "common/strutil.h"
|
||||||
#include "common/debug.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 = nullptr; // don't auto dlclose
|
|
||||||
}
|
|
||||||
return zlibData.ffinflateEnd == nullptr ? "Failed to load libz" : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
// `headerEnd` itself is non-null per the `nonnull(2)` contract; what it points to is not expressible as an attribute
|
|
||||||
assert(*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") != nullptr;
|
|
||||||
*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 = nullptr;
|
|
||||||
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
|
|
||||||
|
|
||||||
const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen) {
|
const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen) {
|
||||||
uint32_t nameLen = (uint32_t) strlen(name);
|
uint32_t nameLen = (uint32_t) strlen(name);
|
||||||
uint32_t pos = 0;
|
uint32_t pos = 0;
|
||||||
|
|||||||
@@ -214,12 +214,6 @@ static const char* initNetworkingState(FFNetworkingState* state, const char* hos
|
|||||||
}
|
}
|
||||||
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly tell the server we don't need to keep the connection
|
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly tell the server we don't need to keep the connection
|
||||||
|
|
||||||
// If compression needs to be enabled
|
|
||||||
if (state->compression) {
|
|
||||||
FF_DEBUG("Enabling HTTP content compression");
|
|
||||||
ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
ffStrbufAppendS(&state->command, headers);
|
ffStrbufAppendS(&state->command, headers);
|
||||||
ffStrbufAppendS(&state->command, "\r\n");
|
ffStrbufAppendS(&state->command, "\r\n");
|
||||||
|
|
||||||
@@ -331,26 +325,6 @@ error:
|
|||||||
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
|
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
|
||||||
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
|
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
|
||||||
|
|
||||||
if (state->compression) {
|
|
||||||
FF_DEBUG("Compression enabled, checking if zlib is available");
|
|
||||||
|
|
||||||
#ifdef FF_HAVE_ZLIB
|
|
||||||
const char* zlibError = ffNetworkingLoadZlibLibrary();
|
|
||||||
// Only enable compression if zlib library is successfully loaded
|
|
||||||
if (zlibError == nullptr) {
|
|
||||||
FF_DEBUG("Successfully loaded zlib library, compression enabled");
|
|
||||||
} else {
|
|
||||||
FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
|
|
||||||
state->compression = false;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
FF_DEBUG("zlib not supported at build time, compression disabled");
|
|
||||||
state->compression = false;
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
FF_DEBUG("Compression disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* initResult = initNetworkingState(state, host, port, path, headers);
|
const char* initResult = initNetworkingState(state, host, port, path, headers);
|
||||||
if (initResult != nullptr) {
|
if (initResult != nullptr) {
|
||||||
FF_DEBUG("Initialization failed: %s", initResult);
|
FF_DEBUG("Initialization failed: %s", initResult);
|
||||||
@@ -612,18 +586,5 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
|||||||
return "Content length mismatch";
|
return "Content length mismatch";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If compression was used, try to decompress
|
|
||||||
#ifdef FF_HAVE_ZLIB
|
|
||||||
if (state->compression) {
|
|
||||||
FF_DEBUG("Content received, checking if compressed");
|
|
||||||
if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {
|
|
||||||
FF_DEBUG("Decompression failed or invalid compression format");
|
|
||||||
return "Failed to decompress or invalid format";
|
|
||||||
} else {
|
|
||||||
FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,24 +52,6 @@ static const char* initWsaData(WSADATA* wsaData) {
|
|||||||
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
|
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
|
||||||
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
|
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
|
||||||
|
|
||||||
if (state->compression) {
|
|
||||||
#ifdef FF_HAVE_ZLIB
|
|
||||||
const char* zlibError = ffNetworkingLoadZlibLibrary();
|
|
||||||
// Only enable compression if zlib library is successfully loaded
|
|
||||||
if (zlibError == nullptr) {
|
|
||||||
FF_DEBUG("Successfully loaded zlib library, compression enabled");
|
|
||||||
} else {
|
|
||||||
FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
|
|
||||||
state->compression = false;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
FF_DEBUG("zlib not supported at build time, compression disabled");
|
|
||||||
state->compression = false;
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
FF_DEBUG("Compression disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
static WSADATA wsaData;
|
static WSADATA wsaData;
|
||||||
if (wsaData.wVersion == 0) {
|
if (wsaData.wVersion == 0) {
|
||||||
const char* error = initWsaData(&wsaData);
|
const char* error = initWsaData(&wsaData);
|
||||||
@@ -180,13 +162,6 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
|
|||||||
ffStrbufAppendF(&state->command, ":%u", port);
|
ffStrbufAppendF(&state->command, ":%u", port);
|
||||||
}
|
}
|
||||||
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly request connection closure
|
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly request connection closure
|
||||||
|
|
||||||
// Add compression support if enabled
|
|
||||||
if (state->compression) {
|
|
||||||
FF_DEBUG("Enabling HTTP content compression");
|
|
||||||
ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
ffStrbufAppendS(&state->command, headers);
|
ffStrbufAppendS(&state->command, headers);
|
||||||
ffStrbufAppendS(&state->command, "\r\n");
|
ffStrbufAppendS(&state->command, "\r\n");
|
||||||
|
|
||||||
@@ -441,18 +416,5 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
|||||||
return "Content length mismatch";
|
return "Content length mismatch";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If compression was used, try to decompress
|
|
||||||
#ifdef FF_HAVE_ZLIB
|
|
||||||
if (state->compression) {
|
|
||||||
FF_DEBUG("Content received, checking if compressed");
|
|
||||||
if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {
|
|
||||||
FF_DEBUG("Decompression failed or invalid compression format");
|
|
||||||
return "Failed to decompress or invalid format";
|
|
||||||
} else {
|
|
||||||
FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ typedef struct FFNetworkingState {
|
|||||||
FFstrbuf command;
|
FFstrbuf command;
|
||||||
uint32_t timeout;
|
uint32_t timeout;
|
||||||
bool ipv6;
|
bool ipv6;
|
||||||
bool compression; // if true, HTTP content compression will be enabled if supported
|
|
||||||
bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails
|
bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails
|
||||||
} FFNetworkingState;
|
} FFNetworkingState;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ void ffPreparePublicIp(FFPublicIPOptions* options) {
|
|||||||
state->ipv6 = options->ipv6;
|
state->ipv6 = options->ipv6;
|
||||||
|
|
||||||
if (options->url.length == 0) {
|
if (options->url.length == 0) {
|
||||||
state->compression = true;
|
|
||||||
state->tfo = true;
|
state->tfo = true;
|
||||||
*status = ffNetworkingSendHttpRequest(state, options->ipv6 ? "v6.ipinfo.io" : "ipinfo.io", 80, "/json", nullptr);
|
*status = ffNetworkingSendHttpRequest(state, options->ipv6 ? "v6.ipinfo.io" : "ipinfo.io", 80, "/json", nullptr);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user