Networking (Windows): apply the same optimizations of *nix's

This commit is contained in:
李通洲
2025-03-15 23:35:58 +08:00
parent 840434e709
commit 244625d26a
6 changed files with 381 additions and 213 deletions
+1
View File
@@ -364,6 +364,7 @@ set(LIBFASTFETCH_SRC
src/common/library.c
src/common/modules.c
src/common/netif/netif.c
src/common/networking_common.c
src/common/option.c
src/common/parsing.c
src/common/printing.c
+5
View File
@@ -31,3 +31,8 @@ typedef struct FFNetworkingState {
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers);
const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer);
#ifdef FF_HAVE_ZLIB
const char* ffNetworkingLoadZlibLibrary(void);
bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd);
#endif
+188
View File
@@ -0,0 +1,188 @@
#include "fastfetch.h"
#include "common/library.h"
#include "common/networking.h"
#include "util/stringUtils.h"
#include "util/debug.h"
#ifdef FF_HAVE_ZLIB
#include <zlib.h>
struct FFZlibLibrary
{
FF_LIBRARY_SYMBOL(inflateInit2_)
FF_LIBRARY_SYMBOL(inflate)
FF_LIBRARY_SYMBOL(inflateEnd)
FF_LIBRARY_SYMBOL(inflateGetHeader)
bool inited;
} zlibData;
const char* ffNetworkingLoadZlibLibrary(void)
{
if (!zlibData.inited)
{
zlibData.inited = true;
FF_LIBRARY_LOAD(zlib, "dlopen libz failed", "libz" 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)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateGetHeader)
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 = tail[0] | (tail[1] << 8) | (tail[2] << 16) | (tail[3] << 24);
// 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)
{
// 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
// Calculate compressed content size
uint32_t compressedSize = buffer->length - headerSize - 4;
if (compressedSize <= 0) {
// No content to decompress
FF_DEBUG("Compressed content size is 0, skipping decompression");
return true;
}
// 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'; // Ensure null-terminated string
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);
}
zlibData.ffinflateEnd(&zs);
// Calculate decompressed size
uint32_t decompressedSize = (uint32_t)(availableOut - zs.avail_out);
decompressedBuffer.length += decompressedSize;
decompressedBuffer.chars[decompressedSize] = '\0'; // Ensure null-terminated string
FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length);
// Modify Content-Length header and remove Content-Encoding header
FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedSize + 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", decompressedSize);
continue;
}
else if (line[0] == '\r')
{
ffStrbufAppendS(&newBuffer, "\r\n");
ffStrbufGetlineRestore(&line, &len, buffer);
break;
}
ffStrbufAppendS(&newBuffer, line);
ffStrbufAppendC(&newBuffer, '\n');
}
ffStrbufAppend(&newBuffer, &decompressedBuffer);
ffStrbufDestroy(buffer);
ffStrbufInitMove(buffer, &newBuffer);
return true;
}
#endif // FF_HAVE_ZLIB
+2 -185
View File
@@ -235,189 +235,6 @@ error:
return ret;
}
#ifdef FF_HAVE_ZLIB
#include <zlib.h>
struct FFZlibLibrary
{
FF_LIBRARY_SYMBOL(inflateInit2_)
FF_LIBRARY_SYMBOL(inflate)
FF_LIBRARY_SYMBOL(inflateEnd)
FF_LIBRARY_SYMBOL(inflateGetHeader)
bool inited;
} zlibData;
static const char* loadZlibLibrary(void)
{
if (!zlibData.inited)
{
zlibData.inited = true;
FF_LIBRARY_LOAD(zlib, "dlopen libz failed", "libz" 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)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateGetHeader)
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 = tail[0] | (tail[1] << 8) | (tail[2] << 16) | (tail[3] << 24);
// 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
static bool decompressGzip(FFstrbuf* buffer, char* headerEnd)
{
// 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
// Calculate compressed content size
uint32_t compressedSize = buffer->length - headerSize - 4;
if (compressedSize <= 0) {
// No content to decompress
FF_DEBUG("Compressed content size is 0, skipping decompression");
return true;
}
// 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'; // Ensure null-terminated string
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);
}
zlibData.ffinflateEnd(&zs);
// Calculate decompressed size
uint32_t decompressedSize = (uint32_t)(availableOut - zs.avail_out);
decompressedBuffer.length += decompressedSize;
decompressedBuffer.chars[decompressedSize] = '\0'; // Ensure null-terminated string
FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length);
// Modify Content-Length header and remove Content-Encoding header
FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedSize + 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", decompressedSize);
continue;
}
else if (line[0] == '\r')
{
ffStrbufAppendS(&newBuffer, "\r\n");
ffStrbufGetlineRestore(&line, &len, buffer);
break;
}
ffStrbufAppendS(&newBuffer, line);
ffStrbufAppendC(&newBuffer, '\n');
}
ffStrbufAppend(&newBuffer, &decompressedBuffer);
ffStrbufDestroy(buffer);
ffStrbufInitMove(buffer, &newBuffer);
return true;
}
#endif
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers)
{
FF_DEBUG("Preparing to send HTTP request: host=%s, path=%s", host, path);
@@ -426,7 +243,7 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
state->compression = false;
#ifdef FF_HAVE_ZLIB
const char* zlibError = loadZlibLibrary();
const char* zlibError = ffNetworkingLoadZlibLibrary();
// Only enable compression if zlib library is successfully loaded
if (zlibError == NULL)
{
@@ -584,7 +401,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
#ifdef FF_HAVE_ZLIB
if (state->compression) {
FF_DEBUG("Content received, checking if compressed");
if (!decompressGzip(buffer, headerEnd)) {
if (!ffNetworkingDecompressGzip(buffer, headerEnd)) {
FF_DEBUG("Decompression failed or invalid compression format");
return "Failed to decompress or invalid format";
} else {
+180 -21
View File
@@ -4,37 +4,68 @@
//Must be included after <mswsock.h>
#include "fastfetch.h"
#include "common/networking.h"
#include "util/stringUtils.h"
#include "util/debug.h"
static LPFN_CONNECTEX ConnectEx;
static const char* initWsaData(WSADATA* wsaData)
{
if(WSAStartup(MAKEWORD(2, 2), wsaData) != 0)
FF_DEBUG("Initializing WinSock");
if(WSAStartup(MAKEWORD(2, 2), wsaData) != 0) {
FF_DEBUG("WSAStartup() failed");
return "WSAStartup() failed";
}
if(LOBYTE(wsaData->wVersion) != 2 || HIBYTE(wsaData->wVersion) != 2)
if(LOBYTE(wsaData->wVersion) != 2 || HIBYTE(wsaData->wVersion) != 2) {
FF_DEBUG("Invalid wsaData version found: %d.%d", LOBYTE(wsaData->wVersion), HIBYTE(wsaData->wVersion));
return "Invalid wsaData version found";
}
//Dummy socket needed for WSAIoctl
SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == INVALID_SOCKET)
if(sockfd == INVALID_SOCKET) {
FF_DEBUG("socket(AF_INET, SOCK_STREAM) failed");
return "socket(AF_INET, SOCK_STREAM) failed";
}
DWORD dwBytes;
GUID guid = WSAID_CONNECTEX;
if(WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER,
&guid, sizeof(guid),
&ConnectEx, sizeof(ConnectEx),
&dwBytes, NULL, NULL) != 0)
&dwBytes, NULL, NULL) != 0) {
FF_DEBUG("WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed");
return "WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed";
}
closesocket(sockfd);
FF_DEBUG("WinSock initialized successfully");
return NULL;
}
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers)
{
FF_DEBUG("Preparing to send HTTP request: host=%s, path=%s", host, path);
// Initialize with compression disabled by default
state->compression = false;
#ifdef FF_HAVE_ZLIB
const char* zlibError = ffNetworkingLoadZlibLibrary();
// Only enable compression if zlib library is successfully loaded
if (zlibError == NULL)
{
state->compression = true;
FF_DEBUG("Successfully loaded zlib library, compression enabled");
} else {
FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
}
#else
FF_DEBUG("zlib not supported at build time, compression disabled");
#endif
static WSADATA wsaData;
if (wsaData.wVersion == 0)
{
@@ -42,27 +73,54 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
if (error != NULL)
{
wsaData.wVersion = (WORD) -1;
FF_DEBUG("WinSock initialization failed: %s", error);
return error;
}
}
else if (wsaData.wVersion == (WORD) -1)
{
FF_DEBUG("WinSock initialization previously failed");
return "initWsaData() failed before";
}
struct addrinfo* addr;
if(getaddrinfo(host, "80", &(struct addrinfo) {
struct addrinfo hints = {
.ai_family = state->ipv6 ? AF_INET6 : AF_INET,
.ai_socktype = SOCK_STREAM,
}, &addr) != 0)
.ai_flags = AI_NUMERICSERV,
};
FF_DEBUG("Resolving address: %s (%s)", host, state->ipv6 ? "IPv6" : "IPv4");
if(getaddrinfo(host, "80", &hints, &addr) != 0)
{
FF_DEBUG("getaddrinfo() failed");
return "getaddrinfo() failed";
}
state->sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if(state->sockfd == INVALID_SOCKET)
{
FF_DEBUG("socket() failed");
freeaddrinfo(addr);
return "socket() failed";
}
#ifdef TCP_NODELAY
// Enable TCP_NODELAY to disable Nagle's algorithm
DWORD flag = 1;
if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)) != 0) {
FF_DEBUG("Failed to set TCP_NODELAY: %d", WSAGetLastError());
} else {
FF_DEBUG("Successfully disabled Nagle's algorithm");
}
#endif
// Set timeout if needed
if (state->timeout > 0) {
FF_DEBUG("Setting connection timeout: %u ms", state->timeout);
setsockopt(state->sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&state->timeout, sizeof(state->timeout));
}
//ConnectEx requires the socket to be initially bound
if((state->ipv6
? bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in6) {
@@ -74,44 +132,69 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
.sin_addr.s_addr = INADDR_ANY,
}, sizeof(struct sockaddr_in))) != 0)
{
FF_DEBUG("bind() failed: %d", WSAGetLastError());
closesocket(state->sockfd);
freeaddrinfo(addr);
state->sockfd = INVALID_SOCKET;
return "bind() failed";
}
// Initialize overlapped structure for asynchronous I/O
memset(&state->overlapped, 0, sizeof(OVERLAPPED));
// Build HTTP command
FF_STRBUF_AUTO_DESTROY command = ffStrbufCreateA(64);
ffStrbufAppendS(&command, "GET ");
ffStrbufAppendS(&command, path);
ffStrbufAppendS(&command, " HTTP/1.1\nHost: ");
ffStrbufAppendS(&command, host);
ffStrbufAppendS(&command, "\r\n");
ffStrbufAppendS(&command, "Connection: close\r\n"); // Explicitly request connection closure
// Add compression support if enabled
if (state->compression) {
FF_DEBUG("Enabling HTTP content compression");
ffStrbufAppendS(&command, "Accept-Encoding: gzip\r\n");
}
ffStrbufAppendS(&command, headers);
ffStrbufAppendS(&command, "\r\n");
BOOL result = ConnectEx(state->sockfd, addr->ai_addr, (int)addr->ai_addrlen, command.chars, command.length, NULL, &state->overlapped);
FF_DEBUG("Using ConnectEx to send %u bytes of data", command.length);
BOOL result = ConnectEx(state->sockfd, addr->ai_addr, (int)addr->ai_addrlen,
command.chars, command.length, NULL, &state->overlapped);
freeaddrinfo(addr);
if(!result && WSAGetLastError() != WSA_IO_PENDING)
{
FF_DEBUG("ConnectEx() failed: %d", WSAGetLastError());
closesocket(state->sockfd);
state->sockfd = INVALID_SOCKET;
return "ConnectEx() failed";
}
// No need to cleanup state fields here since we need them in the receive function
return NULL;
}
const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer)
{
FF_DEBUG("Preparing to receive HTTP response");
if (state->sockfd == INVALID_SOCKET)
{
FF_DEBUG("Invalid socket, HTTP request might have failed");
return "ffNetworkingSendHttpRequest() failed";
}
uint32_t timeout = state->timeout;
if (timeout > 0)
{
if (WaitForSingleObject((HANDLE) state->sockfd, timeout) != WAIT_OBJECT_0)
FF_DEBUG("WaitForSingleObject with timeout: %u ms", timeout);
if (WaitForSingleObject((HANDLE)state->sockfd, timeout) != WAIT_OBJECT_0)
{
FF_DEBUG("WaitForSingleObject failed or timed out");
CancelIo((HANDLE) state->sockfd);
closesocket(state->sockfd);
return "WaitForSingleObject(state->sockfd) failed or timeout";
@@ -121,26 +204,102 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
DWORD transfer, flags;
if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags))
{
FF_DEBUG("WSAGetOverlappedResult failed: %d", WSAGetLastError());
closesocket(state->sockfd);
return "WSAGetOverlappedResult() failed";
}
if(timeout > 0)
{
//https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-setsockopt
setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &timeout, sizeof(timeout));
FF_DEBUG("Setting receive timeout: %u ms", timeout);
setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
}
uint32_t recvStart;
do {
recvStart = buffer->length;
ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, (int) ffStrbufGetFree(buffer), 0);
if (received <= 0) break;
buffer->length = recvStart + (uint32_t) received;
buffer->chars[buffer->length] = '\0';
} while (ffStrbufGetFree(buffer) > 0 && strstr(buffer->chars + recvStart, "\r\n\r\n") == NULL);
// Set larger receive buffer for better performance
int rcvbuf = 65536; // 64KB
setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&rcvbuf, sizeof(rcvbuf));
FF_DEBUG("Starting data reception");
FF_MAYBE_UNUSED int recvCount = 0;
uint32_t contentLength = 0;
char* headerEnd = NULL;
do {
size_t availSpace = ffStrbufGetFree(buffer);
FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %zu",
++recvCount, buffer->length, availSpace);
ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, (int)ffStrbufGetFree(buffer), 0);
if (received <= 0) {
if (received == 0) {
FF_DEBUG("Connection closed (received=0)");
} else {
FF_DEBUG("Reception failed: %d", WSAGetLastError());
}
break;
}
buffer->length += (uint32_t) received;
buffer->chars[buffer->length] = '\0';
FF_DEBUG("Successfully received %zd bytes of data, total: %u bytes", received, buffer->length);
// Check if HTTP header end marker is found
if (headerEnd == NULL) {
headerEnd = strstr(buffer->chars, "\r\n\r\n");
if (headerEnd != NULL) {
FF_DEBUG("Found HTTP header end marker, position: %ld", (long)(headerEnd - buffer->chars));
// Check for Content-Length header to pre-allocate enough memory
const char* clHeader = strcasestr(buffer->chars, "Content-Length:");
if (clHeader) {
contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10);
if (contentLength > 0) {
FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength);
// Ensure buffer is large enough, adding header size and some margin
ffStrbufEnsureFree(buffer, contentLength + 16);
FF_DEBUG("Extended receive buffer to %u bytes", buffer->allocated);
}
}
}
}
} while (ffStrbufGetFree(buffer) > 0);
FF_DEBUG("Closing socket: fd=%u", (unsigned)state->sockfd);
closesocket(state->sockfd);
if (buffer->length == 0) return "Empty server response received";
return ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n") ? NULL : "Invalid response";
state->sockfd = INVALID_SOCKET;
if (buffer->length == 0) {
FF_DEBUG("Server response is empty");
return "Empty server response received";
}
if (headerEnd == NULL) {
FF_DEBUG("No HTTP header end marker found");
return "No HTTP header end found";
}
if (ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n")) {
FF_DEBUG("Received valid HTTP 200 response, content length: %u bytes, total length: %u bytes",
contentLength, buffer->length);
} else {
FF_DEBUG("Invalid response: %.40s...", buffer->chars);
return "Invalid response";
}
// If compression was used, try to decompress
#ifdef FF_HAVE_ZLIB
if (state->compression) {
FF_DEBUG("Content received, checking if compressed");
if (!ffNetworkingDecompressGzip(buffer, 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 NULL;
}
+5 -7
View File
@@ -2,13 +2,11 @@
static inline const char* ffFindFileName(const char* file)
{
const char* lastSlash = __builtin_strrchr(file,
#ifdef _WIN32
'\\'
#else
'/'
#endif
);
const char* lastSlash = __builtin_strrchr(file, '/');
#ifdef _WIN32
if (lastSlash == NULL)
lastSlash = __builtin_strrchr(file, '\\');
#endif
if (lastSlash != NULL)
return lastSlash + 1;
return file;