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