Skip to content

Commit 2f00700

Browse files
committed
core, mod_proxy: Send the supported methods in Allow for a denied TRACE.
With TraceEnable off the 405 for a TRACE request carried an empty Allow field, although GET, HEAD and OPTIONS work. The 405 is sent before any handler has declared the methods of the resource, so list the ones httpd answers for any resource. BZ 65357.
1 parent f6b14da commit 2f00700

4 files changed

Lines changed: 80 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*) core, mod_proxy: Send GET, HEAD and OPTIONS in the Allow field of the 405
2+
response to a TRACE request denied by TraceEnable, instead of an empty
3+
list. PR 65357

‎modules/http/http_filters.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,7 @@ AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r)
10571057
apr_size_t bodylen = 0;
10581058
apr_size_t bodybuf;
10591059
long res = -1; /* init to avoid gcc -Wall warning */
1060+
request_rec *cur = r;
10601061

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

‎modules/proxy/mod_proxy.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,8 @@ static int proxy_handler(request_rec *r)
13411341
apr_table_setn(r->notes, "verbose-error-to", "*");
13421342
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01139)
13431343
"TRACE forbidden by server configuration");
1344+
/* The methods of the backend resource are not known here */
1345+
ap_allow_standard_methods(r, REPLACE_ALLOW, M_GET, M_OPTIONS, -1);
13441346
return HTTP_METHOD_NOT_ALLOWED;
13451347
}
13461348

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import pytest
2+
3+
from pyhttpd.conf import HttpdConf
4+
5+
6+
def _conf(env, trace):
7+
conf = HttpdConf(env)
8+
conf.add(f"TraceEnable {trace}")
9+
conf.start_vhost(domains=[f"test1.{env.http_tld}"], port=env.http_port,
10+
doc_root="htdocs/test1", with_ssl=False)
11+
# the backend does not exist, a denied TRACE is not forwarded
12+
conf.add("ProxyPass /proxy/ http://127.0.0.1:1/")
13+
conf.end_vhost()
14+
conf.install()
15+
assert env.apache_restart() == 0
16+
17+
18+
def _request(env, method, path):
19+
options = ['-I'] if method == 'HEAD' else ['-X', method]
20+
r = env.curl_get(env.mkurl("http", "test1", path), options=options)
21+
assert r.response, f"no response: {r.stderr}"
22+
# TRACE is denied on purpose
23+
env.httpd_error_log.ignore_recent(lognos=["AH01139"])
24+
allow = r.response["header"].get("allow")
25+
methods = None if allow is None else \
26+
{m.strip() for m in allow.split(',') if m.strip()}
27+
return r.response["status"], methods
28+
29+
30+
class TestTraceDisabledAllow:
31+
32+
@pytest.fixture(autouse=True, scope='class')
33+
def _class_scope(self, env):
34+
_conf(env, "off")
35+
36+
# the 405 of a TRACE denied by TraceEnable lists methods that do work
37+
@pytest.mark.parametrize("path", ["/index.html", "/no-such-file", "/proxy/x"])
38+
def test_core_010_001(self, env, path):
39+
status, allow = _request(env, "TRACE", path)
40+
assert status == 405
41+
assert allow is not None, "no Allow field"
42+
assert {"GET", "HEAD", "OPTIONS"} <= allow, f"Allow: {allow}"
43+
assert "TRACE" not in allow, f"Allow: {allow}"
44+
45+
# the advertised methods are really supported
46+
@pytest.mark.parametrize("method", ["GET", "HEAD", "OPTIONS"])
47+
def test_core_010_002(self, env, method):
48+
assert _request(env, method, "/index.html")[0] == 200
49+
50+
# OPTIONS does not offer TRACE either
51+
def test_core_010_003(self, env):
52+
status, allow = _request(env, "OPTIONS", "/index.html")
53+
assert status == 200
54+
assert "TRACE" not in allow
55+
56+
57+
class TestTraceEnabledAllow:
58+
59+
@pytest.fixture(autouse=True, scope='class')
60+
def _class_scope(self, env):
61+
_conf(env, "on")
62+
63+
# unchanged: TRACE works and is offered
64+
def test_core_010_101(self, env):
65+
assert _request(env, "TRACE", "/index.html")[0] == 200
66+
status, allow = _request(env, "OPTIONS", "/index.html")
67+
assert status == 200
68+
assert "TRACE" in allow

0 commit comments

Comments
 (0)