Repository: falconry/falcon

Issue:
Make "bytes" prefix in Range header value required
Currently, range header values without a "bytes" prefix are allowed. Change the code to always require a "bytes" prefix.



Discussion:
Will work on this also

Can you explain this a little more?  Where in the code are we allowing a range header without a bytes prefix?  When a client sends one or when someone using the falcon framework sets one?

@lwcolton Here's the line of code in question:

https://github.com/falconry/falcon/blob/master/falcon/request.py#L344

Currently Falcon allows the sender to leave off the bytes prefix. We just want to make it mandatory.

@kgriffs sounds good


Resolving patch:
diff --git a/falcon/request.py b/falcon/request.py
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -343,6 +343,9 @@ def range(self):
             value = self.env['HTTP_RANGE']
             if value.startswith('bytes='):
                 value = value[6:]
+            else:
+                msg = "The value must be prefixed with 'bytes='"
+                raise HTTPInvalidHeader(msg, 'Range')
         except KeyError:
             return None
 


Test patch:
diff --git a/tests/test_req_vars.py b/tests/test_req_vars.py
--- a/tests/test_req_vars.py
+++ b/tests/test_req_vars.py
@@ -368,15 +368,15 @@ def test_client_prefers(self):
         self.assertEqual(preferred_type, None)
 
     def test_range(self):
-        headers = {'Range': '10-'}
+        headers = {'Range': 'bytes=10-'}
         req = Request(testing.create_environ(headers=headers))
         self.assertEqual(req.range, (10, -1))
 
-        headers = {'Range': '10-20'}
+        headers = {'Range': 'bytes=10-20'}
         req = Request(testing.create_environ(headers=headers))
         self.assertEqual(req.range, (10, 20))
 
-        headers = {'Range': '-10240'}
+        headers = {'Range': 'bytes=-10240'}
         req = Request(testing.create_environ(headers=headers))
         self.assertEqual(req.range, (-10240, -1))
 
@@ -392,62 +392,62 @@ def test_range(self):
         self.assertIs(req.range, None)
 
     def test_range_invalid(self):
-        headers = {'Range': '10240'}
+        headers = {'Range': 'bytes=10240'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '-'}
+        headers = {'Range': 'bytes=-'}
         expected_desc = ('The value provided for the Range header is '
                          'invalid. The byte offsets are missing.')
         self._test_error_details(headers, 'range',
                                  falcon.HTTPInvalidHeader,
                                  'Invalid header value', expected_desc)
 
-        headers = {'Range': '--'}
+        headers = {'Range': 'bytes=--'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '-3-'}
+        headers = {'Range': 'bytes=-3-'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '-3-4'}
+        headers = {'Range': 'bytes=-3-4'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '3-3-4'}
+        headers = {'Range': 'bytes=3-3-4'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '3-3-'}
+        headers = {'Range': 'bytes=3-3-'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '3-3- '}
+        headers = {'Range': 'bytes=3-3- '}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': 'fizbit'}
+        headers = {'Range': 'bytes=fizbit'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': 'a-'}
+        headers = {'Range': 'bytes=a-'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': 'a-3'}
+        headers = {'Range': 'bytes=a-3'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '-b'}
+        headers = {'Range': 'bytes=-b'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': '3-b'}
+        headers = {'Range': 'bytes=3-b'}
         req = Request(testing.create_environ(headers=headers))
         self.assertRaises(falcon.HTTPBadRequest, lambda: req.range)
 
-        headers = {'Range': 'x-y'}
+        headers = {'Range': 'bytes=x-y'}
         expected_desc = ('The value provided for the Range header is '
                          'invalid. It must be a byte range formatted '
                          'according to RFC 2616.')
@@ -463,6 +463,14 @@ def test_range_invalid(self):
                                  falcon.HTTPInvalidHeader,
                                  'Invalid header value', expected_desc)
 
+        headers = {'Range': '10-'}
+        expected_desc = ("The value provided for the Range "
+                         "header is invalid. The value must be "
+                         "prefixed with 'bytes='")
+        self._test_error_details(headers, 'range',
+                                 falcon.HTTPInvalidHeader,
+                                 'Invalid header value', expected_desc)
+
     def test_missing_attribute_header(self):
         req = Request(testing.create_environ())
         self.assertEqual(req.range, None)