~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/response.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
2
#
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
12
12
#
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
16
16
 
17
17
"""Handlers for HTTP Responses.
18
18
 
21
21
responses.
22
22
"""
23
23
 
 
24
from __future__ import absolute_import
24
25
 
 
26
import os
25
27
import httplib
26
28
from cStringIO import StringIO
27
29
import rfc822
28
30
 
29
31
from bzrlib import (
30
32
    errors,
31
 
    trace,
32
33
    osutils,
33
34
    )
34
35
 
35
36
 
 
37
class ResponseFile(object):
 
38
    """A wrapper around the http socket containing the result of a GET request.
 
39
 
 
40
    Only read() and seek() (forward) are supported.
 
41
 
 
42
    """
 
43
    def __init__(self, path, infile):
 
44
        """Constructor.
 
45
 
 
46
        :param path: File url, for error reports.
 
47
 
 
48
        :param infile: File-like socket set at body start.
 
49
        """
 
50
        self._path = path
 
51
        self._file = infile
 
52
        self._pos = 0
 
53
 
 
54
    def close(self):
 
55
        """Close this file.
 
56
 
 
57
        Dummy implementation for consistency with the 'file' API.
 
58
        """
 
59
 
 
60
    def read(self, size=-1):
 
61
        """Read size bytes from the current position in the file.
 
62
 
 
63
        :param size:  The number of bytes to read.  Leave unspecified or pass
 
64
            -1 to read to EOF.
 
65
        """
 
66
        data =  self._file.read(size)
 
67
        self._pos += len(data)
 
68
        return data
 
69
 
 
70
    def readline(self):
 
71
        data = self._file.readline()
 
72
        self._pos += len(data)
 
73
        return data
 
74
 
 
75
    def __iter__(self):
 
76
        while True:
 
77
            line = self.readline()
 
78
            if not line:
 
79
                return
 
80
            yield line
 
81
 
 
82
    def tell(self):
 
83
        return self._pos
 
84
 
 
85
    def seek(self, offset, whence=os.SEEK_SET):
 
86
        if whence == os.SEEK_SET:
 
87
            if offset < self._pos:
 
88
                raise AssertionError(
 
89
                    "Can't seek backwards, pos: %s, offset: %s"
 
90
                    % (self._pos, offset))
 
91
            to_discard = offset - self._pos
 
92
        elif whence == os.SEEK_CUR:
 
93
            to_discard = offset
 
94
        else:
 
95
            raise AssertionError("Can't seek backwards")
 
96
        if to_discard:
 
97
            # Just discard the unwanted bytes
 
98
            self.read(to_discard)
 
99
 
36
100
# A RangeFile expects the following grammar (simplified to outline the
37
101
# assumptions we rely upon).
38
102
 
39
 
# file: whole_file
40
 
#     | single_range
 
103
# file: single_range
41
104
#     | multiple_range
42
105
 
43
 
# whole_file: [content_length_header] data
44
 
 
45
106
# single_range: content_range_header data
46
107
 
47
108
# multiple_range: boundary_header boundary (content_range_header data boundary)+
48
109
 
49
 
class RangeFile(object):
 
110
class RangeFile(ResponseFile):
50
111
    """File-like object that allow access to partial available data.
51
112
 
52
113
    All accesses should happen sequentially since the acquisition occurs during
71
132
        """Constructor.
72
133
 
73
134
        :param path: File url, for error reports.
 
135
 
74
136
        :param infile: File-like socket set at body start.
75
137
        """
76
 
        self._path = path
77
 
        self._file = infile
 
138
        super(RangeFile, self).__init__(path, infile)
78
139
        self._boundary = None
79
140
        # When using multi parts response, this will be set with the headers
80
141
        # associated with the range currently read.
91
152
 
92
153
    def set_boundary(self, boundary):
93
154
        """Define the boundary used in a multi parts message.
94
 
        
 
155
 
95
156
        The file should be at the beginning of the body, the first range
96
157
        definition is read and taken into account.
97
158
        """
109
170
            # To be on the safe side we allow it before any boundary line
110
171
            boundary_line = self._file.readline()
111
172
 
 
173
        if boundary_line == '':
 
174
            # A timeout in the proxy server caused the response to end early.
 
175
            # See launchpad bug 198646.
 
176
            raise errors.HttpBoundaryMissing(
 
177
                self._path,
 
178
                self._boundary)
 
179
 
112
180
        if boundary_line != '--' + self._boundary + '\r\n':
113
181
            # rfc822.unquote() incorrectly unquotes strings enclosed in <>
114
182
            # IIS 6 and 7 incorrectly wrap boundary strings in <>
115
183
            # together they make a beautiful bug, which we will be gracious
116
184
            # about here
117
 
            if (self._unquote_boundary(boundary_line) != 
 
185
            if (self._unquote_boundary(boundary_line) !=
118
186
                '--' + self._boundary + '\r\n'):
119
187
                raise errors.InvalidHttpResponse(
120
188
                    self._path,
221
289
                    % (size, self._start, self._size))
222
290
 
223
291
        # read data from file
224
 
        buffer = StringIO()
 
292
        buf = StringIO()
225
293
        limited = size
226
294
        if self._size > 0:
227
295
            # Don't read past the range definition
228
296
            limited = self._start + self._size - self._pos
229
297
            if size >= 0:
230
298
                limited = min(limited, size)
231
 
        osutils.pumpfile(self._file, buffer, limited, self._max_read_size)
232
 
        data = buffer.getvalue()
 
299
        osutils.pumpfile(self._file, buf, limited, self._max_read_size)
 
300
        data = buf.getvalue()
233
301
 
234
302
        # Update _pos respecting the data effectively read
235
303
        self._pos += len(data)
288
356
    :param msg: An HTTPMessage containing the headers for the response
289
357
    :param data: A file-like object that can be read() to get the
290
358
                 requested data
291
 
    :return: A file-like object that can seek()+read() the 
 
359
    :return: A file-like object that can seek()+read() the
292
360
             ranges indicated by the headers.
293
361
    """
294
 
    rfile = RangeFile(url, data)
295
362
    if code == 200:
296
363
        # A whole file
297
 
        size = msg.getheader('content-length', None)
298
 
        if size is None:
299
 
            size = -1
300
 
        else:
301
 
            size = int(size)
302
 
        rfile.set_range(0, size)
 
364
        rfile = ResponseFile(url, data)
303
365
    elif code == 206:
 
366
        rfile = RangeFile(url, data)
304
367
        content_type = msg.getheader('content-type', None)
305
368
        if content_type is None:
306
369
            # When there is no content-type header we treat the response as