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