mirror of
https://github.com/apache/httpd.git
synced 2026-10-23 22:49:01 +03:00
core: Accept a b/k/m/g suffix in size-valued directive parameters:
* server/util.c (ap_size_parameter_parse): Allow an optional case-insensitive 'b', 'k', 'm' or 'g' suffix, bounding the multiplication by the range of apr_off_t. * include/httpd.h: Update doxygen accordingly. * test/unit/util.c (size_parameter_parse_handles_suffixes): New test. * test/modules/core/test_012_config_off_t.py: Update for suffix support. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> GitHub: PR #851 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1939295 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@@ -2364,13 +2364,16 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse(
|
||||
const char *default_time_unit);
|
||||
|
||||
/**
|
||||
* Parse a configuration parameter as a base 10 apr_off_t. The whole parameter
|
||||
* has to be a number, there must not be any other character after it. What
|
||||
* Parse a configuration parameter as a base 10 apr_off_t. The number may be
|
||||
* followed by a single optional suffix, 'b'/'B' (bytes), 'k'/'K' (multiply
|
||||
* by 1024), 'm'/'M' (multiply by 1024*1024) or 'g'/'G' (multiply by
|
||||
* 1024*1024*1024); there must not be any other character after it. What
|
||||
* values are acceptable is up to the caller.
|
||||
* @param parameter The string containing the number.
|
||||
* @param value The parsed value, only set on success.
|
||||
* @return APR_SUCCESS, APR_EINVAL if the parameter is not a number, or
|
||||
* APR_ERANGE if the number does not fit in an apr_off_t.
|
||||
* @return APR_SUCCESS, APR_EINVAL if the parameter is not a number with
|
||||
* optional suffix, or APR_ERANGE if the value does not fit in an
|
||||
* apr_off_t.
|
||||
*/
|
||||
AP_DECLARE(apr_status_t) ap_size_parameter_parse(const char *parameter,
|
||||
apr_off_t *value);
|
||||
|
||||
@@ -2777,7 +2777,10 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse(
|
||||
AP_DECLARE(apr_status_t) ap_size_parameter_parse(const char *parameter,
|
||||
apr_off_t *value)
|
||||
{
|
||||
apr_off_t parsed;
|
||||
/* The maximum apr_off_t value. */
|
||||
const apr_off_t off_max = sizeof(apr_off_t) == 8 ? APR_INT64_MAX
|
||||
: APR_INT32_MAX;
|
||||
apr_off_t parsed, multiplier = 1;
|
||||
apr_status_t rv;
|
||||
char *end;
|
||||
|
||||
@@ -2790,11 +2793,32 @@ AP_DECLARE(apr_status_t) ap_size_parameter_parse(const char *parameter,
|
||||
if (rv != APR_SUCCESS) {
|
||||
return APR_ERANGE;
|
||||
}
|
||||
|
||||
if (*end == 'k' || *end == 'K') {
|
||||
multiplier = 1024;
|
||||
end++;
|
||||
}
|
||||
else if (*end == 'm' || *end == 'M') {
|
||||
multiplier = 1024 * 1024;
|
||||
end++;
|
||||
}
|
||||
else if (*end == 'g' || *end == 'G') {
|
||||
multiplier = 1024 * 1024 * 1024;
|
||||
end++;
|
||||
}
|
||||
else if (*end == 'b' || *end == 'B') {
|
||||
end++;
|
||||
}
|
||||
if (*end != '\0') {
|
||||
return APR_EINVAL;
|
||||
}
|
||||
|
||||
*value = parsed;
|
||||
if (parsed > off_max / multiplier
|
||||
|| parsed < (-off_max - 1) / multiplier) {
|
||||
return APR_ERANGE;
|
||||
}
|
||||
|
||||
*value = parsed * multiplier;
|
||||
return APR_SUCCESS;
|
||||
}
|
||||
#undef CHECK_OVERFLOW
|
||||
|
||||
@@ -28,7 +28,9 @@ class TestConfigOffT:
|
||||
"-f", os.path.join(env.server_dir, 'conf', 'httpd.conf')])
|
||||
|
||||
@pytest.mark.parametrize("directive", DIRECTIVES)
|
||||
@pytest.mark.parametrize("value", ["0", "1", "123", "1000000", "00123"])
|
||||
@pytest.mark.parametrize("value", ["0", "1", "123", "1000000", "00123",
|
||||
"512b", "512B", "1k", "1K", "2m", "2M",
|
||||
"1g", "1G"])
|
||||
def test_core_012_001(self, env, directive, value):
|
||||
r = self.check_config(env, directive, value)
|
||||
assert r.exit_code == 0, f"{r.stderr}"
|
||||
@@ -36,7 +38,8 @@ class TestConfigOffT:
|
||||
# only a whole decimal number is valid
|
||||
@pytest.mark.parametrize("directive", DIRECTIVES)
|
||||
@pytest.mark.parametrize("value", [
|
||||
"abc", "abc123", "123abc", "123foo456", "12.3", "0x10", "1K", "+", "-",
|
||||
"abc", "abc123", "123abc", "123foo456", "12.3", "0x10", "1kb", "k",
|
||||
"+", "-",
|
||||
])
|
||||
def test_core_012_002(self, env, directive, value):
|
||||
r = self.check_config(env, directive, value)
|
||||
@@ -45,7 +48,8 @@ class TestConfigOffT:
|
||||
|
||||
# a number the directive does not accept, or too large to be stored
|
||||
@pytest.mark.parametrize("directive", DIRECTIVES)
|
||||
@pytest.mark.parametrize("value", ["-1", "9223372036854775808", "9" * 40])
|
||||
@pytest.mark.parametrize("value", ["-1", "9223372036854775808", "9" * 40,
|
||||
"8589934592g"])
|
||||
def test_core_012_003(self, env, directive, value):
|
||||
r = self.check_config(env, directive, value)
|
||||
assert r.exit_code != 0, f"{value} was accepted"
|
||||
|
||||
@@ -149,6 +149,75 @@ HTTPD_START_LOOP_TEST(check_escape_quotes, ap_test_escape_quotes_cases_len)
|
||||
END_TEST
|
||||
|
||||
|
||||
/*
|
||||
* ap_size_parameter_parse()
|
||||
*/
|
||||
|
||||
struct ap_size_parameter_parse_case {
|
||||
const char *input;
|
||||
apr_status_t expected_rv;
|
||||
apr_int64_t expected; /* for a 64-bit apr_off_t */
|
||||
};
|
||||
|
||||
const struct ap_size_parameter_parse_case ap_size_parameter_parse_cases[] = {
|
||||
{ "0", APR_SUCCESS, 0 },
|
||||
{ "1", APR_SUCCESS, 1 },
|
||||
{ "-1", APR_SUCCESS, -1 },
|
||||
{ "1024", APR_SUCCESS, 1024 },
|
||||
{ "0b", APR_SUCCESS, 0 },
|
||||
{ "1b", APR_SUCCESS, 1 },
|
||||
{ "512B", APR_SUCCESS, 512 },
|
||||
{ "1k", APR_SUCCESS, 1024 },
|
||||
{ "1K", APR_SUCCESS, 1024 },
|
||||
{ "10k", APR_SUCCESS, 10240 },
|
||||
{ "1m", APR_SUCCESS, 1024 * 1024 },
|
||||
{ "2M", APR_SUCCESS, 2 * 1024 * 1024 },
|
||||
{ "1g", APR_SUCCESS, 1024 * 1024 * 1024 },
|
||||
{ "4G", APR_SUCCESS, APR_INT64_C(4) * 1024 * 1024 * 1024 },
|
||||
{ "-2k", APR_SUCCESS, -2048 },
|
||||
{ "", APR_EINVAL, 0 },
|
||||
{ "k", APR_EINVAL, 0 },
|
||||
{ "-", APR_EINVAL, 0 },
|
||||
{ "1t", APR_EINVAL, 0 },
|
||||
{ "1kb", APR_EINVAL, 0 },
|
||||
{ "1 k", APR_EINVAL, 0 },
|
||||
{ "1k ", APR_EINVAL, 0 },
|
||||
{ "2 ", APR_EINVAL, 0 },
|
||||
{ "0x10", APR_EINVAL, 0 },
|
||||
{ "1.5k", APR_EINVAL, 0 },
|
||||
/* overflows whether apr_off_t is 32 or 64 bits wide */
|
||||
{ "99999999999999999999", APR_ERANGE, 0 },
|
||||
{ "9223372036854775807k", APR_ERANGE, 0 },
|
||||
{ "9007199254740992m", APR_ERANGE, 0 },
|
||||
{ "-9007199254740992m", APR_ERANGE, 0 },
|
||||
{ "8589934592g", APR_ERANGE, 0 },
|
||||
};
|
||||
|
||||
const size_t ap_size_parameter_parse_cases_len =
|
||||
sizeof(ap_size_parameter_parse_cases) /
|
||||
sizeof(ap_size_parameter_parse_cases[0]);
|
||||
|
||||
HTTPD_START_LOOP_TEST(size_parameter_parse_handles_suffixes, ap_size_parameter_parse_cases_len)
|
||||
{
|
||||
const struct ap_size_parameter_parse_case *c = &ap_size_parameter_parse_cases[_i];
|
||||
apr_status_t expected_rv = c->expected_rv;
|
||||
apr_off_t value = 0;
|
||||
apr_status_t rv;
|
||||
|
||||
/* A value which does not fit a narrower apr_off_t is a range error. */
|
||||
if (expected_rv == APR_SUCCESS && (apr_off_t)c->expected != c->expected) {
|
||||
expected_rv = APR_ERANGE;
|
||||
}
|
||||
|
||||
rv = ap_size_parameter_parse(c->input, &value);
|
||||
ck_assert_int_eq(rv, expected_rv);
|
||||
if (expected_rv == APR_SUCCESS) {
|
||||
ck_assert_int_eq(value, (apr_off_t)c->expected);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
||||
/*
|
||||
* Test Case Boilerplate
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user