Skip to content

Commit

Permalink
Merge pull request #581 from ennamarie19/master
Browse files Browse the repository at this point in the history
Add fixes for OSS-Fuzz bugs and test cases
  • Loading branch information
niccokunzmann committed Nov 6, 2023
2 parents 1b2e838 + 099b0de commit 11eb89b
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ New features:
Bug fixes:

- ...
- Fixed index error in cal.py when attempting to pop from an empty stack
- Fixed type error in prop.py when attempting to join strings into a byte-string
- Caught Wrong Date Format in ical_fuzzer to resolve fuzzing coverage blocker

5.0.11 (2023-11-03)
-------------------
Expand Down
4 changes: 4 additions & 0 deletions src/icalendar/cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ def from_ical(cls, st, multiple=False):
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)
Expand Down
19 changes: 19 additions & 0 deletions src/icalendar/parser_tools.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
from typing import Any

SEQUENCE_TYPES = (list, tuple)
DEFAULT_ENCODING = 'utf-8'


def from_unicode(value: Any, encoding='utf-8') -> bytes:
"""
Converts a value to bytes, even if it already is bytes
:param value: The value to convert
:param encoding: The encoding to use in the conversion
:return: The bytes representation of the value
"""
if isinstance(value, bytes):
value = value
elif isinstance(value, str):
try:
value = value.encode(encoding)
except UnicodeEncodeError:
value = value.encode('utf-8', 'replace')
return value


def to_unicode(value, encoding='utf-8'):
"""Converts a value to unicode, even if it is already a unicode string.
"""
Expand Down
17 changes: 13 additions & 4 deletions src/icalendar/prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from icalendar.parser_tools import DEFAULT_ENCODING
from icalendar.parser_tools import SEQUENCE_TYPES
from icalendar.parser_tools import to_unicode
from icalendar.parser_tools import from_unicode
from icalendar.timezone_cache import _timezone_cache
from icalendar.windows_to_olson import WINDOWS_TO_OLSON

Expand All @@ -62,14 +63,12 @@
import re
import time as _time


DURATION_REGEX = re.compile(r'([-+]?)P(?:(\d+)W)?(?:(\d+)D)?'
r'(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$')

WEEKDAY_RULE = re.compile(r'(?P<signal>[+-]?)(?P<relative>[\d]{0,2})'
r'(?P<weekday>[\w]{2})$')


####################################################
# handy tzinfo classes you can use.
#
Expand Down Expand Up @@ -174,6 +173,7 @@ def from_ical(cls, ical):
class vCalAddress(str):
"""This just returns an unquoted string.
"""

def __new__(cls, value, encoding=DEFAULT_ENCODING):
value = to_unicode(value, encoding=encoding)
self = super().__new__(cls, value)
Expand All @@ -194,6 +194,7 @@ def from_ical(cls, ical):
class vFloat(float):
"""Just a float.
"""

def __new__(cls, *args, **kwargs):
self = super().__new__(cls, *args, **kwargs)
self.params = Parameters()
Expand All @@ -213,6 +214,7 @@ def from_ical(cls, ical):
class vInt(int):
"""Just an int.
"""

def __new__(cls, *args, **kwargs):
self = super().__new__(cls, *args, **kwargs)
self.params = Parameters()
Expand Down Expand Up @@ -250,7 +252,7 @@ def __init__(self, dt_list):
self.dts = vDDD

def to_ical(self):
dts_ical = (dt.to_ical() for dt in self.dts)
dts_ical = (from_unicode(dt.to_ical()) for dt in self.dts)
return b",".join(dts_ical)

@staticmethod
Expand Down Expand Up @@ -288,6 +290,7 @@ def __eq__(self, other):
"""self == other"""
return isinstance(other, vCategory) and self.cats == other.cats


class TimeBase:
"""Make classes with a datetime/date comparable."""

Expand Down Expand Up @@ -366,6 +369,7 @@ def __repr__(self):
"""repr(self)"""
return f"{self.__class__.__name__}({self.dt}, {self.params})"


class vDate(TimeBase):
"""Render and generates iCalendar date format.
"""
Expand Down Expand Up @@ -516,6 +520,7 @@ def dt(self):
"""The time delta for compatibility."""
return self.td


class vPeriod(TimeBase):
"""A precise period of time.
"""
Expand Down Expand Up @@ -588,6 +593,7 @@ def dt(self):
"""Make this cooperate with the other vDDDTypes."""
return (self.start, (self.duration if self.by_duration else self.end))


class vWeekday(str):
"""This returns an unquoted weekday abbrevation.
"""
Expand Down Expand Up @@ -830,11 +836,13 @@ def from_ical(ical):
def __eq__(self, other):
return self.to_ical() == other.to_ical()


class vUTCOffset:
"""Renders itself as a utc offset.
"""

ignore_exceptions = False # if True, and we cannot parse this
ignore_exceptions = False # if True, and we cannot parse this

# component, we will silently ignore
# it, rather than let the exception
# propagate upwards
Expand Down Expand Up @@ -896,6 +904,7 @@ class vInline(str):
has parameters. Conversion of inline values are handled by the Component
class, so no further processing is needed.
"""

def __new__(cls, value, encoding=DEFAULT_ENCODING):
value = to_unicode(value, encoding=encoding)
self = super().__new__(cls, value)
Expand Down
18 changes: 18 additions & 0 deletions src/icalendar/tests/test_oss_fuzz_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""This file collects errors that the OSS FUZZ build has found."""
from datetime import time
from icalendar import Calendar
from icalendar.prop import vDDDLists

import pytest


def test_stack_is_empty():
"""If we get passed an invalid string, we expect to get a ValueError."""
with pytest.raises(ValueError):
Calendar.from_ical("END:CALENDAR")


def test_vdd_list_type_mismatch():
"""If we pass in a string type, we expect it to be converted to bytes"""
vddd_list = vDDDLists([time(hour=6, minute=6, second=6)])
assert vddd_list.to_ical() == b'060606'

0 comments on commit 11eb89b

Please sign in to comment.