59
62
def makefile(self, mode='r', bufsize=None):
60
63
return self.readfile
62
66
class FakeHTTPConnection(_urllib2_wrappers.HTTPConnection):
64
68
def __init__(self, sock):
221
225
self.assertEquals('', f.read(0))
222
226
self.assertEquals('', f.read(1))
224
229
class TestRangeFileSizeKnown(tests.TestCase, TestRangeFileMixin):
225
230
"""Test a RangeFile for a whole file whose size is known."""
249
254
f._pos = 0 # Force an invalid pos
250
255
self.assertRaises(errors.InvalidRange, f.read, 2)
252
258
class TestRangeFileMultipleRanges(tests.TestCase, TestRangeFileMixin):
253
259
"""Test a RangeFile for multiple ranges.
262
268
fact) in real uses but may lead to hard to track bugs.
271
# The following is used to represent the boundary paramter defined
272
# in HTTP response headers and the boundary lines that separate
275
boundary = "separation"
266
278
super(TestRangeFileMultipleRanges, self).setUp()
268
boundary = 'separation'
280
boundary = self.boundary
271
283
self.first_range_start = 25
277
289
content += self._multipart_byterange(part, start, boundary,
280
content += self._boundary_line(boundary)
292
content += self._boundary_line()
282
294
self._file = response.RangeFile('Multiple_ranges_file',
283
295
StringIO(content))
296
self.set_file_boundary()
298
def _boundary_line(self):
299
"""Helper to build the formatted boundary line."""
300
return '--' + self.boundary + '\r\n'
302
def set_file_boundary(self):
284
303
# Ranges are set by decoding the range headers, the RangeFile user is
285
304
# supposed to call the following before using seek or read since it
286
305
# requires knowing the *response* headers (in that case the boundary
287
306
# which is part of the Content-Type header).
288
self._file.set_boundary(boundary)
290
def _boundary_line(self, boundary):
291
"""Helper to build the formatted boundary line."""
292
return '--' + boundary + '\r\n'
307
self._file.set_boundary(self.boundary)
294
309
def _multipart_byterange(self, data, offset, boundary, file_size='*'):
295
310
"""Encode a part of a file as a multipart/byterange MIME type.
307
322
:return: a string containing the data encoded as it will appear in the
308
323
HTTP response body.
310
bline = self._boundary_line(boundary)
325
bline = self._boundary_line()
311
326
# Each range begins with a boundary line
313
328
# A range is described by a set of headers, but only 'Content-Range' is
391
406
self.assertRaises(errors.InvalidHttpResponse, f.read, 1)
409
class TestRangeFileMultipleRangesQuotedBoundaries(TestRangeFileMultipleRanges):
410
"""Perform the same tests as TestRangeFileMultipleRanges, but uses
411
an angle-bracket quoted boundary string like IIS 6.0 and 7.0
412
(but not IIS 5, which breaks the RFC in a different way
413
by using square brackets, not angle brackets)
415
This reveals a bug caused by
417
- The bad implementation of RFC 822 unquoting in Python (angles are not
418
quotes), coupled with
420
- The bad implementation of RFC 2046 in IIS (angles are not permitted chars
424
# The boundary as it appears in boundary lines
425
# IIS 6 and 7 use this value
426
_boundary_trimmed = "q1w2e3r4t5y6u7i8o9p0zaxscdvfbgnhmjklkl"
427
boundary = '<' + _boundary_trimmed + '>'
429
def set_file_boundary(self):
430
# Emulate broken rfc822.unquote() here by removing angles
431
self._file.set_boundary(self._boundary_trimmed)
394
434
class TestRangeFileVarious(tests.TestCase):
395
435
"""Tests RangeFile aspects not covered elsewhere."""
753
793
out.read() # Read the whole range
754
794
# Fail to find the boundary line
755
795
self.assertRaises(errors.InvalidHttpResponse, out.seek, 1, 1)
798
class TestRangeFileSizeReadLimited(tests.TestCase):
799
"""Test RangeFile _max_read_size functionality which limits the size of
800
read blocks to prevent MemoryError messages in socket.recv.
804
# create a test datablock larger than _max_read_size.
805
chunk_size = response.RangeFile._max_read_size
806
test_pattern = '0123456789ABCDEF'
807
self.test_data = test_pattern * (3 * chunk_size / len(test_pattern))
808
self.test_data_len = len(self.test_data)
810
def test_max_read_size(self):
811
"""Read data in blocks and verify that the reads are not larger than
812
the maximum read size.
814
# retrieve data in large blocks from response.RangeFile object
815
mock_read_file = FakeReadFile(self.test_data)
816
range_file = response.RangeFile('test_max_read_size', mock_read_file)
817
response_data = range_file.read(self.test_data_len)
819
# verify read size was equal to the maximum read size
820
self.assertTrue(mock_read_file.get_max_read_size() > 0)
821
self.assertEqual(mock_read_file.get_max_read_size(),
822
response.RangeFile._max_read_size)
823
self.assertEqual(mock_read_file.get_read_count(), 3)
825
# report error if the data wasn't equal (we only report the size due
826
# to the length of the data)
827
if response_data != self.test_data:
828
message = "Data not equal. Expected %d bytes, received %d."
829
self.fail(message % (len(response_data), self.test_data_len))