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.
236 lines
7.4 KiB
Rust
236 lines
7.4 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use crate::{
|
|
DavError,
|
|
common::{
|
|
AddressbookFilter, DavQuery,
|
|
propfind::{PropFindItem, PropFindRequestHandler},
|
|
uri::DavUriResource,
|
|
},
|
|
};
|
|
use calcard::vcard::{
|
|
ArchivedVCard, ArchivedVCardEntry, ArchivedVCardParameter, VCardParameterName, VCardProperty,
|
|
VCardVersion,
|
|
};
|
|
use common::{Server, auth::AccessToken};
|
|
use dav_proto::{
|
|
RequestHeaders,
|
|
schema::{
|
|
property::CardDavPropertyName,
|
|
request::{AddressbookQuery, Filter, FilterOp, VCardPropertyWithGroup},
|
|
response::MultiStatus,
|
|
},
|
|
};
|
|
use groupware::cache::GroupwareCache;
|
|
use http_proto::HttpResponse;
|
|
use hyper::StatusCode;
|
|
use std::fmt::Write;
|
|
use trc::AddContext;
|
|
use types::{acl::Acl, collection::SyncCollection};
|
|
|
|
pub(crate) trait CardQueryRequestHandler: Sync + Send {
|
|
fn handle_card_query_request(
|
|
&self,
|
|
access_token: &AccessToken,
|
|
headers: &RequestHeaders<'_>,
|
|
request: AddressbookQuery,
|
|
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
|
}
|
|
|
|
impl CardQueryRequestHandler for Server {
|
|
async fn handle_card_query_request(
|
|
&self,
|
|
access_token: &AccessToken,
|
|
headers: &RequestHeaders<'_>,
|
|
request: AddressbookQuery,
|
|
) -> crate::Result<HttpResponse> {
|
|
// Validate URI
|
|
let resource_ = self
|
|
.validate_uri(access_token, headers.uri)
|
|
.await?
|
|
.into_owned_uri()?;
|
|
let account_id = resource_.account_id;
|
|
let resources = self
|
|
.fetch_dav_resources(
|
|
access_token.account_id(),
|
|
account_id,
|
|
SyncCollection::AddressBook,
|
|
)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
let Some(resource) = resources.by_path(
|
|
resource_
|
|
.resource
|
|
.ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?,
|
|
) else {
|
|
return Ok(HttpResponse::new(StatusCode::MULTI_STATUS)
|
|
.with_xml_body(MultiStatus::not_found(headers.uri).to_string()));
|
|
};
|
|
if !resource.is_container() {
|
|
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
|
|
}
|
|
|
|
// Obtain shared ids
|
|
let shared_ids = if !access_token.is_member(account_id) {
|
|
resources
|
|
.shared_items(access_token, [Acl::ReadItems], false)
|
|
.into()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Obtain document ids in folder
|
|
let mut items = Vec::with_capacity(16);
|
|
for resource in resources.children(resource.document_id()) {
|
|
if shared_ids
|
|
.as_ref()
|
|
.is_none_or(|ids| ids.contains(resource.document_id()))
|
|
{
|
|
items.push(PropFindItem::new(
|
|
resources.format_resource(resource),
|
|
account_id,
|
|
resource,
|
|
));
|
|
}
|
|
}
|
|
|
|
self.handle_dav_query(
|
|
access_token,
|
|
DavQuery::addressbook_query(request, items, headers),
|
|
)
|
|
.await
|
|
}
|
|
}
|
|
|
|
pub(crate) fn vcard_query(card: &ArchivedVCard, filters: &AddressbookFilter) -> bool {
|
|
let mut is_all = true;
|
|
let mut matches_one = false;
|
|
|
|
for filter in filters {
|
|
match filter {
|
|
Filter::AnyOf => {
|
|
is_all = false;
|
|
}
|
|
Filter::AllOf => {
|
|
is_all = true;
|
|
}
|
|
Filter::Property { prop, op, .. } => {
|
|
let mut properties = find_properties(card, prop).peekable();
|
|
let result = if properties.peek().is_some() {
|
|
properties.any(|entry| match op {
|
|
FilterOp::Exists => true,
|
|
FilterOp::Undefined => false,
|
|
FilterOp::TextMatch(text_match) => {
|
|
let mut matched_any = false;
|
|
|
|
for value in entry.values.iter() {
|
|
if let Some(text) = value.as_text()
|
|
&& text_match.matches(text)
|
|
{
|
|
matched_any = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
matched_any
|
|
}
|
|
FilterOp::TimeRange(_) => false,
|
|
})
|
|
} else {
|
|
matches!(op, FilterOp::Undefined)
|
|
};
|
|
|
|
if result {
|
|
matches_one = true;
|
|
} else if is_all {
|
|
return false;
|
|
}
|
|
}
|
|
Filter::Parameter {
|
|
prop, param, op, ..
|
|
} => {
|
|
let mut properties = find_properties(card, prop)
|
|
.filter_map(|entry| find_parameter(entry, param))
|
|
.peekable();
|
|
let result = if properties.peek().is_some() {
|
|
properties.any(|entry| match op {
|
|
FilterOp::Exists => true,
|
|
FilterOp::Undefined => false,
|
|
FilterOp::TextMatch(text_match) => {
|
|
if let Some(text) = entry.value.as_text() {
|
|
text_match.matches(text)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
FilterOp::TimeRange(_) => false,
|
|
})
|
|
} else {
|
|
matches!(op, FilterOp::Undefined)
|
|
};
|
|
|
|
if result {
|
|
matches_one = true;
|
|
} else if is_all {
|
|
return false;
|
|
}
|
|
}
|
|
Filter::Component { .. } => {}
|
|
}
|
|
}
|
|
|
|
is_all || matches_one
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn find_properties<'x>(
|
|
card: &'x ArchivedVCard,
|
|
prop: &VCardPropertyWithGroup,
|
|
) -> impl Iterator<Item = &'x ArchivedVCardEntry> {
|
|
card.entries
|
|
.iter()
|
|
.filter(move |entry| entry.name == prop.name && entry.group == prop.group)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn find_parameter<'x>(
|
|
entry: &'x ArchivedVCardEntry,
|
|
name: &VCardParameterName,
|
|
) -> Option<&'x ArchivedVCardParameter> {
|
|
entry.params.iter().find(|param| param.name == *name)
|
|
}
|
|
|
|
pub(crate) fn serialize_vcard_with_props(
|
|
card: &ArchivedVCard,
|
|
props: &[CardDavPropertyName],
|
|
version: VCardVersion,
|
|
) -> String {
|
|
let mut vcard = String::with_capacity(128);
|
|
if !props.is_empty() {
|
|
let _ = write!(&mut vcard, "BEGIN:VCARD\r\n");
|
|
let is_v4 = matches!(version, VCardVersion::V4_0);
|
|
|
|
for entry in card.entries.iter() {
|
|
for item in props {
|
|
if entry.name == item.name && entry.group == item.group {
|
|
if item.name != VCardProperty::Version {
|
|
let _ = entry.write_to(&mut vcard, !item.no_value, is_v4);
|
|
} else {
|
|
let _ = write!(&mut vcard, "VERSION:{version}\r\n");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
let _ = write!(&mut vcard, "END:VCARD\r\n");
|
|
} else {
|
|
let _ = card.write_to(&mut vcard, version);
|
|
}
|
|
|
|
vcard
|
|
}
|