diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py index 9bfed80..3b05866 100644 --- a/src/icalendar/cal.py +++ b/src/icalendar/cal.py @@ -358,6 +358,10 @@ class Component(CaselessDict): elif uname == 'END': # we are done adding properties to this component # so pop it from the stack and add it to the new top. + if not stack: + # The stack is currently empty, the input must be invalid + raise ValueError('END encountered without an accompanying BEGIN!') + component = stack.pop() if not stack: # we are at the end comps.append(component) diff --git a/src/icalendar/fuzzing/ical_fuzzer.py b/src/icalendar/fuzzing/ical_fuzzer.py index d524920..bffd078 100644 --- a/src/icalendar/fuzzing/ical_fuzzer.py +++ b/src/icalendar/fuzzing/ical_fuzzer.py @@ -17,16 +17,24 @@ import atheris import sys -with atheris.instrument_imports(include=['icalendar']): - from icalendar import Calendar +with atheris.instrument_imports( + include=['icalendar'], + exclude=['pytz', 'six', 'site_packages', 'pkg_resources', 'dateutil']): + import icalendar + +_value_error_matches = [ + "component", "parse", "Expected", "Wrong date format", "END encountered", + "vDDD", 'recurrence', 'Wrong datetime', 'Offset must', 'Invalid iCalendar' +] +@atheris.instrument_func def TestOneInput(data): fdp = atheris.FuzzedDataProvider(data) try: b = fdp.ConsumeBool() - cal = Calendar.from_ical(fdp.ConsumeString(fdp.remaining_bytes())) + cal = icalendar.Calendar.from_ical(fdp.ConsumeString(fdp.remaining_bytes())) if b: for event in cal.walk('VEVENT'): @@ -34,10 +42,11 @@ def TestOneInput(data): else: cal.to_ical() except ValueError as e: - if "component" in str(e) or "parse" in str(e) or "Expected" in str(e): + if any(m in str(e) for m in _value_error_matches): return -1 raise e + def main(): atheris.Setup(sys.argv, TestOneInput) atheris.Fuzz() diff --git a/src/icalendar/prop.py b/src/icalendar/prop.py index c7e7840..f078d74 100644 --- a/src/icalendar/prop.py +++ b/src/icalendar/prop.py @@ -250,8 +250,19 @@ class vDDDLists: self.dts = vDDD def to_ical(self): - dts_ical = (dt.to_ical() for dt in self.dts) - return b",".join(dts_ical) + dts_ical = [dt.to_ical() for dt in self.dts] + + # Make sure all elements are of the same type + if dts_ical and all(type(dt) is type(dts_ical[0]) for dt in dts_ical): + first_dt = dts_ical[0] + if isinstance(first_dt, bytes): + return b",".join(dts_ical) + elif isinstance(first_dt, str): + return ",".join(dts_ical) + else: + raise ValueError(f"Unexpected type {type(first_dt)} in vDDD list!") + else: + raise ValueError("Type mismatch in vDDD list!") @staticmethod def from_ical(ical, timezone=None):