Skip to content

Commit 244625d

Browse files
committed
Networking (Windows): apply the same optimizations of *nix's
1 parent 840434e commit 244625d

File tree

6 files changed

+379
-211
lines changed

6 files changed

+379
-211
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ set(LIBFASTFETCH_SRC
364364
src/common/library.c
365365
src/common/modules.c
366366
src/common/netif/netif.c
367+
src/common/networking_common.c
367368
src/common/option.c
368369
src/common/parsing.c
369370
src/common/printing.c

src/common/networking.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@ typedef struct FFNetworkingState {
3131

3232
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers);
3333
const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer);
34+
35+
#ifdef FF_HAVE_ZLIB
36+
const char* ffNetworkingLoadZlibLibrary(void);
37+
bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd);
38+
#endif

src/common/networking_common.c

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#include "fastfetch.h"
2+
#include "common/library.h"
3+
#include "common/networking.h"
4+
#include "util/stringUtils.h"
5+
#include "util/debug.h"
6+
7+
#ifdef FF_HAVE_ZLIB
8+
#include <zlib.h>
9+
10+
struct FFZlibLibrary
11+
{
12+
FF_LIBRARY_SYMBOL(inflateInit2_)
13+
FF_LIBRARY_SYMBOL(inflate)
14+
FF_LIBRARY_SYMBOL(inflateEnd)
15+
FF_LIBRARY_SYMBOL(inflateGetHeader)
16+
17+
bool inited;
18+
} zlibData;
19+
20+
const char* ffNetworkingLoadZlibLibrary(void)
21+
{
22+
if (!zlibData.inited)
23+
{
24+
zlibData.inited = true;
25+
FF_LIBRARY_LOAD(zlib, "dlopen libz failed", "libz" FF_LIBRARY_EXTENSION, 2)
26+
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateInit2_)
27+
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflate)
28+
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateEnd)
29+
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateGetHeader)
30+
zlib = NULL; // don't auto dlclose
31+
}
32+
return zlibData.ffinflateEnd == NULL ? "Failed to load libz" : NULL;
33+
}
34+
35+
// Try to pre-read gzip header to determine uncompressed size
36+
static uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize)
37+
{
38+
// gzip file format: http://www.zlib.org/rfc-gzip.html
39+
if (dataSize < 10 || ((const uint8_t*)data)[0] != 0x1f || ((const uint8_t*)data)[1] != 0x8b)
40+
return 0;
41+
42+
// Uncompressed size in gzip format is stored in the last 4 bytes, but only valid if data is less than 4GB
43+
if (dataSize > 18) {
44+
// Get ISIZE value from the end of file (little endian)
45+
const uint8_t* tail = (const uint8_t*)data + dataSize - 4;
46+
uint32_t uncompressedSize = tail[0] | (tail[1] << 8) | (tail[2] << 16) | (tail[3] << 24);
47+
48+
// For valid gzip files, this value is the length of the uncompressed data modulo 2^32
49+
if (uncompressedSize > 0) {
50+
FF_DEBUG("Read uncompressed size from GZIP trailer: %u bytes", uncompressedSize);
51+
// Add some margin to the estimated size for safety
52+
return uncompressedSize + 64;
53+
}
54+
}
55+
56+
// If unable to get size from trailer or size is 0, use estimated value
57+
// Typically, text data compression ratio is between 3-5x, we use the larger value
58+
uint32_t estimatedSize = dataSize * 5;
59+
FF_DEBUG("Unable to read exact uncompressed size, estimated as 5x of compressed data: %u bytes", estimatedSize);
60+
return estimatedSize;
61+
}
62+
63+
// Decompress gzip content
64+
bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd)
65+
{
66+
// Calculate header size
67+
uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars);
68+
69+
*headerEnd = '\0'; // Replace delimiter with null character for easier processing
70+
// Ensure Content-Encoding is in response headers, not in response body
71+
bool hasGzip = strcasestr(buffer->chars, "\nContent-Encoding: gzip") != NULL;
72+
*headerEnd = '\r'; // Restore delimiter
73+
74+
if (!hasGzip) {
75+
FF_DEBUG("No gzip compressed content detected, skipping decompression");
76+
return true;
77+
}
78+
79+
FF_DEBUG("Gzip compressed content detected, preparing for decompression");
80+
81+
const char* bodyStart = headerEnd + 4; // Skip delimiter
82+
83+
// Calculate compressed content size
84+
uint32_t compressedSize = buffer->length - headerSize - 4;
85+
86+
if (compressedSize <= 0) {
87+
// No content to decompress
88+
FF_DEBUG("Compressed content size is 0, skipping decompression");
89+
return true;
90+
}
91+
92+
// Check if content is actually in gzip format (gzip header magic is 0x1f 0x8b)
93+
if (compressedSize < 2 || (uint8_t)bodyStart[0] != 0x1f || (uint8_t)bodyStart[1] != 0x8b) {
94+
FF_DEBUG("Content is not valid gzip format, skipping decompression");
95+
return false;
96+
}
97+
98+
// Predict uncompressed size
99+
uint32_t estimatedSize = guessGzipOutputSize(bodyStart, compressedSize);
100+
101+
// Create decompression buffer with estimated size
102+
FF_STRBUF_AUTO_DESTROY decompressedBuffer = ffStrbufCreateA(estimatedSize > 0 ? estimatedSize : compressedSize * 5);
103+
FF_DEBUG("Created decompression buffer: %u bytes", decompressedBuffer.allocated);
104+
105+
// Initialize decompression
106+
z_stream zs = {
107+
.zalloc = Z_NULL,
108+
.zfree = Z_NULL,
109+
.opaque = Z_NULL,
110+
.avail_in = (uInt)compressedSize,
111+
.next_in = (Bytef*)bodyStart,
112+
.avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer),
113+
.next_out = (Bytef*)decompressedBuffer.chars,
114+
};
115+
116+
// Initialize decompression engine
117+
if (zlibData.ffinflateInit2_(&zs, 16 + MAX_WBITS, ZLIB_VERSION, (int)sizeof(z_stream)) != Z_OK) {
118+
FF_DEBUG("Failed to initialize decompression engine");
119+
return false;
120+
}
121+
uInt availableOut = zs.avail_out;
122+
123+
// Perform decompression
124+
int result = zlibData.ffinflate(&zs, Z_FINISH);
125+
126+
// If output buffer is insufficient, try to extend buffer
127+
while (result == Z_BUF_ERROR || (result != Z_STREAM_END && zs.avail_out == 0))
128+
{
129+
FF_DEBUG("Output buffer insufficient, trying to extend");
130+
131+
// Save already decompressed data amount
132+
uint32_t alreadyDecompressed = (uint32_t)(availableOut - zs.avail_out);
133+
decompressedBuffer.length += alreadyDecompressed;
134+
decompressedBuffer.chars[decompressedBuffer.length] = '\0'; // Ensure null-terminated string
135+
136+
ffStrbufEnsureFree(&decompressedBuffer, decompressedBuffer.length / 2);
137+
138+
// Set output parameters to point to new buffer
139+
zs.avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer);
140+
zs.next_out = (Bytef*)(decompressedBuffer.chars + decompressedBuffer.length);
141+
availableOut = zs.avail_out;
142+
143+
// Decompress again
144+
result = zlibData.ffinflate(&zs, Z_FINISH);
145+
}
146+
147+
zlibData.ffinflateEnd(&zs);
148+
149+
// Calculate decompressed size
150+
uint32_t decompressedSize = (uint32_t)(availableOut - zs.avail_out);
151+
decompressedBuffer.length += decompressedSize;
152+
decompressedBuffer.chars[decompressedSize] = '\0'; // Ensure null-terminated string
153+
FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length);
154+
155+
// Modify Content-Length header and remove Content-Encoding header
156+
FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedSize + 64);
157+
158+
char* line = NULL;
159+
size_t len = 0;
160+
while (ffStrbufGetline(&line, &len, buffer))
161+
{
162+
if (ffStrStartsWithIgnCase(line, "Content-Encoding:"))
163+
{
164+
continue;
165+
}
166+
else if (ffStrStartsWithIgnCase(line, "Content-Length:"))
167+
{
168+
ffStrbufAppendF(&newBuffer, "Content-Length: %u\r\n", decompressedSize);
169+
continue;
170+
}
171+
else if (line[0] == '\r')
172+
{
173+
ffStrbufAppendS(&newBuffer, "\r\n");
174+
ffStrbufGetlineRestore(&line, &len, buffer);
175+
break;
176+
}
177+
178+
ffStrbufAppendS(&newBuffer, line);
179+
ffStrbufAppendC(&newBuffer, '\n');
180+
}
181+
182+
ffStrbufAppend(&newBuffer, &decompressedBuffer);
183+
ffStrbufDestroy(buffer);
184+
ffStrbufInitMove(buffer, &newBuffer);
185+
186+
return true;
187+
}
188+
#endif // FF_HAVE_ZLIB

0 commit comments

Comments
 (0)