~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_server.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-03-08 16:59:36 UTC
  • mfrom: (5705.1.2 731240-range-parsing)
  • Revision ID: pqm@pqm.ubuntu.com-20110308165936-hp3voq41wvr83wnl
(vila) Correctly parse partial range specifiers in the HTTP test server.
 (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
128
128
    def _handle_one_request(self):
129
129
        SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
130
130
 
131
 
    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
 
131
    _range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)?$')
132
132
    _tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
133
133
 
134
 
    def parse_ranges(self, ranges_header):
135
 
        """Parse the range header value and returns ranges and tail.
136
 
 
137
 
        RFC2616 14.35 says that syntactically invalid range
138
 
        specifiers MUST be ignored. In that case, we return 0 for
139
 
        tail and [] for ranges.
 
134
    def _parse_ranges(self, ranges_header, file_size):
 
135
        """Parse the range header value and returns ranges.
 
136
 
 
137
        RFC2616 14.35 says that syntactically invalid range specifiers MUST be
 
138
        ignored. In that case, we return None instead of a range list.
 
139
 
 
140
        :param ranges_header: The 'Range' header value.
 
141
 
 
142
        :param file_size: The size of the requested file.
 
143
 
 
144
        :return: A list of (start, end) tuples or None if some invalid range
 
145
            specifier is encountered.
140
146
        """
141
 
        tail = 0
142
 
        ranges = []
143
147
        if not ranges_header.startswith('bytes='):
144
148
            # Syntactically invalid header
145
 
            return 0, []
 
149
            return None
146
150
 
 
151
        tail = None
 
152
        ranges = []
147
153
        ranges_header = ranges_header[len('bytes='):]
148
154
        for range_str in ranges_header.split(','):
149
 
            # FIXME: RFC2616 says end is optional and default to file_size
150
155
            range_match = self._range_regexp.match(range_str)
151
156
            if range_match is not None:
152
157
                start = int(range_match.group('start'))
153
 
                end = int(range_match.group('end'))
 
158
                end_match = range_match.group('end')
 
159
                if end_match is None:
 
160
                    # RFC2616 says end is optional and default to file_size
 
161
                    end = file_size
 
162
                else:
 
163
                    end = int(end_match)
154
164
                if start > end:
155
165
                    # Syntactically invalid range
156
 
                    return 0, []
 
166
                    return None
157
167
                ranges.append((start, end))
158
168
            else:
159
169
                tail_match = self._tail_regexp.match(range_str)
161
171
                    tail = int(tail_match.group('tail'))
162
172
                else:
163
173
                    # Syntactically invalid range
164
 
                    return 0, []
165
 
        return tail, ranges
 
174
                    return None
 
175
        if tail is not None:
 
176
            # Normalize tail into ranges
 
177
            ranges.append((max(0, file_size - tail), file_size))
 
178
 
 
179
        checked_ranges = []
 
180
        for start, end in ranges:
 
181
            if start >= file_size:
 
182
                # RFC2616 14.35, ranges are invalid if start >= file_size
 
183
                return None
 
184
            # RFC2616 14.35, end values should be truncated
 
185
            # to file_size -1 if they exceed it
 
186
            end = min(end, file_size - 1)
 
187
            checked_ranges.append((start, end))
 
188
        return checked_ranges
166
189
 
167
190
    def _header_line_length(self, keyword, value):
168
191
        header_line = '%s: %s\r\n' % (keyword, value)
258
281
            return
259
282
 
260
283
        file_size = os.fstat(f.fileno())[6]
261
 
        tail, ranges = self.parse_ranges(ranges_header_value)
262
 
        # Normalize tail into ranges
263
 
        if tail != 0:
264
 
            ranges.append((file_size - tail, file_size))
265
 
 
266
 
        self._satisfiable_ranges = True
267
 
        if len(ranges) == 0:
268
 
            self._satisfiable_ranges = False
269
 
        else:
270
 
            def check_range(range_specifier):
271
 
                start, end = range_specifier
272
 
                # RFC2616 14.35, ranges are invalid if start >= file_size
273
 
                if start >= file_size:
274
 
                    self._satisfiable_ranges = False # Side-effect !
275
 
                    return 0, 0
276
 
                # RFC2616 14.35, end values should be truncated
277
 
                # to file_size -1 if they exceed it
278
 
                end = min(end, file_size - 1)
279
 
                return start, end
280
 
 
281
 
            ranges = map(check_range, ranges)
282
 
 
283
 
        if not self._satisfiable_ranges:
 
284
        ranges = self._parse_ranges(ranges_header_value, file_size)
 
285
        if not ranges:
284
286
            # RFC2616 14.16 and 14.35 says that when a server
285
287
            # encounters unsatisfiable range specifiers, it
286
288
            # SHOULD return a 416.