diff --git a/changes-entries/dav-options-trace.txt b/changes-entries/dav-options-trace.txt new file mode 100644 index 00000000000..a7d10a354a0 --- /dev/null +++ b/changes-entries/dav-options-trace.txt @@ -0,0 +1,2 @@ + *) mod_dav: Do not list TRACE in the Allow field of an OPTIONS response when + TraceEnable is off. PR 70264 diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c index 2a024396f5f..aadeb97c1bc 100644 --- a/modules/dav/main/mod_dav.c +++ b/modules/dav/main/mod_dav.c @@ -1844,6 +1844,8 @@ static int dav_method_search(request_rec *r) /* handle the OPTIONS method */ static int dav_method_options(request_rec *r) { + core_server_config *conf = + ap_get_core_module_config(r->server->module_config); const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r); @@ -1975,7 +1977,9 @@ static int dav_method_options(request_rec *r) apr_table_addn(methods, "HEAD", ""); apr_table_addn(methods, "POST", ""); apr_table_addn(methods, "DELETE", ""); - apr_table_addn(methods, "TRACE", ""); + if (conf->trace_enable != AP_TRACE_DISABLE) { + apr_table_addn(methods, "TRACE", ""); + } apr_table_addn(methods, "PROPFIND", ""); apr_table_addn(methods, "PROPPATCH", ""); apr_table_addn(methods, "COPY", ""); diff --git a/test/modules/dav/test_002_options_trace.py b/test/modules/dav/test_002_options_trace.py new file mode 100644 index 00000000000..33c2c9fcceb --- /dev/null +++ b/test/modules/dav/test_002_options_trace.py @@ -0,0 +1,75 @@ +import os + +import pytest + +from pyhttpd.conf import HttpdConf + +DAV_METHODS = {"OPTIONS", "GET", "HEAD", "POST", "DELETE", "PROPFIND", + "PROPPATCH", "COPY", "MOVE", "LOCK", "UNLOCK"} + + +def _setup(env, trace): + dav_dir = os.path.join(env.gen_dir, 'dav') + os.makedirs(os.path.join(dav_dir, 'col'), exist_ok=True) + with open(os.path.join(dav_dir, 'file.txt'), 'w') as fd: + fd.write('hello\n') + conf = HttpdConf(env, extras={ + 'base': f""" + DavLockDB "{env.gen_dir}/davlock" + TraceEnable {trace} + """, + f"test1.{env.http_tld}": f""" + Alias /dav "{dav_dir}" + + Dav On + Require all granted + + """, + }) + conf.add_vhost_test1() + conf.install() + assert env.apache_restart() == 0 + + +def _request(env, method, path): + r = env.curl_raw(env.mkurl("https", "test1", path), + options=['-X', method]) + assert r.response, f"no response: {r.stderr}" + allow = r.response["header"].get("allow", "") + return r.response["status"], {m.strip() for m in allow.split(',') if m.strip()} + + +class TestOptionsTraceDisabled: + + @pytest.fixture(autouse=True, scope='class') + def _class_scope(self, env): + _setup(env, "off") + + # TRACE is not offered while the server refuses it + @pytest.mark.parametrize("path", ["/dav/file.txt", "/dav/col/"]) + def test_dav_002_001(self, env, path): + status, allow = _request(env, "OPTIONS", path) + assert status == 200 + assert "TRACE" not in allow, f"Allow: {allow}" + assert DAV_METHODS <= allow, f"Allow: {allow}" + + def test_dav_002_002(self, env): + assert _request(env, "TRACE", "/dav/file.txt")[0] == 405 + + +class TestOptionsTraceEnabled: + + @pytest.fixture(autouse=True, scope='class', + params=["on", "extended"]) + def _class_scope(self, env, request): + _setup(env, request.param) + + # unchanged: TRACE is offered and works + @pytest.mark.parametrize("path", ["/dav/file.txt", "/dav/col/"]) + def test_dav_002_101(self, env, path): + status, allow = _request(env, "OPTIONS", path) + assert status == 200 + assert (DAV_METHODS | {"TRACE"}) <= allow, f"Allow: {allow}" + + def test_dav_002_102(self, env): + assert _request(env, "TRACE", "/dav/file.txt")[0] == 200