Skip to content

Commit

Permalink
fix: Custom timezone ids were not unescaped when parsing the definiti…
Browse files Browse the repository at this point in the history
…ons. This can then cause a mismatch if that ID is used as part of the identifier of a custom recurrence rule.
  • Loading branch information
aggregat4 committed Nov 13, 2024
1 parent 477b356 commit 1ec00f9
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 17 deletions.
9 changes: 9 additions & 0 deletions src/ical_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ pub fn ical_event_to_string(event: &IcalEvent) -> String {
properties_to_string(&event.properties)
}

pub fn unescape_string(input: &str) -> String {
input
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\,", ",")
.replace("\\'", "'")
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
24 changes: 9 additions & 15 deletions src/meeters_ical.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::custom_timezone::CustomTz;
use crate::ical_util::unescape_string;
use crate::timezones::parse_ical_timezones;
use crate::timezones::parse_tzid;
use chrono::prelude::*;
Expand Down Expand Up @@ -70,9 +71,9 @@ fn extract_ical_datetime(
if prop.params.is_some() && find_param(prop.params.as_ref().unwrap(), "TZID").is_some() {
// timestamp with an explicit timezone: YYYYMMDDTHHMMSS
// We are assuming there is only one value in the TZID param
let tzid = &find_param(prop.params.as_ref().unwrap(), "TZID").unwrap()[0];
let tzid = unescape_string(&find_param(prop.params.as_ref().unwrap(), "TZID").unwrap()[0]);
// println!("We have a TZID: {}", tzid);
match parse_tzid(tzid, calendar_timezones) {
match parse_tzid(&tzid, calendar_timezones) {
Ok(timezone) => parse_ical_datetime(date_time_str, &timezone, local_tz),
// in case we can't parse the timezone ID we just default to local, also not optimal
Err(_) => {
Expand Down Expand Up @@ -183,29 +184,20 @@ fn parse_zoom_url(text: &str) -> Option<String> {
.map(|mat| mat.as_str().to_string())
}

fn sanitise_string(input: &str) -> String {
input
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\,", ",")
.replace("\\'", "'")
}

// See https://tools.ietf.org/html/rfc5545#section-3.6.1
fn parse_event(
ical_event: &IcalEvent,
calendar_timezones: &HashMap<String, CustomTz>,
local_tz: &Tz,
) -> Result<Event, CalendarError> {
let summary = sanitise_string(
let summary = unescape_string(
&find_property_value(&ical_event.properties, "SUMMARY").unwrap_or_else(|| "".to_string()),
);
let description = sanitise_string(
let description = unescape_string(
&find_property_value(&ical_event.properties, "DESCRIPTION")
.unwrap_or_else(|| "".to_string()),
);
let location = sanitise_string(
let location = unescape_string(
&find_property_value(&ical_event.properties, "LOCATION").unwrap_or_else(|| "".to_string()),
);
// println!("Parsing event '{}'", summary);
Expand Down Expand Up @@ -288,7 +280,8 @@ fn parse_occurrences(
.as_ref()
.and_then(|params| find_param(params, "TZID"));
let maybe_original_tz = if let Some(tzid_param) = maybe_tzid_param {
match parse_tzid(&tzid_param[0], custom_timezones) {
let unescaped_tzid = unescape_string(&tzid_param[0]);
match parse_tzid(&unescaped_tzid, custom_timezones) {
Ok(original_tz) => Some(original_tz),
Err(e) => {
return Err(CalendarError {
Expand Down Expand Up @@ -600,6 +593,7 @@ pub fn extract_events(text: &str, local_tz: &Tz) -> Result<Vec<Event>, CalendarE
match parse_calendar(text)? {
Some(calendar) => {
let calendar_timezones = parse_ical_timezones(&calendar, local_tz)?;
//println!("Calendar timezones found: {:?}", calendar_timezones);
let event_tuples = parse_events(calendar, &calendar_timezones, local_tz)?;
// Events are either normal events (potentially recurring) or they are modifying events
// that defines exceptions to recurrences of other events. We need to split these types out
Expand Down
8 changes: 6 additions & 2 deletions src/timezones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::custom_timezone::FixedTimespanSet;
use crate::ical_util::find_property;
use crate::ical_util::find_property_value;
use crate::ical_util::properties_to_string;
use crate::ical_util::unescape_string;
use crate::CalendarError;
use chrono::prelude::*;
use chrono::DateTime;
Expand Down Expand Up @@ -50,6 +51,7 @@ pub fn parse_tzid<'a>(
tzid: &str,
custom_timezones: &'a HashMap<String, CustomTz>,
) -> Result<Either<Tz, &'a CustomTz>, String> {
//println!("Parsing tzid: {}", tzid);
match custom_timezones.get(tzid) {
Some(tz) => Ok(Right(tz)),
None => Ok(Left(parse_standard_tz(tzid)?)),
Expand Down Expand Up @@ -91,16 +93,18 @@ fn parse_ical_timezone(
) -> Result<(String, CustomTz), CalendarError> {
match find_property_value(&vtimezone.properties, "TZID") {
Some(name) => {
// All string values in the icalendar are escaped so we need to unescape them
let unescaped_name = unescape_string(&name.to_string());
let timezone = CustomTz {
name: name.to_string(),
name: unescaped_name.clone(),
timespanset: parse_timespansets(vtimezone, local_tz)?, // pass on the error
};
println!(
"Parsed custom timezone definition '{:?}' with '{:?}' spans",
timezone.name,
timezone.timespanset.rest.len()
);
Ok((name, timezone))
Ok((unescaped_name, timezone))
}
None => Err(CalendarError {
msg: "Expecting TZID property for custom timezone".to_string(),
Expand Down

0 comments on commit 1ec00f9

Please sign in to comment.