mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-11-04 20:24:19 +01:00
Global: fixes issues found by AI
This commit is contained in:
@@ -197,7 +197,13 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
|
||||
.read_consumed = 0,
|
||||
.read_buffer = 0,
|
||||
};
|
||||
ioctl(binder->fd, BINDER_WRITE_READ, &release);
|
||||
// This one ioctl carries the whole tail, so its failure is not cosmetic: without
|
||||
// BC_ACQUIRE the handles handed to the caller are only weakly referenced and every
|
||||
// later transaction on them fails with BR_FAILED_REPLY, which says nothing about the
|
||||
// real cause. Reported, but only when nothing more specific is already at hand.
|
||||
if (ioctl(binder->fd, BINDER_WRITE_READ, &release) != 0 && error == nullptr) {
|
||||
error = "Releasing the binder reply failed";
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -235,3 +241,37 @@ const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t t
|
||||
*handle = reply.handles[0];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ffBinderServiceHandleRelease(FFBinderServiceHandle* service) {
|
||||
if (service->binder == nullptr || service->handle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The mirror of the acquire at the end of ffBinderTransact(): one strong and one weak reference
|
||||
// each, dropped in the same order they were taken. The kernel keeps the service node alive until
|
||||
// the last one goes, so this is what stops a short-lived fastfetch from holding one for the whole
|
||||
// of its life.
|
||||
const uint32_t commands[] = { BC_RELEASE, BC_DECREFS };
|
||||
uint8_t tail[2 * 2 * sizeof(uint32_t)];
|
||||
size_t tailSize = 0;
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
memcpy(tail + tailSize, &commands[i], sizeof(uint32_t));
|
||||
tailSize += sizeof(uint32_t);
|
||||
memcpy(tail + tailSize, &service->handle, sizeof(uint32_t));
|
||||
tailSize += sizeof(uint32_t);
|
||||
}
|
||||
|
||||
struct binder_write_read release = {
|
||||
.write_size = tailSize,
|
||||
.write_consumed = 0,
|
||||
.write_buffer = (binder_uintptr_t) (uintptr_t) tail,
|
||||
.read_size = 0,
|
||||
.read_consumed = 0,
|
||||
.read_buffer = 0,
|
||||
};
|
||||
// Not reported: this runs from a cleanup attribute, where there is no caller left to report to,
|
||||
// and the only way for it to fail is a binder that is already dead -- which every earlier call on
|
||||
// the same binder would have said already.
|
||||
ioctl(service->binder->fd, BINDER_WRITE_READ, &release);
|
||||
service->handle = 0;
|
||||
}
|
||||
|
||||
@@ -222,3 +222,16 @@ typedef struct FFBinderReply {
|
||||
|
||||
// Resolves a service name to a handle through the service manager. Returns nullptr on success.
|
||||
[[gnu::nonnull(1, 2, 4), nodiscard]] const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t transactionCode, uint32_t* handle);
|
||||
|
||||
// A handle resolved through the service manager, held together with the binder it came from so that a
|
||||
// single cleanup attribute can give it back on every path out of the function that took it. Zeroed --
|
||||
// or left at handle 0 by a lookup that has not run or did not succeed -- it is a no-op, which is what
|
||||
// makes it safe to declare above the lookup itself.
|
||||
typedef struct FFBinderServiceHandle {
|
||||
FFBinder* binder;
|
||||
uint32_t handle;
|
||||
} FFBinderServiceHandle;
|
||||
|
||||
// Returns the strong and weak references ffBinderLookupService() acquired on the handle. Usable as a
|
||||
// cleanup attribute; safe on a zeroed or already released FFBinderServiceHandle.
|
||||
[[gnu::nonnull(1)]] void ffBinderServiceHandleRelease(FFBinderServiceHandle* service);
|
||||
|
||||
+68
-22
@@ -55,10 +55,37 @@ static uint32_t dexU32(const uint8_t* p) {
|
||||
return (uint32_t) p[0] | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) | ((uint32_t) p[3] << 24);
|
||||
}
|
||||
|
||||
// LEB128, at most five bytes for the 32-bit values a dex stores this way.
|
||||
static uint32_t dexUleb128(const uint8_t** p) {
|
||||
// The dex header points at its tables with offsets that a corrupt or hostile file is free to set
|
||||
// anywhere, including past the end of the mapping. Testing `end - p` alone does not catch that: once
|
||||
// `p` is past `end` the subtraction is negative and casting it to size_t turns it into a huge value,
|
||||
// so the comparison passes and the read goes off the end. Both directions have to be one test.
|
||||
static bool dexInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, size_t size) {
|
||||
return p >= dex && p <= end && (size_t) (end - p) >= size;
|
||||
}
|
||||
|
||||
// Same, for a table of `count` elements of `elementSize` bytes. `count` is 64-bit because the
|
||||
// callers add one to an index that came out of the file, and doing that in 32 bits can wrap to zero
|
||||
// and make the check pass for an index that is not in the table. Dividing rather than multiplying is
|
||||
// what keeps a hostile `count` from wrapping the size_t on a 32-bit build -- armeabi-v7a is one, and
|
||||
// there `count * 4` overflows for any count above 0x3FFFFFFF and the check passes again.
|
||||
static bool dexTableInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, uint64_t count, size_t elementSize) {
|
||||
if (p < dex || p > end) {
|
||||
return false;
|
||||
}
|
||||
return (size_t) (end - p) / elementSize >= count;
|
||||
}
|
||||
|
||||
// LEB128, at most five bytes for the 32-bit values a dex stores this way. `end` bounds every read:
|
||||
// the lengths and indexes decoded here come out of the file, so a truncated one must not walk off
|
||||
// the end. A read that runs into `end` yields what was decoded so far, and the callers treat the
|
||||
// resulting value as untrusted -- it is either compared against another count or used as an index
|
||||
// that is range checked in turn.
|
||||
static uint32_t dexUleb128(const uint8_t** p, const uint8_t* end) {
|
||||
uint32_t value = 0;
|
||||
for (int shift = 0; shift <= 28; shift += 7) {
|
||||
if (*p >= end) {
|
||||
break;
|
||||
}
|
||||
const uint8_t byte = *(*p)++;
|
||||
value |= (uint32_t) (byte & 0x7f) << shift;
|
||||
if ((byte & 0x80) == 0) {
|
||||
@@ -71,12 +98,18 @@ static uint32_t dexUleb128(const uint8_t** p) {
|
||||
// encoded_value: one header byte holding ((size - 1) << 5) | type, then that many payload bytes. A
|
||||
// `static final int` constant is written as VALUE_INT, whose payload is a sign-extended
|
||||
// little-endian integer. Reading through 64 bits keeps every shift in range even if the size field
|
||||
// is nonsense; the caller's length check is what rejects such a dex.
|
||||
static int32_t dexEncodedInt(const uint8_t** p) {
|
||||
// is nonsense; `end` is what keeps a truncated payload from running off the end of the mapping.
|
||||
static int32_t dexEncodedInt(const uint8_t** p, const uint8_t* end) {
|
||||
if (*p >= end) {
|
||||
return 0;
|
||||
}
|
||||
const uint8_t header = *(*p)++;
|
||||
const uint32_t size = (uint32_t) (header >> 5) + 1;
|
||||
uint64_t value = 0;
|
||||
for (uint32_t i = 0; i < size && i < 8; ++i) {
|
||||
if (*p >= end) {
|
||||
break;
|
||||
}
|
||||
value |= (uint64_t) (*(*p)++) << (i * 8);
|
||||
}
|
||||
const uint32_t bits = size * 8;
|
||||
@@ -91,14 +124,17 @@ static int32_t dexEncodedInt(const uint8_t** p) {
|
||||
// bytes can be used as they are.
|
||||
static const char* dexString(const uint8_t* dex, const uint8_t* end, uint32_t index) {
|
||||
const uint8_t* ids = dex + dexU32(dex + FF_DEX_OFF_STRING_IDS);
|
||||
if (ids < dex || (size_t) (end - ids) < (size_t) index * 4 + 4) {
|
||||
// `string_ids_size` is not read: the check is that the entry `index` names lies inside the
|
||||
// mapping, which is what the read below needs, and the table is the only thing that can say it
|
||||
// without trusting a second field out of the same file.
|
||||
if (!dexTableInRange(dex, end, ids, (uint64_t) index + 1, 4)) {
|
||||
return nullptr;
|
||||
}
|
||||
const uint8_t* p = dex + dexU32(ids + (size_t) index * 4);
|
||||
if (p < dex || p >= end) {
|
||||
return nullptr;
|
||||
}
|
||||
(void) dexUleb128(&p);
|
||||
(void) dexUleb128(&p, end);
|
||||
if (p >= end) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -123,7 +159,7 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
|
||||
// The class's type index, so that the class_def_item and the field ids can be filtered by class.
|
||||
const uint8_t* types = dex + dexU32(dex + FF_DEX_OFF_TYPE_IDS);
|
||||
const uint32_t typeCount = dexU32(dex + FF_DEX_OFF_TYPE_IDS_SIZE);
|
||||
if (types < dex || (size_t) (end - types) < (size_t) typeCount * 4) {
|
||||
if (!dexTableInRange(dex, end, types, typeCount, 4)) {
|
||||
return "The dex type table is out of range";
|
||||
}
|
||||
uint32_t typeIndex = UINT32_MAX;
|
||||
@@ -140,7 +176,7 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
|
||||
|
||||
const uint8_t* classes = dex + dexU32(dex + FF_DEX_OFF_CLASS_DEFS);
|
||||
const uint32_t classCount = dexU32(dex + FF_DEX_OFF_CLASS_DEFS_SIZE);
|
||||
if (classes < dex || (size_t) (end - classes) < (size_t) classCount * FF_DEX_CLASS_DEF_SIZE) {
|
||||
if (!dexTableInRange(dex, end, classes, classCount, FF_DEX_CLASS_DEF_SIZE)) {
|
||||
return "The dex class table is out of range";
|
||||
}
|
||||
|
||||
@@ -152,15 +188,16 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
|
||||
|
||||
const uint8_t* fields = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_DATA);
|
||||
const uint8_t* values = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_STATIC_VALUES);
|
||||
if (fields < dex || fields >= end || values < dex || values >= end) {
|
||||
// One byte each, so that the first uleb128 below has something to read.
|
||||
if (!dexInRange(dex, end, fields, 1) || !dexInRange(dex, end, values, 1)) {
|
||||
return "The dex class data is out of range";
|
||||
}
|
||||
|
||||
const uint32_t staticFields = dexUleb128(&fields);
|
||||
(void) dexUleb128(&fields); // instance_fields_size
|
||||
(void) dexUleb128(&fields); // direct_methods_size
|
||||
(void) dexUleb128(&fields); // virtual_methods_size
|
||||
const uint32_t staticValues = dexUleb128(&values);
|
||||
const uint32_t staticFields = dexUleb128(&fields, end);
|
||||
(void) dexUleb128(&fields, end); // instance_fields_size
|
||||
(void) dexUleb128(&fields, end); // direct_methods_size
|
||||
(void) dexUleb128(&fields, end); // virtual_methods_size
|
||||
const uint32_t staticValues = dexUleb128(&values, end);
|
||||
if (staticFields != staticValues) {
|
||||
return "The dex static fields and values do not pair up";
|
||||
}
|
||||
@@ -168,14 +205,20 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
|
||||
const uint8_t* fieldIds = dex + dexU32(dex + FF_DEX_OFF_FIELD_IDS);
|
||||
uint32_t fieldIndex = 0;
|
||||
for (uint32_t j = 0; j < staticFields; ++j) {
|
||||
if (fields >= end) {
|
||||
// The two arrays are walked in step, so running out of either one is a truncation.
|
||||
if (fields >= end || values >= end) {
|
||||
return "The dex static field list is truncated";
|
||||
}
|
||||
fieldIndex += dexUleb128(&fields); // field_idx_diff
|
||||
(void) dexUleb128(&fields); // access_flags
|
||||
const int32_t value = dexEncodedInt(&values);
|
||||
|
||||
if ((size_t) (end - fieldIds) < ((size_t) fieldIndex + 1) * FF_DEX_FIELD_ID_SIZE) {
|
||||
const uint32_t fieldIndexDiff = dexUleb128(&fields, end); // field_idx_diff
|
||||
if (fieldIndexDiff > UINT32_MAX - fieldIndex) {
|
||||
return "The dex field index is out of range";
|
||||
}
|
||||
fieldIndex += fieldIndexDiff;
|
||||
(void) dexUleb128(&fields, end); // access_flags
|
||||
const int32_t value = dexEncodedInt(&values, end);
|
||||
|
||||
if (!dexTableInRange(dex, end, fieldIds, (uint64_t) fieldIndex + 1, FF_DEX_FIELD_ID_SIZE)) {
|
||||
return "The dex field table is out of range";
|
||||
}
|
||||
const char* name = dexString(dex, end, dexU32(fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
|
||||
@@ -335,8 +378,6 @@ const char* ffDexStaticInt(const char* jarPath, const char* classDescriptor, con
|
||||
if (data == nullptr) {
|
||||
return "No dex in the jar";
|
||||
}
|
||||
} else if (method == FF_ZIP_METHOD_STORED) {
|
||||
dataSize = uncompressedSize;
|
||||
} else if (method == FF_ZIP_METHOD_DEFLATED) {
|
||||
#ifdef FF_HAVE_ZLIB
|
||||
const char* error = inflateDex(data, dataSize, uncompressedSize, &mapping.inflated);
|
||||
@@ -348,9 +389,14 @@ const char* ffDexStaticInt(const char* jarPath, const char* classDescriptor, con
|
||||
#else
|
||||
return "The jar deflates classes.dex and fastfetch was built without zlib";
|
||||
#endif
|
||||
} else {
|
||||
} else if (method != FF_ZIP_METHOD_STORED) {
|
||||
return "classes.dex uses an unsupported compression method";
|
||||
}
|
||||
// A STORED entry needs no further work: `dataSize` already holds the `compressedSize` that
|
||||
// `findDexEntry` checked against the mapping, and for a STORED entry the payload is the file
|
||||
// itself. The header's `uncompressedSize` is deliberately not used for it -- nothing bounds that
|
||||
// value, so a corrupt one reaches past the end of the mapping, which is exactly what the dex
|
||||
// header would then be validated against.
|
||||
|
||||
mapping.data = data;
|
||||
mapping.size = dataSize;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
+10
-4
@@ -132,12 +132,18 @@ static inline ssize_t ffReadFDData(FFNativeFD fd, size_t dataSize, void* data) {
|
||||
return ffReadFDData(fd, dataSize, data);
|
||||
}
|
||||
|
||||
// `CreateFileA` reads the path in the ANSI code page, so a UTF-8 path holding anything outside ASCII
|
||||
// names a different file, or none at all. The wide API is the one that takes what UTF-8 decodes to,
|
||||
// and the conversion is done the same way ffPathExists() below does it.
|
||||
[[gnu::nonnull(1, 2)]] static inline bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer) {
|
||||
FF_AUTO_CLOSE_FD FFNativeFD fd =
|
||||
#ifndef _WIN32
|
||||
open(fileName, O_RDONLY | O_CLOEXEC);
|
||||
#ifdef _WIN32
|
||||
wchar_t fileNameW[MAX_PATH];
|
||||
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), nullptr, fileName, (ULONG) strlen(fileName) + 1))) {
|
||||
return false;
|
||||
}
|
||||
FF_AUTO_CLOSE_FD FFNativeFD fd = CreateFileW(fileNameW, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
#else
|
||||
CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
FF_AUTO_CLOSE_FD FFNativeFD fd = open(fileName, O_RDONLY | O_CLOEXEC);
|
||||
#endif
|
||||
|
||||
if (!ffIsValidNativeFD(fd)) {
|
||||
|
||||
@@ -60,7 +60,10 @@ typedef struct FFNetworkingState {
|
||||
|
||||
// Decodes a `Transfer-Encoding: chunked` body in place and rewrites the response with a
|
||||
// `Content-Length` header in place of `Transfer-Encoding`.
|
||||
[[gnu::nonnull(1), nodiscard]] bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd);
|
||||
// `headerEnd` is updated in place: the rewrite changes the header length, so the value the caller
|
||||
// passed in no longer describes the buffer afterwards. Callers must use the new one for anything
|
||||
// that addresses the body, such as ffNetworkingDecompressGzip().
|
||||
[[gnu::nonnull(1, 2), nodiscard]] bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t* headerEnd);
|
||||
|
||||
// Result of parsing a `Transfer-Encoding` header value
|
||||
typedef enum FFNetworkingTransferEncoding {
|
||||
|
||||
@@ -230,7 +230,15 @@ std::int32_t STDMETHODCALLTYPE FFWinrtHstringIterator::GetMany(std::uint32_t cap
|
||||
HSTRING* hstrings = reinterpret_cast<HSTRING*>(values);
|
||||
|
||||
for (std::uint32_t i = 0; i < taken; ++i) {
|
||||
WindowsDuplicateString(*(HSTRING*) ffListGet(&owner->strings, sizeof(HSTRING), index + i), &hstrings[i]);
|
||||
HRESULT hr = WindowsDuplicateString(*(HSTRING*) ffListGet(&owner->strings, sizeof(HSTRING), index + i), &hstrings[i]);
|
||||
if (FAILED(hr)) {
|
||||
// Whatever was handed out already belongs to the caller, so a half filled array has to
|
||||
// be taken back before the error goes out: the caller has no way to free a partial one.
|
||||
for (std::uint32_t j = 0; j < i; ++j) {
|
||||
WindowsDeleteString(hstrings[j]);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
index += taken;
|
||||
@@ -259,6 +267,9 @@ HRESULT ffWinrtCreateHstringIterable(const wchar_t* const* items, uint32_t count
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
HSTRING* slot = (HSTRING*) ffListAdd(&iterable->strings, sizeof(HSTRING));
|
||||
// ffListAdd does not zero the slot it hands out, and Release() walks every one of them, so
|
||||
// the slot has to hold a usable HSTRING before WindowsCreateString gets a chance to fail.
|
||||
*slot = nullptr;
|
||||
HRESULT hr = WindowsCreateString(items[i], (UINT32) ::wcslen(items[i]), slot);
|
||||
if (FAILED(hr)) {
|
||||
iterable->Release();
|
||||
|
||||
@@ -99,6 +99,8 @@ static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** re
|
||||
|
||||
template <typename TResultProjection, typename TOperation>
|
||||
static HRESULT ffRunAndWait(TOperation&& operation, abi_t<TResultProjection>** result) {
|
||||
*result = nullptr;
|
||||
|
||||
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Foundation::IAsyncOperation<TResultProjection>>* opResult = nullptr;
|
||||
HRESULT hr = operation(reinterpret_cast<void**>(&opResult));
|
||||
if (FAILED(hr) || !opResult) {
|
||||
|
||||
Reference in New Issue
Block a user