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