xref: /php-src/ext/standard/formatted_print.c (revision c2d3734e)
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 		char *str = is_negative ? "-INF" : "INF";
251 		php_sprintf_appendstring(buffer, pos, str, strlen(str), 0, padding,
252 								alignment, strlen(str), is_negative, 0, always_sign);
253 		return;
254 	}
255 
256 	switch (fmt) {
257 		case 'e':
258 		case 'E':
259 		case 'f':
260 		case 'F':
261 #ifdef ZTS
262 			localeconv_r(&lconv);
263 #else
264 			lconv = localeconv();
265 #endif
266 			s = php_conv_fp((fmt == 'f')?'F':fmt, number, 0, precision,
267 						(fmt == 'f')?LCONV_DECIMAL_POINT:'.',
268 						&is_negative, &num_buf[1], &s_len);
269 			if (is_negative) {
270 				num_buf[0] = '-';
271 				s = num_buf;
272 				s_len++;
273 			} else if (always_sign) {
274 				num_buf[0] = '+';
275 				s = num_buf;
276 				s_len++;
277 			}
278 			break;
279 
280 		case 'g':
281 		case 'G':
282 		case 'h':
283 		case 'H':
284 		{
285 			if (precision == 0)
286 				precision = 1;
287 
288 			char decimal_point = '.';
289 			if (fmt == 'g' || fmt == 'G') {
290 #ifdef ZTS
291 				localeconv_r(&lconv);
292 #else
293 				lconv = localeconv();
294 #endif
295 				decimal_point = LCONV_DECIMAL_POINT;
296 			}
297 
298 			char exp_char = fmt == 'G' || fmt == 'H' ? 'E' : 'e';
299 			/* We use &num_buf[ 1 ], so that we have room for the sign. */
300 			s = zend_gcvt(number, precision, decimal_point, exp_char, &num_buf[1]);
301 			is_negative = 0;
302 			if (*s == '-') {
303 				is_negative = 1;
304 				s = &num_buf[1];
305 			} else if (always_sign) {
306 				num_buf[0] = '+';
307 				s = num_buf;
308 			}
309 
310 			s_len = strlen(s);
311 			break;
312 		}
313 	}
314 
315 	php_sprintf_appendstring(buffer, pos, s, width, 0, padding,
316 							 alignment, s_len, is_negative, 0, always_sign);
317 }
318 /* }}} */
319 
320 /* php_spintf_appendd2n() {{{ */
321 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)322 php_sprintf_append2n(zend_string **buffer, size_t *pos, zend_long number,
323 					 size_t width, char padding, size_t alignment, int n,
324 					 const char *chartable, int expprec)
325 {
326 	char numbuf[NUM_BUF_SIZE];
327 	zend_ulong num;
328 	zend_ulong  i = NUM_BUF_SIZE - 1;
329 	int andbits = (1 << n) - 1;
330 
331 	PRINTF_DEBUG(("sprintf: append2n(%x, %x, %x, %d, %d, '%c', %d, %d, %x)\n",
332 				  *buffer, pos, &ZSTR_LEN(*buffer), number, width, padding, alignment, n,
333 				  chartable));
334 	PRINTF_DEBUG(("sprintf: append2n 2^%d andbits=%x\n", n, andbits));
335 
336 	num = (zend_ulong) number;
337 	numbuf[i] = '\0';
338 
339 	do {
340 		numbuf[--i] = chartable[(num & andbits)];
341 		num >>= n;
342 	}
343 	while (num > 0);
344 
345 	php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
346 							 padding, alignment, (NUM_BUF_SIZE - 1) - i,
347 							 /* neg */ false, expprec, 0);
348 }
349 /* }}} */
350 
351 /* php_spintf_getnumber() {{{ */
352 inline static int
php_sprintf_getnumber(char ** buffer,size_t * len)353 php_sprintf_getnumber(char **buffer, size_t *len)
354 {
355 	char *endptr;
356 	zend_long num = ZEND_STRTOL(*buffer, &endptr, 10);
357 	size_t i;
358 
359 	if (endptr != NULL) {
360 		i = (endptr - *buffer);
361 		*len -= i;
362 		*buffer = endptr;
363 	}
364 	PRINTF_DEBUG(("sprintf_getnumber: number was %d bytes long\n", i));
365 
366 	if (num >= INT_MAX || num < 0) {
367 		return -1;
368 	} else {
369 		return (int) num;
370 	}
371 }
372 /* }}} */
373 
374 #define ARG_NUM_NEXT -1
375 #define ARG_NUM_INVALID -2
376 
php_sprintf_get_argnum(char ** format,size_t * format_len)377 int php_sprintf_get_argnum(char **format, size_t *format_len) {
378 	char *temppos = *format;
379 	while (isdigit((int) *temppos)) temppos++;
380 	if (*temppos != '$') {
381 		return ARG_NUM_NEXT;
382 	}
383 
384 	int argnum = php_sprintf_getnumber(format, format_len);
385 	if (argnum <= 0) {
386 		zend_value_error("Argument number specifier must be greater than zero and less than %d", INT_MAX);
387 		return ARG_NUM_INVALID;
388 	}
389 
390 	(*format)++;  /* skip the '$' */
391 	(*format_len)--;
392 	return argnum - 1;
393 }
394 
395 /* php_formatted_print() {{{
396  * New sprintf implementation for PHP.
397  *
398  * Modifiers:
399  *
400  *  " "   pad integers with spaces
401  *  "-"   left adjusted field
402  *   n    field size
403  *  "."n  precision (floats only)
404  *  "+"   Always place a sign (+ or -) in front of a number
405  *
406  * Type specifiers:
407  *
408  *  "%"   literal "%", modifiers are ignored.
409  *  "b"   integer argument is printed as binary
410  *  "c"   integer argument is printed as a single character
411  *  "d"   argument is an integer
412  *  "f"   the argument is a float
413  *  "o"   integer argument is printed as octal
414  *  "s"   argument is a string
415  *  "x"   integer argument is printed as lowercase hexadecimal
416  *  "X"   integer argument is printed as uppercase hexadecimal
417  *
418  * nb_additional_parameters is used for throwing errors:
419  *  - -1: ValueError is thrown (for vsprintf where args originates from an array)
420  *  - 0 or more: ArgumentCountError is thrown
421  */
422 static zend_string *
php_formatted_print(char * format,size_t format_len,zval * args,int argc,int nb_additional_parameters)423 php_formatted_print(char *format, size_t format_len, zval *args, int argc, int nb_additional_parameters)
424 {
425 	size_t size = 240, outpos = 0;
426 	int alignment, currarg, adjusting, argnum, width, precision;
427 	char *temppos, padding;
428 	zend_string *result;
429 	int always_sign;
430 	int max_missing_argnum = -1;
431 
432 	result = zend_string_alloc(size, 0);
433 
434 	currarg = 0;
435 	argnum = 0;
436 
437 	while (format_len) {
438 		int expprec;
439 		zval *tmp;
440 
441 		temppos = memchr(format, '%', format_len);
442 		if (!temppos) {
443 			php_sprintf_appendchars(&result, &outpos, format, format_len);
444 			break;
445 		} else if (temppos != format) {
446 			php_sprintf_appendchars(&result, &outpos, format, temppos - format);
447 			format_len -= temppos - format;
448 			format = temppos;
449 		}
450 		format++;			/* skip the '%' */
451 		format_len--;
452 
453 		if (*format == '%') {
454 			php_sprintf_appendchar(&result, &outpos, '%');
455 			format++;
456 			format_len--;
457 		} else {
458 			/* starting a new format specifier, reset variables */
459 			alignment = ALIGN_RIGHT;
460 			adjusting = 0;
461 			padding = ' ';
462 			always_sign = 0;
463 			expprec = 0;
464 
465 			PRINTF_DEBUG(("sprintf: first looking at '%c', inpos=%d\n",
466 						  *format, format - Z_STRVAL_P(z_format)));
467 			if (isalpha((int)*format)) {
468 				width = precision = 0;
469 				argnum = ARG_NUM_NEXT;
470 			} else {
471 				/* first look for argnum */
472 				argnum = php_sprintf_get_argnum(&format, &format_len);
473 				if (argnum == ARG_NUM_INVALID) {
474 					goto fail;
475 				}
476 
477 				/* after argnum comes modifiers */
478 				PRINTF_DEBUG(("sprintf: looking for modifiers\n"
479 							  "sprintf: now looking at '%c', inpos=%d\n",
480 							  *format, format - Z_STRVAL_P(z_format)));
481 				for (;; format++, format_len--) {
482 					if (*format == ' ' || *format == '0') {
483 						padding = *format;
484 					} else if (*format == '-') {
485 						alignment = ALIGN_LEFT;
486 						/* space padding, the default */
487 					} else if (*format == '+') {
488 						always_sign = 1;
489 					} else if (*format == '\'') {
490 						if (format_len > 1) {
491 							format++;
492 							format_len--;
493 							padding = *format;
494 						} else {
495 							zend_value_error("Missing padding character");
496 							goto fail;
497 						}
498 					} else {
499 						PRINTF_DEBUG(("sprintf: end of modifiers\n"));
500 						break;
501 					}
502 				}
503 				PRINTF_DEBUG(("sprintf: padding='%c'\n", padding));
504 				PRINTF_DEBUG(("sprintf: alignment=%s\n",
505 							  (alignment == ALIGN_LEFT) ? "left" : "right"));
506 
507 
508 				/* after modifiers comes width */
509 				if (*format == '*') {
510 					format++;
511 					format_len--;
512 
513 					int width_argnum = php_sprintf_get_argnum(&format, &format_len);
514 					if (width_argnum == ARG_NUM_INVALID) {
515 						goto fail;
516 					}
517 					if (width_argnum == ARG_NUM_NEXT) {
518 						width_argnum = currarg++;
519 					}
520 					if (width_argnum >= argc) {
521 						max_missing_argnum = MAX(max_missing_argnum, width_argnum);
522 						continue;
523 					}
524 					tmp = &args[width_argnum];
525 					ZVAL_DEREF(tmp);
526 					if (Z_TYPE_P(tmp) != IS_LONG) {
527 						zend_value_error("Width must be an integer");
528 						goto fail;
529 					}
530 					if (Z_LVAL_P(tmp) < 0 || Z_LVAL_P(tmp) > INT_MAX) {
531 						zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
532 						goto fail;
533 					}
534 					width = Z_LVAL_P(tmp);
535 					adjusting |= ADJ_WIDTH;
536 				} else if (isdigit((int)*format)) {
537 					PRINTF_DEBUG(("sprintf: getting width\n"));
538 					if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) {
539 						zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
540 						goto fail;
541 					}
542 					adjusting |= ADJ_WIDTH;
543 				} else {
544 					width = 0;
545 				}
546 				PRINTF_DEBUG(("sprintf: width=%d\n", width));
547 
548 				/* after width and argnum comes precision */
549 				if (*format == '.') {
550 					format++;
551 					format_len--;
552 					PRINTF_DEBUG(("sprintf: getting precision\n"));
553 					if (*format == '*') {
554 						format++;
555 						format_len--;
556 
557 						int prec_argnum = php_sprintf_get_argnum(&format, &format_len);
558 						if (prec_argnum == ARG_NUM_INVALID) {
559 							goto fail;
560 						}
561 						if (prec_argnum == ARG_NUM_NEXT) {
562 							prec_argnum = currarg++;
563 						}
564 						if (prec_argnum >= argc) {
565 							max_missing_argnum = MAX(max_missing_argnum, prec_argnum);
566 							continue;
567 						}
568 						tmp = &args[prec_argnum];
569 						ZVAL_DEREF(tmp);
570 						if (Z_TYPE_P(tmp) != IS_LONG) {
571 							zend_value_error("Precision must be an integer");
572 							goto fail;
573 						}
574 						if (Z_LVAL_P(tmp) < -1 || Z_LVAL_P(tmp) > INT_MAX) {
575 							zend_value_error("Precision must be between -1 and %d", INT_MAX);
576 							goto fail;
577 						}
578 						precision = Z_LVAL_P(tmp);
579 						adjusting |= ADJ_PRECISION;
580 						expprec = 1;
581 					} else if (isdigit((int)*format)) {
582 						if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) {
583 							zend_value_error("Precision must be greater than zero and less than %d", INT_MAX);
584 							goto fail;
585 						}
586 						adjusting |= ADJ_PRECISION;
587 						expprec = 1;
588 					} else {
589 						precision = 0;
590 					}
591 				} else {
592 					precision = 0;
593 				}
594 				PRINTF_DEBUG(("sprintf: precision=%d\n", precision));
595 			}
596 
597 			if (*format == 'l') {
598 				format++;
599 				format_len--;
600 			}
601 			PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
602 
603 			if (argnum == ARG_NUM_NEXT) {
604 				argnum = currarg++;
605 			}
606 			if (argnum >= argc) {
607 				max_missing_argnum = MAX(max_missing_argnum, argnum);
608 				continue;
609 			}
610 
611 			if (expprec && precision == -1
612 					&& *format != 'g' && *format != 'G' && *format != 'h' && *format != 'H') {
613 				zend_value_error("Precision -1 is only supported for %%g, %%G, %%h and %%H");
614 				goto fail;
615 			}
616 
617 			/* now we expect to find a type specifier */
618 			tmp = &args[argnum];
619 			switch (*format) {
620 				case 's': {
621 					zend_string *t;
622 					zend_string *str = zval_get_tmp_string(tmp, &t);
623 					php_sprintf_appendstring(&result, &outpos,
624 											 ZSTR_VAL(str),
625 											 width, precision, padding,
626 											 alignment,
627 											 ZSTR_LEN(str),
628 											 /* neg */ false, expprec, 0);
629 					zend_tmp_string_release(t);
630 					break;
631 				}
632 
633 				case 'd':
634 					php_sprintf_appendint(&result, &outpos,
635 										  zval_get_long(tmp),
636 										  width, padding, alignment,
637 										  always_sign);
638 					break;
639 
640 				case 'u':
641 					php_sprintf_appenduint(&result, &outpos,
642 										  zval_get_long(tmp),
643 										  width, padding, alignment);
644 					break;
645 
646 				case 'e':
647 				case 'E':
648 				case 'f':
649 				case 'F':
650 				case 'g':
651 				case 'G':
652 				case 'h':
653 				case 'H':
654 					php_sprintf_appenddouble(&result, &outpos,
655 											 zval_get_double(tmp),
656 											 width, padding, alignment,
657 											 precision, adjusting,
658 											 *format, always_sign
659 											);
660 					break;
661 
662 				case 'c':
663 					php_sprintf_appendchar(&result, &outpos,
664 										(char) zval_get_long(tmp));
665 					break;
666 
667 				case 'o':
668 					php_sprintf_append2n(&result, &outpos,
669 										 zval_get_long(tmp),
670 										 width, padding, alignment, 3,
671 										 hexchars, expprec);
672 					break;
673 
674 				case 'x':
675 					php_sprintf_append2n(&result, &outpos,
676 										 zval_get_long(tmp),
677 										 width, padding, alignment, 4,
678 										 hexchars, expprec);
679 					break;
680 
681 				case 'X':
682 					php_sprintf_append2n(&result, &outpos,
683 										 zval_get_long(tmp),
684 										 width, padding, alignment, 4,
685 										 HEXCHARS, expprec);
686 					break;
687 
688 				case 'b':
689 					php_sprintf_append2n(&result, &outpos,
690 										 zval_get_long(tmp),
691 										 width, padding, alignment, 1,
692 										 hexchars, expprec);
693 					break;
694 
695 				case '%':
696 					php_sprintf_appendchar(&result, &outpos, '%');
697 
698 					break;
699 
700 				case '\0':
701 					if (!format_len) {
702 						zend_value_error("Missing format specifier at end of string");
703 						goto fail;
704 					}
705 					ZEND_FALLTHROUGH;
706 
707 				default:
708 					zend_value_error("Unknown format specifier \"%c\"", *format);
709 					goto fail;
710 			}
711 			format++;
712 			format_len--;
713 		}
714 	}
715 
716 	if (max_missing_argnum >= 0) {
717 		if (nb_additional_parameters == -1) {
718 			zend_value_error("The arguments array must contain %d items, %d given", max_missing_argnum + 1, argc);
719 		} else {
720 			zend_argument_count_error("%d arguments are required, %d given", max_missing_argnum + nb_additional_parameters + 1, argc + nb_additional_parameters);
721 		}
722 		goto fail;
723 	}
724 
725 	/* possibly, we have to make sure we have room for the terminating null? */
726 	ZSTR_VAL(result)[outpos]=0;
727 	ZSTR_LEN(result) = outpos;
728 	return result;
729 
730 fail:
731 	zend_string_efree(result);
732 	return NULL;
733 }
734 /* }}} */
735 
736 /* php_formatted_print_get_array() {{{ */
php_formatted_print_get_array(zend_array * array,int * argc)737 static zval *php_formatted_print_get_array(zend_array *array, int *argc)
738 {
739 	zval *args, *zv;
740 	int n;
741 
742 	n = zend_hash_num_elements(array);
743 	args = (zval *)safe_emalloc(n, sizeof(zval), 0);
744 	n = 0;
745 	ZEND_HASH_FOREACH_VAL(array, zv) {
746 		ZVAL_COPY_VALUE(&args[n], zv);
747 		n++;
748 	} ZEND_HASH_FOREACH_END();
749 
750 	*argc = n;
751 	return args;
752 }
753 /* }}} */
754 
755 /* {{{ Return a formatted string */
PHP_FUNCTION(sprintf)756 PHP_FUNCTION(sprintf)
757 {
758 	zend_string *result;
759 	char *format;
760 	size_t format_len;
761 	zval *args;
762 	int argc;
763 
764 	ZEND_PARSE_PARAMETERS_START(1, -1)
765 		Z_PARAM_STRING(format, format_len)
766 		Z_PARAM_VARIADIC('*', args, argc)
767 	ZEND_PARSE_PARAMETERS_END();
768 
769 	result = php_formatted_print(format, format_len, args, argc, 1);
770 	if (result == NULL) {
771 		RETURN_THROWS();
772 	}
773 	RETVAL_STR(result);
774 }
775 /* }}} */
776 
777 /* {{{ Return a formatted string */
PHP_FUNCTION(vsprintf)778 PHP_FUNCTION(vsprintf)
779 {
780 	zend_string *result;
781 	char *format;
782 	size_t format_len;
783 	zval *args;
784 	zend_array *array;
785 	int argc;
786 
787 	ZEND_PARSE_PARAMETERS_START(2, 2)
788 		Z_PARAM_STRING(format, format_len)
789 		Z_PARAM_ARRAY_HT(array)
790 	ZEND_PARSE_PARAMETERS_END();
791 
792 	args = php_formatted_print_get_array(array, &argc);
793 
794 	result = php_formatted_print(format, format_len, args, argc, -1);
795 	efree(args);
796 	if (result == NULL) {
797 		RETURN_THROWS();
798 	}
799 	RETVAL_STR(result);
800 }
801 /* }}} */
802 
803 /* {{{ Output a formatted string */
PHP_FUNCTION(printf)804 PHP_FUNCTION(printf)
805 {
806 	zend_string *result;
807 	size_t rlen;
808 	char *format;
809 	size_t format_len;
810 	zval *args;
811 	int argc;
812 
813 	ZEND_PARSE_PARAMETERS_START(1, -1)
814 		Z_PARAM_STRING(format, format_len)
815 		Z_PARAM_VARIADIC('*', args, argc)
816 	ZEND_PARSE_PARAMETERS_END();
817 
818 	result = php_formatted_print(format, format_len, args, argc, 1);
819 	if (result == NULL) {
820 		RETURN_THROWS();
821 	}
822 	rlen = PHPWRITE(ZSTR_VAL(result), ZSTR_LEN(result));
823 	zend_string_efree(result);
824 	RETURN_LONG(rlen);
825 }
826 /* }}} */
827 
828 /* {{{ Output a formatted string */
PHP_FUNCTION(vprintf)829 PHP_FUNCTION(vprintf)
830 {
831 	zend_string *result;
832 	size_t rlen;
833 	char *format;
834 	size_t format_len;
835 	zval *args;
836 	zend_array *array;
837 	int argc;
838 
839 	ZEND_PARSE_PARAMETERS_START(2, 2)
840 		Z_PARAM_STRING(format, format_len)
841 		Z_PARAM_ARRAY_HT(array)
842 	ZEND_PARSE_PARAMETERS_END();
843 
844 	args = php_formatted_print_get_array(array, &argc);
845 
846 	result = php_formatted_print(format, format_len, args, argc, -1);
847 	efree(args);
848 	if (result == NULL) {
849 		RETURN_THROWS();
850 	}
851 	rlen = PHPWRITE(ZSTR_VAL(result), ZSTR_LEN(result));
852 	zend_string_efree(result);
853 	RETURN_LONG(rlen);
854 }
855 /* }}} */
856 
857 /* {{{ Output a formatted string into a stream */
PHP_FUNCTION(fprintf)858 PHP_FUNCTION(fprintf)
859 {
860 	php_stream *stream;
861 	char *format;
862 	size_t format_len;
863 	zval *arg1, *args;
864 	int argc;
865 	zend_string *result;
866 
867 	ZEND_PARSE_PARAMETERS_START(2, -1)
868 		Z_PARAM_RESOURCE(arg1)
869 		Z_PARAM_STRING(format, format_len)
870 		Z_PARAM_VARIADIC('*', args, argc)
871 	ZEND_PARSE_PARAMETERS_END();
872 
873 	php_stream_from_zval(stream, arg1);
874 
875 	result = php_formatted_print(format, format_len, args, argc, 2);
876 	if (result == NULL) {
877 		RETURN_THROWS();
878 	}
879 
880 	php_stream_write(stream, ZSTR_VAL(result), ZSTR_LEN(result));
881 
882 	RETVAL_LONG(ZSTR_LEN(result));
883 	zend_string_efree(result);
884 }
885 /* }}} */
886 
887 /* {{{ Output a formatted string into a stream */
PHP_FUNCTION(vfprintf)888 PHP_FUNCTION(vfprintf)
889 {
890 	php_stream *stream;
891 	char *format;
892 	size_t format_len;
893 	zval *arg1, *args;
894 	zend_array *array;
895 	int argc;
896 	zend_string *result;
897 
898 	ZEND_PARSE_PARAMETERS_START(3, 3)
899 		Z_PARAM_RESOURCE(arg1)
900 		Z_PARAM_STRING(format, format_len)
901 		Z_PARAM_ARRAY_HT(array)
902 	ZEND_PARSE_PARAMETERS_END();
903 
904 	php_stream_from_zval(stream, arg1);
905 
906 	args = php_formatted_print_get_array(array, &argc);
907 
908 	result = php_formatted_print(format, format_len, args, argc, -1);
909 	efree(args);
910 	if (result == NULL) {
911 		RETURN_THROWS();
912 	}
913 
914 	php_stream_write(stream, ZSTR_VAL(result), ZSTR_LEN(result));
915 
916 	RETVAL_LONG(ZSTR_LEN(result));
917 	zend_string_efree(result);
918 }
919 /* }}} */
920