Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,34 @@ def test_issue50(self):
cal = base.readOne(test_file)
self.assertEqual(datetime.datetime(2024, 8, 12, 22, 30, tzinfo=tzutc()), cal.vevent.dtend.value)

def test_nested_begin_depth(self):
"""
Catch deeply nested BEGIN and raise ParseError rather than
exceeding interpreter recursion limit.

Reported by: oc-8a8a9d
"""
depth = 200
deep_ical = ("".join("BEGIN:X\r\n" for _ in range(depth))
+ "FN:test\r\n"
+ "".join("END:X\r\n" for _ in range(depth)))
obj = None

default_recursion_limit = sys.getrecursionlimit()
sys.setrecursionlimit(150)
try:
# Vector 1: default readOne (transform=True)
with self.assertRaises(ParseError):
obj = base.readOne(deep_ical)

# Vector 2: serialize()
if obj:
with self.assertRaises(ParseError):
obj.serialize()

finally:
sys.setrecursionlimit(default_recursion_limit)


class TestChangeTZ(unittest.TestCase):
"""
Expand Down
15 changes: 14 additions & 1 deletion vobject/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ def to_basestring(s):
TAB = '\t'
SPACEORTAB = SPACE + TAB

# Maximum allowed nesting depth of components (nested BEGIN:/END:
# blocks). Deeply-nested input would otherwise build a component tree
# that overflows the interpreter stack during the recursive
# transformChildrenToNative()/serialize() walks, raising an uncaught
# RecursionError (a denial of service). Instead we raise the
# documented ParseError once this depth is exceeded. The limit is far
# above any legitimate vCard/iCal nesting.
DEFAULT_MAX_NESTING = 100

# --------------------------------- Main classes -------------------------------


Expand Down Expand Up @@ -1077,7 +1086,8 @@ def pop(self):


def readComponents(streamOrString, validate=False, transform=True,
ignoreUnreadable=False, allowQP=False):
ignoreUnreadable=False, allowQP=False,
max_nesting=DEFAULT_MAX_NESTING):
"""
Generate one Component at a time from a stream.
"""
Expand Down Expand Up @@ -1107,6 +1117,9 @@ def readComponents(streamOrString, validate=False, transform=True,
versionLine = vline
stack.modifyTop(vline)
elif vline.name == "BEGIN":
if len(stack) >= max_nesting:
raise ParseError("Component nesting depth exceeds requested "
"maximum of {0} levels".format(max_nesting), n)
stack.push(Component(vline.value, group=vline.group))
elif vline.name == "PROFILE":
if not stack.top():
Expand Down
6 changes: 5 additions & 1 deletion vobject/icalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def getTzid(tzid, smart=True):
tz = timezone(tzid)
registerTzid(toUnicode(tzid), tz)
except UnknownTimeZoneError as e:
logging.error(e)
logging.error("Unknown Timezone: %r", e.args[0])
except ImportError as e:
logging.error(e)
return tz
Expand Down Expand Up @@ -335,6 +335,10 @@ def pickTzid(tzinfo, allowUTC=False):
if tzinfo is None or (not allowUTC and tzinfo_eq(tzinfo, utc)):
return None

# Try a zoneinfo (CPython 3.9+) first.
if hasattr(tzinfo, 'key'):
return toUnicode(tzinfo.key)

# Try pytz tzid key
if hasattr(tzinfo, 'tzid'):
return toUnicode(tzinfo.tzid)
Expand Down
Loading