~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Patch Queue Manager
  • Date: 2014-05-08 02:56:29 UTC
  • mfrom: (6596.1.1 bzr)
  • Revision ID: pqm@pqm.ubuntu.com-20140508025629-de62pqrditrp349y
(richard-wilbur) Jelmer: Don't pass blob to file.writelines(),
 but rather to file.write(). (Dimitri John Ledkov)

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
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.
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 <>
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)
291
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