- Null terminate the end of the snprintf output buffer on Windows.
Old versions of the Windows CRT (which are often found on later versions
of Windows) do not terminate the snprintf output buffer if the output
reaches the max size.
This is a follow-up to parent 7e32f656 which made the same change but
limited it to mingw, however it is a CRT version issue irrespective of
compiler.
Ref: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/snprintf-snprintf-snprintf-l-snwprintf-snwprintf-l?view=msvc-170#remarks
Closes https://github.com/curl/curl/pull/15997
1224 lines
32 KiB
C
1224 lines
32 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
*/
|
|
|
|
#include "curl_setup.h"
|
|
#include "dynbuf.h"
|
|
#include "curl_printf.h"
|
|
|
|
#include "curl_memory.h"
|
|
/* The last #include file should be: */
|
|
#include "memdebug.h"
|
|
|
|
/*
|
|
* If SIZEOF_SIZE_T has not been defined, default to the size of long.
|
|
*/
|
|
|
|
#ifdef HAVE_LONGLONG
|
|
# define LONG_LONG_TYPE long long
|
|
# define HAVE_LONG_LONG_TYPE
|
|
#elif defined(_MSC_VER)
|
|
# define LONG_LONG_TYPE __int64
|
|
# define HAVE_LONG_LONG_TYPE
|
|
#else
|
|
# undef LONG_LONG_TYPE
|
|
# undef HAVE_LONG_LONG_TYPE
|
|
#endif
|
|
|
|
/*
|
|
* Max integer data types that mprintf.c is capable
|
|
*/
|
|
|
|
#ifdef HAVE_LONG_LONG_TYPE
|
|
# define mp_intmax_t LONG_LONG_TYPE
|
|
# define mp_uintmax_t unsigned LONG_LONG_TYPE
|
|
#else
|
|
# define mp_intmax_t long
|
|
# define mp_uintmax_t unsigned long
|
|
#endif
|
|
|
|
#define BUFFSIZE 326 /* buffer for long-to-str and float-to-str calcs, should
|
|
fit negative DBL_MAX (317 letters) */
|
|
#define MAX_PARAMETERS 128 /* number of input arguments */
|
|
#define MAX_SEGMENTS 128 /* number of output segments */
|
|
|
|
#ifdef __AMIGA__
|
|
# undef FORMAT_INT
|
|
#endif
|
|
|
|
/* Lower-case digits. */
|
|
static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
|
|
/* Upper-case digits. */
|
|
static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
#define OUTCHAR(x) \
|
|
do { \
|
|
if(!stream((unsigned char)x, userp)) \
|
|
done++; \
|
|
else \
|
|
return done; /* return on failure */ \
|
|
} while(0)
|
|
|
|
/* Data type to read from the arglist */
|
|
typedef enum {
|
|
FORMAT_STRING,
|
|
FORMAT_PTR,
|
|
FORMAT_INTPTR,
|
|
FORMAT_INT,
|
|
FORMAT_LONG,
|
|
FORMAT_LONGLONG,
|
|
FORMAT_INTU,
|
|
FORMAT_LONGU,
|
|
FORMAT_LONGLONGU,
|
|
FORMAT_DOUBLE,
|
|
FORMAT_LONGDOUBLE,
|
|
FORMAT_WIDTH,
|
|
FORMAT_PRECISION
|
|
} FormatType;
|
|
|
|
/* conversion and display flags */
|
|
enum {
|
|
FLAGS_SPACE = 1 << 0,
|
|
FLAGS_SHOWSIGN = 1 << 1,
|
|
FLAGS_LEFT = 1 << 2,
|
|
FLAGS_ALT = 1 << 3,
|
|
FLAGS_SHORT = 1 << 4,
|
|
FLAGS_LONG = 1 << 5,
|
|
FLAGS_LONGLONG = 1 << 6,
|
|
FLAGS_LONGDOUBLE = 1 << 7,
|
|
FLAGS_PAD_NIL = 1 << 8,
|
|
FLAGS_UNSIGNED = 1 << 9,
|
|
FLAGS_OCTAL = 1 << 10,
|
|
FLAGS_HEX = 1 << 11,
|
|
FLAGS_UPPER = 1 << 12,
|
|
FLAGS_WIDTH = 1 << 13, /* '*' or '*<num>$' used */
|
|
FLAGS_WIDTHPARAM = 1 << 14, /* width PARAMETER was specified */
|
|
FLAGS_PREC = 1 << 15, /* precision was specified */
|
|
FLAGS_PRECPARAM = 1 << 16, /* precision PARAMETER was specified */
|
|
FLAGS_CHAR = 1 << 17, /* %c story */
|
|
FLAGS_FLOATE = 1 << 18, /* %e or %E */
|
|
FLAGS_FLOATG = 1 << 19, /* %g or %G */
|
|
FLAGS_SUBSTR = 1 << 20 /* no input, only substring */
|
|
};
|
|
|
|
enum {
|
|
DOLLAR_UNKNOWN,
|
|
DOLLAR_NOPE,
|
|
DOLLAR_USE
|
|
};
|
|
|
|
/*
|
|
* Describes an input va_arg type and hold its value.
|
|
*/
|
|
struct va_input {
|
|
FormatType type; /* FormatType */
|
|
union {
|
|
char *str;
|
|
void *ptr;
|
|
mp_intmax_t nums; /* signed */
|
|
mp_uintmax_t numu; /* unsigned */
|
|
double dnum;
|
|
} val;
|
|
};
|
|
|
|
/*
|
|
* Describes an output segment.
|
|
*/
|
|
struct outsegment {
|
|
int width; /* width OR width parameter number */
|
|
int precision; /* precision OR precision parameter number */
|
|
unsigned int flags;
|
|
unsigned int input; /* input argument array index */
|
|
char *start; /* format string start to output */
|
|
size_t outlen; /* number of bytes from the format string to output */
|
|
};
|
|
|
|
struct nsprintf {
|
|
char *buffer;
|
|
size_t length;
|
|
size_t max;
|
|
};
|
|
|
|
struct asprintf {
|
|
struct dynbuf *b;
|
|
char merr;
|
|
};
|
|
|
|
/* the provided input number is 1-based but this returns the number 0-based.
|
|
|
|
returns -1 if no valid number was provided.
|
|
*/
|
|
static int dollarstring(char *input, char **end)
|
|
{
|
|
if(ISDIGIT(*input)) {
|
|
int number = 0;
|
|
do {
|
|
if(number < MAX_PARAMETERS) {
|
|
number *= 10;
|
|
number += *input - '0';
|
|
}
|
|
input++;
|
|
} while(ISDIGIT(*input));
|
|
|
|
if(number && (number <= MAX_PARAMETERS) && ('$' == *input)) {
|
|
*end = ++input;
|
|
return number - 1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Parse the format string.
|
|
*
|
|
* Create two arrays. One describes the inputs, one describes the outputs.
|
|
*
|
|
* Returns zero on success.
|
|
*/
|
|
|
|
#define PFMT_OK 0
|
|
#define PFMT_DOLLAR 1 /* bad dollar for main param */
|
|
#define PFMT_DOLLARWIDTH 2 /* bad dollar use for width */
|
|
#define PFMT_DOLLARPREC 3 /* bad dollar use for precision */
|
|
#define PFMT_MANYARGS 4 /* too many input arguments used */
|
|
#define PFMT_PREC 5 /* precision overflow */
|
|
#define PFMT_PRECMIX 6 /* bad mix of precision specifiers */
|
|
#define PFMT_WIDTH 7 /* width overflow */
|
|
#define PFMT_INPUTGAP 8 /* gap in arguments */
|
|
#define PFMT_WIDTHARG 9 /* attempted to use same arg twice, for width */
|
|
#define PFMT_PRECARG 10 /* attempted to use same arg twice, for prec */
|
|
#define PFMT_MANYSEGS 11 /* maxed out output segments */
|
|
|
|
static int parsefmt(const char *format,
|
|
struct outsegment *out,
|
|
struct va_input *in,
|
|
int *opieces,
|
|
int *ipieces, va_list arglist)
|
|
{
|
|
char *fmt = (char *)format;
|
|
int param_num = 0;
|
|
int param;
|
|
int width;
|
|
int precision;
|
|
unsigned int flags;
|
|
FormatType type;
|
|
int max_param = -1;
|
|
int i;
|
|
int ocount = 0;
|
|
unsigned char usedinput[MAX_PARAMETERS/8];
|
|
size_t outlen = 0;
|
|
struct outsegment *optr;
|
|
int use_dollar = DOLLAR_UNKNOWN;
|
|
char *start = fmt;
|
|
|
|
/* clear, set a bit for each used input */
|
|
memset(usedinput, 0, sizeof(usedinput));
|
|
|
|
while(*fmt) {
|
|
if(*fmt == '%') {
|
|
struct va_input *iptr;
|
|
bool loopit = TRUE;
|
|
fmt++;
|
|
outlen = (size_t)(fmt - start - 1);
|
|
if(*fmt == '%') {
|
|
/* this means a %% that should be output only as %. Create an output
|
|
segment. */
|
|
if(outlen) {
|
|
optr = &out[ocount++];
|
|
if(ocount > MAX_SEGMENTS)
|
|
return PFMT_MANYSEGS;
|
|
optr->input = 0;
|
|
optr->flags = FLAGS_SUBSTR;
|
|
optr->start = start;
|
|
optr->outlen = outlen;
|
|
}
|
|
start = fmt;
|
|
fmt++;
|
|
continue; /* while */
|
|
}
|
|
|
|
flags = 0;
|
|
width = precision = 0;
|
|
|
|
if(use_dollar != DOLLAR_NOPE) {
|
|
param = dollarstring(fmt, &fmt);
|
|
if(param < 0) {
|
|
if(use_dollar == DOLLAR_USE)
|
|
/* illegal combo */
|
|
return PFMT_DOLLAR;
|
|
|
|
/* we got no positional, just get the next arg */
|
|
param = -1;
|
|
use_dollar = DOLLAR_NOPE;
|
|
}
|
|
else
|
|
use_dollar = DOLLAR_USE;
|
|
}
|
|
else
|
|
param = -1;
|
|
|
|
/* Handle the flags */
|
|
while(loopit) {
|
|
switch(*fmt++) {
|
|
case ' ':
|
|
flags |= FLAGS_SPACE;
|
|
break;
|
|
case '+':
|
|
flags |= FLAGS_SHOWSIGN;
|
|
break;
|
|
case '-':
|
|
flags |= FLAGS_LEFT;
|
|
flags &= ~(unsigned int)FLAGS_PAD_NIL;
|
|
break;
|
|
case '#':
|
|
flags |= FLAGS_ALT;
|
|
break;
|
|
case '.':
|
|
if('*' == *fmt) {
|
|
/* The precision is picked from a specified parameter */
|
|
flags |= FLAGS_PRECPARAM;
|
|
fmt++;
|
|
|
|
if(use_dollar == DOLLAR_USE) {
|
|
precision = dollarstring(fmt, &fmt);
|
|
if(precision < 0)
|
|
/* illegal combo */
|
|
return PFMT_DOLLARPREC;
|
|
}
|
|
else
|
|
/* get it from the next argument */
|
|
precision = -1;
|
|
}
|
|
else {
|
|
bool is_neg = FALSE;
|
|
flags |= FLAGS_PREC;
|
|
precision = 0;
|
|
if('-' == *fmt) {
|
|
is_neg = TRUE;
|
|
fmt++;
|
|
}
|
|
while(ISDIGIT(*fmt)) {
|
|
int n = *fmt - '0';
|
|
if(precision > (INT_MAX - n) / 10)
|
|
return PFMT_PREC;
|
|
precision = precision * 10 + n;
|
|
fmt++;
|
|
}
|
|
if(is_neg)
|
|
precision = -precision;
|
|
}
|
|
if((flags & (FLAGS_PREC | FLAGS_PRECPARAM)) ==
|
|
(FLAGS_PREC | FLAGS_PRECPARAM))
|
|
/* it is not permitted to use both kinds of precision for the same
|
|
argument */
|
|
return PFMT_PRECMIX;
|
|
break;
|
|
case 'h':
|
|
flags |= FLAGS_SHORT;
|
|
break;
|
|
#if defined(_WIN32) || defined(_WIN32_WCE)
|
|
case 'I':
|
|
/* Non-ANSI integer extensions I32 I64 */
|
|
if((fmt[0] == '3') && (fmt[1] == '2')) {
|
|
flags |= FLAGS_LONG;
|
|
fmt += 2;
|
|
}
|
|
else if((fmt[0] == '6') && (fmt[1] == '4')) {
|
|
flags |= FLAGS_LONGLONG;
|
|
fmt += 2;
|
|
}
|
|
else {
|
|
#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
|
|
flags |= FLAGS_LONGLONG;
|
|
#else
|
|
flags |= FLAGS_LONG;
|
|
#endif
|
|
}
|
|
break;
|
|
#endif /* _WIN32 || _WIN32_WCE */
|
|
case 'l':
|
|
if(flags & FLAGS_LONG)
|
|
flags |= FLAGS_LONGLONG;
|
|
else
|
|
flags |= FLAGS_LONG;
|
|
break;
|
|
case 'L':
|
|
flags |= FLAGS_LONGDOUBLE;
|
|
break;
|
|
case 'q':
|
|
flags |= FLAGS_LONGLONG;
|
|
break;
|
|
case 'z':
|
|
/* the code below generates a warning if -Wunreachable-code is
|
|
used */
|
|
#if (SIZEOF_SIZE_T > SIZEOF_LONG)
|
|
flags |= FLAGS_LONGLONG;
|
|
#else
|
|
flags |= FLAGS_LONG;
|
|
#endif
|
|
break;
|
|
case 'O':
|
|
#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
|
|
flags |= FLAGS_LONGLONG;
|
|
#else
|
|
flags |= FLAGS_LONG;
|
|
#endif
|
|
break;
|
|
case '0':
|
|
if(!(flags & FLAGS_LEFT))
|
|
flags |= FLAGS_PAD_NIL;
|
|
FALLTHROUGH();
|
|
case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
flags |= FLAGS_WIDTH;
|
|
width = 0;
|
|
fmt--;
|
|
do {
|
|
int n = *fmt - '0';
|
|
if(width > (INT_MAX - n) / 10)
|
|
return PFMT_WIDTH;
|
|
width = width * 10 + n;
|
|
fmt++;
|
|
} while(ISDIGIT(*fmt));
|
|
break;
|
|
case '*': /* read width from argument list */
|
|
flags |= FLAGS_WIDTHPARAM;
|
|
if(use_dollar == DOLLAR_USE) {
|
|
width = dollarstring(fmt, &fmt);
|
|
if(width < 0)
|
|
/* illegal combo */
|
|
return PFMT_DOLLARWIDTH;
|
|
}
|
|
else
|
|
/* pick from the next argument */
|
|
width = -1;
|
|
break;
|
|
default:
|
|
loopit = FALSE;
|
|
fmt--;
|
|
break;
|
|
} /* switch */
|
|
} /* while */
|
|
|
|
switch(*fmt) {
|
|
case 'S':
|
|
flags |= FLAGS_ALT;
|
|
FALLTHROUGH();
|
|
case 's':
|
|
type = FORMAT_STRING;
|
|
break;
|
|
case 'n':
|
|
type = FORMAT_INTPTR;
|
|
break;
|
|
case 'p':
|
|
type = FORMAT_PTR;
|
|
break;
|
|
case 'd':
|
|
case 'i':
|
|
if(flags & FLAGS_LONGLONG)
|
|
type = FORMAT_LONGLONG;
|
|
else if(flags & FLAGS_LONG)
|
|
type = FORMAT_LONG;
|
|
else
|
|
type = FORMAT_INT;
|
|
break;
|
|
case 'u':
|
|
if(flags & FLAGS_LONGLONG)
|
|
type = FORMAT_LONGLONGU;
|
|
else if(flags & FLAGS_LONG)
|
|
type = FORMAT_LONGU;
|
|
else
|
|
type = FORMAT_INTU;
|
|
flags |= FLAGS_UNSIGNED;
|
|
break;
|
|
case 'o':
|
|
if(flags & FLAGS_LONGLONG)
|
|
type = FORMAT_LONGLONGU;
|
|
else if(flags & FLAGS_LONG)
|
|
type = FORMAT_LONGU;
|
|
else
|
|
type = FORMAT_INTU;
|
|
flags |= FLAGS_OCTAL|FLAGS_UNSIGNED;
|
|
break;
|
|
case 'x':
|
|
if(flags & FLAGS_LONGLONG)
|
|
type = FORMAT_LONGLONGU;
|
|
else if(flags & FLAGS_LONG)
|
|
type = FORMAT_LONGU;
|
|
else
|
|
type = FORMAT_INTU;
|
|
flags |= FLAGS_HEX|FLAGS_UNSIGNED;
|
|
break;
|
|
case 'X':
|
|
if(flags & FLAGS_LONGLONG)
|
|
type = FORMAT_LONGLONGU;
|
|
else if(flags & FLAGS_LONG)
|
|
type = FORMAT_LONGU;
|
|
else
|
|
type = FORMAT_INTU;
|
|
flags |= FLAGS_HEX|FLAGS_UPPER|FLAGS_UNSIGNED;
|
|
break;
|
|
case 'c':
|
|
type = FORMAT_INT;
|
|
flags |= FLAGS_CHAR;
|
|
break;
|
|
case 'f':
|
|
type = FORMAT_DOUBLE;
|
|
break;
|
|
case 'e':
|
|
type = FORMAT_DOUBLE;
|
|
flags |= FLAGS_FLOATE;
|
|
break;
|
|
case 'E':
|
|
type = FORMAT_DOUBLE;
|
|
flags |= FLAGS_FLOATE|FLAGS_UPPER;
|
|
break;
|
|
case 'g':
|
|
type = FORMAT_DOUBLE;
|
|
flags |= FLAGS_FLOATG;
|
|
break;
|
|
case 'G':
|
|
type = FORMAT_DOUBLE;
|
|
flags |= FLAGS_FLOATG|FLAGS_UPPER;
|
|
break;
|
|
default:
|
|
/* invalid instruction, disregard and continue */
|
|
continue;
|
|
} /* switch */
|
|
|
|
if(flags & FLAGS_WIDTHPARAM) {
|
|
if(width < 0)
|
|
width = param_num++;
|
|
else {
|
|
/* if this identifies a parameter already used, this
|
|
is illegal */
|
|
if(usedinput[width/8] & (1 << (width&7)))
|
|
return PFMT_WIDTHARG;
|
|
}
|
|
if(width >= MAX_PARAMETERS)
|
|
return PFMT_MANYARGS;
|
|
if(width >= max_param)
|
|
max_param = width;
|
|
|
|
in[width].type = FORMAT_WIDTH;
|
|
/* mark as used */
|
|
usedinput[width/8] |= (unsigned char)(1 << (width&7));
|
|
}
|
|
|
|
if(flags & FLAGS_PRECPARAM) {
|
|
if(precision < 0)
|
|
precision = param_num++;
|
|
else {
|
|
/* if this identifies a parameter already used, this
|
|
is illegal */
|
|
if(usedinput[precision/8] & (1 << (precision&7)))
|
|
return PFMT_PRECARG;
|
|
}
|
|
if(precision >= MAX_PARAMETERS)
|
|
return PFMT_MANYARGS;
|
|
if(precision >= max_param)
|
|
max_param = precision;
|
|
|
|
in[precision].type = FORMAT_PRECISION;
|
|
usedinput[precision/8] |= (unsigned char)(1 << (precision&7));
|
|
}
|
|
|
|
/* Handle the specifier */
|
|
if(param < 0)
|
|
param = param_num++;
|
|
if(param >= MAX_PARAMETERS)
|
|
return PFMT_MANYARGS;
|
|
if(param >= max_param)
|
|
max_param = param;
|
|
|
|
iptr = &in[param];
|
|
iptr->type = type;
|
|
|
|
/* mark this input as used */
|
|
usedinput[param/8] |= (unsigned char)(1 << (param&7));
|
|
|
|
fmt++;
|
|
optr = &out[ocount++];
|
|
if(ocount > MAX_SEGMENTS)
|
|
return PFMT_MANYSEGS;
|
|
optr->input = (unsigned int)param;
|
|
optr->flags = flags;
|
|
optr->width = width;
|
|
optr->precision = precision;
|
|
optr->start = start;
|
|
optr->outlen = outlen;
|
|
start = fmt;
|
|
}
|
|
else
|
|
fmt++;
|
|
}
|
|
|
|
/* is there a trailing piece */
|
|
outlen = (size_t)(fmt - start);
|
|
if(outlen) {
|
|
optr = &out[ocount++];
|
|
if(ocount > MAX_SEGMENTS)
|
|
return PFMT_MANYSEGS;
|
|
optr->input = 0;
|
|
optr->flags = FLAGS_SUBSTR;
|
|
optr->start = start;
|
|
optr->outlen = outlen;
|
|
}
|
|
|
|
/* Read the arg list parameters into our data list */
|
|
for(i = 0; i < max_param + 1; i++) {
|
|
struct va_input *iptr = &in[i];
|
|
if(!(usedinput[i/8] & (1 << (i&7))))
|
|
/* bad input */
|
|
return PFMT_INPUTGAP;
|
|
|
|
/* based on the type, read the correct argument */
|
|
switch(iptr->type) {
|
|
case FORMAT_STRING:
|
|
iptr->val.str = va_arg(arglist, char *);
|
|
break;
|
|
|
|
case FORMAT_INTPTR:
|
|
case FORMAT_PTR:
|
|
iptr->val.ptr = va_arg(arglist, void *);
|
|
break;
|
|
|
|
case FORMAT_LONGLONGU:
|
|
iptr->val.numu = (mp_uintmax_t)va_arg(arglist, mp_uintmax_t);
|
|
break;
|
|
|
|
case FORMAT_LONGLONG:
|
|
iptr->val.nums = (mp_intmax_t)va_arg(arglist, mp_intmax_t);
|
|
break;
|
|
|
|
case FORMAT_LONGU:
|
|
iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned long);
|
|
break;
|
|
|
|
case FORMAT_LONG:
|
|
iptr->val.nums = (mp_intmax_t)va_arg(arglist, long);
|
|
break;
|
|
|
|
case FORMAT_INTU:
|
|
iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned int);
|
|
break;
|
|
|
|
case FORMAT_INT:
|
|
case FORMAT_WIDTH:
|
|
case FORMAT_PRECISION:
|
|
iptr->val.nums = (mp_intmax_t)va_arg(arglist, int);
|
|
break;
|
|
|
|
case FORMAT_DOUBLE:
|
|
iptr->val.dnum = va_arg(arglist, double);
|
|
break;
|
|
|
|
default:
|
|
DEBUGASSERT(NULL); /* unexpected */
|
|
break;
|
|
}
|
|
}
|
|
*ipieces = max_param + 1;
|
|
*opieces = ocount;
|
|
|
|
return PFMT_OK;
|
|
}
|
|
|
|
/*
|
|
* formatf() - the general printf function.
|
|
*
|
|
* It calls parsefmt() to parse the format string. It populates two arrays;
|
|
* one that describes the input arguments and one that describes a number of
|
|
* output segments.
|
|
*
|
|
* On success, the input array describes the type of all arguments and their
|
|
* values.
|
|
*
|
|
* The function then iterates over the output segments and outputs them one
|
|
* by one until done. Using the appropriate input arguments (if any).
|
|
*
|
|
* All output is sent to the 'stream()' callback, one byte at a time.
|
|
*/
|
|
|
|
static int formatf(
|
|
void *userp, /* untouched by format(), just sent to the stream() function in
|
|
the second argument */
|
|
/* function pointer called for each output character */
|
|
int (*stream)(unsigned char, void *),
|
|
const char *format, /* %-formatted string */
|
|
va_list ap_save) /* list of parameters */
|
|
{
|
|
static const char nilstr[] = "(nil)";
|
|
const char *digits = lower_digits; /* Base-36 digits for numbers. */
|
|
int done = 0; /* number of characters written */
|
|
int i;
|
|
int ocount = 0; /* number of output segments */
|
|
int icount = 0; /* number of input arguments */
|
|
|
|
struct outsegment output[MAX_SEGMENTS];
|
|
struct va_input input[MAX_PARAMETERS];
|
|
char work[BUFFSIZE + 2];
|
|
|
|
/* 'workend' points to the final buffer byte position, but with an extra
|
|
byte as margin to avoid the (FALSE?) warning Coverity gives us
|
|
otherwise */
|
|
char *workend = &work[BUFFSIZE - 2];
|
|
|
|
/* Parse the format string */
|
|
if(parsefmt(format, output, input, &ocount, &icount, ap_save))
|
|
return 0;
|
|
|
|
for(i = 0; i < ocount; i++) {
|
|
struct outsegment *optr = &output[i];
|
|
struct va_input *iptr;
|
|
bool is_alt; /* Format spec modifiers. */
|
|
int width; /* Width of a field. */
|
|
int prec; /* Precision of a field. */
|
|
bool is_neg; /* Decimal integer is negative. */
|
|
unsigned long base; /* Base of a number to be written. */
|
|
mp_uintmax_t num; /* Integral values to be written. */
|
|
mp_intmax_t signed_num; /* Used to convert negative in positive. */
|
|
char *w;
|
|
size_t outlen = optr->outlen;
|
|
unsigned int flags = optr->flags;
|
|
|
|
if(outlen) {
|
|
char *str = optr->start;
|
|
for(; outlen && *str; outlen--)
|
|
OUTCHAR(*str++);
|
|
if(optr->flags & FLAGS_SUBSTR)
|
|
/* this is just a substring */
|
|
continue;
|
|
}
|
|
|
|
/* pick up the specified width */
|
|
if(flags & FLAGS_WIDTHPARAM) {
|
|
width = (int)input[optr->width].val.nums;
|
|
if(width < 0) {
|
|
/* "A negative field width is taken as a '-' flag followed by a
|
|
positive field width." */
|
|
if(width == INT_MIN)
|
|
width = INT_MAX;
|
|
else
|
|
width = -width;
|
|
flags |= FLAGS_LEFT;
|
|
flags &= ~(unsigned int)FLAGS_PAD_NIL;
|
|
}
|
|
}
|
|
else
|
|
width = optr->width;
|
|
|
|
/* pick up the specified precision */
|
|
if(flags & FLAGS_PRECPARAM) {
|
|
prec = (int)input[optr->precision].val.nums;
|
|
if(prec < 0)
|
|
/* "A negative precision is taken as if the precision were
|
|
omitted." */
|
|
prec = -1;
|
|
}
|
|
else if(flags & FLAGS_PREC)
|
|
prec = optr->precision;
|
|
else
|
|
prec = -1;
|
|
|
|
is_alt = (flags & FLAGS_ALT) ? 1 : 0;
|
|
iptr = &input[optr->input];
|
|
|
|
switch(iptr->type) {
|
|
case FORMAT_INTU:
|
|
case FORMAT_LONGU:
|
|
case FORMAT_LONGLONGU:
|
|
flags |= FLAGS_UNSIGNED;
|
|
FALLTHROUGH();
|
|
case FORMAT_INT:
|
|
case FORMAT_LONG:
|
|
case FORMAT_LONGLONG:
|
|
num = iptr->val.numu;
|
|
if(flags & FLAGS_CHAR) {
|
|
/* Character. */
|
|
if(!(flags & FLAGS_LEFT))
|
|
while(--width > 0)
|
|
OUTCHAR(' ');
|
|
OUTCHAR((char) num);
|
|
if(flags & FLAGS_LEFT)
|
|
while(--width > 0)
|
|
OUTCHAR(' ');
|
|
break;
|
|
}
|
|
if(flags & FLAGS_OCTAL) {
|
|
/* Octal unsigned integer */
|
|
base = 8;
|
|
is_neg = FALSE;
|
|
}
|
|
else if(flags & FLAGS_HEX) {
|
|
/* Hexadecimal unsigned integer */
|
|
digits = (flags & FLAGS_UPPER) ? upper_digits : lower_digits;
|
|
base = 16;
|
|
is_neg = FALSE;
|
|
}
|
|
else if(flags & FLAGS_UNSIGNED) {
|
|
/* Decimal unsigned integer */
|
|
base = 10;
|
|
is_neg = FALSE;
|
|
}
|
|
else {
|
|
/* Decimal integer. */
|
|
base = 10;
|
|
|
|
is_neg = (iptr->val.nums < (mp_intmax_t)0);
|
|
if(is_neg) {
|
|
/* signed_num might fail to hold absolute negative minimum by 1 */
|
|
signed_num = iptr->val.nums + (mp_intmax_t)1;
|
|
signed_num = -signed_num;
|
|
num = (mp_uintmax_t)signed_num;
|
|
num += (mp_uintmax_t)1;
|
|
}
|
|
}
|
|
number:
|
|
/* Supply a default precision if none was given. */
|
|
if(prec == -1)
|
|
prec = 1;
|
|
|
|
/* Put the number in WORK. */
|
|
w = workend;
|
|
switch(base) {
|
|
case 10:
|
|
while(num > 0) {
|
|
*w-- = (char)('0' + (num % 10));
|
|
num /= 10;
|
|
}
|
|
break;
|
|
default:
|
|
while(num > 0) {
|
|
*w-- = digits[num % base];
|
|
num /= base;
|
|
}
|
|
break;
|
|
}
|
|
width -= (int)(workend - w);
|
|
prec -= (int)(workend - w);
|
|
|
|
if(is_alt && base == 8 && prec <= 0) {
|
|
*w-- = '0';
|
|
--width;
|
|
}
|
|
|
|
if(prec > 0) {
|
|
width -= prec;
|
|
while(prec-- > 0 && w >= work)
|
|
*w-- = '0';
|
|
}
|
|
|
|
if(is_alt && base == 16)
|
|
width -= 2;
|
|
|
|
if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE))
|
|
--width;
|
|
|
|
if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL))
|
|
while(width-- > 0)
|
|
OUTCHAR(' ');
|
|
|
|
if(is_neg)
|
|
OUTCHAR('-');
|
|
else if(flags & FLAGS_SHOWSIGN)
|
|
OUTCHAR('+');
|
|
else if(flags & FLAGS_SPACE)
|
|
OUTCHAR(' ');
|
|
|
|
if(is_alt && base == 16) {
|
|
OUTCHAR('0');
|
|
if(flags & FLAGS_UPPER)
|
|
OUTCHAR('X');
|
|
else
|
|
OUTCHAR('x');
|
|
}
|
|
|
|
if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL))
|
|
while(width-- > 0)
|
|
OUTCHAR('0');
|
|
|
|
/* Write the number. */
|
|
while(++w <= workend) {
|
|
OUTCHAR(*w);
|
|
}
|
|
|
|
if(flags & FLAGS_LEFT)
|
|
while(width-- > 0)
|
|
OUTCHAR(' ');
|
|
break;
|
|
|
|
case FORMAT_STRING: {
|
|
const char *str;
|
|
size_t len;
|
|
|
|
str = (char *)iptr->val.str;
|
|
if(!str) {
|
|
/* Write null string if there is space. */
|
|
if(prec == -1 || prec >= (int) sizeof(nilstr) - 1) {
|
|
str = nilstr;
|
|
len = sizeof(nilstr) - 1;
|
|
/* Disable quotes around (nil) */
|
|
flags &= ~(unsigned int)FLAGS_ALT;
|
|
}
|
|
else {
|
|
str = "";
|
|
len = 0;
|
|
}
|
|
}
|
|
else if(prec != -1)
|
|
len = (size_t)prec;
|
|
else if(*str == '\0')
|
|
len = 0;
|
|
else
|
|
len = strlen(str);
|
|
|
|
width -= (len > INT_MAX) ? INT_MAX : (int)len;
|
|
|
|
if(flags & FLAGS_ALT)
|
|
OUTCHAR('"');
|
|
|
|
if(!(flags & FLAGS_LEFT))
|
|
while(width-- > 0)
|
|
OUTCHAR(' ');
|
|
|
|
for(; len && *str; len--)
|
|
OUTCHAR(*str++);
|
|
if(flags & FLAGS_LEFT)
|
|
while(width-- > 0)
|
|
OUTCHAR(' ');
|
|
|
|
if(flags & FLAGS_ALT)
|
|
OUTCHAR('"');
|
|
break;
|
|
}
|
|
|
|
case FORMAT_PTR:
|
|
/* Generic pointer. */
|
|
if(iptr->val.ptr) {
|
|
/* If the pointer is not NULL, write it as a %#x spec. */
|
|
base = 16;
|
|
digits = (flags & FLAGS_UPPER) ? upper_digits : lower_digits;
|
|
is_alt = TRUE;
|
|
num = (size_t) iptr->val.ptr;
|
|
is_neg = FALSE;
|
|
goto number;
|
|
}
|
|
else {
|
|
/* Write "(nil)" for a nil pointer. */
|
|
const char *point;
|
|
|
|
width -= (int)(sizeof(nilstr) - 1);
|
|
if(flags & FLAGS_LEFT)
|
|
while(width-- > 0)
|
|
OUTCHAR(' ');
|
|
for(point = nilstr; *point != '\0'; ++point)
|
|
OUTCHAR(*point);
|
|
if(!(flags & FLAGS_LEFT))
|
|
while(width-- > 0)
|
|
OUTCHAR(' ');
|
|
}
|
|
break;
|
|
|
|
case FORMAT_DOUBLE: {
|
|
char formatbuf[32]="%";
|
|
char *fptr = &formatbuf[1];
|
|
size_t left = sizeof(formatbuf)-strlen(formatbuf);
|
|
int len;
|
|
|
|
if(flags & FLAGS_WIDTH)
|
|
width = optr->width;
|
|
|
|
if(flags & FLAGS_PREC)
|
|
prec = optr->precision;
|
|
|
|
if(flags & FLAGS_LEFT)
|
|
*fptr++ = '-';
|
|
if(flags & FLAGS_SHOWSIGN)
|
|
*fptr++ = '+';
|
|
if(flags & FLAGS_SPACE)
|
|
*fptr++ = ' ';
|
|
if(flags & FLAGS_ALT)
|
|
*fptr++ = '#';
|
|
|
|
*fptr = 0;
|
|
|
|
if(width >= 0) {
|
|
size_t dlen;
|
|
if(width >= BUFFSIZE)
|
|
width = BUFFSIZE - 1;
|
|
/* RECURSIVE USAGE */
|
|
dlen = (size_t)curl_msnprintf(fptr, left, "%d", width);
|
|
fptr += dlen;
|
|
left -= dlen;
|
|
}
|
|
if(prec >= 0) {
|
|
/* for each digit in the integer part, we can have one less
|
|
precision */
|
|
int maxprec = BUFFSIZE - 1;
|
|
double val = iptr->val.dnum;
|
|
if(prec > maxprec)
|
|
prec = maxprec - 1;
|
|
if(width > 0 && prec <= width)
|
|
maxprec -= width;
|
|
while(val >= 10.0) {
|
|
val /= 10;
|
|
maxprec--;
|
|
}
|
|
|
|
if(prec > maxprec)
|
|
prec = maxprec - 1;
|
|
if(prec < 0)
|
|
prec = 0;
|
|
/* RECURSIVE USAGE */
|
|
len = curl_msnprintf(fptr, left, ".%d", prec);
|
|
fptr += len;
|
|
}
|
|
if(flags & FLAGS_LONG)
|
|
*fptr++ = 'l';
|
|
|
|
if(flags & FLAGS_FLOATE)
|
|
*fptr++ = (char)((flags & FLAGS_UPPER) ? 'E' : 'e');
|
|
else if(flags & FLAGS_FLOATG)
|
|
*fptr++ = (char)((flags & FLAGS_UPPER) ? 'G' : 'g');
|
|
else
|
|
*fptr++ = 'f';
|
|
|
|
*fptr = 0; /* and a final null-termination */
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wformat-nonliteral"
|
|
#endif
|
|
/* NOTE NOTE NOTE!! Not all sprintf implementations return number of
|
|
output characters */
|
|
#ifdef HAVE_SNPRINTF
|
|
(snprintf)(work, BUFFSIZE, formatbuf, iptr->val.dnum); /* NOLINT */
|
|
#ifdef _WIN32
|
|
/* Old versions of the Windows CRT do not terminate the snprintf output
|
|
buffer if it reaches the max size so we do that here. */
|
|
work[BUFFSIZE - 1] = 0;
|
|
#endif
|
|
#else
|
|
(sprintf)(work, formatbuf, iptr->val.dnum);
|
|
#endif
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
DEBUGASSERT(strlen(work) < BUFFSIZE);
|
|
for(fptr = work; *fptr; fptr++)
|
|
OUTCHAR(*fptr);
|
|
break;
|
|
}
|
|
|
|
case FORMAT_INTPTR:
|
|
/* Answer the count of characters written. */
|
|
#ifdef HAVE_LONG_LONG_TYPE
|
|
if(flags & FLAGS_LONGLONG)
|
|
*(LONG_LONG_TYPE *) iptr->val.ptr = (LONG_LONG_TYPE)done;
|
|
else
|
|
#endif
|
|
if(flags & FLAGS_LONG)
|
|
*(long *) iptr->val.ptr = (long)done;
|
|
else if(!(flags & FLAGS_SHORT))
|
|
*(int *) iptr->val.ptr = (int)done;
|
|
else
|
|
*(short *) iptr->val.ptr = (short)done;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return done;
|
|
}
|
|
|
|
/* fputc() look-alike */
|
|
static int addbyter(unsigned char outc, void *f)
|
|
{
|
|
struct nsprintf *infop = f;
|
|
if(infop->length < infop->max) {
|
|
/* only do this if we have not reached max length yet */
|
|
*infop->buffer++ = (char)outc; /* store */
|
|
infop->length++; /* we are now one byte larger */
|
|
return 0; /* fputc() returns like this on success */
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format,
|
|
va_list ap_save)
|
|
{
|
|
int retcode;
|
|
struct nsprintf info;
|
|
|
|
info.buffer = buffer;
|
|
info.length = 0;
|
|
info.max = maxlength;
|
|
|
|
retcode = formatf(&info, addbyter, format, ap_save);
|
|
if(info.max) {
|
|
/* we terminate this with a zero byte */
|
|
if(info.max == info.length) {
|
|
/* we are at maximum, scrap the last letter */
|
|
info.buffer[-1] = 0;
|
|
DEBUGASSERT(retcode);
|
|
retcode--; /* do not count the nul byte */
|
|
}
|
|
else
|
|
info.buffer[0] = 0;
|
|
}
|
|
return retcode;
|
|
}
|
|
|
|
int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...)
|
|
{
|
|
int retcode;
|
|
va_list ap_save; /* argument pointer */
|
|
va_start(ap_save, format);
|
|
retcode = curl_mvsnprintf(buffer, maxlength, format, ap_save);
|
|
va_end(ap_save);
|
|
return retcode;
|
|
}
|
|
|
|
/* fputc() look-alike */
|
|
static int alloc_addbyter(unsigned char outc, void *f)
|
|
{
|
|
struct asprintf *infop = f;
|
|
CURLcode result = Curl_dyn_addn(infop->b, &outc, 1);
|
|
if(result) {
|
|
infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM;
|
|
return 1 ; /* fail */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* appends the formatted string, returns MERR error code */
|
|
int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save)
|
|
{
|
|
struct asprintf info;
|
|
info.b = dyn;
|
|
info.merr = MERR_OK;
|
|
|
|
(void)formatf(&info, alloc_addbyter, format, ap_save);
|
|
if(info.merr) {
|
|
Curl_dyn_free(info.b);
|
|
return info.merr;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
char *curl_mvaprintf(const char *format, va_list ap_save)
|
|
{
|
|
struct asprintf info;
|
|
struct dynbuf dyn;
|
|
info.b = &dyn;
|
|
Curl_dyn_init(info.b, DYN_APRINTF);
|
|
info.merr = MERR_OK;
|
|
|
|
(void)formatf(&info, alloc_addbyter, format, ap_save);
|
|
if(info.merr) {
|
|
Curl_dyn_free(info.b);
|
|
return NULL;
|
|
}
|
|
if(Curl_dyn_len(info.b))
|
|
return Curl_dyn_ptr(info.b);
|
|
return strdup("");
|
|
}
|
|
|
|
char *curl_maprintf(const char *format, ...)
|
|
{
|
|
va_list ap_save;
|
|
char *s;
|
|
va_start(ap_save, format);
|
|
s = curl_mvaprintf(format, ap_save);
|
|
va_end(ap_save);
|
|
return s;
|
|
}
|
|
|
|
static int storebuffer(unsigned char outc, void *f)
|
|
{
|
|
char **buffer = f;
|
|
**buffer = (char)outc;
|
|
(*buffer)++;
|
|
return 0;
|
|
}
|
|
|
|
int curl_msprintf(char *buffer, const char *format, ...)
|
|
{
|
|
va_list ap_save; /* argument pointer */
|
|
int retcode;
|
|
va_start(ap_save, format);
|
|
retcode = formatf(&buffer, storebuffer, format, ap_save);
|
|
va_end(ap_save);
|
|
*buffer = 0; /* we terminate this with a zero byte */
|
|
return retcode;
|
|
}
|
|
|
|
static int fputc_wrapper(unsigned char outc, void *f)
|
|
{
|
|
int out = outc;
|
|
FILE *s = f;
|
|
int rc = fputc(out, s);
|
|
return rc == EOF;
|
|
}
|
|
|
|
int curl_mprintf(const char *format, ...)
|
|
{
|
|
int retcode;
|
|
va_list ap_save; /* argument pointer */
|
|
va_start(ap_save, format);
|
|
|
|
retcode = formatf(stdout, fputc_wrapper, format, ap_save);
|
|
va_end(ap_save);
|
|
return retcode;
|
|
}
|
|
|
|
int curl_mfprintf(FILE *whereto, const char *format, ...)
|
|
{
|
|
int retcode;
|
|
va_list ap_save; /* argument pointer */
|
|
va_start(ap_save, format);
|
|
retcode = formatf(whereto, fputc_wrapper, format, ap_save);
|
|
va_end(ap_save);
|
|
return retcode;
|
|
}
|
|
|
|
int curl_mvsprintf(char *buffer, const char *format, va_list ap_save)
|
|
{
|
|
int retcode = formatf(&buffer, storebuffer, format, ap_save);
|
|
*buffer = 0; /* we terminate this with a zero byte */
|
|
return retcode;
|
|
}
|
|
|
|
int curl_mvprintf(const char *format, va_list ap_save)
|
|
{
|
|
return formatf(stdout, fputc_wrapper, format, ap_save);
|
|
}
|
|
|
|
int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save)
|
|
{
|
|
return formatf(whereto, fputc_wrapper, format, ap_save);
|
|
}
|