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