Skip to content

Commit

Permalink
Validate HTTP header names as per RFC 9110.
Browse files Browse the repository at this point in the history
Fixes #497.
  • Loading branch information
carltongibson committed Feb 10, 2024
1 parent 9a282dd commit 87042e6
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 1 deletion.
9 changes: 8 additions & 1 deletion daphne/http_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from twisted.web import http
from zope.interface import implementer

from .utils import parse_x_forwarded_for
from .utils import HEADER_NAME_RE, parse_x_forwarded_for

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -69,6 +69,13 @@ def __init__(self, *args, **kwargs):
def process(self):
try:
self.request_start = time.time()

# Validate header names.
for name, _ in self.requestHeaders.getAllRawHeaders():
if not HEADER_NAME_RE.fullmatch(name):
self.basic_error(400, b"Bad Request", "Invalid header name")
return

# Get upgrade header
upgrade_header = None
if self.requestHeaders.hasHeader(b"Upgrade"):
Expand Down
5 changes: 5 additions & 0 deletions daphne/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import importlib
import re

from twisted.web.http_headers import Headers

# Header name regex as per h11.
# https://github.com/python-hyper/h11/blob/a2c68948accadc3876dffcf979d98002e4a4ed27/h11/_abnf.py#L10-L21
HEADER_NAME_RE = re.compile(rb"[-!#$%&'*+.^_`|~0-9a-zA-Z]+")


def import_by_path(path):
"""
Expand Down
12 changes: 12 additions & 0 deletions tests/test_http_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,15 @@ def test_bad_requests(self):
b"GET /?\xc3\xa4\xc3\xb6\xc3\xbc HTTP/1.0\r\n\r\n"
)
self.assertTrue(response.startswith(b"HTTP/1.0 400 Bad Request"))

def test_invalid_header_name(self):
"""
Tests that requests with invalid header names fail.
"""
# Test cases follow those used by h11
# https://github.com/python-hyper/h11/blob/a2c68948accadc3876dffcf979d98002e4a4ed27/h11/tests/test_headers.py#L24-L35
for header_name in [b"foo bar", b"foo\x00bar", b"foo\xffbar", b"foo\x01bar"]:
response = self.run_daphne_raw(
f"GET / HTTP/1.0\r\n{header_name}: baz\r\n\r\n".encode("ascii")
)
self.assertTrue(response.startswith(b"HTTP/1.0 400 Bad Request"))

0 comments on commit 87042e6

Please sign in to comment.