Coverage Report

Created: 2024-03-08 06:32

/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
2.65M
#define FLAG_ZERO_PADDED   1U
48
42.2k
#define FLAG_LEFT_ADJUST   2U
49
0
#define FLAG_ALTERNATE     4U
50
32.2k
#define FLAG_SIGNED        8U
51
2.64M
#define FLAG_DECIMAL      16U
52
0
#define FLAG_OCTAL        32U
53
2.61M
#define FLAG_HEXLO        64U
54
5.23M
#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
389k
{
59
389k
  size_t length;
60
61
389k
  if (!arg) {
62
402
    wget_buffer_strcat(buf, "(null)");
63
402
    return;
64
402
  }
65
66
388k
  if (precision >= 0) {
67
387k
    length = strnlen(arg, precision);
68
387k
  } else {
69
1.20k
    length = strlen(arg);
70
1.20k
  }
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
388k
  if (field_width) {
76
1.60k
    if ((unsigned)field_width > length) {
77
1.53k
      if (flags & FLAG_LEFT_ADJUST) {
78
331
        wget_buffer_memcat(buf, arg, length);
79
331
        wget_buffer_memset_append(buf, ' ', field_width - length);
80
1.20k
      } else {
81
1.20k
        wget_buffer_memset_append(buf, ' ', field_width - length);
82
1.20k
        wget_buffer_memcat(buf, arg, length);
83
1.20k
      }
84
1.53k
    } else {
85
71
      wget_buffer_memcat(buf, arg, length);
86
71
    }
87
387k
  } else {
88
387k
    wget_buffer_memcat(buf, arg, length);
89
387k
  }
90
388k
}
91
92
static void convert_dec_fast(wget_buffer *buf, int arg)
93
11.2k
{
94
11.2k
  char str[32]; // long enough to hold decimal long long
95
11.2k
  char *dst = str + sizeof(str) - 1;
96
11.2k
  int minus;
97
98
11.2k
  if (arg < 0) {
99
5.65k
    minus = 1;
100
5.65k
    arg = -arg;
101
5.65k
  } else
102
5.58k
    minus = 0;
103
104
16.1k
  while (arg >= 10) {
105
4.87k
    *dst-- = (arg % 10) + '0';
106
4.87k
    arg /= 10;
107
4.87k
  }
108
11.2k
  *dst-- = (arg % 10) + '0';
109
110
11.2k
  if (minus)
111
5.65k
    *dst-- = '-';
112
113
11.2k
  wget_buffer_memcat(buf, dst + 1, sizeof(str) - (dst - str) - 1);
114
11.2k
}
115
116
static void convert_dec(wget_buffer *buf, unsigned int flags, int field_width, int precision, long long arg)
117
2.63M
{
118
2.63M
  unsigned long long argu = (unsigned long long) arg;
119
2.63M
  char str[32], minus = 0; // long enough to hold decimal long long
120
2.63M
  char *dst = str + sizeof(str) - 1;
121
2.63M
  unsigned char c;
122
2.63M
  size_t length;
123
124
  // info_printf("arg1 = %lld %lld\n",arg,-arg);
125
126
2.63M
  if (flags & FLAG_DECIMAL) {
127
15.1k
    if (flags & FLAG_SIGNED && arg < 0) {
128
0
      minus = 1;
129
0
      argu = -arg;
130
0
    }
131
132
27.2k
    while (argu) {
133
12.0k
      *dst-- = argu % 10 + '0';
134
12.0k
      argu /= 10;
135
12.0k
    }
136
2.61M
  } 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
2.61M
  } else if (flags & FLAG_HEXUP) {
143
7.81M
    while (argu) {
144
      // slightly faster than having a HEX[] lookup table
145
5.19M
      *dst-- = (c = (argu & 0xf)) >= 10 ? c + 'A' - 10 : c + '0';
146
5.19M
      argu >>= 4;
147
5.19M
    }
148
2.61M
  } 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
2.63M
  dst++;
159
160
2.63M
  length =  sizeof(str) - (dst - str);
161
162
2.63M
  if (precision < 0) {
163
2.63M
    precision = 1;
164
2.63M
  } 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
2.63M
  if (field_width) {
172
2.61M
    if ((unsigned)field_width > length + minus) {
173
39.8k
      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
39.8k
      } else {
187
39.8k
        if (length < (unsigned)precision) {
188
540
          if (field_width > precision + minus) {
189
540
            if (flags & FLAG_ZERO_PADDED) {
190
89
              if (minus)
191
0
                wget_buffer_memset_append(buf, '-', 1);
192
89
              wget_buffer_memset_append(buf, '0', field_width - precision - minus);
193
451
            } else {
194
451
              wget_buffer_memset_append(buf, ' ', field_width - precision - minus);
195
451
              if (minus)
196
0
                wget_buffer_memset_append(buf, '-', 1);
197
451
            }
198
540
          } else {
199
0
            if (minus)
200
0
              wget_buffer_memset_append(buf, '-', 1);
201
0
          }
202
540
          wget_buffer_memset_append(buf, '0', precision - length);
203
39.3k
        } else {
204
39.3k
          if (flags & FLAG_ZERO_PADDED) {
205
39.3k
            if (minus)
206
0
              wget_buffer_memset_append(buf, '-', 1);
207
39.3k
            wget_buffer_memset_append(buf, '0', field_width - length - minus);
208
39.3k
          } else {
209
33
            wget_buffer_memset_append(buf, ' ', field_width - length - minus);
210
33
            if (minus)
211
0
              wget_buffer_memset_append(buf, '-', 1);
212
33
          }
213
39.3k
        }
214
39.8k
        wget_buffer_memcat(buf, dst, length);
215
39.8k
      }
216
2.57M
    } else {
217
2.57M
      if (minus)
218
0
        wget_buffer_memset_append(buf, '-', 1);
219
2.57M
      if (length < (unsigned)precision)
220
0
        wget_buffer_memset_append(buf, '0', precision - length);
221
2.57M
      wget_buffer_memcat(buf, dst, length);
222
2.57M
    }
223
2.61M
  } else {
224
13.9k
    if (minus)
225
0
      wget_buffer_memset_append(buf, '-', 1);
226
227
13.9k
    if (length < (unsigned)precision)
228
9.54k
      wget_buffer_memset_append(buf, '0', precision - length);
229
230
13.9k
    wget_buffer_memcat(buf, dst, length);
231
13.9k
  }
232
2.63M
}
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
387k
{
266
387k
  int precision;
267
268
387k
  if (precision_is_external) {
269
387k
    precision = *out;
270
387k
    if (precision < 0 )
271
0
      precision = 0;
272
387k
    p++;
273
387k
  } 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
387k
  *out = precision;
283
387k
  return p;
284
387k
}
285
286
static const char *read_flag_chars(const char *p, unsigned int *out)
287
3.02M
{
288
3.02M
  unsigned int flags;
289
290
5.64M
  for (flags = 0; *p; p++) {
291
5.64M
    if (*p == '0')
292
2.61M
      flags |= FLAG_ZERO_PADDED;
293
3.02M
    else if (*p == '-')
294
804
      flags |= FLAG_LEFT_ADJUST;
295
3.02M
    else if (*p == '#')
296
0
      flags |= FLAG_ALTERNATE;
297
3.02M
    else
298
3.02M
      break;
299
5.64M
  }
300
301
3.02M
  *out = flags;
302
3.02M
  return p;
303
3.02M
}
304
305
static const char *read_field_width(const char *p, int *out, unsigned int *flags, bool width_is_external)
306
3.02M
{
307
3.02M
  int field_width;
308
309
3.02M
  if (width_is_external) {
310
2.61k
    field_width = *out;
311
312
2.61k
    if (field_width < 0) {
313
0
      *flags |= FLAG_LEFT_ADJUST;
314
0
      field_width = -field_width;
315
0
    }
316
317
2.61k
    p++;
318
3.01M
  } else {
319
5.63M
    for (field_width = 0; c_isdigit(*p); p++)
320
2.61M
      field_width = field_width * 10 + (*p - '0');
321
3.01M
  }
322
323
3.02M
  *out = field_width;
324
3.02M
  return p;
325
3.02M
}
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
3.14M
{
340
3.14M
  const char *p = fmt, *begin;
341
3.14M
  int field_width, precision;
342
3.14M
  unsigned int flags;
343
3.14M
  long long arg;
344
3.14M
  unsigned long long argu;
345
346
3.14M
  if (!p)
347
0
    return 0;
348
349
9.02M
  for (;*p;) {
350
351
    /*
352
     * Collect plain char sequence.
353
     * Walk the string until we find a '%' character.
354
     */
355
6.82M
    for (begin = p; *p && *p != '%'; p++);
356
5.93M
    if (p != begin)
357
228k
      wget_buffer_memcat(buf, begin, p - begin);
358
359
5.93M
    if (!*p)
360
61.2k
      break;
361
362
    /* Shortcut to %s and %p, handle %% */
363
5.87M
    if (*++p == 's') {
364
226k
      const char *s = va_arg(args, const char *);
365
226k
      wget_buffer_strcat(buf, s ? s : "(null)");
366
226k
      p++;
367
226k
      continue;
368
5.65M
    } else if (*p == 'd') {
369
11.2k
      convert_dec_fast(buf, va_arg(args, int));
370
11.2k
      p++;
371
11.2k
      continue;
372
5.64M
    } else if (*p == 'c') {
373
823
      char c = (char ) va_arg(args, int);
374
823
      wget_buffer_memcat(buf, &c, 1);
375
823
      p++;
376
823
      continue;
377
5.63M
    } else if (*p == 'p') {
378
0
      convert_pointer(buf, va_arg(args, void *));
379
0
      p++;
380
0
      continue;
381
5.63M
    } else if (*p == '%') {
382
2.61M
      wget_buffer_memset_append(buf, '%', 1);
383
2.61M
      p++;
384
2.61M
      continue;
385
2.61M
    }
386
387
    /* Read the flag chars (optional, simplified) */
388
3.02M
    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
3.02M
    if (*p == '*') {
396
2.61k
      field_width = va_arg(args, int);
397
2.61k
      p = read_field_width(p, &field_width, &flags, 1);
398
3.01M
    } else {
399
3.01M
      p = read_field_width(p, &field_width, &flags, 0);
400
3.01M
    }
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
3.02M
    if (*p == '.') {
408
387k
      if (*++p == '*') {
409
387k
        precision = va_arg(args, int);
410
387k
        p = read_precision(p, &precision, 1);
411
387k
      } else {
412
0
        p = read_precision(p, &precision, 0);
413
0
      }
414
387k
    } else
415
2.63M
      precision = -1;
416
417
    /* Read length modifier (optional) */
418
3.02M
    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
681
    case 'l':
426
681
      if (p[1] == 'l') {
427
681
        p += 2;
428
681
        arg = va_arg(args, long long);
429
681
        argu = (unsigned long long)arg;
430
681
      } else {
431
0
        p++;
432
0
        arg = (long)va_arg(args, long);
433
0
        argu = (unsigned long)arg;
434
0
      }
435
681
      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
3.94k
    case 'h':
444
3.94k
      if (p[1] == 'h') {
445
0
        p += 2;
446
0
        arg = (signed char) va_arg(args, int);
447
0
        argu = (unsigned char) arg;
448
3.94k
      } else {
449
3.94k
        p++;
450
3.94k
        arg = (short) va_arg(args, int);
451
3.94k
        argu = (unsigned short) arg;
452
3.94k
      }
453
3.94k
      break;
454
455
389k
    case 's':
456
389k
      p++;
457
389k
      copy_string(buf, flags, field_width, precision, va_arg(args, const char *));
458
389k
      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
2.62M
    default:
474
2.62M
      arg = va_arg(args, int);
475
2.62M
      argu = (unsigned int)arg;
476
3.02M
    }
477
478
2.63M
    if (*p == 'd' || *p == 'i') {
479
1.89k
      convert_dec(buf, flags | FLAG_SIGNED | FLAG_DECIMAL, field_width, precision, arg);
480
2.63M
    } else if (*p == 'u') {
481
13.2k
      convert_dec(buf, flags | FLAG_DECIMAL, field_width, precision, (long long) argu);
482
2.61M
    } else if (*p == 'x') {
483
0
      convert_dec(buf, flags | FLAG_HEXLO, field_width, precision, (long long) argu);
484
2.61M
    } else if (*p == 'X') {
485
2.61M
      convert_dec(buf, flags | FLAG_HEXUP, field_width, precision, (long long) argu);
486
2.61M
    } 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
2.63M
    p++;
499
2.63M
  }
500
501
3.14M
  return buf->length;
502
3.14M
}
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
496k
{
522
496k
  buf->length = 0;
523
524
496k
  return wget_buffer_vprintf_append(buf, fmt, args);
525
496k
}
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
2.65M
{
543
2.65M
  va_list args;
544
545
2.65M
  va_start(args, fmt);
546
2.65M
  wget_buffer_vprintf_append(buf, fmt, args);
547
2.65M
  va_end(args);
548
549
2.65M
  return buf->length;
550
2.65M
}
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
8.39k
{
568
8.39k
  va_list args;
569
570
8.39k
  va_start(args, fmt);
571
8.39k
  size_t len = wget_buffer_vprintf(buf, fmt, args);
572
8.39k
  va_end(args);
573
574
8.39k
  return len;
575
8.39k
}
576
/** @} */