~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Handlers for HTTP Responses.
18
18
 
21
21
responses.
22
22
"""
23
23
 
24
 
from __future__ import absolute_import
25
24
 
26
 
import os
27
25
import httplib
28
26
from cStringIO import StringIO
29
27
import rfc822
30
28
 
31
29
from bzrlib import (
32
30
    errors,
 
31
    trace,
33
32
    osutils,
34
33
    )
35
34
 
36
35
 
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
 
 
100
36
# A RangeFile expects the following grammar (simplified to outline the
101
37
# assumptions we rely upon).
102
38
 
103
 
# file: single_range
 
39
# file: whole_file
 
40
#     | single_range
104
41
#     | multiple_range
105
42
 
 
43
# whole_file: [content_length_header] data
 
44
 
106
45
# single_range: content_range_header data
107
46
 
108
47
# multiple_range: boundary_header boundary (content_range_header data boundary)+
109
48
 
110
 
class RangeFile(ResponseFile):
 
49
class RangeFile(object):
111
50
    """File-like object that allow access to partial available data.
112
51
 
113
52
    All accesses should happen sequentially since the acquisition occurs during
132
71
        """Constructor.
133
72
 
134
73
        :param path: File url, for error reports.
135
 
 
136
74
        :param infile: File-like socket set at body start.
137
75
        """
138
 
        super(RangeFile, self).__init__(path, infile)
 
76
        self._path = path
 
77
        self._file = infile
139
78
        self._boundary = None
140
79
        # When using multi parts response, this will be set with the headers
141
80
        # associated with the range currently read.
152
91
 
153
92
    def set_boundary(self, boundary):
154
93
        """Define the boundary used in a multi parts message.
155
 
 
 
94
        
156
95
        The file should be at the beginning of the body, the first range
157
96
        definition is read and taken into account.
158
97
        """
170
109
            # To be on the safe side we allow it before any boundary line
171
110
            boundary_line = self._file.readline()
172
111
 
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
 
 
180
112
        if boundary_line != '--' + self._boundary + '\r\n':
181
113
            # rfc822.unquote() incorrectly unquotes strings enclosed in <>
182
114
            # IIS 6 and 7 incorrectly wrap boundary strings in <>
183
115
            # together they make a beautiful bug, which we will be gracious
184
116
            # about here
185
 
            if (self._unquote_boundary(boundary_line) !=
 
117
            if (self._unquote_boundary(boundary_line) != 
186
118
                '--' + self._boundary + '\r\n'):
187
119
                raise errors.InvalidHttpResponse(
188
120
                    self._path,
289
221
                    % (size, self._start, self._size))
290
222
 
291
223
        # read data from file
292
 
        buf = StringIO()
 
224
        buffer = StringIO()
293
225
        limited = size
294
226
        if self._size > 0:
295
227
            # Don't read past the range definition
296
228
            limited = self._start + self._size - self._pos
297
229
            if size >= 0:
298
230
                limited = min(limited, size)
299
 
        osutils.pumpfile(self._file, buf, limited, self._max_read_size)
300
 
        data = buf.getvalue()
 
231
        osutils.pumpfile(self._file, buffer, limited, self._max_read_size)
 
232
        data = buffer.getvalue()
301
233
 
302
234
        # Update _pos respecting the data effectively read
303
235
        self._pos += len(data)
356
288
    :param msg: An HTTPMessage containing the headers for the response
357
289
    :param data: A file-like object that can be read() to get the
358
290
                 requested data
359
 
    :return: A file-like object that can seek()+read() the
 
291
    :return: A file-like object that can seek()+read() the 
360
292
             ranges indicated by the headers.
361
293
    """
 
294
    rfile = RangeFile(url, data)
362
295
    if code == 200:
363
296
        # A whole file
364
 
        rfile = ResponseFile(url, data)
 
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)
365
303
    elif code == 206:
366
 
        rfile = RangeFile(url, data)
367
304
        content_type = msg.getheader('content-type', None)
368
305
        if content_type is None:
369
306
            # When there is no content-type header we treat the response as