/*
** (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"

#define SAFESTR_FMTLIMIT_ARGCOUNT       256

#define SAFESTR_FMTARGTYPE_CHAR         1
#define SAFESTR_FMTARGTYPE_UCHAR        2
#define SAFESTR_FMTARGTYPE_SHORT        3
#define SAFESTR_FMTARGTYPE_USHORT       4
#define SAFESTR_FMTARGTYPE_INT          5
#define SAFESTR_FMTARGTYPE_UINT         6
#define SAFESTR_FMTARGTYPE_LONG         7
#define SAFESTR_FMTARGTYPE_ULONG        8
#define SAFESTR_FMTARGTYPE_LONG_LONG    9
#define SAFESTR_FMTARGTYPE_ULONG_LONG   10
#define SAFESTR_FMTARGTYPE_INTMAX_T     11
#define SAFESTR_FMTARGTYPE_UINTMAX_T    12
#define SAFESTR_FMTARGTYPE_SSIZE_T      13
#define SAFESTR_FMTARGTYPE_SIZE_T       14
#define SAFESTR_FMTARGTYPE_PTRDIFF_T    15
#define SAFESTR_FMTARGTYPE_UPTRDIFF_T   16
#define SAFESTR_FMTARGTYPE_SAFESTR_T    17
#define SAFESTR_FMTARGTYPE_VOID_PTR     18
#define SAFESTR_FMTARGTYPE_DOUBLE       19
#define SAFESTR_FMTARGTYPE_LONG_DOUBLE  20

#define SAFESTR_FMTFLAG_LEFT_JUSTIFY    0x00000001
#define SAFESTR_FMTFLAG_POSITIVE_SIGN   0x00000002
#define SAFESTR_FMTFLAG_POSITIVE_BLANK  0x00000004
#define SAFESTR_FMTFLAG_ALTERNATE_FORM  0x00000008
#define SAFESTR_FMTFLAG_ZERO_PAD        0x00000010
#define SAFESTR_FMTFLAG_WIDTH           0x00000020
#define SAFESTR_FMTFLAG_PRECISION       0x00000040
#define SAFESTR_FMTFLAG_OCTAL           0x00000080
#define SAFESTR_FMTFLAG_LOWER_HEX       0x00000100
#define SAFESTR_FMTFLAG_UPPER_HEX       0x00000200

#define SAFESTR_MODIFIER_CHAR           1
#define SAFESTR_MODIFIER_SHORT          2
#define SAFESTR_MODIFIER_LONG           3
#define SAFESTR_MODIFIER_LONG_LONG      4
#define SAFESTR_MODIFIER_INTMAX_T       5
#define SAFESTR_MODIFIER_SIZE_T         6
#define SAFESTR_MODIFIER_PTRDIFF_T      7
#define SAFESTR_MODIFIER_LONG_DOUBLE    8

#define DO_PADDING(len)                                                                     \
    do {                                                                                    \
        if ((u_int32_t)(len) < (u_int32_t)width && (flags & SAFESTR_FMTFLAG_WIDTH)) {       \
            if (padding_size < (u_int32_t)(width - (len))) {                                \
                if (!padding_size)                                                          \
                    padding = (char *)safestr_malloc(width - (len), XXL_ASSET_TEMPORARY,    \
                                                     __FILE__, __LINE__);                   \
                else                                                                        \
                    padding = (char *)safestr_realloc(padding, width - (len),               \
                                                      __FILE__, __LINE__);                  \
                memset(padding + padding_size, ' ', (width - (len)) - padding_size);        \
                padding_size = width - (len);                                               \
            }                                                                               \
            *nbytes += outfn(padding, width - (len), arg);                                  \
        }                                                                                   \
    } while (0)

#define PARSE_ARGNUM(arg)                                                                   \
    do {                                                                                    \
        (arg) = 0;                                                                          \
        if (*(c + 1) >= '0' && *(c + 1) <= '9') {                                           \
            tmpint = 0;                                                                     \
            for (c_arg = c + 1;  *c_arg;  c_arg++) {                                        \
                if (*c_arg >= '0' && *c_arg <= '9') {                                       \
                    tmpint = (tmpint * 10) + (*c_arg - '0');                                \
                    continue;                                                               \
                }                                                                           \
                if (*c_arg == '$') {                                                        \
                    if (tmpint < 1 || tmpint > SAFESTR_FMTLIMIT_ARGCOUNT)                   \
                        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_ARG, NULL);            \
                    (arg) = (int)tmpint;                                                    \
                    c = c_arg;                                                              \
                    break;                                                                  \
                }                                                                           \
                break;                                                                      \
            }                                                                               \
        }                                                                                   \
    } while (0)

#define SET_ARGLIST_TYPE(arg, _type)                                                        \
    do {                                                                                    \
        if ((arg) != -1) {                                                                  \
            if (!(arg)) {                                                                   \
                if (current_arg == SAFESTR_FMTLIMIT_ARGCOUNT)                               \
                    XXL_THROW_ERROR(SAFESTR_ERROR_TOO_MANY_FORMAT_ARGS, NULL);              \
                (arg) = current_arg++;                                                      \
            }                                                                               \
            if ((arg) < max_arg && arglist->list[(arg)].type &&                             \
                arglist->list[(arg)].type != (_type))                                       \
            {                                                                               \
                XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_ARG, NULL);                    \
            }                                                                               \
            arglist->list[(arg)].type = (_type);                                            \
            if ((arg) >= max_arg) max_arg = (arg) + 1;                                      \
        }                                                                                   \
    } while (0)

#define CHECK_OR_SET_ARGLIST()                                                              \
    do {                                                                                    \
        if (idx && !arglist->scanned)                                                       \
            scan_format_string(ifmt, arglist);                                              \
        else if (!idx) {                                                                    \
            if (arglist->current == SAFESTR_FMTLIMIT_ARGCOUNT)                              \
                XXL_THROW_ERROR(SAFESTR_ERROR_TOO_MANY_FORMAT_ARGS, NULL);                  \
            idx = arglist->current++;                                                       \
            if (idx == arglist->max) {                                                      \
                arglist->list[idx].type = type;                                             \
                arglist->max++;                                                             \
                load_argument(idx, arglist);                                                \
            }                                                                               \
        }                                                                                   \
        if (idx >= arglist->max ||                                                          \
            (arglist->list[idx].type && arglist->list[idx].type != type))                   \
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_ARG, NULL);                        \
    } while (0)

typedef struct fmtarg_t
{
    u_int32_t   type;
    union {
        intmax_t    fa_int;
        uintmax_t   fa_uint;
        safestr_t   fa_string;
        void *      fa_voidptr;
        double      fa_double;
#ifdef HAVE_LONG_DOUBLE
        long double fa_long_double;
#endif
    }           arg;
} fmtarg_t;

typedef struct arglist_t
{
    int         current;
    int         max;
    int         scanned;
    va_list     ap;
    fmtarg_t    list[SAFESTR_FMTLIMIT_ARGCOUNT];
} arglist_t;

typedef u_int32_t (*output_fn)(char *, u_int32_t, void *);

static void        load_argument          (int, arglist_t *);
static void        scan_format_string     (isafestr_t, arglist_t *);
static intmax_t    get_arglist_int        (isafestr_t, arglist_t *, int, u_int32_t);
static uintmax_t   get_arglist_uint       (isafestr_t, arglist_t *, int, u_int32_t);
static safestr_t   get_arglist_string     (isafestr_t, arglist_t *, int, u_int32_t);
static void *      get_arglist_voidptr    (isafestr_t, arglist_t *, int, u_int32_t);
static double      get_arglist_double     (isafestr_t, arglist_t *, int, u_int32_t);
#ifdef HAVE_LONG_DOUBLE
static long double get_arglist_long_double(isafestr_t, arglist_t *, int, u_int32_t);
#endif
static u_int32_t   output_to_isafestr     (char *, u_int32_t, void *);
static u_int32_t   output_to_stdout       (char *, u_int32_t, void *);
static u_int32_t   output_to_stream       (char *, u_int32_t, void *);
static char *      format_signed_int      (intmax_t, u_int32_t, int, int, int *);
static char *      format_unsigned_int    (uintmax_t, u_int32_t, int, int, int *);
static int         parse_format_string    (output_fn, void *, isafestr_t, va_list, u_int32_t *);

static void
load_argument(int idx, arglist_t *arglist)
{
    switch (arglist->list[idx].type)
    {
        case SAFESTR_FMTARGTYPE_CHAR:
            arglist->list[idx].arg.fa_int = (intmax_t)(char)va_arg(arglist->ap, int);
            break;
        case SAFESTR_FMTARGTYPE_UCHAR:
            arglist->list[idx].arg.fa_uint = (uintmax_t)(unsigned char)va_arg(arglist->ap, unsigned int);
            break;
        case SAFESTR_FMTARGTYPE_SHORT:
            arglist->list[idx].arg.fa_int = (intmax_t)(short)va_arg(arglist->ap, int);
            break;
        case SAFESTR_FMTARGTYPE_USHORT:
            arglist->list[idx].arg.fa_uint = (uintmax_t)(unsigned short)va_arg(arglist->ap, int);
            break;
        case SAFESTR_FMTARGTYPE_INT:
            arglist->list[idx].arg.fa_int = (intmax_t)va_arg(arglist->ap, int);
            break;
        case SAFESTR_FMTARGTYPE_UINT:
            arglist->list[idx].arg.fa_uint = (uintmax_t)va_arg(arglist->ap, unsigned int);
            break;
        case SAFESTR_FMTARGTYPE_LONG:
            arglist->list[idx].arg.fa_int = (intmax_t)va_arg(arglist->ap, long);
            break;
        case SAFESTR_FMTARGTYPE_ULONG:
            arglist->list[idx].arg.fa_uint = (uintmax_t)va_arg(arglist->ap, unsigned long);
            break;
        case SAFESTR_FMTARGTYPE_LONG_LONG:
            arglist->list[idx].arg.fa_int = (intmax_t)va_arg(arglist->ap, int64_t);
            break;
        case SAFESTR_FMTARGTYPE_ULONG_LONG:
            arglist->list[idx].arg.fa_uint = (uintmax_t)va_arg(arglist->ap, u_int64_t);
            break;
        case SAFESTR_FMTARGTYPE_INTMAX_T:
            arglist->list[idx].arg.fa_int = va_arg(arglist->ap, intmax_t);
            break;
        case SAFESTR_FMTARGTYPE_UINTMAX_T:
            arglist->list[idx].arg.fa_uint = va_arg(arglist->ap, uintmax_t);
            break;
        case SAFESTR_FMTARGTYPE_SSIZE_T:
            arglist->list[idx].arg.fa_int = (intmax_t)va_arg(arglist->ap, ssize_t);
            break;
        case SAFESTR_FMTARGTYPE_SIZE_T:
            arglist->list[idx].arg.fa_uint = (uintmax_t)va_arg(arglist->ap, size_t);
            break;
        case SAFESTR_FMTARGTYPE_PTRDIFF_T:
            arglist->list[idx].arg.fa_int = (intmax_t)va_arg(arglist->ap, ptrdiff_t);
            break;
        case SAFESTR_FMTARGTYPE_UPTRDIFF_T:
            arglist->list[idx].arg.fa_uint = (uintmax_t)va_arg(arglist->ap, uptrdiff_t);
            break;
        case SAFESTR_FMTARGTYPE_SAFESTR_T:
            arglist->list[idx].arg.fa_string = va_arg(arglist->ap, safestr_t);
            break;
        case SAFESTR_FMTARGTYPE_VOID_PTR:
            arglist->list[idx].arg.fa_voidptr = va_arg(arglist->ap, void *);
            break;
        case SAFESTR_FMTARGTYPE_DOUBLE:
            arglist->list[idx].arg.fa_double = va_arg(arglist->ap, double);
            break;
#ifdef HAVE_LONG_DOUBLE
        case SAFESTR_FMTARGTYPE_LONG_DOUBLE:
            arglist->list[idx].arg.fa_long_double = va_arg(arglist->ap, long double);
            break;
#endif
        default:
            /* Grr, we have no idea what type this argument is.  Do we
             * throw an exception?  Do we assume an int?  Yet another
             * example of how broken printf is ...
             */
            arglist->list[idx].arg.fa_int = (intmax_t)va_arg(arglist->ap, int);
            break;
    }
}

static void
scan_format_string(isafestr_t ifmt, arglist_t *arglist)
{
    int         current_arg, done, format_arg, max_arg, modifier, precision_arg,
                x, width_arg;
    char        *c, *c_arg;
    va_list     ap;
    intmax_t    tmpint;
    u_int32_t   type;

    current_arg = 0;
    max_arg = arglist->max;
    for (c = ifmt->str;  c < ifmt->str + ifmt->hdr.length;  c++)
    {
        while (*c != '%' && c < ifmt->str + ifmt->hdr.length)
            c++;
        if (c == ifmt->str + ifmt->hdr.length)
            break;
        if (*(c + 1) == '%')
            continue;

        precision_arg = width_arg = -1;
        PARSE_ARGNUM(format_arg);

        for (done = 0, c++;  *c && !done;  c++)
        {
            switch (*c)
            {
                case '-':
                case '+':
                case ' ':
                case '#':
                case '0':
                    break;
                default:
                    done = 1;
                    c--;
                    break;
            }
        }

        if (*c == '*')
        {
            PARSE_ARGNUM(width_arg);
            c++;
        }
        else
        {
            while (*c >= '0' && *c <= '9')
                c++;
        }

        if (*c == '.')
        {
            c++;
            if (*c == '*')
            {
                PARSE_ARGNUM(precision_arg);
                c++;
            }
            else
            {
                while (*c >= '0' && *c <= '9')
                    c++;
            }
        }

        switch (*c)
        {
            case 'h':
                if (*++c != 'h')
                    modifier = SAFESTR_MODIFIER_SHORT;
                else
                {
                    c++;
                    modifier = SAFESTR_MODIFIER_CHAR;
                }
                break;
            case 'l':
                if (*++c != 'l')
                    modifier = SAFESTR_MODIFIER_LONG;
                else
                {
                    c++;
                    modifier = SAFESTR_MODIFIER_LONG_LONG;
                }
                break;
            case 'j':
                c++;
                modifier = SAFESTR_MODIFIER_INTMAX_T;
                break;
            case 'z':
                c++;
                modifier = SAFESTR_MODIFIER_SIZE_T;
                break;
            case 't':
                c++;
                modifier = SAFESTR_MODIFIER_PTRDIFF_T;
                break;
            case 'L':
                c++;
                modifier = SAFESTR_MODIFIER_LONG_DOUBLE;
                break;
            default:
                modifier = 0;
                break;
        }

        type = 0;
        switch (*c)
        {
            case 'd': case 'i':
                switch (modifier)
                {
                    case SAFESTR_MODIFIER_CHAR:
                        type = SAFESTR_FMTARGTYPE_CHAR;
                        break;
                    case SAFESTR_MODIFIER_SHORT:
                        type = SAFESTR_FMTARGTYPE_SHORT;
                        break;
                    case SAFESTR_MODIFIER_LONG:
                        type = SAFESTR_FMTARGTYPE_LONG;
                        break;
                    case SAFESTR_MODIFIER_LONG_LONG:
                        type = SAFESTR_FMTARGTYPE_LONG_LONG;
                        break;
                    case SAFESTR_MODIFIER_INTMAX_T:
                        type = SAFESTR_FMTARGTYPE_INTMAX_T;
                        break;
                    case SAFESTR_MODIFIER_SIZE_T:
                        type = SAFESTR_FMTARGTYPE_SSIZE_T;
                        break;
                    case SAFESTR_MODIFIER_PTRDIFF_T:
                        type = SAFESTR_FMTARGTYPE_PTRDIFF_T;
                        break;
                    case SAFESTR_MODIFIER_LONG_DOUBLE:
                        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                        break;
                    default:
                        type = SAFESTR_FMTARGTYPE_INT;
                        break;
                }
                break;
            case 'o': case 'u': case 'x': case 'X':
                switch (modifier)
                {
                    case SAFESTR_MODIFIER_CHAR:
                        type = SAFESTR_FMTARGTYPE_UCHAR;
                        break;
                    case SAFESTR_MODIFIER_SHORT:
                        type = SAFESTR_FMTARGTYPE_USHORT;
                        break;
                    case SAFESTR_MODIFIER_LONG:
                        type = SAFESTR_FMTARGTYPE_ULONG;
                        break;
                    case SAFESTR_MODIFIER_LONG_LONG:
                        type = SAFESTR_FMTARGTYPE_ULONG_LONG;
                        break;
                    case SAFESTR_MODIFIER_INTMAX_T:
                        type = SAFESTR_FMTARGTYPE_UINTMAX_T;
                        break;
                    case SAFESTR_MODIFIER_SIZE_T:
                        type = SAFESTR_FMTARGTYPE_SIZE_T;
                        break;
                    case SAFESTR_MODIFIER_PTRDIFF_T:
                        type = SAFESTR_FMTARGTYPE_UPTRDIFF_T;
                        break;
                    case SAFESTR_MODIFIER_LONG_DOUBLE:
                        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                        break;
                    default:
                        type = SAFESTR_FMTARGTYPE_UINT;
                        break;
                }
                break;
            case 'e': case 'E':
            case 'f': case 'F':
            case 'g': case 'G':
                if (!modifier)
                    type = SAFESTR_FMTARGTYPE_DOUBLE;
                else if (modifier == SAFESTR_MODIFIER_LONG_DOUBLE)
#ifdef HAVE_LONG_DOUBLE
                    type = SAFESTR_FMTARGTYPE_LONG_DOUBLE;
#else
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_ARG, NULL);
#endif
                else
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                break;
            case 'c':
                if (modifier)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                type = SAFESTR_FMTARGTYPE_CHAR;
                break;
            case 's':
                if (modifier)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                type = SAFESTR_FMTARGTYPE_SAFESTR_T;
                break;
            case 'p':
                if (modifier)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                type = SAFESTR_FMTARGTYPE_VOID_PTR;
                break;
            case 'n':
                XXL_THROW_ERROR(SAFESTR_ERROR_ILLEGAL_PERCENT_N, NULL);
                break;
            case '%':
            default:
                XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                break;
        }

        SET_ARGLIST_TYPE(width_arg, SAFESTR_FMTARGTYPE_INT);
        SET_ARGLIST_TYPE(precision_arg, SAFESTR_FMTARGTYPE_INT);
        SET_ARGLIST_TYPE(format_arg, type);
    }

    va_copy(ap, arglist->ap);
    for (x = arglist->current;  x < max_arg;  x++)
        load_argument(x, arglist);
    va_copy(arglist->ap, ap);

    arglist->max     = max_arg;
    arglist->scanned = 1;
}

static intmax_t
get_arglist_int(isafestr_t ifmt, arglist_t *arglist, int idx, u_int32_t type)
{
    CHECK_OR_SET_ARGLIST();
    return arglist->list[idx].arg.fa_int;
}

static uintmax_t
get_arglist_uint(isafestr_t ifmt, arglist_t *arglist, int idx, u_int32_t type)
{
    CHECK_OR_SET_ARGLIST();
    return arglist->list[idx].arg.fa_uint;
}

static safestr_t
get_arglist_string(isafestr_t ifmt, arglist_t *arglist, int idx, u_int32_t type)
{
    CHECK_OR_SET_ARGLIST();
    return arglist->list[idx].arg.fa_string;
}

static void *
get_arglist_voidptr(isafestr_t ifmt, arglist_t *arglist, int idx, u_int32_t type)
{
    CHECK_OR_SET_ARGLIST();
    return arglist->list[idx].arg.fa_voidptr;
}

static double
get_arglist_double(isafestr_t ifmt, arglist_t *arglist, int idx, u_int32_t type)
{
    CHECK_OR_SET_ARGLIST();
    return arglist->list[idx].arg.fa_double;
}

#ifdef HAVE_LONG_DOUBLE
static long double
get_arglist_long_double(isafestr_t ifmt, arglist_t *arglist, int idx, u_int32_t type)
{
    CHECK_OR_SET_ARGLIST();
    return arglist->list[idx].arg.fa_long_double;
}
#endif

static u_int32_t
output_to_isafestr(char *data, u_int32_t nbytes, void *arg)
{
    void        *old_str;
    isafestr_t  *istr = (isafestr_t *)arg;

    if ((*istr)->hdr.length + nbytes <= (*istr)->hdr.size)
        (*istr)->hdr.length += nbytes;
    else
    {
        old_str = (*istr)->str;
        (*istr) = safestr_resize((*istr), (*istr)->hdr.length + nbytes);
        XXL_ASSET_UPDATE(old_str, (*istr)->str);
    }
    memcpy((*istr)->str + (*istr)->hdr.length - nbytes, data, nbytes);
    return nbytes;
}

static u_int32_t
output_to_stdout(char *data, u_int32_t nbytes, void *arg)
{
    if (nbytes && fwrite(data, nbytes, 1, stdout) != 1)
        XXL_THROW_ERROR(errno, NULL);
    return nbytes;
}

static u_int32_t
output_to_stream(char *data, u_int32_t nbytes, void *arg)
{
    if (nbytes && fwrite(data, nbytes, 1, (FILE *)arg) != 1)
        XXL_THROW_ERROR(errno, NULL);
    return nbytes;
}

static char *
format_signed_int(intmax_t value, u_int32_t flags, int width, int precision, int *result_length)
{
    int         length;
    char        *result;
    intmax_t    tmp;
    static char *digits = "0123456789";

    length = (value ? 0 : 1);
    for (tmp = value;  tmp;  tmp /= 10)
        length++;
    if (length < precision)
        length += (precision - length);
    if (value < 0 || (flags & (SAFESTR_FMTFLAG_POSITIVE_SIGN | SAFESTR_FMTFLAG_POSITIVE_BLANK)))
        length++;
    if ((flags & SAFESTR_FMTFLAG_WIDTH) && (flags & SAFESTR_FMTFLAG_ZERO_PAD) && length < width)
        length = width;
    *result_length = length;

    result = (char *)safestr_malloc(length + 1, XXL_ASSET_TEMPORARY, __FILE__, __LINE__);
    result[length] = '\0';
    if (!value)
        result[--length] = '0';
    for (tmp = value;  tmp;  tmp /= 10)
        result[--length] = digits[(int)SAFESTR_ABS(tmp % 10)];
    while (length)
        result[--length] = '0';
    if (value < 0)
        result[0] = '-';
    else if (flags & SAFESTR_FMTFLAG_POSITIVE_SIGN)
        result[0] = '+';
    else if (flags & SAFESTR_FMTFLAG_POSITIVE_BLANK)
        result[0] = ' ';
    return result;
}

static char *
format_unsigned_int(uintmax_t value, u_int32_t flags, int width, int precision, int *result_length)
{
    int         base, length;
    char        *result, *set;
    uintmax_t   tmp;
    static char *lower_set = "0123456789abcdef";
    static char *upper_set = "0123456789ABCDEF";

    if (flags & SAFESTR_FMTFLAG_OCTAL)          base = 8;
    else if (flags & SAFESTR_FMTFLAG_LOWER_HEX) base = 16;
    else if (flags & SAFESTR_FMTFLAG_UPPER_HEX) base = 16;
    else                                        base = 10;
    set = ((flags & SAFESTR_FMTFLAG_LOWER_HEX) ? lower_set : upper_set);

    length = (value ? 0 : 1);
    for (tmp = value;  tmp;  tmp /= base)
        length++;
    if ((flags & SAFESTR_FMTFLAG_ALTERNATE_FORM) && (flags & SAFESTR_FMTFLAG_OCTAL))
        if (length >= precision && value > 0)
            precision = length + 1;
    if (length < precision)
        length += (precision - length);
    if ((flags & SAFESTR_FMTFLAG_ALTERNATE_FORM) && (flags & (SAFESTR_FMTFLAG_LOWER_HEX | SAFESTR_FMTFLAG_UPPER_HEX)))
        length += 2;
    if ((flags & SAFESTR_FMTFLAG_WIDTH) && (flags & SAFESTR_FMTFLAG_ZERO_PAD) && length < width)
        length = width;
    *result_length = length;

    result = (char *)safestr_malloc(length + 1, XXL_ASSET_TEMPORARY, __FILE__, __LINE__);
    result[length] = '\0';
    if (!value)
        result[--length] = '0';
    for (tmp = value;  tmp;  tmp /= base)
        result[--length] = set[(tmp % base)];
    while (length)
        result[--length] = '0';

    if (flags & SAFESTR_FMTFLAG_ALTERNATE_FORM)
    {
        if (flags & SAFESTR_FMTFLAG_LOWER_HEX)      result[1] = 'x';
        else if (flags & SAFESTR_FMTFLAG_UPPER_HEX) result[1] = 'X';
    }

    return result;
}

static int
parse_format_string(output_fn outfn, void *arg, isafestr_t ifmt,
                    va_list ap, u_int32_t *nbytes)
{
    int         argnum, done, length, modifier, trusted, width, precision, tmparg;
    char        *c, *c_arg, cs_c, *cs_fp, *padding, *result, *start;
    intmax_t    cs_d, tmpint;
    arglist_t   arglist;
    safestr_t   cs_s;
    u_int32_t   flags, padding_size, str_len;
    uintmax_t   cs_u;
    isafestr_t  ics_s;

    arglist.current = 0;
    arglist.max     = 0;
    arglist.scanned = 0;
    va_copy(arglist.ap, ap);
    memset(arglist.list, 0, sizeof(arglist.list));

    cs_d = 0;
    cs_u = 0;
    padding = NULL;
    *nbytes = padding_size = 0;
    arglist.current = arglist.max = arglist.scanned = 0;
    va_copy(arglist.ap, ap);
    trusted = (ifmt->hdr.flags & SAFESTR_TRUSTED) == SAFESTR_TRUSTED;

    for (c = start = ifmt->str;  c < ifmt->str + ifmt->hdr.length;  start = ++c)
    {
        while (*c != '%' && c < ifmt->str + ifmt->hdr.length)
            c++;
        if (c == ifmt->str + ifmt->hdr.length)
            break;
        if (c > start)
            *nbytes += outfn(start, c - start, arg);

        start = c;  /* save this for use with a, A, e, E, f, F, g, and G */
        if (*(c + 1) == '%')
        {
            *nbytes += outfn(++c, 1, arg);
            continue;
        }

        /* check for nn$ argument specifier */
        PARSE_ARGNUM(argnum);

        /* flags */
        done = width = precision = 0;
        flags = 0;
        for (c++;  *c && !done;  c++)
        {
            switch (*c)
            {
                case '-':
                    flags |= SAFESTR_FMTFLAG_LEFT_JUSTIFY;
                    break;
                case '+':
                    flags |= SAFESTR_FMTFLAG_POSITIVE_SIGN;
                    break;
                case ' ':
                    flags |= SAFESTR_FMTFLAG_POSITIVE_BLANK;
                    break;
                case '#':
                    flags |= SAFESTR_FMTFLAG_ALTERNATE_FORM;
                    break;
                case '0':
                    flags |= SAFESTR_FMTFLAG_ZERO_PAD;
                    break;
                default:
                    done = 1;
                    c--;
                    break;
            }
        }

        /* field width */
        if (*c == '*')
        {
            PARSE_ARGNUM(tmparg);
            flags |= SAFESTR_FMTFLAG_WIDTH;
            width = (int)get_arglist_int(ifmt, &arglist, tmparg, SAFESTR_FMTARGTYPE_INT);
            if (width < 0)
                flags |= SAFESTR_FMTFLAG_LEFT_JUSTIFY;
            c++;
        }
        else if (*c >= '0' && *c <= '9')
        {
            flags |= SAFESTR_FMTFLAG_WIDTH;
            for (tmpint = 0;  *c >= '0' && *c <= '9';  c++)
                tmpint = (tmpint * 10) + (*c - '0');
            if (tmpint > INT_MAX)
                XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
            width = (int)tmpint;
        }

        /* precision */
        if (*c == '.')
        {
            precision = 0;
            flags |= SAFESTR_FMTFLAG_PRECISION;
            c++;
            if (*c == '*')
            {
                PARSE_ARGNUM(tmparg);
                precision = (int)get_arglist_int(ifmt, &arglist, tmparg, SAFESTR_FMTARGTYPE_INT);
                if (precision < 0)
                    flags &= ~SAFESTR_FMTFLAG_PRECISION;
                c++;
            }
            else if (*c >= '0' && *c <= '9')
            {
                for (tmpint = 0;  *c >= '0' && *c <= '9';  c++)
                    tmpint = (tmpint * 10) + (*c - '0');
                if (tmpint > INT_MAX)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                precision = (int)tmpint;
            }
        }
        if ((flags & SAFESTR_FMTFLAG_PRECISION) && (flags & SAFESTR_FMTFLAG_ZERO_PAD))
            flags &= ~SAFESTR_FMTFLAG_ZERO_PAD;
        if ((flags & SAFESTR_FMTFLAG_POSITIVE_SIGN) && (flags & SAFESTR_FMTFLAG_POSITIVE_BLANK))
            flags &= ~SAFESTR_FMTFLAG_POSITIVE_BLANK;
        if ((flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY) && (flags & SAFESTR_FMTFLAG_ZERO_PAD))
            flags &= ~SAFESTR_FMTFLAG_ZERO_PAD;

        /* length modifier */
        modifier = 0;
        switch (*c)
        {
            case 'h':
                if (*++c != 'h')
                    modifier = SAFESTR_MODIFIER_SHORT;
                else
                {
                    c++;
                    modifier = SAFESTR_MODIFIER_CHAR;
                }
                break;
            case 'l':
                if (*++c != 'l')
                    modifier = SAFESTR_MODIFIER_LONG;
                else
                {
                    c++;
                    modifier = SAFESTR_MODIFIER_LONG_LONG;
                }
                break;
            case 'j':
                c++;
                modifier = SAFESTR_MODIFIER_INTMAX_T;
                break;
            case 'z':
                c++;
                modifier = SAFESTR_MODIFIER_SIZE_T;
                break;
            case 't':
                c++;
                modifier = SAFESTR_MODIFIER_PTRDIFF_T;
                break;
            case 'L':
                c++;
                modifier = SAFESTR_MODIFIER_LONG_DOUBLE;
                break;
        }

        /* conversion specifier */
        switch (*c)
        {
            case 'd': case 'i':
                if (flags & SAFESTR_FMTFLAG_ALTERNATE_FORM)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                if ((flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY) && (flags & SAFESTR_FMTFLAG_ZERO_PAD))
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                if (!(flags & SAFESTR_FMTFLAG_PRECISION))
                    precision = 1;
                switch (modifier)
                {
                    case SAFESTR_MODIFIER_CHAR:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_CHAR);
                        break;
                    case SAFESTR_MODIFIER_SHORT:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_SHORT);
                        break;
                    case SAFESTR_MODIFIER_LONG:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_LONG);
                        break;
                    case SAFESTR_MODIFIER_LONG_LONG:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_ULONG_LONG);
                        break;
                    case SAFESTR_MODIFIER_INTMAX_T:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_INTMAX_T);
                        break;
                    case SAFESTR_MODIFIER_SIZE_T:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_SSIZE_T);
                        break;
                    case SAFESTR_MODIFIER_PTRDIFF_T:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_PTRDIFF_T);
                        break;
                    case SAFESTR_MODIFIER_LONG_DOUBLE:
                        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                        break;
                    default:
                        cs_d = get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_INT);
                        break;
                }
                result = format_signed_int(cs_d, flags, width, precision, &length);
                if (!(flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY))
                    DO_PADDING(length);
                *nbytes += outfn(result, length, arg);
                if (flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY)
                    DO_PADDING(length);
                break;
            case 'o': case 'u': case 'x': case 'X':
                if (*c == 'u' && (flags & SAFESTR_FMTFLAG_ALTERNATE_FORM))
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                if (flags & (SAFESTR_FMTFLAG_POSITIVE_SIGN | SAFESTR_FMTFLAG_POSITIVE_BLANK))
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                if (!(flags & SAFESTR_FMTFLAG_PRECISION))
                    precision = 1;
                switch (modifier)
                {
                    case SAFESTR_MODIFIER_CHAR:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_UCHAR);
                        break;
                    case SAFESTR_MODIFIER_SHORT:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_USHORT);
                        break;
                    case SAFESTR_MODIFIER_LONG:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_ULONG);
                        break;
                    case SAFESTR_MODIFIER_LONG_LONG:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_ULONG_LONG);
                        break;
                    case SAFESTR_MODIFIER_INTMAX_T:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_UINTMAX_T);
                        break;
                    case SAFESTR_MODIFIER_SIZE_T:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_SIZE_T);
                        break;
                    case SAFESTR_MODIFIER_PTRDIFF_T:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_PTRDIFF_T);
                        break;
                    case SAFESTR_MODIFIER_LONG_DOUBLE:
                        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                        break;
                    default:
                        cs_u = get_arglist_uint(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_UINT);
                        break;
                }
                if (*c == 'o') flags |= SAFESTR_FMTFLAG_OCTAL;
                if (*c == 'x') flags |= SAFESTR_FMTFLAG_LOWER_HEX;
                if (*c == 'X') flags |= SAFESTR_FMTFLAG_UPPER_HEX;
                result = format_unsigned_int(cs_u, flags, width, precision, &length);
                if (!(flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY))
                    DO_PADDING(length);
                *nbytes += outfn(result, length, arg);
                if (flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY)
                    DO_PADDING(length);
                break;
            case 'e': case 'E':
            case 'f': case 'F':
            case 'g': case 'G':
                if (modifier && modifier != SAFESTR_MODIFIER_LONG_DOUBLE)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                result = (char *)safestr_malloc(c - start + 2, XXL_ASSET_TEMPORARY, __FILE__, __LINE__);
                memcpy(result, start, c - start + 1);
                result[c - start + 1] = '\0';
                if (!(flags & SAFESTR_FMTFLAG_PRECISION))
                    precision = 6;
                length = precision + width + 64;    /* 64 = fudge */
                cs_fp = (char *)safestr_malloc(length + 1, XXL_ASSET_TEMPORARY, __FILE__, __LINE__);
                if (modifier == SAFESTR_MODIFIER_LONG_DOUBLE)
                {
#ifdef HAVE_LONG_DOUBLE
                    length = sprintf(cs_fp, result,
                                     get_arglist_long_double(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_LONG_DOUBLE));
#else
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_ARG, NULL);
#endif
                }
                else
                {
                    length = sprintf(cs_fp, result,
                                     get_arglist_double(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_DOUBLE));
                }
                *nbytes += outfn(cs_fp, length, arg);
                break;
            case 'c':
                /* Since wide characters are not supported, throw an error for
                 * any kind of length modifier even though C99 allows 'l' for 'c'
                 */
                if (modifier)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                if (flags & ~(SAFESTR_FMTFLAG_LEFT_JUSTIFY | SAFESTR_FMTFLAG_WIDTH))
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                cs_c = (char)get_arglist_int(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_CHAR);
                if (!(flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY))
                    DO_PADDING(1);
                *nbytes += outfn(&cs_c, 1, arg);
                if (flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY)
                    DO_PADDING(1);
                break;
            case 's':
                /* Since wide characters are not supported, throw an error for
                 * any kind of length modifier even though C99 allows 'l' for 's'
                 */
                if (modifier)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
#if 0
                if (!(cs_s = get_arglist_string(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_SAFESTR_T)))
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_ARG, NULL);
#else
                if (!(cs_s = get_arglist_string(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_SAFESTR_T)))
                    cs_s = SAFESTR_TEMP_TRUSTED("(null)");
                else
                    safestr_reference(cs_s);
#endif
                ics_s = (isafestr_t)((char *)cs_s - sizeof(small_isafestr_t));
                if (ics_s->hdr.cookie != safestr_cookie)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_ARG, NULL);
                str_len = ics_s->hdr.length;
                if (flags & SAFESTR_FMTFLAG_PRECISION && (u_int32_t)precision < ics_s->hdr.length)
                    str_len = precision;
                if (!(flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY))
                    DO_PADDING(str_len);
                *nbytes += outfn(ics_s->str, str_len, arg);
                safestr_release(cs_s);
                if (flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY)
                    DO_PADDING(str_len);
                break;
            case 'p':
                if (modifier)
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                if (flags & ~(SAFESTR_FMTFLAG_LEFT_JUSTIFY | SAFESTR_FMTFLAG_WIDTH | SAFESTR_FMTFLAG_PRECISION))
                    XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                flags |= (SAFESTR_FMTFLAG_ALTERNATE_FORM | SAFESTR_FMTFLAG_LOWER_HEX);
                cs_u = (uintmax_t)(uintptr_t)get_arglist_voidptr(ifmt, &arglist, argnum, SAFESTR_FMTARGTYPE_VOID_PTR);
                if (!(flags & SAFESTR_FMTFLAG_PRECISION))
                    precision = sizeof(void *) * 2;
                else
                    precision = SAFESTR_MAX((int)(sizeof(void *) * 2), precision);
                result = format_unsigned_int(cs_u, flags, width, precision, &length);
                if (!(flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY))
                    DO_PADDING(length);
                *nbytes += outfn(result, length, arg);
                if (flags & SAFESTR_FMTFLAG_LEFT_JUSTIFY)
                    DO_PADDING(length);
                break;
            case 'n':
                XXL_THROW_ERROR(SAFESTR_ERROR_ILLEGAL_PERCENT_N, NULL);
                break;
            case '%':
            default:
                XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT_STRING, NULL);
                break;
        }
    }

    if (c > start)
        *nbytes += outfn(start, c - start, arg);

    return trusted;
}

u_int32_t
safestr_asprintf(safestr_t *str, safestr_t fmt, ...)
{
    int         trusted;
    va_list     ap;
    u_int32_t   result;
    isafestr_t  ifmt, iorig, istr;

    XXL_ASSET_BLOCK_BEGIN
    {
        ifmt = safestr_get(fmt, SAFESTR_GET_READONLY);
        *str = safestr_alloc(ifmt->hdr.length, SAFESTR_ASSET_PROMOTE);
        iorig = istr = safestr_get(*str, SAFESTR_GET_WRITABLE);

        va_start(ap, fmt);
        trusted = parse_format_string(output_to_isafestr, (void *)&istr,
                                      ifmt, ap, &result);
        va_end(ap);
        istr->str[istr->hdr.length] = '\0';

        if (trusted) istr->hdr.flags |= SAFESTR_TRUSTED;
        else         istr->hdr.flags &= ~SAFESTR_TRUSTED;
        *str = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;
  
    return result;
}

u_int32_t
safestr_fprintf(FILE *stream, safestr_t fmt, ...)
{
    va_list     ap;
    u_int32_t   result;
    isafestr_t  ifmt;

    XXL_ASSET_BLOCK_BEGIN
    {
        ifmt = safestr_get(fmt, SAFESTR_GET_READONLY);

        va_start(ap, fmt);
        parse_format_string(output_to_stream, stream, ifmt, ap, &result);
        va_end(ap);
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

u_int32_t
safestr_printf(safestr_t fmt, ...)
{
    va_list     ap;
    u_int32_t   result;
    isafestr_t  ifmt;

    XXL_ASSET_BLOCK_BEGIN
    {
        ifmt = safestr_get(fmt, SAFESTR_GET_READONLY);

        va_start(ap, fmt);
        parse_format_string(output_to_stdout, NULL, ifmt, ap, &result);
        va_end(ap);
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

u_int32_t
safestr_sprintf(safestr_t *str, safestr_t fmt, ...)
{
    int         trusted;
    va_list     ap;
    safestr_t   temp;
    u_int32_t   result;
    isafestr_t  ifmt, iorig, istr, itemp;

    XXL_ASSET_BLOCK_BEGIN
    {
        istr  = iorig = safestr_get(*str, SAFESTR_GET_WRITABLE);
        ifmt  = safestr_get(fmt, SAFESTR_GET_READONLY);
        temp  = safestr_alloc(ifmt->hdr.length, SAFESTR_ASSET_TEMPORARY);
        itemp = safestr_get(temp, SAFESTR_GET_WRITABLE);

        va_start(ap, fmt);
        trusted = parse_format_string(output_to_isafestr, (void *)&itemp,
                                      ifmt, ap, &result);
        va_end(ap);
        itemp->str[itemp->hdr.length] = '\0';

        if (istr->hdr.size < itemp->hdr.length)
            istr = safestr_resize(istr, itemp->hdr.length);
        else
            istr->hdr.length = itemp->hdr.length;
        memcpy(istr->str, itemp->str, itemp->hdr.length + 1);
        if (trusted) istr->hdr.flags |= SAFESTR_TRUSTED;
        else         istr->hdr.flags &= ~SAFESTR_TRUSTED;
        *str = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;
  
    return result;
}

u_int32_t
safestr_vasprintf(safestr_t *str, safestr_t fmt, va_list ap)
{
    int         trusted;
    u_int32_t   result;
    isafestr_t  ifmt, iorig, istr;

    XXL_ASSET_BLOCK_BEGIN
    {
        ifmt  = safestr_get(fmt, SAFESTR_GET_READONLY);
        *str  = safestr_alloc(ifmt->hdr.length, SAFESTR_ASSET_PROMOTE);
        iorig = istr = safestr_get(*str, SAFESTR_GET_WRITABLE);

        trusted = parse_format_string(output_to_isafestr, (void *)&istr,
                                      ifmt, ap, &result);
        istr->str[istr->hdr.length] = '\0';

        if (trusted) istr->hdr.flags |= SAFESTR_TRUSTED;
        else         istr->hdr.flags &= ~SAFESTR_TRUSTED;
        *str = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;
  
    return result;
}

u_int32_t
safestr_vfprintf(FILE *stream, safestr_t fmt, va_list ap)
{
    u_int32_t   result;
    isafestr_t  ifmt;

    XXL_ASSET_BLOCK_BEGIN
    {
        ifmt = safestr_get(fmt, SAFESTR_GET_READONLY);
        parse_format_string(output_to_stream, stream, ifmt, ap, &result);
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

u_int32_t
safestr_vprintf(safestr_t fmt, va_list ap)
{
    u_int32_t   result;
    isafestr_t  ifmt;

    XXL_ASSET_BLOCK_BEGIN
    {
        ifmt = safestr_get(fmt, SAFESTR_GET_READONLY);
        parse_format_string(output_to_stdout, NULL, ifmt, ap, &result);
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

u_int32_t
safestr_vsprintf(safestr_t *str, safestr_t fmt, va_list ap)
{
    int         trusted;
    safestr_t   temp;
    u_int32_t   result;
    isafestr_t  ifmt, iorig, istr, itemp;

    XXL_ASSET_BLOCK_BEGIN
    {
        istr  = iorig = safestr_get(*str, SAFESTR_GET_WRITABLE);
        ifmt  = safestr_get(fmt, SAFESTR_GET_READONLY);
        temp  = safestr_alloc(ifmt->hdr.length, SAFESTR_ASSET_TEMPORARY);
        itemp = safestr_get(temp, SAFESTR_GET_WRITABLE);

        trusted = parse_format_string(output_to_isafestr, (void *)&itemp,
                                      ifmt, ap, &result);
        itemp->str[itemp->hdr.length] = '\0';

        if (istr->hdr.size < itemp->hdr.length)
            istr = safestr_resize(istr, itemp->hdr.length);
        else
            istr->hdr.length = itemp->hdr.length;
        memcpy(istr->str, itemp->str, itemp->hdr.length + 1);
        if (trusted) istr->hdr.flags |= SAFESTR_TRUSTED;
        else         istr->hdr.flags &= ~SAFESTR_TRUSTED;
        *str = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;

    return result;
}


syntax highlighted by Code2HTML, v. 0.9.1