Skip to content

Commit 7926f0b

Browse files
authored
relax get_host strictness (#3148)
2 parents 65eb639 + deab88f commit 7926f0b

3 files changed

Lines changed: 25 additions & 14 deletions

File tree

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Version 3.1.8
55

66
Unreleased
77

8+
- ``Request.host`` and ``get_host`` return the empty string if the header is
9+
missing or has invalid characters. :issue:`3142`
10+
811

912
Version 3.1.7
1013
-------------

src/werkzeug/sansio/utils.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ def get_host(
9292
up of valid characters, but this does not check validity beyond that. If a
9393
list of trusted domains is given, the domain must match one.
9494
95+
If the host header is not available, such as for HTTP/0.9 and 1.0, or it has
96+
invalid characters, the empty string is returned. Subdomain and host
97+
routing, and external URL building, will not work in these cases.
98+
9599
:param scheme: The protocol of the request. Used to omit the standard ports
96100
80 and 443.
97101
:param host_header: The ``Host`` header value.
@@ -107,7 +111,11 @@ def get_host(
107111
:return: Host, with port if necessary.
108112
:raise .SecurityError: If the host is not trusted.
109113
110-
.. versionchanged:: 3.2
114+
.. versionchanged:: 3.1.8
115+
The empty string is again returned if no host header value is available,
116+
or if the characters are invalid.
117+
118+
.. versionchanged:: 3.1.7
111119
The characters of the host value are validated. The empty string is no
112120
longer allowed if no header value is available.
113121
@@ -130,15 +138,20 @@ def get_host(
130138

131139
host = f"{host}:{server[1]}"
132140
else:
133-
host = ""
141+
# Pass through empty host from HTTP/0.9 and 1.0.
142+
return ""
134143

135144
if scheme in {"http", "ws"}:
136145
host = host.removesuffix(":80")
137146
elif scheme in {"https", "wss"}:
138147
host = host.removesuffix(":443")
139148

140149
if not host_is_trusted(host, trusted_hosts):
141-
raise SecurityError(f"Host {host!r} is not trusted.")
150+
if trusted_hosts:
151+
raise SecurityError(f"Host {host!r} is not trusted.")
152+
153+
# Invalid characters, treat as empty.
154+
return ""
142155

143156
return host
144157

tests/sansio/test_utils.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import pytest
44

5-
from werkzeug.exceptions import SecurityError
65
from werkzeug.sansio.utils import get_content_length
76
from werkzeug.sansio.utils import get_host
87

@@ -37,23 +36,19 @@ def test_get_host(
3736
assert get_host(scheme, host_header, server) == expected
3837

3938

40-
def test_get_host_unix_invalid() -> None:
41-
with pytest.raises(SecurityError):
42-
get_host("http", None, ("unix/socket", None))
39+
def test_get_host_unix() -> None:
40+
assert get_host("http", None, ("unix/socket", None)) == ""
4341

4442

45-
def test_get_host_missing_invalid() -> None:
46-
with pytest.raises(SecurityError):
47-
get_host("http", None, None)
43+
def test_get_host_missing() -> None:
44+
assert get_host("http", None, None) == ""
4845

4946

5047
@pytest.mark.parametrize(
51-
"value",
52-
["", "a.test:8080@b.test", "a.test:port", "[z:443]:8080"],
48+
"value", ["", "a.test:8080@b.test", "a.test:port", "[z:443]:8080"]
5349
)
5450
def test_get_host_invalid(value: str | None) -> None:
55-
with pytest.raises(SecurityError):
56-
get_host("http", value, None)
51+
assert get_host("http", value, None) == ""
5752

5853

5954
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)