#![cfg(feature = "parser")]
use std::borrow::Cow;
use ical::{
param::IcalParam,
tree::{component::vevent::VEVENT, cst::IcalCst, prop::description::DESCRIPTION},
value::{IcalValue, binary::IcalBinary},
};
fn calendar(prop: &str) -> String {
format!(
"BEGIN:VCALENDAR\r\n\
VERSION:2.0\r\n\
BEGIN:VEVENT\r\n\
UID:encoding@example.com\r\n\
{prop}\r\n\
END:VEVENT\r\n\
END:VCALENDAR\r\n"
)
}
fn raw_calendar(prop: &[u8]) -> Vec<u8> {
let mut raw =
b"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nUID:encoding@example.com\r\n".to_vec();
raw.extend_from_slice(prop);
raw.extend_from_slice(b"\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n");
raw
}
fn params(ics: &str) -> Vec<IcalParam<'static>> {
let cst = IcalCst::parse(ics).expect("a readable calendar");
cst.decode().components[0].props[1]
.params
.iter()
.map(|param| param.clone().into_owned())
.collect()
}
#[test]
fn keeps_the_charset_parameter_on_the_decoded_model() {
let ics = calendar("DESCRIPTION;CHARSET=ISO-8859-1:cafe");
assert!(params(&ics).contains(&IcalParam::Charset(Cow::Borrowed("ISO-8859-1"))));
}
#[test]
fn keeps_a_value_in_a_foreign_charset_as_its_own_bytes() {
let raw = raw_calendar(b"DESCRIPTION;CHARSET=ISO-8859-1:caf\xe9");
let mut cst = IcalCst::parse(&raw).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.bytes().as_ref(), b"caf\xe9");
assert_eq!(cst.to_bytes(), raw);
}
#[test]
fn keeps_quoted_printable_octets_raw_and_says_so() {
let ics = calendar("DESCRIPTION;ENCODING=QUOTED-PRINTABLE:caf=C3=A9");
let mut cst = IcalCst::parse(&ics).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.bytes().as_ref(), b"caf=C3=A9");
assert!(params(&ics).contains(&IcalParam::Encoding(Cow::Borrowed("QUOTED-PRINTABLE"))));
}
#[test]
fn keeps_a_base64_payload_verbatim() {
let ics = calendar("ATTACH;ENCODING=BASE64;VALUE=BINARY:Zm9v");
let cst = IcalCst::parse(&ics).expect("a readable calendar");
let decoded = cst.decode();
assert_eq!(
decoded.components[0].props[1].value,
IcalValue::Binary(IcalBinary::Base64(Cow::Borrowed("Zm9v")))
);
}
#[test]
fn tells_an_inline_payload_from_a_uri_reference() {
let uri = calendar("ATTACH:https://example.com/agenda.pdf");
let cst = IcalCst::parse(&uri).expect("a readable calendar");
let decoded = cst.decode();
assert!(matches!(
decoded.components[0].props[1].value,
IcalValue::Uri(_)
));
}
#[cfg(feature = "quoted-printable")]
#[test]
fn resolves_quoted_printable_octets_on_request() {
let ics = calendar("DESCRIPTION;ENCODING=QUOTED-PRINTABLE:caf=C3=A9");
let mut cst = IcalCst::parse(&ics).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.quoted_printable(), "café".as_bytes());
}
#[cfg(feature = "quoted-printable")]
#[test]
fn leaves_a_value_alone_when_no_encoding_is_declared() {
let ics = calendar("DESCRIPTION:caf=C3=A9");
let mut cst = IcalCst::parse(&ics).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.quoted_printable(), b"caf=C3=A9");
}
#[cfg(feature = "quoted-printable")]
#[test]
fn accepts_the_vcalendar_bare_encoding_token() {
let ics = "BEGIN:VCALENDAR\r\n\
VERSION:1.0\r\n\
BEGIN:VEVENT\r\n\
UID:bare@example.com\r\n\
DESCRIPTION;QUOTED-PRINTABLE:caf=C3=A9\r\n\
END:VEVENT\r\n\
END:VCALENDAR\r\n";
let mut cst = IcalCst::parse(ics).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.quoted_printable(), "café".as_bytes());
}
#[cfg(feature = "base64")]
#[test]
fn decodes_an_inline_base64_payload_on_request() {
let ics = calendar("ATTACH;ENCODING=BASE64;VALUE=BINARY:Zm9v");
let cst = IcalCst::parse(&ics).expect("a readable calendar");
let decoded = cst.decode();
let IcalValue::Binary(binary) = &decoded.components[0].props[1].value else {
panic!("the attachment did not decode as binary");
};
assert_eq!(
binary.decode_base64().expect("inline data").unwrap(),
b"foo"
);
}
#[cfg(feature = "base64")]
#[test]
fn decodes_nothing_for_a_uri_reference() {
let reference = IcalBinary::Uri(Cow::Borrowed("https://example.com/agenda.pdf"));
assert!(reference.decode_base64().is_none());
}
#[cfg(feature = "base64")]
#[test]
fn reports_a_malformed_base64_payload_rather_than_guessing() {
let broken = IcalBinary::Base64(Cow::Borrowed("not base64!"));
assert!(broken.decode_base64().expect("inline data").is_err());
}
#[cfg(feature = "encoding")]
#[test]
fn transcodes_a_foreign_charset_to_text() {
let raw = raw_calendar(b"DESCRIPTION;CHARSET=ISO-8859-1:caf\xe9");
let mut cst = IcalCst::parse(&raw).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.charset(), "café");
}
#[cfg(feature = "encoding")]
#[test]
fn reads_a_value_as_utf8_when_no_charset_is_declared() {
let ics = calendar("DESCRIPTION:café");
let mut cst = IcalCst::parse(&ics).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.charset(), "café");
}
#[cfg(feature = "encoding")]
#[test]
fn falls_back_to_utf8_for_a_charset_label_nobody_knows() {
let ics = calendar("DESCRIPTION;CHARSET=X-NOT-A-CHARSET:café");
let mut cst = IcalCst::parse(&ics).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.charset(), "café");
}
#[cfg(all(feature = "encoding", feature = "quoted-printable"))]
#[test]
fn resolves_octets_before_transcoding_them() {
let ics = calendar("DESCRIPTION;CHARSET=ISO-8859-1;ENCODING=QUOTED-PRINTABLE:caf=E9");
let mut cst = IcalCst::parse(&ics).expect("a readable calendar");
let event = cst.component_mut::<VEVENT>().expect("the event");
let value = event.prop_mut::<DESCRIPTION>().expect("the description");
assert_eq!(value.charset(), "café");
}