Skip to content
Open
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
25 changes: 19 additions & 6 deletions rust/src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ impl<'a> Parser<'a> {
}
}

fn checked_duration_add(&mut self, current: u32, value: u32) -> Result<u32, ParseError> {
current
.checked_add(value)
.ok_or_else(|| self.parse_error("Duration value is too large".to_string()))
}

fn unexpected_character_error(
&mut self,
field_name: &str,
Expand Down Expand Up @@ -632,7 +638,8 @@ impl<'a> Parser<'a> {
);
}

duration.hours += value;
duration.hours =
self.checked_duration_add(duration.hours, value)?;

if let Some(fraction) = op_fraction {
let extra_minutes = fraction * 60_f64;
Expand All @@ -656,7 +663,8 @@ impl<'a> Parser<'a> {
);
}

duration.minutes += value;
duration.minutes =
self.checked_duration_add(duration.minutes, value)?;

if let Some(fraction) = op_fraction {
let extra_seconds = fraction * 60_f64;
Expand Down Expand Up @@ -729,7 +737,10 @@ impl<'a> Parser<'a> {
if let Some(fraction) = op_fraction {
let extra_days = fraction * 7_f64;
let extra_full_days = extra_days.trunc();
duration.days += extra_full_days as u32;
duration.days = self.checked_duration_add(
duration.days,
extra_full_days as u32,
)?;
let extra_hours = (extra_days - extra_full_days) * 24.0;
let extra_full_hours = extra_hours.trunc();
duration.hours += extra_full_hours as u32;
Expand All @@ -755,7 +766,7 @@ impl<'a> Parser<'a> {
));
}

duration.days += value;
duration.days = self.checked_duration_add(duration.days, value)?;
if let Some(fraction) = op_fraction {
let extra_hours = fraction * 24.0;
let extra_full_hours = extra_hours.trunc();
Expand Down Expand Up @@ -820,8 +831,10 @@ impl<'a> Parser<'a> {
};

while let Some(digit) = self.inc().and_then(|ch| ch.to_digit(10)) {
value *= 10;
value += digit;
value = value
.checked_mul(10)
.and_then(|value| value.checked_add(digit))
.ok_or_else(|| self.parse_error("Duration value is too large".to_string()))?;
}

Ok(value)
Expand Down
17 changes: 17 additions & 0 deletions tests/parsing/test_parsing_duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pendulum.parsing import ParserError
from pendulum.parsing import parse
from pendulum.parsing import with_extensions


def test_parse_duration():
Expand Down Expand Up @@ -304,3 +305,19 @@ def test_parse_interval_invalid():
def test_parse_duration_fraction_only_allowed_on_last_component():
with pytest.raises(ParserError):
parse("P2Y3M4DT5.5H6M7S")


@pytest.mark.skipif(
not with_extensions, reason="Only the Rust parser bounds duration values"
)
def test_parse_duration_value_too_large():
# 2**32 seconds does not fit the parser's accumulator
with pytest.raises(ParserError):
parse("PT4294967296S")

# Repeating a unit must not wrap the accumulated value either
with pytest.raises(ParserError):
parse("PT4294967295H1H")

with pytest.raises(ParserError):
parse("P4294967295D0.5W")