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