~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-05-06 11:40:10 UTC
  • mfrom: (3400.1.3 trivial)
  • Revision ID: pqm@pqm.ubuntu.com-20080506114010-jwclr2qtiekvawjg
Remove erroneous creation of branch-name file in cmd_branch

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
 
from cStringIO import StringIO
29
 
import rfc822
30
26
 
31
27
from bzrlib import (
32
28
    errors,
33
 
    osutils,
 
29
    trace,
34
30
    )
35
31
 
36
32
 
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
33
# A RangeFile expects the following grammar (simplified to outline the
101
34
# assumptions we rely upon).
102
35
 
103
 
# file: single_range
 
36
# file: whole_file
 
37
#     | single_range
104
38
#     | multiple_range
105
39
 
 
40
# whole_file: [content_length_header] data
 
41
 
106
42
# single_range: content_range_header data
107
43
 
108
44
# multiple_range: boundary_header boundary (content_range_header data boundary)+
109
45
 
110
 
class RangeFile(ResponseFile):
 
46
class RangeFile(object):
111
47
    """File-like object that allow access to partial available data.
112
48
 
113
49
    All accesses should happen sequentially since the acquisition occurs during
125
61
    # 8k chunks should be fine.
126
62
    _discarded_buf_size = 8192
127
63
 
128
 
    # maximum size of read requests -- used to avoid MemoryError issues in recv
129
 
    _max_read_size = 512 * 1024
130
 
 
131
64
    def __init__(self, path, infile):
132
65
        """Constructor.
133
66
 
134
67
        :param path: File url, for error reports.
135
 
 
136
68
        :param infile: File-like socket set at body start.
137
69
        """
138
 
        super(RangeFile, self).__init__(path, infile)
 
70
        self._path = path
 
71
        self._file = infile
139
72
        self._boundary = None
140
73
        # When using multi parts response, this will be set with the headers
141
74
        # associated with the range currently read.
152
85
 
153
86
    def set_boundary(self, boundary):
154
87
        """Define the boundary used in a multi parts message.
155
 
 
 
88
        
156
89
        The file should be at the beginning of the body, the first range
157
90
        definition is read and taken into account.
158
91
        """
169
102
            # string entity.
170
103
            # To be on the safe side we allow it before any boundary line
171
104
            boundary_line = self._file.readline()
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
 
 
180
105
        if boundary_line != '--' + self._boundary + '\r\n':
181
 
            # rfc822.unquote() incorrectly unquotes strings enclosed in <>
182
 
            # IIS 6 and 7 incorrectly wrap boundary strings in <>
183
 
            # together they make a beautiful bug, which we will be gracious
184
 
            # about here
185
 
            if (self._unquote_boundary(boundary_line) !=
186
 
                '--' + self._boundary + '\r\n'):
187
 
                raise errors.InvalidHttpResponse(
188
 
                    self._path,
189
 
                    "Expected a boundary (%s) line, got '%s'"
190
 
                    % (self._boundary, boundary_line))
191
 
 
192
 
    def _unquote_boundary(self, b):
193
 
        return b[:2] + rfc822.unquote(b[2:-2]) + b[-2:]
 
106
            raise errors.InvalidHttpResponse(
 
107
                self._path,
 
108
                "Expected a boundary (%s) line, got '%s'" % (self._boundary,
 
109
                                                             boundary_line))
194
110
 
195
111
    def read_range_definition(self):
196
112
        """Read a new range definition in a multi parts message.
266
182
        client to clean the socket if we leave bytes unread. This may occur for
267
183
        the final boundary line of a multipart response or for any range
268
184
        request not entirely consumed by the client (due to offset coalescing)
269
 
 
270
 
        :param size:  The number of bytes to read.  Leave unspecified or pass
271
 
            -1 to read to EOF.
272
185
        """
273
186
        if (self._size > 0
274
187
            and self._pos == self._start + self._size):
288
201
                    "Can't read %s bytes across range (%s, %s)"
289
202
                    % (size, self._start, self._size))
290
203
 
291
 
        # read data from file
292
 
        buffer = StringIO()
293
 
        limited = size
294
204
        if self._size > 0:
295
205
            # Don't read past the range definition
296
206
            limited = self._start + self._size - self._pos
297
207
            if size >= 0:
298
208
                limited = min(limited, size)
299
 
        osutils.pumpfile(self._file, buffer, limited, self._max_read_size)
300
 
        data = buffer.getvalue()
301
 
 
 
209
            data = self._file.read(limited)
 
210
        else:
 
211
            # Size of file unknown, the user may have specified a size or not,
 
212
            # we delegate that to the filesocket object (-1 means read until
 
213
            # EOF)
 
214
            data = self._file.read(size)
302
215
        # Update _pos respecting the data effectively read
303
216
        self._pos += len(data)
304
217
        return data
356
269
    :param msg: An HTTPMessage containing the headers for the response
357
270
    :param data: A file-like object that can be read() to get the
358
271
                 requested data
359
 
    :return: A file-like object that can seek()+read() the
 
272
    :return: A file-like object that can seek()+read() the 
360
273
             ranges indicated by the headers.
361
274
    """
 
275
    rfile = RangeFile(url, data)
362
276
    if code == 200:
363
277
        # A whole file
364
 
        rfile = ResponseFile(url, data)
 
278
        size = msg.getheader('content-length', None)
 
279
        if size is None:
 
280
            size = -1
 
281
        else:
 
282
            size = int(size)
 
283
        rfile.set_range(0, size)
365
284
    elif code == 206:
366
 
        rfile = RangeFile(url, data)
367
285
        content_type = msg.getheader('content-type', None)
368
286
        if content_type is None:
369
287
            # When there is no content-type header we treat the response as