diff --git a/crates/common/src/manager/boot.rs b/crates/common/src/manager/boot.rs index 9ebfbdf6..4064ff09 100644 --- a/crates/common/src/manager/boot.rs +++ b/crates/common/src/manager/boot.rs @@ -5,7 +5,6 @@ */ use std::{ - collections::BTreeMap, net::{IpAddr, Ipv4Addr}, path::PathBuf, sync::Arc, diff --git a/crates/dav-proto/src/responses/mod.rs b/crates/dav-proto/src/responses/mod.rs index a6d80935..98e3beff 100644 --- a/crates/dav-proto/src/responses/mod.rs +++ b/crates/dav-proto/src/responses/mod.rs @@ -25,6 +25,10 @@ trait XmlEscape { fn write_escaped_to(&self, out: &mut impl Write) -> std::fmt::Result; } +trait XmlCdataEscape { + fn write_cdata_escaped_to(&self, out: &mut impl Write) -> std::fmt::Result; +} + impl> XmlEscape for T { fn write_escaped_to(&self, out: &mut impl Write) -> std::fmt::Result { let str = self.as_ref(); @@ -44,6 +48,30 @@ impl> XmlEscape for T { } } +impl> XmlCdataEscape for T { + fn write_cdata_escaped_to(&self, out: &mut impl Write) -> std::fmt::Result { + let str = self.as_ref(); + let mut last_ch = '\0'; + let mut last_ch2 = '\0'; + + out.write_str("' if last_ch == ']' && last_ch2 == ']' => { + out.write_str("]]>")?; + } + _ => out.write_char(ch)?, + } + + last_ch2 = last_ch; + last_ch = ch; + } + + out.write_str("]]>") + } +} + impl Display for Namespaces { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("xmlns:D=\"DAV:\"")?; @@ -176,6 +204,7 @@ mod tests { use crate::{ parser::{tokenizer::Tokenizer, Token}, + responses::XmlCdataEscape, schema::{ property::{ ActiveLock, CalDavProperty, CardDavProperty, DavValue, LockScope, Privilege, @@ -199,6 +228,18 @@ mod tests { } } + impl From for DavValue { + fn from(v: ICalendar) -> Self { + DavValue::ICalendar(v) + } + } + + impl From for DavValue { + fn from(v: VCard) -> Self { + DavValue::VCard(v) + } + } + #[test] fn parse_responses() { for (num, test) in [ @@ -634,4 +675,30 @@ END:VCARD } } } + + #[test] + fn escape_cdata() { + for (test, expected) in [ + ("", ""), + ("hello", ""), + ("hello world", ""), + ("", "]]>"), + ("&hello;", ""), + ("'hello'", ""), + ("\"hello\"", ""), + ("<>&'\"", "&'\"]]>"), + (">", "]]>"), + ("]]>]", "]]]>"), + ("]]>", "]]>"), + ("hello]]>world", "world]]>"), + ( + "hello]]>pure-evil", + "pure-evil]]>", + ), + ] { + let mut output = String::new(); + test.write_cdata_escaped_to(&mut output).unwrap(); + assert_eq!(output, expected, "failed for input: {test:?}"); + } + } } diff --git a/crates/dav-proto/src/responses/property.rs b/crates/dav-proto/src/responses/property.rs index 4fd82e6b..42a0bb41 100644 --- a/crates/dav-proto/src/responses/property.rs +++ b/crates/dav-proto/src/responses/property.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use super::XmlEscape; +use super::{XmlCdataEscape, XmlEscape}; use crate::schema::{ property::{ ActiveLock, CalDavProperty, CardDavProperty, Comp, DavProperty, DavValue, LockDiscovery, @@ -15,7 +15,6 @@ use crate::schema::{ response::{Ace, AclRestrictions, Href, List, PropResponse, SupportedPrivilege}, Namespace, Namespaces, }; -use calcard::{icalendar::ICalendar, vcard::VCard}; use mail_parser::{ parsers::fields::date::{DOW, MONTH}, DateTime, @@ -88,15 +87,7 @@ impl Display for DavValue { DavValue::ActiveLocks(v) => v.fmt(f), DavValue::LockEntries(v) => v.fmt(f), DavValue::ReportSets(v) => v.fmt(f), - DavValue::VCard(v) => { - write!(f, "") - } - DavValue::ICalendar(v) => { - write!(f, "") - } - DavValue::CData(v) => { - write!(f, "") - } + DavValue::CData(v) => v.write_cdata_escaped_to(f), DavValue::Components(v) => v.fmt(f), DavValue::Collations(v) => v.fmt(f), DavValue::Href(v) => v.fmt(f), @@ -145,7 +136,7 @@ impl Display for DavValue { ) } DavValue::Response(v) => v.fmt(f), - DavValue::Null => Ok(()), + DavValue::VCard(_) | DavValue::ICalendar(_) | DavValue::Null => Ok(()), } } } @@ -353,18 +344,6 @@ impl From> for DavValue { } } -impl From for DavValue { - fn from(v: ICalendar) -> Self { - DavValue::ICalendar(v) - } -} - -impl From for DavValue { - fn from(v: VCard) -> Self { - DavValue::VCard(v) - } -} - impl From for DavValue { fn from(v: SupportedLock) -> Self { DavValue::LockEntries(v.0)