1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1999 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.haxx.se/docs/copyright.html.
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
23 * A merge of Bjorn Reese's format() function and Daniel's dsprintf()
24 * 1.0. A full blooded printf() clone with full support for <num>$
25 * everywhere (parameters, widths and precisions) including variabled
26 * sized parameters (like doubles, long longs, long doubles and even
27 * void * in 64-bit architectures).
29 * Current restrictions:
30 * - Max 128 parameters
31 * - No 'long double' support.
33 * If you ever want truly portable and good *printf() clones, the project that
34 * took on from here is named 'Trio' and you find more details on the trio web
35 * page at https://daniel.haxx.se/projects/trio/
38 #include "curl_setup.h"
39 #include <curl/mprintf.h>
41 #include "curl_memory.h"
42 /* The last #include file should be: */
46 * If SIZEOF_SIZE_T has not been defined, default to the size of long.
50 # define LONG_LONG_TYPE long long
51 # define HAVE_LONG_LONG_TYPE
53 # if defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64)
54 # define LONG_LONG_TYPE __int64
55 # define HAVE_LONG_LONG_TYPE
57 # undef LONG_LONG_TYPE
58 # undef HAVE_LONG_LONG_TYPE
63 * Non-ANSI integer extensions
66 #if (defined(__BORLANDC__) && (__BORLANDC__ >= 0x520)) || \
67 (defined(__WATCOMC__) && defined(__386__)) || \
68 (defined(__POCC__) && defined(_MSC_VER)) || \
69 (defined(_WIN32_WCE)) || \
70 (defined(__MINGW32__)) || \
71 (defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64))
72 # define MP_HAVE_INT_EXTENSIONS
76 * Max integer data types that mprintf.c is capable
79 #ifdef HAVE_LONG_LONG_TYPE
80 # define mp_intmax_t LONG_LONG_TYPE
81 # define mp_uintmax_t unsigned LONG_LONG_TYPE
83 # define mp_intmax_t long
84 # define mp_uintmax_t unsigned long
87 #define BUFFSIZE 326 /* buffer for long-to-str and float-to-str calcs, should
88 fit negative DBL_MAX (317 letters) */
89 #define MAX_PARAMETERS 128 /* lame static limit */
95 /* Lower-case digits. */
96 static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
98 /* Upper-case digits. */
99 static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
103 if(stream((unsigned char)(x), (FILE *)data) != -1) \
106 return done; /* return immediately on failure */ \
109 /* Data type to read from the arglist */
120 FORMAT_WIDTH /* For internal use */
123 /* conversion and display flags */
127 FLAGS_SHOWSIGN = 1<<1,
132 FLAGS_LONGLONG = 1<<6,
133 FLAGS_LONGDOUBLE = 1<<7,
134 FLAGS_PAD_NIL = 1<<8,
135 FLAGS_UNSIGNED = 1<<9,
139 FLAGS_WIDTH = 1<<13, /* '*' or '*<num>$' used */
140 FLAGS_WIDTHPARAM = 1<<14, /* width PARAMETER was specified */
141 FLAGS_PREC = 1<<15, /* precision was specified */
142 FLAGS_PRECPARAM = 1<<16, /* precision PARAMETER was specified */
143 FLAGS_CHAR = 1<<17, /* %c story */
144 FLAGS_FLOATE = 1<<18, /* %e or %E */
145 FLAGS_FLOATG = 1<<19 /* %g or %G */
151 long width; /* width OR width parameter number */
152 long precision; /* precision OR precision parameter number */
157 mp_intmax_t as_signed;
158 mp_uintmax_t as_unsigned;
171 char *buffer; /* allocated buffer */
172 size_t len; /* length of string */
173 size_t alloc; /* length of alloc */
174 int fail; /* (!= 0) if an alloc has failed and thus
175 the output is not the complete data */
178 static long dprintf_DollarString(char *input, char **end)
181 while(ISDIGIT(*input)) {
183 number += *input-'0';
186 if(number && ('$'==*input++)) {
193 static bool dprintf_IsQualifierNoDollar(const char *fmt)
195 #if defined(MP_HAVE_INT_EXTENSIONS)
196 if(!strncmp(fmt, "I32", 3) || !strncmp(fmt, "I64", 3)) {
202 case '-': case '+': case ' ': case '#': case '.':
203 case '0': case '1': case '2': case '3': case '4':
204 case '5': case '6': case '7': case '8': case '9':
205 case 'h': case 'l': case 'L': case 'z': case 'q':
207 #if defined(MP_HAVE_INT_EXTENSIONS)
217 /******************************************************************
220 * Create an index with the type of each parameter entry and its
221 * value (may vary in size)
223 * Returns zero on success.
225 ******************************************************************/
227 static int dprintf_Pass1(const char *format, va_stack_t *vto, char **endpos,
230 char *fmt = (char *)format;
243 continue; /* while */
248 /* Handle the positional case (N$) */
252 this_param = dprintf_DollarString(fmt, &fmt);
254 /* we got no positional, get the next counter */
255 this_param = param_num;
257 if(this_param > max_param)
258 max_param = this_param;
261 * The parameter with number 'i' should be used. Next, we need
262 * to get SIZE and TYPE of the parameter. Add the information
269 /* Handle the flags */
271 while(dprintf_IsQualifierNoDollar(fmt)) {
272 #if defined(MP_HAVE_INT_EXTENSIONS)
273 if(!strncmp(fmt, "I32", 3)) {
277 else if(!strncmp(fmt, "I64", 3)) {
278 flags |= FLAGS_LONGLONG;
286 flags |= FLAGS_SPACE;
289 flags |= FLAGS_SHOWSIGN;
293 flags &= ~FLAGS_PAD_NIL;
300 /* The precision is picked from a specified parameter */
302 flags |= FLAGS_PRECPARAM;
306 i = dprintf_DollarString(fmt, &fmt);
310 precision = param_num;
312 if(precision > max_param)
313 max_param = precision;
317 precision = strtol(fmt, &fmt, 10);
321 flags |= FLAGS_SHORT;
323 #if defined(MP_HAVE_INT_EXTENSIONS)
325 #if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
326 flags |= FLAGS_LONGLONG;
333 if(flags & FLAGS_LONG)
334 flags |= FLAGS_LONGLONG;
339 flags |= FLAGS_LONGDOUBLE;
342 flags |= FLAGS_LONGLONG;
345 /* the code below generates a warning if -Wunreachable-code is
347 #if (SIZEOF_SIZE_T > SIZEOF_LONG)
348 flags |= FLAGS_LONGLONG;
354 #if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
355 flags |= FLAGS_LONGLONG;
361 if(!(flags & FLAGS_LEFT))
362 flags |= FLAGS_PAD_NIL;
364 case '1': case '2': case '3': case '4':
365 case '5': case '6': case '7': case '8': case '9':
366 flags |= FLAGS_WIDTH;
367 width = strtol(fmt-1, &fmt, 10);
369 case '*': /* Special case */
370 flags |= FLAGS_WIDTHPARAM;
373 i = dprintf_DollarString(fmt, &fmt);
378 if(width > max_param)
386 /* Handle the specifier */
390 if((i < 0) || (i >= MAX_PARAMETERS))
391 /* out of allowed range */
399 vto[i].type = FORMAT_STRING;
402 vto[i].type = FORMAT_INTPTR;
405 vto[i].type = FORMAT_PTR;
408 vto[i].type = FORMAT_INT;
411 vto[i].type = FORMAT_INT;
412 flags |= FLAGS_UNSIGNED;
415 vto[i].type = FORMAT_INT;
416 flags |= FLAGS_OCTAL;
419 vto[i].type = FORMAT_INT;
420 flags |= FLAGS_HEX|FLAGS_UNSIGNED;
423 vto[i].type = FORMAT_INT;
424 flags |= FLAGS_HEX|FLAGS_UPPER|FLAGS_UNSIGNED;
427 vto[i].type = FORMAT_INT;
431 vto[i].type = FORMAT_DOUBLE;
434 vto[i].type = FORMAT_DOUBLE;
435 flags |= FLAGS_FLOATE;
438 vto[i].type = FORMAT_DOUBLE;
439 flags |= FLAGS_FLOATE|FLAGS_UPPER;
442 vto[i].type = FORMAT_DOUBLE;
443 flags |= FLAGS_FLOATG;
446 vto[i].type = FORMAT_DOUBLE;
447 flags |= FLAGS_FLOATG|FLAGS_UPPER;
450 vto[i].type = FORMAT_UNKNOWN;
454 vto[i].flags = flags;
455 vto[i].width = width;
456 vto[i].precision = precision;
458 if(flags & FLAGS_WIDTHPARAM) {
459 /* we have the width specified from a parameter, so we make that
460 parameter's info setup properly */
463 vto[k].type = FORMAT_WIDTH;
464 vto[k].flags = FLAGS_NEW;
465 /* can't use width or precision of width! */
467 vto[k].precision = 0;
469 if(flags & FLAGS_PRECPARAM) {
470 /* we have the precision specified from a parameter, so we make that
471 parameter's info setup properly */
472 long k = precision - 1;
473 vto[i].precision = k;
474 vto[k].type = FORMAT_WIDTH;
475 vto[k].flags = FLAGS_NEW;
476 /* can't use width or precision of width! */
478 vto[k].precision = 0;
480 *endpos++ = fmt + 1; /* end of this sequence */
484 /* Read the arg list parameters into our data list */
485 for(i = 0; i<max_param; i++) {
486 /* Width/precision arguments must be read before the main argument
487 they are attached to */
488 if(vto[i].flags & FLAGS_WIDTHPARAM) {
489 vto[vto[i].width].data.num.as_signed =
490 (mp_intmax_t)va_arg(arglist, int);
492 if(vto[i].flags & FLAGS_PRECPARAM) {
493 vto[vto[i].precision].data.num.as_signed =
494 (mp_intmax_t)va_arg(arglist, int);
497 switch(vto[i].type) {
499 vto[i].data.str = va_arg(arglist, char *);
505 vto[i].data.ptr = va_arg(arglist, void *);
509 #ifdef HAVE_LONG_LONG_TYPE
510 if((vto[i].flags & FLAGS_LONGLONG) && (vto[i].flags & FLAGS_UNSIGNED))
511 vto[i].data.num.as_unsigned =
512 (mp_uintmax_t)va_arg(arglist, mp_uintmax_t);
513 else if(vto[i].flags & FLAGS_LONGLONG)
514 vto[i].data.num.as_signed =
515 (mp_intmax_t)va_arg(arglist, mp_intmax_t);
519 if((vto[i].flags & FLAGS_LONG) && (vto[i].flags & FLAGS_UNSIGNED))
520 vto[i].data.num.as_unsigned =
521 (mp_uintmax_t)va_arg(arglist, unsigned long);
522 else if(vto[i].flags & FLAGS_LONG)
523 vto[i].data.num.as_signed =
524 (mp_intmax_t)va_arg(arglist, long);
525 else if(vto[i].flags & FLAGS_UNSIGNED)
526 vto[i].data.num.as_unsigned =
527 (mp_uintmax_t)va_arg(arglist, unsigned int);
529 vto[i].data.num.as_signed =
530 (mp_intmax_t)va_arg(arglist, int);
535 vto[i].data.dnum = va_arg(arglist, double);
539 /* Argument has been read. Silently convert it into an integer
542 vto[i].type = FORMAT_INT;
554 static int dprintf_formatf(
555 void *data, /* untouched by format(), just sent to the stream() function in
556 the second argument */
557 /* function pointer called for each output character */
558 int (*stream)(int, FILE *),
559 const char *format, /* %-formatted string */
560 va_list ap_save) /* list of parameters */
562 /* Base-36 digits for numbers. */
563 const char *digits = lower_digits;
565 /* Pointer into the format string. */
568 /* Number of characters written. */
571 long param; /* current parameter to read */
572 long param_num = 0; /* parameter counter */
574 va_stack_t vto[MAX_PARAMETERS];
575 char *endpos[MAX_PARAMETERS];
582 /* 'workend' points to the final buffer byte position, but with an extra
583 byte as margin to avoid the (false?) warning Coverity gives us
585 char *workend = &work[sizeof(work) - 2];
587 /* Do the actual %-code parsing */
588 if(dprintf_Pass1(format, vto, endpos, ap_save))
591 end = &endpos[0]; /* the initial end-position from the list dprintf_Pass1()
596 /* Format spec modifiers. */
599 /* Width of a field. */
602 /* Precision of a field. */
605 /* Decimal integer is negative. */
608 /* Base of a number to be written. */
611 /* Integral values to be written. */
614 /* Used to convert negative in positive. */
615 mp_intmax_t signed_num;
620 /* This isn't a format spec, so write everything out until the next one
621 OR end of string is reached. */
624 } while(*++f && ('%' != *f));
630 /* Check for "%%". Note that although the ANSI standard lists
631 '%' as a conversion specifier, it says "The complete format
632 specification shall be `%%'," so we can avoid all the width
633 and precision processing. */
640 /* If this is a positional parameter, the position must follow immediately
641 after the %, thus create a %<num>$ sequence */
642 param = dprintf_DollarString(f, &f);
649 param_num++; /* increase this always to allow "%2$s %1$s %s" and then the
650 third %s will pick the 3rd argument */
654 /* pick up the specified width */
655 if(p->flags & FLAGS_WIDTHPARAM) {
656 width = (long)vto[p->width].data.num.as_signed;
657 param_num++; /* since the width is extracted from a parameter, we
658 must skip that to get to the next one properly */
660 /* "A negative field width is taken as a '-' flag followed by a
661 positive field width." */
663 p->flags |= FLAGS_LEFT;
664 p->flags &= ~FLAGS_PAD_NIL;
670 /* pick up the specified precision */
671 if(p->flags & FLAGS_PRECPARAM) {
672 prec = (long)vto[p->precision].data.num.as_signed;
673 param_num++; /* since the precision is extracted from a parameter, we
674 must skip that to get to the next one properly */
676 /* "A negative precision is taken as if the precision were
680 else if(p->flags & FLAGS_PREC)
685 is_alt = (p->flags & FLAGS_ALT) ? 1 : 0;
689 num = p->data.num.as_unsigned;
690 if(p->flags & FLAGS_CHAR) {
692 if(!(p->flags & FLAGS_LEFT))
696 if(p->flags & FLAGS_LEFT)
701 if(p->flags & FLAGS_OCTAL) {
702 /* Octal unsigned integer. */
704 goto unsigned_number;
706 else if(p->flags & FLAGS_HEX) {
707 /* Hexadecimal unsigned integer. */
709 digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits;
711 goto unsigned_number;
713 else if(p->flags & FLAGS_UNSIGNED) {
714 /* Decimal unsigned integer. */
716 goto unsigned_number;
719 /* Decimal integer. */
722 is_neg = (p->data.num.as_signed < (mp_intmax_t)0) ? 1 : 0;
724 /* signed_num might fail to hold absolute negative minimum by 1 */
725 signed_num = p->data.num.as_signed + (mp_intmax_t)1;
726 signed_num = -signed_num;
727 num = (mp_uintmax_t)signed_num;
728 num += (mp_uintmax_t)1;
734 /* Unsigned number of base BASE. */
738 /* Number of base BASE. */
740 /* Supply a default precision if none was given. */
744 /* Put the number in WORK. */
747 *w-- = digits[num % base];
750 width -= (long)(workend - w);
751 prec -= (long)(workend - w);
753 if(is_alt && base == 8 && prec <= 0) {
764 if(is_alt && base == 16)
767 if(is_neg || (p->flags & FLAGS_SHOWSIGN) || (p->flags & FLAGS_SPACE))
770 if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL))
776 else if(p->flags & FLAGS_SHOWSIGN)
778 else if(p->flags & FLAGS_SPACE)
781 if(is_alt && base == 16) {
783 if(p->flags & FLAGS_UPPER)
789 if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL))
793 /* Write the number. */
794 while(++w <= workend) {
798 if(p->flags & FLAGS_LEFT)
806 static const char null[] = "(nil)";
810 str = (char *) p->data.str;
812 /* Write null[] if there's space. */
813 if(prec == -1 || prec >= (long) sizeof(null) - 1) {
815 len = sizeof(null) - 1;
816 /* Disable quotes around (nil) */
817 p->flags &= (~FLAGS_ALT);
829 width -= (len > LONG_MAX) ? LONG_MAX : (long)len;
831 if(p->flags & FLAGS_ALT)
834 if(!(p->flags&FLAGS_LEFT))
838 while((len-- > 0) && *str)
840 if(p->flags&FLAGS_LEFT)
844 if(p->flags & FLAGS_ALT)
850 /* Generic pointer. */
853 ptr = (void *) p->data.ptr;
855 /* If the pointer is not NULL, write it as a %#x spec. */
857 digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits;
864 /* Write "(nil)" for a nil pointer. */
865 static const char strnil[] = "(nil)";
868 width -= (long)(sizeof(strnil) - 1);
869 if(p->flags & FLAGS_LEFT)
872 for(point = strnil; *point != '\0'; ++point)
874 if(! (p->flags & FLAGS_LEFT))
883 char formatbuf[32]="%";
884 char *fptr = &formatbuf[1];
885 size_t left = sizeof(formatbuf)-strlen(formatbuf);
889 if(p->flags & FLAGS_WIDTH)
891 else if(p->flags & FLAGS_WIDTHPARAM)
892 width = (long)vto[p->width].data.num.as_signed;
895 if(p->flags & FLAGS_PREC)
897 else if(p->flags & FLAGS_PRECPARAM)
898 prec = (long)vto[p->precision].data.num.as_signed;
900 if(p->flags & FLAGS_LEFT)
902 if(p->flags & FLAGS_SHOWSIGN)
904 if(p->flags & FLAGS_SPACE)
906 if(p->flags & FLAGS_ALT)
912 if(width >= (long)sizeof(work))
913 width = sizeof(work)-1;
914 /* RECURSIVE USAGE */
915 len = curl_msnprintf(fptr, left, "%ld", width);
920 /* for each digit in the integer part, we can have one less
922 size_t maxprec = sizeof(work) - 2;
923 double val = p->data.dnum;
929 if(prec > (long)maxprec)
930 prec = (long)maxprec-1;
931 /* RECURSIVE USAGE */
932 len = curl_msnprintf(fptr, left, ".%ld", prec);
935 if(p->flags & FLAGS_LONG)
938 if(p->flags & FLAGS_FLOATE)
939 *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'E':'e');
940 else if(p->flags & FLAGS_FLOATG)
941 *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'G' : 'g');
945 *fptr = 0; /* and a final zero termination */
947 /* NOTE NOTE NOTE!! Not all sprintf implementations return number of
949 (sprintf)(work, formatbuf, p->data.dnum);
950 DEBUGASSERT(strlen(work) <= sizeof(work));
951 for(fptr = work; *fptr; fptr++)
957 /* Answer the count of characters written. */
958 #ifdef HAVE_LONG_LONG_TYPE
959 if(p->flags & FLAGS_LONGLONG)
960 *(LONG_LONG_TYPE *) p->data.ptr = (LONG_LONG_TYPE)done;
963 if(p->flags & FLAGS_LONG)
964 *(long *) p->data.ptr = (long)done;
965 else if(!(p->flags & FLAGS_SHORT))
966 *(int *) p->data.ptr = (int)done;
968 *(short *) p->data.ptr = (short)done;
974 f = *end++; /* goto end of %-code */
980 /* fputc() look-alike */
981 static int addbyter(int output, FILE *data)
983 struct nsprintf *infop = (struct nsprintf *)data;
984 unsigned char outc = (unsigned char)output;
986 if(infop->length < infop->max) {
987 /* only do this if we haven't reached max length yet */
988 infop->buffer[0] = outc; /* store */
989 infop->buffer++; /* increase pointer */
990 infop->length++; /* we are now one byte larger */
991 return outc; /* fputc() returns like this on success */
996 int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format,
1000 struct nsprintf info;
1002 info.buffer = buffer;
1004 info.max = maxlength;
1006 retcode = dprintf_formatf(&info, addbyter, format, ap_save);
1007 if((retcode != -1) && info.max) {
1008 /* we terminate this with a zero byte */
1009 if(info.max == info.length)
1010 /* we're at maximum, scrap the last letter */
1011 info.buffer[-1] = 0;
1018 int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...)
1021 va_list ap_save; /* argument pointer */
1022 va_start(ap_save, format);
1023 retcode = curl_mvsnprintf(buffer, maxlength, format, ap_save);
1028 /* fputc() look-alike */
1029 static int alloc_addbyter(int output, FILE *data)
1031 struct asprintf *infop = (struct asprintf *)data;
1032 unsigned char outc = (unsigned char)output;
1034 if(!infop->buffer) {
1035 infop->buffer = malloc(32);
1036 if(!infop->buffer) {
1038 return -1; /* fail */
1043 else if(infop->len + 1 >= infop->alloc) {
1044 char *newptr = NULL;
1045 size_t newsize = infop->alloc*2;
1047 /* detect wrap-around or other overflow problems */
1048 if(newsize > infop->alloc)
1049 newptr = realloc(infop->buffer, newsize);
1053 return -1; /* fail */
1055 infop->buffer = newptr;
1056 infop->alloc = newsize;
1059 infop->buffer[ infop->len ] = outc;
1063 return outc; /* fputc() returns like this on success */
1066 char *curl_maprintf(const char *format, ...)
1068 va_list ap_save; /* argument pointer */
1070 struct asprintf info;
1077 va_start(ap_save, format);
1078 retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save);
1080 if((-1 == retcode) || info.fail) {
1086 info.buffer[info.len] = 0; /* we terminate this with a zero byte */
1092 char *curl_mvaprintf(const char *format, va_list ap_save)
1095 struct asprintf info;
1102 retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save);
1103 if((-1 == retcode) || info.fail) {
1110 info.buffer[info.len] = 0; /* we terminate this with a zero byte */
1116 static int storebuffer(int output, FILE *data)
1118 char **buffer = (char **)data;
1119 unsigned char outc = (unsigned char)output;
1122 return outc; /* act like fputc() ! */
1125 int curl_msprintf(char *buffer, const char *format, ...)
1127 va_list ap_save; /* argument pointer */
1129 va_start(ap_save, format);
1130 retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save);
1132 *buffer = 0; /* we terminate this with a zero byte */
1136 int curl_mprintf(const char *format, ...)
1139 va_list ap_save; /* argument pointer */
1140 va_start(ap_save, format);
1142 retcode = dprintf_formatf(stdout, fputc, format, ap_save);
1147 int curl_mfprintf(FILE *whereto, const char *format, ...)
1150 va_list ap_save; /* argument pointer */
1151 va_start(ap_save, format);
1152 retcode = dprintf_formatf(whereto, fputc, format, ap_save);
1157 int curl_mvsprintf(char *buffer, const char *format, va_list ap_save)
1160 retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save);
1161 *buffer = 0; /* we terminate this with a zero byte */
1165 int curl_mvprintf(const char *format, va_list ap_save)
1167 return dprintf_formatf(stdout, fputc, format, ap_save);
1170 int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save)
1172 return dprintf_formatf(whereto, fputc, format, ap_save);