]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Handle negative byte ranges
authorDavid Wolever <david@wolever.net>
Fri, 17 May 2013 04:34:36 +0000 (00:34 -0400)
committerDavid Wolever <david@wolever.net>
Fri, 17 May 2013 04:34:36 +0000 (00:34 -0400)
tornado/httputil.py
tornado/test/web_test.py

index 83e4558d8b7dd8a838b1a21827c3625babf40309..67814a233165b72bf08c6db9d24c37ee4ea864a1 100644 (file)
@@ -235,7 +235,7 @@ class HTTPFile(ObjectDict):
     """
     pass
 
-def parse_request_range(range_header):
+def parse_request_range(range_header):
     """Parses a Range header.
 
     Returns either ``None`` or an instance of ``slice``::
@@ -247,12 +247,18 @@ def parse_request_range(range_header):
         [1, 2]
         >>> parse_request_range("bytes=6-")
         slice(6, None, None)
+        >>> parse_request_range("bytes=-6")
+        slice(-6, None, None)
         >>> parse_request_range("bytes=")
         slice(None, None, None)
         >>> parse_request_range("foo=42")
         >>> parse_request_range("bytes=1-2,6-10")
 
     Note: only supports one range (ex, ``bytes=1-2,6-10`` is not allowed).
+
+    See [0] for the details of the range header.
+
+    [0]: http://greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#byte.ranges
     """
     unit, _, value = range_header.partition("=")
     unit, value = unit.strip(), value.strip()
@@ -265,7 +271,11 @@ def parse_request_range(range_header):
     except ValueError:
         return None
     if end is not None:
-        end += 1
+        if start is None:
+            start = -end
+            end = None
+        else:
+            end += 1
     return slice(start, end)
 
 def get_content_range(data, request_range):
@@ -280,11 +290,12 @@ def get_content_range(data, request_range):
     """
 
     data_len = len(data)
-    return "%s-%s/%s" %(
-        request_range.start or 0,
-        (request_range.stop or data_len) - 1,
-        data_len,
-    )
+    start, stop = request_range.start, request_range.stop
+    start = start or 0
+    if start < 0:
+        start = data_len + start
+    stop = (stop or data_len) - 1
+    return "%s-%s/%s" %(start, stop, data_len)
 
 def _int_or_none(val):
     val = val.strip()
index c305a8513ec588b8c175e51afba670c9b3fa2922..09f04ae6ef544a447404d9e1182c98c5b78d6a4e 100644 (file)
@@ -878,6 +878,22 @@ class StaticFileTest(WebTestCase):
         self.assertEqual(response.headers.get("Content-Range"),
                          "0-9/26")
 
+    def test_static_with_range_end_edge(self):
+        response = self.fetch('/static/robots.txt', headers={
+                'Range': 'bytes=22-'})
+        self.assertEqual(response.body, b": /\n")
+        self.assertEqual(response.headers.get("Content-Length"), "4")
+        self.assertEqual(response.headers.get("Content-Range"),
+                         "22-25/26")
+
+    def test_static_with_range_neg_end(self):
+        response = self.fetch('/static/robots.txt', headers={
+                'Range': 'bytes=-4'})
+        self.assertEqual(response.body, b": /\n")
+        self.assertEqual(response.headers.get("Content-Length"), "4")
+        self.assertEqual(response.headers.get("Content-Range"),
+                         "22-25/26")
+
     def test_static_invalid_range(self):
         response = self.fetch('/static/robots.txt', headers={
                 'Range': 'asdf'})