/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 bzrlib/pack.py

  • Committer: Andrew Bennetts
  • Date: 2007-06-14 11:23:38 UTC
  • mto: (2506.2.7 container-format)
  • mto: This revision was merged to the branch mainline in revision 2572.
  • Revision ID: andrew.bennetts@canonical.com-20070614112338-6u3900u6nkag66u8
Return a callable instead of a str from read, and add more validation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Container format for Bazaar data.
 
18
 
 
19
"Containers" and "records" are described in doc/developers/container-format.txt.
 
20
"""
 
21
 
 
22
import re
 
23
 
 
24
from bzrlib import errors
 
25
 
 
26
 
 
27
FORMAT_ONE = "Bazaar pack format 1"
 
28
 
 
29
 
 
30
_whitespace_re = re.compile('[\t\n\x0b\x0c\r ]')
 
31
 
 
32
 
 
33
def _check_name(name):
 
34
    """Do some basic checking of 'name'.
 
35
    
 
36
    At the moment, this just checks that there are no whitespace characters in a
 
37
    name.
 
38
 
 
39
    :raises InvalidRecordError: if name is not valid.
 
40
    :seealso: _check_name_encoding
 
41
    """
 
42
    if _whitespace_re.search(name) is not None:
 
43
        raise errors.InvalidRecordError("%r is not a valid name." % (name,))
 
44
 
 
45
 
 
46
def _check_name_encoding(name):
 
47
    """Check that 'name' is valid UTF-8.
 
48
    
 
49
    This is separate from _check_name because UTF-8 decoding is relatively
 
50
    expensive, and we usually want to avoid it.
 
51
 
 
52
    :raises InvalidRecordError: if name is not valid UTF-8.
 
53
    """
 
54
    try:
 
55
        name.decode('utf-8')
 
56
    except UnicodeDecodeError, e:
 
57
        raise errors.InvalidRecordError(str(e))
 
58
 
 
59
 
 
60
class ContainerWriter(object):
 
61
    """A class for writing containers."""
 
62
 
 
63
    def __init__(self, write_func):
 
64
        """Constructor.
 
65
 
 
66
        :param write_func: a callable that will be called when this
 
67
            ContainerWriter needs to write some bytes.
 
68
        """
 
69
        self.write_func = write_func
 
70
 
 
71
    def begin(self):
 
72
        """Begin writing a container."""
 
73
        self.write_func(FORMAT_ONE + "\n")
 
74
 
 
75
    def end(self):
 
76
        """Finish writing a container."""
 
77
        self.write_func("E")
 
78
 
 
79
    def add_bytes_record(self, bytes, names):
 
80
        """Add a Bytes record with the given names."""
 
81
        # Kind marker
 
82
        self.write_func("B")
 
83
        # Length
 
84
        self.write_func(str(len(bytes)) + "\n")
 
85
        # Names
 
86
        for name in names:
 
87
            # Make sure we're writing valid names.  Note that we will leave a
 
88
            # half-written record if a name is bad!
 
89
            _check_name(name)
 
90
            self.write_func(name + "\n")
 
91
        # End of headers
 
92
        self.write_func("\n")
 
93
        # Finally, the contents.
 
94
        self.write_func(bytes)
 
95
 
 
96
 
 
97
class BaseReader(object):
 
98
 
 
99
    def __init__(self, reader_func):
 
100
        """Constructor.
 
101
 
 
102
        :param reader_func: a callable that takes one optional argument,
 
103
            ``size``, and returns at most that many bytes.  When the callable
 
104
            returns less than the requested number of bytes, then the end of the
 
105
            file/stream has been reached.
 
106
        """
 
107
        self.reader_func = reader_func
 
108
 
 
109
    def _read_line(self):
 
110
        """Read a line from the input stream.
 
111
 
 
112
        This is a simple but inefficient implementation that just reads one byte
 
113
        at a time.  Lines should not be very long, so this is probably
 
114
        tolerable.
 
115
 
 
116
        :returns: a line, without the trailing newline
 
117
        """
 
118
        # XXX: Have a maximum line length, to prevent malicious input from
 
119
        # consuming an unreasonable amount of resources?
 
120
        #   -- Andrew Bennetts, 2007-05-07.
 
121
        line = ''
 
122
        while not line.endswith('\n'):
 
123
            byte = self.reader_func(1)
 
124
            if byte == '':
 
125
                raise errors.UnexpectedEndOfContainerError()
 
126
            line += byte
 
127
        return line[:-1]
 
128
 
 
129
 
 
130
class ContainerReader(BaseReader):
 
131
    """A class for reading Bazaar's container format."""
 
132
 
 
133
    def iter_records(self):
 
134
        """Iterate over the container, yielding each record as it is read.
 
135
 
 
136
        Each yielded record will be a 2-tuple of (names, bytes), where names is
 
137
        a ``list`` and bytes is a ``str``.
 
138
 
 
139
        :raises ContainerError: if any sort of containter corruption is
 
140
            detected, e.g. UnknownContainerFormatError is the format of the
 
141
            container is unrecognised.
 
142
        """
 
143
        self._read_format()
 
144
        return self._iter_records()
 
145
    
 
146
    def iter_record_objects(self):
 
147
        """Iterate over the container, yielding each record as it is read.
 
148
 
 
149
        Each yielded record will be an object with ``read`` and ``validate``
 
150
        methods.
 
151
 
 
152
        :raises ContainerError: if any sort of containter corruption is
 
153
            detected, e.g. UnknownContainerFormatError is the format of the
 
154
            container is unrecognised.
 
155
        """
 
156
        self._read_format()
 
157
        return self._iter_record_objects()
 
158
    
 
159
    def _iter_records(self):
 
160
        for record in self._iter_record_objects():
 
161
            yield record.read()
 
162
 
 
163
    def _iter_record_objects(self):
 
164
        while True:
 
165
            record_kind = self.reader_func(1)
 
166
            if record_kind == 'B':
 
167
                # Bytes record.
 
168
                reader = BytesRecordReader(self.reader_func)
 
169
                yield reader
 
170
            elif record_kind == 'E':
 
171
                # End marker.  There are no more records.
 
172
                return
 
173
            elif record_kind == '':
 
174
                # End of stream encountered, but no End Marker record seen, so
 
175
                # this container is incomplete.
 
176
                raise errors.UnexpectedEndOfContainerError()
 
177
            else:
 
178
                # Unknown record type.
 
179
                raise errors.UnknownRecordTypeError(record_kind)
 
180
 
 
181
    def _read_format(self):
 
182
        format = self._read_line()
 
183
        if format != FORMAT_ONE:
 
184
            raise errors.UnknownContainerFormatError(format)
 
185
 
 
186
    def validate(self):
 
187
        """Validate this container and its records.
 
188
 
 
189
        You can either validate or iter_records, you can't do both.
 
190
 
 
191
        :raises ContainerError: if something is invalid.
 
192
        """
 
193
        all_names = set()
 
194
        for record_names, read_bytes in self.iter_records():
 
195
            read_bytes(None)
 
196
            for name in record_names:
 
197
                _check_name_encoding(name)
 
198
                # Check that the name is unique.  Note that Python will refuse
 
199
                # to decode non-shortest forms of UTF-8 encoding, so there is no
 
200
                # risk that the same unicode string has been encoded two
 
201
                # different ways.
 
202
                if name in all_names:
 
203
                    raise errors.DuplicateRecordNameError(name)
 
204
                all_names.add(name)
 
205
        excess_bytes = self.reader_func(1)
 
206
        if excess_bytes != '':
 
207
            raise errors.ContainerHasExcessDataError(excess_bytes)
 
208
 
 
209
 
 
210
class BytesRecordReader(BaseReader):
 
211
 
 
212
    def read(self):
 
213
        """Read this record.
 
214
 
 
215
        You can either validate or read, you can't do both.
 
216
 
 
217
        :returns: A tuple of (names, callable).  The callable can be called
 
218
            repeatedly to obtain the bytes for the record, with a max_length
 
219
            argument.  If max_length is None, returns all the bytes.  Because
 
220
            records can be arbitrarily large, using None is not recommended
 
221
            unless you have reason to believe the content will fit in memory.
 
222
        """
 
223
        # Read the content length.
 
224
        length_line = self._read_line()
 
225
        try:
 
226
            length = int(length_line)
 
227
        except ValueError:
 
228
            raise errors.InvalidRecordError(
 
229
                "%r is not a valid length." % (length_line,))
 
230
        
 
231
        # Read the list of names.
 
232
        names = []
 
233
        while True:
 
234
            name = self._read_line()
 
235
            if name == '':
 
236
                break
 
237
            _check_name(name)
 
238
            names.append(name)
 
239
 
 
240
        self._remaining_length = length
 
241
        return names, self._content_reader
 
242
 
 
243
    def _content_reader(self, max_length):
 
244
        if max_length is None:
 
245
            length_to_read = self._remaining_length
 
246
        else:
 
247
            length_to_read = min(max_length, self._remaining_length)
 
248
        self._remaining_length -= length_to_read
 
249
        bytes = self.reader_func(length_to_read)
 
250
        if len(bytes) != length_to_read:
 
251
            raise errors.UnexpectedEndOfContainerError()
 
252
        return bytes
 
253
 
 
254
    def validate(self):
 
255
        """Validate this record.
 
256
 
 
257
        You can either validate or read, you can't do both.
 
258
 
 
259
        :raises ContainerError: if this record is invalid.
 
260
        """
 
261
        names, read_bytes = self.read()
 
262
        for name in names:
 
263
            _check_name_encoding(name)
 
264
        read_bytes(None)
 
265