Rev 5706: (vila) Correctly parse partial range specifiers in the HTTP test server. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Tue Mar 8 16:59:39 UTC 2011


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5706 [merge]
revision-id: pqm at pqm.ubuntu.com-20110308165936-hp3voq41wvr83wnl
parent: pqm at pqm.ubuntu.com-20110308014146-kflr8jyyx1yahq31
parent: v.ladeuil+lp at free.fr-20110308161618-kon43u6huz2cwfv0
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Tue 2011-03-08 16:59:36 +0000
message:
  (vila) Correctly parse partial range specifiers in the HTTP test server.
   (Vincent Ladeuil)
modified:
  bzrlib/tests/http_server.py    httpserver.py-20061012142527-m1yxdj1xazsf8d7s-1
  bzrlib/tests/test_http.py      testhttp.py-20051018020158-b2eef6e867c514d9
  doc/en/release-notes/bzr-2.4.txt bzr2.4.txt-20110114053217-k7ym9jfz243fddjm-1
=== modified file 'bzrlib/tests/http_server.py'
--- a/bzrlib/tests/http_server.py	2011-01-12 01:01:53 +0000
+++ b/bzrlib/tests/http_server.py	2011-03-08 16:00:55 +0000
@@ -128,32 +128,42 @@
     def _handle_one_request(self):
         SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
 
-    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
+    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)?$')
     _tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
 
-    def parse_ranges(self, ranges_header):
-        """Parse the range header value and returns ranges and tail.
-
-        RFC2616 14.35 says that syntactically invalid range
-        specifiers MUST be ignored. In that case, we return 0 for
-        tail and [] for ranges.
+    def _parse_ranges(self, ranges_header, file_size):
+        """Parse the range header value and returns ranges.
+
+        RFC2616 14.35 says that syntactically invalid range specifiers MUST be
+        ignored. In that case, we return None instead of a range list.
+
+        :param ranges_header: The 'Range' header value.
+
+        :param file_size: The size of the requested file.
+
+        :return: A list of (start, end) tuples or None if some invalid range
+            specifier is encountered.
         """
-        tail = 0
-        ranges = []
         if not ranges_header.startswith('bytes='):
             # Syntactically invalid header
-            return 0, []
+            return None
 
+        tail = None
+        ranges = []
         ranges_header = ranges_header[len('bytes='):]
         for range_str in ranges_header.split(','):
-            # FIXME: RFC2616 says end is optional and default to file_size
             range_match = self._range_regexp.match(range_str)
             if range_match is not None:
                 start = int(range_match.group('start'))
-                end = int(range_match.group('end'))
+                end_match = range_match.group('end')
+                if end_match is None:
+                    # RFC2616 says end is optional and default to file_size
+                    end = file_size
+                else:
+                    end = int(end_match)
                 if start > end:
                     # Syntactically invalid range
-                    return 0, []
+                    return None
                 ranges.append((start, end))
             else:
                 tail_match = self._tail_regexp.match(range_str)
@@ -161,8 +171,21 @@
                     tail = int(tail_match.group('tail'))
                 else:
                     # Syntactically invalid range
-                    return 0, []
-        return tail, ranges
+                    return None
+        if tail is not None:
+            # Normalize tail into ranges
+            ranges.append((max(0, file_size - tail), file_size))
+
+        checked_ranges = []
+        for start, end in ranges:
+            if start >= file_size:
+                # RFC2616 14.35, ranges are invalid if start >= file_size
+                return None
+            # RFC2616 14.35, end values should be truncated
+            # to file_size -1 if they exceed it
+            end = min(end, file_size - 1)
+            checked_ranges.append((start, end))
+        return checked_ranges
 
     def _header_line_length(self, keyword, value):
         header_line = '%s: %s\r\n' % (keyword, value)
@@ -258,29 +281,8 @@
             return
 
         file_size = os.fstat(f.fileno())[6]
-        tail, ranges = self.parse_ranges(ranges_header_value)
-        # Normalize tail into ranges
-        if tail != 0:
-            ranges.append((file_size - tail, file_size))
-
-        self._satisfiable_ranges = True
-        if len(ranges) == 0:
-            self._satisfiable_ranges = False
-        else:
-            def check_range(range_specifier):
-                start, end = range_specifier
-                # RFC2616 14.35, ranges are invalid if start >= file_size
-                if start >= file_size:
-                    self._satisfiable_ranges = False # Side-effect !
-                    return 0, 0
-                # RFC2616 14.35, end values should be truncated
-                # to file_size -1 if they exceed it
-                end = min(end, file_size - 1)
-                return start, end
-
-            ranges = map(check_range, ranges)
-
-        if not self._satisfiable_ranges:
+        ranges = self._parse_ranges(ranges_header_value, file_size)
+        if not ranges:
             # RFC2616 14.16 and 14.35 says that when a server
             # encounters unsatisfiable range specifiers, it
             # SHOULD return a 416.

=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py	2011-02-10 12:37:27 +0000
+++ b/bzrlib/tests/test_http.py	2011-03-08 16:16:18 +0000
@@ -255,6 +255,40 @@
         self.assertEqual('realm="Thou should not pass"', remainder)
 
 
+class TestHTTPRangeParsing(tests.TestCase):
+
+    def setUp(self):
+        super(TestHTTPRangeParsing, self).setUp()
+        # We focus on range  parsing here and ignore everything else
+        class RequestHandler(http_server.TestingHTTPRequestHandler):
+            def setup(self): pass
+            def handle(self): pass
+            def finish(self): pass
+
+        self.req_handler = RequestHandler(None, None, None)
+
+    def assertRanges(self, ranges, header, file_size):
+        self.assertEquals(ranges,
+                          self.req_handler._parse_ranges(header, file_size))
+
+    def test_simple_range(self):
+        self.assertRanges([(0,2)], 'bytes=0-2', 12)
+
+    def test_tail(self):
+        self.assertRanges([(8, 11)], 'bytes=-4', 12)
+
+    def test_tail_bigger_than_file(self):
+        self.assertRanges([(0, 11)], 'bytes=-99', 12)
+
+    def test_range_without_end(self):
+        self.assertRanges([(4, 11)], 'bytes=4-', 12)
+
+    def test_invalid_ranges(self):
+        self.assertRanges(None, 'bytes=12-22', 12)
+        self.assertRanges(None, 'bytes=1-3,12-22', 12)
+        self.assertRanges(None, 'bytes=-', 12)
+
+
 class TestHTTPServer(tests.TestCase):
     """Test the HTTP servers implementations."""
 
@@ -428,7 +462,7 @@
     """Test the http connections."""
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -493,7 +527,7 @@
 class TestPost(tests.TestCase):
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -552,7 +586,7 @@
     """
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -1030,7 +1064,7 @@
     """Tests readv requests against a server erroring out on too much ranges."""
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -1076,7 +1110,7 @@
 
     def _proxied_request(self):
         handler = _urllib2_wrappers.ProxyHandler()
-        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
+        request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
         handler.set_proxy(request, 'http')
         return request
 
@@ -1090,6 +1124,12 @@
         request = self._proxied_request()
         self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
+    def test_user_with_at(self):
+        self.overrideEnv('http_proxy',
+                         'http://username@domain:password@proxy_host:1234')
+        request = self._proxied_request()
+        self.assertFalse(request.headers.has_key('Proxy-authorization'))
+
     def test_invalid_proxy(self):
         """A proxy env variable without scheme"""
         self.overrideEnv('http_proxy', 'host:1234')
@@ -1126,7 +1166,7 @@
     """
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -1223,7 +1263,7 @@
     """Test the Range header in GET methods."""
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -1273,7 +1313,7 @@
     """Test redirection between http servers."""
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -1346,7 +1386,7 @@
     """
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -1401,7 +1441,7 @@
     """Test transport.do_catching_redirections."""
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 
@@ -1756,7 +1796,7 @@
 class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
 
     scenarios = multiply_scenarios(
-        vary_by_http_client_implementation(), 
+        vary_by_http_client_implementation(),
         vary_by_http_protocol_version(),
         )
 

=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- a/doc/en/release-notes/bzr-2.4.txt	2011-03-06 22:45:05 +0000
+++ b/doc/en/release-notes/bzr-2.4.txt	2011-03-08 16:00:55 +0000
@@ -221,5 +221,8 @@
    suite.  This can include new facilities for writing tests, fixes to 
    spurious test failures and changes to the way things should be tested.
 
+* The Range parsing for HTTP requests will correctly parse incomplete ranges.
+  (Vincent Ladeuil, #731240)
+
 ..
    vim: tw=74 ft=rst ff=unix




More information about the bazaar-commits mailing list