Coverage Report

Created: 2026-09-14 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
1.28k
#define LCONV_DECIMAL_POINT (*lconv->decimal_point)
25
#endif
26
27
2.26k
#define ALIGN_LEFT 0
28
5.00k
#define ALIGN_RIGHT 1
29
833
#define ADJ_WIDTH 1
30
2.68k
#define ADJ_PRECISION 2
31
1.35k
#define NUM_BUF_SIZE 500
32
80
#define FLOAT_PRECISION 6
33
1.26k
#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
713
{
49
713
  if ((*pos + 1) >= ZSTR_LEN(*buffer)) {
50
2
    PRINTF_DEBUG(("%s(): ereallocing buffer to %zu bytes\n", get_active_function_name(), ZSTR_LEN(*buffer)));
51
2
    *buffer = zend_string_extend(*buffer, ZSTR_LEN(*buffer) << 1, 0);
52
2
  }
53
713
  PRINTF_DEBUG(("sprintf: appending '%c', pos=%zu\n", add, *pos));
54
713
  ZSTR_VAL(*buffer)[(*pos)++] = add;
55
713
}
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
3.30k
{
62
3.30k
  if ((*pos + len) >= ZSTR_LEN(*buffer)) {
63
143
    size_t nlen = ZSTR_LEN(*buffer);
64
65
143
    PRINTF_DEBUG(("%s(): ereallocing buffer to %zu bytes\n", get_active_function_name(), ZSTR_LEN(*buffer)));
66
186
    do {
67
186
      nlen = nlen << 1;
68
186
    } while ((*pos + len) >= nlen);
69
143
    *buffer = zend_string_extend(*buffer, nlen, 0);
70
143
  }
71
3.30k
  PRINTF_DEBUG(("sprintf: appending \"%s\", pos=%zu\n", add, *pos));
72
3.30k
  memcpy(ZSTR_VAL(*buffer) + (*pos), add, len);
73
3.30k
  *pos += len;
74
3.30k
}
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
2.18k
{
83
2.18k
  size_t npad;
84
2.18k
  size_t req_size;
85
2.18k
  size_t copy_len;
86
2.18k
  size_t m_width;
87
88
2.18k
  copy_len = (expprec ? MIN(max_width, len) : len);
89
2.18k
  npad = (min_width < copy_len) ? 0 : min_width - copy_len;
90
91
2.18k
  PRINTF_DEBUG(("sprintf: appendstring(%p, %zu, %zu, \"%s\", %zu, '%c', %zu)\n",
92
2.18k
          *buffer, *pos, ZSTR_LEN(*buffer), add, min_width, padding, alignment));
93
2.18k
  m_width = MAX(min_width, copy_len);
94
95
2.18k
  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
2.18k
  req_size = *pos + m_width + 1;
100
101
2.18k
  if (req_size > ZSTR_LEN(*buffer)) {
102
27
    size_t size = ZSTR_LEN(*buffer);
103
238
    while (req_size > size) {
104
211
      if (size > SIZE_MAX/2) {
105
0
        zend_error_noreturn(E_ERROR, "Field width %zd is too long", req_size);
106
0
      }
107
211
      size <<= 1;
108
211
    }
109
27
    PRINTF_DEBUG(("sprintf ereallocing buffer to %zu bytes\n", size));
110
27
    *buffer = zend_string_extend(*buffer, size, 0);
111
27
  }
112
2.18k
  if (alignment == ALIGN_RIGHT) {
113
2.16k
    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
303M
    while (npad-- > 0) {
120
303M
      ZSTR_VAL(*buffer)[(*pos)++] = padding;
121
303M
    }
122
2.16k
  }
123
2.18k
  PRINTF_DEBUG(("sprintf: appending \"%s\"\n", add));
124
2.18k
  memcpy(&ZSTR_VAL(*buffer)[*pos], add, copy_len + 1);
125
2.18k
  *pos += copy_len;
126
2.18k
  if (alignment == ALIGN_LEFT) {
127
217k
    while (npad--) {
128
217k
      ZSTR_VAL(*buffer)[(*pos)++] = padding;
129
217k
    }
130
19
  }
131
2.18k
}
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
629
{
140
629
  char numbuf[NUM_BUF_SIZE];
141
629
  zend_ulong magn, nmagn;
142
629
  unsigned int i = NUM_BUF_SIZE - 1, neg = 0;
143
144
629
  PRINTF_DEBUG(("sprintf: appendint(%p, %zu, %zu, " ZEND_LONG_FMT ", %zu, '%c', %zu)\n",
145
629
          *buffer, *pos, ZSTR_LEN(*buffer), number, width, padding, alignment));
146
629
  if (number < 0) {
147
2
    neg = 1;
148
2
    magn = ((zend_ulong) -(number + 1)) + 1;
149
627
  } else {
150
627
    magn = (zend_ulong) number;
151
627
  }
152
153
  /* Can't right-pad 0's on integers */
154
629
  if(alignment==0 && padding=='0') padding=' ';
155
156
629
  numbuf[i] = '\0';
157
158
1.50k
  do {
159
1.50k
    nmagn = magn / 10;
160
161
1.50k
    numbuf[--i] = (unsigned char)(magn - (nmagn * 10)) + '0';
162
1.50k
    magn = nmagn;
163
1.50k
  }
164
1.50k
  while (magn > 0 && i > 1);
165
629
  if (neg) {
166
2
    numbuf[--i] = '-';
167
627
  } else if (always_sign) {
168
0
    numbuf[--i] = '+';
169
0
  }
170
629
  PRINTF_DEBUG(("sprintf: appending " ZEND_LONG_FMT " as \"%s\", i=%u\n",
171
629
          number, &numbuf[i], i));
172
629
  php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
173
629
               padding, alignment, (NUM_BUF_SIZE - 1) - i,
174
629
               neg, 0, always_sign);
175
629
}
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
4
{
184
4
  char numbuf[NUM_BUF_SIZE];
185
4
  zend_ulong magn, nmagn;
186
4
  unsigned int i = NUM_BUF_SIZE - 1;
187
188
4
  PRINTF_DEBUG(("sprintf: appenduint(%p, %zu, %zu, " ZEND_LONG_FMT ", %zu, '%c', %zu)\n",
189
4
          *buffer, *pos, ZSTR_LEN(*buffer), number, width, padding, alignment));
190
4
  magn = (zend_ulong) number;
191
192
  /* Can't right-pad 0's on integers */
193
4
  if (alignment == 0 && padding == '0') padding = ' ';
194
195
4
  numbuf[i] = '\0';
196
197
16
  do {
198
16
    nmagn = magn / 10;
199
200
16
    numbuf[--i] = (unsigned char)(magn - (nmagn * 10)) + '0';
201
16
    magn = nmagn;
202
16
  } while (magn > 0 && i > 0);
203
204
4
  PRINTF_DEBUG(("sprintf: appending " ZEND_LONG_FMT " as \"%s\", i=%d\n", number, &numbuf[i], i));
205
4
  php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
206
4
               padding, alignment, (NUM_BUF_SIZE - 1) - i, /* neg */ false, 0, 0);
207
4
}
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
1.34k
{
220
1.34k
  char num_buf[NUM_BUF_SIZE];
221
1.34k
  char *s = NULL;
222
1.34k
  size_t s_len = 0;
223
1.34k
  bool is_negative = false;
224
#ifdef ZTS
225
  struct lconv lconv;
226
#else
227
1.34k
  struct lconv *lconv;
228
1.34k
#endif
229
230
1.34k
  PRINTF_DEBUG(("sprintf: appenddouble(%p, %zu, %zu, %f, %zu, '%c', %zu, %c)\n",
231
1.34k
          *buffer, *pos, ZSTR_LEN(*buffer), number, width, padding, alignment, fmt));
232
1.34k
  if ((adjust & ADJ_PRECISION) == 0) {
233
80
    precision = FLOAT_PRECISION;
234
1.26k
  } 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
1.34k
  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
1.34k
  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
1.34k
  switch (fmt) {
255
61
    case 'e':
256
63
    case 'E':
257
1.33k
    case 'f':
258
1.33k
    case 'F':
259
#ifdef ZTS
260
      localeconv_r(&lconv);
261
#else
262
1.33k
      lconv = localeconv();
263
1.33k
#endif
264
1.33k
      s = php_conv_fp((fmt == 'f')?'F':fmt, number, 0, precision,
265
1.33k
            (fmt == 'f')?LCONV_DECIMAL_POINT:'.',
266
1.33k
            &is_negative, &num_buf[1], &s_len);
267
1.33k
      if (is_negative) {
268
206
        num_buf[0] = '-';
269
206
        s = num_buf;
270
206
        s_len++;
271
1.12k
      } else if (always_sign) {
272
0
        num_buf[0] = '+';
273
0
        s = num_buf;
274
0
        s_len++;
275
0
      }
276
1.33k
      break;
277
278
12
    case 'g':
279
12
    case 'G':
280
12
    case 'h':
281
12
    case 'H':
282
12
    {
283
12
      if (precision == 0)
284
9
        precision = 1;
285
286
12
      char decimal_point = '.';
287
12
      if (fmt == 'g' || fmt == 'G') {
288
#ifdef ZTS
289
        localeconv_r(&lconv);
290
#else
291
12
        lconv = localeconv();
292
12
#endif
293
12
        decimal_point = LCONV_DECIMAL_POINT;
294
12
      }
295
296
12
      char exp_char = fmt == 'G' || fmt == 'H' ? 'E' : 'e';
297
      /* We use &num_buf[ 1 ], so that we have room for the sign. */
298
12
      s = zend_gcvt(number, precision, decimal_point, exp_char, &num_buf[1]);
299
12
      is_negative = false;
300
12
      if (*s == '-') {
301
9
        is_negative = true;
302
9
        s = &num_buf[1];
303
9
      } else if (always_sign) {
304
0
        num_buf[0] = '+';
305
0
        s = num_buf;
306
0
      }
307
308
12
      s_len = strlen(s);
309
12
      break;
310
12
    }
311
1.34k
  }
312
313
1.34k
  php_sprintf_appendstring(buffer, pos, s, width, 0, padding,
314
1.34k
               alignment, s_len, is_negative, 0, always_sign);
315
1.34k
}
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
43
{
324
43
  char numbuf[NUM_BUF_SIZE];
325
43
  zend_ulong num;
326
43
  zend_ulong  i = NUM_BUF_SIZE - 1;
327
43
  int andbits = (1 << n) - 1;
328
329
43
  PRINTF_DEBUG(("sprintf: append2n(%p, %zu, %zu, " ZEND_LONG_FMT ", %zu, '%c', %zu, %d, %p)\n",
330
43
          *buffer, *pos, ZSTR_LEN(*buffer), number, width, padding, alignment, n,
331
43
          chartable));
332
43
  PRINTF_DEBUG(("sprintf: append2n 2^%d andbits=%x\n", n, andbits));
333
334
43
  num = (zend_ulong) number;
335
43
  numbuf[i] = '\0';
336
337
103
  do {
338
103
    numbuf[--i] = chartable[(num & andbits)];
339
103
    num >>= n;
340
103
  }
341
103
  while (num > 0);
342
343
43
  php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
344
43
               padding, alignment, (NUM_BUF_SIZE - 1) - i,
345
43
               /* neg */ false, expprec, 0);
346
43
}
347
/* }}} */
348
349
/* php_spintf_getnumber() {{{ */
350
inline static int
351
php_sprintf_getnumber(char **buffer, size_t *len)
352
2.10k
{
353
2.10k
  char *endptr;
354
2.10k
  zend_long num = ZEND_STRTOL(*buffer, &endptr, 10);
355
2.10k
  size_t i;
356
357
2.10k
  if (endptr != NULL) {
358
2.10k
    i = (endptr - *buffer);
359
2.10k
    *len -= i;
360
2.10k
    *buffer = endptr;
361
2.10k
  }
362
2.10k
  PRINTF_DEBUG(("sprintf_getnumber: number was %zu bytes long\n", i));
363
364
2.10k
  if (num >= INT_MAX || num < 0) {
365
8
    return -1;
366
2.09k
  } else {
367
2.09k
    return (int) num;
368
2.09k
  }
369
2.10k
}
370
/* }}} */
371
372
5.66k
#define ARG_NUM_NEXT -1
373
2.55k
#define ARG_NUM_INVALID -2
374
375
2.55k
int php_sprintf_get_argnum(char **format, size_t *format_len) {
376
2.55k
  char *temppos = *format;
377
2.55k
  while (isdigit((unsigned char)*temppos)) temppos++;
378
2.55k
  if (*temppos != '$') {
379
2.54k
    return ARG_NUM_NEXT;
380
2.54k
  }
381
382
12
  int argnum = php_sprintf_getnumber(format, format_len);
383
12
  if (argnum <= 0) {
384
3
    zend_value_error("Argument number specifier must be greater than zero and less than %d", INT_MAX);
385
3
    return ARG_NUM_INVALID;
386
3
  }
387
388
9
  (*format)++;  /* skip the '$' */
389
9
  (*format_len)--;
390
9
  return argnum - 1;
391
12
}
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
990
{
423
990
  size_t size = 240, outpos = 0;
424
990
  int alignment, currarg, adjusting, argnum, width, precision;
425
990
  char *temppos, padding;
426
990
  zend_string *result;
427
990
  int always_sign;
428
990
  int max_missing_argnum = -1;
429
430
  /* For debugging */
431
990
  const char *format_orig = format;
432
990
  ZEND_IGNORE_VALUE(format_orig);
433
434
990
  result = zend_string_alloc(size, 0);
435
436
990
  currarg = 0;
437
990
  argnum = 0;
438
439
4.42k
  while (format_len) {
440
4.38k
    int expprec;
441
4.38k
    zval *tmp;
442
443
4.38k
    temppos = memchr(format, '%', format_len);
444
4.38k
    if (!temppos) {
445
878
      php_sprintf_appendchars(&result, &outpos, format, format_len);
446
878
      break;
447
3.50k
    } else if (temppos != format) {
448
2.43k
      php_sprintf_appendchars(&result, &outpos, format, temppos - format);
449
2.43k
      format_len -= temppos - format;
450
2.43k
      format = temppos;
451
2.43k
    }
452
3.50k
    format++;     /* skip the '%' */
453
3.50k
    format_len--;
454
455
3.50k
    if (*format == '%') {
456
675
      php_sprintf_appendchar(&result, &outpos, '%');
457
675
      format++;
458
675
      format_len--;
459
2.82k
    } else {
460
      /* starting a new format specifier, reset variables */
461
2.82k
      alignment = ALIGN_RIGHT;
462
2.82k
      adjusting = 0;
463
2.82k
      padding = ' ';
464
2.82k
      always_sign = 0;
465
2.82k
      expprec = 0;
466
467
2.82k
      PRINTF_DEBUG(("sprintf: first looking at '%c', inpos=%zu\n",
468
2.82k
              *format, format - format_orig));
469
2.82k
      if (isalpha((unsigned char)*format)) {
470
296
        width = precision = 0;
471
296
        argnum = ARG_NUM_NEXT;
472
2.53k
      } else {
473
        /* first look for argnum */
474
2.53k
        argnum = php_sprintf_get_argnum(&format, &format_len);
475
2.53k
        if (argnum == ARG_NUM_INVALID) {
476
3
          goto fail;
477
3
        }
478
479
        /* after argnum comes modifiers */
480
2.52k
        PRINTF_DEBUG(("sprintf: looking for modifiers\n"
481
2.52k
                "sprintf: now looking at '%c', inpos=%zu\n",
482
2.52k
                *format, format - format_orig));
483
3.42k
        for (;; format++, format_len--) {
484
3.42k
          if (*format == ' ' || *format == '0') {
485
776
            padding = *format;
486
2.64k
          } else if (*format == '-') {
487
86
            alignment = ALIGN_LEFT;
488
            /* space padding, the default */
489
2.56k
          } else if (*format == '+') {
490
28
            always_sign = 1;
491
2.53k
          } else if (*format == '\'') {
492
5
            if (format_len > 1) {
493
4
              format++;
494
4
              format_len--;
495
4
              padding = *format;
496
4
            } else {
497
1
              zend_value_error("Missing padding character");
498
1
              goto fail;
499
1
            }
500
2.52k
          } else {
501
2.52k
            PRINTF_DEBUG(("sprintf: end of modifiers\n"));
502
2.52k
            break;
503
2.52k
          }
504
3.42k
        }
505
2.52k
        PRINTF_DEBUG(("sprintf: padding='%c'\n", padding));
506
2.52k
        PRINTF_DEBUG(("sprintf: alignment=%s\n",
507
2.52k
                (alignment == ALIGN_LEFT) ? "left" : "right"));
508
509
510
        /* after modifiers comes width */
511
2.52k
        if (*format == '*') {
512
22
          format++;
513
22
          format_len--;
514
515
22
          int width_argnum = php_sprintf_get_argnum(&format, &format_len);
516
22
          if (width_argnum == ARG_NUM_INVALID) {
517
0
            goto fail;
518
0
          }
519
22
          if (width_argnum == ARG_NUM_NEXT) {
520
22
            width_argnum = currarg++;
521
22
          }
522
22
          if (width_argnum >= argc) {
523
9
            max_missing_argnum = MAX(max_missing_argnum, width_argnum);
524
9
            continue;
525
9
          }
526
13
          tmp = &args[width_argnum];
527
13
          ZVAL_DEREF(tmp);
528
13
          if (Z_TYPE_P(tmp) != IS_LONG) {
529
0
            zend_value_error("Width must be an integer");
530
0
            goto fail;
531
0
          }
532
13
          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
13
          width = Z_LVAL_P(tmp);
537
13
          adjusting |= ADJ_WIDTH;
538
2.50k
        } else if (isdigit((unsigned char)*format)) {
539
828
          PRINTF_DEBUG(("sprintf: getting width\n"));
540
828
          if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) {
541
8
            zend_value_error("Width must be between 0 and %d", INT_MAX);
542
8
            goto fail;
543
8
          }
544
820
          adjusting |= ADJ_WIDTH;
545
1.67k
        } else {
546
1.67k
          width = 0;
547
1.67k
        }
548
2.51k
        PRINTF_DEBUG(("sprintf: width=%d\n", width));
549
550
        /* after width and argnum comes precision */
551
2.51k
        if (*format == '.') {
552
1.33k
          format++;
553
1.33k
          format_len--;
554
1.33k
          PRINTF_DEBUG(("sprintf: getting precision\n"));
555
1.33k
          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
1.33k
          } else if (isdigit((unsigned char)*format)) {
584
1.26k
            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
1.26k
            adjusting |= ADJ_PRECISION;
589
1.26k
            expprec = 1;
590
1.26k
          } else {
591
75
            precision = 0;
592
75
            adjusting |= ADJ_PRECISION;
593
75
          }
594
1.33k
        } else {
595
1.17k
          precision = 0;
596
1.17k
        }
597
2.51k
        PRINTF_DEBUG(("sprintf: precision=%d\n", precision));
598
2.51k
      }
599
600
2.80k
      if (*format == 'l') {
601
2
        format++;
602
2
        format_len--;
603
2
      }
604
2.80k
      PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
605
606
2.80k
      if (argnum == ARG_NUM_NEXT) {
607
2.79k
        argnum = currarg++;
608
2.79k
      }
609
2.80k
      if (argnum >= argc) {
610
529
        max_missing_argnum = MAX(max_missing_argnum, argnum);
611
529
        continue;
612
529
      }
613
614
2.27k
      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
2.27k
      tmp = &args[argnum];
622
2.27k
      switch (*format) {
623
159
        case 's': {
624
159
          zend_string *t;
625
159
          zend_string *str = zval_get_tmp_string(tmp, &t);
626
159
          php_sprintf_appendstring(&result, &outpos,
627
159
                       ZSTR_VAL(str),
628
159
                       width, precision, padding,
629
159
                       alignment,
630
159
                       ZSTR_LEN(str),
631
159
                       /* neg */ false, expprec, 0);
632
159
          zend_tmp_string_release(t);
633
159
          break;
634
0
        }
635
636
629
        case 'd':
637
629
          php_sprintf_appendint(&result, &outpos,
638
629
                      zval_get_long(tmp),
639
629
                      width, padding, alignment,
640
629
                      always_sign);
641
629
          break;
642
643
4
        case 'u':
644
4
          php_sprintf_appenduint(&result, &outpos,
645
4
                      zval_get_long(tmp),
646
4
                      width, padding, alignment);
647
4
          break;
648
649
61
        case 'e':
650
63
        case 'E':
651
1.33k
        case 'f':
652
1.33k
        case 'F':
653
1.34k
        case 'g':
654
1.34k
        case 'G':
655
1.34k
        case 'h':
656
1.34k
        case 'H':
657
1.34k
          php_sprintf_appenddouble(&result, &outpos,
658
1.34k
                       zval_get_double(tmp),
659
1.34k
                       width, padding, alignment,
660
1.34k
                       precision, adjusting,
661
1.34k
                       *format, always_sign
662
1.34k
                      );
663
1.34k
          break;
664
665
13
        case 'c':
666
13
          php_sprintf_appendchar(&result, &outpos,
667
13
                    (char) zval_get_long(tmp));
668
13
          break;
669
670
2
        case 'o':
671
2
          php_sprintf_append2n(&result, &outpos,
672
2
                     zval_get_long(tmp),
673
2
                     width, padding, alignment, 3,
674
2
                     hexchars, expprec);
675
2
          break;
676
677
36
        case 'x':
678
36
          php_sprintf_append2n(&result, &outpos,
679
36
                     zval_get_long(tmp),
680
36
                     width, padding, alignment, 4,
681
36
                     hexchars, expprec);
682
36
          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
5
        case 'b':
692
5
          php_sprintf_append2n(&result, &outpos,
693
5
                     zval_get_long(tmp),
694
5
                     width, padding, alignment, 1,
695
5
                     hexchars, expprec);
696
5
          break;
697
698
25
        case '%':
699
25
          php_sprintf_appendchar(&result, &outpos, '%');
700
701
25
          break;
702
703
6
        case '\0':
704
6
          if (!format_len) {
705
0
            zend_value_error("Missing format specifier at end of string");
706
0
            goto fail;
707
0
          }
708
6
          ZEND_FALLTHROUGH;
709
710
58
        default:
711
58
          zend_value_error("Unknown format specifier \"%c\"", *format);
712
58
          goto fail;
713
2.27k
      }
714
2.21k
      format++;
715
2.21k
      format_len--;
716
2.21k
    }
717
3.50k
  }
718
719
920
  if (max_missing_argnum >= 0) {
720
104
    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
104
    } else {
723
104
      zend_argument_count_error("%d arguments are required, %d given", max_missing_argnum + nb_additional_parameters + 1, argc + nb_additional_parameters);
724
104
    }
725
104
    goto fail;
726
104
  }
727
728
  /* possibly, we have to make sure we have room for the terminating null? */
729
816
  ZSTR_VAL(result)[outpos]=0;
730
816
  ZSTR_LEN(result) = outpos;
731
816
  return result;
732
733
174
fail:
734
174
  zend_string_efree(result);
735
174
  return NULL;
736
920
}
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
38
{
761
38
  zend_string *result;
762
38
  char *format;
763
38
  size_t format_len;
764
38
  zval *args;
765
38
  int argc;
766
767
113
  ZEND_PARSE_PARAMETERS_START(1, -1)
768
148
    Z_PARAM_STRING(format, format_len)
769
37
    Z_PARAM_VARIADIC('*', args, argc)
770
38
  ZEND_PARSE_PARAMETERS_END();
771
772
37
  result = php_formatted_print(format, format_len, args, argc, 1);
773
37
  if (result == NULL) {
774
7
    RETURN_THROWS();
775
7
  }
776
30
  RETVAL_STR(result);
777
30
}
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
953
{
809
953
  zend_string *result;
810
953
  size_t rlen;
811
953
  char *format;
812
953
  size_t format_len;
813
953
  zval *args;
814
953
  int argc;
815
816
2.85k
  ZEND_PARSE_PARAMETERS_START(1, -1)
817
3.81k
    Z_PARAM_STRING(format, format_len)
818
953
    Z_PARAM_VARIADIC('*', args, argc)
819
953
  ZEND_PARSE_PARAMETERS_END();
820
821
953
  result = php_formatted_print(format, format_len, args, argc, 1);
822
953
  if (result == NULL) {
823
167
    RETURN_THROWS();
824
167
  }
825
786
  rlen = PHPWRITE(ZSTR_VAL(result), ZSTR_LEN(result));
826
786
  zend_string_efree(result);
827
786
  RETURN_LONG(rlen);
828
786
}
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
/* }}} */