1
# Copyright (C) 2006, 2007 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
24
from __future__ import absolute_import
26
from cStringIO import StringIO
34
from ...sixish import (
39
class ResponseFile(object):
40
"""A wrapper around the http socket containing the result of a GET request.
42
Only read() and seek() (forward) are supported.
45
def __init__(self, path, infile):
48
:param path: File url, for error reports.
50
:param infile: File-like socket set at body start.
59
Dummy implementation for consistency with the 'file' API.
62
def read(self, size=-1):
63
"""Read size bytes from the current position in the file.
65
:param size: The number of bytes to read. Leave unspecified or pass
68
data = self._file.read(size)
69
self._pos += len(data)
73
data = self._file.readline()
74
self._pos += len(data)
79
line = self.readline()
87
def seek(self, offset, whence=os.SEEK_SET):
88
if whence == os.SEEK_SET:
89
if offset < self._pos:
91
"Can't seek backwards, pos: %s, offset: %s"
92
% (self._pos, offset))
93
to_discard = offset - self._pos
94
elif whence == os.SEEK_CUR:
97
raise AssertionError("Can't seek backwards")
99
# Just discard the unwanted bytes
100
self.read(to_discard)
36
102
# A RangeFile expects the following grammar (simplified to outline the
37
103
# assumptions we rely upon).
41
106
# | multiple_range
43
# whole_file: [content_length_header] data
45
108
# single_range: content_range_header data
47
110
# multiple_range: boundary_header boundary (content_range_header data boundary)+
49
class RangeFile(object):
112
class RangeFile(ResponseFile):
50
113
"""File-like object that allow access to partial available data.
52
115
All accesses should happen sequentially since the acquisition occurs during
61
124
# in _checked_read() below, we may have to discard several MB in the worst
62
125
# case. To avoid buffering that much, we read and discard by chunks
63
# instead. The underlying file is either a socket or a StringIO, so reading
126
# instead. The underlying file is either a socket or a BytesIO, so reading
64
127
# 8k chunks should be fine.
65
128
_discarded_buf_size = 8192
73
136
:param path: File url, for error reports.
74
138
:param infile: File-like socket set at body start.
140
super(RangeFile, self).__init__(path, infile)
78
141
self._boundary = None
79
142
# When using multi parts response, this will be set with the headers
80
143
# associated with the range currently read.
109
172
# To be on the safe side we allow it before any boundary line
110
173
boundary_line = self._file.readline()
175
if boundary_line == '':
176
# A timeout in the proxy server caused the response to end early.
177
# See launchpad bug 198646.
178
raise errors.HttpBoundaryMissing(
112
182
if boundary_line != '--' + self._boundary + '\r\n':
113
183
# rfc822.unquote() incorrectly unquotes strings enclosed in <>
114
184
# IIS 6 and 7 incorrectly wrap boundary strings in <>
221
291
% (size, self._start, self._size))
223
293
# read data from file
226
296
if self._size > 0:
227
297
# Don't read past the range definition
228
298
limited = self._start + self._size - self._pos
230
300
limited = min(limited, size)
231
osutils.pumpfile(self._file, buffer, limited, self._max_read_size)
232
data = buffer.getvalue()
301
osutils.pumpfile(self._file, buf, limited, self._max_read_size)
302
data = buf.getvalue()
234
304
# Update _pos respecting the data effectively read
235
305
self._pos += len(data)
291
361
:return: A file-like object that can seek()+read() the
292
362
ranges indicated by the headers.
294
rfile = RangeFile(url, data)
297
size = msg.getheader('content-length', None)
302
rfile.set_range(0, size)
366
rfile = ResponseFile(url, data)
303
367
elif code == 206:
368
rfile = RangeFile(url, data)
304
369
content_type = msg.getheader('content-type', None)
305
370
if content_type is None:
306
371
# When there is no content-type header we treat the response as