Coverage Report

Created: 2025-09-27 06:26

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 (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
476
#define LCONV_DECIMAL_POINT (*lconv->decimal_point)
27
#endif
28
29
5.10k
#define ALIGN_LEFT 0
30
10.2k
#define ALIGN_RIGHT 1
31
24
#define ADJ_WIDTH 1
32
949
#define ADJ_PRECISION 2
33
1.78k
#define NUM_BUF_SIZE 500
34
11
#define FLOAT_PRECISION 6
35
467
#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
110
{
51
110
  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
110
  PRINTF_DEBUG(("sprintf: appending '%c', pos=%zu\n", add, *pos));
56
110
  ZSTR_VAL(*buffer)[(*pos)++] = add;
57
110
}
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.49k
{
64
7.49k
  if ((*pos + len) >= ZSTR_LEN(*buffer)) {
65
41
    size_t nlen = ZSTR_LEN(*buffer);
66
67
41
    PRINTF_DEBUG(("%s(): ereallocing buffer to %zu bytes\n", get_active_function_name(), ZSTR_LEN(*buffer)));
68
60
    do {
69
60
      nlen = nlen << 1;
70
60
    } while ((*pos + len) >= nlen);
71
41
    *buffer = zend_string_extend(*buffer, nlen, 0);
72
41
  }
73
7.49k
  PRINTF_DEBUG(("sprintf: appending \"%s\", pos=%zu\n", add, *pos));
74
7.49k
  memcpy(ZSTR_VAL(*buffer) + (*pos), add, len);
75
7.49k
  *pos += len;
76
7.49k
}
77
/* }}} */
78
79
/* php_spintf_appendstring() {{{ */
80
inline static void
81
php_sprintf_appendstring(zend_string **buffer, size_t *pos, char *add,
82
               size_t min_width, size_t max_width, char padding,
83
               size_t alignment, size_t len, bool neg, int expprec, int always_sign)
84
5.09k
{
85
5.09k
  size_t npad;
86
5.09k
  size_t req_size;
87
5.09k
  size_t copy_len;
88
5.09k
  size_t m_width;
89
90
5.09k
  copy_len = (expprec ? MIN(max_width, len) : len);
91
5.09k
  npad = (min_width < copy_len) ? 0 : min_width - copy_len;
92
93
5.09k
  PRINTF_DEBUG(("sprintf: appendstring(%p, %zu, %zu, \"%s\", %zu, '%c', %zu)\n",
94
5.09k
          *buffer, *pos, ZSTR_LEN(*buffer), add, min_width, padding, alignment));
95
5.09k
  m_width = MAX(min_width, copy_len);
96
97
5.09k
  if(m_width > INT_MAX - *pos - 1) {
98
0
    zend_error_noreturn(E_ERROR, "Field width %zd is too long", m_width);
99
0
  }
100
101
5.09k
  req_size = *pos + m_width + 1;
102
103
5.09k
  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
5.09k
  if (alignment == ALIGN_RIGHT) {
115
5.09k
    if ((neg || always_sign) && padding=='0') {
116
0
      ZSTR_VAL(*buffer)[(*pos)++] = (neg) ? '-' : '+';
117
0
      add++;
118
0
      len--;
119
0
      copy_len--;
120
0
    }
121
5.11k
    while (npad-- > 0) {
122
18
      ZSTR_VAL(*buffer)[(*pos)++] = padding;
123
18
    }
124
5.09k
  }
125
5.09k
  PRINTF_DEBUG(("sprintf: appending \"%s\"\n", add));
126
5.09k
  memcpy(&ZSTR_VAL(*buffer)[*pos], add, copy_len + 1);
127
5.09k
  *pos += copy_len;
128
5.09k
  if (alignment == ALIGN_LEFT) {
129
0
    while (npad--) {
130
0
      ZSTR_VAL(*buffer)[(*pos)++] = padding;
131
0
    }
132
0
  }
133
5.09k
}
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
885
{
142
885
  char numbuf[NUM_BUF_SIZE];
143
885
  zend_ulong magn, nmagn;
144
885
  unsigned int i = NUM_BUF_SIZE - 1, neg = 0;
145
146
885
  PRINTF_DEBUG(("sprintf: appendint(%p, %zu, %zu, " ZEND_LONG_FMT ", %zu, '%c', %zu)\n",
147
885
          *buffer, *pos, ZSTR_LEN(*buffer), number, width, padding, alignment));
148
885
  if (number < 0) {
149
0
    neg = 1;
150
0
    magn = ((zend_ulong) -(number + 1)) + 1;
151
885
  } else {
152
885
    magn = (zend_ulong) number;
153
885
  }
154
155
  /* Can't right-pad 0's on integers */
156
885
  if(alignment==0 && padding=='0') padding=' ';
157
158
885
  numbuf[i] = '\0';
159
160
901
  do {
161
901
    nmagn = magn / 10;
162
163
901
    numbuf[--i] = (unsigned char)(magn - (nmagn * 10)) + '0';
164
901
    magn = nmagn;
165
901
  }
166
901
  while (magn > 0 && i > 1);
167
885
  if (neg) {
168
0
    numbuf[--i] = '-';
169
885
  } else if (always_sign) {
170
0
    numbuf[--i] = '+';
171
0
  }
172
885
  PRINTF_DEBUG(("sprintf: appending " ZEND_LONG_FMT " as \"%s\", i=%u\n",
173
885
          number, &numbuf[i], i));
174
885
  php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
175
885
               padding, alignment, (NUM_BUF_SIZE - 1) - i,
176
885
               neg, 0, always_sign);
177
885
}
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
478
{
222
478
  char num_buf[NUM_BUF_SIZE];
223
478
  char *s = NULL;
224
478
  size_t s_len = 0;
225
478
  bool is_negative = false;
226
#ifdef ZTS
227
  struct lconv lconv;
228
#else
229
478
  struct lconv *lconv;
230
478
#endif
231
232
478
  PRINTF_DEBUG(("sprintf: appenddouble(%p, %zu, %zu, %f, %zu, '%c', %zu, %c)\n",
233
478
          *buffer, *pos, ZSTR_LEN(*buffer), number, width, padding, alignment, fmt));
234
478
  if ((adjust & ADJ_PRECISION) == 0) {
235
11
    precision = FLOAT_PRECISION;
236
467
  } 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
478
  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
478
  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
478
  switch (fmt) {
257
0
    case 'e':
258
2
    case 'E':
259
476
    case 'f':
260
476
    case 'F':
261
#ifdef ZTS
262
      localeconv_r(&lconv);
263
#else
264
476
      lconv = localeconv();
265
476
#endif
266
476
      s = php_conv_fp((fmt == 'f')?'F':fmt, number, 0, precision,
267
476
            (fmt == 'f')?LCONV_DECIMAL_POINT:'.',
268
476
            &is_negative, &num_buf[1], &s_len);
269
476
      if (is_negative) {
270
129
        num_buf[0] = '-';
271
129
        s = num_buf;
272
129
        s_len++;
273
347
      } else if (always_sign) {
274
0
        num_buf[0] = '+';
275
0
        s = num_buf;
276
0
        s_len++;
277
0
      }
278
476
      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 = false;
302
2
      if (*s == '-') {
303
0
        is_negative = true;
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
478
  }
314
315
478
  php_sprintf_appendstring(buffer, pos, s, width, 0, padding,
316
478
               alignment, s_len, is_negative, 0, always_sign);
317
478
}
318
/* }}} */
319
320
/* php_spintf_appendd2n() {{{ */
321
inline static void
322
php_sprintf_append2n(zend_string **buffer, size_t *pos, zend_long number,
323
           size_t width, char padding, size_t alignment, int n,
324
           const char *chartable, int expprec)
325
3
{
326
3
  char numbuf[NUM_BUF_SIZE];
327
3
  zend_ulong num;
328
3
  zend_ulong  i = NUM_BUF_SIZE - 1;
329
3
  int andbits = (1 << n) - 1;
330
331
3
  PRINTF_DEBUG(("sprintf: append2n(%p, %zu, %zu, " ZEND_LONG_FMT ", %zu, '%c', %zu, %d, %p)\n",
332
3
          *buffer, *pos, ZSTR_LEN(*buffer), number, width, padding, alignment, n,
333
3
          chartable));
334
3
  PRINTF_DEBUG(("sprintf: append2n 2^%d andbits=%x\n", n, andbits));
335
336
3
  num = (zend_ulong) number;
337
3
  numbuf[i] = '\0';
338
339
3
  do {
340
3
    numbuf[--i] = chartable[(num & andbits)];
341
3
    num >>= n;
342
3
  }
343
3
  while (num > 0);
344
345
3
  php_sprintf_appendstring(buffer, pos, &numbuf[i], width, 0,
346
3
               padding, alignment, (NUM_BUF_SIZE - 1) - i,
347
3
               /* neg */ false, expprec, 0);
348
3
}
349
/* }}} */
350
351
/* php_spintf_getnumber() {{{ */
352
inline static int
353
php_sprintf_getnumber(char **buffer, size_t *len)
354
478
{
355
478
  char *endptr;
356
478
  zend_long num = ZEND_STRTOL(*buffer, &endptr, 10);
357
478
  size_t i;
358
359
478
  if (endptr != NULL) {
360
478
    i = (endptr - *buffer);
361
478
    *len -= i;
362
478
    *buffer = endptr;
363
478
  }
364
478
  PRINTF_DEBUG(("sprintf_getnumber: number was %zu bytes long\n", i));
365
366
478
  if (num >= INT_MAX || num < 0) {
367
0
    return -1;
368
478
  } else {
369
478
    return (int) num;
370
478
  }
371
478
}
372
/* }}} */
373
374
10.3k
#define ARG_NUM_NEXT -1
375
559
#define ARG_NUM_INVALID -2
376
377
559
int php_sprintf_get_argnum(char **format, size_t *format_len) {
378
559
  char *temppos = *format;
379
559
  while (isdigit((int) *temppos)) temppos++;
380
559
  if (*temppos != '$') {
381
559
    return ARG_NUM_NEXT;
382
559
  }
383
384
0
  int argnum = php_sprintf_getnumber(format, format_len);
385
0
  if (argnum <= 0) {
386
0
    zend_value_error("Argument number specifier must be greater than zero and less than %d", INT_MAX);
387
0
    return ARG_NUM_INVALID;
388
0
  }
389
390
0
  (*format)++;  /* skip the '$' */
391
0
  (*format_len)--;
392
0
  return argnum - 1;
393
0
}
394
395
/* php_formatted_print() {{{
396
 * New sprintf implementation for PHP.
397
 *
398
 * Modifiers:
399
 *
400
 *  " "   pad integers with spaces
401
 *  "-"   left adjusted field
402
 *   n    field size
403
 *  "."n  precision (floats only)
404
 *  "+"   Always place a sign (+ or -) in front of a number
405
 *
406
 * Type specifiers:
407
 *
408
 *  "%"   literal "%", modifiers are ignored.
409
 *  "b"   integer argument is printed as binary
410
 *  "c"   integer argument is printed as a single character
411
 *  "d"   argument is an integer
412
 *  "f"   the argument is a float
413
 *  "o"   integer argument is printed as octal
414
 *  "s"   argument is a string
415
 *  "x"   integer argument is printed as lowercase hexadecimal
416
 *  "X"   integer argument is printed as uppercase hexadecimal
417
 *
418
 * nb_additional_parameters is used for throwing errors:
419
 *  - -1: ValueError is thrown (for vsprintf where args originates from an array)
420
 *  - 0 or more: ArgumentCountError is thrown
421
 */
422
static zend_string *
423
php_formatted_print(char *format, size_t format_len, zval *args, int argc, int nb_additional_parameters)
424
4.02k
{
425
4.02k
  size_t size = 240, outpos = 0;
426
4.02k
  int alignment, currarg, adjusting, argnum, width, precision;
427
4.02k
  char *temppos, padding;
428
4.02k
  zend_string *result;
429
4.02k
  int always_sign;
430
4.02k
  int max_missing_argnum = -1;
431
432
  /* For debugging */
433
4.02k
  const char *format_orig = format;
434
4.02k
  ZEND_IGNORE_VALUE(format_orig);
435
436
4.02k
  result = zend_string_alloc(size, 0);
437
438
4.02k
  currarg = 0;
439
4.02k
  argnum = 0;
440
441
9.27k
  while (format_len) {
442
9.25k
    int expprec;
443
9.25k
    zval *tmp;
444
445
9.25k
    temppos = memchr(format, '%', format_len);
446
9.25k
    if (!temppos) {
447
3.98k
      php_sprintf_appendchars(&result, &outpos, format, format_len);
448
3.98k
      break;
449
5.26k
    } else if (temppos != format) {
450
3.50k
      php_sprintf_appendchars(&result, &outpos, format, temppos - format);
451
3.50k
      format_len -= temppos - format;
452
3.50k
      format = temppos;
453
3.50k
    }
454
5.26k
    format++;     /* skip the '%' */
455
5.26k
    format_len--;
456
457
5.26k
    if (*format == '%') {
458
108
      php_sprintf_appendchar(&result, &outpos, '%');
459
108
      format++;
460
108
      format_len--;
461
5.15k
    } else {
462
      /* starting a new format specifier, reset variables */
463
5.15k
      alignment = ALIGN_RIGHT;
464
5.15k
      adjusting = 0;
465
5.15k
      padding = ' ';
466
5.15k
      always_sign = 0;
467
5.15k
      expprec = 0;
468
469
5.15k
      PRINTF_DEBUG(("sprintf: first looking at '%c', inpos=%zu\n",
470
5.15k
              *format, format - format_orig));
471
5.15k
      if (isalpha((int)*format)) {
472
4.62k
        width = precision = 0;
473
4.62k
        argnum = ARG_NUM_NEXT;
474
4.62k
      } else {
475
        /* first look for argnum */
476
535
        argnum = php_sprintf_get_argnum(&format, &format_len);
477
535
        if (argnum == ARG_NUM_INVALID) {
478
0
          goto fail;
479
0
        }
480
481
        /* after argnum comes modifiers */
482
535
        PRINTF_DEBUG(("sprintf: looking for modifiers\n"
483
535
                "sprintf: now looking at '%c', inpos=%zu\n",
484
535
                *format, format - format_orig));
485
546
        for (;; format++, format_len--) {
486
546
          if (*format == ' ' || *format == '0') {
487
2
            padding = *format;
488
544
          } else if (*format == '-') {
489
9
            alignment = ALIGN_LEFT;
490
            /* space padding, the default */
491
535
          } else if (*format == '+') {
492
0
            always_sign = 1;
493
535
          } 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
535
          } else {
503
535
            PRINTF_DEBUG(("sprintf: end of modifiers\n"));
504
535
            break;
505
535
          }
506
546
        }
507
535
        PRINTF_DEBUG(("sprintf: padding='%c'\n", padding));
508
535
        PRINTF_DEBUG(("sprintf: alignment=%s\n",
509
535
                (alignment == ALIGN_LEFT) ? "left" : "right"));
510
511
512
        /* after modifiers comes width */
513
535
        if (*format == '*') {
514
24
          format++;
515
24
          format_len--;
516
517
24
          int width_argnum = php_sprintf_get_argnum(&format, &format_len);
518
24
          if (width_argnum == ARG_NUM_INVALID) {
519
0
            goto fail;
520
0
          }
521
24
          if (width_argnum == ARG_NUM_NEXT) {
522
24
            width_argnum = currarg++;
523
24
          }
524
24
          if (width_argnum >= argc) {
525
9
            max_missing_argnum = MAX(max_missing_argnum, width_argnum);
526
9
            continue;
527
9
          }
528
15
          tmp = &args[width_argnum];
529
15
          ZVAL_DEREF(tmp);
530
15
          if (Z_TYPE_P(tmp) != IS_LONG) {
531
0
            zend_value_error("Width must be an integer");
532
0
            goto fail;
533
0
          }
534
15
          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
15
          width = Z_LVAL_P(tmp);
539
15
          adjusting |= ADJ_WIDTH;
540
511
        } else if (isdigit((int)*format)) {
541
9
          PRINTF_DEBUG(("sprintf: getting width\n"));
542
9
          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
9
          adjusting |= ADJ_WIDTH;
547
502
        } else {
548
502
          width = 0;
549
502
        }
550
526
        PRINTF_DEBUG(("sprintf: width=%d\n", width));
551
552
        /* after width and argnum comes precision */
553
526
        if (*format == '.') {
554
471
          format++;
555
471
          format_len--;
556
471
          PRINTF_DEBUG(("sprintf: getting precision\n"));
557
471
          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
471
          } else if (isdigit((int)*format)) {
586
469
            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
469
            adjusting |= ADJ_PRECISION;
591
469
            expprec = 1;
592
469
          } else {
593
2
            precision = 0;
594
2
            adjusting |= ADJ_PRECISION;
595
2
          }
596
471
        } else {
597
55
          precision = 0;
598
55
        }
599
526
        PRINTF_DEBUG(("sprintf: precision=%d\n", precision));
600
526
      }
601
602
5.15k
      if (*format == 'l') {
603
0
        format++;
604
0
        format_len--;
605
0
      }
606
5.15k
      PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
607
608
5.15k
      if (argnum == ARG_NUM_NEXT) {
609
5.15k
        argnum = currarg++;
610
5.15k
      }
611
5.15k
      if (argnum >= argc) {
612
37
        max_missing_argnum = MAX(max_missing_argnum, argnum);
613
37
        continue;
614
37
      }
615
616
5.11k
      if (expprec && precision == -1
617
0
          && *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
5.11k
      tmp = &args[argnum];
624
5.11k
      switch (*format) {
625
3.72k
        case 's': {
626
3.72k
          zend_string *t;
627
3.72k
          zend_string *str = zval_get_tmp_string(tmp, &t);
628
3.72k
          php_sprintf_appendstring(&result, &outpos,
629
3.72k
                       ZSTR_VAL(str),
630
3.72k
                       width, precision, padding,
631
3.72k
                       alignment,
632
3.72k
                       ZSTR_LEN(str),
633
3.72k
                       /* neg */ false, expprec, 0);
634
3.72k
          zend_tmp_string_release(t);
635
3.72k
          break;
636
0
        }
637
638
885
        case 'd':
639
885
          php_sprintf_appendint(&result, &outpos,
640
885
                      zval_get_long(tmp),
641
885
                      width, padding, alignment,
642
885
                      always_sign);
643
885
          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
476
        case 'f':
654
476
        case 'F':
655
476
        case 'g':
656
478
        case 'G':
657
478
        case 'h':
658
478
        case 'H':
659
478
          php_sprintf_appenddouble(&result, &outpos,
660
478
                       zval_get_double(tmp),
661
478
                       width, padding, alignment,
662
478
                       precision, adjusting,
663
478
                       *format, always_sign
664
478
                      );
665
478
          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
3
        case 'o':
673
3
          php_sprintf_append2n(&result, &outpos,
674
3
                     zval_get_long(tmp),
675
3
                     width, padding, alignment, 3,
676
3
                     hexchars, expprec);
677
3
          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
2
        case '%':
701
2
          php_sprintf_appendchar(&result, &outpos, '%');
702
703
2
          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
17
        default:
713
17
          zend_value_error("Unknown format specifier \"%c\"", *format);
714
17
          goto fail;
715
5.11k
      }
716
5.09k
      format++;
717
5.09k
      format_len--;
718
5.09k
    }
719
5.26k
  }
720
721
4.00k
  if (max_missing_argnum >= 0) {
722
19
    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
19
    } else {
725
19
      zend_argument_count_error("%d arguments are required, %d given", max_missing_argnum + nb_additional_parameters + 1, argc + nb_additional_parameters);
726
19
    }
727
19
    goto fail;
728
19
  }
729
730
  /* possibly, we have to make sure we have room for the terminating null? */
731
3.98k
  ZSTR_VAL(result)[outpos]=0;
732
3.98k
  ZSTR_LEN(result) = outpos;
733
3.98k
  return result;
734
735
36
fail:
736
36
  zend_string_efree(result);
737
36
  return NULL;
738
4.00k
}
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
45
{
763
45
  zend_string *result;
764
45
  char *format;
765
45
  size_t format_len;
766
45
  zval *args;
767
45
  int argc;
768
769
135
  ZEND_PARSE_PARAMETERS_START(1, -1)
770
180
    Z_PARAM_STRING(format, format_len)
771
44
    Z_PARAM_VARIADIC('*', args, argc)
772
45
  ZEND_PARSE_PARAMETERS_END();
773
774
44
  result = php_formatted_print(format, format_len, args, argc, 1);
775
44
  if (result == NULL) {
776
11
    RETURN_THROWS();
777
11
  }
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.98k
{
811
3.98k
  zend_string *result;
812
3.98k
  size_t rlen;
813
3.98k
  char *format;
814
3.98k
  size_t format_len;
815
3.98k
  zval *args;
816
3.98k
  int argc;
817
818
11.9k
  ZEND_PARSE_PARAMETERS_START(1, -1)
819
15.9k
    Z_PARAM_STRING(format, format_len)
820
3.98k
    Z_PARAM_VARIADIC('*', args, argc)
821
3.98k
  ZEND_PARSE_PARAMETERS_END();
822
823
3.98k
  result = php_formatted_print(format, format_len, args, argc, 1);
824
3.98k
  if (result == NULL) {
825
25
    RETURN_THROWS();
826
25
  }
827
3.95k
  rlen = PHPWRITE(ZSTR_VAL(result), ZSTR_LEN(result));
828
3.95k
  zend_string_efree(result);
829
3.95k
  RETURN_LONG(rlen);
830
3.95k
}
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
/* }}} */