diff --git a/tornado/httputil.py b/tornado/httputil.py index 4bd17786d..e03ba61a5 100644 --- a/tornado/httputil.py +++ b/tornado/httputil.py @@ -874,6 +874,8 @@ def _parse_request_range( (None, 0) >>> _parse_request_range("bytes=") (None, None) + >>> _parse_request_range("BYTES=1-2") + (1, 3) >>> _parse_request_range("foo=42") >>> _parse_request_range("bytes=1-2,6-10") @@ -885,7 +887,7 @@ def _parse_request_range( """ unit, _, value = range_header.partition("=") unit, value = unit.strip(), value.strip() - if unit != "bytes": + if unit.lower() != "bytes": return None start_b, _, end_b = value.partition("-") try: diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py index 4e966eb50..5619ef286 100644 --- a/tornado/test/httputil_test.py +++ b/tornado/test/httputil_test.py @@ -14,6 +14,7 @@ HTTPServerRequest, ParseMultipartConfig, RequestStartLine, + _parse_request_range, format_timestamp, parse_cookie, parse_multipart_form_data, @@ -623,6 +624,31 @@ def test_parse_request_start_line(self): self.assertEqual(parsed_start_line.version, self.VERSION) +class ParseRequestRangeTest(unittest.TestCase): + """Tests for httputil._parse_request_range.""" + + def test_lowercase_unit(self): + self.assertEqual(_parse_request_range("bytes=1-2"), (1, 3)) + + def test_uppercase_unit_accepted(self): + # Per RFC 7233 section 2.1: "all rules derived from token are to be + # compared case-insensitively, like range-unit and acceptable-ranges." + # Pre-fix, an uppercase "BYTES" unit returned None. + self.assertEqual(_parse_request_range("BYTES=1-2"), (1, 3)) + + def test_mixed_case_unit_accepted(self): + self.assertEqual(_parse_request_range("Bytes=1-2"), (1, 3)) + self.assertEqual(_parse_request_range("bYtEs=1-2"), (1, 3)) + + def test_uppercase_unit_with_suffix_range(self): + self.assertEqual(_parse_request_range("BYTES=6-"), (6, None)) + self.assertEqual(_parse_request_range("BYTES=-6"), (-6, None)) + + def test_non_bytes_unit_still_rejected(self): + # An unknown unit is rejected regardless of case. + self.assertIsNone(_parse_request_range("items=1-2")) + + class ParseCookieTest(unittest.TestCase): # These tests copied from Django: # https://github.com/django/django/pull/6277/commits/da810901ada1cae9fc1f018f879f11a7fb467b28