~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to python/simplejson-2.1.1/simplejson/decoder.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""Implementation of JSONDecoder
2
 
"""
3
 
import re
4
 
import sys
5
 
import struct
6
 
 
7
 
from simplejson.scanner import make_scanner
8
 
def _import_c_scanstring():
9
 
    try:
10
 
        from simplejson._speedups import scanstring
11
 
        return scanstring
12
 
    except ImportError:
13
 
        return None
14
 
c_scanstring = _import_c_scanstring()
15
 
 
16
 
__all__ = ['JSONDecoder']
17
 
 
18
 
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
19
 
 
20
 
def _floatconstants():
21
 
    _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
22
 
    # The struct module in Python 2.4 would get frexp() out of range here
23
 
    # when an endian is specified in the format string. Fixed in Python 2.5+
24
 
    if sys.byteorder != 'big':
25
 
        _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
26
 
    nan, inf = struct.unpack('dd', _BYTES)
27
 
    return nan, inf, -inf
28
 
 
29
 
NaN, PosInf, NegInf = _floatconstants()
30
 
 
31
 
 
32
 
class JSONDecodeError(ValueError):
33
 
    """Subclass of ValueError with the following additional properties:
34
 
    
35
 
    msg: The unformatted error message
36
 
    doc: The JSON document being parsed
37
 
    pos: The start index of doc where parsing failed
38
 
    end: The end index of doc where parsing failed (may be None)
39
 
    lineno: The line corresponding to pos
40
 
    colno: The column corresponding to pos
41
 
    endlineno: The line corresponding to end (may be None)
42
 
    endcolno: The column corresponding to end (may be None)
43
 
    
44
 
    """
45
 
    def __init__(self, msg, doc, pos, end=None):
46
 
        ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
47
 
        self.msg = msg
48
 
        self.doc = doc
49
 
        self.pos = pos
50
 
        self.end = end
51
 
        self.lineno, self.colno = linecol(doc, pos)
52
 
        if end is not None:
53
 
            self.endlineno, self.endcolno = linecol(doc, pos)
54
 
        else:
55
 
            self.endlineno, self.endcolno = None, None
56
 
 
57
 
 
58
 
def linecol(doc, pos):
59
 
    lineno = doc.count('\n', 0, pos) + 1
60
 
    if lineno == 1:
61
 
        colno = pos
62
 
    else:
63
 
        colno = pos - doc.rindex('\n', 0, pos)
64
 
    return lineno, colno
65
 
 
66
 
 
67
 
def errmsg(msg, doc, pos, end=None):
68
 
    # Note that this function is called from _speedups
69
 
    lineno, colno = linecol(doc, pos)
70
 
    if end is None:
71
 
        #fmt = '{0}: line {1} column {2} (char {3})'
72
 
        #return fmt.format(msg, lineno, colno, pos)
73
 
        fmt = '%s: line %d column %d (char %d)'
74
 
        return fmt % (msg, lineno, colno, pos)
75
 
    endlineno, endcolno = linecol(doc, end)
76
 
    #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
77
 
    #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
78
 
    fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
79
 
    return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
80
 
 
81
 
 
82
 
_CONSTANTS = {
83
 
    '-Infinity': NegInf,
84
 
    'Infinity': PosInf,
85
 
    'NaN': NaN,
86
 
}
87
 
 
88
 
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
89
 
BACKSLASH = {
90
 
    '"': u'"', '\\': u'\\', '/': u'/',
91
 
    'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
92
 
}
93
 
 
94
 
DEFAULT_ENCODING = "utf-8"
95
 
 
96
 
def py_scanstring(s, end, encoding=None, strict=True,
97
 
        _b=BACKSLASH, _m=STRINGCHUNK.match):
98
 
    """Scan the string s for a JSON string. End is the index of the
99
 
    character in s after the quote that started the JSON string.
100
 
    Unescapes all valid JSON string escape sequences and raises ValueError
101
 
    on attempt to decode an invalid string. If strict is False then literal
102
 
    control characters are allowed in the string.
103
 
 
104
 
    Returns a tuple of the decoded string and the index of the character in s
105
 
    after the end quote."""
106
 
    if encoding is None:
107
 
        encoding = DEFAULT_ENCODING
108
 
    chunks = []
109
 
    _append = chunks.append
110
 
    begin = end - 1
111
 
    while 1:
112
 
        chunk = _m(s, end)
113
 
        if chunk is None:
114
 
            raise JSONDecodeError(
115
 
                "Unterminated string starting at", s, begin)
116
 
        end = chunk.end()
117
 
        content, terminator = chunk.groups()
118
 
        # Content is contains zero or more unescaped string characters
119
 
        if content:
120
 
            if not isinstance(content, unicode):
121
 
                content = unicode(content, encoding)
122
 
            _append(content)
123
 
        # Terminator is the end of string, a literal control character,
124
 
        # or a backslash denoting that an escape sequence follows
125
 
        if terminator == '"':
126
 
            break
127
 
        elif terminator != '\\':
128
 
            if strict:
129
 
                msg = "Invalid control character %r at" % (terminator,)
130
 
                #msg = "Invalid control character {0!r} at".format(terminator)
131
 
                raise JSONDecodeError(msg, s, end)
132
 
            else:
133
 
                _append(terminator)
134
 
                continue
135
 
        try:
136
 
            esc = s[end]
137
 
        except IndexError:
138
 
            raise JSONDecodeError(
139
 
                "Unterminated string starting at", s, begin)
140
 
        # If not a unicode escape sequence, must be in the lookup table
141
 
        if esc != 'u':
142
 
            try:
143
 
                char = _b[esc]
144
 
            except KeyError:
145
 
                msg = "Invalid \\escape: " + repr(esc)
146
 
                raise JSONDecodeError(msg, s, end)
147
 
            end += 1
148
 
        else:
149
 
            # Unicode escape sequence
150
 
            esc = s[end + 1:end + 5]
151
 
            next_end = end + 5
152
 
            if len(esc) != 4:
153
 
                msg = "Invalid \\uXXXX escape"
154
 
                raise JSONDecodeError(msg, s, end)
155
 
            uni = int(esc, 16)
156
 
            # Check for surrogate pair on UCS-4 systems
157
 
            if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
158
 
                msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
159
 
                if not s[end + 5:end + 7] == '\\u':
160
 
                    raise JSONDecodeError(msg, s, end)
161
 
                esc2 = s[end + 7:end + 11]
162
 
                if len(esc2) != 4:
163
 
                    raise JSONDecodeError(msg, s, end)
164
 
                uni2 = int(esc2, 16)
165
 
                uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
166
 
                next_end += 6
167
 
            char = unichr(uni)
168
 
            end = next_end
169
 
        # Append the unescaped character
170
 
        _append(char)
171
 
    return u''.join(chunks), end
172
 
 
173
 
 
174
 
# Use speedup if available
175
 
scanstring = c_scanstring or py_scanstring
176
 
 
177
 
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
178
 
WHITESPACE_STR = ' \t\n\r'
179
 
 
180
 
def JSONObject((s, end), encoding, strict, scan_once, object_hook,
181
 
        object_pairs_hook, memo=None,
182
 
        _w=WHITESPACE.match, _ws=WHITESPACE_STR):
183
 
    # Backwards compatibility
184
 
    if memo is None:
185
 
        memo = {}
186
 
    memo_get = memo.setdefault
187
 
    pairs = []
188
 
    # Use a slice to prevent IndexError from being raised, the following
189
 
    # check will raise a more specific ValueError if the string is empty
190
 
    nextchar = s[end:end + 1]
191
 
    # Normally we expect nextchar == '"'
192
 
    if nextchar != '"':
193
 
        if nextchar in _ws:
194
 
            end = _w(s, end).end()
195
 
            nextchar = s[end:end + 1]
196
 
        # Trivial empty object
197
 
        if nextchar == '}':
198
 
            if object_pairs_hook is not None:
199
 
                result = object_pairs_hook(pairs)
200
 
                return result, end
201
 
            pairs = {}
202
 
            if object_hook is not None:
203
 
                pairs = object_hook(pairs)
204
 
            return pairs, end + 1
205
 
        elif nextchar != '"':
206
 
            raise JSONDecodeError("Expecting property name", s, end)
207
 
    end += 1
208
 
    while True:
209
 
        key, end = scanstring(s, end, encoding, strict)
210
 
        key = memo_get(key, key)
211
 
 
212
 
        # To skip some function call overhead we optimize the fast paths where
213
 
        # the JSON key separator is ": " or just ":".
214
 
        if s[end:end + 1] != ':':
215
 
            end = _w(s, end).end()
216
 
            if s[end:end + 1] != ':':
217
 
                raise JSONDecodeError("Expecting : delimiter", s, end)
218
 
 
219
 
        end += 1
220
 
 
221
 
        try:
222
 
            if s[end] in _ws:
223
 
                end += 1
224
 
                if s[end] in _ws:
225
 
                    end = _w(s, end + 1).end()
226
 
        except IndexError:
227
 
            pass
228
 
 
229
 
        try:
230
 
            value, end = scan_once(s, end)
231
 
        except StopIteration:
232
 
            raise JSONDecodeError("Expecting object", s, end)
233
 
        pairs.append((key, value))
234
 
 
235
 
        try:
236
 
            nextchar = s[end]
237
 
            if nextchar in _ws:
238
 
                end = _w(s, end + 1).end()
239
 
                nextchar = s[end]
240
 
        except IndexError:
241
 
            nextchar = ''
242
 
        end += 1
243
 
 
244
 
        if nextchar == '}':
245
 
            break
246
 
        elif nextchar != ',':
247
 
            raise JSONDecodeError("Expecting , delimiter", s, end - 1)
248
 
 
249
 
        try:
250
 
            nextchar = s[end]
251
 
            if nextchar in _ws:
252
 
                end += 1
253
 
                nextchar = s[end]
254
 
                if nextchar in _ws:
255
 
                    end = _w(s, end + 1).end()
256
 
                    nextchar = s[end]
257
 
        except IndexError:
258
 
            nextchar = ''
259
 
 
260
 
        end += 1
261
 
        if nextchar != '"':
262
 
            raise JSONDecodeError("Expecting property name", s, end - 1)
263
 
 
264
 
    if object_pairs_hook is not None:
265
 
        result = object_pairs_hook(pairs)
266
 
        return result, end
267
 
    pairs = dict(pairs)
268
 
    if object_hook is not None:
269
 
        pairs = object_hook(pairs)
270
 
    return pairs, end
271
 
 
272
 
def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
273
 
    values = []
274
 
    nextchar = s[end:end + 1]
275
 
    if nextchar in _ws:
276
 
        end = _w(s, end + 1).end()
277
 
        nextchar = s[end:end + 1]
278
 
    # Look-ahead for trivial empty array
279
 
    if nextchar == ']':
280
 
        return values, end + 1
281
 
    _append = values.append
282
 
    while True:
283
 
        try:
284
 
            value, end = scan_once(s, end)
285
 
        except StopIteration:
286
 
            raise JSONDecodeError("Expecting object", s, end)
287
 
        _append(value)
288
 
        nextchar = s[end:end + 1]
289
 
        if nextchar in _ws:
290
 
            end = _w(s, end + 1).end()
291
 
            nextchar = s[end:end + 1]
292
 
        end += 1
293
 
        if nextchar == ']':
294
 
            break
295
 
        elif nextchar != ',':
296
 
            raise JSONDecodeError("Expecting , delimiter", s, end)
297
 
 
298
 
        try:
299
 
            if s[end] in _ws:
300
 
                end += 1
301
 
                if s[end] in _ws:
302
 
                    end = _w(s, end + 1).end()
303
 
        except IndexError:
304
 
            pass
305
 
 
306
 
    return values, end
307
 
 
308
 
class JSONDecoder(object):
309
 
    """Simple JSON <http://json.org> decoder
310
 
 
311
 
    Performs the following translations in decoding by default:
312
 
 
313
 
    +---------------+-------------------+
314
 
    | JSON          | Python            |
315
 
    +===============+===================+
316
 
    | object        | dict              |
317
 
    +---------------+-------------------+
318
 
    | array         | list              |
319
 
    +---------------+-------------------+
320
 
    | string        | unicode           |
321
 
    +---------------+-------------------+
322
 
    | number (int)  | int, long         |
323
 
    +---------------+-------------------+
324
 
    | number (real) | float             |
325
 
    +---------------+-------------------+
326
 
    | true          | True              |
327
 
    +---------------+-------------------+
328
 
    | false         | False             |
329
 
    +---------------+-------------------+
330
 
    | null          | None              |
331
 
    +---------------+-------------------+
332
 
 
333
 
    It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
334
 
    their corresponding ``float`` values, which is outside the JSON spec.
335
 
 
336
 
    """
337
 
 
338
 
    def __init__(self, encoding=None, object_hook=None, parse_float=None,
339
 
            parse_int=None, parse_constant=None, strict=True,
340
 
            object_pairs_hook=None):
341
 
        """
342
 
        *encoding* determines the encoding used to interpret any
343
 
        :class:`str` objects decoded by this instance (``'utf-8'`` by
344
 
        default).  It has no effect when decoding :class:`unicode` objects.
345
 
 
346
 
        Note that currently only encodings that are a superset of ASCII work,
347
 
        strings of other encodings should be passed in as :class:`unicode`.
348
 
 
349
 
        *object_hook*, if specified, will be called with the result of every
350
 
        JSON object decoded and its return value will be used in place of the
351
 
        given :class:`dict`.  This can be used to provide custom
352
 
        deserializations (e.g. to support JSON-RPC class hinting).
353
 
 
354
 
        *object_pairs_hook* is an optional function that will be called with
355
 
        the result of any object literal decode with an ordered list of pairs.
356
 
        The return value of *object_pairs_hook* will be used instead of the
357
 
        :class:`dict`.  This feature can be used to implement custom decoders
358
 
        that rely on the order that the key and value pairs are decoded (for
359
 
        example, :func:`collections.OrderedDict` will remember the order of
360
 
        insertion). If *object_hook* is also defined, the *object_pairs_hook*
361
 
        takes priority.
362
 
 
363
 
        *parse_float*, if specified, will be called with the string of every
364
 
        JSON float to be decoded.  By default, this is equivalent to
365
 
        ``float(num_str)``. This can be used to use another datatype or parser
366
 
        for JSON floats (e.g. :class:`decimal.Decimal`).
367
 
 
368
 
        *parse_int*, if specified, will be called with the string of every
369
 
        JSON int to be decoded.  By default, this is equivalent to
370
 
        ``int(num_str)``.  This can be used to use another datatype or parser
371
 
        for JSON integers (e.g. :class:`float`).
372
 
 
373
 
        *parse_constant*, if specified, will be called with one of the
374
 
        following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``.  This
375
 
        can be used to raise an exception if invalid JSON numbers are
376
 
        encountered.
377
 
 
378
 
        *strict* controls the parser's behavior when it encounters an
379
 
        invalid control character in a string. The default setting of
380
 
        ``True`` means that unescaped control characters are parse errors, if
381
 
        ``False`` then control characters will be allowed in strings.
382
 
 
383
 
        """
384
 
        self.encoding = encoding
385
 
        self.object_hook = object_hook
386
 
        self.object_pairs_hook = object_pairs_hook
387
 
        self.parse_float = parse_float or float
388
 
        self.parse_int = parse_int or int
389
 
        self.parse_constant = parse_constant or _CONSTANTS.__getitem__
390
 
        self.strict = strict
391
 
        self.parse_object = JSONObject
392
 
        self.parse_array = JSONArray
393
 
        self.parse_string = scanstring
394
 
        self.memo = {}
395
 
        self.scan_once = make_scanner(self)
396
 
 
397
 
    def decode(self, s, _w=WHITESPACE.match):
398
 
        """Return the Python representation of ``s`` (a ``str`` or ``unicode``
399
 
        instance containing a JSON document)
400
 
 
401
 
        """
402
 
        obj, end = self.raw_decode(s, idx=_w(s, 0).end())
403
 
        end = _w(s, end).end()
404
 
        if end != len(s):
405
 
            raise JSONDecodeError("Extra data", s, end, len(s))
406
 
        return obj
407
 
 
408
 
    def raw_decode(self, s, idx=0):
409
 
        """Decode a JSON document from ``s`` (a ``str`` or ``unicode``
410
 
        beginning with a JSON document) and return a 2-tuple of the Python
411
 
        representation and the index in ``s`` where the document ended.
412
 
 
413
 
        This can be used to decode a JSON document from a string that may
414
 
        have extraneous data at the end.
415
 
 
416
 
        """
417
 
        try:
418
 
            obj, end = self.scan_once(s, idx)
419
 
        except StopIteration:
420
 
            raise JSONDecodeError("No JSON object could be decoded", s, idx)
421
 
        return obj, end