Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changes-entries/trace-disabled-allow.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*) core, mod_proxy: Send GET, HEAD and OPTIONS in the Allow field of the 405
response to a TRACE request denied by TraceEnable, instead of an empty
list. PR 65357
7 changes: 7 additions & 0 deletions modules/http/http_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,7 @@ AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r)
apr_size_t bodylen = 0;
apr_size_t bodybuf;
long res = -1; /* init to avoid gcc -Wall warning */
request_rec *cur = r;

if (r->method_number != M_TRACE) {
return DECLINED;
Expand All @@ -1071,6 +1072,12 @@ AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r)
if (conf->trace_enable == AP_TRACE_DISABLE) {
apr_table_setn(r->notes, "error-notes",
"TRACE denied by server configuration");
/* No handler has run yet, so the methods of the resource are not
* known. The Allow field of the 405 (RFC 9110 15.5.6) lists the
* ones httpd itself answers for any resource: GET and HEAD
* (RFC 9110 9.1) and OPTIONS.
*/
ap_allow_standard_methods(cur, REPLACE_ALLOW, M_GET, M_OPTIONS, -1);
return HTTP_METHOD_NOT_ALLOWED;
}

Expand Down
2 changes: 2 additions & 0 deletions modules/proxy/mod_proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,8 @@ static int proxy_handler(request_rec *r)
apr_table_setn(r->notes, "verbose-error-to", "*");
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01139)
"TRACE forbidden by server configuration");
/* The methods of the backend resource are not known here */
ap_allow_standard_methods(r, REPLACE_ALLOW, M_GET, M_OPTIONS, -1);
return HTTP_METHOD_NOT_ALLOWED;
}

Expand Down
68 changes: 68 additions & 0 deletions test/modules/core/test_010_trace_allow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import pytest

from pyhttpd.conf import HttpdConf


def _conf(env, trace):
conf = HttpdConf(env)
conf.add(f"TraceEnable {trace}")
conf.start_vhost(domains=[f"test1.{env.http_tld}"], port=env.http_port,
doc_root="htdocs/test1", with_ssl=False)
# the backend does not exist, a denied TRACE is not forwarded
conf.add("ProxyPass /proxy/ http://127.0.0.1:1/")
conf.end_vhost()
conf.install()
assert env.apache_restart() == 0


def _request(env, method, path):
options = ['-I'] if method == 'HEAD' else ['-X', method]
r = env.curl_get(env.mkurl("http", "test1", path), options=options)
assert r.response, f"no response: {r.stderr}"
# TRACE is denied on purpose
env.httpd_error_log.ignore_recent(lognos=["AH01139"])
allow = r.response["header"].get("allow")
methods = None if allow is None else \
{m.strip() for m in allow.split(',') if m.strip()}
return r.response["status"], methods


class TestTraceDisabledAllow:

@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
_conf(env, "off")

# the 405 of a TRACE denied by TraceEnable lists methods that do work
@pytest.mark.parametrize("path", ["/index.html", "/no-such-file", "/proxy/x"])
def test_core_010_001(self, env, path):
status, allow = _request(env, "TRACE", path)
assert status == 405
assert allow is not None, "no Allow field"
assert {"GET", "HEAD", "OPTIONS"} <= allow, f"Allow: {allow}"
assert "TRACE" not in allow, f"Allow: {allow}"

# the advertised methods are really supported
@pytest.mark.parametrize("method", ["GET", "HEAD", "OPTIONS"])
def test_core_010_002(self, env, method):
assert _request(env, method, "/index.html")[0] == 200

# OPTIONS does not offer TRACE either
def test_core_010_003(self, env):
status, allow = _request(env, "OPTIONS", "/index.html")
assert status == 200
assert "TRACE" not in allow


class TestTraceEnabledAllow:

@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
_conf(env, "on")

# unchanged: TRACE works and is offered
def test_core_010_101(self, env):
assert _request(env, "TRACE", "/index.html")[0] == 200
status, allow = _request(env, "OPTIONS", "/index.html")
assert status == 200
assert "TRACE" in allow
Loading