Skip to content

Commit 39d22de

Browse files
arnaud-lbsgolemon
andcommitted
Add zend_ini_parse_quantity() and deprecate zend_atol(), zend_atoi()
zend_atol() and zend_atoi() don't just do number parsing. They also check for a 'K', 'M', or 'G' at the end of the string, and multiply the parsed value out accordingly. Unfortunately, they ignore any other non-numerics between the numeric component and the last character in the string. This means that numbers such as the following are both valid and non-intuitive in their final output. * "123KMG" is interpreted as "123G" -> 132070244352 * "123G " is interpreted as "123 " -> 123 * "123GB" is interpreted as "123B" -> 123 * "123 I like tacos." is also interpreted as "123." -> 123 Currently, in php-src these functions are used only for parsing ini values. In this change we deprecate zend_atol(), zend_atoi(), and introduce a new function with the same behaviour, but with the ability to report invalid inputs to the caller. The function's name also makes the behaviour less unexpected: zend_ini_parse_quantity(). Co-authored-by: Sara Golemon <pollita@php.net>
1 parent 313371a commit 39d22de

File tree

8 files changed

+508
-3
lines changed

8 files changed

+508
-3
lines changed

Zend/zend_ini.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ static HashTable *registered_zend_ini_directives;
2929
#define NO_VALUE_PLAINTEXT "no value"
3030
#define NO_VALUE_HTML "<i>no value</i>"
3131

32+
#define ZEND_IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r') || ((c) == '\v') || ((c) == '\f'))
33+
3234
/*
3335
* hash_apply functions
3436
*/
@@ -495,6 +497,67 @@ ZEND_API bool zend_ini_parse_bool(zend_string *str)
495497
}
496498
}
497499

500+
ZEND_API zend_long zend_ini_parse_quantity(zend_string *value, zend_string **errstr) /* {{{ */
501+
{
502+
char *digits_end = NULL;
503+
char *str = ZSTR_VAL(value);
504+
size_t str_len = ZSTR_LEN(value);
505+
506+
/* Ignore trailing whitespace */
507+
while (str_len && ZEND_IS_WHITESPACE(str[str_len-1])) --str_len;
508+
if (!str_len) return 0;
509+
510+
/* Perform following multiplications on unsigned to avoid overflow UB.
511+
* For now overflow is silently ignored -- not clear what else can be
512+
* done here, especially as the final result of this function may be
513+
* used in an unsigned context (e.g. "memory_limit=3G", which overflows
514+
* zend_long on 32-bit, but not size_t). */
515+
zend_ulong retval = (zend_ulong) ZEND_STRTOL(str, &digits_end, 0);
516+
517+
if (digits_end == str) {
518+
*errstr = zend_strpprintf(0, "Invalid numeric string '%.*s': no valid leading digits, interpreting as '0' for backwards compatibility",
519+
(int)str_len, str);
520+
return 0;
521+
}
522+
523+
/* Allow for whitespace between integer portion and any suffix character */
524+
while (ZEND_IS_WHITESPACE(*digits_end)) ++digits_end;
525+
526+
/* No exponent suffix. */
527+
if (!*digits_end) return retval;
528+
529+
if (str_len>0) {
530+
switch (str[str_len-1]) {
531+
case 'g':
532+
case 'G':
533+
retval *= 1024;
534+
ZEND_FALLTHROUGH;
535+
case 'm':
536+
case 'M':
537+
retval *= 1024;
538+
ZEND_FALLTHROUGH;
539+
case 'k':
540+
case 'K':
541+
retval *= 1024;
542+
break;
543+
default:
544+
/* Unknown suffix */
545+
*errstr = zend_strpprintf(0, "Invalid numeric string '%.*s': unknown multipler '%c', interpreting as '%.*s' for backwards compatibility",
546+
(int)str_len, str, str[str_len-1], (int)(digits_end - str), str);
547+
return retval;
548+
}
549+
}
550+
551+
if (digits_end < &str[str_len-1]) {
552+
/* More than one character in suffix */
553+
*errstr = zend_strpprintf(0, "Invalid numeric string '%.*s', interpreting as '%.*s%c' for backwards compatibility",
554+
(int)str_len, str, (int)(digits_end - str), str, str[str_len-1]);
555+
}
556+
557+
return (zend_long) retval;
558+
}
559+
/* }}} */
560+
498561
ZEND_INI_DISP(zend_ini_boolean_displayer_cb) /* {{{ */
499562
{
500563
int value;

Zend/zend_ini.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,28 @@ ZEND_API char *zend_ini_string_ex(const char *name, size_t name_length, int orig
8888
ZEND_API zend_string *zend_ini_get_value(zend_string *name);
8989
ZEND_API bool zend_ini_parse_bool(zend_string *str);
9090

91+
/**
92+
* Parses a quantity
93+
*
94+
* value must be a string of digits optionally followed by a multiplier
95+
* character K, M, or G (for 2**10, 2**20, and 2**30, respectively).
96+
*
97+
* The digits are parsed as decimal unless the first character is '0', in which
98+
* case they are parsed as octal.
99+
*
100+
* Whitespaces before and after the digits portion are ignored.
101+
*
102+
* For backwards compatibility, invalid values are handled as follows:
103+
* - No leading digits: value is treated as '0'
104+
* - Invalid multiplier: multiplier is ignored
105+
* - Invalid characters between digits and multiplier: invalid characters are
106+
* ignored
107+
*
108+
* In all of these cases an error string is stored in *errstr (caller must
109+
* release it).
110+
*/
111+
ZEND_API zend_long zend_ini_parse_quantity(zend_string *value, zend_string **errstr);
112+
91113
ZEND_API zend_result zend_ini_register_displayer(const char *name, uint32_t name_length, void (*displayer)(zend_ini_entry *ini_entry, int type));
92114

93115
ZEND_API ZEND_INI_DISP(zend_ini_boolean_displayer_cb);

Zend/zend_operators.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,11 @@ ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables(HashTable *ht1, HashTable
468468
ZEND_API int ZEND_FASTCALL zend_compare_arrays(zval *a1, zval *a2);
469469
ZEND_API int ZEND_FASTCALL zend_compare_objects(zval *o1, zval *o2);
470470

471-
ZEND_API int ZEND_FASTCALL zend_atoi(const char *str, size_t str_len);
472-
ZEND_API zend_long ZEND_FASTCALL zend_atol(const char *str, size_t str_len);
471+
/** Deprecatd in favor of ZEND_STRTOL() */
472+
ZEND_ATTRIBUTE_DEPRECATED ZEND_API int ZEND_FASTCALL zend_atoi(const char *str, size_t str_len);
473+
474+
/** Deprecatd in favor of ZEND_STRTOL() */
475+
ZEND_ATTRIBUTE_DEPRECATED ZEND_API zend_long ZEND_FASTCALL zend_atol(const char *str, size_t str_len);
473476

474477
#define convert_to_null_ex(zv) convert_to_null(zv)
475478
#define convert_to_boolean_ex(zv) convert_to_boolean(zv)

ext/zend_test/test.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,23 @@ static ZEND_FUNCTION(zend_get_unit_enum)
307307
RETURN_OBJ_COPY(zend_enum_get_case_cstr(zend_test_unit_enum, "Foo"));
308308
}
309309

310+
static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity)
311+
{
312+
zend_string *str;
313+
zend_string *errstr = NULL;
314+
315+
ZEND_PARSE_PARAMETERS_START(1, 1)
316+
Z_PARAM_STR(str)
317+
ZEND_PARSE_PARAMETERS_END();
318+
319+
RETVAL_LONG(zend_ini_parse_quantity(str, &errstr));
320+
321+
if (errstr) {
322+
zend_error(E_WARNING, "%s", errstr);
323+
zend_string_release(errstr);
324+
}
325+
}
326+
310327
static ZEND_FUNCTION(namespaced_func)
311328
{
312329
ZEND_PARSE_PARAMETERS_NONE();

ext/zend_test/test.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ function zend_weakmap_remove(object $object): bool {}
9292
function zend_weakmap_dump(): array {}
9393

9494
function zend_get_unit_enum(): ZendTestUnitEnum {}
95+
96+
function zend_test_zend_ini_parse_quantity(string $str): int {}
9597
}
9698

9799
namespace ZendTestNS {

ext/zend_test/test_arginfo.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 7326163f8ce5340c12e74af72d47a8926eb39786 */
2+
* Stub hash: 6ab822237eacd3831c1d8dea23b7d5e4c88e4a88 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
55
ZEND_END_ARG_INFO()
@@ -71,6 +71,10 @@ ZEND_END_ARG_INFO()
7171
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_zend_get_unit_enum, 0, 0, ZendTestUnitEnum, 0)
7272
ZEND_END_ARG_INFO()
7373

74+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_zend_ini_parse_quantity, 0, 1, IS_LONG, 0)
75+
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
76+
ZEND_END_ARG_INFO()
77+
7478
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0)
7579
ZEND_END_ARG_INFO()
7680

@@ -116,6 +120,7 @@ static ZEND_FUNCTION(zend_weakmap_attach);
116120
static ZEND_FUNCTION(zend_weakmap_remove);
117121
static ZEND_FUNCTION(zend_weakmap_dump);
118122
static ZEND_FUNCTION(zend_get_unit_enum);
123+
static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity);
119124
static ZEND_FUNCTION(namespaced_func);
120125
static ZEND_METHOD(_ZendTestClass, is_object);
121126
static ZEND_METHOD(_ZendTestClass, __toString);
@@ -147,6 +152,7 @@ static const zend_function_entry ext_functions[] = {
147152
ZEND_FE(zend_weakmap_remove, arginfo_zend_weakmap_remove)
148153
ZEND_FE(zend_weakmap_dump, arginfo_zend_weakmap_dump)
149154
ZEND_FE(zend_get_unit_enum, arginfo_zend_get_unit_enum)
155+
ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity)
150156
ZEND_NS_FE("ZendTestNS2\\ZendSubNS", namespaced_func, arginfo_ZendTestNS2_ZendSubNS_namespaced_func)
151157
ZEND_FE_END
152158
};

0 commit comments

Comments
 (0)