8 n__str_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

src/n_str.c

String functions, everything you need to use string is here. More...

Detailed Description

String functions, everything you need to use string is here.

Author: Castagnier Mickael

Version: 1.0

Date: 01/04/05

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#ifndef NO_NSTR

#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_str.h"
#include "nilorea/n_hash.h"

#include <errno.h>
#include <pthread.h>
#include <math.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <dirent.h>

#ifdef __windows__
const char* strcasestr(const char* s1, const char* s2) {
    __n_assert(s1, return NULL);
    __n_assert(s2, return NULL);

    size_t n = strlen(s2);
    while (*s1) {
        if (!strnicmp(s1++, s2, n))
            return (s1 - 1);
    }
    return NULL;
} /* strcasestr */
#endif

void free_nstr_ptr(void* ptr) {
    N_STR* strptr = (N_STR*)ptr;
    if (ptr && strptr) {
        FreeNoLog(strptr->data);
        FreeNoLog(strptr);
    }
} /* free_nstr_ptr( ... ) */

int _free_nstr(N_STR** ptr) {
    __n_assert(ptr && (*ptr), return FALSE);

    FreeNoLog((*ptr)->data);
    FreeNoLog((*ptr));

    return TRUE;
} /* free_nstr( ... ) */

int free_nstr_nolog(N_STR** ptr) {
    if ((*ptr)) {
        FreeNoLog((*ptr)->data);
        FreeNoLog((*ptr));
    }

    return TRUE;
} /* free_nstr( ... ) */

void free_nstr_ptr_nolog(void* ptr) {
    N_STR* strptr = (N_STR*)ptr;
    if (strptr) {
        FreeNoLog(strptr->data);
        FreeNoLog(strptr);
    }
} /* free_nstr_ptr_nolog( ... ) */

char* trim_nocopy(char* s) {
    __n_assert(s, return NULL);

    if (strlen(s) == 0)
        return s;

    char* start = s;

    /* skip spaces at start */
    while (*start && isspace((unsigned char)*start))
        start++;

    char* end = s + strlen(s) - 1;
    /* iterate over the rest remebering last non-whitespace */
    while (*end && isspace((unsigned char)*end) && end > s) {
        end--;
    }
    end++;
    /* write the terminating zero after last non-whitespace */
    *end = 0;

    return start;
} /* trim_nocopy */

char* trim(const char* s) {
    __n_assert(s, return NULL);

    char* copy = strdup(s);
    __n_assert(copy, return NULL);

    const char* trimmed = trim_nocopy(copy);
    char* result = strdup(trimmed);
    free(copy);
    return result;
} /* trim */

char* nfgets(char* buffer, NSTRBYTE size, FILE* stream) {
    __n_assert(buffer, return NULL);
    __n_assert(stream, return NULL);

    if (size >= INT_MAX) {
        n_log(LOG_ERR, "size of %zu too big, >INT_MAX for buffer %p", size, buffer);
        return NULL;
    }

    if (!fgets(buffer, (int)size, stream)) {
        return NULL;
    }

    return buffer;
} /* nfgets(...) */

int empty_nstr(N_STR* nstr) {
    __n_assert(nstr, return FALSE);
    __n_assert(nstr->data, return FALSE);

    nstr->written = 0;
    memset(nstr->data, 0, nstr->length);

    return TRUE;
}

N_STR* new_nstr(NSTRBYTE size) {
    N_STR* str = NULL;

    if (size == (NSTRBYTE)-1) {
        n_log(LOG_ERR, "new_nstr: size %zu would wrap the terminator allocation", (size_t)size);
        return NULL;
    }

    Malloc(str, N_STR, 1);
    __n_assert(str, return NULL);

    str->written = 0;
    if (size == 0) {
        str->data = NULL;
        str->length = 0;
    } else {
        Malloc(str->data, char, size + 1);
        __n_assert(str->data, Free(str); return NULL);
        str->length = size;
    }
    return str;
} /* new_nstr(...) */

int char_to_nstr_ex(const char* from, NSTRBYTE nboct, N_STR** to) {
    if ((*to)) {
        n_log(LOG_ERR, "destination N_STR **str is not NULL (%p), it contain (%s). You must provide an empty destination.", (*to), ((*to) && (*to)->data) ? (*to)->data : "DATA_IS_NULL");
        n_log(LOG_ERR, "Data to copy: %s", _str(from));
        return FALSE;
    };

    (*to) = new_nstr(nboct + 1);
    __n_assert(to && (*to), return FALSE);
    /* added a sizeof( void * ) to add a consistant and secure padding at the end */
    __n_assert((*to)->data, Free((*to)); return FALSE);

    memcpy((*to)->data, from, nboct);
    (*to)->written = nboct;

    return TRUE;
} /* char_to_nstr(...) */

N_STR* char_to_nstr(const char* src) {
    __n_assert(src, return NULL);
    N_STR* strptr = NULL;
    size_t length = strlen(src);
    char_to_nstr_ex(src, length, &strptr);
    return strptr;
} /* char_to_str(...) */

N_STR* char_to_nstr_nocopy(char* src) {
    __n_assert(src, return NULL);

    N_STR* to = NULL;
    to = new_nstr(0);
    __n_assert(to, return NULL);

    to->data = src;
    to->written = strlen(src);
    to->length = to->written + 1;  // include src end of string

    return to;
} /* char_to_str_nocopy(...) */

N_STR* file_to_nstr(char* filename) {
    N_STR* tmpstr = NULL;
    struct stat filestat;
    FILE* in = NULL;
    int error = 0;

    __n_assert(filename, n_log(LOG_ERR, "Unable to make a string from NULL filename"); return NULL);

    int fd = open(filename, O_RDONLY);
    error = errno;
    if (fd == -1) {
        n_log(LOG_ERR, "Unable to open %s for reading. Errno: %s", filename, strerror(error));
        return NULL;
    }

    if (fstat(fd, &filestat) != 0) {
        error = errno;
#ifdef __linux__
        if (error == EOVERFLOW) {
            n_log(LOG_ERR, "%s size is too big ,EOVERFLOW)", filename);
        } else {
#endif
            n_log(LOG_ERR, "Couldn't stat %s. Errno: %s", filename, strerror(error));
#ifdef __linux__
        }
#endif
        close(fd);
        return NULL;
    }

    if ((uint64_t)filestat.st_size >= (uint64_t)UINT32_MAX) {
        n_log(LOG_ERR, "file size >= 4GB is not possible yet. %s is %lld oct", filename, (long long)filestat.st_size);
        close(fd);
        return NULL;
    }

    n_log(LOG_DEBUG, "%s file size is: %lld", filename, (long long)filestat.st_size);

    in = fdopen(fd, "rb");
    if (!in) {
        n_log(LOG_ERR, "fdopen failed on %s (fd=%d)", filename, fd);
        close(fd);
        return NULL;
    }

    tmpstr = new_nstr((size_t)filestat.st_size + 1);
    __n_assert(tmpstr, fclose(in); n_log(LOG_ERR, "Unable to get a new nstr of %lld octets", (long long)filestat.st_size + 1); return NULL);

    tmpstr->written = (size_t)filestat.st_size;

    /* an empty file (0 bytes) is a valid read, not an error: fread would be asked to
       read 0 bytes and return 0, which is not a failure. Return the empty (calloc'd,
       NUL-terminated) string so callers treat an empty file as empty content instead
       of a read error. Only read when there is at least one byte. */
    if (tmpstr->written > 0) {
        if (fread(tmpstr->data, sizeof(char), tmpstr->written, in) == 0) {
            n_log(LOG_ERR, "Couldn't read %s, fread return 0", filename);
            free_nstr(&tmpstr);
            fclose(in);
            return NULL;
        }
        if (ferror(in)) {
            n_log(LOG_ERR, "There were some errors when reading %s", filename);
            free_nstr(&tmpstr);
            fclose(in);
            return NULL;
        }
    }

    fclose(in);

    return tmpstr;
} /*file_to_nstr */


/* Write a whole N_STR into a file */
int nstr_to_fd(N_STR* str, FILE* out, int lock) {
#ifdef __linux__
    struct flock out_lock;
#endif
    __n_assert(out, return FALSE);
    __n_assert(str, return FALSE);

    int ret = TRUE;

    if (lock == 1) {
#ifdef __linux__
        memset(&out_lock, 0, sizeof(out_lock));
        out_lock.l_type = F_WRLCK;
        /* Place a write lock on the file. */
        fcntl(fileno(out), F_SETLKW, &out_lock);
#else
        // cppcheck-suppress uselessAssignmentArg ; intentional: suppress unused parameter warning on non-POSIX
        lock = 2;
#endif
    }

    size_t written_to_file = 0;
    if ((written_to_file = fwrite(str->data, sizeof(char), str->written, out)) != (size_t)str->written) {
        n_log(LOG_ERR, "Couldn't write file, fwrite %zu of %zu octets", written_to_file, str->written);
        ret = FALSE;
    }
    if (ferror(out)) {
        n_log(LOG_ERR, "There were some errors when writing to %d", fileno(out));
        ret = FALSE;
    }

    if (lock == 1) {
#ifdef __linux__
        memset(&out_lock, 0, sizeof(out_lock));
        out_lock.l_type = F_WRLCK;
        /* Place a write lock on the file. */
        fcntl(fileno(out), F_SETLKW, &out_lock);
#else
        // cppcheck-suppress uselessAssignmentArg
        // cppcheck-suppress unreadVariable
        lock = 2; /* compiler warning suppressor for non-POSIX */
#endif
    }

    return ret;
} /* nstr_to_fd( ... ) */

int nstr_to_file(N_STR* str, char* filename) {
    __n_assert(str, return FALSE);
    __n_assert(filename, return FALSE);

    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);  // Secure permissions: rw-------
    if (fd == -1) {
        n_log(LOG_ERR, "Couldn't open %s for writing (0600). Errno: %s", _str(filename), strerror(errno));
        return FALSE;
    }

    FILE* out = NULL;
    out = fdopen(fd, "wb");
    if (!out) {
        n_log(LOG_ERR, "fdopen failed for %s (fd=%d). Errno: %s", _str(filename), fd, strerror(errno));
        close(fd);
        return FALSE;
    }

    int ret = nstr_to_fd(str, out, 1);

    fclose(out);

    n_log(LOG_DEBUG, "%s file size is: %lld", _str(filename), (long long)str->written);

    return ret;
} /* nstr_to_file(...) */

int str_to_int_ex(const char* s, NSTRBYTE start, NSTRBYTE end, int* i, const int base) {
    char* tmpstr = NULL;
    char* endstr = NULL;
    long int l = 0;

    __n_assert(s, return FALSE);

    if (start > end) return FALSE;

    Malloc(tmpstr, char, sizeof(int) + end - start + 8);
    __n_assert(tmpstr, n_log(LOG_ERR, "Unable to Malloc( tmpstr , char ,  sizeof( int ) + %zu - %zu )", end, start); return FALSE);

    memcpy(tmpstr, s + start, end - start);

    errno = 0;
    l = strtol(tmpstr, &endstr, base);
    if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
        n_log(LOG_ERR, "OVERFLOW reached when converting %s to int", tmpstr);
        Free(tmpstr);
        return FALSE;
    }
    if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
        n_log(LOG_ERR, "UNDERFLOW reached when converting %s to int", tmpstr);
        Free(tmpstr);
        return FALSE;
    }
    if (*endstr != '\0' && *endstr != '\n') {
        n_log(LOG_ERR, "Impossible conversion for %s", tmpstr);
        Free(tmpstr);
        return FALSE;
    }
    Free(tmpstr);
    *i = (int)l;
    return TRUE;
} /* str_to_int_ex( ... ) */

int str_to_int_nolog(const char* s, NSTRBYTE start, NSTRBYTE end, int* i, const int base, N_STR** infos) {
    char* tmpstr = NULL;
    char* endstr = NULL;
    long int l = 0;

    __n_assert(s, return FALSE);

    if (start > end) return FALSE;

    Malloc(tmpstr, char, sizeof(int) + end - start + 8);
    __n_assert(tmpstr, n_log(LOG_ERR, "Unable to Malloc( tmpstr , char ,  sizeof( int ) + %zu - %zu )", end, start); return FALSE);

    memcpy(tmpstr, s + start, end - start);

    errno = 0;
    l = strtol(tmpstr, &endstr, base);
    if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
        nstrprintf((*infos), "OVERFLOW reached when converting %s to int", tmpstr);
        Free(tmpstr);
        return FALSE;
    }
    if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
        nstrprintf((*infos), "UNDERFLOW reached when converting %s to int", tmpstr);
        Free(tmpstr);
        return FALSE;
    }
    if (*endstr != '\0' && *endstr != '\n') {
        nstrprintf((*infos), "Impossible conversion for %s", tmpstr);
        Free(tmpstr);
        return FALSE;
    }
    Free(tmpstr);
    *i = (int)l;
    return TRUE;
} /* str_to_int_nolog( ... ) */

int str_to_int(const char* s, int* i, const int base) {
    int ret = FALSE;
    if (s) {
        ret = str_to_int_ex(s, 0, strlen(s), i, base);
    }
    return ret;
} /* str_to_int(...) */

int str_to_long_ex(const char* s, NSTRBYTE start, NSTRBYTE end, long int* i, const int base) {
    char* tmpstr = NULL;
    char* endstr = NULL;
    long l = 0;

    __n_assert(s, return FALSE);

    if (start > end) return FALSE;

    Malloc(tmpstr, char, sizeof(int) + end - start + 8);
    __n_assert(tmpstr, n_log(LOG_ERR, "Unable to Malloc( tmpstr , char ,  sizeof( int ) + %zu - %zu )", end, start); return FALSE);

    memcpy(tmpstr, s + start, end - start);

    errno = 0;
    l = strtol(tmpstr, &endstr, base);
    int error = errno;

    /* test return to number and errno values */
    if (tmpstr == endstr) {
        n_log(LOG_ERR, " number : %ld  invalid  (no digits found, 0 returned)", l);
        Free(tmpstr);
        return FALSE;
    } else if (error == ERANGE && l == LONG_MIN) {
        n_log(LOG_ERR, " number : %ld  invalid  (underflow occurred)", l);
        Free(tmpstr);
        return FALSE;
    } else if (error == ERANGE && l == LONG_MAX) {
        n_log(LOG_ERR, " number : %ld  invalid  (overflow occurred)", l);
        Free(tmpstr);
        return FALSE;
    } else if (error == EINVAL) /* not in all c99 implementations - gcc OK */
    {
        n_log(LOG_ERR, " number : %ld  invalid  (base contains unsupported value)", l);
        Free(tmpstr);
        return FALSE;
    } else if (error != 0 && l == 0) {
        n_log(LOG_ERR, " number : %ld  invalid  (unspecified error occurred)", l);
        Free(tmpstr);
        return FALSE;
    } else if (error == 0 && !*endstr) {
        n_log(LOG_DEBUG, " number : %ld    valid  (and represents all characters read)", l);
    } else if (error == 0 && *endstr != 0) {
        n_log(LOG_DEBUG, " number : %ld    valid  (but additional characters remain", l);
    }
    Free(tmpstr);
    *i = l;
    return TRUE;
} /* str_to_long_ex( ... ) */

int str_to_long_long_ex(const char* s, NSTRBYTE start, NSTRBYTE end, long long int* i, const int base) {
    char* tmpstr = NULL;
    char* endstr = NULL;
    long long l = 0;

    __n_assert(s, return FALSE);

    if (start > end) return FALSE;

    Malloc(tmpstr, char, sizeof(int) + end - start + 8);
    __n_assert(tmpstr, n_log(LOG_ERR, "Unable to Malloc( tmpstr , char ,  sizeof( int ) + %zu - %zu )", end, start); return FALSE);

    memcpy(tmpstr, s + start, end - start);

    errno = 0;
    l = strtoll(tmpstr, &endstr, base);
    int error = errno;

    /* test return to number and errno values */
    if (tmpstr == endstr) {
        n_log(LOG_ERR, "number: '%s' invalid (no digits found, 0 returned)", s);
        Free(tmpstr);
        return FALSE;
    } else if (error == ERANGE && l == LLONG_MIN) {
        n_log(LOG_ERR, "number: '%s' invalid (underflow occurred)", s);
        Free(tmpstr);
        return FALSE;
    } else if (error == ERANGE && l == LLONG_MAX) {
        n_log(LOG_ERR, "number: '%s' invalid (overflow occurred)", s);
        Free(tmpstr);
        return FALSE;
    } else if (error == EINVAL) /* not in all c99 implementations - gcc OK */
    {
        n_log(LOG_ERR, "number: '%s' invalid (base contains unsupported value)", s);
        Free(tmpstr);
        return FALSE;
    } else if (error != 0 && l == 0) {
        n_log(LOG_ERR, "number: '%s' invalid (unspecified error occurred)", s);
        Free(tmpstr);
        return FALSE;
    } else if (error == 0 && !*endstr) {
        n_log(LOG_DEBUG, "number : '%llu' valid (and represents all characters read)", l);
    } else if (error == 0 && *endstr != 0) {
        n_log(LOG_DEBUG, " number : '%llu' valid (remaining characters: '%s')", l, s);
    }
    Free(tmpstr);
    *i = l;
    return TRUE;
} /* str_to_long_long_ex( ... ) */

int str_to_long(const char* s, long int* i, const int base) {
    int ret = FALSE;
    if (s) {
        ret = str_to_long_ex(s, 0, strlen(s), i, base);
    }
    return ret;
} /* str_to_long(...) */

int str_to_long_long(const char* s, long long int* i, const int base) {
    int ret = FALSE;
    if (s) {
        ret = str_to_long_long_ex(s, 0, strlen(s), i, base);
    }
    return ret;
} /* str_to_long(...) */

N_STR* nstrdup(N_STR* str) {
    N_STR* new_str = NULL;

    __n_assert(str, return NULL);
    __n_assert(str->data, return NULL);

    new_str = new_nstr(str->length);
    if (new_str) {
        if (new_str->data) {
            memcpy(new_str->data, str->data, str->written);
            new_str->length = str->length;
            new_str->written = str->written;
        } else {
            Free(new_str);
            n_log(LOG_ERR, "Error duplicating N_STR %p -> data", str);
        }
    } else {
        n_log(LOG_ERR, "Error duplicating N_STR %p", str);
    }
    return new_str;
} /* nstrdup(...) */

int skipw(const char* string, char toskip, NSTRBYTE* iterator, int inc) {
    int error_flag = 0;

    __n_assert(string, return FALSE);

    NSTRBYTE slen = (NSTRBYTE)strlen(string);
    if (toskip == ' ') {
        while (*iterator <= slen && isspace((unsigned char)string[*iterator])) {
            if (inc < 0 && *iterator == 0) {
                error_flag = 1;
                break;
            } else {
                if (inc > 0)
                    *iterator = *iterator + (NSTRBYTE)inc;
                else
                    *iterator = (*iterator > (NSTRBYTE)(-inc)) ? (*iterator - (NSTRBYTE)(-inc)) : 0;
            }
        }
    } else {
        while (*iterator <= slen && string[*iterator] == toskip) {
            if (inc < 0 && *iterator == 0) {
                error_flag = 1;
                break;
            } else {
                if (inc > 0)
                    *iterator = *iterator + (NSTRBYTE)inc;
                else
                    *iterator = (*iterator > (NSTRBYTE)(-inc)) ? (*iterator - (NSTRBYTE)(-inc)) : 0;
            }
        }
    }
    if (error_flag == 1 || *iterator > slen) {
        return FALSE;
    }

    return TRUE;
} /*skipw(...)*/

int skipu(const char* string, char toskip, NSTRBYTE* iterator, int inc) {
    int error_flag = 0;

    __n_assert(string, return FALSE);

    NSTRBYTE slen = (NSTRBYTE)strlen(string);
    if (toskip == ' ') {
        while (*iterator <= slen && !isspace((unsigned char)string[*iterator])) {
            if (inc < 0 && *iterator == 0) {
                error_flag = 1;
                break;
            } else {
                if (inc > 0)
                    *iterator = *iterator + (NSTRBYTE)inc;
                else
                    *iterator = (*iterator > (NSTRBYTE)(-inc)) ? (*iterator - (NSTRBYTE)(-inc)) : 0;
            }
        }
    } else {
        while (*iterator <= slen && string[*iterator] != toskip) {
            if (inc < 0 && *iterator == 0) {
                error_flag = 1;
                break;
            } else {
                if (inc > 0)
                    *iterator = *iterator + (NSTRBYTE)inc;
                else
                    *iterator = (*iterator > (NSTRBYTE)(-inc)) ? (*iterator - (NSTRBYTE)(-inc)) : 0;
            }
        }
    }

    if (error_flag == 1 || *iterator > slen) {
        return FALSE;
    }

    return TRUE;
} /*Skipu(...)*/

int strup(const char* string, char* dest) {
    NSTRBYTE it = 0;

    __n_assert(string, return FALSE);
    __n_assert(dest, return FALSE);

    for (it = 0; it < (NSTRBYTE)strlen(string); it++)
        dest[it] = (char)toupper(string[it]);

    dest[strlen(string)] = '\0';

    return TRUE;
} /*strup(...)*/

int strlo(const char* string, char* dest) {
    NSTRBYTE it = 0;

    __n_assert(string, return FALSE);
    __n_assert(dest, return FALSE);

    for (it = 0; it < (NSTRBYTE)strlen(string); it++)
        dest[it] = (char)tolower(string[it]);

    dest[strlen(string)] = '\0';

    return TRUE;
} /*strlo(...)*/

int strcpy_u(const char* from, char* to, NSTRBYTE to_size, char delim, NSTRBYTE* it) {
    NSTRBYTE _it = 0;

    __n_assert(from, return FALSE);
    __n_assert(to, return FALSE);
    __n_assert(it, return FALSE);

    while (_it < to_size && from[(*it)] != '\0' && from[(*it)] != delim) {
        to[_it] = from[(*it)];
        (*it) = (*it) + 1;
        _it = _it + 1;
    }

    if (_it == to_size) {
        _it--;
        to[_it] = '\0';
        n_log(LOG_DEBUG, "strcpy_u: not enough space to write %zu octet to dest (%zu max) , %s: %d", _it, to_size, __FILE__, __LINE__);
        return FALSE;
    }

    to[_it] = '\0';

    if (from[(*it)] != delim) {
        n_log(LOG_DEBUG, "strcpy_u: delim value not found, written %zu octet to dest (%zu max) , %s: %d", _it, to_size, __FILE__, __LINE__);
        return FALSE;
    }
    return TRUE;
} /* strcpy_u(...) */

char** split(const char* str, const char* delim, int empty) {
    char** tab = NULL;              /* result array */
    const char* ptr = NULL;         /* tmp pointer: it only ever walks the CONST input */
    char** tmp = NULL;              /* temporary pointer for realloc */
    size_t sizeStr = 0;             /* string token size */
    size_t sizeTab = 0;             /* array size */
    const char* largestring = NULL; /* pointer to start of string */

    __n_assert(str, return NULL);
    __n_assert(delim, return NULL);

    size_t sizeDelim = strlen(delim);
    largestring = str;

    while ((ptr = strstr(largestring, delim)) != NULL) {
        sizeStr = (size_t)(ptr - largestring);
        if (empty == 1 || sizeStr != 0) {
            sizeTab++;
            tmp = (char**)realloc(tab, sizeof(char*) * sizeTab);
            __n_assert(tmp, goto error);
            tab = tmp;

            Malloc(tab[sizeTab - 1], char, sizeof(char) * (sizeStr + 1));
            __n_assert(tab[sizeTab - 1], goto error);

            memcpy(tab[sizeTab - 1], largestring, sizeStr);
            tab[sizeTab - 1][sizeStr] = '\0';
        }
        ptr = ptr + sizeDelim;
        largestring = ptr;
    }

    /* adding last part if any */
    if (strlen(largestring) != 0) {
        sizeStr = strlen(largestring);
        sizeTab++;

        tmp = (char**)realloc(tab, sizeof(char*) * sizeTab);
        __n_assert(tmp, goto error);
        tab = tmp;

        Malloc(tab[sizeTab - 1], char, sizeof(char) * (sizeStr + 1));
        __n_assert(tab[sizeTab - 1], goto error);

        memcpy(tab[sizeTab - 1], largestring, sizeStr);
        tab[sizeTab - 1][sizeStr] = '\0';
    } else {
        if (empty == 1) {
            sizeTab++;

            tmp = (char**)realloc(tab, sizeof(char*) * sizeTab);
            __n_assert(tmp, goto error);
            tab = tmp;

            Malloc(tab[sizeTab - 1], char, (int)(sizeof(char) * 1));
            __n_assert(tab[sizeTab - 1], goto error);

            tab[sizeTab - 1][0] = '\0';
        }
    }

    /* on ajoute une case a null pour finir le tableau */
    sizeTab++;
    tmp = (char**)realloc(tab, sizeof(char*) * sizeTab);
    __n_assert(tmp, goto error);
    tab = tmp;
    tab[sizeTab - 1] = NULL;

    return tab;

error:
    free_split_result(&tab);
    return NULL;
} /* split( ... ) */

int split_count(char** split_result) {
    __n_assert(split_result, return -1);
    __n_assert(split_result[0], return -1);

    int it = 0;
    while (split_result[it]) {
        it++;
    }
    return it;
} /* split_count(...) */

int free_split_result(char*** tab) {
    if (!(*tab))
        return FALSE;

    int it = 0;
    while ((*tab)[it]) {
        // cppcheck-suppress identicalInnerCondition ; Free() macro rechecks pointer internally
        Free((*tab)[it]);
        it++;
    }
    Free((*tab));

    return TRUE;
} /* free_split_result(...)*/

char* join(char** splitresult, const char* delim) {
    size_t delim_length = 0;
    if (delim)
        delim_length = strlen(delim);
    size_t total_length = 0;
    int it = 0;
    while (splitresult[it]) {
        // n_log( LOG_DEBUG , "split entry %d: %s" , it , splitresult[ it ] );
        size_t entry_length = strlen(splitresult[it]);
        if (entry_length >= SIZE_MAX - total_length) {
            n_log(LOG_ERR, "join: total_length overflow accumulating entry %d", it);
            return NULL;
        }
        total_length += entry_length;
        // if there is a delimitor and 'it' isn't the last entry
        if (delim && splitresult[it + 1]) {
            if (delim_length >= SIZE_MAX - total_length) {
                n_log(LOG_ERR, "join: total_length overflow accumulating delimiter at entry %d", it);
                return NULL;
            }
            total_length += delim_length;
        }
        it++;
    }
    char* result = NULL;
    Malloc(result, char, total_length + 1);
    __n_assert(result, return NULL);
    size_t position = 0;
    it = 0;
    while (splitresult[it]) {
        size_t copy_size = strlen(splitresult[it]);
        memcpy(&result[position], splitresult[it], copy_size);
        position += copy_size;
        // if there is a delimitor and 'it' isn't the last entry
        if (delim && splitresult[it + 1]) {
            memcpy(&result[position], delim, delim_length);
            position += delim_length;
        }
        it++;
    }
    result[position] = '\0';
    return result;
} /* join */

N_STR* nstrcat_ex(N_STR** dest, void* src, NSTRBYTE size, int resize_flag) {
    char* ptr = NULL;
    __n_assert(src, return NULL);

    /* guard against size + 1 overflow (also protects new_nstr's internal +1). Must come before any arithmetic using size */
    if (size >= SIZE_MAX - 1) {
        n_log(LOG_ERR, "size too large in nstrcat_ex: %zu", size);
        return NULL;
    }

    if (resize_flag == 0) {
        if ((*dest)) {
            /* check for written + size + 1 overflow before evaluating the expression */
            if (size > SIZE_MAX - (*dest)->written - 1) {
                n_log(LOG_ERR, "integer overflow in nstrcat_ex size calculation");
                return NULL;
            }
            if (((*dest)->written + size + 1) > (*dest)->length) {
                n_log(LOG_ERR, "%p to %p: not enough space. Resize forbidden. %zu needed, %zu available", src, (*dest), (*dest)->written + size + 1, (*dest)->length);
                return NULL;
            }
        } else {
            n_log(LOG_ERR, "%p to %p: not enough space. Resize forbidden. %zu needed, destination is NULL", src, (*dest), size + 1);
            return NULL;
        }
    }

    if (!(*dest)) {
        (*dest) = new_nstr(size + 1);
        if (!(*dest)) {
            n_log(LOG_ERR, "could not allocate new N_STR of size %zu", size + 1);
            return NULL;
        }
    }

    /* check for integer overflow before computing new size */
    if (size > SIZE_MAX - (*dest)->written - 1) {
        n_log(LOG_ERR, "integer overflow in nstrcat_ex size calculation");
        return NULL;
    }

    if ((*dest)->length < (*dest)->written + size + 1) {
        (*dest)->length = (*dest)->written + size + 1;
        if (Reallocz((*dest)->data, char, (*dest)->written, (*dest)->length) == FALSE) {
            free_nstr(dest);
            return NULL;
        }
    }

    ptr = (*dest)->data + (*dest)->written;
    memcpy(ptr, src, size);
    (*dest)->written += size;

    (*dest)->data[(*dest)->written] = '\0';

    return (*dest);
} /* nstrcat_ex( ... ) */

N_STR* nstrcat_bytes_ex(N_STR** dest, void* data, NSTRBYTE size) {
    __n_assert(dest, return NULL);

    if (size == 0) {
        n_log(LOG_ERR, "Could not copy 0 or less (%zu) octet!", size);
        return NULL;
    }

    return nstrcat_ex(dest, data, size, 1);
} /* nstrcat_bytes_ex( ... )*/

int write_and_fit_ex(char** dest, NSTRBYTE* size, NSTRBYTE* written, const char* src, NSTRBYTE src_size, NSTRBYTE additional_padding) {
    if (src_size == 0) return TRUE;
    NSTRBYTE needed_size = (*written) + src_size + 1;
    NSTRBYTE alloc_size = needed_size + additional_padding;
    /* unsigned overflow guard: also documents alloc_size >= needed_size >= 2
     * to the static analyzer (src_size >= 1 here, so needed_size >= 2). */
    if (alloc_size < needed_size) return FALSE;

    /* realloc if needed, also if destination is not allocated */
    if ((needed_size >= (*size)) || !(*dest)) {
        if (!(*dest)) {
            (*written) = 0;
            (*size) = 0;
        }
        if (Reallocz((*dest), char, (*size), alloc_size) == FALSE) {
            n_log(LOG_ERR, "reallocation error !!!!");
            return FALSE;
        }
        /* *size tracks total allocation (including padding) so the next
         * appends that fit in the over-allocation skip the realloc. */
        (*size) = alloc_size;
    }
    char* ptr = (*dest) + (*written);
    memcpy(ptr, src, src_size);
    (*written) += src_size;
    (*dest)[(*written)] = '\0';

    return TRUE;
} /* write_and_fit_ex( ...) */

int write_and_fit(char** dest, NSTRBYTE* size, NSTRBYTE* written, const char* src) {
    return write_and_fit_ex(dest, size, written, src, strlen(src), 8);
} /* write_and_fit( ...) */

int scan_dir(const char* dir, LIST* result, const int recurse) {
    return scan_dir_ex(dir, "*", result, recurse, 0);
}

int scan_dir_ex(const char* dir, const char* pattern, LIST* result, const int recurse, const int mode) {
    DIR* dp = NULL;
    struct dirent* entry = NULL;
    struct stat statbuf;

    if (!result)
        return FALSE;

    if ((dp = opendir(dir)) == NULL) {
        n_log(LOG_ERR, "cannot open directory: %s", dir);
        return FALSE;
    }

    N_STR* newname = NULL;
    while ((entry = readdir(dp)) != NULL) {
        if (!nstrprintf(newname, "%s/%s", dir, entry->d_name)) {
            n_log(LOG_ERR, "could not allocate newname for %s/%s", dir, entry->d_name);
            free_nstr(&newname);
            continue;
        }

        if (stat(newname->data, &statbuf) >= 0) {
            if (S_ISDIR(statbuf.st_mode) != 0) {
                if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0) {
                    free_nstr(&newname);
                    continue;
                }

                /* Recurse */
                if (recurse != FALSE) {
                    if (scan_dir_ex(newname->data, pattern, result, recurse, mode) != TRUE) {
                        n_log(LOG_ERR, "scan_dir_ex( %s , %s , %p , %d , %d ) returned FALSE !", newname->data, pattern, result, recurse, mode);
                    }
                }
                free_nstr(&newname);
                continue;
            } else if (S_ISREG(statbuf.st_mode) != 0) {
                if (wildmatcase(newname->data, pattern) == TRUE) {
                    if (mode == 0) {
                        char* file = strdup(newname->data);
                        if (file) {
                            list_push(result, file, &free);
                        } else {
                            n_log(LOG_ERR, "Error adding %s/%s to list", dir, entry->d_name);
                        }
                    } else if (mode == 1) {
                        list_push(result, newname, &free_nstr_ptr);
                        newname = NULL;
                    }
                }
            }
        }
        if (newname)
            free_nstr(&newname);
    }
    closedir(dp);
    return TRUE;
} /*scan_dir(...) */

int wildmat(register const char* text, register const char* p) {
    register int last = 0;
    register int matched = 0;
    register int reverse = 0;

    for (; *p; text++, p++) {
        if (*text == '\0' && *p != '*')
            return WILDMAT_ABORT;
        switch (*p) {
            case '\\':
                /* Literal match with following character. */
                p++;
            /* FALLTHROUGH */
            default:
                if (*text != *p)
                    return FALSE;
                continue;
            case '?':
                /* Match anything. */
                continue;
            case '*':
                while (*++p == '*')
                    /* Consecutive stars act just like one. */
                    ;
                if (*p == '\0')
                    /* Trailing star matches everything. */
                    return TRUE;
                while (*text)
                    if ((matched = wildmat(text++, p)) != FALSE)
                        return matched;
                return WILDMAT_ABORT;
            case '[':
                reverse = p[1] == WILDMAT_NEGATE_CLASS ? TRUE : FALSE;
                if (reverse)
                    /* Inverted character class. */
                    p++;
                matched = FALSE;
                if (p[1] == ']' || p[1] == '-')
                    if (*++p == *text)
                        matched = TRUE;
                for (last = *p; *++p && *p != ']'; last = *p)
                    /* This next line requires a good C compiler. */
                    if (*p == '-' && p[1] != ']'
                            ? *text <= *++p && *text >= last
                            : *text == *p)
                        matched = TRUE;
                if (matched == reverse)
                    return FALSE;
                continue;
        }
    }
#ifdef WILDMAT_MATCH_TAR_PATTERN
    if (*text == '/')
        return TRUE;
#endif /* MATCH_TAR_ATTERN */
    return *text == '\0';
} /* wildmatch(...) */

int wildmatcase(register const char* text, register const char* p) {
    register int last;
    register int matched;
    register int reverse;

    for (; *p; text++, p++) {
        if (*text == '\0' && *p != '*')
            return WILDMAT_ABORT;
        switch (*p) {
            case '\\':
                /* Literal match with following character. */
                p++;
            /* FALLTHROUGH */
            default:
                if (toupper(*text) != toupper(*p))
                    return FALSE;
                continue;
            case '?':
                /* Match anything. */
                continue;
            case '*':
                while (*++p == '*')
                    /* Consecutive stars act just like one. */
                    ;
                if (*p == '\0')
                    /* Trailing star matches everything. */
                    return TRUE;
                while (*text)
                    if ((matched = wildmatcase(text++, p)) != FALSE)
                        return matched;
                return WILDMAT_ABORT;
            case '[':
                reverse = p[1] == WILDMAT_NEGATE_CLASS ? TRUE : FALSE;
                if (reverse)
                    /* Inverted character class. */
                    p++;
                matched = FALSE;
                if (p[1] == ']' || p[1] == '-')
                    if (toupper(*++p) == toupper(*text))
                        matched = TRUE;
                for (last = toupper(*p); *++p && *p != ']'; last = toupper(*p))
                    if (*p == '-' && p[1] != ']'
                            ? toupper(*text) <= toupper(*++p) && toupper(*text) >= last
                            : toupper(*text) == toupper(*p))
                        matched = TRUE;
                if (matched == reverse)
                    return FALSE;
                continue;
        }
    }
#ifdef WILDMAT_MATCH_TAR_PATTERN
    if (*text == '/')
        return TRUE;
#endif /* MATCH_TAR_ATTERN */
    return *text == '\0';
} /* wildmatcase(...) */

char* str_replace(const char* string, const char* substr, const char* replacement) {
    size_t string_len = 0, substr_len = 0, replacement_len = 0, count = 0, result_len = 0;
    const char* scan = NULL;
    char* result = NULL;
    char* out = NULL;

    if (!string) return NULL;
    if (!substr || !substr[0] || !replacement) return strdup(string);
    string_len = strlen(string);
    substr_len = strlen(substr);
    replacement_len = strlen(replacement);
    for (scan = strstr(string, substr); scan; scan = strstr(scan + substr_len, substr)) count++;
    /* string_len - count * substr_len + count * replacement_len + 1, each
     * step checked: count * substr_len cannot exceed string_len. */
    result_len = string_len - count * substr_len;
    if (replacement_len && count > (SIZE_MAX - 1 - result_len) / replacement_len) {
        n_log(LOG_ERR, "str_replace: the result would be too large (%zu occurrences of %zu bytes)", count,
              replacement_len);
        return NULL;
    }
    result_len += count * replacement_len;
    Malloc(result, char, result_len + 1);
    if (!result) {
        n_log(LOG_ERR, "str_replace: could not allocate %zu bytes", result_len + 1);
        return NULL;
    }
    out = result;
    for (scan = string; count > 0; count--) {
        const char* found = strstr(scan, substr);
        size_t before = (size_t)(found - scan);

        memcpy(out, scan, before);
        out += before;
        memcpy(out, replacement, replacement_len);
        out += replacement_len;
        scan = found + substr_len;
    }
    memcpy(out, scan, strlen(scan) + 1);
    return result;
}

int str_natncasecmp(const char* a, size_t alen, const char* b, size_t blen) {
    const unsigned char* pa = (const unsigned char*)a;
    const unsigned char* pb = (const unsigned char*)b;
    const unsigned char* const enda = pa + alen;
    const unsigned char* const endb = pb + blen;
    int zeros = 0, c = 0;
    size_t common = 0;

    if (!a || !b) return (a == b) ? 0 : (!a ? -1 : 1);
    while (pa < enda && pb < endb) {
        if (*pa >= '0' && *pa <= '9' && *pb >= '0' && *pb <= '9') {
            const unsigned char* sa = pa;
            const unsigned char* sb = pb;
            const unsigned char* ea = NULL;
            const unsigned char* eb = NULL;

            while (sa < enda && *sa == '0') sa++;
            while (sb < endb && *sb == '0') sb++;
            for (ea = sa; ea < enda && *ea >= '0' && *ea <= '9'; ea++);
            for (eb = sb; eb < endb && *eb >= '0' && *eb <= '9'; eb++);
            /* Without leading zeros, the longer run is the larger number. */
            if (ea - sa != eb - sb) return (ea - sa) < (eb - sb) ? -1 : 1;
            c = memcmp(sa, sb, (size_t)(ea - sa));
            if (c) return c < 0 ? -1 : 1;
            /* Same value: remember the first difference in padding, used
             * only if nothing after it tells the strings apart. */
            if (!zeros && (sa - pa) != (sb - pb)) zeros = (sa - pa) < (sb - pb) ? -1 : 1;
            pa = ea;
            pb = eb;
        } else {
            unsigned char ca = *pa, cb = *pb;

            if (ca >= 'A' && ca <= 'Z') ca = (unsigned char)(ca - 'A' + 'a');
            if (cb >= 'A' && cb <= 'Z') cb = (unsigned char)(cb - 'A' + 'a');
            if (ca != cb) return ca < cb ? -1 : 1;
            pa++;
            pb++;
        }
    }
    if (pa < enda) return 1;
    if (pb < endb) return -1;
    if (zeros) return zeros;
    common = alen < blen ? alen : blen;
    c = common ? memcmp(a, b, common) : 0;
    if (c) return c < 0 ? -1 : 1;
    return alen == blen ? 0 : (alen < blen ? -1 : 1);
}

int str_natcasecmp(const char* a, const char* b) {
    return str_natncasecmp(a, a ? strlen(a) : 0, b, b ? strlen(b) : 0);
}

int str_sanitize_ex(char* string, const NSTRBYTE string_len, const char* mask, const NSTRBYTE masklen, const char replacement) {
    __n_assert(string, return FALSE);
    __n_assert(mask, return FALSE);

    NSTRBYTE it = 0;
    for (it = 0; it < string_len; it++) {
        NSTRBYTE mask_it = 0;
        while (mask_it < masklen && mask[mask_it] != '\0') {
            if (string[it] == mask[mask_it])
                string[it] = replacement;
            mask_it++;
        }
    }
    return TRUE;
}

int str_sanitize(char* string, const char* mask, const char replacement) {
    return str_sanitize_ex(string, strlen(string), mask, strlen(mask), replacement);
}

int resize_nstr(N_STR* nstr, size_t size) {
    __n_assert(nstr, return FALSE);
    if (size == 0) {
        // Free the current buffer and reset fields
        Free(nstr->data);
        nstr->written = 0;
        nstr->length = 0;
        return TRUE;
    }

    if (!nstr->data) {
        Malloc(nstr->data, char, size);
        __n_assert(nstr->data, return FALSE);
    } else {
        if (Reallocz(nstr->data, char, nstr->length, size) == FALSE) {
            return FALSE;
        }
    }

    nstr->length = size;

    return TRUE;
}

N_STR* nstrprintf_ex(N_STR** nstr_var, const char* format, ...) {
    __n_assert(format, return NULL);

    va_list args;
    va_list args_copy;
    int needed_size = 0;

    // Calculate the required size for the formatted string
    va_start(args, format);
    va_copy(args_copy, args);  // Copy args for reuse
    needed_size = vsnprintf(NULL, 0, format, args);
    va_end(args);

    if (needed_size < 0) {
        va_end(args_copy);
        n_log(LOG_ERR, "there was an error while computing the new size according to format \"%s\" for N_STR %p", format, (*nstr_var));
        return NULL;
    }

    size_t needed = (size_t)(needed_size + 1);  // Include null-terminator

    // Allocate or resize the N_STR object as needed
    if (!(*nstr_var)) {
        (*nstr_var) = new_nstr(needed);
        if (!(*nstr_var) || !(*nstr_var)->data) {
            va_end(args_copy);
            n_log(LOG_ERR, "could not allocate N_STR of size %zu", needed);
            return NULL;
        }
    } else if (needed > (*nstr_var)->length) {
        if (resize_nstr((*nstr_var), needed) == FALSE) {
            va_end(args_copy);
            n_log(LOG_ERR, "could not resize N_STR %p to size %zu", (*nstr_var), needed);
            return NULL;
        }
    }

    // Write the formatted string into the buffer
    vsnprintf((*nstr_var)->data, needed, format, args_copy);
    va_end(args_copy);

    (*nstr_var)->written = (size_t)needed_size;
    return (*nstr_var);
}

N_STR* nstrprintf_cat_ex(N_STR** nstr_var, const char* format, ...) {
    __n_assert(format, return NULL);

    va_list args;
    va_start(args, format);

    // duplicate va_list
    va_list args_copy;
    va_copy(args_copy, args);

    // compute needed size
    int needed_size = vsnprintf(NULL, 0, format, args_copy);
    va_end(args_copy);

    if (needed_size < 0) {
        n_log(LOG_ERR, "vsnprintf size estimation failed for format \"%s\"", format);
        va_end(args);
        return NULL;
    }

    size_t needed = (size_t)(needed_size + 1);  // +1 for '\0'

    // initialize if needed
    if (!(*nstr_var)) {
        (*nstr_var) = new_nstr(needed);
        if (!(*nstr_var)) {
            va_end(args);
            return NULL;
        }
    }

    // check for overflow before computing total_needed
    if (needed > SIZE_MAX - (*nstr_var)->written) {
        va_end(args);
        n_log(LOG_ERR, "integer overflow computing total_needed in nstrprintf_cat_ex");
        return NULL;
    }

    // resize if needed
    size_t total_needed = (*nstr_var)->written + needed;
    if (total_needed > (*nstr_var)->length) {
        if (resize_nstr((*nstr_var), total_needed) == FALSE) {
            va_end(args);
            n_log(LOG_ERR, "could not resize N_STR %p to size %zu", (*nstr_var), total_needed);
            return NULL;
        }
    }

    // append formatted string
    char* write_ptr = (*nstr_var)->data + (*nstr_var)->written;
    int written_now = vsnprintf(write_ptr, needed, format, args);
    va_end(args);

    if (written_now < 0) {
        n_log(LOG_ERR, "vsnprintf failed while writing at offset %zu in N_STR %p", (*nstr_var)->written, (*nstr_var));
        return NULL;
    }

    (*nstr_var)->written += (size_t)written_now;
    (*nstr_var)->data[(*nstr_var)->written] = '\0';  // ensure final \0

    return *nstr_var;
}

N_STR* n_str_template_expand(const char* tmpl, HASH_TABLE* vars) {
    if (!tmpl) return NULL;
    N_STR* result = new_nstr(strlen(tmpl) + 256);
    if (!result) return NULL;
    const char* p = tmpl;
    while (*p) {
        if (p[0] == '{' && p[1] == '{') {
            const char* end = strstr(p + 2, "}}");
            if (end) {
                size_t klen = (size_t)(end - (p + 2));
                char key[256];
                if (klen < sizeof(key)) {
                    memcpy(key, p + 2, klen);
                    key[klen] = '\0';
                    char* val = NULL;
                    if (vars && ht_get_string(vars, key, &val) == TRUE && val) {
                        nstrprintf_cat(result, "%s", val);
                    } else {
                        nstrprintf_cat(result, "{{%s}}", key);
                    }
                    p = end + 2;
                    continue;
                }
            }
        }
        char ch[2] = {*p, '\0'};
        nstrprintf_cat(result, "%s", ch);
        p++;
    }
    return result;
}

N_STR* n_str_url_encode(const char* src) {
    if (!src) return NULL;
    size_t in_len = strlen(src);
    /* worst case: every byte expands to "%HH" -> 3x */
    if (in_len > (SIZE_MAX - 1) / 3) return NULL;
    N_STR* out = new_nstr(in_len * 3 + 1);
    if (!out) return NULL;
    for (const unsigned char* p = (const unsigned char*)src; *p; p++) {
        unsigned char c = *p;
        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9') ||
            c == '-' || c == '_' || c == '.' || c == '~') {
            char ch[2] = {(char)c, '\0'};
            if (!nstrprintf_cat(out, "%s", ch)) {
                free_nstr(&out);
                return NULL;
            }
        } else {
            if (!nstrprintf_cat(out, "%%%02X", c)) {
                free_nstr(&out);
                return NULL;
            }
        }
    }
    return out;
}

#endif /* #ifndef NOSTR */

int n_str_utf8_valid(const char* s, size_t len) {
    const unsigned char* p = (const unsigned char*)s;
    size_t i = 0;
    if (!s)
        return len == 0 ? TRUE : FALSE;
    while (i < len) {
        unsigned char c = p[i];
        size_t need;
        unsigned long cp;
        if (c < 0x80) {
            i++;
            continue;
        }
        if (c >= 0xC2 && c <= 0xDF) {
            need = 1;
            cp = c & 0x1Fu;
        } else if (c >= 0xE0 && c <= 0xEF) {
            need = 2;
            cp = c & 0x0Fu;
        } else if (c >= 0xF0 && c <= 0xF4) {
            need = 3;
            cp = c & 0x07u;
        } else {
            return FALSE; /* stray continuation byte, C0/C1 overlong lead, or above F4 */
        }
        if (len - i - 1 < need)
            return FALSE;
        for (size_t k = 1; k <= need; k++) {
            if ((p[i + k] & 0xC0u) != 0x80u)
                return FALSE;
            cp = (cp << 6) | (p[i + k] & 0x3Fu);
        }
        if ((need == 2 && cp < 0x800) || (need == 3 && cp < 0x10000) ||
            (cp >= 0xD800 && cp <= 0xDFFF) || cp > 0x10FFFF)
            return FALSE;
        i += need + 1;
    }
    return TRUE;
}