/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/

/*
* This code is based on, and used with the permission of, the
* SIO stdio-replacement strx_* functions by Panos Tsirigotis
* <[email protected]> for xinetd.
*/

#include "config.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <netinet/in.h>

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

typedef struct {
   char *curpos;
   char *endpos;
} ap_vformatter_buff;


#define API_EXPORT(type) type
#define API_EXPORT_NONSTD(type) type

#define ap_isalnum(c) (isalnum(((unsigned char)(c))))
#define ap_isalpha(c) (isalpha(((unsigned char)(c))))
#define ap_iscntrl(c) (iscntrl(((unsigned char)(c))))
#define ap_isdigit(c) (isdigit(((unsigned char)(c))))
#define ap_isgraph(c) (isgraph(((unsigned char)(c))))
#define ap_islower(c) (islower(((unsigned char)(c))))
#define ap_isprint(c) (isprint(((unsigned char)(c))))
#define ap_ispunct(c) (ispunct(((unsigned char)(c))))
#define ap_isspace(c) (isspace(((unsigned char)(c))))
#define ap_isupper(c) (isupper(((unsigned char)(c))))
#define ap_isxdigit(c) (isxdigit(((unsigned char)(c))))
#define ap_tolower(c) (tolower(((unsigned char)(c))))
#define ap_toupper(c) (toupper(((unsigned char)(c))))


typedef enum {
   NO = 0, YES = 1
} boolean_e;

#ifndef FALSE
#define FALSE                   0
#endif
#ifndef TRUE
#define TRUE                    1
#endif
#ifndef AP_LONGEST_LONG
#define AP_LONGEST_LONG         long
#endif
#define NUL                     '\0'
#define WIDE_INT                long
#define WIDEST_INT              AP_LONGEST_LONG

typedef WIDE_INT wide_int;
typedef unsigned WIDE_INT u_wide_int;
typedef WIDEST_INT widest_int;
#ifdef __TANDEM
/* Although Tandem supports "long long" there is no unsigned variant. */
typedef unsigned long       u_widest_int;
#else
typedef unsigned WIDEST_INT u_widest_int;
#endif
typedef int bool_int;

#define S_NULL                  "(null)"
#define S_NULL_LEN              6

#define FLOAT_DIGITS            6
#define EXPONENT_LENGTH         10

/*
* NUM_BUF_SIZE is the size of the buffer used for arithmetic conversions
*
* XXX: this is a magic number; do not decrease it
*/
#define NUM_BUF_SIZE            512

/*
* cvt.c - IEEE floating point formatting routines for FreeBSD
* from GNU libc-4.6.27.  Modified to be thread safe.
*/

/*
*    ap_ecvt converts to decimal
*      the number of digits is specified by ndigit
*      decpt is set to the position of the decimal point
*      sign is set to 0 for positive, 1 for negative
*/

#define NDIG    80

/* buf must have at least NDIG bytes */
static char *ap_cvt(double arg, int ndigits, int *decpt, int *sign, int eflag, char *buf)
{
   register int r2;
   double fi, fj;
   register char *p, *p1;

   if (ndigits >= NDIG - 1)
       ndigits = NDIG - 2;
   r2 = 0;
   *sign = 0;
   p = &buf[0];
   if (arg < 0) {
       *sign = 1;
       arg = -arg;
   }
   arg = modf(arg, &fi);
   p1 = &buf[NDIG];
   /*
    * Do integer part
    */
   if (fi != 0) {
       p1 = &buf[NDIG];
       while (p1 > &buf[0] && fi != 0) {
           fj = modf(fi / 10, &fi);
           *--p1 = (int) ((fj + .03) * 10) + '0';
           r2++;
       }
       while (p1 < &buf[NDIG])
           *p++ = *p1++;
   }
   else if (arg > 0) {
       while ((fj = arg * 10) < 1) {
           arg = fj;
           r2--;
       }
   }
   p1 = &buf[ndigits];
   if (eflag == 0)
       p1 += r2;
   *decpt = r2;
   if (p1 < &buf[0]) {
       buf[0] = '\0';
       return (buf);
   }
   while (p <= p1 && p < &buf[NDIG]) {
       arg *= 10;
       arg = modf(arg, &fj);
       *p++ = (int) fj + '0';
   }
   if (p1 >= &buf[NDIG]) {
       buf[NDIG - 1] = '\0';
       return (buf);
   }
   p = p1;
   *p1 += 5;
   while (*p1 > '9') {
       *p1 = '0';
       if (p1 > buf)
           ++ * --p1;
       else {
           *p1 = '1';
           (*decpt)++;
           if (eflag == 0) {
               if (p > buf)
                   *p = '0';
               p++;
           }
       }
   }
   *p = '\0';
   return (buf);
}

static char *ap_ecvt(double arg, int ndigits, int *decpt, int *sign, char *buf)
{
   return (ap_cvt(arg, ndigits, decpt, sign, 1, buf));
}

static char *ap_fcvt(double arg, int ndigits, int *decpt, int *sign, char *buf)
{
   return (ap_cvt(arg, ndigits, decpt, sign, 0, buf));
}

/*
* ap_gcvt  - Floating output conversion to
* minimal length string
*/

static char *ap_gcvt(double number, int ndigit, char *buf, boolean_e altform)
{
   int sign, decpt;
   register char *p1, *p2;
   register int i;
   char buf1[NDIG];

   p1 = ap_ecvt(number, ndigit, &decpt, &sign, buf1);
   p2 = buf;
   if (sign)
       *p2++ = '-';
   for (i = ndigit - 1; i > 0 && p1[i] == '0'; i--)
       ndigit--;
   if ((decpt >= 0 && decpt - ndigit > 4)
       || (decpt < 0 && decpt < -3)) {         /* use E-style */
       decpt--;
       *p2++ = *p1++;
       *p2++ = '.';
       for (i = 1; i < ndigit; i++)
           *p2++ = *p1++;
       *p2++ = 'e';
       if (decpt < 0) {
           decpt = -decpt;
           *p2++ = '-';
       }
       else
           *p2++ = '+';
       if (decpt / 100 > 0)
           *p2++ = decpt / 100 + '0';
       if (decpt / 10 > 0)
           *p2++ = (decpt % 100) / 10 + '0';
       *p2++ = decpt % 10 + '0';
   }
   else {
       if (decpt <= 0) {
           if (*p1 != '0')
               *p2++ = '.';
           while (decpt < 0) {
               decpt++;
               *p2++ = '0';
           }
       }
       for (i = 1; i <= ndigit; i++) {
           *p2++ = *p1++;
           if (i == decpt)
               *p2++ = '.';
       }
       if (ndigit < decpt) {
           while (ndigit++ < decpt)
               *p2++ = '0';
           *p2++ = '.';
       }
   }
   if (p2[-1] == '.' && !altform)
       p2--;
   *p2 = '\0';
   return (buf);
}

/*
* The INS_CHAR macro inserts a character in the buffer and writes
* the buffer back to disk if necessary
* It uses the char pointers sp and bep:
*      sp points to the next available character in the buffer
*      bep points to the end-of-buffer+1
* While using this macro, note that the nextb pointer is NOT updated.
*
* NOTE: Evaluation of the c argument should not have any side-effects
*/
#define INS_CHAR(c, sp, bep, cc)                                \
           {                                                   \
               if (sp >= bep) {                                \
                   vbuff->curpos = sp;                         \
                   if (flush_func(vbuff))                      \
                       return -1;                              \
                   sp = vbuff->curpos;                         \
                   bep = vbuff->endpos;                        \
               }                                               \
               *sp++ = (c);                                    \
               cc++;                                           \
           }

#define NUM( c )                        ( c - '0' )

#define STR_TO_DEC( str, num )          \
   num = NUM( *str++ ) ;               \
   while ( ap_isdigit( *str ) )                \
   {                                   \
       num *= 10 ;                     \
       num += NUM( *str++ ) ;          \
   }

/*
* This macro does zero padding so that the precision
* requirement is satisfied. The padding is done by
* adding '0's to the left of the string that is going
* to be printed. We don't allow precision to be large
* enough that we continue past the start of s.
*
* NOTE: this makes use of the magic info that s is
* always based on num_buf with a size of NUM_BUF_SIZE.
*/
#define FIX_PRECISION( adjust, precision, s, s_len )    \
   if ( adjust ) {                                     \
       int p = precision < NUM_BUF_SIZE - 1 ? precision : NUM_BUF_SIZE - 1; \
       while ( s_len < p )                             \
       {                                               \
           *--s = '0' ;                                \
           s_len++ ;                                   \
       }                                               \
   }

/*
* Macro that does padding. The padding is done by printing
* the character ch.
*/
#define PAD( width, len, ch )   do              \
       {                                       \
           INS_CHAR( ch, sp, bep, cc ) ;       \
           width-- ;                           \
       }                                       \
       while ( width > len )

/*
* Prefix the character ch to the string str
* Increase length
* Set the has_prefix flag
*/
#define PREFIX( str, length, ch )        *--str = ch ; length++ ; has_prefix = YES


/*
* Convert num to its decimal format.
* Return value:
*   - a pointer to a string containing the number (no sign)
*   - len contains the length of the string
*   - is_negative is set to TRUE or FALSE depending on the sign
*     of the number (always set to FALSE if is_unsigned is TRUE)
*
* The caller provides a buffer for the string: that is the buf_end argument
* which is a pointer to the END of the buffer + 1 (i.e. if the buffer
* is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
*
* Note: we have 2 versions. One is used when we need to use quads
* (conv_10_quad), the other when we don't (conv_10). We're assuming the
* latter is faster.
*/
static char *conv_10(register wide_int num, register bool_int is_unsigned,
                    register bool_int *is_negative, char *buf_end,
                    register int *len)
{
   register char *p = buf_end;
   register u_wide_int magnitude;

   if (is_unsigned) {
       magnitude = (u_wide_int) num;
       *is_negative = FALSE;
   }
   else {
       *is_negative = (num < 0);

       /*
        * On a 2's complement machine, negating the most negative integer
        * results in a number that cannot be represented as a signed integer.
        * Here is what we do to obtain the number's magnitude:
        *      a. add 1 to the number
        *      b. negate it (becomes positive)
        *      c. convert it to unsigned
        *      d. add 1
        */
       if (*is_negative) {
           wide_int t = num + 1;

           magnitude = ((u_wide_int) -t) + 1;
       }
       else
           magnitude = (u_wide_int) num;
   }

   /*
    * We use a do-while loop so that we write at least 1 digit
    */
   do {
       register u_wide_int new_magnitude = magnitude / 10;

       *--p = (char) (magnitude - new_magnitude * 10 + '0');
       magnitude = new_magnitude;
   }
   while (magnitude);

   *len = buf_end - p;
   return (p);
}

static char *conv_10_quad(widest_int num, register bool_int is_unsigned,
                    register bool_int *is_negative, char *buf_end,
                    register int *len)
{
   register char *p = buf_end;
   u_widest_int magnitude;

   /*
    * We see if we can use the faster non-quad version by checking the
    * number against the largest long value it can be. If <=, we
    * punt to the quicker version.
    */
   if ((num <= ULONG_MAX && is_unsigned) || (num <= LONG_MAX && !is_unsigned))
       return(conv_10( (wide_int)num, is_unsigned, is_negative,
              buf_end, len));

   if (is_unsigned) {
       magnitude = (u_widest_int) num;
       *is_negative = FALSE;
   }
   else {
       *is_negative = (num < 0);

       /*
        * On a 2's complement machine, negating the most negative integer
        * results in a number that cannot be represented as a signed integer.
        * Here is what we do to obtain the number's magnitude:
        *      a. add 1 to the number
        *      b. negate it (becomes positive)
        *      c. convert it to unsigned
        *      d. add 1
        */
       if (*is_negative) {
           widest_int t = num + 1;

           magnitude = ((u_widest_int) -t) + 1;
       }
       else
           magnitude = (u_widest_int) num;
   }

   /*
    * We use a do-while loop so that we write at least 1 digit
    */
   do {
       u_widest_int new_magnitude = magnitude / 10;

       *--p = (char) (magnitude - new_magnitude * 10 + '0');
       magnitude = new_magnitude;
   }
   while (magnitude);

   *len = buf_end - p;
   return (p);
}



static char *conv_in_addr(struct in_addr *ia, char *buf_end, int *len)
{
   unsigned addr = ntohl(ia->s_addr);
   char *p = buf_end;
   bool_int is_negative;
   int sub_len;

   p = conv_10((addr & 0x000000FF)      , TRUE, &is_negative, p, &sub_len);
   *--p = '.';
   p = conv_10((addr & 0x0000FF00) >>  8, TRUE, &is_negative, p, &sub_len);
   *--p = '.';
   p = conv_10((addr & 0x00FF0000) >> 16, TRUE, &is_negative, p, &sub_len);
   *--p = '.';
   p = conv_10((addr & 0xFF000000) >> 24, TRUE, &is_negative, p, &sub_len);

   *len = buf_end - p;
   return (p);
}



static char *conv_sockaddr_in(struct sockaddr_in *si, char *buf_end, int *len)
{
   char *p = buf_end;
   bool_int is_negative;
   int sub_len;

   p = conv_10(ntohs(si->sin_port), TRUE, &is_negative, p, &sub_len);
   *--p = ':';
   p = conv_in_addr(&si->sin_addr, p, &sub_len);

   *len = buf_end - p;
   return (p);
}



/*
* Convert a floating point number to a string formats 'f', 'e' or 'E'.
* The result is placed in buf, and len denotes the length of the string
* The sign is returned in the is_negative argument (and is not placed
* in buf).
*/
static char *conv_fp(register char format, register double num,
   boolean_e add_dp, int precision, bool_int *is_negative,
   char *buf, int *len)
{
   register char *s = buf;
   register char *p;
   int decimal_point;
   char buf1[NDIG];

   if (format == 'f')
       p = ap_fcvt(num, precision, &decimal_point, is_negative, buf1);
   else                        /* either e or E format */
       p = ap_ecvt(num, precision + 1, &decimal_point, is_negative, buf1);

   /*
    * Check for Infinity and NaN
    */
   if (ap_isalpha(*p)) {
       *len = strlen(strcpy(buf, p));
       *is_negative = FALSE;
       return (buf);
   }

   if (format == 'f') {
       if (decimal_point <= 0) {
           *s++ = '0';
           if (precision > 0) {
               *s++ = '.';
               while (decimal_point++ < 0)
                   *s++ = '0';
           }
           else if (add_dp)
               *s++ = '.';
       }
       else {
           while (decimal_point-- > 0)
               *s++ = *p++;
           if (precision > 0 || add_dp)
               *s++ = '.';
       }
   }
   else {
       *s++ = *p++;
       if (precision > 0 || add_dp)
           *s++ = '.';
   }

   /*
    * copy the rest of p, the NUL is NOT copied
    */
   while (*p)
       *s++ = *p++;

   if (format != 'f') {
       char temp[EXPONENT_LENGTH];     /* for exponent conversion */
       int t_len;
       bool_int exponent_is_negative;

       *s++ = format;          /* either e or E */
       decimal_point--;
       if (decimal_point != 0) {
           p = conv_10((wide_int) decimal_point, FALSE, &exponent_is_negative,
                       &temp[EXPONENT_LENGTH], &t_len);
           *s++ = exponent_is_negative ? '-' : '+';

           /*
            * Make sure the exponent has at least 2 digits
            */
           if (t_len == 1)
               *s++ = '0';
           while (t_len--)
               *s++ = *p++;
       }
       else {
           *s++ = '+';
           *s++ = '0';
           *s++ = '0';
       }
   }

   *len = s - buf;
   return (buf);
}


/*
* Convert num to a base X number where X is a power of 2. nbits determines X.
* For example, if nbits is 3, we do base 8 conversion
* Return value:
*      a pointer to a string containing the number
*
* The caller provides a buffer for the string: that is the buf_end argument
* which is a pointer to the END of the buffer + 1 (i.e. if the buffer
* is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
*
* As with conv_10, we have a faster version which is used when
* the number isn't quad size.
*/
static char *conv_p2(register u_wide_int num, register int nbits,
                    char format, char *buf_end, register int *len)
{
   register int mask = (1 << nbits) - 1;
   register char *p = buf_end;
   static const char low_digits[] = "0123456789abcdef";
   static const char upper_digits[] = "0123456789ABCDEF";
   register const char *digits = (format == 'X') ? upper_digits : low_digits;

   do {
       *--p = digits[num & mask];
       num >>= nbits;
   }
   while (num);

   *len = buf_end - p;
   return (p);
}

static char *conv_p2_quad(u_widest_int num, register int nbits,
                    char format, char *buf_end, register int *len)
{
   register int mask = (1 << nbits) - 1;
   register char *p = buf_end;
   static const char low_digits[] = "0123456789abcdef";
   static const char upper_digits[] = "0123456789ABCDEF";
   register const char *digits = (format == 'X') ? upper_digits : low_digits;

   if (num <= ULONG_MAX)
       return(conv_p2( (u_wide_int)num, nbits, format, buf_end, len));

   do {
       *--p = digits[num & mask];
       num >>= nbits;
   }
   while (num);

   *len = buf_end - p;
   return (p);
}


/*
* Do format conversion placing the output in buffer
*/
API_EXPORT(int) ap_vformatter(int (*flush_func)(ap_vformatter_buff *),
   ap_vformatter_buff *vbuff, const char *fmt, va_list ap)
{
   register char *sp;
   register char *bep;
   register int cc = 0;
   register int i;

   register char *s = NULL;
   char *q;
   int s_len;

   register int min_width = 0;
   int precision = 0;
   enum {
       LEFT, RIGHT
   } adjust;
   char pad_char;
   char prefix_char;

   double fp_num;
   widest_int i_quad = (widest_int) 0;
   u_widest_int ui_quad;
   wide_int i_num = (wide_int) 0;
   u_wide_int ui_num;

   char num_buf[NUM_BUF_SIZE];
   char char_buf[2];           /* for printing %% and %<unknown> */

   enum var_type_enum {
       IS_QUAD, IS_LONG, IS_SHORT, IS_INT
   };
   enum var_type_enum var_type = IS_INT;

   /*
    * Flag variables
    */
   boolean_e alternate_form;
   boolean_e print_sign;
   boolean_e print_blank;
   boolean_e adjust_precision;
   boolean_e adjust_width;
   bool_int is_negative;

   sp = vbuff->curpos;
   bep = vbuff->endpos;

   while (*fmt) {
       if (*fmt != '%') {
           INS_CHAR(*fmt, sp, bep, cc);
       }
       else {
           /*
            * Default variable settings
            */
           adjust = RIGHT;
           alternate_form = print_sign = print_blank = NO;
           pad_char = ' ';
           prefix_char = NUL;

           fmt++;

           /*
            * Try to avoid checking for flags, width or precision
            */
           if (!ap_islower(*fmt)) {
               /*
                * Recognize flags: -, #, BLANK, +
                */
               for (;; fmt++) {
                   if (*fmt == '-')
                       adjust = LEFT;
                   else if (*fmt == '+')
                       print_sign = YES;
                   else if (*fmt == '#')
                       alternate_form = YES;
                   else if (*fmt == ' ')
                       print_blank = YES;
                   else if (*fmt == '0')
                       pad_char = '0';
                   else
                       break;
               }

               /*
                * Check if a width was specified
                */
               if (ap_isdigit(*fmt)) {
                   STR_TO_DEC(fmt, min_width);
                   adjust_width = YES;
               }
               else if (*fmt == '*') {
                   min_width = va_arg(ap, int);
                   fmt++;
                   adjust_width = YES;
                   if (min_width < 0) {
                       adjust = LEFT;
                       min_width = -min_width;
                   }
               }
               else
                   adjust_width = NO;

               /*
                * Check if a precision was specified
                */
               if (*fmt == '.') {
                   adjust_precision = YES;
                   fmt++;
                   if (ap_isdigit(*fmt)) {
                       STR_TO_DEC(fmt, precision);
                   }
                   else if (*fmt == '*') {
                       precision = va_arg(ap, int);
                       fmt++;
                       if (precision < 0)
                           precision = 0;
                   }
                   else
                       precision = 0;
               }
               else
                   adjust_precision = NO;
           }
           else
               adjust_precision = adjust_width = NO;

           /*
            * Modifier check
            */
           if (*fmt == 'q') {
               var_type = IS_QUAD;
               fmt++;
           }
           else if (*fmt == 'l') {
               var_type = IS_LONG;
               fmt++;
           }
           else if (*fmt == 'h') {
               var_type = IS_SHORT;
               fmt++;
           }
           else {
               var_type = IS_INT;
           }

           /*
            * Argument extraction and printing.
            * First we determine the argument type.
            * Then, we convert the argument to a string.
            * On exit from the switch, s points to the string that
            * must be printed, s_len has the length of the string
            * The precision requirements, if any, are reflected in s_len.
            *
            * NOTE: pad_char may be set to '0' because of the 0 flag.
            *   It is reset to ' ' by non-numeric formats
            */
           switch (*fmt) {
           case 'u':
               if (var_type == IS_QUAD) {
                   i_quad = va_arg(ap, u_widest_int);
                   s = conv_10_quad(i_quad, 1, &is_negative,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               else {
                   if (var_type == IS_LONG)
                       i_num = (wide_int) va_arg(ap, u_wide_int);
                   else if (var_type == IS_SHORT)
                       i_num = (wide_int) (unsigned short) va_arg(ap, unsigned int);
                   else
                       i_num = (wide_int) va_arg(ap, unsigned int);
                   s = conv_10(i_num, 1, &is_negative,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               FIX_PRECISION(adjust_precision, precision, s, s_len);
               break;

           case 'd':
           case 'i':
               if (var_type == IS_QUAD) {
                   i_quad = va_arg(ap, widest_int);
                   s = conv_10_quad(i_quad, 0, &is_negative,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               else {
                   if (var_type == IS_LONG)
                       i_num = (wide_int) va_arg(ap, wide_int);
                   else if (var_type == IS_SHORT)
                       i_num = (wide_int) (short) va_arg(ap, int);
                   else
                       i_num = (wide_int) va_arg(ap, int);
                   s = conv_10(i_num, 0, &is_negative,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               FIX_PRECISION(adjust_precision, precision, s, s_len);

               if (is_negative)
                   prefix_char = '-';
               else if (print_sign)
                   prefix_char = '+';
               else if (print_blank)
                   prefix_char = ' ';
               break;


           case 'o':
               if (var_type == IS_QUAD) {
                   ui_quad = va_arg(ap, u_widest_int);
                   s = conv_p2_quad(ui_quad, 3, *fmt,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               else {
                   if (var_type == IS_LONG)
                       ui_num = (u_wide_int) va_arg(ap, u_wide_int);
                   else if (var_type == IS_SHORT)
                       ui_num = (u_wide_int) (unsigned short) va_arg(ap, unsigned int);
                   else
                       ui_num = (u_wide_int) va_arg(ap, unsigned int);
                   s = conv_p2(ui_num, 3, *fmt,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               FIX_PRECISION(adjust_precision, precision, s, s_len);
               if (alternate_form && *s != '0') {
                   *--s = '0';
                   s_len++;
               }
               break;


           case 'x':
           case 'X':
               if (var_type == IS_QUAD) {
                   ui_quad = va_arg(ap, u_widest_int);
                   s = conv_p2_quad(ui_quad, 4, *fmt,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               else {
                   if (var_type == IS_LONG)
                       ui_num = (u_wide_int) va_arg(ap, u_wide_int);
                   else if (var_type == IS_SHORT)
                       ui_num = (u_wide_int) (unsigned short) va_arg(ap, unsigned int);
                   else
                       ui_num = (u_wide_int) va_arg(ap, unsigned int);
                   s = conv_p2(ui_num, 4, *fmt,
                           &num_buf[NUM_BUF_SIZE], &s_len);
               }
               FIX_PRECISION(adjust_precision, precision, s, s_len);
               if (alternate_form && i_num != 0) {
                   *--s = *fmt;        /* 'x' or 'X' */
                   *--s = '0';
                   s_len += 2;
               }
               break;


           case 's':
               s = va_arg(ap, char *);
               if (s != NULL) {
                   s_len = strlen(s);
                   if (adjust_precision && precision < s_len)
                       s_len = precision;
               }
               else {
                   s = S_NULL;
                   s_len = S_NULL_LEN;
               }
               pad_char = ' ';
               break;


           case 'f':
           case 'e':
           case 'E':
               fp_num = va_arg(ap, double);
               /*
                * * We use &num_buf[ 1 ], so that we have room for the sign
                */
#ifdef HAVE_ISNAN
               if (isnan(fp_num)) {
                   s = "nan";
                   s_len = 3;
               }
               else
#endif
#ifdef HAVE_ISINF
               if (isinf(fp_num)) {
                   s = "inf";
                   s_len = 3;
               }
               else
#endif
               {
                   s = conv_fp(*fmt, fp_num, alternate_form,
                           (adjust_precision == NO) ? FLOAT_DIGITS : precision,
                               &is_negative, &num_buf[1], &s_len);
                   if (is_negative)
                       prefix_char = '-';
                   else if (print_sign)
                       prefix_char = '+';
                   else if (print_blank)
                       prefix_char = ' ';
               }
               break;


           case 'g':
           case 'G':
               if (adjust_precision == NO)
                   precision = FLOAT_DIGITS;
               else if (precision == 0)
                   precision = 1;
               /*
                * * We use &num_buf[ 1 ], so that we have room for the sign
                */
               s = ap_gcvt(va_arg(ap, double), precision, &num_buf[1],
                           alternate_form);
               if (*s == '-')
                   prefix_char = *s++;
               else if (print_sign)
                   prefix_char = '+';
               else if (print_blank)
                   prefix_char = ' ';

               s_len = strlen(s);

               if (alternate_form && (q = strchr(s, '.')) == NULL) {
                   s[s_len++] = '.';
                   s[s_len] = '\0'; /* delimit for following strchr() */
               }
               if (*fmt == 'G' && (q = strchr(s, 'e')) != NULL)
                   *q = 'E';
               break;


           case 'c':
               char_buf[0] = (char) (va_arg(ap, int));
               s = &char_buf[0];
               s_len = 1;
               pad_char = ' ';
               break;


           case '%':
               char_buf[0] = '%';
               s = &char_buf[0];
               s_len = 1;
               pad_char = ' ';
               break;


           case 'n':
               if (var_type == IS_QUAD)
                   *(va_arg(ap, widest_int *)) = cc;
               else if (var_type == IS_LONG)
                   *(va_arg(ap, long *)) = cc;
               else if (var_type == IS_SHORT)
                   *(va_arg(ap, short *)) = cc;
               else
                   *(va_arg(ap, int *)) = cc;
               break;

               /*
                * This is where we extend the printf format, with a second
                * type specifier
                */
           case 'p':
               switch(*++fmt) {
                   /*
                    * If the pointer size is equal to or smaller than the size
                    * of the largest unsigned int, we convert the pointer to a
                    * hex number, otherwise we print "%p" to indicate that we
                    * don't handle "%p".
                    */
               case 'p':
#ifdef AP_VOID_P_IS_QUAD
                   if (sizeof(void *) <= sizeof(u_widest_int)) {
                       ui_quad = (u_widest_int) va_arg(ap, void *);
                       s = conv_p2_quad(ui_quad, 4, 'x',
                               &num_buf[NUM_BUF_SIZE], &s_len);
                   }
#else
                   if (sizeof(void *) <= sizeof(u_wide_int)) {
                       ui_num = (u_wide_int) va_arg(ap, void *);
                       s = conv_p2(ui_num, 4, 'x',
                               &num_buf[NUM_BUF_SIZE], &s_len);
                   }
#endif
                   else {
                       s = "%p";
                       s_len = 2;
                       prefix_char = NUL;
                   }
                   pad_char = ' ';
                   break;

                   /* print a struct sockaddr_in as a.b.c.d:port */
               case 'I':
                   {
                       struct sockaddr_in *si;

                       si = va_arg(ap, struct sockaddr_in *);
                       if (si != NULL) {
                           s = conv_sockaddr_in(si, &num_buf[NUM_BUF_SIZE], &s_len);
                           if (adjust_precision && precision < s_len)
                               s_len = precision;
                       }
                       else {
                           s = S_NULL;
                           s_len = S_NULL_LEN;
                       }
                       pad_char = ' ';
                   }
                   break;

                   /* print a struct in_addr as a.b.c.d */
               case 'A':
                   {
                       struct in_addr *ia;

                       ia = va_arg(ap, struct in_addr *);
                       if (ia != NULL) {
                           s = conv_in_addr(ia, &num_buf[NUM_BUF_SIZE], &s_len);
                           if (adjust_precision && precision < s_len)
                               s_len = precision;
                       }
                       else {
                           s = S_NULL;
                           s_len = S_NULL_LEN;
                       }
                       pad_char = ' ';
                   }
                   break;

               case NUL:
                   /* if %p ends the string, oh well ignore it */
                   continue;

               default:
                   s = "bogus %p";
                   s_len = 8;
                   prefix_char = NUL;
                   break;
               }
               break;

           case NUL:
               /*
                * The last character of the format string was %.
                * We ignore it.
                */
               continue;


               /*
                * The default case is for unrecognized %'s.
                * We print %<char> to help the user identify what
                * option is not understood.
                * This is also useful in case the user wants to pass
                * the output of format_converter to another function
                * that understands some other %<char> (like syslog).
                * Note that we can't point s inside fmt because the
                * unknown <char> could be preceded by width etc.
                */
           default:
               char_buf[0] = '%';
               char_buf[1] = *fmt;
               s = char_buf;
               s_len = 2;
               pad_char = ' ';
               break;
           }

           if (prefix_char != NUL && s != S_NULL && s != char_buf) {
               *--s = prefix_char;
               s_len++;
           }

           if (adjust_width && adjust == RIGHT && min_width > s_len) {
               if (pad_char == '0' && prefix_char != NUL) {
                   INS_CHAR(*s, sp, bep, cc);
                   s++;
                   s_len--;
                   min_width--;
               }
               PAD(min_width, s_len, pad_char);
           }

           /*
            * Print the string s.
            */
           for (i = s_len; i != 0; i--) {
               INS_CHAR(*s, sp, bep, cc);
               s++;
           }

           if (adjust_width && adjust == LEFT && min_width > s_len)
               PAD(min_width, s_len, pad_char);
       }
       fmt++;
   }
   vbuff->curpos = sp;

   return cc;
}


static int snprintf_flush(ap_vformatter_buff *vbuff)
{
   /* if the buffer fills we have to abort immediately, there is no way
    * to "flush" an ap_snprintf... there's nowhere to flush it to.
    */
   return -1;
}


API_EXPORT_NONSTD(int) ap_snprintf(char *buf, size_t len, const char *format,...)
{
   int cc;
   va_list ap;
   ap_vformatter_buff vbuff;

   if (len == 0)
       return 0;

   /* save one byte for nul terminator */
   vbuff.curpos = buf;
   vbuff.endpos = buf + len - 1;
   va_start(ap, format);
   cc = ap_vformatter(snprintf_flush, &vbuff, format, ap);
   va_end(ap);
   *vbuff.curpos = '\0';
   return (cc == -1) ? len : cc;
}


API_EXPORT(int) ap_vsnprintf(char *buf, size_t len, const char *format,
                            va_list ap)
{
   int cc;
   ap_vformatter_buff vbuff;

   if (len == 0)
       return 0;

   /* save one byte for nul terminator */
   vbuff.curpos = buf;
   vbuff.endpos = buf + len - 1;
   cc = ap_vformatter(snprintf_flush, &vbuff, format, ap);
   *vbuff.curpos = '\0';
   return (cc == -1) ? len : cc;
}