Skip to content

Commit

Permalink
Edge cases of Range header are standards compliant
Browse files Browse the repository at this point in the history
Fix a few Range header edge cases to ensure that it is as
standards-compliant as possible.
  • Loading branch information
wolever committed May 29, 2013
1 parent eff4c5e commit 1f45fc8
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 10 deletions.
7 changes: 5 additions & 2 deletions tornado/httputil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions tornado/test/web_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,8 +919,22 @@ def test_static_with_range_neg_end(self):
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)
Expand Down
19 changes: 11 additions & 8 deletions tornado/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -1810,19 +1810,22 @@ def get(self, path, include_body=True):
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
Expand Down

0 comments on commit 1f45fc8

Please sign in to comment.