Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+469
View File
@@ -0,0 +1,469 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
parser::{DavParser, Token, tokenizer::Tokenizer},
schema::{
Element, NamedElement, Namespace,
property::{DavValue, Privilege},
request::{
Acl, AclPrincipalPropSet, DavPropertyValue, PrincipalMatch, PrincipalMatchProperties,
PrincipalPropertySearch, PropertySearch,
},
response::{Ace, GrantDeny, Href, List, Principal},
},
};
impl DavParser for Acl {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
stream.expect_named_element(NamedElement::dav(Element::Acl))?;
let mut acl = Acl { aces: vec![] };
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Ace,
},
..
} => {
acl.aces.push(Ace::parse(stream)?);
}
Token::ElementEnd => {
break;
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
other => {
return Err(other.into_unexpected());
}
}
}
Ok(acl)
}
}
impl DavParser for Ace {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut ace = Ace {
principal: Principal::All,
invert: false,
grant_deny: GrantDeny::Grant(List(vec![])),
protected: false,
inherited: None,
};
let mut depth = 1;
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Principal,
},
..
} => {
ace.principal = Principal::parse(stream)?;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Invert,
},
..
} if depth == 1 => {
ace.invert = true;
depth += 1;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Protected,
},
..
} if depth == 1 => {
ace.protected = true;
stream.expect_element_end()?;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Inherited,
},
..
} if depth == 1 => {
stream.expect_named_element(NamedElement::dav(Element::Href))?;
ace.inherited = stream.collect_string_value()?.map(Href);
stream.expect_element_end()?;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Grant,
},
..
} if depth == 1 => {
ace.grant_deny = GrantDeny::Grant(List(stream.collect_privileges()?));
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Deny,
},
..
} if depth == 1 => {
ace.grant_deny = GrantDeny::Deny(List(stream.collect_privileges()?));
}
Token::ElementEnd => {
depth -= 1;
if depth == 0 {
break;
}
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
other => {
return Err(other.into_unexpected());
}
}
}
Ok(ace)
}
}
impl DavParser for Principal {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let result = match stream.unwrap_named_element()? {
NamedElement {
ns: Namespace::Dav,
element: Element::Href,
} => Principal::Href(Href(stream.collect_string_value()?.unwrap_or_default())),
NamedElement {
ns: Namespace::Dav,
element: Element::All,
} => {
stream.expect_element_end()?;
Principal::All
}
NamedElement {
ns: Namespace::Dav,
element: Element::Authenticated,
} => {
stream.expect_element_end()?;
Principal::Authenticated
}
NamedElement {
ns: Namespace::Dav,
element: Element::Unauthenticated,
} => {
stream.expect_element_end()?;
Principal::Unauthenticated
}
NamedElement {
ns: Namespace::Dav,
element: Element::Property,
} => {
let property = stream.collect_properties(Vec::new())?;
Principal::Property(List(
property
.into_iter()
.map(|prop| DavPropertyValue::new(prop, DavValue::Null))
.collect(),
))
}
NamedElement {
ns: Namespace::Dav,
element: Element::Self_,
} => {
stream.expect_element_end()?;
Principal::Self_
}
other => return Err(other.into_unexpected()),
};
stream.expect_element_end()?;
Ok(result)
}
}
impl Tokenizer<'_> {
pub fn collect_privileges(&mut self) -> crate::parser::Result<Vec<Privilege>> {
let mut privileges = Vec::new();
let mut depth = 1;
loop {
match self.token()? {
Token::ElementStart { name, .. } => {
if let Some(privilege) = Privilege::from_element(name) {
privileges.push(privilege);
self.expect_element_end()?;
} else {
depth += 1;
}
}
Token::ElementEnd => {
depth -= 1;
if depth == 0 {
break;
}
}
Token::UnknownElement(_) => {
self.seek_element_end()?;
}
other => {
return Err(other.into_unexpected());
}
}
}
Ok(privileges)
}
}
impl Privilege {
pub fn from_element(element: NamedElement) -> Option<Self> {
match (element.ns, element.element) {
(Namespace::Dav, Element::Read) => Some(Privilege::Read),
(Namespace::Dav, Element::Write) => Some(Privilege::Write),
(Namespace::Dav, Element::WriteProperties) => Some(Privilege::WriteProperties),
(Namespace::Dav, Element::WriteContent) => Some(Privilege::WriteContent),
(Namespace::Dav, Element::Unlock) => Some(Privilege::Unlock),
(Namespace::Dav, Element::ReadAcl) => Some(Privilege::ReadAcl),
(Namespace::Dav, Element::ReadCurrentUserPrivilegeSet) => {
Some(Privilege::ReadCurrentUserPrivilegeSet)
}
(Namespace::Dav, Element::WriteAcl) => Some(Privilege::WriteAcl),
(Namespace::Dav, Element::Bind) => Some(Privilege::Bind),
(Namespace::Dav, Element::Unbind) => Some(Privilege::Unbind),
(Namespace::Dav, Element::All) => Some(Privilege::All),
(Namespace::CalDav, Element::ReadFreeBusy) => Some(Privilege::ReadFreeBusy),
(Namespace::CalDav, Element::ScheduleDeliver) => Some(Privilege::ScheduleDeliver),
(Namespace::CalDav, Element::ScheduleDeliverInvite) => {
Some(Privilege::ScheduleDeliverInvite)
}
(Namespace::CalDav, Element::ScheduleDeliverReply) => {
Some(Privilege::ScheduleDeliverReply)
}
(Namespace::CalDav, Element::ScheduleQueryFreebusy) => {
Some(Privilege::ScheduleQueryFreeBusy)
}
(Namespace::CalDav, Element::ScheduleSend) => Some(Privilege::ScheduleSend),
(Namespace::CalDav, Element::ScheduleSendInvite) => Some(Privilege::ScheduleSendInvite),
(Namespace::CalDav, Element::ScheduleSendReply) => Some(Privilege::ScheduleSendReply),
(Namespace::CalDav, Element::ScheduleSendFreebusy) => {
Some(Privilege::ScheduleSendFreeBusy)
}
_ => None,
}
}
}
impl DavParser for AclPrincipalPropSet {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut acps = AclPrincipalPropSet { properties: vec![] };
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
},
..
} => {
acps.properties = stream.collect_properties(acps.properties)?;
}
Token::ElementEnd => {
break;
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
other => {
return Err(other.into_unexpected());
}
}
}
Ok(acps)
}
}
impl DavParser for PrincipalMatch {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut pm = PrincipalMatch {
principal_properties: PrincipalMatchProperties::Self_,
properties: vec![],
};
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::PrincipalProperty,
},
..
} => {
pm.principal_properties = PrincipalMatchProperties::Properties(
stream.collect_properties(Vec::new())?,
);
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Self_,
},
..
} => {
pm.principal_properties = PrincipalMatchProperties::Self_;
stream.expect_element_end()?;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
},
..
} => {
pm.properties = stream.collect_properties(pm.properties)?;
}
Token::ElementEnd => {
break;
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
other => {
return Err(other.into_unexpected());
}
}
}
Ok(pm)
}
}
impl DavParser for PrincipalPropertySearch {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut pps = PrincipalPropertySearch {
property_search: vec![],
properties: vec![],
apply_to_principal_collection_set: false,
};
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::PropertySearch,
},
..
} => {
if let Some(prop) = PropertySearch::parse(stream)? {
pps.property_search.push(prop);
}
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
},
..
} => {
pps.properties = stream.collect_properties(pps.properties)?;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::ApplyToPrincipalCollectionSet,
},
..
} => {
stream.expect_element_end()?;
pps.apply_to_principal_collection_set = true;
}
Token::ElementEnd => {
break;
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
other => {
return Err(other.into_unexpected());
}
}
}
Ok(pps)
}
}
impl PropertySearch {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Option<Self>> {
let mut property = None;
let mut match_ = None;
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
},
..
} => {
property = stream.collect_properties(Vec::new())?.into_iter().next();
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Match,
},
..
} => {
match_ = stream.collect_string_value()?;
}
Token::ElementEnd => {
break;
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
other => {
return Err(other.into_unexpected());
}
}
}
Ok(property.map(|property| PropertySearch {
property,
match_: match_.unwrap_or_default(),
}))
}
}
+110
View File
@@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
parser::{DavParser, Token, tokenizer::Tokenizer},
schema::{
Element, NamedElement, Namespace,
property::{LockScope, LockType},
request::LockInfo,
},
};
use types::dead_property::DeadProperty;
impl DavParser for LockInfo {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut lockinfo = LockInfo {
lock_scope: LockScope::Exclusive,
lock_type: LockType::Write,
owner: None,
};
if stream.expect_named_element_or_eof(NamedElement::dav(Element::Lockinfo))? {
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Lockscope,
},
..
} => {
lockinfo.lock_scope = LockScope::parse(stream)?;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Locktype,
},
..
} => {
lockinfo.lock_type = LockType::parse(stream)?;
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Owner,
},
..
} => {
lockinfo.owner = Some(DeadProperty::parse(stream)?);
}
Token::ElementEnd | Token::Eof => {
break;
}
other => {
return Err(other.into_unexpected());
}
}
}
}
Ok(lockinfo)
}
}
impl DavParser for LockScope {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
match stream.unwrap_named_element()? {
NamedElement {
ns: Namespace::Dav,
element: Element::Exclusive,
} => {
stream.expect_element_end()?;
stream.expect_element_end()?;
Ok(LockScope::Exclusive)
}
NamedElement {
ns: Namespace::Dav,
element: Element::Shared,
} => {
stream.expect_element_end()?;
stream.expect_element_end()?;
Ok(LockScope::Shared)
}
other => Err(other.into_unexpected()),
}
}
}
impl DavParser for LockType {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
match stream.unwrap_named_element()? {
NamedElement {
ns: Namespace::Dav,
element: Element::Write,
} => {
stream.expect_element_end()?;
stream.expect_element_end()?;
Ok(LockType::Write)
}
other => Err(other.into_unexpected()),
}
}
}
+66
View File
@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
parser::{DavParser, Token, tokenizer::Tokenizer},
schema::{Element, NamedElement, Namespace, request::MkCol},
};
impl DavParser for MkCol {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut mkcol = MkCol {
is_mkcalendar: false,
props: Vec::new(),
};
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Mkcol,
},
..
} => {}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::CalDav,
element: Element::Mkcalendar,
},
..
} => {
mkcol.is_mkcalendar = true;
}
Token::Eof => {
return Ok(mkcol);
}
other => return Err(other.into_unexpected()),
};
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Set,
},
..
} => {
stream.expect_named_element(NamedElement::dav(Element::Prop))?;
stream.collect_property_values(&mut mkcol.props)?;
stream.expect_element_end()?;
}
Token::ElementEnd | Token::Eof => {
break;
}
token => return Err(token.into_unexpected()),
}
}
Ok(mkcol)
}
}
+201
View File
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
parser::{DavParser, RawElement, Token, tokenizer::Tokenizer},
schema::Namespace,
};
use types::dead_property::{DeadElementTag, DeadProperty, DeadPropertyTag};
pub mod acl;
pub mod lockinfo;
pub mod mkcol;
pub mod propertyupdate;
pub mod propfind;
pub mod report;
impl DavParser for DeadProperty {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut depth = 1;
let mut items = DeadProperty::default();
loop {
match stream.token()? {
Token::ElementStart { raw, .. } | Token::UnknownElement(raw) => {
items.0.push(DeadPropertyTag::ElementStart((&raw).into()));
depth += 1;
}
Token::ElementEnd => {
depth -= 1;
if depth == 0 {
break;
}
items.0.push(DeadPropertyTag::ElementEnd);
}
Token::Text(text) => {
items.0.push(DeadPropertyTag::Text(text.into_owned()));
}
Token::Bytes(bytes) => {
items.0.push(DeadPropertyTag::Text(
String::from_utf8_lossy(&bytes).into_owned(),
));
}
Token::Eof => {
break;
}
}
}
Ok(items)
}
}
pub trait NsDeadProperty {
fn single_with_ns(namespace: Namespace, name: &str) -> Self;
}
impl NsDeadProperty for DeadProperty {
fn single_with_ns(namespace: Namespace, name: &str) -> Self {
DeadProperty(vec![
DeadPropertyTag::ElementStart(DeadElementTag {
name: format!("{}:{name}", namespace.prefix()),
attrs: None,
}),
DeadPropertyTag::ElementEnd,
])
}
}
impl From<&RawElement<'_>> for DeadElementTag {
fn from(raw: &RawElement<'_>) -> Self {
let name = std::str::from_utf8(raw.element.local_name().as_ref())
.unwrap_or("invalid-utf8")
.trim_ascii()
.to_string();
let mut attrs = String::with_capacity(raw.element.attributes_raw().len());
if let Some(namespace) = &raw.namespace {
attrs.push_str("xmlns=\"");
attrs.push_str(std::str::from_utf8(namespace).unwrap_or("invalid-utf8"));
attrs.push('"');
}
for attr in raw.element.attributes().flatten() {
if attr.key.as_ref() == b"xmlns" || attr.key.as_ref().starts_with(b"xmlns:") {
// Skip namespace attributes
continue;
}
if let (Ok(key), Ok(value)) = (
std::str::from_utf8(attr.key.as_ref()),
std::str::from_utf8(attr.value.as_ref()),
) {
if !attrs.is_empty() {
attrs.push(' ');
}
attrs.push_str(key);
attrs.push('=');
attrs.push('"');
attrs.push_str(value);
attrs.push('"');
}
}
DeadElementTag {
name,
attrs: (!attrs.is_empty()).then_some(attrs),
}
}
}
#[cfg(test)]
mod tests {
use calcard::vcard::VCardVersion;
use crate::{
parser::{DavParser, tokenizer::Tokenizer},
schema::{
property::{CardDavProperty, DavProperty},
request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report},
},
};
#[test]
fn parse_address_data_version() {
let xml = r#"<?xml version="1.0" encoding="utf-8" ?>
<C:addressbook-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
<D:prop>
<C:address-data content-type="text/vcard" version="3.0">
<C:prop name="FN"/>
</C:address-data>
</D:prop>
</C:addressbook-query>"#;
let mut tokenizer = Tokenizer::new(xml.as_bytes());
let report = Report::parse(&mut tokenizer).unwrap();
let Report::AddressbookQuery(query) = report else {
panic!("expected addressbook-query, got {report:?}");
};
let PropFind::Prop(properties) = query.properties else {
panic!("expected prop, got {:?}", query.properties);
};
let version = properties.iter().find_map(|property| match property {
DavProperty::CardDav(CardDavProperty::AddressData { version, .. }) => Some(*version),
_ => None,
});
assert_eq!(version, Some(Some(VCardVersion::V3_0)));
}
#[test]
fn parse_requests() {
for entry in std::fs::read_dir("resources/requests").unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.extension().map(|ext| ext == "xml").unwrap_or(false) {
println!("Parsing: {:?}", path);
let filename = path.file_name().unwrap().to_str().unwrap();
let xml = std::fs::read_to_string(&path).unwrap();
let mut tokenizer = Tokenizer::new(xml.as_bytes());
let json_path = path.with_extension("json");
let json_output = match filename.split_once('-').unwrap().0 {
"propfind" => match PropFind::parse(&mut tokenizer) {
Ok(propfind) => serde_json::to_string_pretty(&propfind).unwrap(),
Err(_) => String::new(),
},
"propertyupdate" => serde_json::to_string_pretty(
&PropertyUpdate::parse(&mut tokenizer).unwrap(),
)
.unwrap(),
"mkcol" => serde_json::to_string_pretty(&MkCol::parse(&mut tokenizer).unwrap())
.unwrap(),
"lockinfo" => {
serde_json::to_string_pretty(&LockInfo::parse(&mut tokenizer).unwrap())
.unwrap()
}
"report" => {
serde_json::to_string_pretty(&Report::parse(&mut tokenizer).unwrap())
.unwrap()
}
"acl" => {
serde_json::to_string_pretty(&Acl::parse(&mut tokenizer).unwrap()).unwrap()
}
_ => {
panic!("Unknown method: {}", filename);
}
};
/*if json_path.exists() {
let expected = std::fs::read_to_string(json_path).unwrap();
assert_eq!(json_output, expected);
} else {*/
std::fs::write(json_path, json_output).unwrap();
//}
}
}
}
}
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
parser::{DavParser, Token, tokenizer::Tokenizer},
schema::{Element, NamedElement, Namespace, request::PropertyUpdate},
};
impl DavParser for PropertyUpdate {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
stream.expect_named_element(NamedElement::dav(Element::Propertyupdate))?;
let mut update = PropertyUpdate {
set: Vec::with_capacity(4),
remove: Vec::with_capacity(4),
set_first: true,
};
loop {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Set,
},
..
} => {
stream.expect_named_element(NamedElement::dav(Element::Prop))?;
stream.collect_property_values(&mut update.set)?;
stream.expect_element_end()?;
update.set_first = update.remove.is_empty();
}
Token::ElementStart {
name:
NamedElement {
ns: Namespace::Dav,
element: Element::Remove,
},
..
} => {
stream.expect_named_element(NamedElement::dav(Element::Prop))?;
update.remove = stream.collect_properties(update.remove)?;
stream.expect_element_end()?;
}
Token::ElementEnd | Token::Eof => {
break;
}
Token::UnknownElement(_) => {
// Ignore unknown elements
stream.seek_element_end()?;
}
token => return Err(token.into_unexpected()),
}
}
Ok(update)
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
parser::{DavParser, Token, tokenizer::Tokenizer},
schema::{Element, NamedElement, Namespace, request::PropFind},
};
impl DavParser for PropFind {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
if stream.expect_named_element_or_eof(NamedElement::dav(Element::Propfind))? {
match stream.unwrap_named_element()? {
NamedElement {
ns: Namespace::Dav,
element: Element::Propname,
} => Ok(PropFind::PropName),
NamedElement {
ns: Namespace::Dav,
element: Element::Allprop,
} => {
stream.expect_element_end()?;
if matches!(
stream.token()?,
Token::ElementStart {
name: NamedElement {
ns: Namespace::Dav,
element: Element::Include
},
..
}
) {
stream.collect_properties(Vec::new()).map(PropFind::AllProp)
} else {
Ok(PropFind::AllProp(vec![]))
}
}
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
} => stream.collect_properties(Vec::new()).map(PropFind::Prop),
element => Err(element.into_unexpected()),
}
} else {
Ok(PropFind::AllProp(vec![]))
}
}
}
+641
View File
@@ -0,0 +1,641 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
Depth,
parser::{
DavParser, RawElement, Token, XmlValueParser, property::TimeRangeFromRaw,
tokenizer::Tokenizer,
},
schema::{
Attribute, Collation, Element, MatchType, NamedElement, Namespace,
property::DavProperty,
request::{
AclPrincipalPropSet, AddressbookQuery, CalendarQuery, ExpandProperty,
ExpandPropertyItem, Filter, FilterOp, FreeBusyQuery, MultiGet, PrincipalMatch,
PrincipalPropertySearch, PropFind, Report, SyncCollection, TextMatch, Timezone,
VCardPropertyWithGroup,
},
},
};
use calcard::{
icalendar::{ICalendarComponentType, ICalendarParameterName, ICalendarProperty},
vcard::VCardParameterName,
};
use types::{TimeRange, dead_property::DeadElementTag};
impl DavParser for Report {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
match stream.unwrap_named_element()? {
NamedElement {
ns: Namespace::CalDav,
element: Element::CalendarQuery,
} => CalendarQuery::parse(stream).map(Report::CalendarQuery),
NamedElement {
ns: Namespace::CalDav,
element: Element::FreeBusyQuery,
} => FreeBusyQuery::parse(stream).map(Report::FreeBusyQuery),
NamedElement {
ns: Namespace::CalDav,
element: Element::CalendarMultiget,
} => MultiGet::parse(stream).map(Report::CalendarMultiGet),
NamedElement {
ns: Namespace::CardDav,
element: Element::AddressbookQuery,
} => AddressbookQuery::parse(stream).map(Report::AddressbookQuery),
NamedElement {
ns: Namespace::CardDav,
element: Element::AddressbookMultiget,
} => MultiGet::parse(stream).map(Report::AddressbookMultiGet),
NamedElement {
ns: Namespace::Dav,
element: Element::SyncCollection,
} => SyncCollection::parse(stream).map(Report::SyncCollection),
NamedElement {
ns: Namespace::Dav,
element: Element::AclPrincipalPropSet,
} => AclPrincipalPropSet::parse(stream).map(Report::AclPrincipalPropSet),
NamedElement {
ns: Namespace::Dav,
element: Element::PrincipalMatch,
} => PrincipalMatch::parse(stream).map(Report::PrincipalMatch),
NamedElement {
ns: Namespace::Dav,
element: Element::PrincipalPropertySearch,
} => PrincipalPropertySearch::parse(stream).map(Report::PrincipalPropertySearch),
NamedElement {
ns: Namespace::Dav,
element: Element::PrincipalSearchPropertySet,
} => stream
.expect_element_end()
.map(|_| Report::PrincipalSearchPropertySet),
NamedElement {
ns: Namespace::Dav,
element: Element::ExpandProperty,
} => ExpandProperty::parse(stream).map(Report::ExpandProperty),
other => Err(other.into_unexpected()),
}
}
}
impl DavParser for CalendarQuery {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut cq = CalendarQuery {
properties: PropFind::AllProp(vec![]),
filters: vec![],
timezone: Timezone::None,
};
let mut depth = 1;
let mut components = Vec::with_capacity(3);
let mut property = None;
let mut parameter = None;
loop {
match stream.token()? {
Token::ElementStart { name, raw } => match name {
NamedElement {
ns: Namespace::Dav,
element: Element::Propname,
} if depth == 1 => {
cq.properties = PropFind::PropName;
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::Allprop,
} if depth == 1 => {
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
} if depth == 1 => {
cq.properties = PropFind::Prop(stream.collect_properties(Vec::new())?);
}
NamedElement {
ns: Namespace::CalDav,
element: Element::Filter,
} if depth == 1 => {
depth += 1;
}
NamedElement {
ns: Namespace::CalDav,
element: Element::Timezone,
} if depth == 1 => {
cq.timezone =
Timezone::Name(stream.collect_string_value()?.unwrap_or_default());
}
NamedElement {
ns: Namespace::CalDav,
element: Element::TimezoneId,
} if depth == 1 => {
cq.timezone =
Timezone::Id(stream.collect_string_value()?.unwrap_or_default());
}
NamedElement {
ns: Namespace::CalDav,
element: Element::CompFilter,
} if depth >= 2 => {
for attribute in raw.attributes::<ICalendarComponentType>() {
if let Attribute::Name(name) = attribute? {
components.push((name, depth));
}
}
depth += 1;
}
NamedElement {
ns: Namespace::CalDav,
element: Element::PropFilter,
} if depth >= 3 => {
for attribute in raw.attributes::<ICalendarProperty>() {
if let Attribute::Name(name) = attribute? {
property = Some(name);
}
}
depth += 1;
}
NamedElement {
ns: Namespace::CalDav,
element: Element::ParamFilter,
} if depth >= 4 => {
for attribute in raw.attributes::<ICalendarParameterName>() {
if let Attribute::Name(name) = attribute? {
parameter = Some(name);
}
}
depth += 1;
}
NamedElement {
ns: Namespace::CalDav,
element: Element::IsNotDefined,
} => {
stream.expect_element_end()?;
if let Some(filter) = Filter::from_parts(
components.iter().map(|(c, _)| c.clone()).collect(),
property.clone(),
parameter.clone(),
FilterOp::Undefined,
) {
cq.filters.push(filter);
}
}
NamedElement {
ns: Namespace::CalDav,
element: Element::TextMatch,
} => {
let mut tm = TextMatch::parse(raw)?;
tm.value = stream.collect_string_value()?.unwrap_or_default();
if let Some(filter) = Filter::from_parts(
components.iter().map(|(c, _)| c.clone()).collect(),
property.clone(),
parameter.clone(),
FilterOp::TextMatch(tm),
) {
cq.filters.push(filter);
}
}
NamedElement {
ns: Namespace::CalDav,
element: Element::TimeRange,
} => {
let range = TimeRange::from_raw(&raw)?;
stream.expect_element_end()?;
if let Some(filter) = range.and_then(|range| {
Filter::from_parts(
components.iter().map(|(c, _)| c.clone()).collect(),
property.clone(),
parameter.clone(),
FilterOp::TimeRange(range),
)
}) {
cq.filters.push(filter);
}
}
name => return Err(name.into_unexpected()),
},
Token::ElementEnd => {
depth -= 1;
if depth == 0 {
break;
}
if matches!(components.last(), Some((_, d)) if *d == depth) {
if components.len() > 1
&& cq
.filters
.last()
.and_then(|c| c.components())
.is_none_or(|c| c.len() < components.len())
{
cq.filters.push(Filter::Component {
comp: components.iter().map(|(c, _)| c.clone()).collect(),
op: FilterOp::Exists,
});
}
components.pop();
}
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
element => return Err(element.into_unexpected()),
}
}
Ok(cq)
}
}
impl DavParser for AddressbookQuery {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut aq = AddressbookQuery {
properties: PropFind::AllProp(vec![]),
filters: vec![],
limit: None,
};
let mut depth = 1;
let mut property = None;
let mut parameter = None;
loop {
match stream.token()? {
Token::ElementStart { name, raw } => match name {
NamedElement {
ns: Namespace::Dav,
element: Element::Propname,
} if depth == 1 => {
aq.properties = PropFind::PropName;
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::Allprop,
} if depth == 1 => {
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
} if depth == 1 => {
aq.properties = PropFind::Prop(stream.collect_properties(Vec::new())?);
}
NamedElement {
ns: Namespace::CardDav,
element: Element::Filter,
} if depth == 1 => {
if let Some(filter) = Filter::parse(raw)? {
aq.filters.push(filter);
}
depth += 1;
}
NamedElement {
ns: Namespace::CardDav,
element: Element::Limit,
} if depth == 1 => {
stream.expect_named_element(NamedElement::carddav(Element::Nresults))?;
if let Some(Ok(limit)) = stream.parse_value::<u32>()? {
aq.limit = limit.into();
}
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::CardDav,
element: Element::PropFilter,
} if depth == 2 => {
let mut filter = None;
for attribute in raw.attributes::<VCardPropertyWithGroup>() {
match attribute? {
Attribute::Name(name) => {
property = Some(name);
}
Attribute::TestAllOf(all_of) => {
filter =
(if all_of { Filter::AllOf } else { Filter::AnyOf }).into();
}
_ => {}
}
}
if let Some(filter) = filter {
aq.filters.push(filter);
}
depth += 1;
}
NamedElement {
ns: Namespace::CardDav,
element: Element::ParamFilter,
} if depth == 3 => {
for attribute in raw.attributes::<VCardParameterName>() {
if let Attribute::Name(name) = attribute? {
parameter = Some(name);
}
}
depth += 1;
}
NamedElement {
ns: Namespace::CardDav,
element: Element::IsNotDefined,
} => {
stream.expect_element_end()?;
if let Some(filter) = Filter::from_parts(
(),
property.clone(),
parameter.clone(),
FilterOp::Undefined,
) {
aq.filters.push(filter);
}
}
NamedElement {
ns: Namespace::CardDav,
element: Element::TextMatch,
} => {
let mut tm = TextMatch::parse(raw)?;
tm.value = stream.collect_string_value()?.unwrap_or_default();
if let Some(filter) = Filter::from_parts(
(),
property.clone(),
parameter.clone(),
FilterOp::TextMatch(tm),
) {
aq.filters.push(filter);
}
}
name => return Err(name.into_unexpected()),
},
Token::ElementEnd => {
depth -= 1;
if depth == 0 {
break;
}
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
element => return Err(element.into_unexpected()),
}
}
Ok(aq)
}
}
impl DavParser for FreeBusyQuery {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
match stream.token()? {
Token::ElementStart {
name:
NamedElement {
ns: Namespace::CalDav,
element: Element::TimeRange,
},
raw,
} => TimeRange::from_raw(&raw).map(|range| FreeBusyQuery { range }),
other => Err(other.into_unexpected()),
}
}
}
impl DavParser for MultiGet {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut mg = MultiGet {
properties: PropFind::AllProp(vec![]),
hrefs: vec![],
};
loop {
match stream.token()? {
Token::ElementStart { name, .. } => match name {
NamedElement {
ns: Namespace::Dav,
element: Element::Propname,
} => {
mg.properties = PropFind::PropName;
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::Allprop,
} => {
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
} => {
mg.properties = PropFind::Prop(stream.collect_properties(Vec::new())?);
}
NamedElement {
ns: Namespace::Dav,
element: Element::Href,
} => {
if let Some(href) = stream.collect_string_value()? {
mg.hrefs.push(href);
}
}
name => return Err(name.into_unexpected()),
},
Token::ElementEnd => {
break;
}
element => return Err(element.into_unexpected()),
}
}
Ok(mg)
}
}
impl DavParser for SyncCollection {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut sc = SyncCollection {
properties: PropFind::AllProp(vec![]),
limit: None,
sync_token: None,
depth: Depth::None,
};
loop {
match stream.token()? {
Token::ElementStart { name, .. } => match name {
NamedElement {
ns: Namespace::Dav,
element: Element::Prop,
} => {
sc.properties = PropFind::Prop(stream.collect_properties(Vec::new())?);
}
NamedElement {
ns: Namespace::Dav,
element: Element::Limit,
} => {
stream.expect_named_element(NamedElement::dav(Element::Nresults))?;
if let Some(Ok(limit)) = stream.parse_value::<u32>()? {
sc.limit = limit.into();
}
stream.expect_element_end()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::SyncToken,
} => {
sc.sync_token = stream.collect_string_value()?;
}
NamedElement {
ns: Namespace::Dav,
element: Element::SyncLevel,
} => {
if let Some(Ok(depth)) = stream.parse_value::<Depth>()? {
sc.depth = depth;
}
}
name => return Err(name.into_unexpected()),
},
Token::ElementEnd => {
break;
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
element => return Err(element.into_unexpected()),
}
}
Ok(sc)
}
}
impl DavParser for ExpandProperty {
fn parse(stream: &mut Tokenizer<'_>) -> crate::parser::Result<Self> {
let mut ep = ExpandProperty { properties: vec![] };
let mut depth = 1;
loop {
match stream.token()? {
Token::ElementStart { name, raw } => match name {
NamedElement {
ns,
element: Element::Property,
} => {
for attribute in raw.attributes::<String>() {
if let Attribute::Name(name) = attribute? {
if let Some(property) = Element::try_parse(name.as_bytes())
.copied()
.and_then(|element| {
DavProperty::from_element(NamedElement { ns, element })
})
{
ep.properties.push(ExpandPropertyItem {
property,
depth: depth - 1,
});
} else {
let attrs = raw.element.attributes_raw().trim_ascii();
ep.properties.push(ExpandPropertyItem {
property: DavProperty::DeadProperty(DeadElementTag {
name,
attrs: (!attrs.is_empty()).then(|| {
String::from_utf8_lossy(attrs).into_owned()
}),
}),
depth: depth - 1,
});
}
break;
}
}
depth += 1;
}
name => return Err(name.into_unexpected()),
},
Token::ElementEnd => {
depth -= 1;
if depth == 0 {
break;
}
}
Token::UnknownElement(_) => {
stream.seek_element_end()?;
}
element => return Err(element.into_unexpected()),
}
}
Ok(ep)
}
}
impl TextMatch {
fn parse(raw: RawElement<'_>) -> crate::parser::Result<Self> {
let mut tm = TextMatch {
match_type: MatchType::Contains,
value: String::new(),
collation: Collation::AsciiCasemap,
negate: false,
};
for attribute in raw.attributes::<String>() {
match attribute? {
Attribute::MatchType(match_type) => {
tm.match_type = match_type;
}
Attribute::NegateCondition(negate) => {
tm.negate = negate;
}
Attribute::Collation(collation) => {
tm.collation = collation;
}
_ => {}
}
}
Ok(tm)
}
}
impl<A, B, C> Filter<A, B, C> {
fn from_parts(comp: A, prop: Option<B>, param: Option<C>, op: FilterOp) -> Option<Self> {
match (prop, param) {
(Some(prop), Some(param)) => Some(Filter::Parameter {
comp,
prop,
param,
op,
}),
(Some(prop), None) => Some(Filter::Property { comp, prop, op }),
(None, None) => Some(Filter::Component { comp, op }),
_ => None,
}
}
fn components(&self) -> Option<&A> {
match self {
Filter::Component { comp, .. } => Some(comp),
Filter::Property { comp, .. } => Some(comp),
Filter::Parameter { comp, .. } => Some(comp),
_ => None,
}
}
fn parse(raw: RawElement<'_>) -> crate::parser::Result<Option<Self>> {
for attribute in raw.attributes::<String>() {
if let Attribute::TestAllOf(all_of) = attribute? {
return Ok(Some(if all_of { Filter::AllOf } else { Filter::AnyOf }));
}
}
Ok(None)
}
}
impl XmlValueParser for Depth {
fn parse_bytes(bytes: &[u8]) -> Option<Self> {
Depth::parse(bytes)
}
fn parse_str(text: &str) -> Option<Self> {
Depth::parse(text.as_bytes())
}
}