mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 15:53:51 +01:00
Global: fixes issues found by AI
This commit is contained in:
@@ -8,6 +8,20 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// TODO: long paths on Windows.
|
||||
//
|
||||
// Every path that crosses into the wide API from this file is converted into a fixed
|
||||
// `wchar_t[MAX_PATH]` buffer, and `RtlUTF8ToUnicodeN` refuses to fill it for a path of MAX_PATH
|
||||
// characters or more (STATUS_BUFFER_TOO_SMALL) -- which makes each of those call sites a silent
|
||||
// failure above that length rather than an error anyone sees. Raising a buffer on its own does not
|
||||
// help: the wide APIs accept a longer path only either with a `\\?\` prefix on every absolute path
|
||||
// (which also switches off the normalization the rest of the code relies on) or with a
|
||||
// `longPathAware` manifest plus the LongPathsEnabled registry value.
|
||||
//
|
||||
// The plan is the second route: define MAX_PATH as 32768 program-wide, add the manifest flag, and
|
||||
// size these buffers from the UTF-8 input rather than from the constant. It has to be done in one
|
||||
// pass, which is why it is recorded here instead of being applied at the call sites the review
|
||||
// happened to look at.
|
||||
static bool createSubfolders(wchar_t* fileName) {
|
||||
HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle;
|
||||
bool closeRoot = false;
|
||||
@@ -512,7 +526,7 @@ bool ffIsTerminal(int fd) {
|
||||
}
|
||||
|
||||
bool ffRemoveFile(const char* fileName) {
|
||||
wchar_t fileNameW[MAX_PATH];
|
||||
wchar_t fileNameW[MAX_PATH]; // see the long path TODO at the top of this file
|
||||
ULONG len;
|
||||
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG) strlen(fileName) + 1))) {
|
||||
return false;
|
||||
@@ -524,7 +538,7 @@ bool ffRemoveFile(const char* fileName) {
|
||||
}
|
||||
|
||||
uint64_t ffPathGetMtime(const char* path) {
|
||||
wchar_t fileNameW[MAX_PATH];
|
||||
wchar_t fileNameW[MAX_PATH]; // see the long path TODO at the top of this file
|
||||
ULONG len;
|
||||
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, path, (ULONG) strlen(path) + 1))) {
|
||||
return 0;
|
||||
|
||||
@@ -149,7 +149,10 @@ static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_
|
||||
|
||||
static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
|
||||
switch (type[0]) {
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
// Both `cpuusage` and `command` start with a c, and they are disabled by separate options,
|
||||
// so the block has to survive either one being off -- otherwise a build with only the other
|
||||
// one enabled would never prepare this module (see the same guard in `commandoption.c`).
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE || !FF_MODULE_DISABLE_COMMAND
|
||||
case 'c':
|
||||
case 'C': {
|
||||
if (ffStrEqualsIgnCase(type, ffCPUUsageModuleInfo.name)) {
|
||||
@@ -292,6 +295,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
|
||||
}
|
||||
|
||||
yyjson_val* module = item;
|
||||
bool gatedOnSucceeded = false;
|
||||
const char* type = yyjson_get_str(module);
|
||||
if (type) {
|
||||
module = nullptr;
|
||||
@@ -327,6 +331,8 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
|
||||
if (!unsafe_yyjson_is_bool(previousSucceeded)) {
|
||||
return "Property 'succeeded' in 'condition' must be a boolean";
|
||||
}
|
||||
gatedOnSucceeded = true;
|
||||
|
||||
if (prepare) {
|
||||
// Whether this module is printed at all depends on the result of the previous
|
||||
// one, which is only known in the print pass. Preparing it here would leave a
|
||||
@@ -337,6 +343,26 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
|
||||
// to be printed after all.
|
||||
continue;
|
||||
}
|
||||
|
||||
#if !FF_MODULE_DISABLE_COMMAND
|
||||
// A `command` module that runs in parallel is exactly the pairing above, so this
|
||||
// combination cannot be left to the print pass: the module would be handed the
|
||||
// result prepared for the next `command` module and print that instead of its
|
||||
// own output. Refused here rather than in the prepare pass, because returning
|
||||
// from there would leave every later module unprepared -- which the print pass
|
||||
// would then report as a failure of theirs.
|
||||
// `parallel` is on by default, so leaving the key out is the parallel case too.
|
||||
if (gatedOnSucceeded && ffStrEqualsIgnCase(yyjson_get_str(yyjson_obj_get(module, "type")), ffCommandModuleInfo.name)) {
|
||||
yyjson_val* parallel = yyjson_obj_get(module, "parallel");
|
||||
if (parallel == nullptr || yyjson_get_bool(parallel)) {
|
||||
return "Module \"command\" cannot be combined with \"condition.succeeded\" while it runs in "
|
||||
"parallel: whether it is printed is only known in the print pass, so it cannot be "
|
||||
"prepared, and the prepared results are matched to modules by position. Set "
|
||||
"\"parallel\": false on it, or drop the condition.";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (succeeded != unsafe_yyjson_get_bool(previousSucceeded)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -325,7 +325,12 @@ int ffNetworkingChunkedComplete(const char* body, uint32_t bodyLen, uint32_t* co
|
||||
// Keeps the status line and every header except `dropHeader` and `Content-Length`,
|
||||
// then emits a `Content-Length` matching the (already decoded) body.
|
||||
// `body` may point into `buffer->chars`; it is copied before `buffer` is released.
|
||||
static void rebuildResponse(FFstrbuf* buffer, uint32_t headerEnd, const char* body, uint32_t bodyLen, const char* dropHeader) {
|
||||
//
|
||||
// Returns the offset of the `\r` that opens the terminating CRLF CRLF of the new response. The
|
||||
// rebuild drops a header line and rewrites another, so the header it produces is shorter than the
|
||||
// one that went in: the offset the caller held before is no longer the one that describes this
|
||||
// buffer, and handing it on would point into the middle of the body.
|
||||
static uint32_t rebuildResponse(FFstrbuf* buffer, uint32_t headerEnd, const char* body, uint32_t bodyLen, const char* dropHeader) {
|
||||
FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerEnd + bodyLen + 64);
|
||||
|
||||
uint32_t pos = 0;
|
||||
@@ -349,18 +354,24 @@ static void rebuildResponse(FFstrbuf* buffer, uint32_t headerEnd, const char* bo
|
||||
|
||||
ffStrbufDestroy(buffer);
|
||||
ffStrbufInitMove(buffer, &newBuffer);
|
||||
|
||||
// The terminating CRLF CRLF is the four bytes in front of the body
|
||||
return buffer->length - bodyLen - 4;
|
||||
}
|
||||
|
||||
bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd) {
|
||||
bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t* headerEnd) {
|
||||
assert(buffer->allocated > 0);
|
||||
|
||||
// Held in a local because the rebuild below hands a different value back
|
||||
const uint32_t headerLength = *headerEnd;
|
||||
|
||||
// The header block is terminated by CR LF CR LF
|
||||
if (headerEnd + 4 > buffer->length) {
|
||||
if (headerLength + 4 > buffer->length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char* body = buffer->chars + headerEnd + 4;
|
||||
uint32_t bodyLen = buffer->length - headerEnd - 4;
|
||||
char* body = buffer->chars + headerLength + 4;
|
||||
uint32_t bodyLen = buffer->length - headerLength - 4;
|
||||
|
||||
uint32_t consumed = 0;
|
||||
if (ffNetworkingChunkedComplete(body, bodyLen, &consumed) != 1) {
|
||||
@@ -391,6 +402,6 @@ bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd) {
|
||||
}
|
||||
|
||||
FF_DEBUG("Decoded chunked body: %u bytes encoded, %u bytes decoded", bodyLen, outLen);
|
||||
rebuildResponse(buffer, headerEnd, out, outLen, "Transfer-Encoding:");
|
||||
*headerEnd = rebuildResponse(buffer, headerLength, out, outLen, "Transfer-Encoding:");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -466,7 +466,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
||||
return "Response too large";
|
||||
}
|
||||
FF_DEBUG("Receive buffer is full, extending it");
|
||||
ffStrbufEnsureFreeNoCheck(buffer, buffer->allocated);
|
||||
// Asking for exactly the room that is left under the cap, rather than for as much as
|
||||
// the buffer holds again: the allocation is rounded up to a power of two, so a doubling
|
||||
// from a size that is not one of those lands past the cap the check above just cleared.
|
||||
ffStrbufEnsureFreeNoCheck(buffer, FF_NETWORKING_MAX_RESPONSE_SIZE - buffer->length - 1);
|
||||
}
|
||||
|
||||
// When the remaining length is known, ask for exactly that much. MSG_WAITALL then
|
||||
@@ -589,7 +592,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
||||
return "No HTTP header end found";
|
||||
}
|
||||
|
||||
if (chunked && !ffNetworkingDecodeChunked(buffer, headerEnd)) {
|
||||
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
|
||||
return "Failed to decode chunked response";
|
||||
}
|
||||
|
||||
@@ -599,7 +602,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
||||
}
|
||||
FF_DEBUG("Received valid HTTP 200 response, content %u bytes, total %u bytes", contentLength, buffer->length);
|
||||
|
||||
if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
|
||||
// A chunked response was framed by its last-chunk and has been rewritten around it, so any
|
||||
// `Content-Length` the server also sent describes a body that no longer exists. RFC 9112 6.3
|
||||
// says not to send both; when one does, the chunked framing is the one that was acted on.
|
||||
if (!chunked && contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
|
||||
FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4);
|
||||
return "Content length mismatch";
|
||||
}
|
||||
|
||||
@@ -298,7 +298,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
||||
return "Response too large";
|
||||
}
|
||||
FF_DEBUG("Receive buffer is full, extending it");
|
||||
ffStrbufEnsureFreeNoCheck(buffer, buffer->allocated);
|
||||
// Asking for exactly the room that is left under the cap, rather than for as much as
|
||||
// the buffer holds again: the allocation is rounded up to a power of two, so a doubling
|
||||
// from a size that is not one of those lands past the cap the check above just cleared.
|
||||
ffStrbufEnsureFreeNoCheck(buffer, FF_NETWORKING_MAX_RESPONSE_SIZE - buffer->length - 1);
|
||||
}
|
||||
|
||||
FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %u",
|
||||
@@ -413,7 +416,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
||||
return "No HTTP header end found";
|
||||
}
|
||||
|
||||
if (chunked && !ffNetworkingDecodeChunked(buffer, headerEnd)) {
|
||||
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
|
||||
return "Failed to decode chunked response";
|
||||
}
|
||||
|
||||
@@ -425,7 +428,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
|
||||
contentLength,
|
||||
buffer->length);
|
||||
|
||||
if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
|
||||
// A chunked response was framed by its last-chunk and has been rewritten around it, so any
|
||||
// `Content-Length` the server also sent describes a body that no longer exists. RFC 9112 6.3
|
||||
// says not to send both; when one does, the chunked framing is the one that was acted on.
|
||||
if (!chunked && contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
|
||||
FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4);
|
||||
return "Content length mismatch";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user