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