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.
642 lines
25 KiB
Rust
642 lines
25 KiB
Rust
/*
|
|
* 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())
|
|
}
|
|
}
|