Files
wget2/libwget/buffer_printf.c
Tim Rühsen 18165409bb Fix gcc 15 -Wunterminated-string-initialization
* libwget/base64.c (base64_encode): Drop explicit error length.
* libwget/buffer_printf.c (convert_pointer): Likewise.

Just cosmetics - allows to build with -Werror.
2025-11-09 19:43:29 +01:00

577 lines
14 KiB
C

/*
* Copyright (c) 2012 Tim Ruehsen
* Copyright (c) 2015-2024 Free Software Foundation, Inc.
*
* This file is part of libwget.
*
* Libwget is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Libwget is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libwget. If not, see <https://www.gnu.org/licenses/>.
*
*
* Memory buffer printf routines
*
* Changelog
* 24.09.2012 Tim Ruehsen created
*
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <c-ctype.h>
#include <wget.h>
#include "private.h"
/**
* \file
* \brief Buffer management functions
* \defgroup libwget-buffer Buffer management functions
* @{
*/
/* \cond _hide_internal_symbols */
#define FLAG_ZERO_PADDED 1U
#define FLAG_LEFT_ADJUST 2U
#define FLAG_ALTERNATE 4U
#define FLAG_SIGNED 8U
#define FLAG_DECIMAL 16U
#define FLAG_OCTAL 32U
#define FLAG_HEXLO 64U
#define FLAG_HEXUP 128U
/* \endcond */
static void copy_string(wget_buffer *buf, unsigned int flags, int field_width, int precision, const char *arg)
{
size_t length;
if (!arg) {
wget_buffer_strcat(buf, "(null)");
return;
}
if (precision >= 0) {
length = strnlen(arg, precision);
} else {
length = strlen(arg);
}
// debug_printf("flags=0x%02x field_width=%d precision=%d length=%zu arg='%s'\n",
// flags,field_width,precision,length,arg);
if (field_width) {
if ((unsigned)field_width > length) {
if (flags & FLAG_LEFT_ADJUST) {
wget_buffer_memcat(buf, arg, length);
wget_buffer_memset_append(buf, ' ', field_width - length);
} else {
wget_buffer_memset_append(buf, ' ', field_width - length);
wget_buffer_memcat(buf, arg, length);
}
} else {
wget_buffer_memcat(buf, arg, length);
}
} else {
wget_buffer_memcat(buf, arg, length);
}
}
static void convert_dec_fast(wget_buffer *buf, int arg)
{
char str[32]; // long enough to hold decimal long long
char *dst = str + sizeof(str) - 1;
int minus;
if (arg < 0) {
minus = 1;
arg = -arg;
} else
minus = 0;
while (arg >= 10) {
*dst-- = (arg % 10) + '0';
arg /= 10;
}
*dst-- = (arg % 10) + '0';
if (minus)
*dst-- = '-';
wget_buffer_memcat(buf, dst + 1, sizeof(str) - (dst - str) - 1);
}
static void convert_dec(wget_buffer *buf, unsigned int flags, int field_width, int precision, long long arg)
{
unsigned long long argu = (unsigned long long) arg;
char str[32], minus = 0; // long enough to hold decimal long long
char *dst = str + sizeof(str) - 1;
unsigned char c;
size_t length;
// info_printf("arg1 = %lld %lld\n",arg,-arg);
if (flags & FLAG_DECIMAL) {
if (flags & FLAG_SIGNED && arg < 0) {
minus = 1;
argu = -arg;
}
while (argu) {
*dst-- = argu % 10 + '0';
argu /= 10;
}
} else if (flags & FLAG_HEXLO) {
while (argu) {
// slightly faster than having a HEX[] lookup table
*dst-- = (c = (argu & 0xf)) >= 10 ? c + 'a' - 10 : c + '0';
argu >>= 4;
}
} else if (flags & FLAG_HEXUP) {
while (argu) {
// slightly faster than having a HEX[] lookup table
*dst-- = (c = (argu & 0xf)) >= 10 ? c + 'A' - 10 : c + '0';
argu >>= 4;
}
} else if (flags & FLAG_OCTAL) {
while (argu) {
*dst-- = (argu & 0x07) + '0';
argu >>= 3;
}
}
// info_printf("arg2 = %lld\n",arg);
dst++;
length = sizeof(str) - (dst - str);
if (precision < 0) {
precision = 1;
} else {
flags &= ~FLAG_ZERO_PADDED;
}
// info_printf("flags=0x%02x field_width=%d precision=%d length=%zd dst='%.*s'\n",
// flags,field_width,precision,length,length,dst);
if (field_width) {
if ((unsigned)field_width > length + minus) {
if (flags & FLAG_LEFT_ADJUST) {
if (minus)
wget_buffer_memset_append(buf, '-', 1);
if (length < (unsigned)precision) {
wget_buffer_memset_append(buf, '0', precision - length);
wget_buffer_memcat(buf, dst, length);
if (field_width > precision + minus)
wget_buffer_memset_append(buf, ' ', field_width - precision - minus);
} else {
wget_buffer_memcat(buf, dst, length);
wget_buffer_memset_append(buf, ' ', field_width - length - minus);
}
} else {
if (length < (unsigned)precision) {
if (field_width > precision + minus) {
if (flags & FLAG_ZERO_PADDED) {
if (minus)
wget_buffer_memset_append(buf, '-', 1);
wget_buffer_memset_append(buf, '0', field_width - precision - minus);
} else {
wget_buffer_memset_append(buf, ' ', field_width - precision - minus);
if (minus)
wget_buffer_memset_append(buf, '-', 1);
}
} else {
if (minus)
wget_buffer_memset_append(buf, '-', 1);
}
wget_buffer_memset_append(buf, '0', precision - length);
} else {
if (flags & FLAG_ZERO_PADDED) {
if (minus)
wget_buffer_memset_append(buf, '-', 1);
wget_buffer_memset_append(buf, '0', field_width - length - minus);
} else {
wget_buffer_memset_append(buf, ' ', field_width - length - minus);
if (minus)
wget_buffer_memset_append(buf, '-', 1);
}
}
wget_buffer_memcat(buf, dst, length);
}
} else {
if (minus)
wget_buffer_memset_append(buf, '-', 1);
if (length < (unsigned)precision)
wget_buffer_memset_append(buf, '0', precision - length);
wget_buffer_memcat(buf, dst, length);
}
} else {
if (minus)
wget_buffer_memset_append(buf, '-', 1);
if (length < (unsigned)precision)
wget_buffer_memset_append(buf, '0', precision - length);
wget_buffer_memcat(buf, dst, length);
}
}
static void convert_pointer(wget_buffer *buf, void *pointer)
{
static const char HEX[] = "0123456789abcdef";
char str[32]; // long enough to hold hexadecimal pointer
char *dst;
int length;
size_t arg;
if (!pointer) {
wget_buffer_memcat(buf, "0x0", 3);
return;
} else {
wget_buffer_memcat(buf, "0x", 2);
}
// convert to a size_t (covers full address room) tp allow integer arithmetic
arg = (size_t)pointer;
length = 0;
dst = str + sizeof(str);
*--dst = 0;
do {
*--dst = HEX[arg&0xF];
arg >>= 4;
length++;
} while (arg);
wget_buffer_memcat(buf, dst, length);
}
static const char *read_precision(const char *p, int *out, bool precision_is_external)
{
int precision;
if (precision_is_external) {
precision = *out;
if (precision < 0 )
precision = 0;
p++;
} else if (c_isdigit(*p)) {
precision = 0;
do {
precision = precision * 10 + (*p - '0');
} while (c_isdigit(*++p));
} else {
precision = -1;
}
*out = precision;
return p;
}
static const char *read_flag_chars(const char *p, unsigned int *out)
{
unsigned int flags;
for (flags = 0; *p; p++) {
if (*p == '0')
flags |= FLAG_ZERO_PADDED;
else if (*p == '-')
flags |= FLAG_LEFT_ADJUST;
else if (*p == '#')
flags |= FLAG_ALTERNATE;
else
break;
}
*out = flags;
return p;
}
static const char *read_field_width(const char *p, int *out, unsigned int *flags, bool width_is_external)
{
int field_width;
if (width_is_external) {
field_width = *out;
if (field_width < 0) {
*flags |= FLAG_LEFT_ADJUST;
field_width = -field_width;
}
p++;
} else {
for (field_width = 0; c_isdigit(*p); p++)
field_width = field_width * 10 + (*p - '0');
}
*out = field_width;
return p;
}
/**
* \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
* \param[in] fmt A `printf(3)`-like format string
* \param[in] args A `va_list` with the format string placeholders' values
* \return Length of the buffer after appending the formatted string
*
* Formats the string \p fmt (with `printf(3)`-like args) and appends the result to the end
* of the buffer \p buf (using wget_buffer_memcat()).
*
* For more information, see `vprintf(3)`.
*/
size_t wget_buffer_vprintf_append(wget_buffer *buf, const char *fmt, va_list args)
{
const char *p = fmt, *begin;
int field_width, precision;
unsigned int flags;
long long arg;
unsigned long long argu;
if (!p)
return 0;
for (;*p;) {
/*
* Collect plain char sequence.
* Walk the string until we find a '%' character.
*/
for (begin = p; *p && *p != '%'; p++);
if (p != begin)
wget_buffer_memcat(buf, begin, p - begin);
if (!*p)
break;
/* Shortcut to %s and %p, handle %% */
if (*++p == 's') {
const char *s = va_arg(args, const char *);
wget_buffer_strcat(buf, s ? s : "(null)");
p++;
continue;
} else if (*p == 'd') {
convert_dec_fast(buf, va_arg(args, int));
p++;
continue;
} else if (*p == 'c') {
char c = (char ) va_arg(args, int);
wget_buffer_memcat(buf, &c, 1);
p++;
continue;
} else if (*p == 'p') {
convert_pointer(buf, va_arg(args, void *));
p++;
continue;
} else if (*p == '%') {
wget_buffer_memset_append(buf, '%', 1);
p++;
continue;
}
/* Read the flag chars (optional, simplified) */
p = read_flag_chars(p, &flags);
/*
* Read field width (optional).
* If '*', then the field width is given as an additional argument,
* which precedes the argument to be formatted.
*/
if (*p == '*') {
field_width = va_arg(args, int);
p = read_field_width(p, &field_width, &flags, 1);
} else {
p = read_field_width(p, &field_width, &flags, 0);
}
/*
* Read precision (optional).
* If '*', the precision is given as an additional argument,
* just as the case for the field width.
*/
if (*p == '.') {
if (*++p == '*') {
precision = va_arg(args, int);
p = read_precision(p, &precision, 1);
} else {
p = read_precision(p, &precision, 0);
}
} else
precision = -1;
/* Read length modifier (optional) */
switch (*p) {
case 'z':
arg = va_arg(args, ssize_t);
argu = (size_t)arg;
p++;
break;
case 'l':
if (p[1] == 'l') {
p += 2;
arg = va_arg(args, long long);
argu = (unsigned long long)arg;
} else {
p++;
arg = (long)va_arg(args, long);
argu = (unsigned long)arg;
}
break;
case 'L':
p++;
arg = va_arg(args, long long);
argu = (unsigned long long)arg;
break;
case 'h':
if (p[1] == 'h') {
p += 2;
arg = (signed char) va_arg(args, int);
argu = (unsigned char) arg;
} else {
p++;
arg = (short) va_arg(args, int);
argu = (unsigned short) arg;
}
break;
case 's':
p++;
copy_string(buf, flags, field_width, precision, va_arg(args, const char *));
continue;
case 'c':
{
char c[2] = { (char) va_arg(args, int), 0 };
p++;
copy_string(buf, flags, field_width, precision, c);
continue;
}
case 'p': // %p shortcut
p++;
convert_dec(buf, flags | FLAG_HEXLO | FLAG_ALTERNATE, field_width, precision, (long long)(ptrdiff_t)va_arg(args, void *));
continue;
default:
arg = va_arg(args, int);
argu = (unsigned int)arg;
}
if (*p == 'd' || *p == 'i') {
convert_dec(buf, flags | FLAG_SIGNED | FLAG_DECIMAL, field_width, precision, arg);
} else if (*p == 'u') {
convert_dec(buf, flags | FLAG_DECIMAL, field_width, precision, (long long) argu);
} else if (*p == 'x') {
convert_dec(buf, flags | FLAG_HEXLO, field_width, precision, (long long) argu);
} else if (*p == 'X') {
convert_dec(buf, flags | FLAG_HEXUP, field_width, precision, (long long) argu);
} else if (*p == 'o') {
convert_dec(buf, flags | FLAG_OCTAL, field_width, precision, (long long) argu);
} else {
/*
* This is an unknown conversion specifier,
* so just put '%' and move on.
*/
wget_buffer_memset_append(buf, '%', 1);
p = begin + 1;
continue;
}
p++;
}
return buf->length;
}
/**
* \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
* \param[in] fmt A `printf(3)`-like format string
* \param[in] args A `va_list` with the format string placeholders' values
* \return Length of the buffer after appending the formatted string
*
* Formats the string \p fmt (with `printf(3)`-like args) and overwrites the contents
* of the buffer \p buf with that formatted string.
*
* This is equivalent to the following code:
*
* buf->length = 0;
* wget_buffer_vprintf_append(buf, fmt, args);
*
* For more information, see `vprintf(3)`.
*/
size_t wget_buffer_vprintf(wget_buffer *buf, const char *fmt, va_list args)
{
buf->length = 0;
return wget_buffer_vprintf_append(buf, fmt, args);
}
/**
* \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
* \param[in] fmt A `printf(3)`-like format string
* \param[in] ... Variable arguments
* \return Length of the buffer after appending the formatted string
*
* Formats the string \p fmt (with `printf(3)`-like args) and appends the result to the end
* of the buffer \p buf (using wget_buffer_memcat()).
*
* This function is equivalent to wget_buffer_vprintf_append(), except in that it uses
* a variable number of arguments rather than a `va_list`.
*
* For more information, see `printf(3)`.
*/
size_t wget_buffer_printf_append(wget_buffer *buf, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
wget_buffer_vprintf_append(buf, fmt, args);
va_end(args);
return buf->length;
}
/**
* \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
* \param[in] fmt A `printf(3)`-like format string
* \param[in] ... Variable arguments
* \return Length of the buffer after appending the formatted string
*
* Formats the string \p fmt (with `printf(3)`-like args) and overwrites the contents
* of the buffer \p buf with that formatted string.
*
* This function is equivalent to wget_buffer_vprintf(), except in that it uses
* a variable number of arguments rather than a `va_list`.
*
* For more information, see `printf(3)`.
*/
size_t wget_buffer_printf(wget_buffer *buf, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
size_t len = wget_buffer_vprintf(buf, fmt, args);
va_end(args);
return len;
}
/** @} */