Skip to content

Commit e00171d

Browse files
committed
SL-19707 throw an error if we exceed 200 depth in formatting or parsing
1 parent 95584f6 commit e00171d

File tree

6 files changed

+97
-36
lines changed

6 files changed

+97
-36
lines changed

llsd/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
ALL_CHARS = str(bytearray(range(256))) if PY2 else bytes(range(256))
3333

34-
34+
MAX_FORMAT_DEPTH = 200
3535
class _LLSD:
3636
__metaclass__ = abc.ABCMeta
3737

llsd/serde_binary.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import uuid
66

77
from llsd.base import (_LLSD, LLSDBaseParser, LLSDSerializationError, BINARY_HEADER,
8-
_str_to_bytes, binary, is_integer, is_string, uri)
8+
MAX_FORMAT_DEPTH,_str_to_bytes, binary, is_integer, is_string, uri)
99

1010

1111
try:
@@ -15,7 +15,6 @@
1515
# Python 3: 'range()' is already lazy
1616
pass
1717

18-
1918
class LLSDBinaryParser(LLSDBaseParser):
2019
"""
2120
Parse application/llsd+binary to a python object.
@@ -164,15 +163,19 @@ def format_binary(something):
164163

165164
def write_binary(stream, something):
166165
stream.write(b'<?llsd/binary?>\n')
167-
_write_binary_recurse(stream, something)
166+
_write_binary_recurse(stream, something, 0)
168167

169168

170-
def _write_binary_recurse(stream, something):
169+
def _write_binary_recurse(stream, something, depth):
171170
"Binary formatter workhorse."
171+
172+
if depth > MAX_FORMAT_DEPTH:
173+
raise LLSDSerializationError("Cannot serialize depth of more than %d" % MAX_FORMAT_DEPTH)
174+
172175
if something is None:
173176
stream.write(b'!')
174177
elif isinstance(something, _LLSD):
175-
_write_binary_recurse(stream, something.thing)
178+
_write_binary_recurse(stream, something.thing, depth)
176179
elif isinstance(something, bool):
177180
stream.write(b'1' if something else b'0')
178181
elif is_integer(something):
@@ -202,27 +205,27 @@ def _write_binary_recurse(stream, something):
202205
seconds_since_epoch = calendar.timegm(something.timetuple())
203206
stream.writelines([b'd', struct.pack('<d', seconds_since_epoch)])
204207
elif isinstance(something, (list, tuple)):
205-
_write_list(stream, something)
208+
_write_list(stream, something, depth)
206209
elif isinstance(something, dict):
207210
stream.writelines([b'{', struct.pack('!i', len(something))])
208211
for key, value in something.items():
209212
key = _str_to_bytes(key)
210213
stream.writelines([b'k', struct.pack('!i', len(key)), key])
211-
_write_binary_recurse(stream, value)
214+
_write_binary_recurse(stream, value, depth+1)
212215
stream.write(b'}')
213216
else:
214217
try:
215-
return _write_list(stream, list(something))
218+
return _write_list(stream, list(something), depth)
216219
except TypeError:
217220
raise LLSDSerializationError(
218221
"Cannot serialize unknown type: %s (%s)" %
219222
(type(something), something))
220223

221224

222-
def _write_list(stream, something):
225+
def _write_list(stream, something, depth):
223226
stream.writelines([b'[', struct.pack('!i', len(something))])
224227
for item in something:
225-
_write_binary_recurse(stream, item)
228+
_write_binary_recurse(stream, item, depth+1)
226229
stream.write(b']')
227230

228231

llsd/serde_notation.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import uuid
55

66
from llsd.base import (_LLSD, B, LLSDBaseFormatter, LLSDBaseParser, NOTATION_HEADER,
7-
LLSDParseError, LLSDSerializationError, UnicodeType,
7+
MAX_FORMAT_DEPTH, LLSDParseError, LLSDSerializationError, UnicodeType,
88
_format_datestr, _parse_datestr, _str_to_bytes, binary, uri)
99

1010

@@ -411,6 +411,11 @@ class LLSDNotationFormatter(LLSDBaseFormatter):
411411
412412
See http://wiki.secondlife.com/wiki/LLSD#Notation_Serialization
413413
"""
414+
415+
def __init__(self):
416+
super(LLSDNotationFormatter, self).__init__()
417+
self._depth = 0
418+
414419
def _LLSD(self, v):
415420
return self._generate(v.thing)
416421
def _UNDEF(self, v):
@@ -443,18 +448,22 @@ def _DATE(self, v):
443448
def _ARRAY(self, v):
444449
self.stream.write(b'[')
445450
delim = b''
451+
self._depth = self._depth + 1
446452
for item in v:
447453
self.stream.write(delim)
448454
self._generate(item)
449455
delim = b','
456+
self._depth = self._depth - 1
450457
self.stream.write(b']')
451458
def _MAP(self, v):
452459
self.stream.write(b'{')
453460
delim = b''
461+
self._depth = self._depth + 1
454462
for key, value in v.items():
455463
self.stream.writelines([delim, b"'", self._esc(UnicodeType(key)), b"':"])
456464
self._generate(value)
457465
delim = b','
466+
self._depth = self._depth - 1
458467
self.stream.write(b'}')
459468

460469
def _esc(self, data, quote=b"'"):
@@ -466,6 +475,9 @@ def _generate(self, something):
466475
467476
:param something: a python object (typically a dict) to be serialized.
468477
"""
478+
if self._depth > MAX_FORMAT_DEPTH:
479+
raise LLSDSerializationError("Cannot serialize depth of more than %d" % MAX_FORMAT_DEPTH)
480+
469481
t = type(something)
470482
handler = self.type_map.get(t)
471483
if handler:

llsd/serde_xml.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44

55
from llsd.base import (_LLSD, ALL_CHARS, LLSDBaseParser, LLSDBaseFormatter, XML_HEADER,
6-
LLSDParseError, LLSDSerializationError, UnicodeType,
6+
MAX_FORMAT_DEPTH, LLSDParseError, LLSDSerializationError, UnicodeType,
77
_format_datestr, _str_to_bytes, _to_python, is_unicode, PY2)
88
from llsd.fastest_elementtree import ElementTreeError, fromstring, parse as _parse
99

@@ -24,7 +24,6 @@
2424
for x in INVALID_XML_BYTES:
2525
XML_ESC_TRANS[x] = None
2626

27-
2827
def remove_invalid_xml_bytes(b):
2928
"""
3029
Remove characters that aren't allowed in xml.
@@ -76,7 +75,7 @@ def __init__(self, indent_atom = None):
7675
super(LLSDXMLFormatter, self).__init__()
7776
self._indent_atom = b''
7877
self._eol = b''
79-
self._indent_level = 0
78+
self._depth = 1
8079

8180
def _indent(self):
8281
pass
@@ -115,15 +114,15 @@ def _DATE(self, v):
115114
self.stream.writelines([b'<date>', _format_datestr(v), b'</date>', self._eol])
116115
def _ARRAY(self, v):
117116
self.stream.writelines([b'<array>', self._eol])
118-
self._indent_level = self._indent_level + 1
117+
self._depth = self._depth + 1
119118
for item in v:
120119
self._indent()
121120
self._generate(item)
122-
self._indent_level = self._indent_level - 1
121+
self._depth = self._depth - 1
123122
self.stream.writelines([b'</array>', self._eol])
124123
def _MAP(self, v):
125124
self.stream.writelines([b'<map>', self._eol])
126-
self._indent_level = self._indent_level + 1
125+
self._depth = self._depth + 1
127126
for key, value in v.items():
128127
self._indent()
129128
if PY2: # pragma: no cover
@@ -138,12 +137,14 @@ def _MAP(self, v):
138137
self._eol])
139138
self._indent()
140139
self._generate(value)
141-
self._indent_level = self._indent_level - 1
140+
self._depth = self._depth - 1
142141
self._indent()
143142
self.stream.writelines([b'</map>', self._eol])
144143

145144
def _generate(self, something):
146145
"Generate xml from a single python object."
146+
if self._depth - 1 > MAX_FORMAT_DEPTH:
147+
raise LLSDSerializationError("Cannot serialize depth of more than %d" % MAX_FORMAT_DEPTH)
147148
t = type(something)
148149
if t in self.type_map:
149150
return self.type_map[t](something)
@@ -183,13 +184,12 @@ def __init__(self, indent_atom = b' '):
183184
super(LLSDXMLPrettyFormatter, self).__init__()
184185

185186
# Private data used for indentation.
186-
self._indent_level = 1
187187
self._indent_atom = indent_atom
188188
self._eol = b'\n'
189189

190190
def _indent(self):
191191
"Write an indentation based on the atom and indentation level."
192-
self.stream.writelines([self._indent_atom] * self._indent_level)
192+
self.stream.writelines([self._indent_atom] * self._depth)
193193

194194

195195
def format_pretty_xml(something):

tests/bench.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def binary_stream():
8484
def build_deep_xml():
8585
deep_data = {}
8686
curr_data = deep_data
87-
for i in range(250):
87+
for i in range(198):
8888
curr_data["curr_data"] = {}
8989
curr_data["integer"] = 7
9090
curr_data["string"] = "string"

tests/llsd_test.py

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,26 @@ def testParseNotationHalfTruncatedHex(self):
527527
def testParseNotationInvalidHex(self):
528528
self.assertRaises(llsd.LLSDParseError, self.llsd.parse, b"'\\xzz'")
529529

530+
def testDeepMap(self):
531+
"""
532+
Test formatting of a deeply nested map
533+
"""
534+
535+
test_map = {"foo":"bar", "depth":0}
536+
max_depth = 199
537+
for depth in range(max_depth):
538+
test_map = {"foo":"bar", "depth":depth, "next":test_map}
539+
540+
# this should not throw an exception.
541+
test_notation_out = self.llsd.as_notation(test_map)
542+
543+
test_notation_parsed = self.llsd.parse(io.BytesIO(test_notation_out))
544+
self.assertEqual(test_map, test_notation_parsed)
545+
546+
test_map = {"foo":"bar", "depth":depth, "next":test_map}
547+
# this should throw an exception.
548+
self.assertRaises(llsd.LLSDSerializationError, self.llsd.as_notation, test_map)
549+
530550

531551
class LLSDBinaryUnitTest(unittest.TestCase):
532552
"""
@@ -964,6 +984,26 @@ def testParseDelimitedString(self):
964984

965985
self.assertEqual('\t\x07\x08\x0c\n\r\t\x0b\x0fp', llsd.parse(delimited_string))
966986

987+
def testDeepMap(self):
988+
"""
989+
Test formatting of a deeply nested map
990+
"""
991+
992+
test_map = {"foo":"bar", "depth":0}
993+
max_depth = 199
994+
for depth in range(max_depth):
995+
test_map = {"foo":"bar", "depth":depth, "next":test_map}
996+
997+
# this should not throw an exception.
998+
test_binary_out = self.llsd.as_binary(test_map)
999+
1000+
test_binary_parsed = self.llsd.parse(io.BytesIO(test_binary_out))
1001+
self.assertEqual(test_map, test_binary_parsed)
1002+
1003+
test_map = {"foo":"bar", "depth":depth, "next":test_map}
1004+
# this should throw an exception.
1005+
self.assertRaises(llsd.LLSDSerializationError, self.llsd.as_binary, test_map)
1006+
9671007

9681008

9691009
class LLSDPythonXMLUnitTest(unittest.TestCase):
@@ -1345,20 +1385,6 @@ def testMap(self):
13451385
map_within_map_xml)
13461386
self.assertXMLRoundtrip({}, blank_map_xml)
13471387

1348-
def testDeepMap(self):
1349-
"""
1350-
Test that formatting a deeply nested map does not cause a RecursionError
1351-
"""
1352-
1353-
test_map = {"foo":"bar", "depth":0, "next":None}
1354-
max_depth = 200
1355-
for depth in range(max_depth):
1356-
test_map = {"foo":"bar", "depth":depth, "next":test_map}
1357-
1358-
# this should not throw an exception.
1359-
test_xml = self.llsd.as_xml(test_map)
1360-
1361-
13621388
def testBinary(self):
13631389
"""
13641390
Test the parse and serialization of input type : binary.
@@ -1493,6 +1519,26 @@ def testFormatPrettyXML(self):
14931519
self.assertEqual(result[result.find(b"?>") + 2: len(result)],
14941520
format_xml_result[format_xml_result.find(b"?>") + 2: len(format_xml_result)])
14951521

1522+
def testDeepMap(self):
1523+
"""
1524+
Test formatting of a deeply nested map
1525+
"""
1526+
1527+
test_map = {"foo":"bar", "depth":0}
1528+
max_depth = 199
1529+
for depth in range(max_depth):
1530+
test_map = {"foo":"bar", "depth":depth, "next":test_map}
1531+
1532+
# this should not throw an exception.
1533+
test_xml_out = self.llsd.as_xml(test_map)
1534+
1535+
test_xml_parsed = self.llsd.parse(io.BytesIO(test_xml_out))
1536+
self.assertEqual(test_map, test_xml_parsed)
1537+
1538+
test_map = {"foo":"bar", "depth":depth, "next":test_map}
1539+
# this should throw an exception.
1540+
self.assertRaises(llsd.LLSDSerializationError, self.llsd.as_xml, test_map)
1541+
14961542
def testLLSDSerializationFailure(self):
14971543
"""
14981544
Test serialization function as_xml with an object of non-supported type.

0 commit comments

Comments
 (0)