Files
fastfetch/src/common/impl/FFstrbuf.c
T

874 lines
25 KiB
C

#include "common/FFstrbuf.h"
#include "common/mallocHelper.h"
#include "common/strutil.h"
#include "common/debug.h"
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
char* CHAR_NULL_PTR = "";
void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate) {
strbuf->allocated = allocate;
if (strbuf->allocated > 0) {
strbuf->chars = (char*) malloc(sizeof(char) * strbuf->allocated);
}
// This will set the length to zero and the null byte.
ffStrbufClear(strbuf);
}
void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) {
assert(format != NULL);
char* buffer = NULL;
int len = vasprintf(&buffer, format, arguments);
assert(len >= 0);
ffStrbufInitMoveNS(strbuf, (uint32_t) len, buffer);
}
// Takes ownership of `heapStr`. The caller must not free `heapStr` after calling this
// function; the memory will be managed and freed via the associated FFstrbuf.
void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr) {
assert(heapStr != NULL);
strbuf->length = length;
size_t allocSize = ffMallocUsableSize(heapStr);
if (allocSize == 0) {
allocSize = length + 1;
} else if (allocSize > UINT32_MAX) {
allocSize = UINT32_MAX;
}
strbuf->allocated = (uint32_t) allocSize;
strbuf->chars = heapStr;
}
void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...) {
va_list arguments;
va_start(arguments, format);
ffStrbufInitVF(strbuf, format, arguments);
va_end(arguments);
}
FFstrbuf ffStrbufCreateF(const char* format, ...) {
FFstrbuf strbuf;
va_list arguments;
va_start(arguments, format);
ffStrbufInitVF(&strbuf, format, arguments);
va_end(arguments);
return strbuf;
}
void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) {
uint32_t allocate;
if (__builtin_expect(__builtin_uadd_overflow(strbuf->length, free, &allocate), false)) {
FF_DEBUG("Error: Integer overflow when calculating allocation size. Aborting");
abort();
}
if (allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC) { // `<` for null terminator
allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC;
} else {
if (__builtin_expect(allocate > (UINT32_MAX >> 1), false)) {
// User tried to allocate more than 2GB of memory, which exceeds the maximum size supported by FFstrbuf.
// This is likely an error or an attempt to exploit the program. Abort to prevent potential issues.
FF_DEBUG("Error: Attempted to allocate %" PRIu32 " bytes more than 2GB of memory in FFstrbuf. Aborting", allocate);
abort();
}
// Round up to the next power of 2.
// If the value is already a power of 2, it will be rounded up to the next power of 2.
allocate = 1U << (32 - __builtin_clz(allocate));
}
if (strbuf->allocated == 0) {
char* newbuf = malloc(sizeof(*strbuf->chars) * allocate);
if (strbuf->length == 0) {
*newbuf = '\0';
} else {
memcpy(newbuf, strbuf->chars, strbuf->length + 1);
}
strbuf->chars = newbuf;
} else {
strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * allocate);
}
strbuf->allocated = allocate;
}
// Ensure that at least `free` bytes are available in the buffer besides the current length
// for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL)
// This function ensures a dynamic buffer is allocated even if free == 0
void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) {
uint32_t newCap;
if (strbuf->allocated == 0) {
assert(strbuf->length < UINT32_MAX - 1); // We don't use static strings with length >= UINT32_MAX - 1, so this should never happen
if (__builtin_expect(__builtin_uadd_overflow(strbuf->length + 1, free, &newCap), false)) {
FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting");
abort();
}
char* newbuf = malloc(sizeof(*strbuf->chars) * newCap);
if (strbuf->length == 0) {
*newbuf = '\0';
} else {
memcpy(newbuf, strbuf->chars, strbuf->length + 1);
}
strbuf->chars = newbuf;
} else {
uint32_t oldFree = ffStrbufGetFree(strbuf);
if (oldFree >= free) {
return;
}
if (__builtin_expect(__builtin_uadd_overflow(strbuf->allocated, free - oldFree, &newCap), false)) {
FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting");
abort();
}
strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * newCap);
}
strbuf->allocated = newCap;
}
void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int)) {
if (value == NULL) {
return;
}
// Ensure capacity > 0 or the modification below will fail
uint32_t length = (uint32_t) strlen(value);
if (length == 0) {
return;
}
ffStrbufEnsureFree(strbuf, length);
for (uint32_t i = 0; value[i] != '\0'; i++) {
strbuf->chars[strbuf->length++] = (char) transformFunc(value[i]);
}
strbuf->chars[strbuf->length] = '\0';
}
void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments) {
assert(format != NULL);
va_list copy;
va_copy(copy, arguments);
uint32_t free = ffStrbufGetFree(strbuf);
int written = vsnprintf(strbuf->chars + strbuf->length, strbuf->allocated > 0 ? free + 1 : 0, format, arguments);
if (written > 0 && (uint32_t) written > free) {
ffStrbufEnsureFreeNoCheck(strbuf, (uint32_t) written);
written = vsnprintf(strbuf->chars + strbuf->length, (uint32_t) written + 1, format, copy);
}
va_end(copy);
if (written > 0) {
strbuf->length += (uint32_t) written;
}
}
const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until) {
if (value == NULL) {
return NULL;
}
const char* end = strchr(value, until);
if (end == NULL) {
ffStrbufAppendS(strbuf, value);
} else {
ffStrbufAppendNS(strbuf, (uint32_t) (end - value), value);
}
return end;
}
void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) {
assert(format != NULL);
va_list arguments;
va_start(arguments, format);
if (strbuf->allocated == 0) {
ffStrbufInitVF(strbuf, format, arguments);
va_end(arguments);
return;
}
ffStrbufClear(strbuf);
ffStrbufAppendVF(strbuf, format, arguments);
va_end(arguments);
}
void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...) {
assert(format != NULL);
va_list arguments;
va_start(arguments, format);
ffStrbufAppendVF(strbuf, format, arguments);
va_end(arguments);
}
void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
if (value == NULL || length == 0) {
return;
}
ffStrbufEnsureFree(strbuf, length);
memmove(strbuf->chars + length, strbuf->chars, strbuf->length + 1); // + 1 for the null byte
memcpy(strbuf->chars, value, length);
strbuf->length += length;
}
void ffStrbufPrependC(FFstrbuf* strbuf, char c) {
ffStrbufEnsureFree(strbuf, 1);
memmove(strbuf->chars + 1, strbuf->chars, strbuf->length + 1); // + 1 for the null byte
strbuf->chars[0] = c;
strbuf->length += 1;
}
void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
assert(strbuf != NULL);
if (length == 0) {
ffStrbufClear(strbuf);
return;
}
assert(value != NULL);
if (strbuf->allocated <= length) {
char* newBuf = malloc(sizeof(char) * (length + 1));
memcpy(newBuf, value, length);
if (strbuf->allocated > 0) {
free(strbuf->chars);
}
strbuf->chars = newBuf;
strbuf->allocated = length + 1;
} else {
memmove(strbuf->chars, value, length);
}
strbuf->length = length;
strbuf->chars[length] = '\0';
}
void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value) {
assert(value && value != strbuf);
if (value->length == 0) {
ffStrbufClear(strbuf);
return;
}
if (value->allocated == 0) // static string
{
if (strbuf->allocated != 0) {
free(strbuf->chars);
strbuf->allocated = 0;
}
strbuf->chars = value->chars;
strbuf->length = value->length;
return;
}
ffStrbufSetNS(strbuf, value->length, value->chars);
}
void ffStrbufTrimLeft(FFstrbuf* strbuf, char c) {
if (strbuf->length == 0) {
return;
}
uint32_t index = 0;
while (index < strbuf->length && strbuf->chars[index] == c) {
++index;
}
if (index == 0) {
return;
}
if (strbuf->allocated == 0) {
// static string
strbuf->length -= index;
strbuf->chars += index;
return;
}
memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index);
strbuf->length -= index;
strbuf->chars[strbuf->length] = '\0';
}
void ffStrbufTrimRight(FFstrbuf* strbuf, char c) {
if (strbuf->length == 0) {
return;
}
if (!ffStrbufEndsWithC(strbuf, c)) {
return;
}
do {
--strbuf->length;
} while (ffStrbufEndsWithC(strbuf, c));
if (strbuf->allocated == 0) {
// static string
ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);
return;
}
strbuf->chars[strbuf->length] = '\0';
}
void ffStrbufTrimLeftSpace(FFstrbuf* strbuf) {
if (strbuf->length == 0) {
return;
}
uint32_t index = 0;
while (index < strbuf->length && isspace(strbuf->chars[index])) {
++index;
}
if (index == 0) {
return;
}
if (strbuf->allocated == 0) {
// static string
strbuf->length -= index;
strbuf->chars += index;
return;
}
memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index);
strbuf->length -= index;
strbuf->chars[strbuf->length] = '\0';
}
void ffStrbufTrimRightSpace(FFstrbuf* strbuf) {
if (strbuf->length == 0) {
return;
}
if (!ffStrbufEndsWithFn(strbuf, isspace)) {
return;
}
do {
--strbuf->length;
} while (ffStrbufEndsWithFn(strbuf, isspace));
if (strbuf->allocated == 0) {
// static string
ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars);
return;
}
strbuf->chars[strbuf->length] = '\0';
}
bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex) {
if (startIndex > strbuf->length || startIndex >= endIndex) {
return false;
}
if (endIndex > strbuf->length) {
ffStrbufSubstrBefore(strbuf, startIndex);
return true;
}
ffStrbufEnsureFree(strbuf, 0);
memmove(strbuf->chars + startIndex, strbuf->chars + endIndex, strbuf->length - endIndex);
strbuf->length -= (endIndex - startIndex);
strbuf->chars[strbuf->length] = '\0';
return true;
}
void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str) {
uint32_t stringLength = (uint32_t) strlen(str);
for (uint32_t i = ffStrbufNextIndexS(strbuf, 0, str); i < strbuf->length; i = ffStrbufNextIndexS(strbuf, i, str)) {
ffStrbufRemoveSubstr(strbuf, i, i + stringLength);
}
}
void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]) {
for (uint32_t i = 0; i < numStrings; i++) {
ffStrbufRemoveS(strbuf, strings[i]);
}
}
void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace) {
if (strbuf->length == 0) {
return;
}
ffStrbufEnsureFree(strbuf, 0);
for (
char* current_pos = memchr(strbuf->chars, find, strbuf->length);
current_pos;
current_pos = memchr(
current_pos + 1,
find,
strbuf->length - (uint32_t) (current_pos + 1 - strbuf->chars))) {
*current_pos = replace;
}
}
bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index) {
if (strbuf->length <= index) {
return false;
}
if (strbuf->allocated == 0) {
// static string
if (index < strbuf->length) {
ffStrbufInitNS(strbuf, index, strbuf->chars);
}
return true;
}
strbuf->length = index;
strbuf->chars[strbuf->length] = '\0';
return true;
}
bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index) {
if (index >= strbuf->length) {
ffStrbufClear(strbuf);
return true;
}
if (strbuf->allocated == 0) {
// static string
strbuf->length -= index + 1;
strbuf->chars += index + 1;
return true;
}
memmove(strbuf->chars, strbuf->chars + index + 1, strbuf->length - index - 1);
strbuf->length -= (index + 1);
strbuf->chars[strbuf->length] = '\0';
return true;
}
bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c) {
uint32_t index = ffStrbufFirstIndexC(strbuf, c);
if (index >= strbuf->length) {
return false;
}
ffStrbufSubstrAfter(strbuf, index);
return true;
}
bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str) {
if (*str == '\0') {
return false;
}
uint32_t index = ffStrbufFirstIndexS(strbuf, str) + (uint32_t) strlen(str) - 1; // -1, because firstIndexS is already pointing to str[0], we want to add only the remaining length
if (index >= strbuf->length) {
return false;
}
ffStrbufSubstrAfter(strbuf, index);
return true;
}
bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c) {
uint32_t index = ffStrbufLastIndexC(strbuf, c);
if (index >= strbuf->length) {
return false;
}
ffStrbufSubstrAfter(strbuf, index);
return true;
}
bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end) {
if (__builtin_expect(start >= end, false)) {
ffStrbufClear(strbuf);
return false;
}
if (__builtin_expect(start == 0, false)) {
return ffStrbufSubstrBefore(strbuf, end);
}
if (__builtin_expect(end >= strbuf->length, false)) {
return ffStrbufSubstrAfter(strbuf, start - 1);
}
uint32_t len = end - start;
ffStrbufEnsureFixedLengthFree(strbuf, len); // In case of static string
memmove(strbuf->chars, strbuf->chars + start, len);
strbuf->length = len;
strbuf->chars[len] = '\0';
return true;
}
uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c) {
uint32_t result = 0;
for (uint32_t i = 0; i < strbuf->length; i++) {
if (strbuf->chars[i] == c) {
result++;
}
}
return result;
}
bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end) {
uint32_t endLength = (uint32_t) strlen(end);
if (ffStrbufEndsWithIgnCaseNS(strbuf, endLength, end)) {
ffStrbufSubstrBefore(strbuf, strbuf->length - endLength);
return true;
}
return false;
}
bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c) {
if (ffStrbufEndsWithC(strbuf, c)) {
return false;
}
ffStrbufAppendC(strbuf, c);
return true;
}
void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value) {
ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number
char* start = strbuf->chars + strbuf->length;
yyjson_val val = {};
unsafe_yyjson_set_sint(&val, value);
char* end = yyjson_write_number(&val, start);
assert(end != NULL);
strbuf->length += (uint32_t) (end - start);
}
void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value) {
ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number
char* start = strbuf->chars + strbuf->length;
yyjson_val val = {};
unsafe_yyjson_set_uint(&val, value);
char* end = yyjson_write_number(&val, start);
assert(end != NULL);
strbuf->length += (uint32_t) (end - start);
}
void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros) {
assert(precision <= 15); // yyjson_write_number supports up to 15 digits after the decimal point
ffStrbufEnsureFree(strbuf, 40); // Required by yyjson_write_number
char* start = strbuf->chars + strbuf->length;
if (precision == 0) {
value = round(value);
}
yyjson_val val = {};
unsafe_yyjson_set_double(&val, value);
if (precision > 0) {
unsafe_yyjson_set_fp_to_fixed(&val, precision);
}
// Write at most <precision> digits after the decimal point; doesn't append trailing zeros
char* end = yyjson_write_number(&val, start);
assert(end > start);
strbuf->length += (uint32_t) (end - start);
if (__builtin_expect(value > 1e21 || value < -1e21, false)) {
// If the value is too large, yyjson_write_number will write it in scientific notation
return;
}
if (trailingZeros) {
if (precision > 1) {
for (char* p = end - 1; *p != '.' && p > start; --p) {
--precision;
}
if (precision > 0) {
ffStrbufAppendNC(strbuf, (uint32_t) precision, '0');
}
} else if (precision == 0 || (precision < 0 && end[-1] == '0')) {
goto removeDecimalPoint;
}
} else {
if (end[-1] == '0') {
removeDecimalPoint:
// yyjson always appends ".0" to make it a float point number. We need to remove it
strbuf->length -= 2;
strbuf->chars[strbuf->length] = '\0';
}
}
}
void ffStrbufUpperCase(FFstrbuf* strbuf) {
for (uint32_t i = 0; i < strbuf->length; ++i) {
strbuf->chars[i] = (char) toupper(strbuf->chars[i]);
}
}
void ffStrbufLowerCase(FFstrbuf* strbuf) {
for (uint32_t i = 0; i < strbuf->length; ++i) {
strbuf->chars[i] = (char) tolower(strbuf->chars[i]);
}
}
void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c) {
if (num == 0) {
return;
}
if (index >= strbuf->length) {
index = strbuf->length;
}
ffStrbufEnsureFree(strbuf, num);
memmove(strbuf->chars + index + num, strbuf->chars + index, strbuf->length - index + 1);
memset(&strbuf->chars[index], c, num);
strbuf->length += num;
}
bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) {
assert(lineptr && n && buffer);
assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));
assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));
const char* pBufferEnd = buffer->chars + buffer->length;
if (!*lineptr) {
*lineptr = buffer->chars;
} else {
*lineptr += *n;
if (*lineptr >= pBufferEnd) { // non-empty last line
return false;
}
**lineptr = delimiter;
++*lineptr;
}
if (*lineptr >= pBufferEnd) { // empty last line
return false;
}
size_t remaining = (size_t) (pBufferEnd - *lineptr);
char* ending = memchr(*lineptr, delimiter, remaining);
if (ending) {
*n = (size_t) (ending - *lineptr);
*ending = '\0';
} else {
*n = remaining;
}
return true;
}
void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) {
assert(buffer && lineptr && n);
assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));
assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));
if (!*lineptr) {
return;
}
*lineptr += *n;
if (*lineptr < buffer->chars + buffer->length) {
**lineptr = delimiter;
}
}
bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf) {
if (strbuf->allocated == 0) {
return false; // Doesn't work with static strings
}
bool changed = false;
for (uint32_t i = 0; i < strbuf->length; i++) {
if (strbuf->chars[i] != ' ') {
continue;
}
i++;
uint32_t j = i;
for (; j < strbuf->length && strbuf->chars[j] == ' '; j++);
if (j == i) {
continue;
}
memmove(&strbuf->chars[i], &strbuf->chars[j], strbuf->length - j + 1);
strbuf->length -= j - i;
changed = true;
}
return changed;
}
/// @brief Check if a separated string (comp) contains a substring (strbuf).
/// @param strbuf The substring to check.
/// @param compLength The length of the separated string to check.
/// @param comp The separated string to check.
/// @param separator The separator character.
bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
if (strbuf->length == 0) {
return true;
}
if (compLength == 0) {
return false;
}
for (const char* p = comp; p < comp + compLength;) {
const char* colon = memchr(p, separator, (size_t) (comp + compLength - p));
if (colon == NULL) {
uint32_t remainingLen = (uint32_t) (comp + compLength - p);
return strbuf->length == remainingLen && memcmp(strbuf->chars, p, remainingLen) == 0;
}
uint32_t substrLength = (uint32_t) (colon - p);
if (strbuf->length == substrLength && memcmp(strbuf->chars, p, substrLength) == 0) {
return true;
}
p = colon + 1;
}
return false;
}
/// @brief Case insensitive version of ffStrbufMatchSeparatedNS.
bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
if (strbuf->length == 0) {
return true;
}
if (compLength == 0) {
return false;
}
for (const char* p = comp; p < comp + compLength;) {
const char* colon = memchr(p, separator, (size_t) (comp + compLength - p));
if (colon == NULL) {
uint32_t remainingLen = (uint32_t) (comp + compLength - p);
return strbuf->length == remainingLen && strncasecmp(strbuf->chars, p, remainingLen) == 0;
}
uint32_t substrLength = (uint32_t) (colon - p);
if (strbuf->length == substrLength && strncasecmp(strbuf->chars, p, substrLength) == 0) {
return true;
}
p = colon + 1;
}
return false;
}
int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint) {
if (codepoint <= 0x7F) {
ffStrbufAppendC(strbuf, (char) codepoint);
return 1;
} else if (codepoint <= 0x7FF) {
ffStrbufAppendNS(strbuf, 2, (char[]) { (char) (0xC0 | (codepoint >> 6)), (char) (0x80 | (codepoint & 0x3F)) });
return 2;
} else if (codepoint <= 0xFFFF) {
ffStrbufAppendNS(strbuf, 3, (char[]) { (char) (0xE0 | (codepoint >> 12)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) });
return 3;
} else if (codepoint <= 0x10FFFF) {
ffStrbufAppendNS(strbuf, 4, (char[]) { (char) (0xF0 | (codepoint >> 18)), (char) (0x80 | ((codepoint >> 12) & 0x3F)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) });
return 4;
}
ffStrbufAppendS(strbuf, ""); // U+FFFD REPLACEMENT CHARACTER
return 1;
}
/// @brief Check if a separated string (strbuf) contains a substring (comp).
/// @param strbuf The separated to check.
/// @param compLength The length of the separated string to check.
/// @param comp The substring to check.
/// @param separator The separator character.
bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
uint32_t startIndex = 0;
while (startIndex < strbuf->length) {
uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);
uint32_t folderLength = colonIndex - startIndex;
if (folderLength == compLength && memcmp(strbuf->chars + startIndex, comp, compLength) == 0) {
return true;
}
startIndex = colonIndex + 1;
}
return false;
}
bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
uint32_t startIndex = 0;
while (startIndex < strbuf->length) {
uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);
uint32_t folderLength = colonIndex - startIndex;
if (folderLength == compLength && strncasecmp(strbuf->chars + startIndex, comp, compLength) == 0) {
return true;
}
startIndex = colonIndex + 1;
}
return false;
}
bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf) {
assert(strbuf);
if (strbuf->length < 4) {
return false;
}
// Static string must be converted first.
assert(strbuf->allocated > 0);
bool changed = false;
uint32_t read = 0;
uint32_t write = 0;
while (read < strbuf->length) {
if (
read + 3 < strbuf->length &&
strbuf->chars[read] == '\\' &&
strbuf->chars[read + 1] == 'x') {
int8_t hi = ffHexCharToInt(strbuf->chars[read + 2]);
int8_t lo = ffHexCharToInt(strbuf->chars[read + 3]);
if (hi >= 0 && lo >= 0) {
strbuf->chars[write++] = (char) ((hi << 4) | lo);
read += 4;
changed = true;
continue;
}
}
strbuf->chars[write++] = strbuf->chars[read++];
}
strbuf->length = write;
strbuf->chars[write] = '\0';
return changed;
}