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:
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
Reference in New Issue
Block a user