diff --git a/code/logic/cstring.c b/code/logic/cstring.c index 5ead8c9..40902bf 100644 --- a/code/logic/cstring.c +++ b/code/logic/cstring.c @@ -13,10 +13,30 @@ */ #include "fossil/io/cstring.h" #include "fossil/io/output.h" -#include +#include // For strlen, strnlen, strncasecmp +#include // For strncasecmp on POSIX #include +#include // For toupper, tolower #include +#ifndef HAVE_STRNLEN +size_t strnlen(const char *s, size_t maxlen) { + size_t i; + for (i = 0; i < maxlen && s[i]; i++); + return i; +} +#endif + +#ifndef HAVE_STRNCASECMP +int strncasecmp(const char *s1, const char *s2, size_t n) { + for (size_t i = 0; i < n && s1[i] && s2[i]; i++) { + int diff = tolower((unsigned char)s1[i]) - tolower((unsigned char)s2[i]); + if (diff != 0) return diff; + } + return 0; +} +#endif + // ============================================================================ // C String Functions // ============================================================================ @@ -890,56 +910,515 @@ cstring fossil_io_cstring_append(cstring *dest, ccstring src) { } // ============================================================================ -// String Stream Functions +// Secure String Functions // ============================================================================ -fossil_io_cstring_stream* fossil_io_cstring_stream_create(size_t initial_size) { - if (initial_size == 0) return NULL; - fossil_io_cstring_stream *stream = (fossil_io_cstring_stream*)malloc(sizeof(fossil_io_cstring_stream)); - if (stream) { - stream->buffer = (char*)malloc(initial_size); - if (stream->buffer) { - stream->buffer[0] = '\0'; - stream->length = 0; - stream->capacity = initial_size; +cstring fossil_io_cstring_create_safe(const char *init, size_t max_len) { + if (!init) return NULL; + size_t len = strnlen(init, max_len); + cstring result = fossil_io_cstring_create(init); + if (!result) return NULL; + result[len] = '\0'; // enforce null-termination within max_len + return result; +} + +void fossil_io_cstring_free_safe(cstring *str) { + if (str && *str) { + fossil_io_cstring_free(*str); + *str = NULL; + } +} + +cstring fossil_io_cstring_copy_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + size_t len = strnlen(str, max_len); + cstring copy = fossil_io_cstring_copy(str); + if (!copy) return NULL; + copy[len] = '\0'; + return copy; +} + +cstring fossil_io_cstring_dup_safe(ccstring str, size_t max_len) { + return fossil_io_cstring_copy_safe(str, max_len); +} + +cstring fossil_io_cstring_concat_safe(ccstring s1, ccstring s2, size_t max_len) { + if (!s1 || !s2) return NULL; + size_t len1 = strnlen(s1, max_len); + size_t len2 = strnlen(s2, max_len - len1); + cstring result = fossil_io_cstring_create(s1); + if (!result) return NULL; + fossil_io_cstring_append(&result, s2); + result[len1 + len2] = '\0'; + return result; +} + +size_t fossil_io_cstring_length_safe(ccstring str, size_t max_len) { + if (!str) return 0; + return strnlen(str, max_len); +} + +int fossil_io_cstring_compare_safe(ccstring s1, ccstring s2, size_t max_len) { + if (!s1 || !s2) return (s1 == s2) ? 0 : (s1 ? 1 : -1); + return strncasecmp(s1, s2, max_len); +} + +int fossil_io_cstring_append_safe(cstring *dest, ccstring src, size_t max_len) { + if (!dest || !src || !*dest) return -1; + size_t current_len = strnlen(*dest, max_len); + size_t append_len = strnlen(src, max_len - current_len); + if (current_len + append_len >= max_len) return -1; + fossil_io_cstring_append(dest, src); + (*dest)[current_len + append_len] = '\0'; + return 0; +} + +cstring fossil_io_cstring_trim_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + cstring copy = fossil_io_cstring_copy_safe(str, max_len); + if (!copy) return NULL; + + // Trim left + size_t start = 0; + while (start < max_len && isspace((unsigned char)copy[start])) start++; + + // Trim right + size_t end = strnlen(copy, max_len); + while (end > start && isspace((unsigned char)copy[end - 1])) end--; + + size_t trimmed_len = end - start; + cstring trimmed = (cstring)malloc(trimmed_len + 1); + if (!trimmed) { + free(copy); + return NULL; + } + memcpy(trimmed, copy + start, trimmed_len); + trimmed[trimmed_len] = '\0'; + free(copy); + return trimmed; +} + +cstring *fossil_io_cstring_split_safe(ccstring str, char delimiter, size_t *count, size_t max_len) { + if (!str || !count) return NULL; + + size_t n = 0; + for (size_t i = 0; i < strnlen(str, max_len); i++) { + if (str[i] == delimiter) n++; + } + n++; // number of substrings + + cstring *result = (cstring *)calloc(n, sizeof(cstring)); + if (!result) return NULL; + + size_t idx = 0; + const char *start = str; + for (size_t i = 0; i <= strnlen(str, max_len); i++) { + if (str[i] == delimiter || str[i] == '\0') { + size_t len = &str[i] - start; + if (len > max_len) len = max_len; + result[idx++] = fossil_io_cstring_create_safe(start, len); + start = &str[i + 1]; + } + } + *count = n; + return result; +} + +cstring fossil_io_cstring_replace_safe(ccstring str, ccstring old, ccstring new_str, size_t max_len) { + if (!str || !old || !new_str) return NULL; + size_t str_len = strnlen(str, max_len); + cstring result = fossil_io_cstring_create_safe(str, max_len); + if (!result) return NULL; + + const char *pos = strstr(result, old); + while (pos) { + // compute lengths + size_t prefix_len = pos - result; + size_t old_len = strnlen(old, max_len); + size_t new_len = strnlen(new_str, max_len); + if (prefix_len + new_len > max_len) break; + + // perform replacement + memmove(result + prefix_len + new_len, pos + old_len, str_len - prefix_len - old_len + 1); + memcpy(result + prefix_len, new_str, new_len); + + pos = strstr(result + prefix_len + new_len, old); + } + result[max_len - 1] = '\0'; + return result; +} + +// Upper/lower conversion +cstring fossil_io_cstring_to_upper_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + cstring copy = fossil_io_cstring_copy_safe(str, max_len); + for (size_t i = 0; i < strnlen(copy, max_len); i++) copy[i] = toupper((unsigned char)copy[i]); + return copy; +} + +cstring fossil_io_cstring_to_lower_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + cstring copy = fossil_io_cstring_copy_safe(str, max_len); + for (size_t i = 0; i < strnlen(copy, max_len); i++) copy[i] = tolower((unsigned char)copy[i]); + return copy; +} + +// Safe format string +cstring fossil_io_cstring_format_safe(size_t max_len, ccstring format, ...) { + if (!format) return NULL; + cstring buffer = (cstring)malloc(max_len); + if (!buffer) return NULL; + va_list args; + va_start(args, format); + vsnprintf(buffer, max_len, format, args); + va_end(args); + buffer[max_len - 1] = '\0'; + return buffer; +} + +// Safe join +cstring fossil_io_cstring_join_safe(ccstring *strings, size_t count, char delimiter, size_t max_len) { + if (!strings || count == 0) return NULL; + cstring result = fossil_io_cstring_create_safe("", max_len); + for (size_t i = 0; i < count; i++) { + if (i > 0) fossil_io_cstring_append_safe(&result, &delimiter, max_len); + fossil_io_cstring_append_safe(&result, strings[i], max_len); + } + return result; +} + +// JSON escape/unescape +cstring fossil_io_cstring_escape_json_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + cstring result = fossil_io_cstring_create_safe("", max_len); + for (size_t i = 0; i < strnlen(str, max_len); i++) { + char ch = str[i]; + if (ch == '"' || ch == '\\') fossil_io_cstring_append_safe(&result, "\\", max_len); + char tmp[2] = { ch, 0 }; + fossil_io_cstring_append_safe(&result, tmp, max_len); + } + return result; +} + +cstring fossil_io_cstring_unescape_json_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + cstring result = fossil_io_cstring_create_safe("", max_len); + for (size_t i = 0; i < strnlen(str, max_len); i++) { + if (str[i] == '\\' && i + 1 < max_len) { + i++; + char tmp[2] = { str[i], 0 }; + fossil_io_cstring_append_safe(&result, tmp, max_len); } else { - free(stream); - stream = NULL; + char tmp[2] = { str[i], 0 }; + fossil_io_cstring_append_safe(&result, tmp, max_len); } } - return stream; + return result; } -void fossil_io_cstring_stream_free(fossil_io_cstring_stream *stream) { - if (stream) { - free(stream->buffer); - free(stream); +/* ---------------- Substring ---------------- */ +cstring fossil_io_cstring_substring_safe(ccstring str, size_t start, size_t length, size_t max_len) { + if (!str) return NULL; + size_t str_len = strnlen(str, max_len); + if (start >= str_len) return fossil_io_cstring_create_safe("", max_len); + if (start + length > str_len) length = str_len - start; + if (length > max_len) length = max_len; + return fossil_io_cstring_create_safe(str + start, length); +} + +/* ---------------- Reverse ---------------- */ +cstring fossil_io_cstring_reverse_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + size_t len = strnlen(str, max_len); + cstring copy = fossil_io_cstring_copy_safe(str, len); + if (!copy) return NULL; + for (size_t i = 0; i < len / 2; i++) { + char tmp = copy[i]; + copy[i] = copy[len - 1 - i]; + copy[len - 1 - i] = tmp; } + copy[len] = '\0'; + return copy; } -void fossil_io_cstring_stream_write(fossil_io_cstring_stream *stream, ccstring str) { - if (!stream || !str) return; - size_t length = strlen(str); - size_t new_length = stream->length + length; - if (new_length > stream->capacity) { - size_t new_capacity = stream->capacity * 2; - if (new_capacity < new_length) { - new_capacity = new_length; +/* ---------------- Contains ---------------- */ +int fossil_io_cstring_contains_safe(ccstring str, ccstring substr, size_t max_len) { + if (!str || !substr) return 0; + size_t len_str = strnlen(str, max_len); + size_t len_sub = strnlen(substr, max_len); + if (len_sub > len_str) return 0; + return strstr(str, substr) != NULL; +} + +/* ---------------- Repeat ---------------- */ +cstring fossil_io_cstring_repeat_safe(ccstring str, size_t count, size_t max_len) { + if (!str || count == 0) return fossil_io_cstring_create_safe("", max_len); + size_t len = strnlen(str, max_len); + if (len * count > max_len) count = max_len / len; + cstring result = fossil_io_cstring_create_safe("", max_len); + for (size_t i = 0; i < count; i++) { + fossil_io_cstring_append_safe(&result, str, max_len); + } + return result; +} + +/* ---------------- Strip Character ---------------- */ +cstring fossil_io_cstring_strip_safe(ccstring str, char ch, size_t max_len) { + if (!str) return NULL; + size_t len = strnlen(str, max_len); + size_t start = 0, end = len; + while (start < end && str[start] == ch) start++; + while (end > start && str[end - 1] == ch) end--; + return fossil_io_cstring_substring_safe(str, start, end - start, max_len); +} + +/* ---------------- Count Substring ---------------- */ +size_t fossil_io_cstring_count_safe(ccstring str, ccstring substr, size_t max_len) { + if (!str || !substr) return 0; + size_t count = 0; + size_t len_sub = strnlen(substr, max_len); + if (len_sub == 0) return 0; + + const char *p = str; + size_t remaining = strnlen(str, max_len); + while (remaining >= len_sub) { + const char *found = strstr(p, substr); + if (!found) break; + count++; + remaining -= (found - p + len_sub); + p = found + len_sub; + } + return count; +} + +/* ---------------- Pad Left ---------------- */ +cstring fossil_io_cstring_pad_left_safe(ccstring str, size_t total_length, char pad_char, size_t max_len) { + if (!str) return NULL; + size_t len = strnlen(str, max_len); + if (total_length > max_len) total_length = max_len; + if (len >= total_length) return fossil_io_cstring_substring_safe(str, 0, total_length, max_len); + + cstring result = fossil_io_cstring_create_safe("", max_len); + for (size_t i = 0; i < total_length - len; i++) fossil_io_cstring_append_safe(&result, (ccstring)&pad_char, max_len); + fossil_io_cstring_append_safe(&result, str, max_len); + return result; +} + +/* ---------------- Pad Right ---------------- */ +cstring fossil_io_cstring_pad_right_safe(ccstring str, size_t total_length, char pad_char, size_t max_len) { + if (!str) return NULL; + size_t len = strnlen(str, max_len); + if (total_length > max_len) total_length = max_len; + cstring result = fossil_io_cstring_create_safe(str, len); + for (size_t i = len; i < total_length; i++) fossil_io_cstring_append_safe(&result, (ccstring)&pad_char, max_len); + return result; +} + +/* ---------------- Starts With ---------------- */ +int fossil_io_cstring_starts_with_safe(ccstring str, ccstring prefix, size_t max_len) { + if (!str || !prefix) return 0; + size_t len_prefix = strnlen(prefix, max_len); + size_t len_str = strnlen(str, max_len); + if (len_prefix > len_str) return 0; + return strncmp(str, prefix, len_prefix) == 0; +} + +/* ---------------- Ends With ---------------- */ +int fossil_io_cstring_ends_with_safe(ccstring str, ccstring suffix, size_t max_len) { + if (!str || !suffix) return 0; + size_t len_suffix = strnlen(suffix, max_len); + size_t len_str = strnlen(str, max_len); + if (len_suffix > len_str) return 0; + return strncmp(str + len_str - len_suffix, suffix, len_suffix) == 0; +} + +/* ---------------- Equals / Case-insensitive Equals ---------------- */ +int fossil_io_cstring_equals_safe(ccstring a, ccstring b, size_t max_len) { + if (!a || !b) return 0; + return strncmp(a, b, max_len) == 0; +} + +int fossil_io_cstring_iequals_safe(ccstring a, ccstring b, size_t max_len) { + if (!a || !b) return 0; + for (size_t i = 0; i < max_len && a[i] && b[i]; i++) { + if (tolower((unsigned char)a[i]) != tolower((unsigned char)b[i])) return 0; + if (a[i+1] == '\0' && b[i+1] == '\0') break; + } + return 1; +} + +/* ---------------- Case-insensitive Contains ---------------- */ +int fossil_io_cstring_icontains_safe(ccstring str, ccstring substr, size_t max_len) { + if (!str || !substr) return 0; + size_t len_str = strnlen(str, max_len); + size_t len_sub = strnlen(substr, max_len); + if (len_sub > len_str) return 0; + for (size_t i = 0; i <= len_str - len_sub; i++) { + size_t j; + for (j = 0; j < len_sub; j++) { + if (tolower((unsigned char)str[i + j]) != tolower((unsigned char)substr[j])) break; } - char *new_buffer = (char*)realloc(stream->buffer, new_capacity); - if (new_buffer) { - stream->buffer = new_buffer; - stream->capacity = new_capacity; + if (j == len_sub) return 1; + } + return 0; +} + +/* ---------------- Strip Quotes ---------------- */ +cstring fossil_io_cstring_strip_quotes_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + size_t len = strnlen(str, max_len); + if (len < 2) return fossil_io_cstring_copy_safe(str, max_len); + if ((str[0] == '"' && str[len - 1] == '"') || (str[0] == '\'' && str[len - 1] == '\'')) { + return fossil_io_cstring_substring_safe(str, 1, len - 2, max_len); + } + return fossil_io_cstring_copy_safe(str, max_len); +} + +/* ---------------- Normalize Spaces ---------------- */ +cstring fossil_io_cstring_normalize_spaces_safe(ccstring str, size_t max_len) { + if (!str) return NULL; + cstring result = fossil_io_cstring_create_safe("", max_len); + int in_space = 0; + for (size_t i = 0; i < strnlen(str, max_len); i++) { + if (isspace((unsigned char)str[i])) { + if (!in_space) { + fossil_io_cstring_append_safe(&result, " ", max_len); + in_space = 1; + } } else { - return; + char tmp[2] = { str[i], 0 }; + fossil_io_cstring_append_safe(&result, tmp, max_len); + in_space = 0; } } - memcpy(stream->buffer + stream->length, str, length); - stream->length = new_length; + return result; +} + +/* ---------------- Index Of ---------------- */ +int fossil_io_cstring_index_of_safe(ccstring str, ccstring substr, size_t max_len) { + if (!str || !substr) return -1; + const char *found = strstr(str, substr); + if (!found) return -1; + size_t idx = found - str; + return (idx < max_len) ? (int)idx : -1; +} + +// ============================================================================ +// String Stream Functions +// ============================================================================ + +/* ---------------- Creation & Destruction ---------------- */ +fossil_io_cstring_stream* fossil_io_cstring_stream_create(size_t initial_size) { + fossil_io_cstring_stream *stream = (fossil_io_cstring_stream*)malloc(sizeof(fossil_io_cstring_stream)); + if (!stream) return NULL; + stream->buffer = (char*)malloc(initial_size); + if (!stream->buffer) { + free(stream); + return NULL; + } + stream->buffer[0] = '\0'; + stream->length = 0; + stream->capacity = initial_size; + return stream; +} + +void fossil_io_cstring_stream_free(fossil_io_cstring_stream *stream) { + if (!stream) return; + free(stream->buffer); + free(stream); +} + +/* ---------------- Internal Helper ---------------- */ +static int fossil_io_cstring_stream_ensure_capacity(fossil_io_cstring_stream *stream, size_t needed) { + if (!stream) return -1; + if (needed <= stream->capacity) return 0; + size_t new_capacity = stream->capacity * 2; + if (new_capacity < needed) new_capacity = needed; + char *new_buf = (char*)realloc(stream->buffer, new_capacity); + if (!new_buf) return -1; + stream->buffer = new_buf; + stream->capacity = new_capacity; + return 0; +} + +/* ---------------- Basic Write ---------------- */ +void fossil_io_cstring_stream_write(fossil_io_cstring_stream *stream, ccstring str) { + if (!stream || !str) return; + size_t len = strlen(str); + if (fossil_io_cstring_stream_ensure_capacity(stream, stream->length + len + 1) != 0) return; + memcpy(stream->buffer + stream->length, str, len); + stream->length += len; stream->buffer[stream->length] = '\0'; } +/* ---------------- Safe Write ---------------- */ +void fossil_io_cstring_stream_write_safe(fossil_io_cstring_stream *stream, ccstring str, size_t max_len) { + if (!stream || !str) return; + size_t len = strnlen(str, max_len); + if (fossil_io_cstring_stream_ensure_capacity(stream, stream->length + len + 1) != 0) return; + memcpy(stream->buffer + stream->length, str, len); + stream->length += len; + stream->buffer[stream->length] = '\0'; +} + +/* ---------------- Formatted Write ---------------- */ +void fossil_io_cstring_stream_write_format(fossil_io_cstring_stream *stream, ccstring format, ...) { + if (!stream || !format) return; + va_list args; + va_start(args, format); + int needed = vsnprintf(NULL, 0, format, args); + va_end(args); + if (needed <= 0) return; + if (fossil_io_cstring_stream_ensure_capacity(stream, stream->length + needed + 1) != 0) return; + + va_start(args, format); + vsnprintf(stream->buffer + stream->length, needed + 1, format, args); + va_end(args); + stream->length += needed; +} + +/* ---------------- Insert ---------------- */ +void fossil_io_cstring_stream_insert(fossil_io_cstring_stream *stream, ccstring str, size_t pos) { + if (!stream || !str || pos > stream->length) return; + size_t len = strlen(str); + if (fossil_io_cstring_stream_ensure_capacity(stream, stream->length + len + 1) != 0) return; + memmove(stream->buffer + pos + len, stream->buffer + pos, stream->length - pos + 1); + memcpy(stream->buffer + pos, str, len); + stream->length += len; +} + +/* ---------------- Truncate ---------------- */ +void fossil_io_cstring_stream_truncate(fossil_io_cstring_stream *stream, size_t new_length) { + if (!stream) return; + if (new_length >= stream->length) return; + stream->length = new_length; + stream->buffer[new_length] = '\0'; +} + +/* ---------------- Clear ---------------- */ +void fossil_io_cstring_stream_clear(fossil_io_cstring_stream *stream) { + if (!stream) return; + stream->length = 0; + if (stream->buffer) stream->buffer[0] = '\0'; +} + +/* ---------------- Reserve ---------------- */ +int fossil_io_cstring_stream_reserve(fossil_io_cstring_stream *stream, size_t min_capacity) { + if (!stream) return -1; + return fossil_io_cstring_stream_ensure_capacity(stream, min_capacity); +} + +/* ---------------- Read ---------------- */ ccstring fossil_io_cstring_stream_read(fossil_io_cstring_stream *stream) { if (!stream) return NULL; return stream->buffer; } + +/* ---------------- Length / Remaining ---------------- */ +size_t fossil_io_cstring_stream_length(fossil_io_cstring_stream *stream) { + return stream ? stream->length : 0; +} + +size_t fossil_io_cstring_stream_capacity_remaining(fossil_io_cstring_stream *stream) { + return stream ? (stream->capacity - stream->length) : 0; +} diff --git a/code/logic/fossil/io/cstring.h b/code/logic/fossil/io/cstring.h index 186cff6..e643205 100644 --- a/code/logic/fossil/io/cstring.h +++ b/code/logic/fossil/io/cstring.h @@ -463,6 +463,367 @@ cstring fossil_io_cstring_strip_quotes(ccstring str); */ cstring fossil_io_cstring_append(cstring *dest, ccstring src); +// Secure String + +/** + * @brief Creates a new cstring with the given initial value safely. + * + * Allocates a new buffer for the string, ensuring null-termination. + * + * @param init The initial value for the cstring. + * @return A new cstring initialized with the given value, or NULL on failure. + */ +cstring fossil_io_cstring_create_safe(const char *init, size_t max_len); + +/** + * @brief Frees a cstring safely. + * + * Sets the pointer to NULL after freeing to prevent dangling references. + * + * @param str Pointer to the cstring to free. + */ +void fossil_io_cstring_free_safe(cstring *str); + +/** + * @brief Creates a safe copy of the given cstring. + * + * @param str The cstring to copy. + * @param max_len Maximum number of characters to copy. + * @return A newly allocated cstring copy, or NULL on failure. + */ +cstring fossil_io_cstring_copy_safe(ccstring str, size_t max_len); + +/** + * @brief Duplicates the given cstring safely. + * + * @param str The cstring to duplicate. + * @param max_len Maximum number of characters to duplicate. + * @return A newly allocated duplicate cstring, or NULL on failure. + */ +cstring fossil_io_cstring_dup_safe(ccstring str, size_t max_len); + +/** + * @brief Concatenates two cstrings safely. + * + * Ensures that the resulting string is null-terminated and does not overflow. + * + * @param s1 First cstring. + * @param s2 Second cstring. + * @param max_len Maximum allowed length of the resulting string. + * @return A new concatenated cstring, or NULL on failure. + */ +cstring fossil_io_cstring_concat_safe(ccstring s1, ccstring s2, size_t max_len); + +/** + * @brief Returns the length of the given cstring safely. + * + * Ensures that the string is properly null-terminated within max_len. + * + * @param str The cstring whose length is determined. + * @param max_len Maximum characters to scan. + * @return The length of the cstring up to max_len. + */ +size_t fossil_io_cstring_length_safe(ccstring str, size_t max_len); + +/** + * @brief Compares two cstrings safely. + * + * Ensures no buffer overrun; comparison stops at max_len or null terminator. + * + * @param s1 First cstring. + * @param s2 Second cstring. + * @param max_len Maximum characters to compare. + * @return <0 if s10 if s1>s2 + */ +int fossil_io_cstring_compare_safe(ccstring s1, ccstring s2, size_t max_len); + +/** + * @brief Safely concatenates src to dest in-place with bounds checking. + * + * Resizes the buffer as needed. Ensures null-termination. + * + * @param dest Pointer to destination cstring. + * @param src Source cstring to append. + * @param max_len Maximum allowed length for dest after append. + * @return 0 on success, non-zero on failure. + */ +int fossil_io_cstring_append_safe(cstring *dest, ccstring src, size_t max_len); + +/** + * @brief Trims whitespace safely from a cstring. + * + * Ensures null-termination and modifies in-place or allocates a new string. + * + * @param str The cstring to trim. + * @param max_len Maximum length of the string to process. + */ +cstring fossil_io_cstring_trim_safe(ccstring str, size_t max_len); + +/** + * @brief Splits a cstring safely by a delimiter. + * + * Allocates an array of strings, each safely null-terminated. + * + * @param str The string to split. + * @param delimiter Character to split by. + * @param count Pointer to store number of substrings. + * @param max_len Maximum length of each substring. + * @return Array of safe cstrings, or NULL on failure. + */ +cstring *fossil_io_cstring_split_safe(ccstring str, char delimiter, size_t *count, size_t max_len); + +/** + * @brief Replaces all occurrences of a substring safely. + * + * Allocates a new string and ensures null-termination. + * + * @param str Original string. + * @param old Substring to replace. + * @param new_str Replacement substring. + * @param max_len Maximum length of resulting string. + * @return A new cstring with replacements, or NULL on failure. + */ +cstring fossil_io_cstring_replace_safe(ccstring str, ccstring old, ccstring new_str, size_t max_len); + +/** + * @brief Converts a cstring to uppercase safely. + * + * Allocates a new string, ensures null-termination. + * + * @param str The cstring to convert. + * @param max_len Maximum allowed length. + * @return Uppercase cstring, or NULL on failure. + */ +cstring fossil_io_cstring_to_upper_safe(ccstring str, size_t max_len); + +/** + * @brief Converts a cstring to lowercase safely. + * + * @param str The cstring to convert. + * @param max_len Maximum allowed length. + * @return Lowercase cstring, or NULL on failure. + */ +cstring fossil_io_cstring_to_lower_safe(ccstring str, size_t max_len); + +/** + * @brief Safely creates a formatted cstring. + * + * Allocates enough memory for the resulting string; guarantees null-termination. + * + * @param max_len Maximum length for formatted string. + * @param format Format string. + * @param ... Format arguments. + * @return Newly allocated formatted cstring, or NULL on failure. + */ +cstring fossil_io_cstring_format_safe(size_t max_len, ccstring format, ...); + +/** + * @brief Safely joins multiple strings with a delimiter. + * + * @param strings Array of cstrings. + * @param count Number of elements. + * @param delimiter Character to insert. + * @param max_len Maximum allowed length of resulting string. + * @return New cstring or NULL on failure. + */ +cstring fossil_io_cstring_join_safe(ccstring *strings, size_t count, char delimiter, size_t max_len); + +/** + * @brief Safely escapes a cstring for JSON output. + * + * Ensures resulting string is null-terminated and memory-safe. + * + * @param str Input cstring. + * @param max_len Maximum length of resulting escaped string. + * @return Escaped cstring, or NULL on failure. + */ +cstring fossil_io_cstring_escape_json_safe(ccstring str, size_t max_len); + +/** + * @brief Safely unescapes a JSON-escaped cstring. + * + * @param str JSON-escaped string. + * @param max_len Maximum length of resulting string. + * @return Unescaped cstring, or NULL on failure. + */ +cstring fossil_io_cstring_unescape_json_safe(ccstring str, size_t max_len); + +/** + * @brief Extracts a substring from a given cstring safely. + * + * Ensures bounds checking and respects maximum length. + * + * @param str The original cstring. + * @param start The starting index for the substring. + * @param length The number of characters to include. + * @param max_len Maximum allowed length to prevent buffer overrun. + * @return A newly allocated cstring containing the substring, or NULL on failure. + */ +cstring fossil_io_cstring_substring_safe(ccstring str, size_t start, size_t length, size_t max_len); + +/** + * @brief Reverses a cstring safely. + * + * Creates a new string that is the reverse of the input while ensuring memory safety. + * + * @param str The cstring to reverse. + * @param max_len Maximum length to process to prevent overflow. + * @return A newly allocated reversed cstring, or NULL on failure. + */ +cstring fossil_io_cstring_reverse_safe(ccstring str, size_t max_len); + +/** + * @brief Checks if a cstring contains a substring safely. + * + * Ensures that no buffer overruns occur. + * + * @param str The cstring to search. + * @param substr The substring to look for. + * @param max_len Maximum number of characters to scan. + * @return 1 if substring exists, 0 otherwise. + */ +int fossil_io_cstring_contains_safe(ccstring str, ccstring substr, size_t max_len); + +/** + * @brief Repeats a cstring multiple times safely. + * + * Ensures the resulting string does not exceed max_len. + * + * @param str The string to repeat. + * @param count Number of repetitions. + * @param max_len Maximum length of resulting string. + * @return A newly allocated repeated cstring, or NULL on failure. + */ +cstring fossil_io_cstring_repeat_safe(ccstring str, size_t count, size_t max_len); + +/** + * @brief Strips a specified character from both ends of a cstring safely. + * + * @param str The cstring to process. + * @param ch The character to strip. + * @param max_len Maximum length to process. + * @return A new cstring with the specified character removed from start and end. + */ +cstring fossil_io_cstring_strip_safe(ccstring str, char ch, size_t max_len); + +/** + * @brief Counts the occurrences of a substring within a cstring safely. + * + * Ensures safe bounds checking. + * + * @param str The cstring to search. + * @param substr The substring to count. + * @param max_len Maximum length to scan. + * @return The number of occurrences of the substring. + */ +size_t fossil_io_cstring_count_safe(ccstring str, ccstring substr, size_t max_len); + +/** + * @brief Pads a cstring on the left with a specific character safely. + * + * Ensures total length does not exceed max_len. + * + * @param str The cstring to pad. + * @param total_length The desired total length after padding. + * @param pad_char Character to pad with. + * @param max_len Maximum allowed length of the resulting string. + * @return A newly allocated padded cstring. + */ +cstring fossil_io_cstring_pad_left_safe(ccstring str, size_t total_length, char pad_char, size_t max_len); + +/** + * @brief Pads a cstring on the right with a specific character safely. + * + * Ensures total length does not exceed max_len. + * + * @param str The cstring to pad. + * @param total_length The desired total length after padding. + * @param pad_char Character to pad with. + * @param max_len Maximum allowed length of the resulting string. + * @return A newly allocated padded cstring. + */ +cstring fossil_io_cstring_pad_right_safe(ccstring str, size_t total_length, char pad_char, size_t max_len); + +/** + * @brief Checks if a cstring starts with a specified prefix safely. + * + * @param str The cstring to check. + * @param prefix The prefix to test. + * @param max_len Maximum characters to consider. + * @return 1 if the cstring starts with the prefix, 0 otherwise. + */ +int fossil_io_cstring_starts_with_safe(ccstring str, ccstring prefix, size_t max_len); + +/** + * @brief Checks if a cstring ends with a specified suffix safely. + * + * @param str The cstring to check. + * @param suffix The suffix to test. + * @param max_len Maximum characters to consider. + * @return 1 if the cstring ends with the suffix, 0 otherwise. + */ +int fossil_io_cstring_ends_with_safe(ccstring str, ccstring suffix, size_t max_len); + +/** + * @brief Compares two cstrings for exact equality safely. + * + * @param a First cstring. + * @param b Second cstring. + * @param max_len Maximum characters to compare. + * @return 1 if equal, 0 otherwise. + */ +int fossil_io_cstring_equals_safe(ccstring a, ccstring b, size_t max_len); + +/** + * @brief Compares two cstrings for equality ignoring case safely. + * + * @param a First cstring. + * @param b Second cstring. + * @param max_len Maximum characters to compare. + * @return 1 if equal ignoring case, 0 otherwise. + */ +int fossil_io_cstring_iequals_safe(ccstring a, ccstring b, size_t max_len); + +/** + * @brief Checks if a substring exists in a cstring ignoring case safely. + * + * @param str The cstring to search. + * @param substr The substring to look for. + * @param max_len Maximum characters to scan. + * @return 1 if found, 0 otherwise. + */ +int fossil_io_cstring_icontains_safe(ccstring str, ccstring substr, size_t max_len); + +/** + * @brief Removes surrounding quotes from a cstring safely. + * + * @param str The cstring to process. + * @param max_len Maximum characters to process. + * @return A new cstring without surrounding quotes. + */ +cstring fossil_io_cstring_strip_quotes_safe(ccstring str, size_t max_len); + +/** + * @brief Normalizes whitespace in a cstring safely. + * + * Collapses multiple spaces into one and trims leading/trailing whitespace. + * + * @param str The cstring to normalize. + * @param max_len Maximum characters to process. + * @return A newly allocated normalized cstring. + */ +cstring fossil_io_cstring_normalize_spaces_safe(ccstring str, size_t max_len); + +/** + * @brief Returns the index of a substring in a cstring safely. + * + * @param str The cstring to search. + * @param substr The substring to find. + * @param max_len Maximum characters to consider. + * @return Index of first occurrence, or -1 if not found. + */ +int fossil_io_cstring_index_of_safe(ccstring str, ccstring substr, size_t max_len); + // String Stream /** @@ -496,6 +857,81 @@ void fossil_io_cstring_stream_write(fossil_io_cstring_stream *stream, ccstring s */ ccstring fossil_io_cstring_stream_read(fossil_io_cstring_stream *stream); +/** + * @brief Writes a string to the stream safely, respecting a maximum length. + * + * @param stream Pointer to the stream. + * @param str Null-terminated string to append. + * @param max_len Maximum number of characters to append. + */ +void fossil_io_cstring_stream_write_safe(fossil_io_cstring_stream *stream, ccstring str, size_t max_len); + +/** + * @brief Writes a formatted string to the stream (like printf). + * + * @param stream Pointer to the stream. + * @param format Format string. + * @param ... Format arguments. + */ +void fossil_io_cstring_stream_write_format(fossil_io_cstring_stream *stream, ccstring format, ...); + +/** + * @brief Inserts a cstring at a specified position in the stream. + * + * Shifts existing data to accommodate the new string. + * + * @param stream Pointer to the stream. + * @param str String to insert. + * @param pos Position to insert at (0 = beginning). + */ +void fossil_io_cstring_stream_insert(fossil_io_cstring_stream *stream, ccstring str, size_t pos); + +/** + * @brief Truncates the stream to the specified length. + * + * Reduces the stream length safely, optionally zeroing truncated data. + * + * @param stream Pointer to the stream. + * @param new_length New desired length of the stream. + */ +void fossil_io_cstring_stream_truncate(fossil_io_cstring_stream *stream, size_t new_length); + +/** + * @brief Clears the stream content without freeing the buffer. + * + * Resets length to 0 and optionally zeroes memory. + * + * @param stream Pointer to the stream. + */ +void fossil_io_cstring_stream_clear(fossil_io_cstring_stream *stream); + +/** + * @brief Ensures the stream has at least the specified capacity. + * + * Resizes the buffer if necessary. + * + * @param stream Pointer to the stream. + * @param min_capacity Minimum capacity required. + * @return 0 on success, non-zero on allocation failure. + */ +int fossil_io_cstring_stream_reserve(fossil_io_cstring_stream *stream, size_t min_capacity); + +/** + * @brief Returns the current length of the stream. + * + * @param stream Pointer to the stream. + * @return Length of data in the stream. + */ +size_t fossil_io_cstring_stream_length(fossil_io_cstring_stream *stream); + +/** + * @brief Returns the remaining capacity of the stream. + * + * @param stream Pointer to the stream. + * @return Number of bytes available before resizing is required. + */ +size_t fossil_io_cstring_stream_capacity_remaining(fossil_io_cstring_stream *stream); + #ifdef __cplusplus } @@ -996,6 +1432,267 @@ namespace fossil { int append(const std::string &src) { return fossil_io_cstring_append(&_str, src.c_str()) == nullptr ? 1 : 0; } + + /* ---------------- Safe Copy / Dup / Concat ---------------- */ + + /** + * @brief Creates a safe copy of the given string. + * + * Allocates a new CString with a copy of the input string, using the + * secure underlying C function to prevent buffer overflows. + * + * @param str The string to copy. + * @return A new CString containing a safe copy of the input string. + */ + static CString copy_safe(const std::string &str) { + return CString(fossil_io_cstring_copy_safe(str.c_str(), str.size())); + } + + /** + * @brief Duplicates a string safely. + * + * Creates a new CString with the same content as the input string, + * using a secure duplication function that respects the specified length. + * + * @param str The string to duplicate. + * @return A new CString duplicate of the input string. + */ + static CString dup_safe(const std::string &str) { + return CString(fossil_io_cstring_dup_safe(str.c_str(), str.size())); + } + + /** + * @brief Concatenates two strings safely. + * + * Produces a new CString containing s1 followed by s2, using the secure + * concatenation function and respecting the maximum allowed length. + * + * @param s1 The first string. + * @param s2 The second string. + * @param max_len Maximum length of the resulting string to prevent overflow. + * @return A new CString with the concatenated content. + */ + static CString concat_safe(const std::string &s1, const std::string &s2, size_t max_len) { + return CString(fossil_io_cstring_concat_safe(s1.c_str(), s2.c_str(), max_len)); + } + + /* ---------------- Safe Substring / Reverse / Contains ---------------- */ + + /** + * @brief Extracts a safe substring from the CString. + * + * Uses secure substring logic to prevent reading beyond the maximum length. + * + * @param start Starting index of the substring. + * @param length Length of the substring. + * @param max_len Maximum number of characters to read from the original string. + * @return A new CString containing the substring. + */ + CString substring_safe(size_t start, size_t length, size_t max_len) const { + return CString(fossil_io_cstring_substring_safe(_str, start, length, max_len)); + } + + /** + * @brief Reverses the CString safely. + * + * Returns a new CString containing the reverse of the original string + * while respecting the maximum length. + * + * @param max_len Maximum length to process for safety. + * @return A new reversed CString. + */ + CString reverse_safe(size_t max_len) const { + return CString(fossil_io_cstring_reverse_safe(_str, max_len)); + } + + /** + * @brief Checks if the CString contains a substring safely. + * + * Performs a bounds-checked search within the string. + * + * @param substr The substring to search for. + * @param max_len Maximum number of characters to check. + * @return True if substring is found, false otherwise. + */ + bool contains_safe(const std::string &substr, size_t max_len) const { + return fossil_io_cstring_contains_safe(_str, substr.c_str(), max_len) != 0; + } + + /* ---------------- Safe Repeat / Strip / Count ---------------- */ + + /** + * @brief Repeats the CString a specified number of times safely. + * + * Generates a new CString that repeats the content up to max_len characters. + * + * @param count Number of repetitions. + * @param max_len Maximum total length of the resulting string. + * @return A new repeated CString. + */ + CString repeat_safe(size_t count, size_t max_len) const { + return CString(fossil_io_cstring_repeat_safe(_str, count, max_len)); + } + + /** + * @brief Strips a character from both ends of the CString safely. + * + * Produces a new CString with leading and trailing characters removed, + * respecting max_len for safety. + * + * @param ch The character to strip. + * @param max_len Maximum number of characters to consider. + * @return A new CString with the character stripped. + */ + CString strip_safe(char ch, size_t max_len) const { + return CString(fossil_io_cstring_strip_safe(_str, ch, max_len)); + } + + /** + * @brief Counts the number of occurrences of a substring safely. + * + * Uses a secure method that respects maximum string length. + * + * @param substr The substring to count. + * @param max_len Maximum number of characters to process. + * @return Number of occurrences of substr in the CString. + */ + size_t count_safe(const std::string &substr, size_t max_len) const { + return fossil_io_cstring_count_safe(_str, substr.c_str(), max_len); + } + + /* ---------------- Safe Padding ---------------- */ + + /** + * @brief Pads the CString on the left safely. + * + * Ensures the resulting string does not exceed max_len characters. + * + * @param total_length The total desired length after padding. + * @param pad_char Character to pad with. + * @param max_len Maximum allowed length to prevent overflow. + * @return A new left-padded CString. + */ + CString pad_left_safe(size_t total_length, char pad_char, size_t max_len) const { + return CString(fossil_io_cstring_pad_left_safe(_str, total_length, pad_char, max_len)); + } + + /** + * @brief Pads the CString on the right safely. + * + * Ensures the resulting string does not exceed max_len characters. + * + * @param total_length The total desired length after padding. + * @param pad_char Character to pad with. + * @param max_len Maximum allowed length to prevent overflow. + * @return A new right-padded CString. + */ + CString pad_right_safe(size_t total_length, char pad_char, size_t max_len) const { + return CString(fossil_io_cstring_pad_right_safe(_str, total_length, pad_char, max_len)); + } + + /* ---------------- Safe Prefix / Suffix ---------------- */ + + /** + * @brief Checks if the CString starts with a prefix safely. + * + * Considers max_len for safe comparison. + * + * @param prefix The prefix to check. + * @param max_len Maximum number of characters to read. + * @return True if CString starts with prefix, false otherwise. + */ + bool starts_with_safe(const std::string &prefix, size_t max_len) const { + return fossil_io_cstring_starts_with_safe(_str, prefix.c_str(), max_len) != 0; + } + + /** + * @brief Checks if the CString ends with a suffix safely. + * + * Considers max_len for safe comparison. + * + * @param suffix The suffix to check. + * @param max_len Maximum number of characters to read. + * @return True if CString ends with suffix, false otherwise. + */ + bool ends_with_safe(const std::string &suffix, size_t max_len) const { + return fossil_io_cstring_ends_with_safe(_str, suffix.c_str(), max_len) != 0; + } + + /* ---------------- Safe Equality Checks ---------------- */ + + /** + * @brief Checks equality safely (case-sensitive). + * + * Compares CString to another string with max_len bounds. + * + * @param other The string to compare. + * @param max_len Maximum characters to compare. + * @return True if equal, false otherwise. + */ + bool equals_safe(const std::string &other, size_t max_len) const { + return fossil_io_cstring_equals_safe(_str, other.c_str(), max_len) != 0; + } + + /** + * @brief Checks equality safely (case-insensitive). + * + * Compares CString to another string with max_len bounds, ignoring case. + * + * @param other The string to compare. + * @param max_len Maximum characters to compare. + * @return True if equal ignoring case, false otherwise. + */ + bool iequals_safe(const std::string &other, size_t max_len) const { + return fossil_io_cstring_iequals_safe(_str, other.c_str(), max_len) != 0; + } + + /** + * @brief Checks if CString contains a substring safely (case-insensitive). + * + * @param substr Substring to check. + * @param max_len Maximum characters to examine. + * @return True if found, false otherwise. + */ + bool icontains_safe(const std::string &substr, size_t max_len) const { + return fossil_io_cstring_icontains_safe(_str, substr.c_str(), max_len) != 0; + } + + /* ---------------- Safe Quotes / Whitespace / Index ---------------- */ + + /** + * @brief Removes surrounding quotes safely. + * + * Considers max_len and removes only leading/trailing single or double quotes. + * + * @param max_len Maximum characters to process. + * @return A new CString with quotes removed. + */ + CString strip_quotes_safe(size_t max_len) const { + return CString(fossil_io_cstring_strip_quotes_safe(_str, max_len)); + } + + /** + * @brief Normalizes spaces safely. + * + * Collapses multiple consecutive spaces into one, respecting max_len. + * + * @param max_len Maximum characters to process. + * @return A new CString with normalized spaces. + */ + CString normalize_spaces_safe(size_t max_len) const { + return CString(fossil_io_cstring_normalize_spaces_safe(_str, max_len)); + } + + /** + * @brief Finds the index of a substring safely. + * + * @param substr Substring to find. + * @param max_len Maximum characters to search. + * @return Index of substring or -1 if not found. + */ + int index_of_safe(const std::string &substr, size_t max_len) const { + return fossil_io_cstring_index_of_safe(_str, substr.c_str(), max_len); + } /** * Returns the underlying cstring. @@ -1010,7 +1707,7 @@ namespace fossil { class CStringStream { private: fossil_io_cstring_stream* _stream; - + public: /** * Constructor to create a new cstring stream with the specified initial size. @@ -1020,14 +1717,14 @@ namespace fossil { CStringStream(size_t initial_size) { _stream = fossil_io_cstring_stream_create(initial_size); } - + /** * Destructor to free the memory allocated for the cstring stream. */ ~CStringStream() { fossil_io_cstring_stream_free(_stream); } - + /** * Writes a cstring to the cstring stream. * @@ -1036,16 +1733,118 @@ namespace fossil { void write(const std::string &str) { fossil_io_cstring_stream_write(_stream, str.c_str()); } - + + /** + * Writes a cstring to the stream safely, respecting a maximum length. + * + * @param str The cstring to append. + * @param max_len Maximum number of characters to append. + */ + void write_safe(const std::string &str, size_t max_len) { + fossil_io_cstring_stream_write_safe(_stream, str.c_str(), max_len); + } + + /** + * Writes a formatted string to the stream (like printf). + * + * @param format Format string. + * @param ... Format arguments. + */ + void write_format(const char *format, ...) { + va_list args; + va_start(args, format); + fossil_io_cstring_stream_write_format(_stream, format, args); + va_end(args); + } + + /** + * Inserts a cstring at a specified position in the stream. + * + * @param str String to insert. + * @param pos Position to insert at (0 = beginning). + */ + void insert(const std::string &str, size_t pos) { + fossil_io_cstring_stream_insert(_stream, str.c_str(), pos); + } + + /** + * Truncates the stream to the specified length. + * + * @param new_length New desired length of the stream. + */ + void truncate(size_t new_length) { + fossil_io_cstring_stream_truncate(_stream, new_length); + } + + /** + * Clears the stream content without freeing the buffer. + */ + void clear() { + fossil_io_cstring_stream_clear(_stream); + } + + /** + * Ensures the stream has at least the specified capacity. + * + * @param min_capacity Minimum capacity required. + * @return True if successful, false if allocation failed. + */ + bool reserve(size_t min_capacity) { + return fossil_io_cstring_stream_reserve(_stream, min_capacity) == 0; + } + + /** + * Returns the current length of the stream. + * + * @return Length of data in the stream. + */ + size_t length() const { + return fossil_io_cstring_stream_length(_stream); + } + + /** + * Returns the remaining capacity of the stream before resizing is required. + * + * @return Number of bytes available in the stream. + */ + size_t capacity_remaining() const { + return fossil_io_cstring_stream_capacity_remaining(_stream); + } + /** * Reads the contents of the cstring stream. * - * @return The contents of the cstring stream as a cstring. + * @return The contents of the cstring stream as a std::string. */ std::string read() const { - return fossil_io_cstring_stream_read(_stream); + ccstring buf = fossil_io_cstring_stream_read(_stream); + return buf ? std::string(buf) : std::string(); + } + + /** + * Appends a cstring using the << operator for chaining. + * + * @param str The string to append. + * @return Reference to this stream for chaining. + */ + CStringStream& operator<<(const std::string &str) { + write(str); + return *this; + } + + /** + * Appends a number using the << operator for chaining. + * Converts the number to string before writing. + * + * @param num The integer to append. + * @return Reference to this stream for chaining. + */ + CStringStream& operator<<(int num) { + char buf[32]; + snprintf(buf, sizeof(buf), "%d", num); + write(buf); + return *this; } - }; } diff --git a/code/logic/fossil/io/stream.h b/code/logic/fossil/io/stream.h index ebc74e2..48bbf88 100644 --- a/code/logic/fossil/io/stream.h +++ b/code/logic/fossil/io/stream.h @@ -16,6 +16,8 @@ #include #include +#include +#include #ifdef __cplusplus extern "C" { diff --git a/code/logic/meson.build b/code/logic/meson.build index 3e5c80c..6139b88 100644 --- a/code/logic/meson.build +++ b/code/logic/meson.build @@ -1,5 +1,7 @@ dir = include_directories('.') cc = meson.get_compiler('c') +add_project_arguments('-D_POSIX_C_SOURCE=200112L', language: 'c') +add_project_arguments('-D_POSIX_C_SOURCE=200112L', language: 'cpp') fossil_io_lib = library('fossil_io', files('parser.c', 'input.c', 'output.c', 'error.c', 'soap.c', 'stream.c', 'cstring.c'), diff --git a/code/logic/stream.c b/code/logic/stream.c index 507d45e..8b8c6aa 100644 --- a/code/logic/stream.c +++ b/code/logic/stream.c @@ -17,18 +17,16 @@ #include #include #include -#include -#include +#include #ifdef _WIN32 - #include +#include #else - #include - #ifndef _POSIX_C_SOURCE - extern int mkstemp(char *); - #endif - #include +#include +#include +#include #endif +#include fossil_fstream_t *FOSSIL_STDIN; fossil_fstream_t *FOSSIL_STDOUT; @@ -544,24 +542,25 @@ int32_t fossil_fstream_is_readable(const char *filename) { fossil_fstream_t fossil_fstream_tempfile(void) { fossil_fstream_t temp_stream; - char temp_filename[FOSSIL_BUFFER_MEDIUM]; + char temp_filename[FOSSIL_BUFFER_MEDIUM] = {0}; #ifdef _WIN32 + // Use GetTempFileName for Windows if (GetTempFileNameA(".", "fossil", 0, temp_filename) == 0) { fprintf(stderr, "Error: Failed to create temporary file\n"); return (fossil_fstream_t){NULL, ""}; } #else - char template[] = "fossil_tempfile_XXXXXX"; - int fd = mkstemp(template); - if (fd == -1) { - fprintf(stderr, "Error: Failed to create temporary file\n"); - return (fossil_fstream_t){NULL, ""}; - } - close(fd); // Close the file descriptor as it's no longer needed - strncpy(temp_filename, template, FOSSIL_BUFFER_MEDIUM); + // Use PID + timestamp + random number for POSIX + pid_t pid = getpid(); + time_t t = time(NULL); + unsigned int rand_val = (unsigned int)rand(); + snprintf(temp_filename, FOSSIL_BUFFER_MEDIUM, + "/tmp/fossil_tempfile_%d_%ld_%u.tmp", + (int)pid, (long)t, rand_val); #endif + // Open the temporary file if (fossil_fstream_open(&temp_stream, temp_filename, "wb+") != FOSSIL_ERROR_OK) { fprintf(stderr, "Error: Failed to open temporary file - %s\n", temp_filename); return (fossil_fstream_t){NULL, ""}; diff --git a/code/tests/cases/test_cstring.c b/code/tests/cases/test_cstring.c index 23b4b53..8877753 100644 --- a/code/tests/cases/test_cstring.c +++ b/code/tests/cases/test_cstring.c @@ -355,6 +355,261 @@ FOSSIL_TEST(c_test_cstring_append) { fossil_io_cstring_free(str); } +FOSSIL_TEST(c_test_cstring_create_safe_and_free_safe) { + const char *init = "Hello, World!"; + size_t max_len = 20; + cstring str = fossil_io_cstring_create_safe(init, max_len); + ASSUME_NOT_CNULL(str); + ASSUME_ITS_EQUAL_CSTR(init, str); + fossil_io_cstring_free_safe(&str); + ASSUME_ITS_CNULL(str); +} + +FOSSIL_TEST(c_test_cstring_copy_safe_and_dup_safe) { + const char *init = "Safe Copy"; + size_t max_len = 10; + cstring copy = fossil_io_cstring_copy_safe(init, max_len); + ASSUME_NOT_CNULL(copy); + ASSUME_ITS_EQUAL_CSTR("Safe Copy", copy); + cstring dup = fossil_io_cstring_dup_safe(init, max_len); + ASSUME_NOT_CNULL(dup); + ASSUME_ITS_EQUAL_CSTR("Safe Copy", dup); + fossil_io_cstring_free_safe(©); + fossil_io_cstring_free_safe(&dup); +} + +FOSSIL_TEST(c_test_cstring_concat_safe) { + const char *s1 = "Safe"; + const char *s2 = "Concat"; + size_t max_len = 20; + cstring result = fossil_io_cstring_concat_safe(s1, s2, max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("SafeConcat", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_length_safe) { + const char *str = "SafeLength"; + size_t max_len = 20; + size_t length = fossil_io_cstring_length_safe(str, max_len); + ASSUME_ITS_EQUAL_SIZE(strlen(str), length); +} + +FOSSIL_TEST(c_test_cstring_compare_safe) { + const char *s1 = "Safe"; + const char *s2 = "Safe"; + const char *s3 = "Unsafe"; + size_t max_len = 10; + ASSUME_ITS_EQUAL_I32(0, fossil_io_cstring_compare_safe(s1, s2, max_len)); + ASSUME_ITS_TRUE(fossil_io_cstring_compare_safe(s1, s3, max_len) < 0); + ASSUME_ITS_TRUE(fossil_io_cstring_compare_safe(s3, s1, max_len) > 0); +} + +FOSSIL_TEST(c_test_cstring_append_safe) { + cstring str = fossil_io_cstring_create_safe("Hello", 20); + int res = fossil_io_cstring_append_safe(&str, ", Safe!", 20); + ASSUME_ITS_EQUAL_I32(0, res); + ASSUME_ITS_EQUAL_CSTR("Hello, Safe!", str); + fossil_io_cstring_free_safe(&str); +} + +FOSSIL_TEST(c_test_cstring_trim_safe) { + cstring str = fossil_io_cstring_trim_safe(" Safe Trim ", 20); + ASSUME_NOT_CNULL(str); + ASSUME_ITS_EQUAL_CSTR("Safe Trim", str); + fossil_io_cstring_free_safe(&str); +} + +FOSSIL_TEST(c_test_cstring_split_safe) { + const char *str = "Safe,Split,Test"; + size_t count; + size_t max_len = 20; + cstring *result = fossil_io_cstring_split_safe(str, ',', &count, max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_SIZE(3, count); + ASSUME_ITS_EQUAL_CSTR("Safe", result[0]); + ASSUME_ITS_EQUAL_CSTR("Split", result[1]); + ASSUME_ITS_EQUAL_CSTR("Test", result[2]); + for (size_t i = 0; i < count; i++) fossil_io_cstring_free_safe(&result[i]); + free(result); +} + +FOSSIL_TEST(c_test_cstring_replace_safe) { + const char *str = "Safe Replace"; + const char *old = "Replace"; + const char *new_str = "Test"; + size_t max_len = 20; + cstring result = fossil_io_cstring_replace_safe(str, old, new_str, max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("Safe Test", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_to_upper_safe_and_to_lower_safe) { + const char *str = "SafeCase"; + size_t max_len = 20; + cstring upper = fossil_io_cstring_to_upper_safe(str, max_len); + cstring lower = fossil_io_cstring_to_lower_safe(str, max_len); + ASSUME_NOT_CNULL(upper); + ASSUME_NOT_CNULL(lower); + ASSUME_ITS_EQUAL_CSTR("SAFECASE", upper); + ASSUME_ITS_EQUAL_CSTR("safecase", lower); + fossil_io_cstring_free_safe(&upper); + fossil_io_cstring_free_safe(&lower); +} + +FOSSIL_TEST(c_test_cstring_format_safe) { + size_t max_len = 32; + cstring result = fossil_io_cstring_format_safe(max_len, "Safe: %d, %s", 123, "format"); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("Safe: 123, format", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_join_safe) { + ccstring arr[] = {"safe", "join", "test"}; + size_t max_len = 32; + cstring result = fossil_io_cstring_join_safe(arr, 3, '-', max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("safe-join-test", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_escape_json_safe_and_unescape_json_safe) { + size_t max_len = 64; + cstring esc = fossil_io_cstring_escape_json_safe("Safe \"JSON\"\n", max_len); + ASSUME_NOT_CNULL(esc); + ASSUME_ITS_EQUAL_CSTR("Safe \\\"JSON\\\"\\n", esc); + cstring unesc = fossil_io_cstring_unescape_json_safe(esc, max_len); + ASSUME_NOT_CNULL(unesc); + ASSUME_ITS_EQUAL_CSTR("Safe \"JSON\"\n", unesc); + fossil_io_cstring_free_safe(&esc); + fossil_io_cstring_free_safe(&unesc); +} + +FOSSIL_TEST(c_test_cstring_substring_safe) { + const char *str = "SafeSubstring"; + size_t max_len = 20; + cstring result = fossil_io_cstring_substring_safe(str, 4, 9, max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("Substring", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_reverse_safe) { + const char *str = "SafeReverse"; + size_t max_len = 20; + cstring result = fossil_io_cstring_reverse_safe(str, max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("esreveRefaS", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_contains_safe) { + const char *str = "SafeContains"; + const char *substr = "Contain"; + size_t max_len = 20; + ASSUME_ITS_TRUE(fossil_io_cstring_contains_safe(str, substr, max_len)); + ASSUME_ITS_FALSE(fossil_io_cstring_contains_safe(str, "Missing", max_len)); +} + +FOSSIL_TEST(c_test_cstring_repeat_safe) { + const char *str = "Safe"; + size_t max_len = 20; + cstring result = fossil_io_cstring_repeat_safe(str, 3, max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("SafeSafeSafe", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_strip_safe) { + const char *str = "!!!Safe!!!"; + size_t max_len = 20; + cstring result = fossil_io_cstring_strip_safe(str, '!', max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("Safe", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_count_safe) { + const char *str = "Safe Safe Safe"; + const char *substr = "Safe"; + size_t max_len = 20; + size_t count = fossil_io_cstring_count_safe(str, substr, max_len); + ASSUME_ITS_EQUAL_SIZE(3, count); +} + +FOSSIL_TEST(c_test_cstring_pad_left_safe_and_pad_right_safe) { + const char *str = "Safe"; + size_t max_len = 10; + cstring left = fossil_io_cstring_pad_left_safe(str, 8, '*', max_len); + cstring right = fossil_io_cstring_pad_right_safe(str, 8, '*', max_len); + ASSUME_NOT_CNULL(left); + ASSUME_NOT_CNULL(right); + ASSUME_ITS_EQUAL_CSTR("****Safe", left); + ASSUME_ITS_EQUAL_CSTR("Safe****", right); + fossil_io_cstring_free_safe(&left); + fossil_io_cstring_free_safe(&right); +} + +FOSSIL_TEST(c_test_cstring_starts_with_safe_and_ends_with_safe) { + const char *str = "SafePrefixSuffix"; + size_t max_len = 20; + ASSUME_ITS_TRUE(fossil_io_cstring_starts_with_safe(str, "Safe", max_len)); + ASSUME_ITS_TRUE(fossil_io_cstring_ends_with_safe(str, "Suffix", max_len)); + ASSUME_ITS_FALSE(fossil_io_cstring_starts_with_safe(str, "Unsafe", max_len)); + ASSUME_ITS_FALSE(fossil_io_cstring_ends_with_safe(str, "Prefix", max_len)); +} + +FOSSIL_TEST(c_test_cstring_equals_safe_and_iequals_safe) { + const char *a = "SafeTest"; + const char *b = "SafeTest"; + const char *c = "safetest"; + size_t max_len = 20; + ASSUME_ITS_TRUE(fossil_io_cstring_equals_safe(a, b, max_len)); + ASSUME_ITS_FALSE(fossil_io_cstring_equals_safe(a, c, max_len)); + ASSUME_ITS_TRUE(fossil_io_cstring_iequals_safe(a, c, max_len)); +} + +FOSSIL_TEST(c_test_cstring_icontains_safe) { + const char *str = "SafeContains"; + const char *substr = "contains"; + size_t max_len = 20; + ASSUME_ITS_TRUE(fossil_io_cstring_icontains_safe(str, substr, max_len)); + ASSUME_ITS_FALSE(fossil_io_cstring_icontains_safe(str, "missing", max_len)); +} + +FOSSIL_TEST(c_test_cstring_strip_quotes_safe) { + size_t max_len = 20; + cstring result1 = fossil_io_cstring_strip_quotes_safe("\"Safe\"", max_len); + cstring result2 = fossil_io_cstring_strip_quotes_safe("'Safe'", max_len); + cstring result3 = fossil_io_cstring_strip_quotes_safe("NoQuotes", max_len); + ASSUME_NOT_CNULL(result1); + ASSUME_NOT_CNULL(result2); + ASSUME_NOT_CNULL(result3); + ASSUME_ITS_EQUAL_CSTR("Safe", result1); + ASSUME_ITS_EQUAL_CSTR("Safe", result2); + ASSUME_ITS_EQUAL_CSTR("NoQuotes", result3); + fossil_io_cstring_free_safe(&result1); + fossil_io_cstring_free_safe(&result2); + fossil_io_cstring_free_safe(&result3); +} + +FOSSIL_TEST(c_test_cstring_normalize_spaces_safe) { + size_t max_len = 32; + cstring result = fossil_io_cstring_normalize_spaces_safe(" Safe Test ", max_len); + ASSUME_NOT_CNULL(result); + ASSUME_ITS_EQUAL_CSTR("Safe Test", result); + fossil_io_cstring_free_safe(&result); +} + +FOSSIL_TEST(c_test_cstring_index_of_safe) { + const char *str = "SafeIndexTest"; + size_t max_len = 20; + ASSUME_ITS_EQUAL_I32(4, fossil_io_cstring_index_of_safe(str, "Index", max_len)); + ASSUME_ITS_EQUAL_I32(-1, fossil_io_cstring_index_of_safe(str, "Missing", max_len)); +} + FOSSIL_TEST(c_test_cstring_silly_basic) { char input[] = "Hello World"; char output[64]; @@ -580,6 +835,33 @@ FOSSIL_TEST_GROUP(c_string_tests) { FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_number_from_words); FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_number_to_words); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_create_safe_and_free_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_copy_safe_and_dup_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_concat_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_length_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_compare_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_append_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_trim_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_split_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_replace_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_to_upper_safe_and_to_lower_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_format_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_join_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_escape_json_safe_and_unescape_json_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_substring_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_reverse_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_contains_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_repeat_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_strip_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_count_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_pad_left_safe_and_pad_right_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_starts_with_safe_and_ends_with_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_equals_safe_and_iequals_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_icontains_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_strip_quotes_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_normalize_spaces_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_index_of_safe); + FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_create_and_free); FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_write_and_read); FOSSIL_TEST_ADD(c_string_suite, c_test_cstring_stream_multiple_writes); diff --git a/code/tests/cases/test_cstring.cpp b/code/tests/cases/test_cstring.cpp index 41095bf..5497f02 100644 --- a/code/tests/cases/test_cstring.cpp +++ b/code/tests/cases/test_cstring.cpp @@ -593,6 +593,160 @@ FOSSIL_TEST(cpp_test_cstring_class_append) { ASSUME_ITS_EQUAL_CSTR("Hello, World!", str.str()); } +FOSSIL_TEST(cpp_test_cstring_class_copy_safe) { + std::string src = "SafeCopy"; + fossil::io::CString result = fossil::io::CString::copy_safe(src); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("SafeCopy", result.str()); +} + +FOSSIL_TEST(cpp_test_cstring_class_dup_safe) { + std::string src = "SafeDup"; + fossil::io::CString result = fossil::io::CString::dup_safe(src); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("SafeDup", result.str()); +} + +FOSSIL_TEST(cpp_test_cstring_class_concat_safe) { + std::string s1 = "Safe"; + std::string s2 = "Concat"; + fossil::io::CString result = fossil::io::CString::concat_safe(s1, s2, 16); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("SafeConcat", result.str()); + fossil::io::CString truncated = fossil::io::CString::concat_safe(s1, s2, 5); + ASSUME_ITS_EQUAL_CSTR("SafeC", truncated.str()); +} + +FOSSIL_TEST(cpp_test_cstring_class_substring_safe) { + fossil::io::CString str("SafeSubstringTest"); + fossil::io::CString result = str.substring_safe(4, 9, 20); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("Substring", result.str()); + fossil::io::CString limited = str.substring_safe(4, 20, 10); + ASSUME_ITS_TRUE(strlen(limited.str()) <= 10); +} + +FOSSIL_TEST(cpp_test_cstring_class_reverse_safe) { + fossil::io::CString str("SafeReverse"); + fossil::io::CString result = str.reverse_safe(11); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("esreveRefaS", result.str()); + fossil::io::CString limited = str.reverse_safe(4); + ASSUME_ITS_EQUAL_CSTR("efaS", limited.str()); +} + +FOSSIL_TEST(cpp_test_cstring_class_contains_safe) { + fossil::io::CString str("SafeContainsTest"); + ASSUME_ITS_TRUE(str.contains_safe("Contains", 20)); + ASSUME_ITS_FALSE(str.contains_safe("Missing", 20)); + ASSUME_ITS_FALSE(str.contains_safe("Contains", 5)); +} + +FOSSIL_TEST(cpp_test_cstring_class_repeat_safe) { + fossil::io::CString str("Safe"); + fossil::io::CString result = str.repeat_safe(3, 20); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("SafeSafeSafe", result.str()); + fossil::io::CString limited = str.repeat_safe(10, 8); + ASSUME_ITS_TRUE(strlen(limited.str()) <= 8); +} + +FOSSIL_TEST(cpp_test_cstring_class_strip_safe) { + fossil::io::CString str("!!!Safe!!!"); + fossil::io::CString result = str.strip_safe('!', 20); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("Safe", result.str()); + fossil::io::CString limited = str.strip_safe('!', 4); + ASSUME_ITS_TRUE(strlen(limited.str()) <= 4); +} + +FOSSIL_TEST(cpp_test_cstring_class_count_safe) { + fossil::io::CString str("Safe Safe Safe"); + size_t count = str.count_safe("Safe", 20); + ASSUME_ITS_EQUAL_SIZE(3, count); + size_t limited = str.count_safe("Safe", 4); + ASSUME_ITS_EQUAL_SIZE(1, limited); +} + +FOSSIL_TEST(cpp_test_cstring_class_pad_left_safe) { + fossil::io::CString str("Safe"); + fossil::io::CString result = str.pad_left_safe(8, '*', 8); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("****Safe", result.str()); + fossil::io::CString limited = str.pad_left_safe(10, '*', 6); + ASSUME_ITS_TRUE(strlen(limited.str()) <= 6); +} + +FOSSIL_TEST(cpp_test_cstring_class_pad_right_safe) { + fossil::io::CString str("Safe"); + fossil::io::CString result = str.pad_right_safe(8, '*', 8); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("Safe****", result.str()); + fossil::io::CString limited = str.pad_right_safe(10, '*', 6); + ASSUME_ITS_TRUE(strlen(limited.str()) <= 6); +} + +FOSSIL_TEST(cpp_test_cstring_class_starts_with_safe) { + fossil::io::CString str("SafePrefixTest"); + ASSUME_ITS_TRUE(str.starts_with_safe("Safe", 20)); + ASSUME_ITS_FALSE(str.starts_with_safe("Prefix", 4)); +} + +FOSSIL_TEST(cpp_test_cstring_class_ends_with_safe) { + fossil::io::CString str("SafeSuffixTest"); + ASSUME_ITS_TRUE(str.ends_with_safe("Test", 20)); + ASSUME_ITS_FALSE(str.ends_with_safe("Suffix", 4)); +} + +FOSSIL_TEST(cpp_test_cstring_class_equals_safe) { + fossil::io::CString str("SafeEquals"); + ASSUME_ITS_TRUE(str.equals_safe("SafeEquals", 20)); + ASSUME_ITS_FALSE(str.equals_safe("safeequals", 20)); + ASSUME_ITS_FALSE(str.equals_safe("SafeEquals", 4)); +} + +FOSSIL_TEST(cpp_test_cstring_class_iequals_safe) { + fossil::io::CString str("SafeEquals"); + ASSUME_ITS_TRUE(str.iequals_safe("safeequals", 20)); + ASSUME_ITS_FALSE(str.iequals_safe("Safe", 20)); + ASSUME_ITS_FALSE(str.iequals_safe("SafeEquals", 4)); +} + +FOSSIL_TEST(cpp_test_cstring_class_icontains_safe) { + fossil::io::CString str("SafeContainsTest"); + ASSUME_ITS_TRUE(str.icontains_safe("contains", 20)); + ASSUME_ITS_FALSE(str.icontains_safe("missing", 20)); + ASSUME_ITS_FALSE(str.icontains_safe("contains", 5)); +} + +FOSSIL_TEST(cpp_test_cstring_class_strip_quotes_safe) { + fossil::io::CString str1("\"SafeQuotes\""); + fossil::io::CString str2("'SafeQuotes'"); + fossil::io::CString str3("SafeQuotes"); + fossil::io::CString result1 = str1.strip_quotes_safe(20); + fossil::io::CString result2 = str2.strip_quotes_safe(20); + fossil::io::CString result3 = str3.strip_quotes_safe(20); + ASSUME_ITS_EQUAL_CSTR("SafeQuotes", result1.str()); + ASSUME_ITS_EQUAL_CSTR("SafeQuotes", result2.str()); + ASSUME_ITS_EQUAL_CSTR("SafeQuotes", result3.str()); +} + +FOSSIL_TEST(cpp_test_cstring_class_normalize_spaces_safe) { + fossil::io::CString str("Safe Spaces Test"); + fossil::io::CString result = str.normalize_spaces_safe(20); + ASSUME_NOT_CNULL(result.str()); + ASSUME_ITS_EQUAL_CSTR("Safe Spaces Test", result.str()); + fossil::io::CString limited = str.normalize_spaces_safe(4); + ASSUME_ITS_TRUE(strlen(limited.str()) <= 4); +} + +FOSSIL_TEST(cpp_test_cstring_class_index_of_safe) { + fossil::io::CString str("SafeIndexTest"); + ASSUME_ITS_EQUAL_I32(4, str.index_of_safe("Index", 20)); + ASSUME_ITS_EQUAL_I32(-1, str.index_of_safe("Missing", 20)); + ASSUME_ITS_EQUAL_I32(-1, str.index_of_safe("Index", 4)); +} + FOSSIL_TEST(cpp_test_cstring_class_silly) { fossil::io::CString str("Hello, World!"); std::string silly = str.silly(); @@ -772,6 +926,26 @@ FOSSIL_TEST_GROUP(cpp_string_tests) { FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_strip_quotes); FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_append); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_copy_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_dup_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_concat_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_substring_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_reverse_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_contains_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_repeat_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_strip_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_count_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_pad_left_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_pad_right_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_starts_with_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_ends_with_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_equals_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_iequals_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_icontains_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_strip_quotes_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_normalize_spaces_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_class_index_of_safe); + FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_create_and_free); FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_write_and_read); FOSSIL_TEST_ADD(cpp_string_suite, cpp_test_cstring_stream_multiple_writes);