ProxyPass http://@SERVERNAME@:@PORT@/modules/proxy_html
</Location>
+ # Multi-substitution buffer reallocation test
+ <Location /modules/html_proxy/multi_subst>
+ ProxyHTMLEnable on
+ ProxyHTMLExtended on
+ ProxyHTMLLinks a href
+ ProxyHTMLEvents onclick
+ ProxyHTMLBufSize 256
+ ProxyHTMLURLMap http://x/ http://long-rewritten-path.example.com/
+ ProxyPass http://@SERVERNAME@:@PORT@/modules/proxy_html
+ </Location>
+
+ # Multi-substitution regex buffer test
+ <Location /modules/html_proxy/multi_subst_rx>
+ ProxyHTMLEnable on
+ ProxyHTMLExtended on
+ ProxyHTMLLinks a href
+ ProxyHTMLEvents onclick
+ ProxyHTMLBufSize 256
+ ProxyHTMLURLMap "http://rx/" "http://regex-rewritten-path.example.com/" R
+ ProxyPass http://@SERVERNAME@:@PORT@/modules/proxy_html
+ </Location>
+
# Multiple URL maps test
<Location /modules/html_proxy/multiple_maps>
ProxyHTMLEnable on
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head><title>Multi-substitution buffer test</title></head>
+<body>
+<script>
+var u01='http://x/u01';var u02='http://x/u02';var u03='http://x/u03';var u04='http://x/u04';var u05='http://x/u05';var u06='http://x/u06';var u07='http://x/u07';var u08='http://x/u08';var u09='http://x/u09';var u10='http://x/u10';var u11='http://x/u11';var u12='http://x/u12';var u13='http://x/u13';var u14='http://x/u14';var u15='http://x/u15';var u16='http://x/u16';var u17='http://x/u17';var u18='http://x/u18';var u19='http://x/u19';var u20='http://x/u20';var u21='http://x/u21';var u22='http://x/u22';var u23='http://x/u23';var u24='http://x/u24';var u25='http://x/u25';var u26='http://x/u26';var u27='http://x/u27';var u28='http://x/u28';var u29='http://x/u29';var u30='http://x/u30';var u31='http://x/u31';var u32='http://x/u32';var u33='http://x/u33';var u34='http://x/u34';var u35='http://x/u35';var u36='http://x/u36';var u37='http://x/u37';var u38='http://x/u38';var u39='http://x/u39';var u40='http://x/u40';var sentinel='CDATA_END_OK';
+</script>
+<a href="#" onclick="f('http://x/v01','http://x/v02','http://x/v03','http://x/v04','http://x/v05','http://x/v06','http://x/v07','http://x/v08','http://x/v09','http://x/v10','http://x/v11','http://x/v12','http://x/v13','http://x/v14','http://x/v15','http://x/v16','http://x/v17','http://x/v18','http://x/v19','http://x/v20','http://x/v21','http://x/v22','http://x/v23','http://x/v24','http://x/v25','http://x/v26','http://x/v27','http://x/v28','http://x/v29','http://x/v30','http://x/v31','http://x/v32','http://x/v33','http://x/v34','http://x/v35','http://x/v36','http://x/v37','http://x/v38','http://x/v39','http://x/v40','EVENT_END_OK')">test</a>
+</body>
+</html>
+<!DOCTYPE html>
+<html>
+<head><title>Multi-substitution buffer test</title></head>
+<body>
+<script>
+var u01='http://x/u01';var u02='http://x/u02';var u03='http://x/u03';var u04='http://x/u04';var u05='http://x/u05';var u06='http://x/u06';var u07='http://x/u07';var u08='http://x/u08';var u09='http://x/u09';var u10='http://x/u10';var u11='http://x/u11';var u12='http://x/u12';var u13='http://x/u13';var u14='http://x/u14';var u15='http://x/u15';var u16='http://x/u16';var u17='http://x/u17';var u18='http://x/u18';var u19='http://x/u19';var u20='http://x/u20';var u21='http://x/u21';var u22='http://x/u22';var u23='http://x/u23';var u24='http://x/u24';var u25='http://x/u25';var u26='http://x/u26';var u27='http://x/u27';var u28='http://x/u28';var u29='http://x/u29';var u30='http://x/u30';var u31='http://x/u31';var u32='http://x/u32';var u33='http://x/u33';var u34='http://x/u34';var u35='http://x/u35';var u36='http://x/u36';var u37='http://x/u37';var u38='http://x/u38';var u39='http://x/u39';var u40='http://x/u40';var sentinel='CDATA_END_OK';
+</script>
+<a href="#" onclick="f('http://x/v01','http://x/v02','http://x/v03','http://x/v04','http://x/v05','http://x/v06','http://x/v07','http://x/v08','http://x/v09','http://x/v10','http://x/v11','http://x/v12','http://x/v13','http://x/v14','http://x/v15','http://x/v16','http://x/v17','http://x/v18','http://x/v19','http://x/v20','http://x/v21','http://x/v22','http://x/v23','http://x/v24','http://x/v25','http://x/v26','http://x/v27','http://x/v28','http://x/v29','http://x/v30','http://x/v31','http://x/v32','http://x/v33','http://x/v34','http://x/v35','http://x/v36','http://x/v37','http://x/v38','http://x/v39','http://x/v40','EVENT_END_OK')">test</a>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head><title>Multi-substitution regex buffer test</title></head>
+<body>
+<script>
+var u01='http://rx/u01';var u02='http://rx/u02';var u03='http://rx/u03';var u04='http://rx/u04';var u05='http://rx/u05';var u06='http://rx/u06';var u07='http://rx/u07';var u08='http://rx/u08';var u09='http://rx/u09';var u10='http://rx/u10';var u11='http://rx/u11';var u12='http://rx/u12';var u13='http://rx/u13';var u14='http://rx/u14';var u15='http://rx/u15';var u16='http://rx/u16';var u17='http://rx/u17';var u18='http://rx/u18';var u19='http://rx/u19';var u20='http://rx/u20';var u21='http://rx/u21';var u22='http://rx/u22';var u23='http://rx/u23';var u24='http://rx/u24';var u25='http://rx/u25';var u26='http://rx/u26';var u27='http://rx/u27';var u28='http://rx/u28';var u29='http://rx/u29';var u30='http://rx/u30';var u31='http://rx/u31';var u32='http://rx/u32';var u33='http://rx/u33';var u34='http://rx/u34';var u35='http://rx/u35';var u36='http://rx/u36';var u37='http://rx/u37';var u38='http://rx/u38';var u39='http://rx/u39';var u40='http://rx/u40';var sentinel='RX_CDATA_END_OK';
+</script>
+<a href="#" onclick="f('http://rx/v01','http://rx/v02','http://rx/v03','http://rx/v04','http://rx/v05','http://rx/v06','http://rx/v07','http://rx/v08','http://rx/v09','http://rx/v10','http://rx/v11','http://rx/v12','http://rx/v13','http://rx/v14','http://rx/v15','http://rx/v16','http://rx/v17','http://rx/v18','http://rx/v19','http://rx/v20','http://rx/v21','http://rx/v22','http://rx/v23','http://rx/v24','http://rx/v25','http://rx/v26','http://rx/v27','http://rx/v28','http://rx/v29','http://rx/v30','http://rx/v31','http://rx/v32','http://rx/v33','http://rx/v34','http://rx/v35','http://rx/v36','http://rx/v37','http://rx/v38','http://rx/v39','http://rx/v40','RX_EVENT_END_OK')">test</a>
+</body>
+</html>
+<!DOCTYPE html>
+<html>
+<head><title>Multi-substitution regex buffer test</title></head>
+<body>
+<script>
+var u01='http://rx/u01';var u02='http://rx/u02';var u03='http://rx/u03';var u04='http://rx/u04';var u05='http://rx/u05';var u06='http://rx/u06';var u07='http://rx/u07';var u08='http://rx/u08';var u09='http://rx/u09';var u10='http://rx/u10';var u11='http://rx/u11';var u12='http://rx/u12';var u13='http://rx/u13';var u14='http://rx/u14';var u15='http://rx/u15';var u16='http://rx/u16';var u17='http://rx/u17';var u18='http://rx/u18';var u19='http://rx/u19';var u20='http://rx/u20';var u21='http://rx/u21';var u22='http://rx/u22';var u23='http://rx/u23';var u24='http://rx/u24';var u25='http://rx/u25';var u26='http://rx/u26';var u27='http://rx/u27';var u28='http://rx/u28';var u29='http://rx/u29';var u30='http://rx/u30';var u31='http://rx/u31';var u32='http://rx/u32';var u33='http://rx/u33';var u34='http://rx/u34';var u35='http://rx/u35';var u36='http://rx/u36';var u37='http://rx/u37';var u38='http://rx/u38';var u39='http://rx/u39';var u40='http://rx/u40';var sentinel='RX_CDATA_END_OK';
+</script>
+<a href="#" onclick="f('http://rx/v01','http://rx/v02','http://rx/v03','http://rx/v04','http://rx/v05','http://rx/v06','http://rx/v07','http://rx/v08','http://rx/v09','http://rx/v10','http://rx/v11','http://rx/v12','http://rx/v13','http://rx/v14','http://rx/v15','http://rx/v16','http://rx/v17','http://rx/v18','http://rx/v19','http://rx/v20','http://rx/v21','http://rx/v22','http://rx/v23','http://rx/v24','http://rx/v25','http://rx/v26','http://rx/v27','http://rx/v28','http://rx/v29','http://rx/v30','http://rx/v31','http://rx/v32','http://rx/v33','http://rx/v34','http://rx/v35','http://rx/v36','http://rx/v37','http://rx/v38','http://rx/v39','http://rx/v40','RX_EVENT_END_OK')">test</a>
+</body>
+</html>
url_foo = "/apache/"
url_notexist = "/apache/expr/none"
- cases.append((rf"file('{file_foo}') = 'foo\n' ", 1))
-
if http.have_min_apache_version("2.3.13"):
+ # file() and filesize() are restricted in 2.4.68+ (e.g. in .htaccess
+ # context), turning these into parse errors (None => expect 500).
+ restrict2 = None if http.have_min_apache_version("2.4.68") else 1
cases += [
- (f"filesize('{file_foo}') = 4 ", 1),
- (f"filesize('{file_notexist}') = 0 ", 1),
- (f"filesize('{file_zero}') = 0 ", 1),
+ (f"filesize('{file_foo}') = 4 ", restrict2),
+ (f"filesize('{file_notexist}') = 0 ", restrict2),
+ (f"filesize('{file_zero}') = 0 ", restrict2),
+ (rf"file('{file_foo}') = 'foo\n' ", restrict2),
(f"-d '{file_foo}' ", 0),
(f"-e '{file_foo}' ", 1),
(f"-f '{file_foo}' ", 1),
assert resp.status_code == 400, \
f"PR 49825: expect 400 bad request, got {resp.status_code}"
+ # PUT to a .DAV state subdirectory should be blocked (403) in 2.4.68+,
+ # else allowed (201).
+ dav_uri = f"/{DIR}/.DAV/test.html"
+ expected = 403 if http.have_min_apache_version("2.4.68") else 201
+ with httpx.Client(timeout=30.0) as c:
+ resp = c.put(base + dav_uri, content=BODY)
+ assert t_cmp(resp.status_code, expected), \
+ f"PUT to .DAV subdir: expect {expected}, got {resp.status_code}"
+
# clean up
+ try:
+ os.unlink(os.path.join(htdocs, DIR, ".DAV", "test.html"))
+ except OSError:
+ pass
for sub in (".DAV",):
p = os.path.join(htdocs, DIR, sub)
if os.path.isdir(p):
f"[{h1},{h2},{h3},{h4}]"
-# (htaccess content, [request header pairs], [expected response header pairs])
+# (htaccess content, [request header pairs], [expected response header pairs],
+# [optional expected status; defaults to 200])
TESTCASES = [
# echo
("Header echo Test-Header\nHeader echo ^Aaa$\nHeader echo ^Aa$",
# expr=
('Header set Test-Header foo "expr=%{REQUEST_URI} =~ m#htaccess#"',
[], ["Test-Header", "foo"]),
+ # 500 error test - malformed regex (unmatched parenthesis)
+ ("Header edit Test-Header (unclosed bar",
+ [], [], 500),
]
TESTCASES_251 = [
@need_module("headers")
def test_header_directives(http):
cases = list(TESTCASES)
+ if http.have_min_apache_version("2.4.68"):
+ # file() is not permitted in an .htaccess expr context -> 500.
+ htaccess = _htaccess_path(http)
+ cases.append((
+ f'Header set Test-Header "expr=%{{base64:%{{file:{htaccess}}}}}"',
+ [], [], 500))
if http.have_min_apache_version("2.5.1"):
cases += TESTCASES_251
- for htaccess, req_pairs, exp_pairs in cases:
+ for case in cases:
+ htaccess, req_pairs, exp_pairs = case[0], case[1], case[2]
+ # Optional 4th element is the expected status; defaults to 200.
+ expected_status = case[3] if len(case) > 3 else 200
with open(_htaccess_path(http), "w") as f:
f.write(htaccess)
req_headers = _pairs_to_dict(req_pairs)
r = http.GET("/modules/headers/htaccess/", headers=req_headers)
- assert t_cmp(r.status_code, 200), "Checking return code is '200'"
+ assert t_cmp(r.status_code, expected_status), \
+ f"Checking return code is '{expected_status}' [htaccess: {htaccess!r}]"
+
+ # Only validate response headers for successful responses.
+ if expected_status != 200:
+ continue
for i in range(0, len(exp_pairs), 2):
name, expected = exp_pairs[i], exp_pairs[i + 1]
# ProxyHTMLDocType test
{"type": "url_rewrite", "path": "doctype/doctype.html",
"pattern": r"<!DOCTYPE html", "desc": "DOCTYPE declaration added"},
+
+ # Multi-substitution buffer reallocation tests
+ # Tests that many literal substitutions (where replacement > match) in a
+ # single buffer don't truncate content when ap_varbuf_grow reallocates.
+ {"type": "url_rewrite", "path": "multi_subst/multi_subst.html",
+ "pattern": r"http://long-rewritten-path\.example\.com/u01",
+ "desc": "CDATA multi-subst first URL rewritten"},
+ {"type": "url_rewrite", "path": "multi_subst/multi_subst.html",
+ "pattern": r"http://long-rewritten-path\.example\.com/u40",
+ "desc": "CDATA multi-subst last URL rewritten"},
+ {"type": "url_rewrite", "path": "multi_subst/multi_subst.html",
+ "pattern": r"CDATA_END_OK",
+ "desc": "CDATA content preserved after multi-substitution"},
+ {"type": "url_rewrite", "path": "multi_subst/multi_subst.html",
+ "pattern": r"EVENT_END_OK",
+ "desc": "event attr preserved after multi-substitution"},
+
+ # Multi-substitution regex buffer tests
+ # Tests that many regex substitutions in a single CDATA/event buffer
+ # don't cause heap overflow or content truncation.
+ {"type": "url_rewrite", "path": "multi_subst_rx/multi_subst_rx.html",
+ "pattern": r"http://regex-rewritten-path\.example\.com/u01",
+ "desc": "CDATA regex multi-subst first URL rewritten"},
+ {"type": "url_rewrite", "path": "multi_subst_rx/multi_subst_rx.html",
+ "pattern": r"http://regex-rewritten-path\.example\.com/u40",
+ "desc": "CDATA regex multi-subst last URL rewritten"},
+ {"type": "url_rewrite", "path": "multi_subst_rx/multi_subst_rx.html",
+ "pattern": r"RX_CDATA_END_OK",
+ "desc": "CDATA content preserved after regex multi-substitution"},
+ {"type": "url_rewrite", "path": "multi_subst_rx/multi_subst_rx.html",
+ "pattern": r"RX_EVENT_END_OK",
+ "desc": "event attr preserved after regex multi-substitution"},
+
# Multiple URL maps tests
{"type": "url_rewrite", "path": "multiple_maps/multiple_maps.html",
"pattern": r"http://new-a\.example\.com/page1\.html", "desc": "first URL map"},