mirror of
https://github.com/frappe/gunicorn.git
synced 2026-01-14 11:09:11 +08:00
strict HTTP version validation
Note: This is unrelated to a reverse proxy potentially talking HTTP/3 to clients. This is about the HTTP protocol version spoken to Gunicorn, which is HTTP/1.0 or HTTP/1.1. Little legitimate need for processing HTTP 1 requests with ambiguous version numbers. Broadly refuse. Co-authored-by: Ben Kallus <benjamin.p.kallus.gr@dartmouth.edu>
This commit is contained in:
parent
f5501111a2
commit
7ebe442d08
@ -2282,6 +2282,26 @@ class PermitUnconventionalHTTPMethod(Setting):
|
||||
"""
|
||||
|
||||
|
||||
class PermitUnconventionalHTTPVersion(Setting):
|
||||
name = "permit_unconventional_http_version"
|
||||
section = "Server Mechanics"
|
||||
cli = ["--permit-unconventional-http-version"]
|
||||
validator = validate_bool
|
||||
action = "store_true"
|
||||
default = False
|
||||
desc = """\
|
||||
Permit HTTP version not matching conventions of 2023
|
||||
|
||||
This disables the refusal of likely malformed request lines.
|
||||
It is unusual to specify HTTP 1 versions other than 1.0 and 1.1.
|
||||
|
||||
This option is provided to diagnose backwards-incompatible changes.
|
||||
Use with care and only if necessary. May be removed in a future version.
|
||||
|
||||
.. versionadded:: 22.0.0
|
||||
"""
|
||||
|
||||
|
||||
class CasefoldHTTPMethod(Setting):
|
||||
name = "casefold_http_method"
|
||||
section = "Server Mechanics"
|
||||
|
||||
@ -26,7 +26,8 @@ DEFAULT_MAX_HEADERFIELD_SIZE = 8190
|
||||
RFC9110_5_6_2_TOKEN_SPECIALS = r"!#$%&'*+-.^_`|~"
|
||||
TOKEN_RE = re.compile(r"[%s0-9a-zA-Z]+" % (re.escape(RFC9110_5_6_2_TOKEN_SPECIALS)))
|
||||
METHOD_BADCHAR_RE = re.compile("[a-z#]")
|
||||
VERSION_RE = re.compile(r"HTTP/(\d+)\.(\d+)")
|
||||
# usually 1.0 or 1.1 - RFC9112 permits restricting to single-digit versions
|
||||
VERSION_RE = re.compile(r"HTTP/(\d)\.(\d)")
|
||||
|
||||
|
||||
class Message(object):
|
||||
@ -438,6 +439,10 @@ class Request(Message):
|
||||
if match is None:
|
||||
raise InvalidHTTPVersion(bits[2])
|
||||
self.version = (int(match.group(1)), int(match.group(2)))
|
||||
if not (1, 0) <= self.version < (2, 0):
|
||||
# if ever relaxing this, carefully review Content-Encoding processing
|
||||
if not self.cfg.permit_unconventional_http_version:
|
||||
raise InvalidHTTPVersion(self.version)
|
||||
|
||||
def set_body_reader(self):
|
||||
super().set_body_reader()
|
||||
|
||||
4
tests/requests/invalid/prefix_06.http
Normal file
4
tests/requests/invalid/prefix_06.http
Normal file
@ -0,0 +1,4 @@
|
||||
GET /the/future HTTP/1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111.1\r\n
|
||||
Content-Length: 7\r\n
|
||||
\r\n
|
||||
Old Man
|
||||
5
tests/requests/invalid/prefix_06.py
Normal file
5
tests/requests/invalid/prefix_06.py
Normal file
@ -0,0 +1,5 @@
|
||||
from gunicorn.config import Config
|
||||
from gunicorn.http.errors import InvalidHTTPVersion
|
||||
|
||||
cfg = Config()
|
||||
request = InvalidHTTPVersion
|
||||
2
tests/requests/invalid/version_01.http
Normal file
2
tests/requests/invalid/version_01.http
Normal file
@ -0,0 +1,2 @@
|
||||
GET /foo HTTP/0.99\r\n
|
||||
\r\n
|
||||
2
tests/requests/invalid/version_01.py
Normal file
2
tests/requests/invalid/version_01.py
Normal file
@ -0,0 +1,2 @@
|
||||
from gunicorn.http.errors import InvalidHTTPVersion
|
||||
request = InvalidHTTPVersion
|
||||
2
tests/requests/invalid/version_02.http
Normal file
2
tests/requests/invalid/version_02.http
Normal file
@ -0,0 +1,2 @@
|
||||
GET /foo HTTP/2.0\r\n
|
||||
\r\n
|
||||
2
tests/requests/invalid/version_02.py
Normal file
2
tests/requests/invalid/version_02.py
Normal file
@ -0,0 +1,2 @@
|
||||
from gunicorn.http.errors import InvalidHTTPVersion
|
||||
request = InvalidHTTPVersion
|
||||
Loading…
x
Reference in New Issue
Block a user