/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: Jelmer Vernooij
  • Date: 2017-07-20 00:00:04 UTC
  • mfrom: (6690.5.2 bundle-guess)
  • Revision ID: jelmer@jelmer.uk-20170720000004-wlknc5gthdk3tokn
Merge lp:~jelmer/brz/bundle-guess.

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
21
21
responses.
22
22
"""
23
23
 
 
24
from __future__ import absolute_import
24
25
 
 
26
import os
25
27
import httplib
26
 
from cStringIO import StringIO
27
28
import rfc822
28
29
 
29
 
from bzrlib import (
 
30
from ... import (
30
31
    errors,
31
 
    trace,
32
32
    osutils,
33
33
    )
34
 
 
 
34
from ...sixish import (
 
35
    BytesIO,
 
36
    )
 
37
 
 
38
 
 
39
class ResponseFile(object):
 
40
    """A wrapper around the http socket containing the result of a GET request.
 
41
 
 
42
    Only read() and seek() (forward) are supported.
 
43
 
 
44
    """
 
45
    def __init__(self, path, infile):
 
46
        """Constructor.
 
47
 
 
48
        :param path: File url, for error reports.
 
49
 
 
50
        :param infile: File-like socket set at body start.
 
51
        """
 
52
        self._path = path
 
53
        self._file = infile
 
54
        self._pos = 0
 
55
 
 
56
    def close(self):
 
57
        """Close this file.
 
58
 
 
59
        Dummy implementation for consistency with the 'file' API.
 
60
        """
 
61
 
 
62
    def read(self, size=-1):
 
63
        """Read size bytes from the current position in the file.
 
64
 
 
65
        :param size:  The number of bytes to read.  Leave unspecified or pass
 
66
            -1 to read to EOF.
 
67
        """
 
68
        data =  self._file.read(size)
 
69
        self._pos += len(data)
 
70
        return data
 
71
 
 
72
    def readline(self):
 
73
        data = self._file.readline()
 
74
        self._pos += len(data)
 
75
        return data
 
76
 
 
77
    def __iter__(self):
 
78
        while True:
 
79
            line = self.readline()
 
80
            if not line:
 
81
                return
 
82
            yield line
 
83
 
 
84
    def tell(self):
 
85
        return self._pos
 
86
 
 
87
    def seek(self, offset, whence=os.SEEK_SET):
 
88
        if whence == os.SEEK_SET:
 
89
            if offset < self._pos:
 
90
                raise AssertionError(
 
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:
 
95
            to_discard = offset
 
96
        else:
 
97
            raise AssertionError("Can't seek backwards")
 
98
        if to_discard:
 
99
            # Just discard the unwanted bytes
 
100
            self.read(to_discard)
35
101
 
36
102
# A RangeFile expects the following grammar (simplified to outline the
37
103
# assumptions we rely upon).
38
104
 
39
 
# file: whole_file
40
 
#     | single_range
 
105
# file: single_range
41
106
#     | multiple_range
42
107
 
43
 
# whole_file: [content_length_header] data
44
 
 
45
108
# single_range: content_range_header data
46
109
 
47
110
# multiple_range: boundary_header boundary (content_range_header data boundary)+
48
111
 
49
 
class RangeFile(object):
 
112
class RangeFile(ResponseFile):
50
113
    """File-like object that allow access to partial available data.
51
114
 
52
115
    All accesses should happen sequentially since the acquisition occurs during
60
123
 
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
66
129
 
71
134
        """Constructor.
72
135
 
73
136
        :param path: File url, for error reports.
 
137
 
74
138
        :param infile: File-like socket set at body start.
75
139
        """
76
 
        self._path = path
77
 
        self._file = infile
 
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()
111
174
 
 
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(
 
179
                self._path,
 
180
                self._boundary)
 
181
 
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))
222
292
 
223
293
        # read data from file
224
 
        buffer = StringIO()
 
294
        buf = BytesIO()
225
295
        limited = size
226
296
        if self._size > 0:
227
297
            # Don't read past the range definition
228
298
            limited = self._start + self._size - self._pos
229
299
            if size >= 0:
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()
233
303
 
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.
293
363
    """
294
 
    rfile = RangeFile(url, data)
295
364
    if code == 200:
296
365
        # 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)
 
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