1
# Copyright (C) 2006-2010 Canonical Ltd
1
# Copyright (C) 2006-2011 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
139
128
def _handle_one_request(self):
140
129
SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self)
142
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)$')
131
_range_regexp = re.compile(r'^(?P<start>\d+)-(?P<end>\d+)?$')
143
132
_tail_regexp = re.compile(r'^-(?P<tail>\d+)$')
145
def parse_ranges(self, ranges_header):
146
"""Parse the range header value and returns ranges and tail.
148
RFC2616 14.35 says that syntactically invalid range
149
specifiers MUST be ignored. In that case, we return 0 for
150
tail and [] for ranges.
134
def _parse_ranges(self, ranges_header, file_size):
135
"""Parse the range header value and returns ranges.
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.
140
:param ranges_header: The 'Range' header value.
142
:param file_size: The size of the requested file.
144
:return: A list of (start, end) tuples or None if some invalid range
145
specifier is encountered.
154
147
if not ranges_header.startswith('bytes='):
155
148
# Syntactically invalid header
158
153
ranges_header = ranges_header[len('bytes='):]
159
154
for range_str in ranges_header.split(','):
160
# FIXME: RFC2616 says end is optional and default to file_size
161
155
range_match = self._range_regexp.match(range_str)
162
156
if range_match is not None:
163
157
start = int(range_match.group('start'))
164
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
166
165
# Syntactically invalid range
168
167
ranges.append((start, end))
170
169
tail_match = self._tail_regexp.match(range_str)
172
171
tail = int(tail_match.group('tail'))
174
173
# Syntactically invalid range
176
# Normalize tail into ranges
177
ranges.append((max(0, file_size - tail), file_size))
180
for start, end in ranges:
181
if start >= file_size:
182
# RFC2616 14.35, ranges are invalid if start >= file_size
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
178
190
def _header_line_length(self, keyword, value):
179
191
header_line = '%s: %s\r\n' % (keyword, value)
271
283
file_size = os.fstat(f.fileno())[6]
272
tail, ranges = self.parse_ranges(ranges_header_value)
273
# Normalize tail into ranges
275
ranges.append((file_size - tail, file_size))
277
self._satisfiable_ranges = True
279
self._satisfiable_ranges = False
281
def check_range(range_specifier):
282
start, end = range_specifier
283
# RFC2616 14.35, ranges are invalid if start >= file_size
284
if start >= file_size:
285
self._satisfiable_ranges = False # Side-effect !
287
# RFC2616 14.35, end values should be truncated
288
# to file_size -1 if they exceed it
289
end = min(end, file_size - 1)
292
ranges = map(check_range, ranges)
294
if not self._satisfiable_ranges:
284
ranges = self._parse_ranges(ranges_header_value, file_size)
295
286
# RFC2616 14.16 and 14.35 says that when a server
296
287
# encounters unsatisfiable range specifiers, it
297
288
# SHOULD return a 416.
346
337
# abandon query parameters
347
338
path = urlparse.urlparse(path)[2]
348
path = posixpath.normpath(urllib.unquote(path))
339
path = posixpath.normpath(urlutils.unquote(path))
349
340
path = path.decode('utf-8')
350
341
words = path.split('/')
351
342
words = filter(None, words)