xref: /PHP-8.3/ext/standard/formatted_print.c (revision c8955c07)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Stig S�ther Bakken <ssb@php.net>                             |
14    +----------------------------------------------------------------------+
15  */
16 
17 #include <math.h>				/* modf() */
18 #include "php.h"
19 #include "ext/standard/head.h"
20 #include "php_string.h"
21 #include "zend_execute.h"
22 #include <stdio.h>
23 
24 #include <locale.h>
25 #ifdef ZTS
26 #include "ext/standard/php_string.h"
27 #define LCONV_DECIMAL_POINT (*lconv.decimal_point)
28 #else
29 #define LCONV_DECIMAL_POINT (*lconv->decimal_point)
30 #endif
31 
32 #define ALIGN_LEFT 0
33 #define ALIGN_RIGHT 1
34 #define ADJ_WIDTH 1
35 #define ADJ_PRECISION 2
36 #define NUM_BUF_SIZE 500
37 #define FLOAT_PRECISION 6
38 #define MAX_FLOAT_PRECISION 53
39 
40 #if 0
41 /* trick to control varargs functions through cpp */
42 # define PRINTF_DEBUG(arg) php_printf arg
43 #else
44 # define PRINTF_DEBUG(arg)
45 #endif
46 
47 static const char hexchars[] = "0123456789abcdef";
48 static const char HEXCHARS[] = "0123456789ABCDEF";
49 
50 /* php_spintf_appendchar() {{{ */
51 inline static void
php_sprintf_appendchar(zend_string ** buffer,size_t * pos,char add)52 php_sprintf_appendchar(zend_string **buffer, size_t *pos, char add)
53 {
54 	if ((*pos + 1) >= ZSTR_LEN(*buffer)) {
55 		PRINTF_DEBUG(("%s(): ereallocing buffer to %d bytes\n", get_active_function_name(), ZSTR_LEN(*buffer)));
56 		*buffer = zend_string_extend(*buffer, ZSTR_LEN(*buffer) << 1, 0);
57 	}
58 	PRINTF_DEBUG(("sprintf: appending '%c', pos=\n", add, *pos));
59 	ZSTR_VAL(*buffer)[(*pos)++] = add;
60 }
61 /* }}} */
62 
63 /* php_spintf_appendchar() {{{ */
64 inline static void
php_sprintf_appendchars(zend_string ** buffer,size_t * pos,char * add,size_t len)65 php_sprintf_appendchars(zend_string **buffer, size_t *pos, char *add, size_t len)
66 {
67 	if ((*pos + len) >= ZSTR_LEN(*buffer)) {
68 		size_t nlen = ZSTR_LEN(*buffer);
69 
70 		PRINTF_DEBUG(("%s(): ereallocing buffer to %d bytes\n", get_active_function_name(), ZSTR_LEN(*buffer)));
71 		do {
72 			nlen = nlen << 1;
73 		} while ((*pos + len) >= nlen);
74 		*buffer = zend_string_extend(*buffer, nlen, 0);
75 	}
76 	PRINTF_DEBUG(("sprintf: appending \"%s\", pos=\n", add, *pos));
77 	memcpy(ZSTR_VAL(*buffer) + (*pos), add, len);
78 	*pos += len;
79 }
80 /* }}} */
81 
82 /* php_spintf_appendstring() {{{ */
83 inline static void
php_sprintf_appendstring(zend_string ** buffer,size_t * pos,char * add,size_t min_width,size_t max_width,char padding,size_t alignment,size_t len,bool neg,int expprec,int always_sign)84 php_sprintf_appendstring(zend_string **buffer, size_t *pos, char *add,
85 						   size_t min_width, size_t max_width, char padding,
86 						   size_t alignment, size_t len, bool neg, int expprec, int always_sign)
87 {
88 	size_t npad;
89 	size_t req_size;
90 	size_t copy_len;
91 	size_t m_width;
92 
93 	copy_len = (expprec ? MIN(max_width, len) : len);
94 	npad = (min_width < copy_len) ? 0 : min_width - copy_len;
95 
96 	PRINTF_DEBUG(("sprintf: appendstring(%x, %d, %d, \"%s\", %d, '%c', %d)\n",
97 				  *buffer, *pos, ZSTR_LEN(*buffer), add, min_width, padding, alignment));
98 	m_width = MAX(min_width, copy_len);
99 
100 	if(m_width > INT_MAX - *pos - 1) {
101 		zend_error_noreturn(E_ERROR, "Field width %zd is too long", m_width);
102 	}
103 
104 	req_size = *pos + m_width + 1;
105 
106 	if (req_size > ZSTR_LEN(*buffer)) {
107 		size_t size = ZSTR_LEN(*buffer);
108 		while (req_size > size) {
109 			if (size > ZEND_SIZE_MAX/2) {
110 				zend_error_noreturn(E_ERROR, "Field width %zd is too long", req_size);
111 			}
112 			size <<= 1;
113 		}
114 		PRINTF_DEBUG(("sprintf ereallocing buffer to %d bytes\n", size));
115 		*buffer = zend_string_extend(*buffer, size, 0);
116 	}
117 	if (alignment == ALIGN_RIGHT) {
118 		if ((neg || always_sign) && padding=='0') {
119 			ZSTR_VAL(*buffer)[(*pos)++] = (neg) ? '-' : '+';
120 			add++;
121 			len--;
122 			copy_len--;
123 		}
124 		while (npad-- > 0) {
125 			ZSTR_VAL(*buffer)[(*pos)++] = padding;
126 		}
127 	}
128 	PRINTF_DEBUG(("sprintf: appending \"%s\"\n", add));
129 	memcpy(&ZSTR_VAL(*buffer)[*pos], add, copy_len + 1);
130 	*pos += copy_len;
131 	if (alignment == ALIGN_LEFT) {
132 		while (npad--) {
133 			ZSTR_VAL(*buffer)[(*pos)++] = padding;
134 		}
135 	}
136 }
137 /* }}} */
138 
139 /* php_spintf_appendint() {{{ */
140 inline static void
php_sprintf_appendint(zend_string ** buffer,size_t * pos,zend_long number,size_t width,char padding,size_t alignment,int always_sign)141 php_sprintf_appendint(zend_string **buffer, size_t *pos, zend_long number,
142 						size_t width, char padding, size_t alignment,
143 						int always_sign)
144 {
145 	char numbuf[NUM_BUF_SIZE];
146 	zend_ulong magn, nmagn;
147 	unsigned int i = NUM_BUF_SIZE - 1, neg = 0;
148 
149 	PRINTF_DEBUG(("sprintf: appendint(%x, %x, %x, %d, %d, '%c', %d)\n",
150 				  *buffer, pos, &ZSTR_LEN(*buffer), number, width, padding, alignment));
151 	if (number < 0) {
152 		neg = 1;
153 		magn = ((zend_ulong) -(number + 1)) + 1;
154 	} else {
155 		magn = (zend_ulong) number;
156 	}
157 
158 	/* Can't right-pad 0's on integers */
159 	if(alignment==0 && padding=='0') padding=' ';
160 
161 	numbuf[i] = '\0';
162 
163 	do {
164 		nmagn = magn / 10;
165 
166 		numbuf[--i] = (unsigned char)(magn - (nmagn * 10)) + '0';
167 		magn = nmagn;
168 	}
169 	while (magn > 0 && i > 1);
170 	if (neg) {
171 		numbuf[--i] = '-';
172 	} else if (always_sign) {
173 		numbuf[--i] = '+';
174 	}
175 	PRINTF_DEBUG(("sprintf: appending %d as \"%s\", i=%d\n",
176 				  number, &numbuf[i], i));
177 	php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
178 							 padding, alignment, (NUM_BUF_SIZE - 1) - i,
179 							 neg, 0, always_sign);
180 }
181 /* }}} */
182 
183 /* php_spintf_appenduint() {{{ */
184 inline static void
php_sprintf_appenduint(zend_string ** buffer,size_t * pos,zend_ulong number,size_t width,char padding,size_t alignment)185 php_sprintf_appenduint(zend_string **buffer, size_t *pos,
186 					   zend_ulong number,
187 					   size_t width, char padding, size_t alignment)
188 {
189 	char numbuf[NUM_BUF_SIZE];
190 	zend_ulong magn, nmagn;
191 	unsigned int i = NUM_BUF_SIZE - 1;
192 
193 	PRINTF_DEBUG(("sprintf: appenduint(%x, %x, %x, %d, %d, '%c', %d)\n",
194 				  *buffer, pos, &ZSTR_LEN(*buffer), number, width, padding, alignment));
195 	magn = (zend_ulong) number;
196 
197 	/* Can't right-pad 0's on integers */
198 	if (alignment == 0 && padding == '0') padding = ' ';
199 
200 	numbuf[i] = '\0';
201 
202 	do {
203 		nmagn = magn / 10;
204 
205 		numbuf[--i] = (unsigned char)(magn - (nmagn * 10)) + '0';
206 		magn = nmagn;
207 	} while (magn > 0 && i > 0);
208 
209 	PRINTF_DEBUG(("sprintf: appending %d as \"%s\", i=%d\n", number, &numbuf[i], i));
210 	php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
211 							 padding, alignment, (NUM_BUF_SIZE - 1) - i, /* neg */ false, 0, 0);
212 }
213 /* }}} */
214 
215 /* php_spintf_appenddouble() {{{ */
216 inline static void
php_sprintf_appenddouble(zend_string ** buffer,size_t * pos,double number,size_t width,char padding,size_t alignment,int precision,int adjust,char fmt,int always_sign)217 php_sprintf_appenddouble(zend_string **buffer, size_t *pos,
218 						 double number,
219 						 size_t width, char padding,
220 						 size_t alignment, int precision,
221 						 int adjust, char fmt,
222 						 int always_sign
223 						)
224 {
225 	char num_buf[NUM_BUF_SIZE];
226 	char *s = NULL;
227 	size_t s_len = 0;
228 	bool is_negative = false;
229 #ifdef ZTS
230 	struct lconv lconv;
231 #else
232 	struct lconv *lconv;
233 #endif
234 
235 	PRINTF_DEBUG(("sprintf: appenddouble(%x, %x, %x, %f, %d, '%c', %d, %c)\n",
236 				  *buffer, pos, &ZSTR_LEN(*buffer), number, width, padding, alignment, fmt));
237 	if ((adjust & ADJ_PRECISION) == 0) {
238 		precision = FLOAT_PRECISION;
239 	} else if (precision > MAX_FLOAT_PRECISION) {
240 		php_error_docref(NULL, E_NOTICE, "Requested precision of %d digits was truncated to PHP maximum of %d digits", precision, MAX_FLOAT_PRECISION);
241 		precision = MAX_FLOAT_PRECISION;
242 	}
243 
244 	if (zend_isnan(number)) {
245 		is_negative = (number<0);
246 		php_sprintf_appendstring(buffer, pos, "NaN", 3, 0, padding,
247 								 alignment, 3, is_negative, 0, always_sign);
248 		return;
249 	}
250 
251 	if (zend_isinf(number)) {
252 		is_negative = (number<0);
253 		php_sprintf_appendstring(buffer, pos, "INF", 3, 0, padding,
254 								 alignment, 3, is_negative, 0, always_sign);
255 		return;
256 	}
257 
258 	switch (fmt) {
259 		case 'e':
260 		case 'E':
261 		case 'f':
262 		case 'F':
263 #ifdef ZTS
264 			localeconv_r(&lconv);
265 #else
266 			lconv = localeconv();
267 #endif
268 			s = php_conv_fp((fmt == 'f')?'F':fmt, number, 0, precision,
269 						(fmt == 'f')?LCONV_DECIMAL_POINT:'.',
270 						&is_negative, &num_buf[1], &s_len);
271 			if (is_negative) {
272 				num_buf[0] = '-';
273 				s = num_buf;
274 				s_len++;
275 			} else if (always_sign) {
276 				num_buf[0] = '+';
277 				s = num_buf;
278 				s_len++;
279 			}
280 			break;
281 
282 		case 'g':
283 		case 'G':
284 		case 'h':
285 		case 'H':
286 		{
287 			if (precision == 0)
288 				precision = 1;
289 
290 			char decimal_point = '.';
291 			if (fmt == 'g' || fmt == 'G') {
292 #ifdef ZTS
293 				localeconv_r(&lconv);
294 #else
295 				lconv = localeconv();
296 #endif
297 				decimal_point = LCONV_DECIMAL_POINT;
298 			}
299 
300 			char exp_char = fmt == 'G' || fmt == 'H' ? 'E' : 'e';
301 			/* We use &num_buf[ 1 ], so that we have room for the sign. */
302 			s = zend_gcvt(number, precision, decimal_point, exp_char, &num_buf[1]);
303 			is_negative = 0;
304 			if (*s == '-') {
305 				is_negative = 1;
306 				s = &num_buf[1];
307 			} else if (always_sign) {
308 				num_buf[0] = '+';
309 				s = num_buf;
310 			}
311 
312 			s_len = strlen(s);
313 			break;
314 		}
315 	}
316 
317 	php_sprintf_appendstring(buffer, pos, s, width, 0, padding,
318 							 alignment, s_len, is_negative, 0, always_sign);
319 }
320 /* }}} */
321 
322 /* php_spintf_appendd2n() {{{ */
323 inline static void
php_sprintf_append2n(zend_string ** buffer,size_t * pos,zend_long number,size_t width,char padding,size_t alignment,int n,const char * chartable,int expprec)324 php_sprintf_append2n(zend_string **buffer, size_t *pos, zend_long number,
325 					 size_t width, char padding, size_t alignment, int n,
326 					 const char *chartable, int expprec)
327 {
328 	char numbuf[NUM_BUF_SIZE];
329 	zend_ulong num;
330 	zend_ulong  i = NUM_BUF_SIZE - 1;
331 	int andbits = (1 << n) - 1;
332 
333 	PRINTF_DEBUG(("sprintf: append2n(%x, %x, %x, %d, %d, '%c', %d, %d, %x)\n",
334 				  *buffer, pos, &ZSTR_LEN(*buffer), number, width, padding, alignment, n,
335 				  chartable));
336 	PRINTF_DEBUG(("sprintf: append2n 2^%d andbits=%x\n", n, andbits));
337 
338 	num = (zend_ulong) number;
339 	numbuf[i] = '\0';
340 
341 	do {
342 		numbuf[--i] = chartable[(num & andbits)];
343 		num >>= n;
344 	}
345 	while (num > 0);
346 
347 	php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
348 							 padding, alignment, (NUM_BUF_SIZE - 1) - i,
349 							 /* neg */ false, expprec, 0);
350 }
351 /* }}} */
352 
353 /* php_spintf_getnumber() {{{ */
354 inline static int
php_sprintf_getnumber(char ** buffer,size_t * len)355 php_sprintf_getnumber(char **buffer, size_t *len)
356 {
357 	char *endptr;
358 	zend_long num = ZEND_STRTOL(*buffer, &endptr, 10);
359 	size_t i;
360 
361 	if (endptr != NULL) {
362 		i = (endptr - *buffer);
363 		*len -= i;
364 		*buffer = endptr;
365 	}
366 	PRINTF_DEBUG(("sprintf_getnumber: number was %d bytes long\n", i));
367 
368 	if (num >= INT_MAX || num < 0) {
369 		return -1;
370 	} else {
371 		return (int) num;
372 	}
373 }
374 /* }}} */
375 
376 #define ARG_NUM_NEXT -1
377 #define ARG_NUM_INVALID -2
378 
php_sprintf_get_argnum(char ** format,size_t * format_len)379 int php_sprintf_get_argnum(char **format, size_t *format_len) {
380 	char *temppos = *format;
381 	while (isdigit((int) *temppos)) temppos++;
382 	if (*temppos != '$') {
383 		return ARG_NUM_NEXT;
384 	}
385 
386 	int argnum = php_sprintf_getnumber(format, format_len);
387 	if (argnum <= 0) {
388 		zend_value_error("Argument number specifier must be greater than zero and less than %d", INT_MAX);
389 		return ARG_NUM_INVALID;
390 	}
391 
392 	(*format)++;  /* skip the '$' */
393 	(*format_len)--;
394 	return argnum - 1;
395 }
396 
397 /* php_formatted_print() {{{
398  * New sprintf implementation for PHP.
399  *
400  * Modifiers:
401  *
402  *  " "   pad integers with spaces
403  *  "-"   left adjusted field
404  *   n    field size
405  *  "."n  precision (floats only)
406  *  "+"   Always place a sign (+ or -) in front of a number
407  *
408  * Type specifiers:
409  *
410  *  "%"   literal "%", modifiers are ignored.
411  *  "b"   integer argument is printed as binary
412  *  "c"   integer argument is printed as a single character
413  *  "d"   argument is an integer
414  *  "f"   the argument is a float
415  *  "o"   integer argument is printed as octal
416  *  "s"   argument is a string
417  *  "x"   integer argument is printed as lowercase hexadecimal
418  *  "X"   integer argument is printed as uppercase hexadecimal
419  *
420  * nb_additional_parameters is used for throwing errors:
421  *  - -1: ValueError is thrown (for vsprintf where args originates from an array)
422  *  - 0 or more: ArgumentCountError is thrown
423  */
424 static zend_string *
php_formatted_print(char * format,size_t format_len,zval * args,int argc,int nb_additional_parameters)425 php_formatted_print(char *format, size_t format_len, zval *args, int argc, int nb_additional_parameters)
426 {
427 	size_t size = 240, outpos = 0;
428 	int alignment, currarg, adjusting, argnum, width, precision;
429 	char *temppos, padding;
430 	zend_string *result;
431 	int always_sign;
432 	int max_missing_argnum = -1;
433 
434 	result = zend_string_alloc(size, 0);
435 
436 	currarg = 0;
437 	argnum = 0;
438 
439 	while (format_len) {
440 		int expprec;
441 		zval *tmp;
442 
443 		temppos = memchr(format, '%', format_len);
444 		if (!temppos) {
445 			php_sprintf_appendchars(&result, &outpos, format, format_len);
446 			break;
447 		} else if (temppos != format) {
448 			php_sprintf_appendchars(&result, &outpos, format, temppos - format);
449 			format_len -= temppos - format;
450 			format = temppos;
451 		}
452 		format++;			/* skip the '%' */
453 		format_len--;
454 
455 		if (*format == '%') {
456 			php_sprintf_appendchar(&result, &outpos, '%');
457 			format++;
458 			format_len--;
459 		} else {
460 			/* starting a new format specifier, reset variables */
461 			alignment = ALIGN_RIGHT;
462 			adjusting = 0;
463 			padding = ' ';
464 			always_sign = 0;
465 			expprec = 0;
466 
467 			PRINTF_DEBUG(("sprintf: first looking at '%c', inpos=%d\n",
468 						  *format, format - Z_STRVAL_P(z_format)));
469 			if (isalpha((int)*format)) {
470 				width = precision = 0;
471 				argnum = ARG_NUM_NEXT;
472 			} else {
473 				/* first look for argnum */
474 				argnum = php_sprintf_get_argnum(&format, &format_len);
475 				if (argnum == ARG_NUM_INVALID) {
476 					goto fail;
477 				}
478 
479 				/* after argnum comes modifiers */
480 				PRINTF_DEBUG(("sprintf: looking for modifiers\n"
481 							  "sprintf: now looking at '%c', inpos=%d\n",
482 							  *format, format - Z_STRVAL_P(z_format)));
483 				for (;; format++, format_len--) {
484 					if (*format == ' ' || *format == '0') {
485 						padding = *format;
486 					} else if (*format == '-') {
487 						alignment = ALIGN_LEFT;
488 						/* space padding, the default */
489 					} else if (*format == '+') {
490 						always_sign = 1;
491 					} else if (*format == '\'') {
492 						if (format_len > 1) {
493 							format++;
494 							format_len--;
495 							padding = *format;
496 						} else {
497 							zend_value_error("Missing padding character");
498 							goto fail;
499 						}
500 					} else {
501 						PRINTF_DEBUG(("sprintf: end of modifiers\n"));
502 						break;
503 					}
504 				}
505 				PRINTF_DEBUG(("sprintf: padding='%c'\n", padding));
506 				PRINTF_DEBUG(("sprintf: alignment=%s\n",
507 							  (alignment == ALIGN_LEFT) ? "left" : "right"));
508 
509 
510 				/* after modifiers comes width */
511 				if (*format == '*') {
512 					format++;
513 					format_len--;
514 
515 					int width_argnum = php_sprintf_get_argnum(&format, &format_len);
516 					if (width_argnum == ARG_NUM_INVALID) {
517 						goto fail;
518 					}
519 					if (width_argnum == ARG_NUM_NEXT) {
520 						width_argnum = currarg++;
521 					}
522 					if (width_argnum >= argc) {
523 						max_missing_argnum = MAX(max_missing_argnum, width_argnum);
524 						continue;
525 					}
526 					tmp = &args[width_argnum];
527 					ZVAL_DEREF(tmp);
528 					if (Z_TYPE_P(tmp) != IS_LONG) {
529 						zend_value_error("Width must be an integer");
530 						goto fail;
531 					}
532 					if (Z_LVAL_P(tmp) < 0 || Z_LVAL_P(tmp) > INT_MAX) {
533 						zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
534 						goto fail;
535 					}
536 					width = Z_LVAL_P(tmp);
537 					adjusting |= ADJ_WIDTH;
538 				} else if (isdigit((int)*format)) {
539 					PRINTF_DEBUG(("sprintf: getting width\n"));
540 					if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) {
541 						zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
542 						goto fail;
543 					}
544 					adjusting |= ADJ_WIDTH;
545 				} else {
546 					width = 0;
547 				}
548 				PRINTF_DEBUG(("sprintf: width=%d\n", width));
549 
550 				/* after width and argnum comes precision */
551 				if (*format == '.') {
552 					format++;
553 					format_len--;
554 					PRINTF_DEBUG(("sprintf: getting precision\n"));
555 					if (*format == '*') {
556 						format++;
557 						format_len--;
558 
559 						int prec_argnum = php_sprintf_get_argnum(&format, &format_len);
560 						if (prec_argnum == ARG_NUM_INVALID) {
561 							goto fail;
562 						}
563 						if (prec_argnum == ARG_NUM_NEXT) {
564 							prec_argnum = currarg++;
565 						}
566 						if (prec_argnum >= argc) {
567 							max_missing_argnum = MAX(max_missing_argnum, prec_argnum);
568 							continue;
569 						}
570 						tmp = &args[prec_argnum];
571 						ZVAL_DEREF(tmp);
572 						if (Z_TYPE_P(tmp) != IS_LONG) {
573 							zend_value_error("Precision must be an integer");
574 							goto fail;
575 						}
576 						if (Z_LVAL_P(tmp) < -1 || Z_LVAL_P(tmp) > INT_MAX) {
577 							zend_value_error("Precision must be between -1 and %d", INT_MAX);
578 							goto fail;
579 						}
580 						precision = Z_LVAL_P(tmp);
581 						adjusting |= ADJ_PRECISION;
582 						expprec = 1;
583 					} else if (isdigit((int)*format)) {
584 						if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) {
585 							zend_value_error("Precision must be greater than zero and less than %d", INT_MAX);
586 							goto fail;
587 						}
588 						adjusting |= ADJ_PRECISION;
589 						expprec = 1;
590 					} else {
591 						precision = 0;
592 					}
593 				} else {
594 					precision = 0;
595 				}
596 				PRINTF_DEBUG(("sprintf: precision=%d\n", precision));
597 			}
598 
599 			if (*format == 'l') {
600 				format++;
601 				format_len--;
602 			}
603 			PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
604 
605 			if (argnum == ARG_NUM_NEXT) {
606 				argnum = currarg++;
607 			}
608 			if (argnum >= argc) {
609 				max_missing_argnum = MAX(max_missing_argnum, argnum);
610 				continue;
611 			}
612 
613 			if (expprec && precision == -1
614 					&& *format != 'g' && *format != 'G' && *format != 'h' && *format != 'H') {
615 				zend_value_error("Precision -1 is only supported for %%g, %%G, %%h and %%H");
616 				goto fail;
617 			}
618 
619 			/* now we expect to find a type specifier */
620 			tmp = &args[argnum];
621 			switch (*format) {
622 				case 's': {
623 					zend_string *t;
624 					zend_string *str = zval_get_tmp_string(tmp, &t);
625 					php_sprintf_appendstring(&result, &outpos,
626 											 ZSTR_VAL(str),
627 											 width, precision, padding,
628 											 alignment,
629 											 ZSTR_LEN(str),
630 											 /* neg */ false, expprec, 0);
631 					zend_tmp_string_release(t);
632 					break;
633 				}
634 
635 				case 'd':
636 					php_sprintf_appendint(&result, &outpos,
637 										  zval_get_long(tmp),
638 										  width, padding, alignment,
639 										  always_sign);
640 					break;
641 
642 				case 'u':
643 					php_sprintf_appenduint(&result, &outpos,
644 										  zval_get_long(tmp),
645 										  width, padding, alignment);
646 					break;
647 
648 				case 'e':
649 				case 'E':
650 				case 'f':
651 				case 'F':
652 				case 'g':
653 				case 'G':
654 				case 'h':
655 				case 'H':
656 					php_sprintf_appenddouble(&result, &outpos,
657 											 zval_get_double(tmp),
658 											 width, padding, alignment,
659 											 precision, adjusting,
660 											 *format, always_sign
661 											);
662 					break;
663 
664 				case 'c':
665 					php_sprintf_appendchar(&result, &outpos,
666 										(char) zval_get_long(tmp));
667 					break;
668 
669 				case 'o':
670 					php_sprintf_append2n(&result, &outpos,
671 										 zval_get_long(tmp),
672 										 width, padding, alignment, 3,
673 										 hexchars, expprec);
674 					break;
675 
676 				case 'x':
677 					php_sprintf_append2n(&result, &outpos,
678 										 zval_get_long(tmp),
679 										 width, padding, alignment, 4,
680 										 hexchars, expprec);
681 					break;
682 
683 				case 'X':
684 					php_sprintf_append2n(&result, &outpos,
685 										 zval_get_long(tmp),
686 										 width, padding, alignment, 4,
687 										 HEXCHARS, expprec);
688 					break;
689 
690 				case 'b':
691 					php_sprintf_append2n(&result, &outpos,
692 										 zval_get_long(tmp),
693 										 width, padding, alignment, 1,
694 										 hexchars, expprec);
695 					break;
696 
697 				case '%':
698 					php_sprintf_appendchar(&result, &outpos, '%');
699 
700 					break;
701 
702 				case '\0':
703 					if (!format_len) {
704 						zend_value_error("Missing format specifier at end of string");
705 						goto fail;
706 					}
707 					ZEND_FALLTHROUGH;
708 
709 				default:
710 					zend_value_error("Unknown format specifier \"%c\"", *format);
711 					goto fail;
712 			}
713 			format++;
714 			format_len--;
715 		}
716 	}
717 
718 	if (max_missing_argnum >= 0) {
719 		if (nb_additional_parameters == -1) {
720 			zend_value_error("The arguments array must contain %d items, %d given", max_missing_argnum + 1, argc);
721 		} else {
722 			zend_argument_count_error("%d arguments are required, %d given", max_missing_argnum + nb_additional_parameters + 1, argc + nb_additional_parameters);
723 		}
724 		goto fail;
725 	}
726 
727 	/* possibly, we have to make sure we have room for the terminating null? */
728 	ZSTR_VAL(result)[outpos]=0;
729 	ZSTR_LEN(result) = outpos;
730 	return result;
731 
732 fail:
733 	zend_string_efree(result);
734 	return NULL;
735 }
736 /* }}} */
737 
738 /* php_formatted_print_get_array() {{{ */
php_formatted_print_get_array(zend_array * array,int * argc)739 static zval *php_formatted_print_get_array(zend_array *array, int *argc)
740 {
741 	zval *args, *zv;
742 	int n;
743 
744 	n = zend_hash_num_elements(array);
745 	args = (zval *)safe_emalloc(n, sizeof(zval), 0);
746 	n = 0;
747 	ZEND_HASH_FOREACH_VAL(array, zv) {
748 		ZVAL_COPY_VALUE(&args[n], zv);
749 		n++;
750 	} ZEND_HASH_FOREACH_END();
751 
752 	*argc = n;
753 	return args;
754 }
755 /* }}} */
756 
757 /* {{{ Return a formatted string */
PHP_FUNCTION(sprintf)758 PHP_FUNCTION(sprintf)
759 {
760 	zend_string *result;
761 	char *format;
762 	size_t format_len;
763 	zval *args;
764 	int argc;
765 
766 	ZEND_PARSE_PARAMETERS_START(1, -1)
767 		Z_PARAM_STRING(format, format_len)
768 		Z_PARAM_VARIADIC('*', args, argc)
769 	ZEND_PARSE_PARAMETERS_END();
770 
771 	result = php_formatted_print(format, format_len, args, argc, 1);
772 	if (result == NULL) {
773 		RETURN_THROWS();
774 	}
775 	RETVAL_STR(result);
776 }
777 /* }}} */
778 
779 /* {{{ Return a formatted string */
PHP_FUNCTION(vsprintf)780 PHP_FUNCTION(vsprintf)
781 {
782 	zend_string *result;
783 	char *format;
784 	size_t format_len;
785 	zval *args;
786 	zend_array *array;
787 	int argc;
788 
789 	ZEND_PARSE_PARAMETERS_START(2, 2)
790 		Z_PARAM_STRING(format, format_len)
791 		Z_PARAM_ARRAY_HT(array)
792 	ZEND_PARSE_PARAMETERS_END();
793 
794 	args = php_formatted_print_get_array(array, &argc);
795 
796 	result = php_formatted_print(format, format_len, args, argc, -1);
797 	efree(args);
798 	if (result == NULL) {
799 		RETURN_THROWS();
800 	}
801 	RETVAL_STR(result);
802 }
803 /* }}} */
804 
805 /* {{{ Output a formatted string */
PHP_FUNCTION(printf)806 PHP_FUNCTION(printf)
807 {
808 	zend_string *result;
809 	size_t rlen;
810 	char *format;
811 	size_t format_len;
812 	zval *args;
813 	int argc;
814 
815 	ZEND_PARSE_PARAMETERS_START(1, -1)
816 		Z_PARAM_STRING(format, format_len)
817 		Z_PARAM_VARIADIC('*', args, argc)
818 	ZEND_PARSE_PARAMETERS_END();
819 
820 	result = php_formatted_print(format, format_len, args, argc, 1);
821 	if (result == NULL) {
822 		RETURN_THROWS();
823 	}
824 	rlen = PHPWRITE(ZSTR_VAL(result), ZSTR_LEN(result));
825 	zend_string_efree(result);
826 	RETURN_LONG(rlen);
827 }
828 /* }}} */
829 
830 /* {{{ Output a formatted string */
PHP_FUNCTION(vprintf)831 PHP_FUNCTION(vprintf)
832 {
833 	zend_string *result;
834 	size_t rlen;
835 	char *format;
836 	size_t format_len;
837 	zval *args;
838 	zend_array *array;
839 	int argc;
840 
841 	ZEND_PARSE_PARAMETERS_START(2, 2)
842 		Z_PARAM_STRING(format, format_len)
843 		Z_PARAM_ARRAY_HT(array)
844 	ZEND_PARSE_PARAMETERS_END();
845 
846 	args = php_formatted_print_get_array(array, &argc);
847 
848 	result = php_formatted_print(format, format_len, args, argc, -1);
849 	efree(args);
850 	if (result == NULL) {
851 		RETURN_THROWS();
852 	}
853 	rlen = PHPWRITE(ZSTR_VAL(result), ZSTR_LEN(result));
854 	zend_string_efree(result);
855 	RETURN_LONG(rlen);
856 }
857 /* }}} */
858 
859 /* {{{ Output a formatted string into a stream */
PHP_FUNCTION(fprintf)860 PHP_FUNCTION(fprintf)
861 {
862 	php_stream *stream;
863 	char *format;
864 	size_t format_len;
865 	zval *arg1, *args;
866 	int argc;
867 	zend_string *result;
868 
869 	ZEND_PARSE_PARAMETERS_START(2, -1)
870 		Z_PARAM_RESOURCE(arg1)
871 		Z_PARAM_STRING(format, format_len)
872 		Z_PARAM_VARIADIC('*', args, argc)
873 	ZEND_PARSE_PARAMETERS_END();
874 
875 	php_stream_from_zval(stream, arg1);
876 
877 	result = php_formatted_print(format, format_len, args, argc, 2);
878 	if (result == NULL) {
879 		RETURN_THROWS();
880 	}
881 
882 	php_stream_write(stream, ZSTR_VAL(result), ZSTR_LEN(result));
883 
884 	RETVAL_LONG(ZSTR_LEN(result));
885 	zend_string_efree(result);
886 }
887 /* }}} */
888 
889 /* {{{ Output a formatted string into a stream */
PHP_FUNCTION(vfprintf)890 PHP_FUNCTION(vfprintf)
891 {
892 	php_stream *stream;
893 	char *format;
894 	size_t format_len;
895 	zval *arg1, *args;
896 	zend_array *array;
897 	int argc;
898 	zend_string *result;
899 
900 	ZEND_PARSE_PARAMETERS_START(3, 3)
901 		Z_PARAM_RESOURCE(arg1)
902 		Z_PARAM_STRING(format, format_len)
903 		Z_PARAM_ARRAY_HT(array)
904 	ZEND_PARSE_PARAMETERS_END();
905 
906 	php_stream_from_zval(stream, arg1);
907 
908 	args = php_formatted_print_get_array(array, &argc);
909 
910 	result = php_formatted_print(format, format_len, args, argc, -1);
911 	efree(args);
912 	if (result == NULL) {
913 		RETURN_THROWS();
914 	}
915 
916 	php_stream_write(stream, ZSTR_VAL(result), ZSTR_LEN(result));
917 
918 	RETVAL_LONG(ZSTR_LEN(result));
919 	zend_string_efree(result);
920 }
921 /* }}} */
922