Make it possible to build curl for Windows CE using the CeGCC toolchain.
With both CMake and autotools, including tests and examples, also in CI.
The build configuration is the default one with Schannel enabled. No
3rd-party dependencies have been tested.
Also revive old code to make Schannel build with Windows CE, including
certificate verification.
Builds have been throughougly tested. But, I've made no functional tests
for this PR. Some parts (esp. file operations, like truncate and seek)
are stubbed out and likely broken as a result. Test servers build, but
they do not work on Windows CE. This patch substitutes `fstat()` calls
with `stat()`, which operate on filenames, not file handles. This may or
may not work and/or may not be secure.
About CeGCC: I used the latest available macOS binary build v0.59.1
r1397 from 2009, in native `mingw32ce` build mode. CeGCC is in effect
MinGW + GCC 4.4.0 + old/classic-mingw Windows headers. It targets
Windows CE v3.0 according to its `_WIN32_WCE` value. It means this PR
restores portions of old/classic-mingw support. It makes the Windows CE
codepath compatible with GCC 4.4.0. It also adds workaround for CMake,
which cannot identify and configure this toolchain out of the box.
Notes:
- CMake doesn't recognize CeGCC/mingw32ce, necessitating tricks as seen
with Amiga and MS-DOS.
- CMake doesn't set `MINGW` for mingw32ce. Set it and `MINGW32CE`
manually as a helper variable, in addition to `WINCE` which CMake sets
based on `CMAKE_SYSTEM_NAME`.
- CMake fails to create an implib for `libcurl.dll`, due to not
recognizing the platform as a Windowsy one. This patch adds the
necessary workaround to make it work.
- headers shipping with CeGCC miss some things curl needs for Schannel
support. Fixed by restoring and renovating code previously deleted
old-mingw code.
- it's sometime non-trivial to figure out if a fallout is WinCE,
mingw32ce, old-mingw, or GCC version-specific.
- WinCE is always Unicode. With exceptions: no `wmain`,
`GetProcAddress()`.
- `_fileno()` is said to convert from `FILE *` to `void *` which is
a Win32 file `HANDLE`. (This patch doesn't use this, but with further
effort it probably could be.)
https://stackoverflow.com/questions/3989545/how-do-i-get-the-file-handle-from-the-fopen-file-structure
- WinCE has no signals, current directory, stdio/CRT file handles, no
`_get_osfhandle()`, no `errno`, no `errno.h`. Some of this stuff is
standard C89, yet missing from this platform. Microsoft expects
Windows CE apps to use Win32 file API and `FILE *` exclusively.
- revived CeGCC here (not tested for this PR):
https://building.enlyze.com/posts/a-new-windows-ce-x86-compiler-in-2024/
On `UNDER_CE` vs. `_WIN32_WCE`: (This patch settled on `UNDER_CE`)
- A custom VS2008 WinCE toolchain does not set any of these.
The compiler binaries don't contain these strings, and has no compiler
option for targeting WinCE, hinting that a vanilla toolchain isn't
setting any of them either.
- `UNDER_CE` is automatically defined by the CeGCC compiler.
https://cegcc.sourceforge.net/docs/details.html
- `UNDER_CE` is similar to `_WIN32`, except it's not set automatically
by all compilers. It's not supposed to have any value, like a version.
(Though e.g. OpenSSL sets it to a version)
- `_WIN32_WCE` is the CE counterpart of the non-CE `_WIN32_WINNT` macro.
That does return the targeted Windows CE version.
- `_WIN32_WCE` is not defined by compilers, and relies on a header
setting it to a default, or the build to set it to the desired target
version. This is also how `_WIN32_WINNT` works.
- `_WIN32_WCE` default is set by `windef.h` in CeGCC.
- `_WIN32_WCE` isn't set to a default by MSVC Windows CE headers (the
ones I checked at least).
- CMake sets `_WIN32_WCE=<ver>`, `UNDER_CE`, `WINCE` for MSVC WinCE.
- `_WIN32_WCE` seems more popular in other projects, including CeGCC
itself. `zlib` is a notable exception amongst curl dependencies,
which uses `UNDER_CE`.
- Since `_WIN32_WCE` needs "certain" headers to have it defined, it's
undefined depending on headers included beforehand.
- `curl/curl.h` re-uses `_WIN32_WCE`'s as a self-guard, relying on
its not-(necessarily)-defined-by-default property:
25b445e479/include/curl/curl.h (L77)
Toolchain downloads:
- Windows:
https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_cygwin1.7_r1399.tar.bz2
- macOS Intel:
https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_snowleopard_r1397.tar.bz2
Closes #15975
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;
|
|
#ifdef _WIN32
|
|
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 */
|
|
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);
|
|
}
|