Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/zend_ini.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine                                                          |
4
   +----------------------------------------------------------------------+
5
   | Copyright © Zend Technologies Ltd., a subsidiary company of          |
6
   |     Perforce Software, Inc., and Contributors.                       |
7
   +----------------------------------------------------------------------+
8
   | This source file is subject to the Modified BSD License that is      |
9
   | bundled with this package in the file LICENSE, and is available      |
10
   | through the World Wide Web at <https://www.php.net/license/>.        |
11
   |                                                                      |
12
   | SPDX-License-Identifier: BSD-3-Clause                                |
13
   +----------------------------------------------------------------------+
14
   | Author: Zeev Suraski <zeev@php.net>                                  |
15
   +----------------------------------------------------------------------+
16
*/
17
18
#include "zend.h"
19
#include "zend_sort.h"
20
#include "zend_API.h"
21
#include "zend_ini.h"
22
#include "zend_alloc.h"
23
#include "zend_operators.h"
24
#include "zend_strtod.h"
25
#include "zend_modules.h"
26
#include "zend_smart_str.h"
27
#include <ctype.h>
28
29
static HashTable *registered_zend_ini_directives;
30
31
#define NO_VALUE_PLAINTEXT    "no value"
32
#define NO_VALUE_HTML     "<i>no value</i>"
33
34
2.11k
static inline bool zend_is_whitespace(char c) {
35
2.11k
  return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f';
36
2.11k
}
37
38
/*
39
 * hash_apply functions
40
 */
41
static int zend_remove_ini_entries(zval *el, void *arg) /* {{{ */
42
0
{
43
0
  const zend_ini_entry *ini_entry = (zend_ini_entry *)Z_PTR_P(el);
44
0
  int module_number = *(int *)arg;
45
46
0
  return ini_entry->module_number == module_number;
47
0
}
48
/* }}} */
49
50
static zend_result zend_restore_ini_entry_cb(zend_ini_entry *ini_entry, int stage) /* {{{ */
51
45.1k
{
52
45.1k
  zend_result result = FAILURE;
53
54
45.1k
  if (ini_entry->modified) {
55
45.1k
    if (ini_entry->on_modify) {
56
45.1k
      zend_try {
57
      /* even if on_modify bails out, we have to continue on with restoring,
58
        since there can be allocated variables that would be freed on MM shutdown
59
        and would lead to memory corruption later ini entry is modified again */
60
45.1k
        result = ini_entry->on_modify(ini_entry, ini_entry->orig_value, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage);
61
45.1k
      } zend_end_try();
62
45.1k
    }
63
45.1k
    if (stage == ZEND_INI_STAGE_RUNTIME && result == FAILURE) {
64
      /* runtime failure is OK */
65
0
      return FAILURE;
66
0
    }
67
45.1k
    if (ini_entry->value != ini_entry->orig_value) {
68
45.0k
      zend_string_release(ini_entry->value);
69
45.0k
    }
70
45.1k
    ini_entry->value = ini_entry->orig_value;
71
45.1k
    ini_entry->modifiable = ini_entry->orig_modifiable;
72
45.1k
    ini_entry->modified = false;
73
45.1k
    ini_entry->orig_value = NULL;
74
45.1k
    ini_entry->orig_modifiable = false;
75
45.1k
  }
76
45.1k
  return SUCCESS;
77
45.1k
}
78
/* }}} */
79
80
static void free_ini_entry(zval *zv) /* {{{ */
81
0
{
82
0
  zend_ini_entry *entry = (zend_ini_entry*)Z_PTR_P(zv);
83
84
0
  zend_string_release_ex(entry->name, true);
85
0
  if (entry->value) {
86
0
    zend_string_release(entry->value);
87
0
  }
88
0
  if (entry->orig_value) {
89
0
    zend_string_release_ex(entry->orig_value, true);
90
0
  }
91
0
  free(entry);
92
0
}
93
/* }}} */
94
95
/*
96
 * Startup / shutdown
97
 */
98
ZEND_API void zend_ini_startup(void) /* {{{ */
99
16
{
100
16
  registered_zend_ini_directives = (HashTable *) malloc(sizeof(HashTable));
101
102
16
  EG(ini_directives) = registered_zend_ini_directives;
103
16
  EG(modified_ini_directives) = NULL;
104
16
  EG(error_reporting_ini_entry) = NULL;
105
16
  zend_hash_init(registered_zend_ini_directives, 128, NULL, free_ini_entry, true);
106
16
}
107
/* }}} */
108
109
ZEND_API void zend_ini_shutdown(void) /* {{{ */
110
0
{
111
0
  zend_ini_dtor(EG(ini_directives));
112
0
}
113
/* }}} */
114
115
ZEND_API void zend_ini_dtor(HashTable *ini_directives) /* {{{ */
116
0
{
117
0
  zend_hash_destroy(ini_directives);
118
0
  free(ini_directives);
119
0
}
120
/* }}} */
121
122
ZEND_API void zend_ini_global_shutdown(void) /* {{{ */
123
0
{
124
0
  zend_hash_destroy(registered_zend_ini_directives);
125
0
  free(registered_zend_ini_directives);
126
0
}
127
/* }}} */
128
129
ZEND_API void zend_ini_deactivate(void) /* {{{ */
130
306k
{
131
306k
  if (EG(modified_ini_directives)) {
132
45.0k
    zend_ini_entry *ini_entry;
133
134
180k
    ZEND_HASH_MAP_FOREACH_PTR(EG(modified_ini_directives), ini_entry) {
135
180k
      zend_restore_ini_entry_cb(ini_entry, ZEND_INI_STAGE_DEACTIVATE);
136
180k
    } ZEND_HASH_FOREACH_END();
137
45.0k
    zend_hash_destroy(EG(modified_ini_directives));
138
45.0k
    FREE_HASHTABLE(EG(modified_ini_directives));
139
45.0k
    EG(modified_ini_directives) = NULL;
140
45.0k
  }
141
306k
}
142
/* }}} */
143
144
#ifdef ZTS
145
static void copy_ini_entry(zval *zv) /* {{{ */
146
{
147
  zend_ini_entry *old_entry = (zend_ini_entry*)Z_PTR_P(zv);
148
  zend_ini_entry *new_entry = pemalloc(sizeof(zend_ini_entry), true);
149
150
  Z_PTR_P(zv) = new_entry;
151
  memcpy(new_entry, old_entry, sizeof(zend_ini_entry));
152
  if (old_entry->name) {
153
    new_entry->name = zend_string_dup(old_entry->name, true);
154
  }
155
  if (old_entry->value) {
156
    new_entry->value = zend_string_dup(old_entry->value, true);
157
  }
158
  if (old_entry->orig_value) {
159
    new_entry->orig_value = zend_string_dup(old_entry->orig_value, true);
160
  }
161
}
162
/* }}} */
163
164
ZEND_API void zend_copy_ini_directives(void) /* {{{ */
165
{
166
  EG(modified_ini_directives) = NULL;
167
  EG(error_reporting_ini_entry) = NULL;
168
  EG(ini_directives) = (HashTable *) malloc(sizeof(HashTable));
169
  zend_hash_init(EG(ini_directives), registered_zend_ini_directives->nNumOfElements, NULL, free_ini_entry, true);
170
  zend_hash_copy(EG(ini_directives), registered_zend_ini_directives, copy_ini_entry);
171
}
172
/* }}} */
173
#endif
174
175
static int ini_key_compare(Bucket *f, Bucket *s) /* {{{ */
176
0
{
177
0
  if (!f->key && !s->key) { /* both numeric */
178
0
    if (f->h > s->h) {
179
0
      return -1;
180
0
    } else if (f->h < s->h) {
181
0
      return 1;
182
0
    }
183
0
    return 0;
184
0
  } else if (!f->key) { /* f is numeric, s is not */
185
0
    return -1;
186
0
  } else if (!s->key) { /* s is numeric, f is not */
187
0
    return 1;
188
0
  } else { /* both strings */
189
0
    return zend_binary_strcasecmp(ZSTR_VAL(f->key), ZSTR_LEN(f->key), ZSTR_VAL(s->key), ZSTR_LEN(s->key));
190
0
  }
191
0
}
192
/* }}} */
193
194
ZEND_API void zend_ini_sort_entries(void) /* {{{ */
195
0
{
196
0
  zend_hash_sort(EG(ini_directives), ini_key_compare, false);
197
0
}
198
/* }}} */
199
200
/*
201
 * Registration / unregistration
202
 */
203
ZEND_API zend_result zend_register_ini_entries_ex(const zend_ini_entry_def *ini_entry, int module_number, int module_type)
204
160
{
205
160
  zend_ini_entry *p;
206
160
  zval *default_value;
207
160
  HashTable *directives = registered_zend_ini_directives;
208
209
#ifdef ZTS
210
  /* if we are called during the request, eg: from dl(),
211
   * then we should not touch the global directives table,
212
   * and should update the per-(request|thread) version instead.
213
   * This solves two problems: one is that ini entries for dl()'d
214
   * extensions will now work, and the second is that updating the
215
   * global hash here from dl() is not mutex protected and can
216
   * lead to death.
217
   */
218
  if (directives != EG(ini_directives)) {
219
    directives = EG(ini_directives);
220
  } else {
221
    ZEND_ASSERT(module_type == MODULE_PERSISTENT);
222
  }
223
#endif
224
225
3.04k
  while (ini_entry->name) {
226
2.88k
    p = pemalloc(sizeof(zend_ini_entry), true);
227
2.88k
    p->def = ini_entry;
228
2.88k
    p->name = zend_string_init_interned(ini_entry->name, ini_entry->name_length, true);
229
2.88k
    p->on_modify = ini_entry->on_modify;
230
2.88k
    p->mh_arg1 = ini_entry->mh_arg1;
231
2.88k
    p->mh_arg2 = ini_entry->mh_arg2;
232
2.88k
    p->mh_arg3 = ini_entry->mh_arg3;
233
2.88k
    p->value = NULL;
234
2.88k
    p->orig_value = NULL;
235
2.88k
    p->displayer = ini_entry->displayer;
236
2.88k
    p->modifiable = ini_entry->modifiable;
237
238
2.88k
    p->orig_modifiable = false;
239
2.88k
    p->modified = false;
240
2.88k
    p->module_number = module_number;
241
242
2.88k
    if (zend_hash_add_ptr(directives, p->name, (void*)p) == NULL) {
243
0
      pefree(p, true);
244
0
      zend_unregister_ini_entries_ex(module_number, module_type);
245
0
      return FAILURE;
246
0
    }
247
248
2.88k
    zend_string *prev_value = p->value;
249
250
2.88k
    if (((default_value = zend_get_configuration_directive(p->name)) != NULL) &&
251
180
        (!p->on_modify || p->on_modify(p, Z_STR_P(default_value), p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP) == SUCCESS)) {
252
253
      /* Skip assigning the value if the handler has already done so. */
254
180
      if (p->value == prev_value) {
255
180
        p->value = zend_new_interned_string(zend_string_copy(Z_STR_P(default_value)));
256
180
      }
257
2.70k
    } else {
258
2.70k
      p->value = ini_entry->value ?
259
2.70k
        zend_string_init_interned(ini_entry->value, ini_entry->value_length, true) : NULL;
260
261
2.70k
      if (p->on_modify) {
262
2.46k
        p->on_modify(p, p->value, p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP);
263
2.46k
      }
264
2.70k
    }
265
2.88k
    ini_entry++;
266
2.88k
  }
267
160
  return SUCCESS;
268
160
}
269
270
ZEND_API zend_result zend_register_ini_entries(const zend_ini_entry_def *ini_entry, int module_number) /* {{{ */
271
0
{
272
0
  zend_module_entry *module;
273
274
  /* Module is likely to be the last one in the list */
275
0
  ZEND_HASH_REVERSE_FOREACH_PTR(&module_registry, module) {
276
0
    if (module->module_number == module_number) {
277
0
      return zend_register_ini_entries_ex(ini_entry, module_number, module->type);
278
0
    }
279
0
  } ZEND_HASH_FOREACH_END();
280
281
0
  return FAILURE;
282
0
}
283
/* }}} */
284
285
ZEND_API void zend_unregister_ini_entries_ex(int module_number, int module_type)
286
0
{
287
0
  HashTable *ini_directives;
288
289
0
  if (module_type == MODULE_TEMPORARY) {
290
0
    ini_directives = EG(ini_directives);
291
0
  } else {
292
0
    ini_directives = registered_zend_ini_directives;
293
0
  }
294
295
0
  zend_hash_apply_with_argument(ini_directives, zend_remove_ini_entries, (void *) &module_number);
296
0
}
297
298
ZEND_API void zend_unregister_ini_entries(int module_number) /* {{{ */
299
0
{
300
0
  zend_module_entry *module;
301
302
  /* Module is likely to be the last one in the list */
303
0
  ZEND_HASH_REVERSE_FOREACH_PTR(&module_registry, module) {
304
0
    if (module->module_number == module_number) {
305
0
      zend_unregister_ini_entries_ex(module_number, module->type);
306
0
      return;
307
0
    }
308
0
  } ZEND_HASH_FOREACH_END();
309
0
}
310
/* }}} */
311
312
#ifdef ZTS
313
ZEND_API void zend_ini_refresh_caches(int stage) /* {{{ */
314
{
315
  zend_ini_entry *p;
316
317
  ZEND_HASH_MAP_FOREACH_PTR(EG(ini_directives), p) {
318
    if (p->on_modify) {
319
      p->on_modify(p, p->value, p->mh_arg1, p->mh_arg2, p->mh_arg3, stage);
320
    }
321
  } ZEND_HASH_FOREACH_END();
322
}
323
/* }}} */
324
#endif
325
326
ZEND_API zend_result zend_alter_ini_entry(zend_string *name, zend_string *new_value, int modify_type, int stage) /* {{{ */
327
0
{
328
329
0
  return zend_alter_ini_entry_ex(name, new_value, modify_type, stage, false);
330
0
}
331
/* }}} */
332
333
ZEND_API zend_result zend_alter_ini_entry_chars(zend_string *name, const char *value, size_t value_length, int modify_type, int stage) /* {{{ */
334
44.9k
{
335
44.9k
  zend_result ret;
336
44.9k
  zend_string *new_value;
337
338
44.9k
  new_value = zend_string_init(value, value_length, !(stage & ZEND_INI_STAGE_IN_REQUEST));
339
44.9k
  ret = zend_alter_ini_entry_ex(name, new_value, modify_type, stage, false);
340
44.9k
  zend_string_release(new_value);
341
44.9k
  return ret;
342
44.9k
}
343
/* }}} */
344
345
ZEND_API zend_result zend_alter_ini_entry_chars_ex(zend_string *name, const char *value, size_t value_length, int modify_type, int stage, bool force_change) /* {{{ */
346
0
{
347
0
  zend_result ret;
348
0
  zend_string *new_value;
349
350
0
  new_value = zend_string_init(value, value_length, !(stage & ZEND_INI_STAGE_IN_REQUEST));
351
0
  ret = zend_alter_ini_entry_ex(name, new_value, modify_type, stage, force_change);
352
0
  zend_string_release(new_value);
353
0
  return ret;
354
0
}
355
/* }}} */
356
357
ZEND_API zend_result zend_alter_ini_entry_ex(zend_string *name, zend_string *new_value, int modify_type, int stage, bool force_change) /* {{{ */
358
45.1k
{
359
45.1k
  zend_ini_entry *ini_entry;
360
45.1k
  zend_string *duplicate;
361
45.1k
  uint8_t modifiable;
362
45.1k
  bool modified;
363
364
45.1k
  if ((ini_entry = zend_hash_find_ptr(EG(ini_directives), name)) == NULL) {
365
36
    return FAILURE;
366
36
  }
367
368
45.1k
  modifiable = ini_entry->modifiable;
369
45.1k
  modified = ini_entry->modified;
370
371
45.1k
  if (stage == ZEND_INI_STAGE_ACTIVATE && modify_type == ZEND_INI_SYSTEM) {
372
0
    ini_entry->modifiable = ZEND_INI_SYSTEM;
373
0
  }
374
375
45.1k
  if (!force_change) {
376
45.1k
    if (!(ini_entry->modifiable & modify_type)) {
377
0
      return FAILURE;
378
0
    }
379
45.1k
  }
380
381
45.1k
  if (!EG(modified_ini_directives)) {
382
45.0k
    ALLOC_HASHTABLE(EG(modified_ini_directives));
383
45.0k
    zend_hash_init(EG(modified_ini_directives), 8, NULL, NULL, false);
384
45.0k
  }
385
45.1k
  if (!modified) {
386
45.1k
    ini_entry->orig_value = ini_entry->value;
387
45.1k
    ini_entry->orig_modifiable = modifiable;
388
45.1k
    ini_entry->modified = true;
389
45.1k
    zend_hash_add_ptr(EG(modified_ini_directives), ini_entry->name, ini_entry);
390
45.1k
  }
391
392
45.1k
  zend_string *prev_value = ini_entry->value;
393
45.1k
  duplicate = zend_string_copy(new_value);
394
395
45.1k
  if (!ini_entry->on_modify
396
45.1k
    || ini_entry->on_modify(ini_entry, duplicate, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage) == SUCCESS) {
397
45.0k
    if (modified && ini_entry->orig_value != prev_value) { /* we already changed the value, free the changed value */
398
11
      zend_string_release(prev_value);
399
11
    }
400
    /* Skip assigning the value if the handler has already done so. */
401
45.0k
    if (ini_entry->value == prev_value) {
402
45.0k
      ini_entry->value = duplicate;
403
45.0k
    } else {
404
0
      zend_string_release(duplicate);
405
0
    }
406
45.0k
  } else {
407
44
    zend_string_release(duplicate);
408
44
    return FAILURE;
409
44
  }
410
411
45.0k
  return SUCCESS;
412
45.1k
}
413
/* }}} */
414
415
ZEND_API zend_result zend_restore_ini_entry(zend_string *name, int stage)
416
0
{
417
0
  zend_ini_entry *ini_entry;
418
419
0
  if ((ini_entry = zend_hash_find_ptr(EG(ini_directives), name)) == NULL ||
420
0
    (stage == ZEND_INI_STAGE_RUNTIME && (ini_entry->modifiable & ZEND_INI_USER) == 0)) {
421
0
    return FAILURE;
422
0
  }
423
424
0
  if (EG(modified_ini_directives)) {
425
0
    if (zend_restore_ini_entry_cb(ini_entry, stage) == SUCCESS) {
426
0
      zend_hash_del(EG(modified_ini_directives), ini_entry->name);
427
0
    } else {
428
0
      return FAILURE;
429
0
    }
430
0
  }
431
432
0
  return SUCCESS;
433
0
}
434
435
ZEND_API zend_result zend_ini_register_displayer(const char *name, uint32_t name_length, void (*displayer)(zend_ini_entry *ini_entry, int type)) /* {{{ */
436
0
{
437
0
  zend_ini_entry *ini_entry;
438
439
0
  ini_entry = zend_hash_str_find_ptr(registered_zend_ini_directives, name, name_length);
440
0
  if (ini_entry == NULL) {
441
0
    return FAILURE;
442
0
  }
443
444
0
  ini_entry->displayer = displayer;
445
0
  return SUCCESS;
446
0
}
447
/* }}} */
448
449
/*
450
 * Data retrieval
451
 */
452
453
ZEND_API zend_long zend_ini_long(const char *name, size_t name_length, bool orig) /* {{{ */
454
10
{
455
10
  zend_ini_entry *ini_entry;
456
457
10
  ini_entry = zend_hash_str_find_ptr(EG(ini_directives), name, name_length);
458
10
  if (ini_entry) {
459
10
    if (orig && ini_entry->modified) {
460
0
      return (ini_entry->orig_value ? ZEND_STRTOL(ZSTR_VAL(ini_entry->orig_value), NULL, 0) : 0);
461
10
    } else {
462
10
      return (ini_entry->value      ? ZEND_STRTOL(ZSTR_VAL(ini_entry->value), NULL, 0)      : 0);
463
10
    }
464
10
  }
465
466
0
  return 0;
467
10
}
468
/* }}} */
469
470
ZEND_API double zend_ini_double(const char *name, size_t name_length, bool orig) /* {{{ */
471
0
{
472
0
  zend_ini_entry *ini_entry;
473
474
0
  ini_entry = zend_hash_str_find_ptr(EG(ini_directives), name, name_length);
475
0
  if (ini_entry) {
476
0
    if (orig && ini_entry->modified) {
477
0
      return (double) (ini_entry->orig_value ? zend_strtod(ZSTR_VAL(ini_entry->orig_value), NULL) : 0.0);
478
0
    } else {
479
0
      return (double) (ini_entry->value      ? zend_strtod(ZSTR_VAL(ini_entry->value), NULL)      : 0.0);
480
0
    }
481
0
  }
482
483
0
  return 0.0;
484
0
}
485
/* }}} */
486
487
ZEND_API const char *zend_ini_string_ex(const char *name, size_t name_length, bool orig, bool *exists) /* {{{ */
488
0
{
489
0
  zend_string *str = zend_ini_str_ex(name, name_length, orig, exists);
490
491
0
  return str ? ZSTR_VAL(str) : NULL;
492
0
}
493
/* }}} */
494
495
ZEND_API const char *zend_ini_string(const char *name, size_t name_length, bool orig) /* {{{ */
496
11.5k
{
497
11.5k
  zend_string *str = zend_ini_str(name, name_length, orig);
498
499
11.5k
  return str ? ZSTR_VAL(str) : NULL;
500
11.5k
}
501
/* }}} */
502
503
504
ZEND_API zend_string *zend_ini_str_ex(const char *name, size_t name_length, bool orig, bool *exists) /* {{{ */
505
11.5k
{
506
11.5k
  zend_ini_entry *ini_entry;
507
508
11.5k
  ini_entry = zend_hash_str_find_ptr(EG(ini_directives), name, name_length);
509
11.5k
  if (ini_entry) {
510
11.5k
    if (exists) {
511
11.5k
      *exists = true;
512
11.5k
    }
513
514
11.5k
    if (orig && ini_entry->modified) {
515
0
      return ini_entry->orig_value ? ini_entry->orig_value : NULL;
516
11.5k
    } else {
517
11.5k
      return ini_entry->value ? ini_entry->value : NULL;
518
11.5k
    }
519
11.5k
  } else {
520
0
    if (exists) {
521
0
      *exists = false;
522
0
    }
523
0
    return NULL;
524
0
  }
525
11.5k
}
526
/* }}} */
527
528
ZEND_API zend_string *zend_ini_str(const char *name, size_t name_length, bool orig) /* {{{ */
529
11.5k
{
530
11.5k
  bool exists = true;
531
11.5k
  zend_string *return_value;
532
533
11.5k
  return_value = zend_ini_str_ex(name, name_length, orig, &exists);
534
11.5k
  if (!exists) {
535
0
    return NULL;
536
11.5k
  } else if (!return_value) {
537
1.23k
    return_value = ZSTR_EMPTY_ALLOC();
538
1.23k
  }
539
11.5k
  return return_value;
540
11.5k
}
541
/* }}} */
542
543
ZEND_API zend_string *zend_ini_get_value(zend_string *name) /* {{{ */
544
151
{
545
151
  zend_ini_entry *ini_entry;
546
547
151
  ini_entry = zend_hash_find_ptr(EG(ini_directives), name);
548
151
  if (ini_entry) {
549
115
    return ini_entry->value ? ini_entry->value : ZSTR_EMPTY_ALLOC();
550
115
  } else {
551
36
    return NULL;
552
36
  }
553
151
}
554
/* }}} */
555
556
ZEND_API bool zend_ini_parse_bool(const zend_string *str)
557
833
{
558
  /* May happen if an unknown INI setting is queried via zend_ini_bool_literal(),
559
   * as zend_ini_str() would return NULL */
560
833
  if (UNEXPECTED(str == NULL)) {
561
0
    return false;
562
0
  }
563
833
  if (zend_string_equals_literal_ci(str, "true")
564
833
      || zend_string_equals_literal_ci(str, "yes")
565
833
      || zend_string_equals_literal_ci(str, "on")
566
833
  ) {
567
0
    return true;
568
833
  } else {
569
833
    return atoi(ZSTR_VAL(str)) != 0;
570
833
  }
571
833
}
572
573
typedef enum {
574
  ZEND_INI_PARSE_QUANTITY_SIGNED,
575
  ZEND_INI_PARSE_QUANTITY_UNSIGNED,
576
} zend_ini_parse_quantity_signed_result_t;
577
578
16
static const char *zend_ini_consume_quantity_prefix(const char *const digits, const char *const str_end, int base) {
579
16
  const char *digits_consumed = digits;
580
  /* Ignore leading whitespace. */
581
16
  while (digits_consumed < str_end && zend_is_whitespace(*digits_consumed)) {++digits_consumed;}
582
16
  if (digits_consumed[0] == '+' || digits_consumed[0] == '-') {
583
0
    ++digits_consumed;
584
0
  }
585
586
16
  if (digits_consumed[0] == '0' && !isdigit((unsigned char)digits_consumed[1])) {
587
    /* Value is just 0 */
588
0
    if ((digits_consumed+1) == str_end) {
589
0
      return digits_consumed;
590
0
    }
591
592
0
    switch (digits_consumed[1]) {
593
0
      case 'x':
594
0
      case 'X':
595
0
      case 'o':
596
0
      case 'O':
597
0
        digits_consumed += 2;
598
0
        break;
599
0
      case 'b':
600
0
      case 'B':
601
0
        if (base != 16) {
602
          /* 0b or 0B is valid in base 16, but not in the other supported bases. */
603
0
          digits_consumed += 2;
604
0
        }
605
0
        break;
606
0
    }
607
0
  }
608
16
  return digits_consumed;
609
16
}
610
611
static zend_ulong zend_ini_parse_quantity_internal(const zend_string *value, zend_ini_parse_quantity_signed_result_t signed_result, zend_string **errstr) /* {{{ */
612
923
{
613
923
  char *digits_end = NULL;
614
923
  const char *str = ZSTR_VAL(value);
615
923
  const char *str_end = &str[ZSTR_LEN(value)];
616
923
  const char *digits = str;
617
923
  bool overflow = false;
618
923
  zend_ulong factor;
619
923
  smart_str invalid = {0};
620
923
  smart_str interpreted = {0};
621
923
  smart_str chr = {0};
622
623
  /* Ignore leading whitespace. ZEND_STRTOL() also skips leading whitespaces,
624
   * but we need the position of the first non-whitespace later. */
625
923
  while (digits < str_end && zend_is_whitespace(*digits)) {++digits;}
626
627
  /* Ignore trailing whitespace */
628
928
  while (digits < str_end && zend_is_whitespace(*(str_end-1))) {--str_end;}
629
630
923
  if (digits == str_end) {
631
0
    *errstr = NULL;
632
0
    return 0;
633
0
  }
634
635
923
  bool is_negative = false;
636
923
  if (digits[0] == '+') {
637
0
    ++digits;
638
923
  } else if (digits[0] == '-') {
639
93
    is_negative = true;
640
93
    ++digits;
641
93
  }
642
643
  /* if there is no digit after +/- */
644
923
  if (!isdigit((unsigned char)digits[0])) {
645
    /* Escape the string to avoid null bytes and to make non-printable chars
646
     * visible */
647
1
    smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value));
648
1
    smart_str_0(&invalid);
649
650
1
    *errstr = zend_strpprintf(0, "Invalid quantity \"%s\": no valid leading digits, interpreting as \"0\" for backwards compatibility",
651
1
            ZSTR_VAL(invalid.s));
652
653
1
    smart_str_free(&invalid);
654
1
    return 0;
655
1
  }
656
657
922
  int base = 0;
658
922
  if (digits[0] == '0' && !isdigit((unsigned char)digits[1])) {
659
    /* Value is just 0 */
660
151
    if ((digits+1) == str_end) {
661
135
      *errstr = NULL;
662
135
      return 0;
663
135
    }
664
665
16
    switch (digits[1]) {
666
      /* Multiplier suffixes */
667
0
      case 'g':
668
0
      case 'G':
669
0
      case 'm':
670
0
      case 'M':
671
0
      case 'k':
672
0
      case 'K':
673
0
        goto evaluation;
674
16
      case 'x':
675
16
      case 'X':
676
16
        base = 16;
677
16
        break;
678
0
      case 'o':
679
0
      case 'O':
680
0
        base = 8;
681
0
        break;
682
0
      case 'b':
683
0
      case 'B':
684
0
        base = 2;
685
0
        break;
686
0
      default:
687
0
        *errstr = zend_strpprintf(0, "Invalid prefix \"0%c\", interpreting as \"0\" for backwards compatibility",
688
0
          digits[1]);
689
0
        return 0;
690
16
        }
691
16
        digits += 2;
692
    /* STRTOULL may silently ignore a prefix of whitespace, sign, and base prefix, which would be invalid at this position */
693
16
    if (UNEXPECTED(digits == str_end || digits != zend_ini_consume_quantity_prefix(digits, str_end, base))) {
694
      /* Escape the string to avoid null bytes and to make non-printable chars
695
       * visible */
696
0
      smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value));
697
0
      smart_str_0(&invalid);
698
699
0
      *errstr = zend_strpprintf(0, "Invalid quantity \"%s\": no digits after base prefix, interpreting as \"0\" for backwards compatibility",
700
0
              ZSTR_VAL(invalid.s));
701
702
0
      smart_str_free(&invalid);
703
0
      return 0;
704
0
    }
705
16
  }
706
787
  evaluation:
707
708
787
  errno = 0;
709
787
  zend_ulong retval = ZEND_STRTOUL(digits, &digits_end, base);
710
711
787
  if (errno == ERANGE) {
712
0
    overflow = true;
713
787
  } else if (signed_result == ZEND_INI_PARSE_QUANTITY_UNSIGNED) {
714
213
    if (is_negative) {
715
      /* Ignore "-1" as it is commonly used as max value, for instance in memory_limit=-1. */
716
74
      if (retval == 1 && digits_end == str_end) {
717
19
        retval = -1;
718
55
      } else {
719
55
        overflow = true;
720
55
      }
721
74
    }
722
574
  } else if (signed_result == ZEND_INI_PARSE_QUANTITY_SIGNED) {
723
    /* Handle PHP_INT_MIN case */
724
574
    if (is_negative && retval == ((zend_ulong)ZEND_LONG_MAX +1)) {
725
0
      retval = 0u - retval;
726
574
    } else if ((zend_long) retval < 0) {
727
0
      overflow = true;
728
574
    } else if (is_negative) {
729
18
      retval = 0u - retval;
730
18
    }
731
574
  }
732
733
787
  if (UNEXPECTED(digits_end == digits)) {
734
    /* No leading digits */
735
736
    /* Escape the string to avoid null bytes and to make non-printable chars
737
     * visible */
738
0
    smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value));
739
0
    smart_str_0(&invalid);
740
741
0
    *errstr = zend_strpprintf(0, "Invalid quantity \"%s\": no valid leading digits, interpreting as \"0\" for backwards compatibility",
742
0
            ZSTR_VAL(invalid.s));
743
744
0
    smart_str_free(&invalid);
745
0
    return 0;
746
0
  }
747
748
  /* Allow for whitespace between integer portion and any suffix character */
749
851
  while (digits_end < str_end && zend_is_whitespace(*digits_end)) ++digits_end;
750
751
  /* No exponent suffix. */
752
787
  if (digits_end == str_end) {
753
607
    goto end;
754
607
  }
755
756
180
  switch (*(str_end-1)) {
757
0
    case 'g':
758
0
    case 'G':
759
0
      factor = 1<<30;
760
0
      break;
761
0
    case 'm':
762
161
    case 'M':
763
161
      factor = 1<<20;
764
161
      break;
765
0
    case 'k':
766
16
    case 'K':
767
16
      factor = 1<<10;
768
16
      break;
769
3
    default:
770
      /* Unknown suffix */
771
3
      smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value));
772
3
      smart_str_0(&invalid);
773
3
      smart_str_append_escaped(&interpreted, str, digits_end - str);
774
3
      smart_str_0(&interpreted);
775
3
      smart_str_append_escaped(&chr, str_end-1, 1);
776
3
      smart_str_0(&chr);
777
778
3
      *errstr = zend_strpprintf(0, "Invalid quantity \"%s\": unknown multiplier \"%s\", interpreting as \"%s\" for backwards compatibility",
779
3
            ZSTR_VAL(invalid.s), ZSTR_VAL(chr.s), ZSTR_VAL(interpreted.s));
780
781
3
      smart_str_free(&invalid);
782
3
      smart_str_free(&interpreted);
783
3
      smart_str_free(&chr);
784
785
3
      return retval;
786
180
  }
787
788
177
  if (!overflow) {
789
177
    if (signed_result == ZEND_INI_PARSE_QUANTITY_SIGNED) {
790
64
      zend_long sretval = (zend_long)retval;
791
64
      if (sretval > 0) {
792
64
        overflow = (zend_long)retval > ZEND_LONG_MAX / (zend_long)factor;
793
64
      } else {
794
0
        overflow = (zend_long)retval < ZEND_LONG_MIN / (zend_long)factor;
795
0
      }
796
113
    } else {
797
113
      overflow = retval > ZEND_ULONG_MAX / factor;
798
113
    }
799
177
  }
800
801
177
  retval *= factor;
802
803
177
  if (UNEXPECTED(digits_end != str_end-1)) {
804
    /* More than one character in suffix */
805
3
    smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value));
806
3
    smart_str_0(&invalid);
807
3
    smart_str_append_escaped(&interpreted, str, digits_end - str);
808
3
    smart_str_0(&interpreted);
809
3
    smart_str_append_escaped(&chr, str_end-1, 1);
810
3
    smart_str_0(&chr);
811
812
3
    *errstr = zend_strpprintf(0, "Invalid quantity \"%s\", interpreting as \"%s%s\" for backwards compatibility",
813
3
            ZSTR_VAL(invalid.s), ZSTR_VAL(interpreted.s), ZSTR_VAL(chr.s));
814
815
3
    smart_str_free(&invalid);
816
3
    smart_str_free(&interpreted);
817
3
    smart_str_free(&chr);
818
819
3
    return retval;
820
3
  }
821
822
781
end:
823
781
  if (UNEXPECTED(overflow)) {
824
54
    smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value));
825
54
    smart_str_0(&invalid);
826
827
    /* Not specifying the resulting value here because the caller may make
828
     * additional conversions. Not specifying the allowed range
829
     * because the caller may do narrower range checks. */
830
54
    *errstr = zend_strpprintf(0, "Invalid quantity \"%s\": value is out of range, using overflow result for backwards compatibility",
831
54
            ZSTR_VAL(invalid.s));
832
833
54
    smart_str_free(&invalid);
834
54
    smart_str_free(&interpreted);
835
54
    smart_str_free(&chr);
836
837
54
    return retval;
838
54
  }
839
840
727
  *errstr = NULL;
841
727
  return retval;
842
781
}
843
/* }}} */
844
845
ZEND_API zend_long zend_ini_parse_quantity(const zend_string *value, zend_string **errstr) /* {{{ */
846
691
{
847
691
  return (zend_long) zend_ini_parse_quantity_internal(value, ZEND_INI_PARSE_QUANTITY_SIGNED, errstr);
848
691
}
849
/* }}} */
850
851
ZEND_API zend_ulong zend_ini_parse_uquantity(const zend_string *value, zend_string **errstr) /* {{{ */
852
232
{
853
232
  return zend_ini_parse_quantity_internal(value, ZEND_INI_PARSE_QUANTITY_UNSIGNED, errstr);
854
232
}
855
/* }}} */
856
857
ZEND_API zend_long zend_ini_parse_quantity_warn(const zend_string *value, zend_string *setting) /* {{{ */
858
690
{
859
690
  zend_string *errstr;
860
690
  zend_long retval = zend_ini_parse_quantity(value, &errstr);
861
862
690
  if (errstr) {
863
1
    zend_error(E_WARNING, "Invalid \"%s\" setting. %s", ZSTR_VAL(setting), ZSTR_VAL(errstr));
864
1
    zend_string_release(errstr);
865
1
  }
866
867
690
  return retval;
868
690
}
869
/* }}} */
870
871
ZEND_API zend_ulong zend_ini_parse_uquantity_warn(const zend_string *value, zend_string *setting) /* {{{ */
872
232
{
873
232
  zend_string *errstr;
874
232
  zend_ulong retval = zend_ini_parse_uquantity(value, &errstr);
875
876
232
  if (errstr) {
877
60
    zend_error(E_WARNING, "Invalid \"%s\" setting. %s", ZSTR_VAL(setting), ZSTR_VAL(errstr));
878
60
    zend_string_release(errstr);
879
60
  }
880
881
232
  return retval;
882
232
}
883
/* }}} */
884
885
ZEND_INI_DISP(zend_ini_boolean_displayer_cb) /* {{{ */
886
0
{
887
0
  bool value;
888
889
0
  if (type == ZEND_INI_DISPLAY_ORIG && ini_entry->modified) {
890
0
    value = zend_ini_parse_bool(ini_entry->orig_value);
891
0
  } else if (ini_entry->value) {
892
0
    value = zend_ini_parse_bool(ini_entry->value);
893
0
  } else {
894
0
    value = false;
895
0
  }
896
897
0
  if (value) {
898
0
    ZEND_PUTS("On");
899
0
  } else {
900
0
    ZEND_PUTS("Off");
901
0
  }
902
0
}
903
/* }}} */
904
905
ZEND_INI_DISP(zend_ini_color_displayer_cb) /* {{{ */
906
0
{
907
0
  const char *value;
908
909
0
  if (type == ZEND_INI_DISPLAY_ORIG && ini_entry->modified) {
910
0
    value = ZSTR_VAL(ini_entry->orig_value);
911
0
  } else if (ini_entry->value) {
912
0
    value = ZSTR_VAL(ini_entry->value);
913
0
  } else {
914
0
    value = NULL;
915
0
  }
916
0
  if (value) {
917
0
    if (zend_uv.html_errors) {
918
0
      zend_printf("<span style=\"color: %s\">%s</span>", value, value);
919
0
    } else {
920
0
      ZEND_PUTS(value);
921
0
    }
922
0
  } else {
923
0
    if (zend_uv.html_errors) {
924
0
      ZEND_PUTS(NO_VALUE_HTML);
925
0
    } else {
926
0
      ZEND_PUTS(NO_VALUE_PLAINTEXT);
927
0
    }
928
0
  }
929
0
}
930
/* }}} */
931
932
ZEND_INI_DISP(display_link_numbers) /* {{{ */
933
0
{
934
0
  const char *value;
935
936
0
  if (type == ZEND_INI_DISPLAY_ORIG && ini_entry->modified) {
937
0
    value = ZSTR_VAL(ini_entry->orig_value);
938
0
  } else if (ini_entry->value) {
939
0
    value = ZSTR_VAL(ini_entry->value);
940
0
  } else {
941
0
    value = NULL;
942
0
  }
943
944
0
  if (value) {
945
0
    if (atoi(value) == -1) {
946
0
      ZEND_PUTS("Unlimited");
947
0
    } else {
948
0
      zend_printf("%s", value);
949
0
    }
950
0
  }
951
0
}
952
/* }}} */
953
954
/* Standard message handlers */
955
ZEND_API ZEND_INI_MH(OnUpdateBool) /* {{{ */
956
690
{
957
690
  bool *p = ZEND_INI_GET_ADDR();
958
690
  *p = zend_ini_parse_bool(new_value);
959
690
  return SUCCESS;
960
690
}
961
/* }}} */
962
963
ZEND_API ZEND_INI_MH(OnUpdateLong) /* {{{ */
964
432
{
965
432
  zend_long *p = ZEND_INI_GET_ADDR();
966
432
  *p = zend_ini_parse_quantity_warn(new_value, entry->name);
967
432
  return SUCCESS;
968
432
}
969
/* }}} */
970
971
ZEND_API ZEND_INI_MH(OnUpdateLongGEZero) /* {{{ */
972
32
{
973
32
  zend_long tmp = zend_ini_parse_quantity_warn(new_value, entry->name);
974
32
  if (tmp < 0) {
975
0
    return FAILURE;
976
0
  }
977
978
32
  zend_long *p = ZEND_INI_GET_ADDR();
979
32
  *p = tmp;
980
981
32
  return SUCCESS;
982
32
}
983
/* }}} */
984
985
ZEND_API ZEND_INI_MH(OnUpdateReal) /* {{{ */
986
16
{
987
16
  double *p = ZEND_INI_GET_ADDR();
988
16
  *p = zend_strtod(ZSTR_VAL(new_value), NULL);
989
16
  return SUCCESS;
990
16
}
991
/* }}} */
992
993
ZEND_API ZEND_INI_MH(OnUpdateString) /* {{{ */
994
90.4k
{
995
90.4k
  char **p = ZEND_INI_GET_ADDR();
996
90.4k
  *p = new_value ? ZSTR_VAL(new_value) : NULL;
997
90.4k
  return SUCCESS;
998
90.4k
}
999
/* }}} */
1000
1001
ZEND_API ZEND_INI_MH(OnUpdateStringUnempty) /* {{{ */
1002
120
{
1003
120
  if (new_value && !ZSTR_VAL(new_value)[0]) {
1004
48
    return FAILURE;
1005
48
  }
1006
1007
72
  char **p = ZEND_INI_GET_ADDR();
1008
72
  *p = new_value ? ZSTR_VAL(new_value) : NULL;
1009
72
  return SUCCESS;
1010
120
}
1011
/* }}} */
1012
1013
ZEND_API ZEND_INI_MH(OnUpdateStr) /* {{{ */
1014
64
{
1015
64
  zend_string **p = ZEND_INI_GET_ADDR();
1016
64
  *p = new_value;
1017
64
  return SUCCESS;
1018
64
}
1019
/* }}} */
1020
1021
ZEND_API ZEND_INI_MH(OnUpdateStrNotEmpty) /* {{{ */
1022
80
{
1023
80
  if (new_value && ZSTR_LEN(new_value) == 0) {
1024
0
    return FAILURE;
1025
0
  }
1026
1027
80
  zend_string **p = ZEND_INI_GET_ADDR();
1028
80
  *p = new_value;
1029
80
  return SUCCESS;
1030
80
}
1031
/* }}} */