~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: 2012-02-14 17:49:28 UTC
  • mfrom: (6450.2.1 924746-http-fileobj)
  • Revision ID: pqm@pqm.ubuntu.com-20120214174928-2ybbnystvv1mhgvz
(vila) Avoid invalid range access errors on whole files when using http
 transport (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
 
24
24
from __future__ import absolute_import
25
25
 
 
26
import os
26
27
import httplib
27
28
from cStringIO import StringIO
28
29
import rfc822
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
    def __init__(self, path, infile):
 
43
        """Constructor.
 
44
 
 
45
        :param path: File url, for error reports.
 
46
 
 
47
        :param infile: File-like socket set at body start.
 
48
        """
 
49
        self._path = path
 
50
        self._file = infile
 
51
        self._pos = 0
 
52
 
 
53
    def close(self):
 
54
        """Close this file.
 
55
 
 
56
        Dummy implementation for consistency with the 'file' API.
 
57
        """
 
58
 
 
59
    def read(self, size=-1):
 
60
        """Read size bytes from the current position in the file.
 
61
 
 
62
        :param size:  The number of bytes to read.  Leave unspecified or pass
 
63
            -1 to read to EOF.
 
64
        """
 
65
        data =  self._file.read(size)
 
66
        self._pos += len(data)
 
67
        return data
 
68
 
 
69
    def seek(self, offset, whence=os.SEEK_SET):
 
70
        if whence == os.SEEK_SET:
 
71
            if offset < self._pos:
 
72
                raise AsserttionError(
 
73
                    "Can't seek backwards, pos: %s, offset: %s"
 
74
                    % (self._pos, offfset))
 
75
            to_discard = offset - self._pos
 
76
        elif whence == os.SEEK_CUR:
 
77
            to_discard = offset
 
78
        else:
 
79
            raise AssertionError("Can't seek backwards")
 
80
        if to_discard:
 
81
            # Just discard the unwanted bytes
 
82
            self.read(to_discard)
 
83
 
36
84
# A RangeFile expects the following grammar (simplified to outline the
37
85
# assumptions we rely upon).
38
86
 
39
 
# file: whole_file
40
 
#     | single_range
 
87
# file: single_range
41
88
#     | multiple_range
42
89
 
43
 
# whole_file: [content_length_header] data
44
 
 
45
90
# single_range: content_range_header data
46
91
 
47
92
# multiple_range: boundary_header boundary (content_range_header data boundary)+
48
93
 
49
 
class RangeFile(object):
 
94
class RangeFile(ResponseFile):
50
95
    """File-like object that allow access to partial available data.
51
96
 
52
97
    All accesses should happen sequentially since the acquisition occurs during
71
116
        """Constructor.
72
117
 
73
118
        :param path: File url, for error reports.
 
119
 
74
120
        :param infile: File-like socket set at body start.
75
121
        """
76
 
        self._path = path
77
 
        self._file = infile
 
122
        super(RangeFile, self).__init__(path, infile)
78
123
        self._boundary = None
79
124
        # When using multi parts response, this will be set with the headers
80
125
        # associated with the range currently read.
82
127
        # Default to the whole file of unspecified size
83
128
        self.set_range(0, -1)
84
129
 
85
 
    def close(self):
86
 
        """Close this file.
87
 
 
88
 
        Dummy implementation for consistency with the 'file' API.
89
 
        """
90
 
 
91
130
    def set_range(self, start, size):
92
131
        """Change the range mapping"""
93
132
        self._start = start
304
343
    :return: A file-like object that can seek()+read() the
305
344
             ranges indicated by the headers.
306
345
    """
307
 
    rfile = RangeFile(url, data)
308
346
    if code == 200:
309
347
        # A whole file
310
 
        size = msg.getheader('content-length', None)
311
 
        if size is None:
312
 
            size = -1
313
 
        else:
314
 
            size = int(size)
315
 
        rfile.set_range(0, size)
 
348
        rfile = ResponseFile(url, data)
316
349
    elif code == 206:
 
350
        rfile = RangeFile(url, data)
317
351
        content_type = msg.getheader('content-type', None)
318
352
        if content_type is None:
319
353
            # When there is no content-type header we treat the response as