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