blob: 973e86b11bd20c88fb9bb7aaf978432f59272ffb [file] [edit]
/*
* Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
/*
* C99 snprintf and vsnprintf emulation for MSVC versions earlier than
* Visual Studio 2015 (_MSC_VER < 1900). Those compilers ship _snprintf
* and _vsnprintf with non-C99 semantics (return -1 on truncation, no
* guaranteed NUL termination) and do not provide the standard names at
* all. This file supplies the missing C99 names.
*
* This file is only compiled when the configured target is one of the
* Windows MSVC 2013 compatibility variants; on every other platform
* the standard library already provides these symbols.
*
* IMPORTANT: This translation unit MUST define snprintf and vsnprintf
* and nothing else. This single file is compiled directly into libcrypto,
* libssl, and the apps; each references it from its own build.info rather
* than keeping a copy. Because each definition lives in its own .obj, the
* linker's archive-search rule ensures only one copy is pulled into any
* final binary. Defining any additional symbol here would risk pulling
* multiple copies and producing a duplicate-symbol link error.
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <openssl/bio.h>
#include <openssl/crypto.h>
/*
* _MSC_VER described here:
* https://learn.microsoft.com/en-us/cpp/overview/compiler-versions?view=msvc-170
*
* Beginning with the UCRT in Visual Studio 2015 and Windows 10, snprintf is no
* longer identical to _snprintf. The snprintf behavior is now C99 standard
* conformant. The difference is that if you run out of buffer, snprintf
* null-terminates the end of the buffer and returns the number of characters
* that would have been required whereas _snprintf doesn't null-terminate the
* buffer and returns -1. Also, snprintf() includes one more character in the
* output because it doesn't null-terminate the buffer.
* [ https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/snprintf-snprintf-snprintf-l-snwprintf-snwprintf-l?view=msvc-170#remarks
*
* for older MSVC (older than 2015) we can use _vscprintf() and _vsnprintf()
* as suggested here:
* https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
*
*/
int msvc_translate_printf_format(const char *format, const char **out,
char **tmp)
{
/* Valid printf conversion specifiers, grouped by category: signed
* integers (d i), unsigned (o u x X), floating-point (f F e E g G a A),
* misc (c s p n) and MSVC-specific (S Z C). */
static const char conv[] = "diouxXfFeEgGaAcspnSZC";
const char *p = format;
char *dst = NULL, *q = NULL;
/*
* The VS 2013 CRT does not understand the C99 z, t and j length
* modifiers. Translate z and t to I (both are pointer-sized on Windows)
* and j to I64 (intmax_t is 64 bits). Every input character expands to
* at most three output characters (j -> I64), so 3 * length is a safe
* bound for the buffer.
*
* This is done in a single pass: nothing is allocated until the first
* modifier is seen, so formats that need no translation return the
* original string untouched. EMIT_CHAR() appends a character to the
* output once the buffer exists; before that it is a no-op.
*/
#define EMIT_CHAR(c) \
do { \
if (dst != NULL) \
*q++ = (c); \
} while (0)
*out = format;
*tmp = NULL;
while (*p != '\0') {
if (*p != '%') { /* literal character */
EMIT_CHAR(*p);
p++;
continue;
}
p++; /* consume '%' */
if (*p == '%') { /* literal "%%" */
EMIT_CHAR('%');
EMIT_CHAR('%');
p++;
continue;
}
EMIT_CHAR('%');
while (*p != '\0' && strchr(conv, *p) == NULL) {
char c = *p++;
if (c != 'z' && c != 't' && c != 'j') { /* verbatim */
EMIT_CHAR(c);
continue;
}
if (dst == NULL) { /* first modifier: allocate + flush prefix */
size_t len = strlen(format);
if (len > (SIZE_MAX - 1) / 3) /* make static analysis happy */
return 0;
dst = (char *)OPENSSL_malloc(3 * len + 1);
if (dst == NULL)
return 0;
q = dst;
memcpy(q, format, (size_t)(p - 1 - format));
q += p - 1 - format;
}
EMIT_CHAR('I');
if (c == 'j') {
EMIT_CHAR('6');
EMIT_CHAR('4');
}
}
if (*p != '\0') { /* copy the conversion specifier */
EMIT_CHAR(*p);
p++;
}
}
#undef EMIT_CHAR
if (dst != NULL) {
*q = '\0';
*out = dst;
*tmp = dst;
}
return 1;
}
int vsnprintf(char *buf, size_t n, const char *format, va_list args)
{
int count = -1;
va_list args_copy;
char *fmt_alloc = NULL;
const char *fmt;
if (!msvc_translate_printf_format(format, &fmt, &fmt_alloc))
goto done;
va_copy(args_copy, args);
count = _vscprintf(fmt, args_copy);
va_end(args_copy);
if (count < 0)
goto done;
if (n > 0)
(void)_vsnprintf_s(buf, n, _TRUNCATE, fmt, args);
done:
OPENSSL_free(fmt_alloc);
return count;
}
int snprintf(char *buf, size_t n, const char *fmt, ...)
{
va_list args;
int ret;
va_start(args, fmt);
ret = vsnprintf(buf, n, fmt, args);
va_end(args);
return ret;
}