Coverage Report

Created: 2025-01-28 06:58

/src/wget2/libwget/buffer_printf.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2012 Tim Ruehsen
3
 * Copyright (c) 2015-2024 Free Software Foundation, Inc.
4
 *
5
 * This file is part of libwget.
6
 *
7
 * Libwget is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * Libwget is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libwget.  If not, see <https://www.gnu.org/licenses/>.
19
 *
20
 *
21
 * Memory buffer printf routines
22
 *
23
 * Changelog
24
 * 24.09.2012  Tim Ruehsen  created
25
 *
26
 */
27
28
#include <config.h>
29
30
#include <stdio.h>
31
#include <stdlib.h>
32
#include <string.h>
33
#include <stdbool.h>
34
#include <c-ctype.h>
35
36
#include <wget.h>
37
#include "private.h"
38
39
/**
40
 * \file
41
 * \brief Buffer management functions
42
 * \defgroup libwget-buffer Buffer management functions
43
 * @{
44
 */
45
46
/* \cond _hide_internal_symbols */
47
0
#define FLAG_ZERO_PADDED   1U
48
0
#define FLAG_LEFT_ADJUST   2U
49
0
#define FLAG_ALTERNATE     4U
50
0
#define FLAG_SIGNED        8U
51
0
#define FLAG_DECIMAL      16U
52
0
#define FLAG_OCTAL        32U
53
0
#define FLAG_HEXLO        64U
54
0
#define FLAG_HEXUP       128U
55
/* \endcond */
56
57
static void copy_string(wget_buffer *buf, unsigned int flags, int field_width, int precision, const char *arg)
58
0
{
59
0
  size_t length;
60
61
0
  if (!arg) {
62
0
    wget_buffer_strcat(buf, "(null)");
63
0
    return;
64
0
  }
65
66
0
  if (precision >= 0) {
67
0
    length = strnlen(arg, precision);
68
0
  } else {
69
0
    length = strlen(arg);
70
0
  }
71
72
  // debug_printf("flags=0x%02x field_width=%d precision=%d length=%zu arg='%s'\n",
73
  //  flags,field_width,precision,length,arg);
74
75
0
  if (field_width) {
76
0
    if ((unsigned)field_width > length) {
77
0
      if (flags & FLAG_LEFT_ADJUST) {
78
0
        wget_buffer_memcat(buf, arg, length);
79
0
        wget_buffer_memset_append(buf, ' ', field_width - length);
80
0
      } else {
81
0
        wget_buffer_memset_append(buf, ' ', field_width - length);
82
0
        wget_buffer_memcat(buf, arg, length);
83
0
      }
84
0
    } else {
85
0
      wget_buffer_memcat(buf, arg, length);
86
0
    }
87
0
  } else {
88
0
    wget_buffer_memcat(buf, arg, length);
89
0
  }
90
0
}
91
92
static void convert_dec_fast(wget_buffer *buf, int arg)
93
0
{
94
0
  char str[32]; // long enough to hold decimal long long
95
0
  char *dst = str + sizeof(str) - 1;
96
0
  int minus;
97
98
0
  if (arg < 0) {
99
0
    minus = 1;
100
0
    arg = -arg;
101
0
  } else
102
0
    minus = 0;
103
104
0
  while (arg >= 10) {
105
0
    *dst-- = (arg % 10) + '0';
106
0
    arg /= 10;
107
0
  }
108
0
  *dst-- = (arg % 10) + '0';
109
110
0
  if (minus)
111
0
    *dst-- = '-';
112
113
0
  wget_buffer_memcat(buf, dst + 1, sizeof(str) - (dst - str) - 1);
114
0
}
115
116
static void convert_dec(wget_buffer *buf, unsigned int flags, int field_width, int precision, long long arg)
117
0
{
118
0
  unsigned long long argu = (unsigned long long) arg;
119
0
  char str[32], minus = 0; // long enough to hold decimal long long
120
0
  char *dst = str + sizeof(str) - 1;
121
0
  unsigned char c;
122
0
  size_t length;
123
124
  // info_printf("arg1 = %lld %lld\n",arg,-arg);
125
126
0
  if (flags & FLAG_DECIMAL) {
127
0
    if (flags & FLAG_SIGNED && arg < 0) {
128
0
      minus = 1;
129
0
      argu = -arg;
130
0
    }
131
132
0
    while (argu) {
133
0
      *dst-- = argu % 10 + '0';
134
0
      argu /= 10;
135
0
    }
136
0
  } else if (flags & FLAG_HEXLO) {
137
0
    while (argu) {
138
      // slightly faster than having a HEX[] lookup table
139
0
      *dst-- = (c = (argu & 0xf)) >= 10 ? c + 'a' - 10 : c + '0';
140
0
      argu >>= 4;
141
0
    }
142
0
  } else if (flags & FLAG_HEXUP) {
143
0
    while (argu) {
144
      // slightly faster than having a HEX[] lookup table
145
0
      *dst-- = (c = (argu & 0xf)) >= 10 ? c + 'A' - 10 : c + '0';
146
0
      argu >>= 4;
147
0
    }
148
0
  } else if (flags & FLAG_OCTAL) {
149
0
    while (argu) {
150
0
      *dst-- = (argu & 0x07) + '0';
151
0
      argu >>= 3;
152
0
    }
153
0
  }
154
155
  // info_printf("arg2 = %lld\n",arg);
156
157
158
0
  dst++;
159
160
0
  length =  sizeof(str) - (dst - str);
161
162
0
  if (precision < 0) {
163
0
    precision = 1;
164
0
  } else {
165
0
    flags &= ~FLAG_ZERO_PADDED;
166
0
  }
167
168
  // info_printf("flags=0x%02x field_width=%d precision=%d length=%zd dst='%.*s'\n",
169
  //  flags,field_width,precision,length,length,dst);
170
171
0
  if (field_width) {
172
0
    if ((unsigned)field_width > length + minus) {
173
0
      if (flags & FLAG_LEFT_ADJUST) {
174
0
        if (minus)
175
0
          wget_buffer_memset_append(buf, '-', 1);
176
177
0
        if (length < (unsigned)precision) {
178
0
          wget_buffer_memset_append(buf, '0', precision - length);
179
0
          wget_buffer_memcat(buf, dst, length);
180
0
          if (field_width > precision + minus)
181
0
            wget_buffer_memset_append(buf, ' ', field_width - precision - minus);
182
0
        } else {
183
0
            wget_buffer_memcat(buf, dst, length);
184
0
            wget_buffer_memset_append(buf, ' ', field_width - length - minus);
185
0
        }
186
0
      } else {
187
0
        if (length < (unsigned)precision) {
188
0
          if (field_width > precision + minus) {
189
0
            if (flags & FLAG_ZERO_PADDED) {
190
0
              if (minus)
191
0
                wget_buffer_memset_append(buf, '-', 1);
192
0
              wget_buffer_memset_append(buf, '0', field_width - precision - minus);
193
0
            } else {
194
0
              wget_buffer_memset_append(buf, ' ', field_width - precision - minus);
195
0
              if (minus)
196
0
                wget_buffer_memset_append(buf, '-', 1);
197
0
            }
198
0
          } else {
199
0
            if (minus)
200
0
              wget_buffer_memset_append(buf, '-', 1);
201
0
          }
202
0
          wget_buffer_memset_append(buf, '0', precision - length);
203
0
        } else {
204
0
          if (flags & FLAG_ZERO_PADDED) {
205
0
            if (minus)
206
0
              wget_buffer_memset_append(buf, '-', 1);
207
0
            wget_buffer_memset_append(buf, '0', field_width - length - minus);
208
0
          } else {
209
0
            wget_buffer_memset_append(buf, ' ', field_width - length - minus);
210
0
            if (minus)
211
0
              wget_buffer_memset_append(buf, '-', 1);
212
0
          }
213
0
        }
214
0
        wget_buffer_memcat(buf, dst, length);
215
0
      }
216
0
    } else {
217
0
      if (minus)
218
0
        wget_buffer_memset_append(buf, '-', 1);
219
0
      if (length < (unsigned)precision)
220
0
        wget_buffer_memset_append(buf, '0', precision - length);
221
0
      wget_buffer_memcat(buf, dst, length);
222
0
    }
223
0
  } else {
224
0
    if (minus)
225
0
      wget_buffer_memset_append(buf, '-', 1);
226
227
0
    if (length < (unsigned)precision)
228
0
      wget_buffer_memset_append(buf, '0', precision - length);
229
230
0
    wget_buffer_memcat(buf, dst, length);
231
0
  }
232
0
}
233
234
static void convert_pointer(wget_buffer *buf, void *pointer)
235
0
{
236
0
  static const char HEX[16] = "0123456789abcdef";
237
0
  char str[32]; // long enough to hold hexadecimal pointer
238
0
  char *dst;
239
0
  int length;
240
0
  size_t arg;
241
242
0
  if (!pointer) {
243
0
    wget_buffer_memcat(buf, "0x0", 3);
244
0
    return;
245
0
  } else {
246
0
    wget_buffer_memcat(buf, "0x", 2);
247
0
  }
248
249
  // convert to a size_t (covers full address room) tp allow integer arithmetic
250
0
  arg = (size_t)pointer;
251
252
0
  length = 0;
253
0
  dst = str + sizeof(str);
254
0
  *--dst = 0;
255
0
  do {
256
0
    *--dst = HEX[arg&0xF];
257
0
    arg >>= 4;
258
0
    length++;
259
0
  } while (arg);
260
261
0
  wget_buffer_memcat(buf, dst, length);
262
0
}
263
264
static const char *read_precision(const char *p, int *out, bool precision_is_external)
265
0
{
266
0
  int precision;
267
268
0
  if (precision_is_external) {
269
0
    precision = *out;
270
0
    if (precision < 0 )
271
0
      precision = 0;
272
0
    p++;
273
0
  } else if (c_isdigit(*p)) {
274
0
    precision = 0;
275
0
    do {
276
0
      precision = precision * 10 + (*p - '0');
277
0
    } while (c_isdigit(*++p));
278
0
  } else {
279
0
    precision = -1;
280
0
  }
281
282
0
  *out = precision;
283
0
  return p;
284
0
}
285
286
static const char *read_flag_chars(const char *p, unsigned int *out)
287
0
{
288
0
  unsigned int flags;
289
290
0
  for (flags = 0; *p; p++) {
291
0
    if (*p == '0')
292
0
      flags |= FLAG_ZERO_PADDED;
293
0
    else if (*p == '-')
294
0
      flags |= FLAG_LEFT_ADJUST;
295
0
    else if (*p == '#')
296
0
      flags |= FLAG_ALTERNATE;
297
0
    else
298
0
      break;
299
0
  }
300
301
0
  *out = flags;
302
0
  return p;
303
0
}
304
305
static const char *read_field_width(const char *p, int *out, unsigned int *flags, bool width_is_external)
306
0
{
307
0
  int field_width;
308
309
0
  if (width_is_external) {
310
0
    field_width = *out;
311
312
0
    if (field_width < 0) {
313
0
      *flags |= FLAG_LEFT_ADJUST;
314
0
      field_width = -field_width;
315
0
    }
316
317
0
    p++;
318
0
  } else {
319
0
    for (field_width = 0; c_isdigit(*p); p++)
320
0
      field_width = field_width * 10 + (*p - '0');
321
0
  }
322
323
0
  *out = field_width;
324
0
  return p;
325
0
}
326
327
/**
328
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
329
 * \param[in] fmt A `printf(3)`-like format string
330
 * \param[in] args A `va_list` with the format string placeholders' values
331
 * \return Length of the buffer after appending the formatted string
332
 *
333
 * Formats the string \p fmt (with `printf(3)`-like args) and appends the result to the end
334
 * of the buffer \p buf (using wget_buffer_memcat()).
335
 *
336
 * For more information, see `vprintf(3)`.
337
 */
338
size_t wget_buffer_vprintf_append(wget_buffer *buf, const char *fmt, va_list args)
339
80
{
340
80
  const char *p = fmt, *begin;
341
80
  int field_width, precision;
342
80
  unsigned int flags;
343
80
  long long arg;
344
80
  unsigned long long argu;
345
346
80
  if (!p)
347
0
    return 0;
348
349
160
  for (;*p;) {
350
351
    /*
352
     * Collect plain char sequence.
353
     * Walk the string until we find a '%' character.
354
     */
355
80
    for (begin = p; *p && *p != '%'; p++);
356
80
    if (p != begin)
357
0
      wget_buffer_memcat(buf, begin, p - begin);
358
359
80
    if (!*p)
360
0
      break;
361
362
    /* Shortcut to %s and %p, handle %% */
363
80
    if (*++p == 's') {
364
80
      const char *s = va_arg(args, const char *);
365
80
      wget_buffer_strcat(buf, s ? s : "(null)");
366
80
      p++;
367
80
      continue;
368
80
    } else if (*p == 'd') {
369
0
      convert_dec_fast(buf, va_arg(args, int));
370
0
      p++;
371
0
      continue;
372
0
    } else if (*p == 'c') {
373
0
      char c = (char ) va_arg(args, int);
374
0
      wget_buffer_memcat(buf, &c, 1);
375
0
      p++;
376
0
      continue;
377
0
    } else if (*p == 'p') {
378
0
      convert_pointer(buf, va_arg(args, void *));
379
0
      p++;
380
0
      continue;
381
0
    } else if (*p == '%') {
382
0
      wget_buffer_memset_append(buf, '%', 1);
383
0
      p++;
384
0
      continue;
385
0
    }
386
387
    /* Read the flag chars (optional, simplified) */
388
0
    p = read_flag_chars(p, &flags);
389
390
    /*
391
     * Read field width (optional).
392
     * If '*', then the field width is given as an additional argument,
393
     * which precedes the argument to be formatted.
394
     */
395
0
    if (*p == '*') {
396
0
      field_width = va_arg(args, int);
397
0
      p = read_field_width(p, &field_width, &flags, 1);
398
0
    } else {
399
0
      p = read_field_width(p, &field_width, &flags, 0);
400
0
    }
401
402
    /*
403
     * Read precision (optional).
404
     * If '*', the precision is given as an additional argument,
405
     * just as the case for the field width.
406
     */
407
0
    if (*p == '.') {
408
0
      if (*++p == '*') {
409
0
        precision = va_arg(args, int);
410
0
        p = read_precision(p, &precision, 1);
411
0
      } else {
412
0
        p = read_precision(p, &precision, 0);
413
0
      }
414
0
    } else
415
0
      precision = -1;
416
417
    /* Read length modifier (optional) */
418
0
    switch (*p) {
419
0
    case 'z':
420
0
      arg = va_arg(args, ssize_t);
421
0
      argu = (size_t)arg;
422
0
      p++;
423
0
      break;
424
425
0
    case 'l':
426
0
      if (p[1] == 'l') {
427
0
        p += 2;
428
0
        arg = va_arg(args, long long);
429
0
        argu = (unsigned long long)arg;
430
0
      } else {
431
0
        p++;
432
0
        arg = (long)va_arg(args, long);
433
0
        argu = (unsigned long)arg;
434
0
      }
435
0
      break;
436
437
0
    case 'L':
438
0
      p++;
439
0
      arg = va_arg(args, long long);
440
0
      argu = (unsigned long long)arg;
441
0
      break;
442
443
0
    case 'h':
444
0
      if (p[1] == 'h') {
445
0
        p += 2;
446
0
        arg = (signed char) va_arg(args, int);
447
0
        argu = (unsigned char) arg;
448
0
      } else {
449
0
        p++;
450
0
        arg = (short) va_arg(args, int);
451
0
        argu = (unsigned short) arg;
452
0
      }
453
0
      break;
454
455
0
    case 's':
456
0
      p++;
457
0
      copy_string(buf, flags, field_width, precision, va_arg(args, const char *));
458
0
      continue;
459
460
0
    case 'c':
461
0
    {
462
0
      char c[2] = { (char) va_arg(args, int), 0 };
463
0
      p++;
464
0
      copy_string(buf, flags, field_width, precision, c);
465
0
      continue;
466
0
    }
467
468
0
    case 'p': // %p shortcut
469
0
      p++;
470
0
      convert_dec(buf, flags | FLAG_HEXLO | FLAG_ALTERNATE, field_width, precision, (long long)(ptrdiff_t)va_arg(args, void *));
471
0
      continue;
472
473
0
    default:
474
0
      arg = va_arg(args, int);
475
0
      argu = (unsigned int)arg;
476
0
    }
477
478
0
    if (*p == 'd' || *p == 'i') {
479
0
      convert_dec(buf, flags | FLAG_SIGNED | FLAG_DECIMAL, field_width, precision, arg);
480
0
    } else if (*p == 'u') {
481
0
      convert_dec(buf, flags | FLAG_DECIMAL, field_width, precision, (long long) argu);
482
0
    } else if (*p == 'x') {
483
0
      convert_dec(buf, flags | FLAG_HEXLO, field_width, precision, (long long) argu);
484
0
    } else if (*p == 'X') {
485
0
      convert_dec(buf, flags | FLAG_HEXUP, field_width, precision, (long long) argu);
486
0
    } else if (*p == 'o') {
487
0
      convert_dec(buf, flags | FLAG_OCTAL, field_width, precision, (long long) argu);
488
0
    } else {
489
      /*
490
       * This is an unknown conversion specifier,
491
       * so just put '%' and move on.
492
       */
493
0
      wget_buffer_memset_append(buf, '%', 1);
494
0
      p = begin + 1;
495
0
      continue;
496
0
    }
497
498
0
    p++;
499
0
  }
500
501
80
  return buf->length;
502
80
}
503
504
/**
505
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
506
 * \param[in] fmt A `printf(3)`-like format string
507
 * \param[in] args A `va_list` with the format string placeholders' values
508
 * \return Length of the buffer after appending the formatted string
509
 *
510
 * Formats the string \p fmt (with `printf(3)`-like args) and overwrites the contents
511
 * of the buffer \p buf with that formatted string.
512
 *
513
 * This is equivalent to the following code:
514
 *
515
 *     buf->length = 0;
516
 *     wget_buffer_vprintf_append(buf, fmt, args);
517
 *
518
 * For more information, see `vprintf(3)`.
519
 */
520
size_t wget_buffer_vprintf(wget_buffer *buf, const char *fmt, va_list args)
521
80
{
522
80
  buf->length = 0;
523
524
80
  return wget_buffer_vprintf_append(buf, fmt, args);
525
80
}
526
527
/**
528
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
529
 * \param[in] fmt A `printf(3)`-like format string
530
 * \param[in] ... Variable arguments
531
 * \return Length of the buffer after appending the formatted string
532
 *
533
 * Formats the string \p fmt (with `printf(3)`-like args) and appends the result to the end
534
 * of the buffer \p buf (using wget_buffer_memcat()).
535
 *
536
 * This function is equivalent to wget_buffer_vprintf_append(), except in that it uses
537
 * a variable number of arguments rather than a `va_list`.
538
 *
539
 * For more information, see `printf(3)`.
540
 */
541
size_t wget_buffer_printf_append(wget_buffer *buf, const char *fmt, ...)
542
0
{
543
0
  va_list args;
544
545
0
  va_start(args, fmt);
546
0
  wget_buffer_vprintf_append(buf, fmt, args);
547
0
  va_end(args);
548
549
0
  return buf->length;
550
0
}
551
552
/**
553
 * \param[in] buf A buffer, created with wget_buffer_init() or wget_buffer_alloc()
554
 * \param[in] fmt A `printf(3)`-like format string
555
 * \param[in] ... Variable arguments
556
 * \return Length of the buffer after appending the formatted string
557
 *
558
 * Formats the string \p fmt (with `printf(3)`-like args) and overwrites the contents
559
 * of the buffer \p buf with that formatted string.
560
 *
561
 * This function is equivalent to wget_buffer_vprintf(), except in that it uses
562
 * a variable number of arguments rather than a `va_list`.
563
 *
564
 * For more information, see `printf(3)`.
565
 */
566
size_t wget_buffer_printf(wget_buffer *buf, const char *fmt, ...)
567
0
{
568
0
  va_list args;
569
570
0
  va_start(args, fmt);
571
0
  size_t len = wget_buffer_vprintf(buf, fmt, args);
572
0
  va_end(args);
573
574
0
  return len;
575
0
}
576
/** @} */