From aa0fbdc49e747c859fd2f2b06ba276f6ffbf67a6 Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Tue, 15 Sep 2026 22:14:15 +0100 Subject: [PATCH 1/2] * test/pyhttpd/mod_aptest/mod_aptest.c (aptest_allow_methods): New; pass the methods named in an AP-Test-Allow-Methods request header to ap_allow_methods(). * test/modules/core/test_009_allow_methods.py: New test suite. Co-Authored-By: Claude Opus 5 (1M context) GitHub: PR #428 --- test/modules/core/test_009_allow_methods.py | 46 +++++++++++++++++++++ test/pyhttpd/mod_aptest/mod_aptest.c | 24 +++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/modules/core/test_009_allow_methods.py diff --git a/test/modules/core/test_009_allow_methods.py b/test/modules/core/test_009_allow_methods.py new file mode 100644 index 00000000000..e438e6689b2 --- /dev/null +++ b/test/modules/core/test_009_allow_methods.py @@ -0,0 +1,46 @@ +import pytest + +from pyhttpd.conf import HttpdConf + + +class TestAllowMethods: + + @pytest.fixture(autouse=True, scope='class') + def _class_scope(self, env): + conf = HttpdConf(env) + conf.add_vhost_test1() + conf.install() + assert env.apache_restart() == 0 + + # mod_aptest passes the methods named in AP-Test-Allow-Methods to + # ap_allow_methods() from a fixups hook; the default handler then + # merges its own and answers OPTIONS with the Allow header. + def options(self, env, methods=None): + options = ['-X', 'OPTIONS'] + if methods is not None: + options.extend(['-H', f'AP-Test-Allow-Methods: {methods}']) + r = env.curl_get(env.mkurl("http", "test1", "/index.html"), + options=options) + assert r.response, f"no response: {r.stderr}" + assert r.response["status"] == 200 + return [m.strip() for m in r.response["header"]["allow"].split(',')] + + def test_core_009_01(self, env): + allow = self.options(env) + for method in ["GET", "POST", "OPTIONS"]: + assert method in allow, f"expected {method} in Allow: {allow}" + + def test_core_009_02(self, env): + allow = self.options(env, "DELETE") + assert "DELETE" in allow, f"expected DELETE in Allow: {allow}" + + # An extension method reaches make_allow() only through method_list, + # which is emitted just when the M_INVALID bit is set in method_mask. + def test_core_009_03(self, env): + allow = self.options(env, "FROBNICATE") + assert "FROBNICATE" in allow, f"expected FROBNICATE in Allow: {allow}" + + def test_core_009_04(self, env): + allow = self.options(env, "DELETE, FROBNICATE") + for method in ["DELETE", "FROBNICATE"]: + assert method in allow, f"expected {method} in Allow: {allow}" diff --git a/test/pyhttpd/mod_aptest/mod_aptest.c b/test/pyhttpd/mod_aptest/mod_aptest.c index d1a8e0533d9..48d2bd8403d 100644 --- a/test/pyhttpd/mod_aptest/mod_aptest.c +++ b/test/pyhttpd/mod_aptest/mod_aptest.c @@ -51,6 +51,29 @@ static int aptest_post_read_request(request_rec *r) return DECLINED; } +/* + * Register the methods named in an "AP-Test-Allow-Methods" request header + * via ap_allow_methods(), so a test can check how they are reflected in the + * Allow response header. + */ +static int aptest_allow_methods(request_rec *r) +{ + const char *methods = apr_table_get(r->headers_in, "AP-Test-Allow-Methods"); + char *list, *name, *last; + + if (methods == NULL) { + return DECLINED; + } + + list = apr_pstrdup(r->pool, methods); + for (name = apr_strtok(list, ", ", &last); name != NULL; + name = apr_strtok(NULL, ", ", &last)) { + ap_allow_methods(r, MERGE_ALLOW, name, NULL); + } + + return DECLINED; +} + /* Install this module into the apache2 infrastructure. */ static void aptest_hooks(apr_pool_t *pool) @@ -61,6 +84,7 @@ static void aptest_hooks(apr_pool_t *pool) /* test case monitoring */ ap_hook_post_read_request(aptest_post_read_request, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_fixups(aptest_allow_methods, NULL, NULL, APR_HOOK_MIDDLE); } From 4a0087831f4aeac86de69c9b03a7071da9507f5a Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Wed, 16 Sep 2026 14:36:46 +0100 Subject: [PATCH 2/2] * modules/http/http_protocol.c (make_allow): Don't check for the M_INVALID bit being set in the method mask if unregistered methods are allowed. That bit is never set by ap_method_list_add(), and this branch adding unregistered methods to Allow: never followed. --- modules/http/http_protocol.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c index 41d9f7fc7d3..eb121587661 100644 --- a/modules/http/http_protocol.c +++ b/modules/http/http_protocol.c @@ -897,8 +897,7 @@ static char *make_allow(request_rec *r) /* ### this is rather annoying. we should enforce registration of ### these methods */ - if ((mask & (AP_METHOD_BIT << M_INVALID)) - && (r->allowed_methods->method_list != NULL) + if (r->allowed_methods->method_list != NULL && (r->allowed_methods->method_list->nelts != 0)) { apr_array_cat(allow, r->allowed_methods->method_list); }