/*
** (c) Copyright 2003-2005 Matt Messier and John Viega
** All rights reserved.
** 
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** 
** Redistributions of source code must retain the above copyright notice,
** this list of conditions and the following disclaimer.
** 
** Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 
** Neither the name of the primary nor the names of the contributors may
** be used to endorse or promote products derived from this software
** without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "isafestr.h"

u_int32_t         safestr_cookie     = 0;
u_int64_t         safestr_maxlength  = ((u_int64_t)1 << (sizeof(u_int32_t) * 8)) - sizeof(small_isafestr_t);
safestr_malloc_t  safestr_malloc_fn  = safestr_default_malloc;
safestr_realloc_t safestr_realloc_fn = safestr_default_realloc;
safestr_free_t    safestr_free_fn    = safestr_default_free;

static xxl_assettype_t map_asset(u_int32_t);
static int             compare_strings(isafestr_t, isafestr_t, unsigned char *, u_int32_t);
static void            free_isafestr_asset(isafestr_t, void *);
static void            free_void_asset(void *, void *);
static void            bake_cookie(void);
static u_int32_t       get_cookie(void);
static safestr_t *     charlist(isafestr_t, const char *, unsigned int);

unsigned char safestr_casemap_none[256] =
{
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
     16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
     32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
     48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
     64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
     96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
    112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
    128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
    144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
    160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
    176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
    192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
    208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
    224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
    240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
};

unsigned char safestr_casemap_upper[256] =
{
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
     16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
     32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
     48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
     64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
     96,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90, 123, 124, 125, 126, 127,
    128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
    144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
    160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
    176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
    192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
    208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
    224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
    240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
};

unsigned char safestr_casemap_lower[256] =
{
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
     16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
     32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
     48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
     64,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
    112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122,  91,  92,  93,  94,  95,
     96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
    112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
    128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
    144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
    160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
    176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
    192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
    208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
    224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
    240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
};

static xxl_assettype_t
map_asset(u_int32_t flag)
{
    switch ((flag & SAFESTR_ASSET_MASK))
    {
        case SAFESTR_ASSET_PERMANENT: return XXL_ASSET_PERMANENT;
        case SAFESTR_ASSET_TEMPORARY: return XXL_ASSET_TEMPORARY;
        case SAFESTR_ASSET_PROMOTE:   return XXL_ASSET_PROMOTE;
        case SAFESTR_ASSET_DEMOTE:    return XXL_ASSET_DEMOTE;
    }

    return XXL_ASSET_PERMANENT;
}

static int
compare_strings(isafestr_t istr1, isafestr_t istr2, unsigned char *map, u_int32_t limit)
{
    u_int32_t       i, length;
    unsigned char   *s1, *s2;

    if (map == safestr_casemap_none && limit == (u_int32_t)-1 && istr1->hdr.length == istr2->hdr.length)
        return memcmp(istr1->str, istr2->str, istr1->hdr.length);
    s1 = (unsigned char *)istr1->str;
    s2 = (unsigned char *)istr2->str;
    length = SAFESTR_MIN(istr1->hdr.length, istr2->hdr.length);
    if (limit != (u_int32_t)-1 && limit < length)
        length = limit;

    for (i = 0;  i < length;  i++)
    {
        if (map[s1[i]] == map[s2[i]])
            continue;
        else if (map[s1[i]] < map[s2[i]])
            return -1;
        return 1;
    }

    if (istr1->hdr.length == istr2->hdr.length)
        return 0;
    if (limit == -1)
        return (istr1->hdr.length < istr2->hdr.length ? -1 : 1);
    if (istr1->hdr.length < istr2->hdr.length)
        return (limit <= istr1->hdr.length ? 0 : -1);
    return (limit <= istr2->hdr.length ? 0 : 1);
}

static void
free_isafestr_asset(isafestr_t str, void *arg)
{
    if ((XXL_EXCEPTION_CODE() && !(str->hdr.flags & SAFESTR_ORIGINAL)) ||
        (!XXL_EXCEPTION_CODE() && (str->hdr.flags & SAFESTR_RESIZED)))
    {
        safestr_memzero(str->str, str->hdr.size + 1);
        str->hdr.size = str->hdr.length = str->hdr.flags = str->hdr.cookie = 0;
        safestr_free_fn(str, __FILE__, __LINE__);
        return;
    }

    str->hdr.flags &= ~(SAFESTR_ORIGINAL | SAFESTR_RESIZED);
    DEC_REF_COUNT(str);
    if (REF_COUNT(str) <= 0 ||
        (REF_COUNT(str) == 1 && (str->hdr.flags & SAFESTR_TEMPORARY)))
    {
        if (str->hdr.cookie != safestr_cookie)
            XXL_THROW_ERROR(SAFESTR_ERROR_BAD_ADDRESS, NULL);
#ifdef _DEBUG
        if (getenv("SAFESTR_TRACE"))
        {
            fprintf(stderr, "Freeing string 0x%08X (\"%s\")\n",
                    (unsigned int)str->str, str->str);
        }
#endif
        safestr_memzero(str->str, str->hdr.size + 1);
        str->hdr.size = str->hdr.length = str->hdr.flags = str->hdr.cookie = 0;
        safestr_free_fn(str, __FILE__, __LINE__);
    }
}

static void
free_void_asset(void *ptr, void *arg)
{
    safestr_free_fn(ptr, __FILE__, __LINE__);
}

static void
bake_cookie(void)
{
#ifdef WIN32
    HCRYPTPROV  c;

    if (!CryptAcquireContext(&c, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) 
        XXL_THROW_ERROR(SAFESTR_ERROR_PRNG_FAILURE, NULL);
    do
    {
        if (!CryptGenRandom(c, sizeof(safestr_cookie), (BYTE *)&safestr_cookie))
        {
            CryptReleaseContext(c, 0);
            XXL_THROW_ERROR(SAFESTR_ERROR_PRNG_FAILURE, NULL);
        }
    } while (!safestr_cookie);
    CryptReleaseContext(c, 0);
#else
    int fd;

    if ((fd = open("/dev/urandom", O_RDONLY)) == -1)
        XXL_THROW_ERROR(SAFESTR_ERROR_PRNG_FAILURE, NULL);
    do
    {
        read(fd, &safestr_cookie, sizeof(safestr_cookie));
    } while (!safestr_cookie);
    close(fd);
#endif
}

static u_int32_t
get_cookie(void)
{
    if (!safestr_cookie)
        bake_cookie();
    return safestr_cookie;
}

static safestr_t *
charlist(isafestr_t str, const char *file, unsigned int lineno)
{
    safestr_t   *ret;
    u_int32_t   i, size;
    isafestr_t  istr;

    /* Assets saved in this function will be saved within the context of the
     * caller: safestr_split()
     */

    size = sizeof(safestr_t) * (str->hdr.length + 1);
    ret = (safestr_t *)safestr_malloc(size, XXL_ASSET_PROMOTE, file, lineno);

    for (i = 0;  i < str->hdr.length;  i++)
    {
        ret[i] = safestr_do_alloc(1, (str->hdr.flags & SAFESTR_TRUSTED), file, lineno);
        istr   = (isafestr_t)((char *)ret[i] - sizeof(small_isafestr_t));
        XXL_ASSET_SAVE(istr, free_isafestr_asset, NULL, XXL_ASSET_PROMOTE);
        istr->hdr.length = 1;
        istr->str[0] = str->str[i];
        istr->str[1] = '\0';
    }
    ret[str->hdr.length] = NULL;
    return ret;
}

/* ------------------------------------------------------------------------- */

void *
safestr_malloc(size_t size, xxl_assettype_t type, const char *file, unsigned int lineno)
{
    void    *ptr;

    if (!(ptr = safestr_malloc_fn(size, file, lineno)))
        XXL_THROW_ERROR(SAFESTR_ERROR_OUT_OF_MEMORY, NULL);
    XXL_ASSET_SAVE(ptr, free_void_asset, NULL, type);

    return ptr;
}

void *
safestr_realloc(void *old_ptr, size_t size, const char *file, unsigned int lineno)
{
    void    *new_ptr;

    if (!(new_ptr = safestr_realloc_fn(old_ptr, size, file, lineno)))
        XXL_THROW_ERROR(SAFESTR_ERROR_OUT_OF_MEMORY, NULL);
    XXL_ASSET_UPDATE(old_ptr, new_ptr);

    return new_ptr;
}

isafestr_t
safestr_get(safestr_t s, u_int32_t flags)
{
    isafestr_t  str;

    if (!s)
        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_PARAMETER, NULL);
    str = (isafestr_t)((char *)s - sizeof(small_isafestr_t));
    if (str->hdr.cookie != safestr_cookie)
        XXL_THROW_ERROR(SAFESTR_ERROR_BAD_ADDRESS, NULL);
    if ((flags & SAFESTR_GET_WRITABLE) && (str->hdr.flags & SAFESTR_IMMUTABLE))
        XXL_THROW_ERROR(SAFESTR_ERROR_IMMUTABLE_STRING, s);
    if ((flags & SAFESTR_GET_WRITABLE) && (REF_COUNT(str) > 1))
        XXL_THROW_ERROR(SAFESTR_ERROR_IMMUTABLE_STRING, s);
    if (REF_COUNT(str) == 0xFFFFFFFF)
        XXL_THROW_ERROR(SAFESTR_ERROR_TOO_MANY_REFERENCES, s);
    str->hdr.flags |= SAFESTR_ORIGINAL;
    INC_REF_COUNT(str);
    XXL_ASSET_SAVE(str, free_isafestr_asset, NULL, XXL_ASSET_TEMPORARY);
    return str;
}

isafestr_t
safestr_resize(isafestr_t str, u_int32_t length)
{
    u_int32_t   size;
    isafestr_t  new_str;

    /* neither length nor str->hdr.size consider null-terminator */
    if (length <= str->hdr.size)
    {
        safestr_memzero(str->str + length, str->hdr.size - length);
        str->hdr.length = length;
        return str;
    }
    if (length + 1 > safestr_maxlength)
        XXL_THROW_ERROR(SAFESTR_ERROR_STRING_TOO_LONG, NULL);

    size = SAFESTR_ROUND(sizeof(small_isafestr_t) + length + 1);
    new_str = (isafestr_t)safestr_malloc(size, XXL_ASSET_PERMANENT, __FILE__, __LINE__);
    new_str->hdr.size   = size - sizeof(small_isafestr_t) - 1;
    new_str->hdr.length = length;
    new_str->hdr.flags  = str->hdr.flags & ~SAFESTR_ORIGINAL;
    new_str->hdr.refs   = str->hdr.refs;
    new_str->hdr.cookie = str->hdr.cookie;
    memcpy(new_str->str, str->str, str->hdr.length + 1);
    str->hdr.flags |= SAFESTR_RESIZED;

    XXL_ASSET_SAVE(new_str, free_isafestr_asset, NULL, XXL_ASSET_TEMPORARY);
    return new_str;
}

safestr_t
safestr_complete(isafestr_t iold, isafestr_t inew)
{
    u_int32_t   newsize;
    safestr_t   new_safestr, old_safestr;

    if (iold != inew)
    {
        newsize = inew->hdr.size + sizeof(small_isafestr_t) + 1;
        old_safestr = (safestr_t)iold->str;
        iold = (isafestr_t)safestr_realloc(iold, newsize, __FILE__, __LINE__);
        new_safestr = (safestr_t)iold->str;
        memcpy(iold, inew, newsize);

        iold->hdr.flags &= ~(SAFESTR_ORIGINAL | SAFESTR_RESIZED);
        inew->hdr.flags |= (SAFESTR_ORIGINAL | SAFESTR_RESIZED);

        /* Now, the original exposed pointer may still be on the asset stack
         * somewhere, so we need to update that as well (note that the call to
         * safestr_realloc() takes care of the base isafestr_t pointer for us.
         */
        XXL_ASSET_UPDATE(old_safestr, new_safestr);
    }

    return (safestr_t)iold->str;
}

/* ------------------------------------------------------------------------- */

safestr_t
safestr_do_alloc(u_int32_t length, u_int32_t flags, const char *file, unsigned int lineno)
{
    u_int32_t       size;
    isafestr_t      str;
    xxl_assettype_t asset;

    /* Is the length requested too big? */
    if (length + 1 > safestr_maxlength)
        XXL_THROW_ERROR(SAFESTR_ERROR_STRING_TOO_LONG, NULL);

    size   = SAFESTR_ROUND(sizeof(small_isafestr_t) + length + 1);
    asset  = map_asset(flags);
    flags &= (SAFESTR_TEMPORARY | SAFESTR_IMMUTABLE | SAFESTR_TRUSTED);

    str = (isafestr_t)safestr_malloc(size, XXL_ASSET_PERMANENT, file, lineno);
    str->hdr.size   = size - sizeof(small_isafestr_t) - 1;
    str->hdr.length = 0;
    str->hdr.flags  = flags;
    str->hdr.refs   = 1;
    str->hdr.cookie = get_cookie();
    str->str[0]     = 0;

    XXL_ASSET_SAVE(str->str, safestr_cleanup_asset, NULL, asset);
    return (safestr_t)str->str;
}

char
safestr_charat(safestr_t s, u_int32_t i)
{
    char        result;
    isafestr_t  str;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_READONLY);
        if (i >= str->hdr.length)
            XXL_THROW_ERROR(SAFESTR_ERROR_INDEX_OUT_OF_RANGE, NULL);
        result = str->str[i];
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

void
safestr_cleanup_asset(void *ptr, void *arg)
{
    isafestr_t  istr;

    istr = (isafestr_t)((char *)ptr - sizeof(small_isafestr_t));
    if (istr->hdr.cookie != safestr_cookie)
        XXL_THROW_ERROR(SAFESTR_ERROR_BAD_ADDRESS, NULL);
    if (REF_COUNT(istr) == 0xFFFFFFFF)
        XXL_THROW_ERROR(SAFESTR_ERROR_TOO_MANY_REFERENCES, istr);
    istr->hdr.flags |= SAFESTR_ORIGINAL;
    free_isafestr_asset(istr, NULL);
}

safestr_t
safestr_do_clone(safestr_t s, u_int32_t flags, const char *file, unsigned int lineno)
{
    u_int32_t       size;
    isafestr_t      clone, str;
    xxl_assettype_t asset;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_READONLY);

        /* Taint flag inheritance is independent of flags.  Can't pass taint by
         * calling _rw, because we have no need to check immutable.
         */
        asset  = map_asset(flags);
        flags &= (SAFESTR_TEMPORARY | SAFESTR_IMMUTABLE);
        flags |= (str->hdr.flags & SAFESTR_TRUSTED);

        size = SAFESTR_ROUND(sizeof(small_isafestr_t) + str->hdr.length + 1);
        clone = (isafestr_t)safestr_malloc(size, XXL_ASSET_PERMANENT, file, lineno);
        clone->hdr.size   = size - sizeof(small_isafestr_t) - 1;
        clone->hdr.length = str->hdr.length;
        clone->hdr.flags  = flags;
        clone->hdr.refs   = 1;
        clone->hdr.cookie = get_cookie();
        memcpy(clone->str, str->str, str->hdr.length + 1);
    }
    XXL_ASSET_BLOCK_END;

    XXL_ASSET_SAVE(clone->str, safestr_cleanup_asset, NULL, asset);
    return (safestr_t)clone->str;
}

int
safestr_compare(safestr_t str1, safestr_t str2, u_int32_t flags, ...)
{
    int             result;
    va_list         ap;
    u_int32_t       length = (u_int32_t)-1;
    isafestr_t      istr1, istr2;
    unsigned char   *map;

    XXL_ASSET_BLOCK_BEGIN
    {
        if (str1 == str2)
        {
            istr1  = safestr_get(str1, SAFESTR_GET_READONLY);
            result = 0;
        }
        else
        {
            if (flags & SAFESTR_COMPARE_LIMIT)
            {
                va_start(ap, flags);
                length = va_arg(ap, u_int32_t);
                va_end(ap);
            }

            istr1  = safestr_get(str1, SAFESTR_GET_READONLY);
            istr2  = safestr_get(str2, SAFESTR_GET_READONLY);
            map    = ((flags & SAFESTR_COMPARE_NOCASE) ? safestr_casemap_lower : safestr_casemap_none);
            result = compare_strings(istr1, istr2, map, length);
        }
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

void
safestr_concatenate(safestr_t *dst, safestr_t s, u_int32_t flags, ...)
{
    va_list     ap;
    u_int32_t   dst_length, src_length, tmp_length;
    isafestr_t  idst, iorig, isrc;

    XXL_ASSET_BLOCK_BEGIN
    {
        isrc = safestr_get(s, SAFESTR_GET_READONLY);
        idst = iorig = safestr_get(*dst, SAFESTR_GET_WRITABLE);

        dst_length = idst->hdr.length;
        src_length = isrc->hdr.length;
        if (flags & SAFESTR_COPY_LIMIT)
        {
            /* SAFESTR_COPY_LIMIT specifies the maximum length of the resulting
             * string, so we need to subtract the value from the current length
             * to get the max.
             */
            va_start(ap, flags);
            tmp_length = va_arg(ap, u_int32_t);
            va_end(ap);
            src_length = (tmp_length < src_length ? 0 : tmp_length - src_length);
        }

        if (src_length > 0)
        {
            idst = safestr_resize(idst, dst_length + src_length);
            memcpy(idst->str + dst_length, isrc->str, src_length);
            *(idst->str + idst->hdr.length) = 0;
            if (!(isrc->hdr.flags & SAFESTR_TRUSTED))
                idst->hdr.flags &= ~SAFESTR_TRUSTED;
            *dst = safestr_complete(iorig, idst);
        }
    }
    XXL_ASSET_BLOCK_END;
}

safestr_t *
safestr_do_convertarray(char **arr, u_int32_t flags, const char *file, unsigned int lineno)
{
    int         c, i;
    safestr_t   *res;
    isafestr_t  str;

    XXL_ASSET_BLOCK_BEGIN
    {
        for (c = 0;  arr[c];  c++);
        res = (safestr_t *)safestr_malloc(sizeof(safestr_t) * (c + 1), XXL_ASSET_PROMOTE, file, lineno);

        for (i = 0;  arr[i];  i++)
        {
            res[i] = safestr_do_create(arr[i], flags, file, lineno);
            str = (isafestr_t)((char *)res[i] - sizeof(small_isafestr_t));
            XXL_ASSET_SAVE(str, free_isafestr_asset, NULL, XXL_ASSET_PROMOTE);
        }
        res[i] = NULL;
    }
    XXL_ASSET_BLOCK_END;

    return res;
}

safestr_t
safestr_do_create(char *s, u_int32_t flags, const char *file, unsigned int lineno)
{
    safestr_t   str;
    u_int32_t   length;
    isafestr_t  istr;

#ifndef WIN32
    length = strlen(s);
#else
    length = lstrlenA(s);
#endif
    str = safestr_do_alloc(length, flags, file, lineno);
    memcpy((char *)str, s, length + 1);
    istr = (isafestr_t)((char *)str - sizeof(small_isafestr_t));
    istr->hdr.length = length;
    return str;
}

void
safestr_delete(safestr_t *s, u_int32_t pos, u_int32_t count)
{
    isafestr_t  str;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(*s, SAFESTR_GET_WRITABLE);

        if (pos < str->hdr.length)
        {
            if (pos + count >= str->hdr.length)
            {
                str->hdr.length = pos;
                *(str->str + str->hdr.length) = 0;
            }
            else
            {
                str->hdr.length -= count;
                memmove(str->str + pos, str->str + pos + count, 
                        str->hdr.length - pos + 1);
            }
        }
        *s = (safestr_t)str->str;
    }
    XXL_ASSET_BLOCK_END;
}

void
safestr_duplicate(safestr_t *dst, safestr_t src, u_int32_t flags, ...)
{
    va_list     ap;
    u_int32_t   length;
    isafestr_t  idst, iorig, isrc;

    XXL_ASSET_BLOCK_BEGIN
    {
        isrc = safestr_get(src, SAFESTR_GET_READONLY);
        idst = iorig = safestr_get(*dst, SAFESTR_GET_WRITABLE);

        if (!(flags & SAFESTR_COPY_LIMIT))
            length = isrc->hdr.length;
        else
        {
            va_start(ap, flags);
            length = va_arg(ap, u_int32_t);
            va_end(ap);
            length = SAFESTR_MIN(length, isrc->hdr.length);
        }

        idst = safestr_resize(idst, length);
        memcpy(idst->str, isrc->str, length);
        *(idst->str + length) = 0;
        if (!(isrc->hdr.flags & SAFESTR_TRUSTED))
            idst->hdr.flags &= ~SAFESTR_TRUSTED;
        *dst = safestr_complete(iorig, idst);
    }
    XXL_ASSET_BLOCK_END;
}

int
safestr_endswith(safestr_t str, safestr_t substr)
{
    int         match = 0;
    isafestr_t  istr, isubstr;

    XXL_ASSET_BLOCK_BEGIN
    {
        istr    = safestr_get(str, SAFESTR_GET_READONLY);
        isubstr = safestr_get(substr, SAFESTR_GET_READONLY);

        if (istr->hdr.length >= isubstr->hdr.length &&
            !memcmp(istr->str + istr->hdr.length - isubstr->hdr.length, isubstr->str, isubstr->hdr.length))
        {
            match = 1;
        }
    }
    XXL_ASSET_BLOCK_END;

    return match;
}

int
safestr_equal(safestr_t str1, safestr_t str2, u_int32_t flags, ...)
{
    int             result;
    va_list         ap;
    u_int32_t       length = (u_int32_t)-1;
    isafestr_t      istr1, istr2;
    unsigned char   *map;

    XXL_ASSET_BLOCK_BEGIN
    {
        if (str1 == str2)
        {
            istr1  = safestr_get(str1, SAFESTR_GET_READONLY);
            result = 1;
        }
        else
        {
            if (flags & SAFESTR_COMPARE_LIMIT)
            {
                va_start(ap, flags);
                length = va_arg(ap, u_int32_t);
                va_end(ap);
            }

            istr1  = safestr_get(str1, SAFESTR_GET_READONLY);
            istr2  = safestr_get(str2, SAFESTR_GET_READONLY);
            map    = ((flags & SAFESTR_COMPARE_NOCASE) ? safestr_casemap_lower : safestr_casemap_none);
            result = (compare_strings(istr1, istr2, map, length) == 0);
        }
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

void
safestr_free(safestr_t s)
{
    isafestr_t str;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_READONLY);
        DEC_REF_COUNT(str);
    }
    XXL_ASSET_BLOCK_END;
}

void
safestr_freelist(safestr_t *list)
{
    u_int32_t   i;
    isafestr_t  str;

    for (i = 0;  list[i];  i++)
    {
        XXL_ASSET_BLOCK_BEGIN
        {
            str = safestr_get(list[i], SAFESTR_GET_READONLY);
            DEC_REF_COUNT(str);
        }
        XXL_ASSET_BLOCK_END;
    }
    safestr_free_fn(list, __FILE__, __LINE__);
}

void
safestr_insert(safestr_t *dst, u_int32_t pos, safestr_t s)
{
    u_int32_t   old_length;
    isafestr_t  idst, iorig, isrc;

    XXL_ASSET_BLOCK_BEGIN
    {
        isrc = safestr_get(s, SAFESTR_GET_READONLY);
        idst = iorig = safestr_get(*dst, SAFESTR_GET_WRITABLE);

        if (pos > idst->hdr.length)
            XXL_THROW_ERROR(SAFESTR_ERROR_INDEX_OUT_OF_RANGE, NULL);
        old_length = idst->hdr.length;
        idst = safestr_resize(idst, idst->hdr.length + isrc->hdr.length);
        memmove(idst->str + pos + isrc->hdr.length, idst->str + pos, old_length - pos + 1);
        memcpy(idst->str + pos, isrc->str, isrc->hdr.length);
        if (!isrc->hdr.flags & SAFESTR_TRUSTED)
            idst->hdr.flags &= ~SAFESTR_TRUSTED;
        *dst = safestr_complete(iorig, idst);
    }
    XXL_ASSET_BLOCK_END;
}

safestr_t
safestr_do_join(safestr_t *s, safestr_t joiner, const char *file, unsigned int lineno)
{
    char        *outp;
    safestr_t   ret;
    u_int32_t   i, count, len, trust = SAFESTR_TRUSTED;
    isafestr_t  ijoiner, istr, *strs;

    XXL_ASSET_BLOCK_BEGIN
    {
        ijoiner = safestr_get(joiner, SAFESTR_GET_READONLY);
        trust  &= ijoiner->hdr.flags;

        for (count = 0;  s[count];  count++);
        if (!count)
            ret = safestr_do_alloc(0, SAFESTR_TRUSTED, file, lineno);
        else
        {
            strs = (isafestr_t *)safestr_malloc(sizeof(isafestr_t) * count, XXL_ASSET_TEMPORARY, __FILE__, __LINE__);

            for (i = len = 0;  i < count;  i++)
            {
                strs[i] = safestr_get(s[i], SAFESTR_GET_READONLY);
                len    += strs[i]->hdr.length;
                trust  &= strs[i]->hdr.flags;
            }
            len += (ijoiner->hdr.length * (count - 1));
            ret  = safestr_do_alloc(len, trust, file, lineno);
            istr = (isafestr_t)((char *)ret - sizeof(small_isafestr_t));
            istr->hdr.length = len;
            istr->str[len] = '\0';
            memcpy(istr->str, strs[0]->str, strs[0]->hdr.length);
            for (outp = istr->str + strs[0]->hdr.length, i = 1;  i < count;  i++)
            {
                memcpy(outp, ijoiner->str, ijoiner->hdr.length);
                memcpy(outp + ijoiner->hdr.length, strs[i]->str, strs[i]->hdr.length);
                outp += (ijoiner->hdr.length + strs[i]->hdr.length);
            }
        }
    }
    XXL_ASSET_BLOCK_END;

    return ret;
}

u_int32_t
safestr_length(safestr_t s)
{
    u_int32_t   length;
    isafestr_t  str;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_READONLY);
        length = str->hdr.length;
    }
    XXL_ASSET_BLOCK_END;

    return length;
}

void 
safestr_memzero(volatile void *str, u_int32_t len)
{
    volatile char   *buf;

    for (buf = (volatile char *)str;  len;  buf[--len] = 0);
}

safestr_t
safestr_reference(safestr_t s)
{
    isafestr_t  str;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_READONLY);
        INC_REF_COUNT(str);
    }
    XXL_ASSET_BLOCK_END;

    return s;
}

void
safestr_replace(safestr_t *str, safestr_t oldp, safestr_t newp)
{
    char        *c;
    u_int32_t   new_len, str_len, zero_len = 0;
    isafestr_t  inewp, ioldp, iorig, istr;

    XXL_ASSET_BLOCK_BEGIN
    {
        ioldp = safestr_get(oldp, SAFESTR_GET_READONLY);
        istr  = iorig = safestr_get(*str, SAFESTR_GET_WRITABLE);

        if (istr->hdr.length >= ioldp->hdr.length)
        {
            inewp   = safestr_get(newp, SAFESTR_GET_READONLY);
            new_len = 0;
            str_len = istr->hdr.length;
            for (c = istr->str;  c < istr->str + str_len - ioldp->hdr.length + 1;  c++)
            {
                if (memcmp(c, ioldp->str, ioldp->hdr.length))
                {
                    new_len++;
                    continue;
                }
                new_len += inewp->hdr.length;
                c += (ioldp->hdr.length) - 1;
            }
            if (c < istr->str + str_len)
                new_len += (ioldp->hdr.length - 1);

            /* Note that we cannot use safestr_resize() here if the new length
             * is less than the old length, because the string will be pre-
             * maturely zeroed, resulting in a loss of data.  Instead, we need
             * to do the zero after we move stuff around.
             */
            if (new_len > str_len)
                istr  = safestr_resize(istr, new_len);
            else
            {
                zero_len = istr->hdr.length - new_len;
                istr->hdr.length = new_len;
            }
            if (!(inewp->hdr.flags & SAFESTR_TRUSTED))
                istr->hdr.flags &= ~SAFESTR_TRUSTED;

            for (c = istr->str;  c < istr->str + str_len - ioldp->hdr.length + 1;  c++)
            {
                if (memcmp(c, ioldp->str, ioldp->hdr.length))
                    continue;
                memmove(c + inewp->hdr.length, c + ioldp->hdr.length,
                        (str_len - (c - istr->str) - ioldp->hdr.length) + 1);
                memcpy(c, inewp->str, inewp->hdr.length);
                if (inewp->hdr.length > ioldp->hdr.length)
                    str_len += (inewp->hdr.length - ioldp->hdr.length);
                else
                    str_len -= (ioldp->hdr.length - inewp->hdr.length);
                c += (inewp->hdr.length - 1);
            }
            if (zero_len > 0)
                safestr_memzero(istr->str + new_len, zero_len);
        }

        *str = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;
}

u_int32_t
safestr_search(safestr_t s, safestr_t sub, u_int32_t flags, ...)
{
    int             match;
    va_list         ap;
    u_int32_t       from = 0, fromchar = 0, i, n = 1, result, sublength = 0;
    isafestr_t      str, substr = NULL;
    unsigned char   findchar = 0, *map, *uptr, *usub;
    
    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_READONLY);
        if (!(flags & SAFESTR_FIND_CHARACTER)) {
            substr = safestr_get(sub, SAFESTR_GET_READONLY);
            sublength = substr->hdr.length;
        }

        va_start(ap, flags);
        if (flags & SAFESTR_FIND_FROMCHAR)
            fromchar = va_arg(ap, u_int32_t);
        if (flags & SAFESTR_FIND_FROMNTH)
            from = va_arg(ap, u_int32_t);
        if (flags & SAFESTR_FIND_NTH)
            n = va_arg(ap, u_int32_t);
        if (flags & SAFESTR_FIND_CHARACTER) {
            findchar = (unsigned char)va_arg(ap, int);
            sublength = 1;
        }
        va_end(ap);

        n += from;
        if (n < 1 || fromchar >= str->hdr.length || sublength > str->hdr.length)
            result = SAFESTR_ERROR_NOTFOUND;
        else
        {
            map  = ((flags & SAFESTR_FIND_NOMATCHCASE) ? safestr_casemap_lower : safestr_casemap_none);
            usub = ((flags & SAFESTR_FIND_CHARACTER) ? &findchar : (unsigned char *)substr->str);
            if (flags & SAFESTR_FIND_REVERSE)
            {
                for (uptr = (unsigned char *)str->str + str->hdr.length - fromchar - sublength;
                     uptr >= (unsigned char *)str->str;
                     uptr--)
                {
                    match = 1;
                    for (i = 0;  i < sublength;  i++)
                    {
                        if (map[uptr[i]] != map[usub[i]])
                        {
                            match = 0;
                            break;
                        }
                    }
                    if (match && !--n)
                        break;
                }
                result = (uptr < (unsigned char *)str->str ?
                          SAFESTR_ERROR_NOTFOUND : (u_int32_t)(uptr - (unsigned char *)str->str));
            }
            else
            {
                for (uptr = (unsigned char *)str->str + fromchar;
                     uptr <= (unsigned char *)str->str + str->hdr.length - sublength;
                     uptr++)
                {
                    match = 1;
                    for (i = 0;  i < sublength;  i++)
                    {
                        if (map[uptr[i]] != map[usub[i]])
                        {
                            match = 0;
                            break;
                        }
                    }
                    if (match && !--n)
                        break;
                }
                result = (uptr > (unsigned char *)str->str + str->hdr.length - sublength ?
                          SAFESTR_ERROR_NOTFOUND : (u_int32_t)(uptr - (unsigned char *)str->str));
            }
        }
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

void
safestr_setcharat(safestr_t s, u_int32_t i, char c, int trust)
{
    isafestr_t  str;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_WRITABLE);
        if (i >= str->hdr.length)
            XXL_THROW_ERROR(SAFESTR_ERROR_INDEX_OUT_OF_RANGE, NULL);
        if (!trust)
            str->hdr.flags &= ~SAFESTR_TRUSTED;
        str->str[i] = c;
    }
    XXL_ASSET_BLOCK_END;
}

void
safestr_setmemfns(safestr_malloc_t malloc_fn, safestr_realloc_t realloc_fn,
                  safestr_free_t free_fn)
{
    safestr_malloc_fn  = (malloc_fn  ? malloc_fn  : safestr_default_malloc);
    safestr_realloc_fn = (realloc_fn ? realloc_fn : safestr_default_realloc);
    safestr_free_fn    = (free_fn    ? free_fn    : safestr_default_free);
}

safestr_t
safestr_do_slice(safestr_t s, u_int32_t start, u_int32_t end, const char *file, unsigned int lineno)
{
    u_int32_t   length = 0, size;
    isafestr_t  new_str, str;

    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_get(s, SAFESTR_GET_READONLY);
        if (start < str->hdr.length)
        {
            length  = end - start;
            length  = SAFESTR_MIN(str->hdr.length - start, length);
        }

        size = SAFESTR_ROUND(sizeof(small_isafestr_t) + length + 1);
        new_str = (isafestr_t)safestr_malloc(size, XXL_ASSET_PERMANENT, file, lineno);
        new_str->hdr.size    = size - sizeof(small_isafestr_t) - 1;
        new_str->hdr.length  = length;
        new_str->hdr.flags   = (str->hdr.flags & SAFESTR_TRUSTED);
        new_str->hdr.refs    = 1;
        new_str->hdr.cookie  = get_cookie();
        new_str->str[length] = 0;
        memcpy(new_str->str, str->str + start, length);
    }
    XXL_ASSET_BLOCK_END;

    return (safestr_t)new_str->str;
}

safestr_t *
safestr_do_split(safestr_t s, safestr_t sub, const char *file, unsigned int lineno)
{
    char        *c, *cursor;
    safestr_t   *ret;
    u_int32_t   count;
    isafestr_t  istr, str, substr;

    XXL_ASSET_BLOCK_BEGIN
    {
        str    = safestr_get(s, SAFESTR_GET_READONLY);
        substr = safestr_get(sub, SAFESTR_GET_READONLY);

        if (!substr->hdr.length)
            ret = charlist(str, file, lineno);
        else if (substr->hdr.length > str->hdr.length)
        {
            ret = (safestr_t *)safestr_malloc(sizeof(safestr_t) * 2, XXL_ASSET_PROMOTE, file, lineno);
            ret[0] = safestr_do_clone(s, (str->hdr.flags & SAFESTR_TRUSTED), file, lineno);
            ret[1] = NULL;
        }
        else
        {
            /* Figure out how many array elements are needed */
            count = 1;
            for (c = str->str;  *(c + substr->hdr.length - 1);  c++)
                if (!memcmp(c, substr->str, substr->hdr.length))
                {
                    count++;
                    c += (substr->hdr.length - 1);
                }

            ret = (safestr_t *)safestr_malloc(sizeof(safestr_t) * (count + 1), XXL_ASSET_PROMOTE, file, lineno);

            count = 0;
            for (c = cursor = str->str;  *(c + substr->hdr.length - 1);  c++)
            {
                if (!memcmp(c, substr->str, substr->hdr.length))
                {
                    ret[count] = safestr_do_alloc(c - cursor, (str->hdr.flags & SAFESTR_TRUSTED), file, lineno);
                    istr = (isafestr_t)((char *)ret[count] - sizeof(small_isafestr_t));
                    XXL_ASSET_SAVE(istr, free_isafestr_asset, NULL, XXL_ASSET_PROMOTE);
                    istr->hdr.length = c - cursor;
                    memcpy(istr->str, cursor, istr->hdr.length);
                    istr->str[istr->hdr.length] = '\0';
                    cursor = c + substr->hdr.length;
                    c += (substr->hdr.length - 1);
                    count++;
                }
            }
            if (*c)
                c += (substr->hdr.length - 1);
            ret[count] = safestr_do_alloc(c - cursor, (str->hdr.flags & SAFESTR_TRUSTED), file, lineno);
            istr = (isafestr_t)((char *)ret[count] - sizeof(small_isafestr_t));
            XXL_ASSET_SAVE(istr, free_isafestr_asset, NULL, XXL_ASSET_PROMOTE);
            istr->hdr.length = c - cursor;
            memcpy(istr->str, cursor, istr->hdr.length);
            istr->str[istr->hdr.length] = '\0';
            ret[++count] = NULL;
        }
    }
    XXL_ASSET_BLOCK_END;

    return ret;
}

int
safestr_startswith(safestr_t str, safestr_t substr)
{
    int         match = 0;
    isafestr_t  istr, isubstr;

    XXL_ASSET_BLOCK_BEGIN
    {
        istr    = safestr_get(str, SAFESTR_GET_READONLY);
        isubstr = safestr_get(substr, SAFESTR_GET_READONLY);

        if (istr->hdr.length >= isubstr->hdr.length &&
            !memcmp(istr->str, isubstr->str, isubstr->hdr.length))
        {
            match = 1;
        }
    }
    XXL_ASSET_BLOCK_END;

    return match;
}

char *
safestr_do_strdup(char *s, const char *file, unsigned int lineno)
{
    char        *new_s;
    u_int32_t   len;

#ifndef WIN32
    len = strlen(s) + 1;
#else
    len = lstrlenA(s) + 1;
#endif
    new_s = (char *)safestr_malloc(len, XXL_ASSET_PERMANENT, file, lineno);
    memcpy(new_s, s, len);
    return new_s;
}

void
safestr_truncate(safestr_t *s, u_int32_t length)
{
    u_int32_t   old_length;
    isafestr_t  iorig, istr;

    XXL_ASSET_BLOCK_BEGIN
    {
        istr = iorig = safestr_get(*s, SAFESTR_GET_WRITABLE);
        old_length = istr->hdr.length;
        istr = safestr_resize(istr, length);

        if (length > old_length)
            memset(istr->str + old_length, ' ', length - old_length);
        *(istr->str + istr->hdr.length) = 0;
        *s = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;
}

/* ------------------------------------------------------------------------- */

void
safestr_trust(safestr_t s)
{
    XXL_ASSET_BLOCK_BEGIN
    {
        safestr_get(s, SAFESTR_GET_READONLY)->hdr.flags |= SAFESTR_TRUSTED;
    }
    XXL_ASSET_BLOCK_END;
}

void
safestr_untrust(safestr_t s)
{
    XXL_ASSET_BLOCK_BEGIN
    {
        safestr_get(s, SAFESTR_GET_READONLY)->hdr.flags &= ~SAFESTR_TRUSTED;
    }
    XXL_ASSET_BLOCK_END;
}

int
safestr_istrusted(safestr_t s)
{
    u_int32_t   trusted;

    XXL_ASSET_BLOCK_BEGIN
    {
        trusted = safestr_get(s, SAFESTR_GET_READONLY)->hdr.flags & SAFESTR_TRUSTED;
    }
    XXL_ASSET_BLOCK_END;

    return !!trusted;
}

void 
safestr_makereadonly(safestr_t s)
{
    XXL_ASSET_BLOCK_BEGIN
    {
        safestr_get(s, SAFESTR_GET_READONLY)->hdr.flags |= SAFESTR_IMMUTABLE;
    }
    XXL_ASSET_BLOCK_END;
}

void
safestr_makewritable(safestr_t s)
{
    XXL_ASSET_BLOCK_BEGIN
    {
        safestr_get(s, SAFESTR_GET_READONLY)->hdr.flags &= ~SAFESTR_IMMUTABLE;
    }
    XXL_ASSET_BLOCK_END;
}

int
safestr_isreadonly(safestr_t s)
{
    u_int32_t   immutable;

    XXL_ASSET_BLOCK_BEGIN
    {
        immutable = safestr_get(s, SAFESTR_GET_READONLY)->hdr.flags & SAFESTR_IMMUTABLE;
    }
    XXL_ASSET_BLOCK_END;

    return !!immutable;
}

/* ------------------------------------------------------------------------- */

void *
safestr_default_malloc(size_t nbytes, const char *file, unsigned int lineno)
{
    return malloc(nbytes);
}

void *
safestr_default_realloc(void *ptr, size_t nbytes, const char *file, unsigned int lineno)
{
    return realloc(ptr, nbytes);
}

void
safestr_default_free(void *ptr, const char *file, unsigned int lineno)
{
    free(ptr);
}


syntax highlighted by Code2HTML, v. 0.9.1