]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Edge cases of Range header are standards compliant 808/head
authorDavid Wolever <david@wolever.net>
Wed, 29 May 2013 19:04:15 +0000 (15:04 -0400)
committerDavid Wolever <david@wolever.net>
Wed, 29 May 2013 19:07:52 +0000 (15:07 -0400)
Fix a few Range header edge cases to ensure that it is as
standards-compliant as possible.

tornado/httputil.py
tornado/test/web_test.py
tornado/web.py

index 04a0977b1a4d822615a61d6a05a0a30ffa54cc2a..3e7337d99818dc9c6b3145452374a4fe2f5ec754 100644 (file)
@@ -255,6 +255,8 @@ def _parse_request_range(range_header):
     (6, None)
     >>> _parse_request_range("bytes=-6")
     (-6, None)
+    >>> _parse_request_range("bytes=-0")
+    (None, 0)
     >>> _parse_request_range("bytes=")
     (None, None)
     >>> _parse_request_range("foo=42")
@@ -278,8 +280,9 @@ def _parse_request_range(range_header):
         return None
     if end is not None:
         if start is None:
-            start = -end
-            end = None
+            if end != 0:
+                start = -end
+                end = None
         else:
             end += 1
     return (start, end)
index 5f459a81f168fad351374b99856198bd158133f5..942a8917ea23ab73cd584c1dec800c570c56234c 100644 (file)
@@ -919,8 +919,22 @@ class StaticFileTest(WebTestCase):
     def test_static_invalid_range(self):
         response = self.fetch('/static/robots.txt', headers={
             'Range': 'asdf'})
+        self.assertEqual(response.code, 200)
+
+    def test_static_unsatisfiable_range_zero_suffix(self):
+        response = self.fetch('/static/robots.txt', headers={
+            'Range': 'bytes=-0'})
+        self.assertEqual(response.headers.get("Content-Range"),
+                         "bytes */26")
         self.assertEqual(response.code, 416)
 
+    def test_static_unsatisfiable_range_invalid_start(self):
+        response = self.fetch('/static/robots.txt', headers={
+            'Range': 'bytes=26'})
+        self.assertEqual(response.code, 416)
+        self.assertEqual(response.headers.get("Content-Range"),
+                         "bytes */26")
+
     def test_static_head(self):
         response = self.fetch('/static/robots.txt', method='HEAD')
         self.assertEqual(response.code, 200)
index a924312607d4d64b51206aac75a230f9dbbaac89..eade230ef82c98120b9c8c37d02f64a2c5b375bb 100644 (file)
@@ -1810,19 +1810,22 @@ class StaticFileHandler(RequestHandler):
         request_range = None
         range_header = self.request.headers.get("Range")
         if range_header:
+            # As per RFC 2616 14.16, if an invalid Range header is specified,
+            # the request will be treated as if the header didn't exist.
             request_range = httputil._parse_request_range(range_header)
-            if not request_range:
-                self.set_status(416)  # Range Not Satisfiable
-                self.set_header("Content-Type", "text/plain")
-                self.write("The provided Range header is not valid: %r\n"
-                           "Note: multiple ranges are not supported."
-                           % range_header)
-                return
 
         if request_range:
             start, end = request_range
             size = self.get_content_size()
-            if start < 0:
+            if (start is not None and start >= size) or end == 0:
+                # As per RFC 2616 14.35.1, a range is not satisfiable only: if
+                # the first requested byte is equal to or greater than the
+                # content, or when a suffix with length 0 is specified
+                self.set_status(416)  # Range Not Satisfiable
+                self.set_header("Content-Type", "text/plain")
+                self.set_header("Content-Range", "bytes */%s" %(size, ))
+                return
+            if start is not None and start < 0:
                 start += size
             # Note: only return HTTP 206 if less than the entire range has been
             # requested. Not only is this semantically correct, but Chrome