Coverage Report

Created: 2026-10-28 06:20

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