1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1
# Copyright (C) 2006-2010 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
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""Tests from HTTP response parsing.
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):
92
96
# Override the thresold to force the warning emission
93
97
conn._range_warning_thresold = 6 # There are 7 bytes pending
94
98
conn.cleanup_pipe()
95
self.assertContainsRe(self._get_log(keep_log_file=True),
96
'Got a 200 response when asking')
99
self.assertContainsRe(self.get_log(), 'Got a 200 response when asking')
99
102
class TestRangeFileMixin(object):
221
224
self.assertEquals('', f.read(0))
222
225
self.assertEquals('', f.read(1))
224
228
class TestRangeFileSizeKnown(tests.TestCase, TestRangeFileMixin):
225
229
"""Test a RangeFile for a whole file whose size is known."""
249
253
f._pos = 0 # Force an invalid pos
250
254
self.assertRaises(errors.InvalidRange, f.read, 2)
252
257
class TestRangeFileMultipleRanges(tests.TestCase, TestRangeFileMixin):
253
258
"""Test a RangeFile for multiple ranges.
262
267
fact) in real uses but may lead to hard to track bugs.
270
# The following is used to represent the boundary paramter defined
271
# in HTTP response headers and the boundary lines that separate
274
boundary = "separation"
266
277
super(TestRangeFileMultipleRanges, self).setUp()
268
boundary = 'separation'
279
boundary = self.boundary
271
282
self.first_range_start = 25
277
288
content += self._multipart_byterange(part, start, boundary,
280
content += self._boundary_line(boundary)
291
content += self._boundary_line()
282
293
self._file = response.RangeFile('Multiple_ranges_file',
283
294
StringIO(content))
295
self.set_file_boundary()
297
def _boundary_line(self):
298
"""Helper to build the formatted boundary line."""
299
return '--' + self.boundary + '\r\n'
301
def set_file_boundary(self):
284
302
# Ranges are set by decoding the range headers, the RangeFile user is
285
303
# supposed to call the following before using seek or read since it
286
304
# requires knowing the *response* headers (in that case the boundary
287
305
# 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'
306
self._file.set_boundary(self.boundary)
294
308
def _multipart_byterange(self, data, offset, boundary, file_size='*'):
295
309
"""Encode a part of a file as a multipart/byterange MIME type.
307
321
:return: a string containing the data encoded as it will appear in the
308
322
HTTP response body.
310
bline = self._boundary_line(boundary)
324
bline = self._boundary_line()
311
325
# Each range begins with a boundary line
313
327
# A range is described by a set of headers, but only 'Content-Range' is
391
405
self.assertRaises(errors.InvalidHttpResponse, f.read, 1)
408
class TestRangeFileMultipleRangesQuotedBoundaries(TestRangeFileMultipleRanges):
409
"""Perform the same tests as TestRangeFileMultipleRanges, but uses
410
an angle-bracket quoted boundary string like IIS 6.0 and 7.0
411
(but not IIS 5, which breaks the RFC in a different way
412
by using square brackets, not angle brackets)
414
This reveals a bug caused by
416
- The bad implementation of RFC 822 unquoting in Python (angles are not
417
quotes), coupled with
419
- The bad implementation of RFC 2046 in IIS (angles are not permitted chars
423
# The boundary as it appears in boundary lines
424
# IIS 6 and 7 use this value
425
_boundary_trimmed = "q1w2e3r4t5y6u7i8o9p0zaxscdvfbgnhmjklkl"
426
boundary = '<' + _boundary_trimmed + '>'
428
def set_file_boundary(self):
429
# Emulate broken rfc822.unquote() here by removing angles
430
self._file.set_boundary(self._boundary_trimmed)
394
433
class TestRangeFileVarious(tests.TestCase):
395
434
"""Tests RangeFile aspects not covered elsewhere."""
753
792
out.read() # Read the whole range
754
793
# Fail to find the boundary line
755
794
self.assertRaises(errors.InvalidHttpResponse, out.seek, 1, 1)
797
class TestRangeFileSizeReadLimited(tests.TestCase):
798
"""Test RangeFile _max_read_size functionality which limits the size of
799
read blocks to prevent MemoryError messages in socket.recv.
803
tests.TestCase.setUp(self)
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))