Groupware caching improvements

This commit is contained in:
mdecimus
2025-05-16 16:20:05 +02:00
parent e69b5ec2b7
commit fe7d646966
54 changed files with 2138 additions and 1963 deletions
+16 -2
View File
@@ -116,9 +116,23 @@ impl Caches {
+ (1024 * std::mem::size_of::<MessageUidCache>())
+ (15 * (std::mem::size_of::<MailboxCache>() + 60))) as u64,
),
dav: Cache::from_config(
files: Cache::from_config(
config,
"dav",
"files",
MB_10,
(std::mem::size_of::<DavResources>() + (500 * std::mem::size_of::<DavResource>()))
as u64,
),
events: Cache::from_config(
config,
"events",
MB_10,
(std::mem::size_of::<DavResources>() + (500 * std::mem::size_of::<DavResource>()))
as u64,
),
contacts: Cache::from_config(
config,
"contacts",
MB_10,
(std::mem::size_of::<DavResources>() + (500 * std::mem::size_of::<DavResource>()))
as u64,
+239 -67
View File
@@ -33,7 +33,7 @@ use nlp::bayes::{TokenHash, Weights};
use parking_lot::{Mutex, RwLock};
use rustls::sign::CertifiedKey;
use std::{
hash::{BuildHasher, Hasher},
hash::{BuildHasher, Hash, Hasher},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::{
Arc,
@@ -46,7 +46,6 @@ use tinyvec::TinyVec;
use tokio::sync::{Notify, Semaphore, mpsc};
use tokio_rustls::TlsConnector;
use utils::{
bimap::{IdBimap, IdBimapItem},
cache::{Cache, CacheItemWeight, CacheWithTtl},
snowflake::SnowflakeIdGenerator,
};
@@ -149,7 +148,9 @@ pub struct Caches {
pub permissions: Cache<u32, Arc<RolePermissions>>,
pub messages: Cache<u32, CacheSwap<MessageStoreCache>>,
pub dav: Cache<DavResourceId, Arc<DavResources>>,
pub files: Cache<u32, CacheSwap<DavResources>>,
pub contacts: Cache<u32, CacheSwap<DavResources>>,
pub events: Cache<u32, CacheSwap<DavResources>>,
pub bayes: CacheWithTtl<TokenHash, Weights>,
@@ -242,44 +243,72 @@ pub struct TlsConnectors {
pub struct NameWrapper(pub String);
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct DavResourceId {
pub account_id: u32,
pub collection: u8,
}
#[derive(Debug, Default)]
#[derive(Debug, Clone)]
pub struct DavResources {
pub base_path: String,
pub paths: IdBimap<DavResource>,
pub paths: AHashSet<DavPath>,
pub resources: Vec<DavResource>,
pub item_change_id: u64,
pub container_change_id: u64,
pub highest_change_id: u64,
pub size: u64,
pub modseq: Option<u64>,
pub update_lock: Arc<Semaphore>,
}
#[derive(Debug, Default, Clone)]
#[derive(Debug, Clone)]
pub struct DavPath {
pub path: String,
pub parent_id: Option<u32>,
pub hierarchy_seq: u32,
pub resource_idx: usize,
}
#[derive(Debug, Clone)]
pub struct DavResource {
pub document_id: u32,
pub parent_id: Option<u32>,
pub name: String,
pub data: DavResourceMetadata,
}
#[derive(Debug, Default, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct DavResourcePath<'x> {
pub path: &'x DavPath,
pub resource: &'x DavResource,
}
#[derive(Debug, Clone)]
pub enum DavResourceMetadata {
File {
size: u32,
hierarchy_sequence: u32,
is_container: bool,
name: String,
size: Option<u32>,
parent_id: Option<u32>,
acls: TinyVec<[AclGrant; 2]>,
},
Calendar {
name: String,
acls: TinyVec<[AclGrant; 2]>,
tz: Tz,
},
CalendarEvent {
names: TinyVec<[DavName; 2]>,
start: i64,
duration: u32,
},
#[default]
None,
AddressBook {
name: String,
acls: TinyVec<[AclGrant; 2]>,
},
ContactCard {
names: TinyVec<[DavName; 2]>,
},
}
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
#[rkyv(derive(Debug))]
pub struct DavName {
pub name: String,
pub parent_id: u32,
}
#[derive(Clone, Default)]
@@ -299,12 +328,6 @@ pub struct Core {
pub enterprise: Option<enterprise::Enterprise>,
}
impl CacheItemWeight for DavResourceId {
fn weight(&self) -> u64 {
std::mem::size_of::<DavResourceId>() as u64
}
}
impl<T: CacheItemWeight> CacheItemWeight for CacheSwap<T> {
fn weight(&self) -> u64 {
std::mem::size_of::<CacheSwap<T>>() as u64 + self.0.load().weight()
@@ -428,7 +451,9 @@ impl Default for Caches {
http_auth: Cache::new(1024, 10 * 1024 * 1024),
permissions: Cache::new(1024, 10 * 1024 * 1024),
messages: Cache::new(1024, 25 * 1024 * 1024),
dav: Cache::new(1024, 10 * 1024 * 1024),
files: Cache::new(1024, 10 * 1024 * 1024),
contacts: Cache::new(1024, 10 * 1024 * 1024),
events: Cache::new(1024, 10 * 1024 * 1024),
bayes: CacheWithTtl::new(1024, 10 * 1024 * 1024),
dns_rbl: CacheWithTtl::new(1024, 10 * 1024 * 1024),
dns_txt: CacheWithTtl::new(1024, 10 * 1024 * 1024),
@@ -480,45 +505,117 @@ pub fn ip_to_bytes_prefix(prefix: u8, ip: &IpAddr) -> Vec<u8> {
}
}
impl DavResourcePath<'_> {
#[inline(always)]
pub fn document_id(&self) -> u32 {
self.resource.document_id
}
#[inline(always)]
pub fn parent_id(&self) -> Option<u32> {
self.path.parent_id
}
#[inline(always)]
pub fn path(&self) -> &str {
self.path.path.as_str()
}
#[inline(always)]
pub fn is_container(&self) -> bool {
self.resource.is_container()
}
#[inline(always)]
pub fn hierarchy_seq(&self) -> u32 {
self.path.hierarchy_seq
}
#[inline(always)]
pub fn size(&self) -> u32 {
self.resource.size()
}
}
impl DavResources {
pub fn subtree(&self, search_path: &str) -> impl Iterator<Item = &DavResource> {
let prefix = format!("{search_path}/");
self.paths
pub fn by_path(&self, name: &str) -> Option<DavResourcePath<'_>> {
self.paths.get(name).map(|path| DavResourcePath {
path,
resource: &self.resources[path.resource_idx],
})
}
pub fn container_resource_by_id(&self, id: u32) -> Option<&DavResource> {
self.resources
.iter()
.filter(move |item| item.name.starts_with(&prefix) || item.name == search_path)
.find(|res| res.document_id == id && res.is_container())
}
pub fn subtree(&self, search_path: &str) -> impl Iterator<Item = DavResourcePath<'_>> {
let prefix = format!("{search_path}/");
self.paths.iter().filter_map(move |path| {
if path.path.starts_with(&prefix) || path.path == search_path {
Some(DavResourcePath {
path,
resource: &self.resources[path.resource_idx],
})
} else {
None
}
})
}
pub fn subtree_with_depth(
&self,
search_path: &str,
depth: usize,
) -> impl Iterator<Item = &DavResource> {
) -> impl Iterator<Item = DavResourcePath<'_>> {
let prefix = format!("{search_path}/");
self.paths.iter().filter(move |item| {
item.name
self.paths.iter().filter_map(move |path| {
if path
.path
.strip_prefix(&prefix)
.is_some_and(|name| name.as_bytes().iter().filter(|&&c| c == b'/').count() < depth)
|| item.name == search_path
|| path.path.as_str() == search_path
{
Some(DavResourcePath {
path,
resource: &self.resources[path.resource_idx],
})
} else {
None
}
})
}
pub fn tree_with_depth(&self, depth: usize) -> impl Iterator<Item = &DavResource> {
self.paths.iter().filter(move |item| {
item.name.as_bytes().iter().filter(|&&c| c == b'/').count() <= depth
pub fn tree_with_depth(&self, depth: usize) -> impl Iterator<Item = DavResourcePath<'_>> {
self.paths.iter().filter_map(move |path| {
if path.path.as_bytes().iter().filter(|&&c| c == b'/').count() <= depth {
Some(DavResourcePath {
path,
resource: &self.resources[path.resource_idx],
})
} else {
None
}
})
}
pub fn children(&self, parent_id: u32) -> impl Iterator<Item = &DavResource> {
pub fn children(&self, parent_id: u32) -> impl Iterator<Item = DavResourcePath<'_>> {
self.paths
.iter()
.filter(move |item| item.parent_id.is_some_and(|id| id == parent_id))
.map(|path| DavResourcePath {
path,
resource: &self.resources[path.resource_idx],
})
}
pub fn format_resource(&self, resource: &DavResource) -> String {
if resource.is_container() {
format!("{}{}/", self.base_path, resource.name)
pub fn format_resource(&self, resource: DavResourcePath<'_>) -> String {
if resource.resource.is_container() {
format!("{}{}/", self.base_path, resource.path.path)
} else {
format!("{}{}", self.base_path, resource.name)
format!("{}{}", self.base_path, resource.path.path)
}
}
@@ -532,59 +629,128 @@ impl DavResources {
}
impl DavResource {
pub fn is_child_of(&self, parent_id: u32) -> bool {
match &self.data {
DavResourceMetadata::File { parent_id: id, .. } => id.is_some_and(|id| id == parent_id),
DavResourceMetadata::CalendarEvent { names, .. } => {
names.iter().any(|name| name.parent_id == parent_id)
}
DavResourceMetadata::ContactCard { names } => {
names.iter().any(|name| name.parent_id == parent_id)
}
_ => false,
}
}
pub fn child_names(&self) -> Option<&[DavName]> {
match &self.data {
DavResourceMetadata::CalendarEvent { names, .. } => Some(names.as_slice()),
DavResourceMetadata::ContactCard { names } => Some(names.as_slice()),
_ => None,
}
}
pub fn container_name(&self) -> Option<&str> {
match &self.data {
DavResourceMetadata::File { name, .. } => Some(name.as_str()),
DavResourceMetadata::Calendar { name, .. } => Some(name.as_str()),
DavResourceMetadata::AddressBook { name, .. } => Some(name.as_str()),
_ => None,
}
}
pub fn has_hierarchy_changes(&self, other: &DavResource) -> bool {
match (&self.data, &other.data) {
(
DavResourceMetadata::File {
name: a,
parent_id: c,
..
},
DavResourceMetadata::File {
name: b,
parent_id: d,
..
},
) => a != b || c != d,
(
DavResourceMetadata::Calendar { name: a, .. },
DavResourceMetadata::Calendar { name: b, .. },
) => a != b,
(
DavResourceMetadata::AddressBook { name: a, .. },
DavResourceMetadata::AddressBook { name: b, .. },
) => a != b,
(
DavResourceMetadata::CalendarEvent { names: a, .. },
DavResourceMetadata::CalendarEvent { names: b, .. },
) => a != b,
(
DavResourceMetadata::ContactCard { names: a, .. },
DavResourceMetadata::ContactCard { names: b, .. },
) => a != b,
_ => unreachable!(),
}
}
pub fn event_time_range(&self) -> Option<(i64, i64)> {
match &self.data {
DavResourceMetadata::CalendarEvent { start, duration } => {
Some((*start, *start + *duration as i64))
}
DavResourceMetadata::CalendarEvent {
start, duration, ..
} => Some((*start, *start + *duration as i64)),
_ => None,
}
}
pub fn timezone(&self) -> Option<Tz> {
match &self.data {
DavResourceMetadata::Calendar { tz } => Some(*tz),
DavResourceMetadata::Calendar { tz, .. } => Some(*tz),
_ => None,
}
}
pub fn is_container(&self) -> bool {
match &self.data {
DavResourceMetadata::File { is_container, .. } => *is_container,
_ => self.parent_id.is_none(),
DavResourceMetadata::File { size, .. } => size.is_none(),
DavResourceMetadata::Calendar { .. } | DavResourceMetadata::AddressBook { .. } => true,
_ => false,
}
}
pub fn size(&self) -> u32 {
match &self.data {
DavResourceMetadata::File { size, .. } => *size,
DavResourceMetadata::File { size, .. } => size.unwrap_or_default(),
_ => 0,
}
}
pub fn hierarchy_sequence(&self) -> u32 {
pub fn acls(&self) -> Option<&[AclGrant]> {
match &self.data {
DavResourceMetadata::File {
hierarchy_sequence, ..
} => *hierarchy_sequence,
_ => {
if self.parent_id.is_none() {
0
} else {
1
}
}
DavResourceMetadata::File { acls, .. } => Some(acls.as_slice()),
DavResourceMetadata::Calendar { acls, .. } => Some(acls.as_slice()),
DavResourceMetadata::AddressBook { acls, .. } => Some(acls.as_slice()),
_ => None,
}
}
}
impl IdBimapItem for DavResource {
fn id(&self) -> &u32 {
&self.document_id
impl Hash for DavPath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.path.hash(state);
}
}
fn name(&self) -> &str {
&self.name
impl PartialEq for DavPath {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl Eq for DavPath {}
impl std::borrow::Borrow<str> for DavPath {
fn borrow(&self) -> &str {
&self.path
}
}
@@ -608,6 +774,12 @@ impl std::borrow::Borrow<u32> for DavResource {
}
}
impl DavName {
pub fn new(name: String, parent_id: u32) -> Self {
Self { name, parent_id }
}
}
impl MessageStoreCache {
pub fn assign_thread_id(&self, thread_name: &[u8], message_id: &[u8]) -> u32 {
let mut bytes = Vec::with_capacity(thread_name.len() + message_id.len());
+2 -2
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::auth::AccessToken;
use jmap_proto::types::{
acl::Acl,
value::{AclGrant, ArchivedAclGrant},
@@ -11,10 +12,9 @@ use jmap_proto::types::{
use rkyv::vec::ArchivedVec;
use utils::map::bitmap::Bitmap;
use crate::auth::AccessToken;
pub mod acl;
pub mod document;
pub mod resources;
pub trait EffectiveAcl {
fn effective_acl(&self, access_token: &AccessToken) -> Bitmap<Acl>;
+83
View File
@@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{DavResources, auth::AccessToken};
use jmap_proto::types::acl::Acl;
use store::roaring::RoaringBitmap;
use utils::map::bitmap::Bitmap;
impl DavResources {
pub fn shared_containers(
&self,
access_token: &AccessToken,
check_acls: impl IntoIterator<Item = Acl>,
match_any: bool,
) -> RoaringBitmap {
let check_acls = Bitmap::<Acl>::from_iter(check_acls);
let mut document_ids = RoaringBitmap::new();
for resource in &self.resources {
if let Some(acls) = resource.acls() {
for acl in acls {
if access_token.is_member(acl.account_id) {
let mut grants = acl.grants;
grants.intersection(&check_acls);
if grants == check_acls || (match_any && !grants.is_empty()) {
document_ids.insert(resource.document_id);
}
}
}
}
}
document_ids
}
pub fn has_access_to_container(
&self,
access_token: &AccessToken,
document_id: u32,
check_acls: impl Into<Bitmap<Acl>>,
) -> bool {
let check_acls = check_acls.into();
for resource in &self.resources {
if resource.document_id == document_id {
if let Some(acls) = resource.acls() {
for acl in acls {
if access_token.is_member(acl.account_id) {
let mut grants = acl.grants;
grants.intersection(&check_acls);
return !grants.is_empty();
}
}
break;
}
}
}
false
}
pub fn container_acl(&self, access_token: &AccessToken, document_id: u32) -> Bitmap<Acl> {
let mut account_acls = Bitmap::<Acl>::new();
for resource in &self.resources {
if resource.document_id == document_id {
if let Some(acls) = resource.acls() {
for acl in acls {
if access_token.is_member(acl.account_id) {
account_acls.union(&acl.grants);
}
}
break;
}
}
}
account_acls
}
}
-217
View File
@@ -1,217 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use ahash::AHashMap;
use jmap_proto::types::{collection::Collection, property::Property};
use store::{
Deserialize, IndexKey, IterateParams, SerializeInfallible, SerializedVersion, U32_LEN,
ValueKey,
write::{AlignedBytes, Archive, ValueClass, key::DeserializeBigEndian},
};
use trc::AddContext;
use utils::topological::{TopologicalSort, TopologicalSortIterator};
use crate::Server;
pub struct ExpandedFolders {
names: AHashMap<u32, ExpandedFolder>,
iter: TopologicalSortIterator<u32>,
}
#[derive(Debug, Clone)]
pub struct ExpandedFolder {
pub name: String,
pub document_id: u32,
pub parent_id: Option<u32>,
pub is_container: bool,
pub size: u32,
pub hierarchy_sequence: u32,
}
pub trait FolderHierarchy: Sync + Send {
fn name(&self) -> String;
fn parent_id(&self) -> u32;
fn is_container(&self) -> bool;
fn size(&self) -> u32;
}
pub trait TopologyBuilder: Sync + Send {
fn insert(&mut self, folder_id: u32, parent_id: u32);
}
impl Server {
pub async fn fetch_folders<T>(
&self,
account_id: u32,
collection: Collection,
) -> trc::Result<ExpandedFolders>
where
T: rkyv::Archive + SerializedVersion,
T::Archived: FolderHierarchy
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
> + rkyv::Deserialize<T, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>>,
{
let collection_: u8 = collection.into();
let mut names = AHashMap::with_capacity(10);
let mut topological_sort = TopologicalSort::with_capacity(10);
self.core
.storage
.data
.iterate(
IterateParams::new(
ValueKey {
account_id,
collection: collection_,
document_id: 0,
class: ValueClass::Property(Property::Value.into()),
},
ValueKey {
account_id,
collection: collection_,
document_id: u32::MAX,
class: ValueClass::Property(Property::Value.into()),
},
),
|key, value| {
let document_id = key.deserialize_be_u32(key.len() - U32_LEN)?;
let archive = <Archive<AlignedBytes> as Deserialize>::deserialize(value)?;
let folder = archive.unarchive::<T>()?;
let parent_id = folder.parent_id();
topological_sort.insert(parent_id, document_id + 1);
names.insert(
document_id,
ExpandedFolder {
name: folder.name(),
document_id,
parent_id: if parent_id > 0 {
Some(parent_id - 1)
} else {
None
},
is_container: folder.is_container(),
size: folder.size(),
hierarchy_sequence: 0,
},
);
Ok(true)
},
)
.await
.add_context(|err| {
err.caused_by(trc::location!())
.account_id(account_id)
.collection(collection)
})?;
Ok(ExpandedFolders {
names,
iter: topological_sort.into_iterator(),
})
}
pub async fn fetch_folder_topology<T>(
&self,
account_id: u32,
collection: Collection,
topology: &mut impl TopologyBuilder,
) -> trc::Result<()>
where
T: TopologyBuilder,
{
self.store()
.iterate(
IterateParams::new(
IndexKey {
account_id,
collection: collection.into(),
document_id: 0,
field: Property::ParentId.into(),
key: 0u32.serialize(),
},
IndexKey {
account_id,
collection: collection.into(),
document_id: u32::MAX,
field: Property::ParentId.into(),
key: u32::MAX.serialize(),
},
)
.no_values()
.ascending(),
|key, _| {
let document_id = key
.get(key.len() - U32_LEN..)
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))
.and_then(u32::deserialize)?;
let parent_id = key
.get(key.len() - (U32_LEN * 2)..key.len() - U32_LEN)
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))
.and_then(u32::deserialize)?;
topology.insert(document_id, parent_id);
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
Ok(())
}
}
impl ExpandedFolders {
pub fn len(&self) -> usize {
self.names.len()
}
pub fn is_empty(&self) -> bool {
self.names.is_empty()
}
pub fn format<T>(mut self, formatter: T) -> Self
where
T: Fn(&mut ExpandedFolder),
{
for folder in self.names.values_mut() {
formatter(folder);
}
self
}
pub fn into_iterator(mut self) -> impl Iterator<Item = ExpandedFolder> + Sync + Send {
for (hierarchy_sequence, folder_id) in self.iter.by_ref().enumerate() {
if folder_id != 0 {
let folder_id = folder_id - 1;
if let Some((name, parent_name)) = self
.names
.get(&folder_id)
.and_then(|folder| folder.parent_id.map(|parent_id| (&folder.name, parent_id)))
.and_then(|(name, parent_id)| {
self.names
.get(&parent_id)
.map(|folder| (name, &folder.name))
})
{
let name = format!("{parent_name}/{name}");
let folder = self.names.get_mut(&folder_id).unwrap();
folder.name = name;
folder.hierarchy_sequence = hierarchy_sequence as u32;
} else {
self.names.get_mut(&folder_id).unwrap().hierarchy_sequence =
hierarchy_sequence as u32;
}
}
}
self.names.into_values()
}
}
-1
View File
@@ -5,6 +5,5 @@
*/
pub mod blob;
pub mod folder;
pub mod index;
pub mod state;
+91 -124
View File
@@ -4,16 +4,19 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use common::{DavName, Server, auth::AccessToken};
use dav_proto::{Depth, RequestHeaders};
use groupware::{
DavName, DestroyArchive,
DestroyArchive,
cache::GroupwareCache,
calendar::{Calendar, CalendarEvent, CalendarPreferences, Timezone},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -51,33 +54,27 @@ impl CalendarCopyMoveRequestHandler for Server {
.into_owned_uri()?;
let from_account_id = from_resource_.account_id;
let from_resources = self
.fetch_dav_resources(access_token, from_account_id, Collection::Calendar)
.fetch_dav_resources(access_token, from_account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?;
let from_resource_name = from_resource_
.resource
.ok_or(DavError::Code(StatusCode::FORBIDDEN))?;
let from_resource = from_resources
.paths
.by_name(from_resource_name)
.by_path(from_resource_name)
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
// Validate ACL
if !access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::Calendar,
if from_resource.is_container() {
from_resource.document_id
} else {
from_resource.parent_id.unwrap()
},
Acl::ReadItems,
)
.await
.caused_by(trc::location!())?
&& !from_resources.has_access_to_container(
access_token,
if from_resource.is_container() {
from_resource.document_id()
} else {
from_resource.parent_id().unwrap()
},
Acl::ReadItems,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -101,7 +98,7 @@ impl CalendarCopyMoveRequestHandler for Server {
let to_resources = if to_account_id == from_account_id {
from_resources.clone()
} else {
self.fetch_dav_resources(access_token, to_account_id, Collection::Calendar)
self.fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?
};
@@ -110,7 +107,7 @@ impl CalendarCopyMoveRequestHandler for Server {
let destination_resource_name = destination
.resource
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?;
let to_resource = to_resources.paths.by_name(destination_resource_name);
let to_resource = to_resources.by_path(destination_resource_name);
self.validate_headers(
access_token,
&headers,
@@ -122,7 +119,7 @@ impl CalendarCopyMoveRequestHandler for Server {
} else {
Collection::CalendarEvent
},
document_id: Some(from_resource.document_id),
document_id: Some(from_resource.document_id()),
path: from_resource_name,
..Default::default()
},
@@ -137,7 +134,7 @@ impl CalendarCopyMoveRequestHandler for Server {
}
})
.unwrap_or(Collection::Calendar),
document_id: Some(to_resource.map(|r| r.document_id).unwrap_or(u32::MAX)),
document_id: Some(to_resource.map(|r| r.document_id()).unwrap_or(u32::MAX)),
path: destination_resource_name,
..Default::default()
},
@@ -153,7 +150,7 @@ impl CalendarCopyMoveRequestHandler for Server {
// Map destination
if let Some(to_resource) = to_resource {
if from_resource.name == to_resource.name {
if from_resource.path() == to_resource.path() {
// Same resource
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
}
@@ -167,31 +164,26 @@ impl CalendarCopyMoveRequestHandler for Server {
let from_children_ids = from_resources
.subtree(from_resource_name)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>();
let to_document_ids = to_resources
.subtree(destination_resource_name)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>();
// Validate ACLs
if !access_token.is_member(to_account_id)
|| (!access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::Calendar,
from_resource.document_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?)
&& !from_resources.has_access_to_container(
access_token,
from_resource.document_id(),
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
))
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -201,10 +193,10 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_children_ids,
to_account_id,
to_resource.document_id.into(),
to_resource.document_id().into(),
to_document_ids,
new_name,
is_move,
@@ -213,36 +205,26 @@ impl CalendarCopyMoveRequestHandler for Server {
}
(false, false) => {
// Overwrite event
let from_calendar_id = from_resource.parent_id.unwrap();
let to_calendar_id = to_resource.parent_id.unwrap();
let from_calendar_id = from_resource.parent_id().unwrap();
let to_calendar_id = to_resource.parent_id().unwrap();
// Validate ACL
if (!access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::Calendar,
from_calendar_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?)
&& !from_resources.has_access_to_container(
access_token,
from_calendar_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
))
|| (!access_token.is_member(to_account_id)
&& !self
.has_access_to_document(
access_token,
to_account_id,
Collection::Calendar,
to_calendar_id,
Acl::RemoveItems,
)
.await
.caused_by(trc::location!())?)
&& !to_resources.has_access_to_container(
access_token,
to_calendar_id,
Acl::RemoveItems,
))
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -252,10 +234,10 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_calendar_id,
to_account_id,
to_resource.document_id.into(),
to_resource.document_id().into(),
to_calendar_id,
new_name,
)
@@ -265,9 +247,9 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
to_account_id,
to_resource.document_id.into(),
to_resource.document_id().into(),
to_calendar_id,
new_name,
)
@@ -287,34 +269,24 @@ impl CalendarCopyMoveRequestHandler for Server {
}
// Validate ACL
let from_calendar_id = from_resource.parent_id.unwrap();
let to_calendar_id = parent_resource.document_id;
let from_calendar_id = from_resource.parent_id().unwrap();
let to_calendar_id = parent_resource.document_id();
if (!access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::Calendar,
from_calendar_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?)
&& !from_resources.has_access_to_container(
access_token,
from_calendar_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
))
|| (!access_token.is_member(to_account_id)
&& !self
.has_access_to_document(
access_token,
to_account_id,
Collection::Calendar,
to_calendar_id,
Acl::AddItems,
)
.await
.caused_by(trc::location!())?)
&& !to_resources.has_access_to_container(
access_token,
to_calendar_id,
Acl::AddItems,
))
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -322,13 +294,13 @@ impl CalendarCopyMoveRequestHandler for Server {
// Copy/move event
if is_move {
if from_account_id != to_account_id
|| parent_resource.document_id != from_calendar_id
|| parent_resource.document_id() != from_calendar_id
{
move_event(
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_calendar_id,
to_account_id,
None,
@@ -341,7 +313,7 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_calendar_id,
new_name,
)
@@ -352,7 +324,7 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
to_account_id,
None,
to_calendar_id,
@@ -373,20 +345,15 @@ impl CalendarCopyMoveRequestHandler for Server {
// Validate ACLs
if !access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::Calendar,
from_resource.document_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?
&& !from_resources.has_access_to_container(
access_token,
from_resource.document_id(),
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -395,7 +362,7 @@ impl CalendarCopyMoveRequestHandler for Server {
let from_children_ids = from_resources
.subtree(from_resource_name)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>();
if is_move {
if from_account_id != to_account_id {
@@ -403,7 +370,7 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
if headers.depth != Depth::Zero {
from_children_ids
} else {
@@ -421,7 +388,7 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
new_name,
)
.await
@@ -431,7 +398,7 @@ impl CalendarCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
if headers.depth != Depth::Zero {
from_children_ids
} else {
@@ -478,7 +445,7 @@ async fn copy_event(
assert_is_unique_uid(
server,
server
.fetch_dav_resources(access_token, to_account_id, Collection::Calendar)
.fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?
.as_ref(),
@@ -588,7 +555,7 @@ async fn move_event(
assert_is_unique_uid(
server,
server
.fetch_dav_resources(access_token, to_account_id, Collection::Calendar)
.fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?
.as_ref(),
+12 -19
View File
@@ -8,12 +8,15 @@ use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
use dav_proto::RequestHeaders;
use groupware::{
DestroyArchive,
cache::GroupwareCache,
calendar::{Calendar, CalendarEvent},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -51,16 +54,15 @@ impl CalendarDeleteRequestHandler for Server {
.filter(|r| !r.is_empty())
.ok_or(DavError::Code(StatusCode::FORBIDDEN))?;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::Calendar)
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?;
// Check resource type
let delete_resource = resources
.paths
.by_name(delete_path)
.by_path(delete_path)
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
let document_id = delete_resource.document_id;
let document_id = delete_resource.document_id();
// Fetch entry
let mut batch = BatchBuilder::new();
@@ -113,7 +115,7 @@ impl CalendarDeleteRequestHandler for Server {
resources
.subtree(delete_path)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>(),
&mut batch,
)
@@ -121,18 +123,9 @@ impl CalendarDeleteRequestHandler for Server {
.caused_by(trc::location!())?;
} else {
// Validate ACL
let addresscalendar_id = delete_resource.parent_id.unwrap();
let calendar_id = delete_resource.parent_id().unwrap();
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::Calendar,
addresscalendar_id,
Acl::RemoveItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(access_token, calendar_id, Acl::RemoveItems)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -170,7 +163,7 @@ impl CalendarDeleteRequestHandler for Server {
access_token,
account_id,
document_id,
addresscalendar_id,
calendar_id,
&mut batch,
)
.caused_by(trc::location!())?;
+15 -20
View File
@@ -23,10 +23,13 @@ use dav_proto::{
RequestHeaders,
schema::{property::TimeRange, request::FreeBusyQuery},
};
use groupware::{calendar::CalendarEvent, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, calendar::CalendarEvent};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::{
ahash::AHashMap,
write::{now, serialize::rkyv_deserialize},
@@ -56,12 +59,11 @@ impl CalendarFreebusyRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource_.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::Calendar)
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?;
let resource = resources
.paths
.by_name(
.by_path(
resource_
.resource
.ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?,
@@ -70,20 +72,13 @@ impl CalendarFreebusyRequestHandler for Server {
if !resource.is_container() {
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
}
let default_tz = resource.timezone().unwrap_or(Tz::UTC);
let default_tz = resource.resource.timezone().unwrap_or(Tz::UTC);
// Obtain shared ids
let shared_ids = if !access_token.is_member(account_id) {
self.shared_containers(
access_token,
account_id,
Collection::Calendar,
[Acl::ReadItems, Acl::ReadFreeBusy],
false,
)
.await
.caused_by(trc::location!())?
.into()
resources
.shared_containers(access_token, [Acl::ReadItems, Acl::ReadFreeBusy], false)
.into()
} else {
None
};
@@ -114,14 +109,14 @@ impl CalendarFreebusyRequestHandler for Server {
});
let document_ids = resources
.children(resource.document_id)
.children(resource.document_id())
.filter(|resource| {
shared_ids
.as_ref()
.is_none_or(|ids| ids.contains(resource.document_id))
&& is_resource_in_time_range(resource, &range)
.is_none_or(|ids| ids.contains(resource.document_id()))
&& is_resource_in_time_range(resource.resource, &range)
})
.map(|resource| resource.document_id)
.map(|resource| resource.document_id())
.collect::<Vec<_>>();
let mut fb_entries: AHashMap<ICalendarFreeBusyType, Vec<(i64, i64)>> =
+18 -17
View File
@@ -6,10 +6,13 @@
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime};
use groupware::{calendar::CalendarEvent, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, calendar::CalendarEvent};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use trc::AddContext;
use crate::{
@@ -44,12 +47,11 @@ impl CalendarGetRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource_.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::Calendar)
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?;
let resource = resources
.paths
.by_name(
.by_path(
resource_
.resource
.ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?,
@@ -61,23 +63,22 @@ impl CalendarGetRequestHandler for Server {
// Validate ACL
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::Calendar,
resource.parent_id.unwrap(),
Acl::ReadItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(
access_token,
resource.parent_id().unwrap(),
Acl::ReadItems,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
// Fetch event
let event_ = self
.get_archive(account_id, Collection::CalendarEvent, resource.document_id)
.get_archive(
account_id,
Collection::CalendarEvent,
resource.document_id(),
)
.await
.caused_by(trc::location!())?
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
@@ -93,7 +94,7 @@ impl CalendarGetRequestHandler for Server {
vec![ResourceState {
account_id,
collection: Collection::CalendarEvent,
document_id: resource.document_id.into(),
document_id: resource.document_id().into(),
etag: etag.clone().into(),
path: resource_.resource.unwrap(),
..Default::default()
+5 -8
View File
@@ -9,13 +9,10 @@ use dav_proto::{
RequestHeaders, Return,
schema::{Namespace, request::MkCol, response::MkColResponse},
};
use groupware::{
calendar::{Calendar, CalendarPreferences},
hierarchy::DavHierarchy,
};
use groupware::{cache::GroupwareCache, calendar::{Calendar, CalendarPreferences}};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::collection::Collection;
use jmap_proto::types::collection::{Collection, SyncCollection};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -59,11 +56,11 @@ impl CalendarMkColRequestHandler for Server {
return Err(DavError::Code(StatusCode::FORBIDDEN));
} else if name.contains('/')
|| self
.fetch_dav_resources(access_token, account_id, Collection::Calendar)
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?
.paths
.by_name(name)
.by_path(name)
.is_some()
{
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
+2 -5
View File
@@ -106,11 +106,8 @@ pub(crate) async fn assert_is_unique_uid(
.caused_by(trc::location!())?;
if !hits.results.is_empty() {
for path in resources.paths.iter() {
if !path.is_container()
&& hits.results.contains(path.document_id)
&& path.parent_id.unwrap() == calendar_id
{
for path in resources.children(calendar_id) {
if hits.results.contains(path.document_id()) {
return Err(DavError::Condition(DavErrorCondition::new(
StatusCode::PRECONDITION_FAILED,
CalCondition::NoUidConflict(resources.format_resource(path).into()),
+11 -18
View File
@@ -26,12 +26,15 @@ use dav_proto::{
},
};
use groupware::{
cache::GroupwareCache,
calendar::{Calendar, CalendarEvent, Timezone},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -76,14 +79,14 @@ impl CalendarPropPatchRequestHandler for Server {
let uri = headers.uri;
let account_id = resource_.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::Calendar)
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?;
let resource = resource_
.resource
.and_then(|r| resources.paths.by_name(r))
.and_then(|r| resources.by_path(r))
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
let document_id = resource.document_id;
let document_id = resource.document_id();
let collection = if resource.is_container() {
Collection::Calendar
} else {
@@ -97,22 +100,12 @@ impl CalendarPropPatchRequestHandler for Server {
// Verify ACL
if !access_token.is_member(account_id) {
let (acl, document_id) = if resource.is_container() {
(Acl::Modify, resource.document_id)
(Acl::Modify, resource.document_id())
} else {
(Acl::ModifyItems, resource.parent_id.unwrap())
(Acl::ModifyItems, resource.parent_id().unwrap())
};
if !self
.has_access_to_document(
access_token,
account_id,
Collection::Calendar,
document_id,
acl,
)
.await
.caused_by(trc::location!())?
{
if !resources.has_access_to_container(access_token, document_id, acl) {
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
}
+11 -19
View File
@@ -29,10 +29,10 @@ use dav_proto::{
request::{CalendarQuery, Filter, FilterOp, PropFind, Timezone},
},
};
use groupware::{calendar::ArchivedCalendarEvent, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, calendar::ArchivedCalendarEvent};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{acl::Acl, collection::SyncCollection};
use std::{fmt::Write, slice::Iter, str::FromStr};
use store::{ahash::AHashMap, write::serialize::rkyv_deserialize};
use trc::AddContext;
@@ -62,12 +62,11 @@ impl CalendarQueryRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource_.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::Calendar)
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?;
let resource = resources
.paths
.by_name(
.by_path(
resource_
.resource
.ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?,
@@ -79,16 +78,9 @@ impl CalendarQueryRequestHandler for Server {
// Obtain shared ids
let shared_ids = if !access_token.is_member(account_id) {
self.shared_containers(
access_token,
account_id,
Collection::Calendar,
[Acl::ReadItems],
false,
)
.await
.caused_by(trc::location!())?
.into()
resources
.shared_containers(access_token, [Acl::ReadItems], false)
.into()
} else {
None
};
@@ -98,13 +90,13 @@ impl CalendarQueryRequestHandler for Server {
// Obtain document ids in folder
let mut items = Vec::with_capacity(16);
for resource in resources.children(resource.document_id) {
for resource in resources.children(resource.document_id()) {
if shared_ids
.as_ref()
.is_none_or(|ids| ids.contains(resource.document_id))
.is_none_or(|ids| ids.contains(resource.document_id()))
&& filter_range
.as_ref()
.is_none_or(|range| is_resource_in_time_range(resource, range))
.is_none_or(|range| is_resource_in_time_range(resource.resource, range))
{
items.push(PropFindItem::new(
resources.format_resource(resource),
@@ -134,7 +126,7 @@ pub(crate) fn is_resource_in_time_range(resource: &DavResource, filter: &TimeRan
let c = println!(
"filter from {range_from} to {range_end}, resource is {} from {} to {}, result: {}",
resource.name,
resource.path(),
DateTime::from_timestamp(start, 0).unwrap(),
DateTime::from_timestamp(end, 0).unwrap(),
result
+18 -30
View File
@@ -11,19 +11,21 @@ use calcard::{
common::timezone::Tz,
icalendar::{ICalendar, ICalendarComponentType},
};
use common::{Server, auth::AccessToken};
use common::{DavName, Server, auth::AccessToken};
use dav_proto::{
RequestHeaders, Return,
schema::{property::Rfc1123DateTime, response::CalCondition},
};
use groupware::{
DavName,
cache::GroupwareCache,
calendar::{CalendarEvent, CalendarEventData},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -64,7 +66,7 @@ impl CalendarUpdateRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::Calendar)
.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?;
let resource_name = resource
@@ -94,25 +96,16 @@ impl CalendarUpdateRequestHandler for Server {
}
};
if let Some(resource) = resources.paths.by_name(resource_name) {
if let Some(resource) = resources.by_path(resource_name) {
if resource.is_container() {
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
}
// Validate ACL
let parent_id = resource.parent_id.unwrap();
let document_id = resource.document_id;
let parent_id = resource.parent_id().unwrap();
let document_id = resource.document_id();
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::Calendar,
parent_id,
Acl::ModifyItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(access_token, parent_id, Acl::ModifyItems)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -204,16 +197,11 @@ impl CalendarUpdateRequestHandler for Server {
// Validate ACL
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::Calendar,
parent.document_id,
Acl::AddItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(
access_token,
parent.document_id(),
Acl::AddItems,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -248,7 +236,7 @@ impl CalendarUpdateRequestHandler for Server {
self,
&resources,
account_id,
parent.document_id,
parent.document_id(),
validate_ical(&ical)?.into(),
)
.await?;
@@ -257,7 +245,7 @@ impl CalendarUpdateRequestHandler for Server {
let event = CalendarEvent {
names: vec![DavName {
name: name.to_string(),
parent_id: parent.document_id,
parent_id: parent.document_id(),
}],
data: CalendarEventData::new(
ical,
+91 -124
View File
@@ -4,16 +4,19 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use common::{DavName, Server, auth::AccessToken};
use dav_proto::{Depth, RequestHeaders};
use groupware::{
DavName, DestroyArchive,
DestroyArchive,
cache::GroupwareCache,
contact::{AddressBook, ContactCard},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -51,33 +54,27 @@ impl CardCopyMoveRequestHandler for Server {
.into_owned_uri()?;
let from_account_id = from_resource_.account_id;
let from_resources = self
.fetch_dav_resources(access_token, from_account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, from_account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?;
let from_resource_name = from_resource_
.resource
.ok_or(DavError::Code(StatusCode::FORBIDDEN))?;
let from_resource = from_resources
.paths
.by_name(from_resource_name)
.by_path(from_resource_name)
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
// Validate ACL
if !access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::AddressBook,
if from_resource.is_container() {
from_resource.document_id
} else {
from_resource.parent_id.unwrap()
},
Acl::ReadItems,
)
.await
.caused_by(trc::location!())?
&& !from_resources.has_access_to_container(
access_token,
if from_resource.is_container() {
from_resource.document_id()
} else {
from_resource.parent_id().unwrap()
},
Acl::ReadItems,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -101,7 +98,7 @@ impl CardCopyMoveRequestHandler for Server {
let to_resources = if to_account_id == from_account_id {
from_resources.clone()
} else {
self.fetch_dav_resources(access_token, to_account_id, Collection::AddressBook)
self.fetch_dav_resources(access_token, to_account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?
};
@@ -110,7 +107,7 @@ impl CardCopyMoveRequestHandler for Server {
let destination_resource_name = destination
.resource
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?;
let to_resource = to_resources.paths.by_name(destination_resource_name);
let to_resource = to_resources.by_path(destination_resource_name);
self.validate_headers(
access_token,
&headers,
@@ -122,7 +119,7 @@ impl CardCopyMoveRequestHandler for Server {
} else {
Collection::ContactCard
},
document_id: Some(from_resource.document_id),
document_id: Some(from_resource.document_id()),
path: from_resource_name,
..Default::default()
},
@@ -137,7 +134,7 @@ impl CardCopyMoveRequestHandler for Server {
}
})
.unwrap_or(Collection::AddressBook),
document_id: Some(to_resource.map(|r| r.document_id).unwrap_or(u32::MAX)),
document_id: Some(to_resource.map(|r| r.document_id()).unwrap_or(u32::MAX)),
path: destination_resource_name,
..Default::default()
},
@@ -153,7 +150,7 @@ impl CardCopyMoveRequestHandler for Server {
// Map destination
if let Some(to_resource) = to_resource {
if from_resource.name == to_resource.name {
if from_resource.path() == to_resource.path() {
// Same resource
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
}
@@ -167,31 +164,26 @@ impl CardCopyMoveRequestHandler for Server {
let from_children_ids = from_resources
.subtree(from_resource_name)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>();
let to_document_ids = to_resources
.subtree(destination_resource_name)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>();
// Validate ACLs
if !access_token.is_member(to_account_id)
|| (!access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::AddressBook,
from_resource.document_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?)
&& !from_resources.has_access_to_container(
access_token,
from_resource.document_id(),
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
))
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -201,10 +193,10 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_children_ids,
to_account_id,
to_resource.document_id.into(),
to_resource.document_id().into(),
to_document_ids,
new_name,
is_move,
@@ -213,36 +205,26 @@ impl CardCopyMoveRequestHandler for Server {
}
(false, false) => {
// Overwrite card
let from_addressbook_id = from_resource.parent_id.unwrap();
let to_addressbook_id = to_resource.parent_id.unwrap();
let from_addressbook_id = from_resource.parent_id().unwrap();
let to_addressbook_id = to_resource.parent_id().unwrap();
// Validate ACL
if (!access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::AddressBook,
from_addressbook_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?)
&& !from_resources.has_access_to_container(
access_token,
from_addressbook_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
))
|| (!access_token.is_member(to_account_id)
&& !self
.has_access_to_document(
access_token,
to_account_id,
Collection::AddressBook,
to_addressbook_id,
Acl::RemoveItems,
)
.await
.caused_by(trc::location!())?)
&& !to_resources.has_access_to_container(
access_token,
to_addressbook_id,
Acl::RemoveItems,
))
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -252,10 +234,10 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_addressbook_id,
to_account_id,
to_resource.document_id.into(),
to_resource.document_id().into(),
to_addressbook_id,
new_name,
)
@@ -265,9 +247,9 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
to_account_id,
to_resource.document_id.into(),
to_resource.document_id().into(),
to_addressbook_id,
new_name,
)
@@ -287,34 +269,24 @@ impl CardCopyMoveRequestHandler for Server {
}
// Validate ACL
let from_addressbook_id = from_resource.parent_id.unwrap();
let to_addressbook_id = parent_resource.document_id;
let from_addressbook_id = from_resource.parent_id().unwrap();
let to_addressbook_id = parent_resource.document_id();
if (!access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::AddressBook,
from_addressbook_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?)
&& !from_resources.has_access_to_container(
access_token,
from_addressbook_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
))
|| (!access_token.is_member(to_account_id)
&& !self
.has_access_to_document(
access_token,
to_account_id,
Collection::AddressBook,
to_addressbook_id,
Acl::AddItems,
)
.await
.caused_by(trc::location!())?)
&& !to_resources.has_access_to_container(
access_token,
to_addressbook_id,
Acl::AddItems,
))
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -322,13 +294,13 @@ impl CardCopyMoveRequestHandler for Server {
// Copy/move card
if is_move {
if from_account_id != to_account_id
|| parent_resource.document_id != from_addressbook_id
|| parent_resource.document_id() != from_addressbook_id
{
move_card(
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_addressbook_id,
to_account_id,
None,
@@ -341,7 +313,7 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
from_addressbook_id,
new_name,
)
@@ -352,7 +324,7 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
to_account_id,
None,
to_addressbook_id,
@@ -373,20 +345,15 @@ impl CardCopyMoveRequestHandler for Server {
// Validate ACLs
if !access_token.is_member(from_account_id)
&& !self
.has_access_to_document(
access_token,
from_account_id,
Collection::AddressBook,
from_resource.document_id,
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
.await
.caused_by(trc::location!())?
&& !from_resources.has_access_to_container(
access_token,
from_resource.document_id(),
if is_move {
Acl::RemoveItems
} else {
Acl::ReadItems
},
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -395,7 +362,7 @@ impl CardCopyMoveRequestHandler for Server {
let from_children_ids = from_resources
.subtree(from_resource_name)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>();
if is_move {
if from_account_id != to_account_id {
@@ -403,7 +370,7 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
if headers.depth != Depth::Zero {
from_children_ids
} else {
@@ -421,7 +388,7 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
new_name,
)
.await
@@ -431,7 +398,7 @@ impl CardCopyMoveRequestHandler for Server {
self,
access_token,
from_account_id,
from_resource.document_id,
from_resource.document_id(),
if headers.depth != Depth::Zero {
from_children_ids
} else {
@@ -478,7 +445,7 @@ async fn copy_card(
assert_is_unique_uid(
server,
server
.fetch_dav_resources(access_token, to_account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, to_account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?
.as_ref(),
@@ -588,7 +555,7 @@ async fn move_card(
assert_is_unique_uid(
server,
server
.fetch_dav_resources(access_token, to_account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, to_account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?
.as_ref(),
+15 -18
View File
@@ -8,12 +8,15 @@ use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
use dav_proto::RequestHeaders;
use groupware::{
DestroyArchive,
cache::GroupwareCache,
contact::{AddressBook, ContactCard},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -51,16 +54,15 @@ impl CardDeleteRequestHandler for Server {
.filter(|r| !r.is_empty())
.ok_or(DavError::Code(StatusCode::FORBIDDEN))?;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?;
// Check resource type
let delete_resource = resources
.paths
.by_name(delete_path)
.by_path(delete_path)
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
let document_id = delete_resource.document_id;
let document_id = delete_resource.document_id();
// Fetch entry
let mut batch = BatchBuilder::new();
@@ -113,7 +115,7 @@ impl CardDeleteRequestHandler for Server {
resources
.subtree(delete_path)
.filter(|r| !r.is_container())
.map(|r| r.document_id)
.map(|r| r.document_id())
.collect::<Vec<_>>(),
&mut batch,
)
@@ -121,18 +123,13 @@ impl CardDeleteRequestHandler for Server {
.caused_by(trc::location!())?;
} else {
// Validate ACL
let addressbook_id = delete_resource.parent_id.unwrap();
let addressbook_id = delete_resource.parent_id().unwrap();
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::AddressBook,
addressbook_id,
Acl::RemoveItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(
access_token,
addressbook_id,
Acl::RemoveItems,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
+14 -17
View File
@@ -6,10 +6,13 @@
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime};
use groupware::{contact::ContactCard, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, contact::ContactCard};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use trc::AddContext;
use crate::{
@@ -44,12 +47,11 @@ impl CardGetRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource_.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?;
let resource = resources
.paths
.by_name(
.by_path(
resource_
.resource
.ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?,
@@ -61,23 +63,18 @@ impl CardGetRequestHandler for Server {
// Validate ACL
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::AddressBook,
resource.parent_id.unwrap(),
Acl::ReadItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(
access_token,
resource.parent_id().unwrap(),
Acl::ReadItems,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
// Fetch card
let card_ = self
.get_archive(account_id, Collection::ContactCard, resource.document_id)
.get_archive(account_id, Collection::ContactCard, resource.document_id())
.await
.caused_by(trc::location!())?
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
@@ -93,7 +90,7 @@ impl CardGetRequestHandler for Server {
vec![ResourceState {
account_id,
collection: Collection::ContactCard,
document_id: resource.document_id.into(),
document_id: resource.document_id().into(),
etag: etag.clone().into(),
path: resource_.resource.unwrap(),
..Default::default()
+5 -5
View File
@@ -18,10 +18,10 @@ use dav_proto::{
RequestHeaders, Return,
schema::{Namespace, request::MkCol, response::MkColResponse},
};
use groupware::{contact::AddressBook, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, contact::AddressBook};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::collection::Collection;
use jmap_proto::types::collection::{Collection, SyncCollection};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -54,11 +54,11 @@ impl CardMkColRequestHandler for Server {
return Err(DavError::Code(StatusCode::FORBIDDEN));
} else if name.contains('/')
|| self
.fetch_dav_resources(access_token, account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?
.paths
.by_name(name)
.by_path(name)
.is_some()
{
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
+2 -5
View File
@@ -92,11 +92,8 @@ pub(crate) async fn assert_is_unique_uid(
.await
.caused_by(trc::location!())?;
if !hits.results.is_empty() {
for path in resources.paths.iter() {
if !path.is_container()
&& hits.results.contains(path.document_id)
&& path.parent_id.unwrap() == addressbook_id
{
for path in resources.children(addressbook_id) {
if hits.results.contains(path.document_id()) {
return Err(DavError::Condition(DavErrorCondition::new(
StatusCode::PRECONDITION_FAILED,
CardCondition::NoUidConflict(resources.format_resource(path).into()),
+11 -18
View File
@@ -15,12 +15,15 @@ use dav_proto::{
},
};
use groupware::{
cache::GroupwareCache,
contact::{AddressBook, ContactCard},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -73,14 +76,14 @@ impl CardPropPatchRequestHandler for Server {
let uri = headers.uri;
let account_id = resource_.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?;
let resource = resource_
.resource
.and_then(|r| resources.paths.by_name(r))
.and_then(|r| resources.by_path(r))
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
let document_id = resource.document_id;
let document_id = resource.document_id();
let collection = if resource.is_container() {
Collection::AddressBook
} else {
@@ -94,22 +97,12 @@ impl CardPropPatchRequestHandler for Server {
// Verify ACL
if !access_token.is_member(account_id) {
let (acl, document_id) = if resource.is_container() {
(Acl::Modify, resource.document_id)
(Acl::Modify, resource.document_id())
} else {
(Acl::ModifyItems, resource.parent_id.unwrap())
(Acl::ModifyItems, resource.parent_id().unwrap())
};
if !self
.has_access_to_document(
access_token,
account_id,
Collection::AddressBook,
document_id,
acl,
)
.await
.caused_by(trc::location!())?
{
if !resources.has_access_to_container(access_token, document_id, acl) {
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
}
+9 -17
View File
@@ -23,10 +23,10 @@ use dav_proto::{
request::{AddressbookQuery, Filter, FilterOp, VCardPropertyWithGroup},
},
};
use groupware::hierarchy::DavHierarchy;
use groupware::cache::GroupwareCache;
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{acl::Acl, collection::SyncCollection};
use std::fmt::Write;
use trc::AddContext;
@@ -53,12 +53,11 @@ impl CardQueryRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource_.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?;
let resource = resources
.paths
.by_name(
.by_path(
resource_
.resource
.ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?,
@@ -70,26 +69,19 @@ impl CardQueryRequestHandler for Server {
// Obtain shared ids
let shared_ids = if !access_token.is_member(account_id) {
self.shared_containers(
access_token,
account_id,
Collection::AddressBook,
[Acl::ReadItems],
false,
)
.await
.caused_by(trc::location!())?
.into()
resources
.shared_containers(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) {
for resource in resources.children(resource.document_id()) {
if shared_ids
.as_ref()
.is_none_or(|ids| ids.contains(resource.document_id))
.is_none_or(|ids| ids.contains(resource.document_id()))
{
items.push(PropFindItem::new(
resources.format_resource(resource),
+18 -29
View File
@@ -5,15 +5,18 @@
*/
use calcard::{Entry, Parser};
use common::{Server, auth::AccessToken};
use common::{DavName, Server, auth::AccessToken};
use dav_proto::{
RequestHeaders, Return,
schema::{property::Rfc1123DateTime, response::CardCondition},
};
use groupware::{DavName, contact::ContactCard, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, contact::ContactCard};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -54,7 +57,7 @@ impl CardUpdateRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource.account_id;
let resources = self
.fetch_dav_resources(access_token, account_id, Collection::AddressBook)
.fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook)
.await
.caused_by(trc::location!())?;
let resource_name = resource
@@ -84,25 +87,16 @@ impl CardUpdateRequestHandler for Server {
}
};
if let Some(resource) = resources.paths.by_name(resource_name) {
if let Some(resource) = resources.by_path(resource_name) {
if resource.is_container() {
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
}
// Validate ACL
let parent_id = resource.parent_id.unwrap();
let document_id = resource.document_id;
let parent_id = resource.parent_id().unwrap();
let document_id = resource.document_id();
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::AddressBook,
parent_id,
Acl::ModifyItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(access_token, parent_id, Acl::ModifyItems)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -197,16 +191,11 @@ impl CardUpdateRequestHandler for Server {
// Validate ACL
if !access_token.is_member(account_id)
&& !self
.has_access_to_document(
access_token,
account_id,
Collection::AddressBook,
parent.document_id,
Acl::AddItems,
)
.await
.caused_by(trc::location!())?
&& !resources.has_access_to_container(
access_token,
parent.document_id(),
Acl::AddItems,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -241,7 +230,7 @@ impl CardUpdateRequestHandler for Server {
self,
&resources,
account_id,
parent.document_id,
parent.document_id(),
vcard.uid(),
)
.await?;
@@ -250,7 +239,7 @@ impl CardUpdateRequestHandler for Server {
let card = ContactCard {
names: vec![DavName {
name: name.to_string(),
parent_id: parent.document_id,
parent_id: parent.document_id(),
}],
card: vcard,
size: bytes.len() as u32,
+46 -87
View File
@@ -8,7 +8,7 @@ use crate::{
DavError, DavErrorCondition, DavResourceName, common::uri::DavUriResource,
principal::propfind::PrincipalPropFind,
};
use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
use common::{DavResources, Server, auth::AccessToken, sharing::EffectiveAcl};
use dav_proto::{
RequestHeaders,
schema::{
@@ -18,9 +18,7 @@ use dav_proto::{
},
};
use directory::{QueryBy, Type, backend::internal::manage::ManageDirectory};
use groupware::{
calendar::Calendar, contact::AddressBook, file::FileNode, hierarchy::DavHierarchy,
};
use groupware::{cache::GroupwareCache, calendar::Calendar, contact::AddressBook, file::FileNode};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{
@@ -58,25 +56,6 @@ pub(crate) trait DavAclHandler: Sync + Send {
collection: Collection,
) -> impl Future<Output = crate::Result<Vec<AclGrant>>> + Send;
fn validate_and_map_parent_acl(
&self,
access_token: &AccessToken,
account_id: u32,
collection: Collection,
parent_id: Option<u32>,
check_acls: impl Into<Bitmap<Acl>> + Send,
) -> impl Future<Output = crate::Result<u32>> + Send;
#[allow(clippy::too_many_arguments)]
fn validate_acl(
&self,
access_token: &AccessToken,
account_id: u32,
collection: Collection,
document_id: u32,
acl: impl Into<Bitmap<Acl>> + Send,
) -> impl Future<Output = crate::Result<()>> + Send;
fn resolve_ace(
&self,
access_token: &AccessToken,
@@ -86,6 +65,16 @@ pub(crate) trait DavAclHandler: Sync + Send {
) -> impl Future<Output = crate::Result<Vec<Ace>>> + Send;
}
pub(crate) trait ResourceAcl {
fn validate_and_map_parent_acl(
&self,
access_token: &AccessToken,
is_member: bool,
parent_id: Option<u32>,
check_acls: impl Into<Bitmap<Acl>> + Send,
) -> crate::Result<u32>;
}
impl DavAclHandler for Server {
async fn handle_acl_request(
&self,
@@ -108,20 +97,20 @@ impl DavAclHandler for Server {
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
let resources = self
.fetch_dav_resources(access_token, account_id, collection)
.fetch_dav_resources(access_token, account_id, collection.into())
.await
.caused_by(trc::location!())?;
let resource = resource_
.resource
.and_then(|r| resources.paths.by_name(r))
.and_then(|r| resources.by_path(r))
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
if !resource.is_container() && !matches!(collection, Collection::FileNode) {
if !resource.resource.is_container() && !matches!(collection, Collection::FileNode) {
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
// Fetch node
let archive = self
.get_archive(account_id, collection, resource.document_id)
.get_archive(account_id, collection, resource.document_id())
.await
.caused_by(trc::location!())?
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
@@ -158,7 +147,7 @@ impl DavAclHandler for Server {
access_token,
calendar,
account_id,
resource.document_id,
resource.document_id(),
&mut batch,
)
.caused_by(trc::location!())?;
@@ -173,7 +162,7 @@ impl DavAclHandler for Server {
access_token,
book,
account_id,
resource.document_id,
resource.document_id(),
&mut batch,
)
.caused_by(trc::location!())?;
@@ -187,7 +176,7 @@ impl DavAclHandler for Server {
access_token,
node,
account_id,
resource.document_id,
resource.document_id(),
&mut batch,
)
.caused_by(trc::location!())?;
@@ -412,63 +401,6 @@ impl DavAclHandler for Server {
Ok(grants)
}
async fn validate_and_map_parent_acl(
&self,
access_token: &AccessToken,
account_id: u32,
collection: Collection,
parent_id: Option<u32>,
check_acls: impl Into<Bitmap<Acl>> + Send,
) -> crate::Result<u32> {
match parent_id {
Some(parent_id) => {
if access_token.is_member(account_id)
|| self
.has_access_to_document(
access_token,
account_id,
collection,
parent_id,
check_acls,
)
.await
.caused_by(trc::location!())?
{
Ok(parent_id + 1)
} else {
Err(DavError::Code(StatusCode::FORBIDDEN))
}
}
None => {
if access_token.is_member(account_id) {
Ok(0)
} else {
Err(DavError::Code(StatusCode::FORBIDDEN))
}
}
}
}
async fn validate_acl(
&self,
access_token: &AccessToken,
account_id: u32,
collection: Collection,
document_id: u32,
acl: impl Into<Bitmap<Acl>> + Send,
) -> crate::Result<()> {
if access_token.is_member(account_id)
|| self
.has_access_to_document(access_token, account_id, collection, document_id, acl)
.await
.caused_by(trc::location!())?
{
Ok(())
} else {
Err(DavError::Code(StatusCode::FORBIDDEN))
}
}
async fn resolve_ace(
&self,
access_token: &AccessToken,
@@ -523,6 +455,33 @@ impl DavAclHandler for Server {
}
}
impl ResourceAcl for DavResources {
fn validate_and_map_parent_acl(
&self,
access_token: &AccessToken,
is_member: bool,
parent_id: Option<u32>,
check_acls: impl Into<Bitmap<Acl>> + Send,
) -> crate::Result<u32> {
match parent_id {
Some(parent_id) => {
if is_member || self.has_access_to_container(access_token, parent_id, check_acls) {
Ok(parent_id + 1)
} else {
Err(DavError::Code(StatusCode::FORBIDDEN))
}
}
None => {
if is_member {
Ok(0)
} else {
Err(DavError::Code(StatusCode::FORBIDDEN))
}
}
}
}
}
pub(crate) trait Privileges {
fn current_privilege_set(
&self,
+6 -11
View File
@@ -11,7 +11,8 @@ use dav_proto::schema::request::{DavPropertyValue, DeadProperty};
use dav_proto::schema::response::{BaseCondition, List, PropResponse};
use dav_proto::{Condition, Depth, Timeout};
use dav_proto::{RequestHeaders, schema::request::LockInfo};
use groupware::hierarchy::DavHierarchy;
use groupware::cache::GroupwareCache;
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::collection::Collection;
@@ -553,22 +554,16 @@ impl LockRequestHandler for Server {
// Fetch sync token
if needs_sync_token && resource_state.sync_token.is_none() {
let change_id = self
let id = self
.fetch_dav_resources(
access_token,
resource_state.account_id,
resource_state.collection.main_collection(),
resource_state.collection.into(),
)
.await
.caused_by(trc::location!())?
.modseq;
resource_state.sync_token = Some(
Urn::Sync {
id: change_id.unwrap_or_default(),
seq: 0,
}
.to_string(),
);
.highest_change_id;
resource_state.sync_token = Some(Urn::Sync { id, seq: 0 }.to_string());
}
}
+158 -177
View File
@@ -26,7 +26,7 @@ use crate::{
};
use calcard::common::timezone::Tz;
use common::{
DavResource, DavResources, Server,
DavResourcePath, DavResources, Server,
auth::{AccessToken, AsTenantId},
};
use dav_proto::{
@@ -48,11 +48,14 @@ use dav_proto::{
};
use directory::{Type, backend::internal::manage::ManageDirectory};
use groupware::{
DavCalendarResource, DavResourceName, calendar::ArchivedTimezone, hierarchy::DavHierarchy,
DavCalendarResource, DavResourceName, cache::GroupwareCache, calendar::ArchivedTimezone,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use percent_encoding::NON_ALPHANUMERIC;
use std::sync::Arc;
use store::{
@@ -90,7 +93,7 @@ pub(crate) struct PropFindData {
#[derive(Default)]
pub(crate) struct PropFindAccountData {
pub sync_token: Option<String>,
pub resources: Option<Arc<DavResources>>,
pub quota: Option<PropFindAccountQuota>,
pub owner: Option<Href>,
pub locks: Option<Archive<AlignedBytes>>,
@@ -350,7 +353,7 @@ impl PropFindRequestHandler for Server {
let mut data = PropFindData::new();
let collection_container;
let collection_children;
let mut ctag = None;
let sync_collection;
let mut paths;
let mut query_filter = None;
let mut limit = std::cmp::min(
@@ -366,30 +369,27 @@ impl PropFindRequestHandler for Server {
let account_id = resource.account_id;
collection_container = resource.collection;
collection_children = collection_container.child_collection().unwrap();
sync_collection = SyncCollection::from(collection_container);
let container_has_children = collection_children != collection_container;
let resources = self
.fetch_dav_resources(access_token, account_id, collection_container)
let resources = data
.resources(self, access_token, account_id, sync_collection)
.await
.caused_by(trc::location!())?;
response.set_namespace(collection_container.namespace());
ctag = Some(resources.modseq.unwrap_or_default());
// Obtain document ids
let mut display_containers = if !access_token.is_member(account_id) {
self.shared_containers(
access_token,
account_id,
collection_container,
[if container_has_children {
Acl::ReadItems
} else {
Acl::Read
}],
true,
)
.await
.caused_by(trc::location!())?
.into()
resources
.shared_containers(
access_token,
[if container_has_children {
Acl::ReadItems
} else {
Acl::Read
}],
true,
)
.into()
} else {
None
};
@@ -397,9 +397,9 @@ impl PropFindRequestHandler for Server {
.as_ref()
.filter(|_| container_has_children)
.map(|containers| {
RoaringBitmap::from_iter(resources.paths.iter().filter_map(|r| {
if r.parent_id
.is_some_and(|parent_id| containers.contains(parent_id))
RoaringBitmap::from_iter(resources.resources.iter().filter_map(|r| {
if r.child_names()
.is_some_and(|n| n.iter().any(|n| containers.contains(n.parent_id)))
{
Some(r.document_id)
} else {
@@ -411,47 +411,58 @@ impl PropFindRequestHandler for Server {
// Filter by changelog
match query.sync_type {
SyncType::From { id, seq } => {
let todo = "fix";
let container_changes = self
let changes = self
.store()
.changes(account_id, collection_container, Query::Since(id))
.changes(account_id, sync_collection, Query::Since(id))
.await
.caused_by(trc::location!())?;
let children_changes = if container_has_children {
self.store()
.changes(account_id, collection_children, Query::Since(id))
.await
.caused_by(trc::location!())?
.into()
} else {
None
};
// Merge changes
let mut total_changes = 0;
for (changes, document_ids) in [
Some((&container_changes, &mut display_containers)),
children_changes
.as_ref()
.map(|changes| (changes, &mut display_children)),
]
.into_iter()
.flatten()
{
if container_has_children {
let mut container_changes = RoaringBitmap::new();
let mut item_changes = RoaringBitmap::new();
for change in changes.changes {
match change {
Change::InsertItem(id) | Change::UpdateItem(id) => {
item_changes.insert(id as u32);
}
Change::InsertContainer(id) | Change::UpdateContainer(id) => {
container_changes.insert(id as u32);
}
_ => (),
}
}
for (document_ids, changes) in [
(&mut display_containers, container_changes),
(&mut display_children, item_changes),
] {
if let Some(document_ids) = document_ids {
*document_ids &= changes;
total_changes += document_ids.len() as usize;
} else {
total_changes += changes.len() as usize;
*document_ids = Some(changes);
}
}
} else {
let changes = RoaringBitmap::from_iter(
changes.changes.iter().filter_map(|change| match change {
Change::InsertItem(id) | Change::UpdateItem(id) => {
Some(*id as u32)
}
Change::InsertItem(id)
| Change::UpdateItem(id)
| Change::InsertContainer(id)
| Change::UpdateContainer(id) => Some(*id as u32),
_ => None,
}),
);
if let Some(document_ids) = document_ids {
if let Some(document_ids) = &mut display_containers {
*document_ids &= changes;
total_changes += document_ids.len() as usize;
} else {
total_changes += changes.len() as usize;
*document_ids = Some(changes);
display_containers = Some(changes);
}
}
@@ -483,35 +494,11 @@ impl PropFindRequestHandler for Server {
}
if !is_sync_limited {
// Set sync token
let change_id = std::cmp::max(
container_changes.to_change_id,
children_changes.as_ref().map_or(0, |c| c.to_change_id),
);
let sync_token = if change_id != 0 {
let sync_token = Urn::Sync {
id: change_id,
seq: 0,
}
.to_string();
data.accounts.entry(account_id).or_default().sync_token =
sync_token.clone().into();
sync_token
} else {
data.sync_token(self, account_id, collection_container)
.await
.caused_by(trc::location!())?
};
response.set_sync_token(sync_token);
response.set_sync_token(resources.sync_token());
}
}
SyncType::Initial => {
response.set_sync_token(
data.sync_token(self, account_id, collection_container)
.await
.caused_by(trc::location!())?,
);
response.set_sync_token(resources.sync_token());
}
SyncType::None => (),
}
@@ -523,16 +510,16 @@ impl PropFindRequestHandler for Server {
display_containers.as_ref().is_none_or(|containers| {
if container_has_children {
if item.is_container() {
containers.contains(item.document_id)
containers.contains(item.document_id())
} else {
display_children.as_ref().is_some_and(|children| {
children.contains(item.document_id)
children.contains(item.document_id())
})
}
} else {
containers.contains(item.document_id)
containers.contains(item.document_id())
}
}) && (!query.depth_no_root || item.name != resource)
}) && (!query.depth_no_root || item.path() != resource)
})
.map(|item| {
PropFindItem::new(resources.format_resource(item), account_id, item)
@@ -561,14 +548,14 @@ impl PropFindRequestHandler for Server {
display_containers.as_ref().is_none_or(|containers| {
if container_has_children {
if item.is_container() {
containers.contains(item.document_id)
containers.contains(item.document_id())
} else {
display_children.as_ref().is_some_and(|children| {
children.contains(item.document_id)
children.contains(item.document_id())
})
}
} else {
containers.contains(item.document_id)
containers.contains(item.document_id())
}
})
})
@@ -593,12 +580,11 @@ impl PropFindRequestHandler for Server {
parent_collection,
} => {
paths = Vec::with_capacity(hrefs.len());
let mut resources_by_account: AHashMap<
u32,
(Arc<DavResources>, Arc<Option<RoaringBitmap>>),
> = AHashMap::with_capacity(3);
let mut shared_folders_by_account: AHashMap<u32, Arc<RoaringBitmap>> =
AHashMap::with_capacity(3);
collection_container = parent_collection;
collection_children = collection_container.child_collection().unwrap();
sync_collection = SyncCollection::from(collection_container);
response.set_namespace(collection_container.namespace());
for item in hrefs {
@@ -618,51 +604,38 @@ impl PropFindRequestHandler for Server {
};
let account_id = resource.account_id;
let (resources, document_ids) =
if let Some(resources) = resources_by_account.get(&account_id) {
resources.clone()
let resources = data
.resources(self, access_token, account_id, sync_collection)
.await
.caused_by(trc::location!())?;
let document_ids = if !access_token.is_member(account_id) {
if let Some(document_ids) = shared_folders_by_account.get(&account_id) {
document_ids.clone().into()
} else {
let resources = self
.fetch_dav_resources(access_token, account_id, collection_container)
.await
.caused_by(trc::location!())?;
let document_ids = Arc::new(if !access_token.is_member(account_id) {
self.shared_containers(
access_token,
account_id,
collection_container,
[Acl::ReadItems],
false,
)
.await
.caused_by(trc::location!())?
.into()
} else {
None
});
resources_by_account
.insert(account_id, (resources.clone(), document_ids.clone()));
(resources, document_ids)
};
let document_ids = Arc::new(resources.shared_containers(
access_token,
[if collection_children == collection_container {
Acl::ReadItems
} else {
Acl::Read
}],
true,
));
shared_folders_by_account.insert(account_id, document_ids.clone());
document_ids.into()
}
} else {
None
};
/*let c = println!(
"resources: {:?} resource: {resource:?}",
resources
.paths
.iter()
.map(|r| r.name.to_string())
.collect::<Vec<_>>()
);*/
if let Some(resource) = resource
.resource
.and_then(|name| resources.paths.by_name(name))
if let Some(resource) =
resource.resource.and_then(|name| resources.by_path(name))
{
if !resource.is_container() {
if document_ids
.as_ref()
.as_ref()
.is_none_or(|docs| docs.contains(resource.document_id))
.is_none_or(|docs| docs.contains(resource.document_id()))
{
paths.push(PropFindItem::new(
resources.format_resource(resource),
@@ -699,6 +672,7 @@ impl PropFindRequestHandler for Server {
query_filter = Some(filter);
collection_container = parent_collection;
collection_children = collection_container.child_collection().unwrap();
sync_collection = SyncCollection::from(collection_container);
response.set_namespace(collection_container.namespace());
}
DavQueryResource::None => unreachable!(),
@@ -795,18 +769,19 @@ impl PropFindRequestHandler for Server {
},
ArchivedResource::CalendarEvent(event),
) => {
let mut query_handler = CalendarQueryHandler::new(
event.inner,
*max_time_range,
try_parse_tz(timezone)
.or_else(|| {
item.parent_id.and_then(|calendar_id| {
self.cached_dav_resources(account_id, Collection::Calendar)
.and_then(|r| r.calendar_default_tz(calendar_id))
})
})
.unwrap_or(Tz::UTC),
);
let default_tz = if let Some(tz) = try_parse_tz(timezone) {
tz
} else if let Some(calendar_id) = item.parent_id {
data.resources(self, access_token, account_id, SyncCollection::Calendar)
.await
.caused_by(trc::location!())?
.calendar_default_tz(calendar_id)
.unwrap_or(Tz::UTC)
} else {
Tz::UTC
};
let mut query_handler =
CalendarQueryHandler::new(event.inner, *max_time_range, default_tz);
if !query_handler.filter(event.inner, filter) {
continue;
}
@@ -872,15 +847,12 @@ impl PropFindRequestHandler for Server {
}
WebDavProperty::GetCTag => {
if item.is_container {
let ctag = if let Some(ctag) = ctag {
ctag
} else {
let todo = "fix";
self.store()
.get_last_change_id(account_id, collection)
.await?
.unwrap_or_default()
};
let ctag = data
.resources(self, access_token, account_id, sync_collection)
.await
.caused_by(trc::location!())?
.highest_change_id;
fields.push(DavPropertyValue::new(
property.clone(),
DavValue::String(format!("\"{ctag}\"")),
@@ -928,12 +900,13 @@ impl PropFindRequestHandler for Server {
}
}
WebDavProperty::SyncToken => {
fields.push(DavPropertyValue::new(
property.clone(),
data.sync_token(self, account_id, collection_children)
.await
.caused_by(trc::location!())?,
));
let sync_token = data
.resources(self, access_token, account_id, sync_collection)
.await
.caused_by(trc::location!())?
.sync_token();
fields.push(DavPropertyValue::new(property.clone(), sync_token));
}
WebDavProperty::CurrentUserPrincipal => {
if !query.expand {
@@ -1085,14 +1058,10 @@ impl PropFindRequestHandler for Server {
)
} else if let Some(parent_id) = item.parent_id {
current_user_privilege_set(
self.document_acl(
access_token.primary_id(),
account_id,
collection_container,
parent_id,
)
.await
.caused_by(trc::location!())?,
data.resources(self, access_token, account_id, sync_collection)
.await
.caused_by(trc::location!())?
.container_acl(access_token, parent_id),
)
} else {
vec![]
@@ -1427,12 +1396,12 @@ impl PropFindRequestHandler for Server {
}
impl PropFindItem {
pub fn new(name: String, account_id: u32, resource: &DavResource) -> Self {
pub fn new(name: String, account_id: u32, resource: DavResourcePath<'_>) -> Self {
Self {
name,
account_id,
document_id: resource.document_id,
parent_id: resource.parent_id,
document_id: resource.document_id(),
parent_id: resource.parent_id(),
is_container: resource.is_container(),
}
}
@@ -1479,26 +1448,24 @@ impl PropFindData {
Ok(data.owner.clone().unwrap())
}
pub async fn sync_token(
pub async fn resources(
&mut self,
server: &Server,
access_token: &AccessToken,
account_id: u32,
collection_children: Collection,
) -> trc::Result<String> {
sync_collection: SyncCollection,
) -> trc::Result<Arc<DavResources>> {
let data = self.accounts.entry(account_id).or_default();
if data.sync_token.is_none() {
let todo = "fix";
let id = server
.store()
.get_last_change_id(account_id, collection_children)
if data.resources.is_none() {
let resources = server
.fetch_dav_resources(access_token, account_id, sync_collection)
.await
.caused_by(trc::location!())?
.unwrap_or_default();
data.sync_token = Urn::Sync { id, seq: 0 }.to_string().into();
.caused_by(trc::location!())?;
data.resources = resources.into();
}
Ok(data.sync_token.clone().unwrap())
Ok(data.resources.clone().unwrap())
}
pub async fn locks(
@@ -1538,3 +1505,17 @@ impl PropFindData {
}
}
}
pub(crate) trait SyncTokenUrn {
fn sync_token(&self) -> String;
}
impl SyncTokenUrn for DavResources {
fn sync_token(&self) -> String {
Urn::Sync {
id: self.highest_change_id,
seq: 0,
}
.to_string()
}
}
+5 -5
View File
@@ -9,7 +9,8 @@ use std::fmt::Display;
use common::{Server, auth::AccessToken};
use directory::backend::internal::manage::ManageDirectory;
use groupware::hierarchy::DavHierarchy;
use groupware::cache::GroupwareCache;
use http_proto::request::decode_path_element;
use hyper::StatusCode;
use jmap_proto::types::collection::Collection;
@@ -128,11 +129,10 @@ impl DavUriResource for Server {
) -> trc::Result<Option<DocumentUri>> {
if let Some(resource) = uri.resource {
if let Some(resource) = self
.fetch_dav_resources(access_token, uri.account_id, uri.collection)
.fetch_dav_resources(access_token, uri.account_id, uri.collection.into())
.await
.caused_by(trc::location!())?
.paths
.by_name(resource)
.by_path(resource)
{
Ok(Some(DocumentUri {
collection: if resource.is_container() || uri.collection == Collection::FileNode
@@ -144,7 +144,7 @@ impl DavUriResource for Server {
Collection::ContactCard
},
account_id: uri.account_id,
resource: resource.document_id,
resource: resource.document_id(),
}))
} else {
Ok(None)
+84 -91
View File
@@ -9,18 +9,22 @@ use crate::{
DavError, DavMethod,
common::{
ExtractETag,
acl::DavAclHandler,
lock::{LockRequestHandler, ResourceState},
uri::{DavUriResource, UriResource},
},
file::{DavFileResource, FileItemId},
};
use common::{DavResources, Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
use common::{
DavResourcePath, DavResources, Server, auth::AccessToken, storage::index::ObjectIndexBuilder,
};
use dav_proto::{Depth, RequestHeaders};
use groupware::{DestroyArchive, file::FileNode, hierarchy::DavHierarchy};
use groupware::{DestroyArchive, cache::GroupwareCache, file::FileNode};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use std::sync::Arc;
use store::{
ahash::AHashMap,
@@ -50,32 +54,27 @@ impl FileCopyMoveRequestHandler for Server {
.await?
.into_owned_uri()?;
let from_account_id = from_resource_.account_id;
let from_files = self
.fetch_dav_resources(access_token, from_account_id, Collection::FileNode)
let from_resources = self
.fetch_dav_resources(access_token, from_account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?;
let from_resource = from_files.map_resource::<FileItemId>(&from_resource_)?;
let from_resource = from_resources.map_resource::<FileItemId>(&from_resource_)?;
let from_resource_name = from_resource_.resource.unwrap();
// Validate source ACLs
if !access_token.is_member(from_account_id) {
let shared = self
.shared_containers(
access_token,
from_account_id,
Collection::FileNode,
if is_move {
[Acl::Read, Acl::Delete].as_slice().iter().copied()
} else {
[Acl::Read].as_slice().iter().copied()
},
false,
)
.await
.caused_by(trc::location!())?;
let shared = from_resources.shared_containers(
access_token,
if is_move {
[Acl::Read, Acl::Delete].as_slice().iter().copied()
} else {
[Acl::Read].as_slice().iter().copied()
},
false,
);
for resource in from_files.subtree(from_resource_.resource.unwrap()) {
if !shared.contains(resource.document_id) {
for resource in from_resources.subtree(from_resource_.resource.unwrap()) {
if !shared.contains(resource.document_id()) {
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
}
@@ -97,10 +96,10 @@ impl FileCopyMoveRequestHandler for Server {
let to_account_id = destination
.account_id
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?;
let to_files = if to_account_id == from_account_id {
from_files.clone()
let to_resources = if to_account_id == from_account_id {
from_resources.clone()
} else {
self.fetch_dav_resources(access_token, to_account_id, Collection::FileNode)
self.fetch_dav_resources(access_token, to_account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?
};
@@ -120,29 +119,29 @@ impl FileCopyMoveRequestHandler for Server {
let mut delete_destination = None;
// Check if the resource exists
let mut destination =
if let Some((destination, new_name)) = to_files.map_parent(destination_resource_name) {
if let Some(mut existing_destination) = to_files
.paths
.by_name(destination_resource_name)
.map(Destination::from_dav_resource)
{
if !headers.overwrite_fail {
existing_destination.account_id = to_account_id;
delete_destination = Some(existing_destination);
} else {
return Ok(HttpResponse::new(StatusCode::PRECONDITION_FAILED));
}
let mut destination = if let Some((destination, new_name)) =
to_resources.map_parent(destination_resource_name)
{
if let Some(mut existing_destination) = to_resources
.by_path(destination_resource_name)
.map(Destination::from_dav_resource)
{
if !headers.overwrite_fail {
existing_destination.account_id = to_account_id;
delete_destination = Some(existing_destination);
} else {
return Ok(HttpResponse::new(StatusCode::PRECONDITION_FAILED));
}
}
let mut destination = destination
.map(Destination::from_dav_resource)
.unwrap_or_default();
destination.new_name = Some(new_name.to_string());
destination
} else {
return Err(DavError::Code(StatusCode::CONFLICT));
};
let mut destination = destination
.map(Destination::from_dav_resource)
.unwrap_or_default();
destination.new_name = Some(new_name.to_string());
destination
} else {
return Err(DavError::Code(StatusCode::CONFLICT));
};
destination.account_id = to_account_id;
if delete_destination.is_none()
@@ -158,24 +157,22 @@ impl FileCopyMoveRequestHandler for Server {
// Validate destination ACLs
if let Some(document_id) = destination.document_id {
if let Some(delete_destination) = &delete_destination {
self.validate_acl(
access_token,
to_account_id,
Collection::FileNode,
delete_destination.document_id.unwrap(),
Acl::Delete,
)
.await?;
if !access_token.is_member(to_account_id)
&& !from_resources.has_access_to_container(
access_token,
delete_destination.document_id.unwrap(),
Acl::Delete,
)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
}
self.validate_acl(
access_token,
to_account_id,
Collection::FileNode,
document_id,
Acl::Modify,
)
.await?;
if !access_token.is_member(to_account_id)
&& !from_resources.has_access_to_container(access_token, document_id, Acl::Modify)
{
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
} else if !access_token.is_member(to_account_id) {
return Err(DavError::Code(StatusCode::FORBIDDEN));
}
@@ -217,12 +214,8 @@ impl FileCopyMoveRequestHandler for Server {
// Validate quota
if !is_move || from_account_id != to_account_id {
let res = from_files
.paths
.by_id(from_resource.resource.document_id)
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
let space_needed = from_files
.subtree(&res.name)
let space_needed = from_resources
.subtree(from_resource_name)
.map(|a| a.size() as u64)
.sum::<u64>();
self.has_available_quota(
@@ -239,13 +232,13 @@ impl FileCopyMoveRequestHandler for Server {
if is_overwrite {
delete_destination = None;
// Find ids to delete
let mut ids = to_files
let mut ids = to_resources
.subtree(destination_resource_name)
.collect::<Vec<_>>();
if !ids.is_empty() {
ids.sort_unstable_by_key(|b| std::cmp::Reverse(b.hierarchy_sequence()));
ids.sort_unstable_by_key(|b| std::cmp::Reverse(b.hierarchy_seq()));
let mut sorted_ids = Vec::with_capacity(ids.len());
sorted_ids.extend(ids.into_iter().map(|a| a.document_id));
sorted_ids.extend(ids.into_iter().map(|a| a.document_id()));
DestroyArchive(sorted_ids)
.delete(self, access_token, destination.account_id)
.await
@@ -258,8 +251,9 @@ impl FileCopyMoveRequestHandler for Server {
move_container(
self,
access_token,
from_files,
from_resources,
from_resource,
from_resource_name,
destination,
headers.depth,
)
@@ -269,8 +263,9 @@ impl FileCopyMoveRequestHandler for Server {
copy_container(
self,
access_token,
from_files,
from_resources,
from_resource,
from_resource_name,
destination,
headers.depth,
false,
@@ -309,7 +304,6 @@ pub(crate) struct Destination {
pub account_id: u32,
pub new_name: Option<String>,
pub document_id: Option<u32>,
//pub parent_id: Option<u32>,
pub is_container: bool,
}
@@ -328,8 +322,9 @@ impl Default for Destination {
async fn move_container(
server: &Server,
access_token: &AccessToken,
from_files: Arc<DavResources>,
from_resources: Arc<DavResources>,
from_resource: UriResource<u32, FileItemId>,
from_resource_name: &str,
destination: Destination,
depth: Depth,
) -> crate::Result<HttpResponse> {
@@ -373,8 +368,9 @@ async fn move_container(
copy_container(
server,
access_token,
from_files,
from_resources,
from_resource,
from_resource_name,
destination,
depth,
true,
@@ -383,11 +379,13 @@ async fn move_container(
}
}
#[allow(clippy::too_many_arguments)]
async fn copy_container(
server: &Server,
access_token: &AccessToken,
from_files: Arc<DavResources>,
from_resources: Arc<DavResources>,
from_resource: UriResource<u32, FileItemId>,
from_resource_name: &str,
mut destination: Destination,
depth: Depth,
delete_source: bool,
@@ -402,23 +400,18 @@ async fn copy_container(
let from_account_id = from_resource.account_id;
let to_account_id = destination.account_id;
let from_document_id = from_resource.resource.document_id;
let parent_id = destination.document_id.map(|id| id + 1).unwrap_or(0);
// Obtain files to copy
let res = from_files
.paths
.by_id(from_document_id)
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
let mut copy_files = if infinity_copy {
from_files
.subtree(&res.name)
.map(|r| (r.document_id, r.hierarchy_sequence()))
from_resources
.subtree(from_resource_name)
.map(|r| (r.document_id(), r.hierarchy_seq()))
.collect::<Vec<_>>()
} else {
from_files
.subtree_with_depth(&res.name, 1)
.map(|r| (r.document_id, r.hierarchy_sequence()))
from_resources
.subtree_with_depth(from_resource_name, 1)
.map(|r| (r.document_id(), r.hierarchy_seq()))
.collect::<Vec<_>>()
};
@@ -777,10 +770,10 @@ async fn rename_item(
}
impl FromDavResource for Destination {
fn from_dav_resource(item: &common::DavResource) -> Self {
fn from_dav_resource(item: DavResourcePath<'_>) -> Self {
Destination {
account_id: u32::MAX,
document_id: Some(item.document_id),
document_id: Some(item.document_id()),
is_container: item.is_container(),
new_name: None,
}
+14 -24
View File
@@ -4,14 +4,6 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::RequestHeaders;
use groupware::{DestroyArchive, hierarchy::DavHierarchy};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use trc::AddContext;
use crate::{
DavError, DavMethod,
common::{
@@ -19,6 +11,13 @@ use crate::{
uri::DavUriResource,
},
};
use common::{Server, auth::AccessToken};
use dav_proto::RequestHeaders;
use groupware::{DestroyArchive, cache::GroupwareCache};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::SyncCollection};
use trc::AddContext;
pub(crate) trait FileDeleteRequestHandler: Sync + Send {
fn handle_file_delete_request(
@@ -44,35 +43,26 @@ impl FileDeleteRequestHandler for Server {
.resource
.filter(|r| !r.is_empty())
.ok_or(DavError::Code(StatusCode::FORBIDDEN))?;
let files = self
.fetch_dav_resources(access_token, account_id, Collection::FileNode)
let resources = self
.fetch_dav_resources(access_token, account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?;
// Find ids to delete
let mut ids = files.subtree(delete_path).collect::<Vec<_>>();
let mut ids = resources.subtree(delete_path).collect::<Vec<_>>();
if ids.is_empty() {
return Err(DavError::Code(StatusCode::NOT_FOUND));
}
// Sort ids descending from the deepest to the root
ids.sort_unstable_by_key(|b| std::cmp::Reverse(b.hierarchy_sequence()));
let document_id = ids.last().map(|a| a.document_id).unwrap();
ids.sort_unstable_by_key(|b| std::cmp::Reverse(b.hierarchy_seq()));
let document_id = ids.last().map(|a| a.document_id()).unwrap();
let mut sorted_ids = Vec::with_capacity(ids.len());
sorted_ids.extend(ids.into_iter().map(|a| a.document_id));
sorted_ids.extend(ids.into_iter().map(|a| a.document_id()));
// Validate ACLs
if !access_token.is_member(account_id) {
let permissions = self
.shared_containers(
access_token,
account_id,
Collection::FileNode,
[Acl::Delete],
false,
)
.await
.caused_by(trc::location!())?;
let permissions = resources.shared_containers(access_token, [Acl::Delete], false);
if permissions.len() != sorted_ids.len() as u64
|| !sorted_ids.iter().all(|id| permissions.contains(*id))
{
+3 -3
View File
@@ -6,10 +6,10 @@
use common::{Server, auth::AccessToken, sharing::EffectiveAcl};
use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime};
use groupware::{file::FileNode, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, file::FileNode};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{acl::Acl, collection::{Collection, SyncCollection}};
use trc::AddContext;
use crate::{
@@ -45,7 +45,7 @@ impl FileGetRequestHandler for Server {
.into_owned_uri()?;
let account_id = resource_.account_id;
let files = self
.fetch_dav_resources(access_token, account_id, Collection::FileNode)
.fetch_dav_resources(access_token, account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?;
let resource = files.map_resource(&resource_)?;
+25 -27
View File
@@ -4,30 +4,31 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
use dav_proto::{
RequestHeaders, Return,
schema::{Namespace, request::MkCol, response::MkColResponse},
};
use groupware::{file::FileNode, hierarchy::DavHierarchy};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use store::write::{BatchBuilder, now};
use trc::AddContext;
use super::proppatch::FilePropPatchRequestHandler;
use crate::{
DavMethod, PropStatBuilder,
common::{
ExtractETag,
acl::DavAclHandler,
acl::ResourceAcl,
lock::{LockRequestHandler, ResourceState},
uri::DavUriResource,
},
file::DavFileResource,
};
use super::proppatch::FilePropPatchRequestHandler;
use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
use dav_proto::{
RequestHeaders, Return,
schema::{Namespace, request::MkCol, response::MkColResponse},
};
use groupware::{cache::GroupwareCache, file::FileNode};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::{BatchBuilder, now};
use trc::AddContext;
pub(crate) trait FileMkColRequestHandler: Sync + Send {
fn handle_file_mkcol_request(
@@ -51,22 +52,19 @@ impl FileMkColRequestHandler for Server {
.await?
.into_owned_uri()?;
let account_id = resource_.account_id;
let files = self
.fetch_dav_resources(access_token, account_id, Collection::FileNode)
let resources = self
.fetch_dav_resources(access_token, account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?;
let resource = files.map_parent_resource(&resource_)?;
let resource = resources.map_parent_resource(&resource_)?;
// Validate and map parent ACL
let parent_id = self
.validate_and_map_parent_acl(
access_token,
account_id,
Collection::FileNode,
resource.resource.0,
Acl::AddItems,
)
.await?;
let parent_id = resources.validate_and_map_parent_acl(
access_token,
access_token.is_member(account_id),
resource.resource.0,
Acl::AddItems,
)?;
// Validate headers
self.validate_headers(
+14 -15
View File
@@ -4,14 +4,13 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{DavResource, DavResources};
use dav_proto::schema::property::{DavProperty, WebDavProperty};
use hyper::StatusCode;
use crate::{
DavError,
common::uri::{OwnedUri, UriResource},
};
use common::{DavResourcePath, DavResources};
use dav_proto::schema::property::{DavProperty, WebDavProperty};
use hyper::StatusCode;
pub mod copy_move;
pub mod delete;
@@ -65,7 +64,7 @@ pub(crate) static FILE_ITEM_PROPS: [DavProperty; 19] = [
];
pub(crate) trait FromDavResource {
fn from_dav_resource(item: &DavResource) -> Self;
fn from_dav_resource(item: DavResourcePath<'_>) -> Self;
}
pub(crate) struct FileItemId {
@@ -80,7 +79,7 @@ pub(crate) trait DavFileResource {
resource: &OwnedUri<'_>,
) -> crate::Result<UriResource<u32, T>>;
fn map_parent<'x>(&self, resource: &'x str) -> Option<(Option<&DavResource>, &'x str)>;
fn map_parent<'x>(&self, resource: &'x str) -> Option<(Option<DavResourcePath<'_>>, &'x str)>;
#[allow(clippy::type_complexity)]
fn map_parent_resource<'x, T: FromDavResource>(
@@ -96,7 +95,7 @@ impl DavFileResource for DavResources {
) -> crate::Result<UriResource<u32, T>> {
resource
.resource
.and_then(|r| self.paths.by_name(r))
.and_then(|r| self.by_path(r))
.map(|r| UriResource {
collection: resource.collection,
account_id: resource.account_id,
@@ -105,9 +104,9 @@ impl DavFileResource for DavResources {
.ok_or(DavError::Code(StatusCode::NOT_FOUND))
}
fn map_parent<'x>(&self, resource: &'x str) -> Option<(Option<&DavResource>, &'x str)> {
fn map_parent<'x>(&self, resource: &'x str) -> Option<(Option<DavResourcePath<'_>>, &'x str)> {
let (parent, child) = if let Some((parent, child)) = resource.rsplit_once('/') {
(Some(self.paths.by_name(parent)?), child)
(Some(self.by_path(parent)?), child)
} else {
(None, resource)
};
@@ -120,7 +119,7 @@ impl DavFileResource for DavResources {
resource: &OwnedUri<'x>,
) -> crate::Result<UriResource<u32, (Option<T>, &'x str)>> {
if let Some(r) = resource.resource {
if self.paths.by_name(r).is_none() {
if self.by_path(r).is_none() {
self.map_parent(r)
.map(|(parent, child)| UriResource {
collection: resource.collection,
@@ -138,16 +137,16 @@ impl DavFileResource for DavResources {
}
impl FromDavResource for u32 {
fn from_dav_resource(item: &DavResource) -> Self {
item.document_id
fn from_dav_resource(item: DavResourcePath) -> Self {
item.document_id()
}
}
impl FromDavResource for FileItemId {
fn from_dav_resource(item: &DavResource) -> Self {
fn from_dav_resource(item: DavResourcePath) -> Self {
FileItemId {
document_id: item.document_id,
parent_id: item.parent_id,
document_id: item.document_id(),
parent_id: item.parent_id(),
is_container: item.is_container(),
}
}
+6 -3
View File
@@ -13,10 +13,13 @@ use dav_proto::{
response::{BaseCondition, MultiStatus, Response},
},
};
use groupware::{file::FileNode, hierarchy::DavHierarchy};
use groupware::{cache::GroupwareCache, file::FileNode};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::BatchBuilder;
use trc::AddContext;
@@ -62,7 +65,7 @@ impl FilePropPatchRequestHandler for Server {
let uri = headers.uri;
let account_id = resource_.account_id;
let files = self
.fetch_dav_resources(access_token, account_id, Collection::FileNode)
.fetch_dav_resources(access_token, account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?;
let resource = files.map_resource(&resource_)?;
+29 -35
View File
@@ -4,31 +4,33 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{
Server, auth::AccessToken, sharing::EffectiveAcl, storage::index::ObjectIndexBuilder,
};
use dav_proto::{RequestHeaders, Return, schema::property::Rfc1123DateTime};
use groupware::{
file::{FileNode, FileProperties},
hierarchy::DavHierarchy,
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{acl::Acl, collection::Collection};
use store::write::{BatchBuilder, now};
use trc::AddContext;
use utils::BlobHash;
use crate::{
DavError, DavMethod,
common::{
ETag, ExtractETag,
acl::DavAclHandler,
acl::ResourceAcl,
lock::{LockRequestHandler, ResourceState},
uri::DavUriResource,
},
file::DavFileResource,
};
use common::{
Server, auth::AccessToken, sharing::EffectiveAcl, storage::index::ObjectIndexBuilder,
};
use dav_proto::{RequestHeaders, Return, schema::property::Rfc1123DateTime};
use groupware::{
cache::GroupwareCache,
file::{FileNode, FileProperties},
};
use http_proto::HttpResponse;
use hyper::StatusCode;
use jmap_proto::types::{
acl::Acl,
collection::{Collection, SyncCollection},
};
use store::write::{BatchBuilder, now};
use trc::AddContext;
use utils::BlobHash;
pub(crate) trait FileUpdateRequestHandler: Sync + Send {
fn handle_file_update_request(
@@ -54,8 +56,8 @@ impl FileUpdateRequestHandler for Server {
.await?
.into_owned_uri()?;
let account_id = resource.account_id;
let files = self
.fetch_dav_resources(access_token, account_id, Collection::FileNode)
let resources = self
.fetch_dav_resources(access_token, account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?;
let resource_name = resource
@@ -66,7 +68,7 @@ impl FileUpdateRequestHandler for Server {
return Err(DavError::Code(StatusCode::PAYLOAD_TOO_LARGE));
}
if let Some(document_id) = files.paths.by_name(resource_name).map(|r| r.document_id) {
if let Some(document_id) = resources.by_path(resource_name).map(|r| r.document_id()) {
// Update
let node_ = self
.get_archive(account_id, Collection::FileNode, document_id)
@@ -193,28 +195,20 @@ impl FileUpdateRequestHandler for Server {
} else {
// Insert
let orig_resource_name = resource_name;
let (parent, resource_name) = files
let (parent, resource_name) = resources
.map_parent(resource_name)
.ok_or(DavError::Code(StatusCode::CONFLICT))?;
// Validate ACL
let parent_id = self
.validate_and_map_parent_acl(
access_token,
account_id,
Collection::FileNode,
parent.map(|r| r.document_id),
Acl::AddItems,
)
.await?;
let parent_id = resources.validate_and_map_parent_acl(
access_token,
access_token.is_member(account_id),
parent.map(|r| r.document_id()),
Acl::AddItems,
)?;
// Verify that parent is a collection
if parent_id > 0
&& files
.paths
.by_id(parent_id - 1)
.is_some_and(|r| !r.is_container())
{
if parent.as_ref().is_some_and(|r| !r.is_container()) {
return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED));
}
+7 -10
View File
@@ -16,6 +16,7 @@ use dav_proto::schema::{
response::{Href, MultiStatus, PropStat, Response},
};
use directory::{QueryBy, backend::internal::manage::ManageDirectory};
use groupware::cache::GroupwareCache;
use hyper::StatusCode;
use jmap_proto::types::collection::Collection;
use percent_encoding::NON_ALPHANUMERIC;
@@ -23,7 +24,7 @@ use trc::AddContext;
use crate::{
DavResourceName,
common::{propfind::PropFindRequestHandler, uri::Urn},
common::propfind::{PropFindRequestHandler, SyncTokenUrn},
};
use super::CurrentUserPrincipal;
@@ -184,17 +185,13 @@ impl PrincipalPropFind for Server {
fields.push(DavPropertyValue::new(property.clone(), quota.used));
}
WebDavProperty::SyncToken if !is_principal => {
let todo = "fix";
let id = self
.store()
.get_last_change_id(account_id, collection)
let sync_token = self
.fetch_dav_resources(access_token, account_id, collection.into())
.await
.caused_by(trc::location!())?
.unwrap_or_default();
fields.push(DavPropertyValue::new(
property.clone(),
Urn::Sync { id, seq: 0 }.to_string(),
));
.sync_token();
fields.push(DavPropertyValue::new(property.clone(), sync_token));
}
WebDavProperty::Owner => {
fields.push(DavPropertyValue::new(
+22 -24
View File
@@ -4,27 +4,6 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::Arc;
use common::{Server, auth::AccessToken};
use dav_proto::{
RequestHeaders,
parser::{DavParser, tokenizer::Tokenizer},
schema::{
Namespace,
property::WebDavProperty,
request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report},
response::{
BaseCondition, ErrorResponse, PrincipalSearchProperty, PrincipalSearchPropertySet,
},
},
xml_pretty_print,
};
use directory::Permission;
use http_proto::{HttpRequest, HttpResponse, HttpSessionData, request::fetch_body};
use hyper::{StatusCode, header};
use jmap_proto::types::collection::Collection;
use crate::{
DavError, DavMethod, DavResourceName,
calendar::{
@@ -53,6 +32,25 @@ use crate::{
},
principal::{matching::PrincipalMatching, propsearch::PrincipalPropSearch},
};
use common::{Server, auth::AccessToken};
use dav_proto::{
RequestHeaders,
parser::{DavParser, tokenizer::Tokenizer},
schema::{
Namespace,
property::WebDavProperty,
request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report},
response::{
BaseCondition, ErrorResponse, PrincipalSearchProperty, PrincipalSearchPropertySet,
},
},
xml_pretty_print,
};
use directory::Permission;
use http_proto::{HttpRequest, HttpResponse, HttpSessionData, request::fetch_body};
use hyper::{StatusCode, header};
use jmap_proto::types::collection::Collection;
use std::sync::Arc;
pub trait DavRequestHandler: Sync + Send {
fn handle_dav_request(
@@ -452,7 +450,7 @@ impl DavRequestHandler for Server {
Vec::new()
};
let c = println!("------------------------------------------");
//let c = println!("------------------------------------------");
let std_body = std::str::from_utf8(&body).unwrap_or("[binary]").to_string();
@@ -513,7 +511,7 @@ impl DavRequestHandler for Server {
Err(DavError::Code(code)) => HttpResponse::new(code),
};
let c = println!(
/*let c = println!(
"{:?} {} -> {:?}\nHeaders: {:?}\nBody: {}\nResponse headers: {:?}\nResponse: {}",
method,
request.uri().path(),
@@ -526,7 +524,7 @@ impl DavRequestHandler for Server {
http_proto::HttpResponseBody::Empty => "[empty]".to_string(),
_ => "[binary]".to_string(),
}
);
);*/
result
}
+1 -22
View File
@@ -4,10 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::storage::{
folder::FolderHierarchy,
index::{IndexValue, IndexableAndSerializableObject, IndexableObject},
};
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
use jmap_proto::types::{collection::SyncCollection, value::AclGrant};
use super::{ArchivedMailbox, Mailbox};
@@ -46,21 +43,3 @@ impl IndexableObject for &ArchivedMailbox {
}
impl IndexableAndSerializableObject for Mailbox {}
impl FolderHierarchy for ArchivedMailbox {
fn name(&self) -> String {
self.name.to_string()
}
fn parent_id(&self) -> u32 {
u32::from(self.parent_id)
}
fn is_container(&self) -> bool {
true
}
fn size(&self) -> u32 {
0
}
}
+1
View File
@@ -14,6 +14,7 @@ directory = { path = "../directory" }
dav-proto = { path = "../dav-proto" }
calcard = { path = "/Users/me/code/calcard", features = ["rkyv"] }
hashify = "0.2"
tokio = { version = "1.23", features = ["net", "macros"] }
rkyv = { version = "0.8.10", features = ["little_endian"] }
percent-encoding = "2.3.1"
compact_str = "0.9.0"
+300
View File
@@ -0,0 +1,300 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::GroupwareCache;
use crate::{
DavResourceName,
calendar::{ArchivedCalendar, ArchivedCalendarEvent, Calendar, CalendarEvent},
contact::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard},
};
use calcard::common::timezone::Tz;
use common::{
DavName, DavPath, DavResource, DavResourceMetadata, DavResources, Server, auth::AccessToken,
};
use directory::backend::internal::manage::ManageDirectory;
use jmap_proto::types::{
collection::{Collection, SyncCollection},
value::AclGrant,
};
use percent_encoding::NON_ALPHANUMERIC;
use std::sync::Arc;
use store::ahash::{AHashMap, AHashSet};
use tokio::sync::Semaphore;
use trc::AddContext;
use utils::map::bitmap::Bitmap;
pub(super) async fn build_calcard_resources(
server: &Server,
access_token: &AccessToken,
account_id: u32,
sync_collection: SyncCollection,
container_collection: Collection,
item_collection: Collection,
update_lock: Arc<Semaphore>,
) -> trc::Result<DavResources> {
let mut last_change_id = server
.core
.storage
.data
.get_last_change_id(account_id, sync_collection)
.await
.caused_by(trc::location!())?
.unwrap_or_default();
// Create default folders
let is_calendar = matches!(sync_collection, SyncCollection::Calendar);
let mut container_ids = server
.get_document_ids(account_id, container_collection)
.await
.caused_by(trc::location!())?
.unwrap_or_default();
if container_ids.is_empty() {
if is_calendar {
server
.create_default_calendar(access_token, account_id)
.await?
} else {
server
.create_default_addressbook(access_token, account_id)
.await?
}
last_change_id = server
.core
.storage
.data
.get_last_change_id(account_id, sync_collection)
.await
.caused_by(trc::location!())?
.unwrap_or_default();
container_ids = server
.get_document_ids(account_id, container_collection)
.await
.caused_by(trc::location!())?
.unwrap_or_default();
}
let item_ids = server
.get_document_ids(account_id, item_collection)
.await
.caused_by(trc::location!())?
.unwrap_or_default();
let name = server
.store()
.get_principal_name(account_id)
.await
.caused_by(trc::location!())?
.unwrap_or_else(|| format!("_{account_id}"));
let mut cache = DavResources {
base_path: format!(
"{}/{}/",
if is_calendar {
DavResourceName::Cal
} else {
DavResourceName::Card
}
.base_path(),
percent_encoding::utf8_percent_encode(&name, NON_ALPHANUMERIC),
),
paths: AHashSet::with_capacity((container_ids.len() + item_ids.len()) as usize),
resources: Vec::with_capacity((container_ids.len() + item_ids.len()) as usize),
item_change_id: last_change_id,
container_change_id: last_change_id,
highest_change_id: last_change_id,
size: std::mem::size_of::<DavResources>() as u64,
update_lock,
};
for document_id in container_ids {
if let Some(archive) = server
.get_archive(account_id, container_collection, document_id)
.await
.caused_by(trc::location!())?
{
let resource = if is_calendar {
resource_from_calendar(archive.unarchive::<Calendar>()?, document_id)
} else {
resource_from_addressbook(archive.unarchive::<AddressBook>()?, document_id)
};
let path = DavPath {
path: resource.container_name().unwrap().to_string(),
parent_id: None,
hierarchy_seq: 1,
resource_idx: cache.resources.len(),
};
cache.size += (std::mem::size_of::<DavPath>()
+ std::mem::size_of::<DavResource>()
+ (path.path.len()) * 2) as u64;
cache.paths.insert(path);
cache.resources.push(resource);
}
}
let parent_range = cache.resources.len();
for document_id in item_ids {
if let Some(archive) = server
.get_archive(account_id, item_collection, document_id)
.await
.caused_by(trc::location!())?
{
let resource = if is_calendar {
resource_from_event(archive.unarchive::<CalendarEvent>()?, document_id)
} else {
resource_from_card(archive.unarchive::<ContactCard>()?, document_id)
};
let resource_idx = cache.resources.len();
for name in resource.child_names().unwrap_or_default().iter() {
if let Some(parent) = cache.resources.get(..parent_range).and_then(|resources| {
resources.iter().find(|r| r.document_id == name.parent_id)
}) {
let path = DavPath {
path: format!("{}/{}", parent.container_name().unwrap(), name.name),
parent_id: Some(name.parent_id),
hierarchy_seq: 0,
resource_idx,
};
cache.size +=
(std::mem::size_of::<DavPath>() + name.name.len() + path.path.len()) as u64;
cache.paths.insert(path);
}
}
cache.size += std::mem::size_of::<DavResource>() as u64;
cache.resources.push(resource);
}
}
Ok(cache)
}
pub(super) fn build_simple_hierarchy(cache: &mut DavResources) {
cache.paths = AHashSet::with_capacity(cache.resources.len());
let name_idx = cache
.resources
.iter()
.filter_map(|resource| {
resource
.container_name()
.map(|name| (resource.document_id, name))
})
.collect::<AHashMap<_, _>>();
for (resource_idx, resource) in cache.resources.iter().enumerate() {
match &resource.data {
DavResourceMetadata::Calendar { name, .. }
| DavResourceMetadata::AddressBook { name, .. } => {
let path = DavPath {
path: name.to_string(),
parent_id: None,
hierarchy_seq: 1,
resource_idx,
};
cache.size +=
(std::mem::size_of::<DavPath>() + name.len() + path.path.len()) as u64;
cache.paths.insert(path);
}
DavResourceMetadata::CalendarEvent { names, .. }
| DavResourceMetadata::ContactCard { names } => {
for name in names {
if let Some(parent_name) = name_idx.get(&name.parent_id) {
let path = DavPath {
path: format!("{parent_name}/{}", name.name),
parent_id: Some(name.parent_id),
hierarchy_seq: 1,
resource_idx,
};
cache.size += (std::mem::size_of::<DavPath>()
+ name.name.len()
+ path.path.len()) as u64;
cache.paths.insert(path);
}
}
}
_ => unreachable!(),
}
cache.size += std::mem::size_of::<DavResource>() as u64;
}
}
pub(super) fn resource_from_calendar(calendar: &ArchivedCalendar, document_id: u32) -> DavResource {
DavResource {
document_id,
data: DavResourceMetadata::Calendar {
name: calendar.name.to_string(),
acls: calendar
.acls
.iter()
.map(|acl| AclGrant {
account_id: acl.account_id.to_native(),
grants: Bitmap::from(&acl.grants),
})
.collect(),
tz: calendar
.preferences
.first()
.and_then(|pref| pref.time_zone.tz())
.unwrap_or(Tz::UTC),
},
}
}
pub(super) fn resource_from_event(event: &ArchivedCalendarEvent, document_id: u32) -> DavResource {
let (start, duration) = event.data.event_range().unwrap_or_default();
DavResource {
document_id,
data: DavResourceMetadata::CalendarEvent {
names: event
.names
.iter()
.map(|name| DavName {
name: name.name.to_string(),
parent_id: name.parent_id.to_native(),
})
.collect(),
start,
duration,
},
}
}
pub(super) fn resource_from_addressbook(
book: &ArchivedAddressBook,
document_id: u32,
) -> DavResource {
DavResource {
document_id,
data: DavResourceMetadata::AddressBook {
name: book.name.to_string(),
acls: book
.acls
.iter()
.map(|acl| AclGrant {
account_id: acl.account_id.to_native(),
grants: Bitmap::from(&acl.grants),
})
.collect(),
},
}
}
pub(super) fn resource_from_card(card: &ArchivedContactCard, document_id: u32) -> DavResource {
DavResource {
document_id,
data: DavResourceMetadata::ContactCard {
names: card
.names
.iter()
.map(|name| DavName {
name: name.name.to_string(),
parent_id: name.parent_id.to_native(),
})
.collect(),
},
}
}
+182
View File
@@ -0,0 +1,182 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
DavResourceName,
file::{ArchivedFileNode, FileNode},
};
use common::{DavPath, DavResource, DavResourceMetadata, DavResources, Server};
use directory::backend::internal::manage::ManageDirectory;
use jmap_proto::types::{
collection::{Collection, SyncCollection},
property::Property,
value::AclGrant,
};
use percent_encoding::NON_ALPHANUMERIC;
use std::sync::Arc;
use store::{
Deserialize, IterateParams, U32_LEN, ValueKey,
ahash::{AHashMap, AHashSet},
write::{AlignedBytes, Archive, ValueClass, key::DeserializeBigEndian},
};
use tokio::sync::Semaphore;
use trc::AddContext;
use utils::{map::bitmap::Bitmap, topological::TopologicalSort};
pub(super) async fn build_file_resources(
server: &Server,
account_id: u32,
update_lock: Arc<Semaphore>,
) -> trc::Result<DavResources> {
let last_change_id = server
.core
.storage
.data
.get_last_change_id(account_id, SyncCollection::FileNode)
.await
.caused_by(trc::location!())?
.unwrap_or_default();
let name = server
.store()
.get_principal_name(account_id)
.await
.caused_by(trc::location!())?
.unwrap_or_else(|| format!("_{account_id}"));
let resources = fetch_files(server, account_id).await?;
let mut files = DavResources {
base_path: format!(
"{}/{}/",
DavResourceName::File.base_path(),
percent_encoding::utf8_percent_encode(&name, NON_ALPHANUMERIC),
),
size: std::mem::size_of::<DavResources>() as u64,
paths: AHashSet::with_capacity(resources.len()),
resources,
item_change_id: last_change_id,
container_change_id: last_change_id,
highest_change_id: last_change_id,
update_lock,
};
build_nested_hierarchy(&mut files);
Ok(files)
}
pub(super) fn build_nested_hierarchy(resources: &mut DavResources) {
let mut topological_sort = TopologicalSort::with_capacity(resources.resources.len());
let mut names = AHashMap::with_capacity(resources.resources.len());
for (resource_idx, resource) in resources.resources.iter().enumerate() {
if let DavResourceMetadata::File { parent_id, .. } = resource.data {
topological_sort.insert(
parent_id.map(|id| id + 1).unwrap_or_default(),
resource.document_id + 1,
);
names.insert(
resource.document_id,
DavPath {
path: resource.container_name().unwrap().to_string(),
parent_id,
hierarchy_seq: 0,
resource_idx,
},
);
}
}
for (hierarchy_sequence, folder_id) in topological_sort.into_iterator().enumerate() {
if folder_id != 0 {
let folder_id = folder_id - 1;
if let Some((name, parent_name)) = names
.get(&folder_id)
.and_then(|folder| folder.parent_id.map(|parent_id| (&folder.path, parent_id)))
.and_then(|(name, parent_id)| {
names.get(&parent_id).map(|folder| (name, &folder.path))
})
{
let name = format!("{parent_name}/{name}");
let folder = names.get_mut(&folder_id).unwrap();
folder.path = name;
folder.hierarchy_seq = hierarchy_sequence as u32;
} else {
names.get_mut(&folder_id).unwrap().hierarchy_seq = hierarchy_sequence as u32;
}
}
}
resources.paths = names
.into_values()
.inspect(|v| {
resources.size += (std::mem::size_of::<DavPath>()
+ std::mem::size_of::<u32>()
+ std::mem::size_of::<usize>()
+ std::mem::size_of::<DavResource>()
+ v.path.len()) as u64;
})
.collect();
}
async fn fetch_files(server: &Server, account_id: u32) -> trc::Result<Vec<DavResource>> {
let mut files = Vec::with_capacity(16);
server
.store()
.iterate(
IterateParams::new(
ValueKey {
account_id,
collection: Collection::FileNode.into(),
document_id: 0,
class: ValueClass::Property(Property::Value.into()),
},
ValueKey {
account_id,
collection: Collection::FileNode.into(),
document_id: u32::MAX,
class: ValueClass::Property(Property::Value.into()),
},
),
|key, value| {
let archive = <Archive<AlignedBytes> as Deserialize>::deserialize(value)?;
files.push(resource_from_file(
archive.unarchive::<FileNode>()?,
key.deserialize_be_u32(key.len() - U32_LEN)?,
));
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
Ok(files)
}
pub(super) fn resource_from_file(node: &ArchivedFileNode, document_id: u32) -> DavResource {
let parent_id = node.parent_id.to_native();
DavResource {
document_id,
data: DavResourceMetadata::File {
name: node.name.as_str().to_string(),
size: node.file.as_ref().map(|f| f.size.to_native()),
parent_id: if parent_id > 0 {
Some(parent_id - 1)
} else {
None
},
acls: node
.acls
.iter()
.map(|acl| AclGrant {
account_id: acl.account_id.to_native(),
grants: Bitmap::from(&acl.grants),
})
.collect(),
},
}
}
+414
View File
@@ -0,0 +1,414 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
calendar::{Calendar, CalendarEvent, CalendarPreferences},
contact::{AddressBook, ContactCard},
file::FileNode,
};
use calcard::{
build_calcard_resources, build_simple_hierarchy, resource_from_addressbook,
resource_from_calendar, resource_from_card, resource_from_event,
};
use common::{CacheSwap, DavResource, DavResources, Server, auth::AccessToken};
use file::{build_file_resources, build_nested_hierarchy, resource_from_file};
use jmap_proto::types::collection::{Collection, SyncCollection};
use std::sync::Arc;
use store::{
ahash::AHashMap,
query::log::{Change, Query},
write::{AlignedBytes, Archive, BatchBuilder},
};
use tokio::sync::Semaphore;
use trc::AddContext;
pub mod calcard;
pub mod file;
pub trait GroupwareCache: Sync + Send {
fn fetch_dav_resources(
&self,
access_token: &AccessToken,
account_id: u32,
collection: SyncCollection,
) -> impl Future<Output = trc::Result<Arc<DavResources>>> + Send;
fn create_default_addressbook(
&self,
access_token: &AccessToken,
account_id: u32,
) -> impl Future<Output = trc::Result<()>> + Send;
fn create_default_calendar(
&self,
access_token: &AccessToken,
account_id: u32,
) -> impl Future<Output = trc::Result<()>> + Send;
fn cached_dav_resources(
&self,
account_id: u32,
collection: SyncCollection,
) -> Option<Arc<DavResources>>;
}
impl GroupwareCache for Server {
async fn fetch_dav_resources(
&self,
access_token: &AccessToken,
account_id: u32,
collection: SyncCollection,
) -> trc::Result<Arc<DavResources>> {
let cache_store = match collection {
SyncCollection::Calendar => &self.inner.cache.events,
SyncCollection::AddressBook => &self.inner.cache.contacts,
SyncCollection::FileNode => &self.inner.cache.files,
_ => unreachable!(),
};
let cache_ = match cache_store.get_value_or_guard_async(&account_id).await {
Ok(cache) => cache,
Err(guard) => {
let cache = full_cache_build(
self,
account_id,
collection,
Arc::new(Semaphore::new(1)),
access_token,
)
.await?;
if guard.insert(CacheSwap::new(cache.clone())).is_err() {
cache_store.insert(account_id, CacheSwap::new(cache.clone()));
}
return Ok(cache);
}
};
// Perform full refresh on stale ids
let cache = cache_.load_full();
if cache.highest_change_id > 0
&& self
.core
.jmap
.changes_max_history
.and_then(|history| self.inner.data.jmap_id_gen.past_id(history))
.is_some_and(|last_change_id| cache.highest_change_id < last_change_id)
{
let cache = full_cache_build(
self,
account_id,
collection,
cache.update_lock.clone(),
access_token,
)
.await?;
cache_.update(cache.clone());
return Ok(cache);
}
// Obtain current state
let changes = self
.core
.storage
.data
.changes(
account_id,
collection,
Query::Since(cache.highest_change_id),
)
.await
.caused_by(trc::location!())?;
// Verify changes
if changes.changes.is_empty() {
return Ok(cache);
}
// Lock for updates
let _permit = cache.update_lock.acquire().await;
let cache = cache_.load_full();
if cache.highest_change_id >= changes.to_change_id {
return Ok(cache);
}
let mut updated_resources = AHashMap::with_capacity(8);
let has_no_children = collection == SyncCollection::FileNode;
for change in changes.changes {
match change {
Change::InsertItem(id) | Change::UpdateItem(id) => {
let document_id = id as u32;
if let Some(archive) = self
.get_archive(account_id, collection.collection(false), document_id)
.await
.caused_by(trc::location!())?
{
updated_resources.insert(
(has_no_children, document_id),
Some(resource_from_archive(
archive,
document_id,
collection,
false,
)?),
);
} else {
updated_resources.insert((has_no_children, document_id), None);
}
}
Change::DeleteItem(id) => {
updated_resources.insert((has_no_children, id as u32), None);
}
Change::InsertContainer(id) | Change::UpdateContainer(id) => {
let document_id = id as u32;
if let Some(archive) = self
.get_archive(account_id, collection.collection(true), document_id)
.await
.caused_by(trc::location!())?
{
updated_resources.insert(
(true, document_id),
Some(resource_from_archive(
archive,
document_id,
collection,
true,
)?),
);
} else {
updated_resources.insert((true, document_id), None);
}
}
Change::DeleteContainer(id) => {
updated_resources.insert((true, id as u32), None);
}
Change::UpdateContainerProperty(_) => (),
}
}
let mut rebuild_hierarchy = false;
let mut resources = Vec::with_capacity(cache.resources.len());
for resource in &cache.resources {
let is_container = has_no_children || resource.is_container();
if let Some(updated_resource) =
updated_resources.remove(&(is_container, resource.document_id))
{
if let Some(updated_resource) = updated_resource {
rebuild_hierarchy =
rebuild_hierarchy || updated_resource.has_hierarchy_changes(resource);
resources.push(updated_resource);
} else {
// Deleted resource
rebuild_hierarchy = true;
}
} else {
resources.push(resource.clone());
}
}
// Add new resources
for resource in updated_resources.into_values().flatten() {
resources.push(resource);
rebuild_hierarchy = true;
}
let cache = if rebuild_hierarchy {
let mut cache = DavResources {
base_path: cache.base_path.clone(),
paths: Default::default(),
resources,
item_change_id: changes.item_change_id.unwrap_or(cache.item_change_id),
container_change_id: changes
.container_change_id
.unwrap_or(cache.container_change_id),
highest_change_id: changes.to_change_id,
size: std::mem::size_of::<DavResources>() as u64,
update_lock: cache.update_lock.clone(),
};
if matches!(collection, SyncCollection::FileNode) {
build_nested_hierarchy(&mut cache);
} else {
build_simple_hierarchy(&mut cache);
}
cache
} else {
DavResources {
base_path: cache.base_path.clone(),
paths: cache.paths.clone(),
resources,
item_change_id: changes.item_change_id.unwrap_or(cache.item_change_id),
container_change_id: changes
.container_change_id
.unwrap_or(cache.container_change_id),
highest_change_id: changes.to_change_id,
size: cache.size,
update_lock: cache.update_lock.clone(),
}
};
let cache = Arc::new(cache);
cache_.update(cache.clone());
Ok(cache)
}
async fn create_default_addressbook(
&self,
access_token: &AccessToken,
account_id: u32,
) -> trc::Result<()> {
if let Some(name) = &self.core.groupware.default_addressbook_name {
let mut batch = BatchBuilder::new();
let document_id = self
.store()
.assign_document_ids(account_id, Collection::AddressBook, 1)
.await?;
AddressBook {
name: name.clone(),
display_name: self.core.groupware.default_addressbook_display_name.clone(),
is_default: true,
..Default::default()
}
.insert(access_token, account_id, document_id, &mut batch)?;
self.commit_batch(batch).await?;
}
Ok(())
}
async fn create_default_calendar(
&self,
access_token: &AccessToken,
account_id: u32,
) -> trc::Result<()> {
if let Some(name) = &self.core.groupware.default_calendar_name {
let mut batch = BatchBuilder::new();
let document_id = self
.store()
.assign_document_ids(account_id, Collection::Calendar, 1)
.await?;
Calendar {
name: name.clone(),
preferences: vec![CalendarPreferences {
account_id,
name: name.clone(),
description: self.core.groupware.default_calendar_display_name.clone(),
..Default::default()
}],
..Default::default()
}
.insert(access_token, account_id, document_id, &mut batch)?;
self.commit_batch(batch).await?;
}
Ok(())
}
fn cached_dav_resources(
&self,
account_id: u32,
collection: SyncCollection,
) -> Option<Arc<DavResources>> {
(match collection {
SyncCollection::Calendar => &self.inner.cache.events,
SyncCollection::AddressBook => &self.inner.cache.contacts,
SyncCollection::FileNode => &self.inner.cache.files,
_ => unreachable!(),
})
.get(&account_id)
.map(|cache| cache.load_full())
}
}
async fn full_cache_build(
server: &Server,
account_id: u32,
collection: SyncCollection,
update_lock: Arc<Semaphore>,
access_token: &AccessToken,
) -> trc::Result<Arc<DavResources>> {
match collection {
SyncCollection::Calendar => {
build_calcard_resources(
server,
access_token,
account_id,
SyncCollection::Calendar,
Collection::Calendar,
Collection::CalendarEvent,
update_lock,
)
.await
}
SyncCollection::AddressBook => {
build_calcard_resources(
server,
access_token,
account_id,
SyncCollection::AddressBook,
Collection::AddressBook,
Collection::ContactCard,
update_lock,
)
.await
}
SyncCollection::FileNode => build_file_resources(server, account_id, update_lock).await,
_ => unreachable!(),
}
.map(Arc::new)
}
fn resource_from_archive(
archive: Archive<AlignedBytes>,
document_id: u32,
collection: SyncCollection,
is_container: bool,
) -> trc::Result<DavResource> {
Ok(match collection {
SyncCollection::Calendar => {
if is_container {
resource_from_calendar(
archive
.unarchive::<Calendar>()
.caused_by(trc::location!())?,
document_id,
)
} else {
resource_from_event(
archive
.unarchive::<CalendarEvent>()
.caused_by(trc::location!())?,
document_id,
)
}
}
SyncCollection::AddressBook => {
if is_container {
resource_from_addressbook(
archive
.unarchive::<AddressBook>()
.caused_by(trc::location!())?,
document_id,
)
} else {
resource_from_card(
archive
.unarchive::<ContactCard>()
.caused_by(trc::location!())?,
document_id,
)
}
}
SyncCollection::FileNode => resource_from_file(
archive
.unarchive::<FileNode>()
.caused_by(trc::location!())?,
document_id,
),
_ => unreachable!(),
})
}
-1
View File
@@ -19,7 +19,6 @@ use calcard::{
};
use chrono::{DateTime, TimeZone};
use dav_proto::schema::property::TimeRange;
use rkyv::time;
use std::str::FromStr;
use store::{
ahash::AHashMap,
+3 -78
View File
@@ -4,37 +4,17 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::storage::index::{
IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject,
};
use jmap_proto::types::{collection::SyncCollection, value::AclGrant};
use store::{SerializeInfallible, write::key::KeySerializer};
use crate::{IDX_NAME, IDX_TIME, IDX_UID};
use super::{
ArchivedCalendar, ArchivedCalendarEvent, ArchivedCalendarPreferences, ArchivedDefaultAlert,
ArchivedTimezone, Calendar, CalendarEvent, CalendarPreferences, DefaultAlert, Timezone,
};
use crate::IDX_UID;
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
use jmap_proto::types::{collection::SyncCollection, value::AclGrant};
impl IndexableObject for Calendar {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
// Note: When adding a new value with index id above 0u8, tune `build_hierarchy`` to skip
// this value during iteration.
[
IndexValue::Index {
field: IDX_NAME,
value: self.name.as_str().into(),
},
IndexValue::Index {
field: IDX_TIME,
value: self
.preferences
.first()
.and_then(|p| p.time_zone.tz())
.map(|tz| tz.as_id().serialize())
.into(),
},
IndexValue::Acl {
value: (&self.acls).into(),
},
@@ -55,19 +35,6 @@ impl IndexableObject for Calendar {
impl IndexableObject for &ArchivedCalendar {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Index {
field: IDX_NAME,
value: self.name.as_str().into(),
},
IndexValue::Index {
field: IDX_TIME,
value: self
.preferences
.first()
.and_then(|p| p.time_zone.tz())
.map(|tz| tz.as_id().serialize())
.into(),
},
IndexValue::Acl {
value: self
.acls
@@ -95,31 +62,10 @@ impl IndexableAndSerializableObject for Calendar {}
impl IndexableObject for CalendarEvent {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::IndexList {
field: IDX_NAME,
value: self
.names
.iter()
.map(|v| IndexItem::Vec(v.serialize()))
.collect::<Vec<_>>(),
},
IndexValue::Index {
field: IDX_UID,
value: self.data.event.uids().next().into(),
},
IndexValue::Index {
field: IDX_TIME,
value: self
.data
.event_range()
.map(|(start, duration)| {
KeySerializer::new(std::mem::size_of::<i64>() + std::mem::size_of::<u32>())
.write(start as u64)
.write(duration)
.finalize()
})
.into(),
},
IndexValue::Quota {
used: self.dead_properties.size() as u32
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
@@ -138,31 +84,10 @@ impl IndexableObject for CalendarEvent {
impl IndexableObject for &ArchivedCalendarEvent {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::IndexList {
field: IDX_NAME,
value: self
.names
.iter()
.map(|v| IndexItem::Vec(v.serialize()))
.collect::<Vec<_>>(),
},
IndexValue::Index {
field: IDX_UID,
value: self.data.event.uids().next().into(),
},
IndexValue::Index {
field: IDX_TIME,
value: self
.data
.event_range()
.map(|(start, duration)| {
KeySerializer::new(std::mem::size_of::<i64>() + std::mem::size_of::<u32>())
.write(start as u64)
.write(duration)
.finalize()
})
.into(),
},
IndexValue::Quota {
used: self.dead_properties.size() as u32
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
+1 -1
View File
@@ -8,8 +8,8 @@ pub mod dates;
pub mod index;
pub mod storage;
use crate::DavName;
use calcard::icalendar::ICalendar;
use common::DavName;
use dav_proto::schema::request::DeadProperty;
use jmap_proto::types::{acl::Acl, value::AclGrant};
use store::{SERIALIZE_CALENDAR_V1, SERIALIZE_CALENDAREVENT_V1, SerializedVersion};
+3 -34
View File
@@ -4,25 +4,14 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::storage::index::{
IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject,
};
use jmap_proto::types::{collection::SyncCollection, value::AclGrant};
use store::SerializeInfallible;
use crate::{IDX_NAME, IDX_UID};
use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard};
use crate::IDX_UID;
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
use jmap_proto::types::{collection::SyncCollection, value::AclGrant};
impl IndexableObject for AddressBook {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
// Note: When adding a new value with index id above 0u8, tune `build_hierarchy`` to skip
// this value during iteration.
[
IndexValue::Index {
field: IDX_NAME,
value: self.name.as_str().into(),
},
IndexValue::Acl {
value: (&self.acls).into(),
},
@@ -43,10 +32,6 @@ impl IndexableObject for AddressBook {
impl IndexableObject for &ArchivedAddressBook {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::Index {
field: IDX_NAME,
value: self.name.as_str().into(),
},
IndexValue::Acl {
value: self
.acls
@@ -74,14 +59,6 @@ impl IndexableAndSerializableObject for AddressBook {}
impl IndexableObject for ContactCard {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::IndexList {
field: IDX_NAME,
value: self
.names
.iter()
.map(|v| IndexItem::Vec(v.serialize()))
.collect::<Vec<_>>(),
},
IndexValue::Index {
field: IDX_UID,
value: self.card.uid().into(),
@@ -104,14 +81,6 @@ impl IndexableObject for ContactCard {
impl IndexableObject for &ArchivedContactCard {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
[
IndexValue::IndexList {
field: IDX_NAME,
value: self
.names
.iter()
.map(|v| IndexItem::Vec(v.serialize()))
.collect::<Vec<_>>(),
},
IndexValue::Index {
field: IDX_UID,
value: self.card.uid().into(),
+1 -3
View File
@@ -8,13 +8,11 @@ pub mod index;
pub mod storage;
use calcard::vcard::VCard;
use common::DavName;
use dav_proto::schema::request::DeadProperty;
use jmap_proto::types::{acl::Acl, value::AclGrant};
use store::{SERIALIZE_ADDRESSBOOK_V1, SERIALIZE_CALENDAREVENT_V1, SerializedVersion};
use crate::DavName;
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
+27 -69
View File
@@ -4,57 +4,30 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::storage::{
folder::FolderHierarchy,
index::{IndexValue, IndexableAndSerializableObject, IndexableObject},
};
use jmap_proto::types::{collection::SyncCollection, property::Property, value::AclGrant};
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
use jmap_proto::types::{collection::SyncCollection, value::AclGrant};
use super::{ArchivedFileNode, FileNode};
impl IndexableObject for FileNode {
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
let size = self.dead_properties.size() as u32
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
+ self.name.len() as u32;
let mut values = Vec::with_capacity(6);
values.extend([
IndexValue::Index {
field: Property::Name.into(),
value: percent_encoding::percent_decode_str(&self.name)
.decode_utf8()
.unwrap_or_else(|_| self.name.as_str().into())
.to_lowercase()
.into(),
},
IndexValue::Index {
field: Property::ParentId.into(),
value: self.parent_id.into(),
},
IndexValue::Acl {
value: (&self.acls).into(),
},
IndexValue::LogContainer {
IndexValue::LogItem {
prefix: None,
sync_collection: SyncCollection::FileNode.into(),
},
IndexValue::Quota { used: self.size() },
]);
if let Some(file) = &self.file {
let size = size + file.size;
values.extend([
IndexValue::Blob {
value: file.blob_hash.clone(),
},
IndexValue::Index {
field: Property::Size.into(),
value: size.into(),
},
IndexValue::Quota { used: size },
]);
} else {
values.push(IndexValue::Quota { used: size });
values.extend([IndexValue::Blob {
value: file.blob_hash.clone(),
}]);
}
values.into_iter()
@@ -66,14 +39,6 @@ impl IndexableObject for &ArchivedFileNode {
let mut values = Vec::with_capacity(6);
values.extend([
IndexValue::Index {
field: Property::Name.into(),
value: self.name.to_lowercase().into(),
},
IndexValue::Index {
field: Property::ParentId.into(),
value: u32::from(self.parent_id).into(),
},
IndexValue::Acl {
value: self
.acls
@@ -82,25 +47,17 @@ impl IndexableObject for &ArchivedFileNode {
.collect::<Vec<_>>()
.into(),
},
IndexValue::LogContainer {
IndexValue::LogItem {
prefix: None,
sync_collection: SyncCollection::FileNode.into(),
},
IndexValue::Quota { used: self.size() },
]);
let size = self.size();
if let Some(file) = self.file.as_ref() {
values.extend([
IndexValue::Blob {
value: (&file.blob_hash).into(),
},
IndexValue::Index {
field: Property::Size.into(),
value: size.into(),
},
IndexValue::Quota { used: size },
]);
} else {
values.push(IndexValue::Quota { used: size });
values.extend([IndexValue::Blob {
value: (&file.blob_hash).into(),
}]);
}
values.into_iter()
@@ -109,19 +66,11 @@ impl IndexableObject for &ArchivedFileNode {
impl IndexableAndSerializableObject for FileNode {}
impl FolderHierarchy for ArchivedFileNode {
fn name(&self) -> String {
self.name.to_string()
}
fn parent_id(&self) -> u32 {
u32::from(self.parent_id)
}
fn is_container(&self) -> bool {
self.file.is_none()
}
pub trait NodeSize {
fn size(&self) -> u32;
}
impl NodeSize for ArchivedFileNode {
fn size(&self) -> u32 {
self.dead_properties.size() as u32
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
@@ -129,3 +78,12 @@ impl FolderHierarchy for ArchivedFileNode {
+ self.file.as_ref().map_or(0, |f| u32::from(f.size))
}
}
impl NodeSize for FileNode {
fn size(&self) -> u32 {
self.dead_properties.size() as u32
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
+ self.name.len() as u32
+ self.file.as_ref().map_or(0, |f| f.size)
}
}
-372
View File
@@ -1,372 +0,0 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
DavName, DavResourceName, IDX_NAME, IDX_TIME,
calendar::{Calendar, CalendarPreferences},
contact::AddressBook,
file::FileNode,
};
use calcard::common::timezone::Tz;
use common::{
DavResource, DavResourceId, DavResourceMetadata, DavResources, Server, auth::AccessToken,
};
use directory::backend::internal::manage::ManageDirectory;
use jmap_proto::types::collection::Collection;
use percent_encoding::NON_ALPHANUMERIC;
use std::sync::Arc;
use store::{
Deserialize, IndexKey, IndexKeyPrefix, IterateParams, SerializeInfallible, U32_LEN, U64_LEN,
ahash::AHashMap,
write::{BatchBuilder, key::DeserializeBigEndian},
};
use trc::AddContext;
use utils::bimap::IdBimap;
pub trait DavHierarchy: Sync + Send {
fn fetch_dav_resources(
&self,
access_token: &AccessToken,
account_id: u32,
collection: Collection,
) -> impl Future<Output = trc::Result<Arc<DavResources>>> + Send;
fn create_default_addressbook(
&self,
access_token: &AccessToken,
account_id: u32,
) -> impl Future<Output = trc::Result<()>> + Send;
fn create_default_calendar(
&self,
access_token: &AccessToken,
account_id: u32,
) -> impl Future<Output = trc::Result<()>> + Send;
fn cached_dav_resources(
&self,
account_id: u32,
collection: Collection,
) -> Option<Arc<DavResources>>;
}
impl DavHierarchy for Server {
async fn fetch_dav_resources(
&self,
access_token: &AccessToken,
account_id: u32,
collection: Collection,
) -> trc::Result<Arc<DavResources>> {
let todo = "fix";
let is_files = collection == Collection::FileNode;
let mut change_id = self
.store()
.get_last_change_id(account_id, collection)
.await
.caused_by(trc::location!())?;
if !is_files {
let child_change_id = self
.store()
.get_last_change_id(account_id, collection.child_collection().unwrap())
.await
.caused_by(trc::location!())?;
change_id = change_id.max(child_change_id);
}
let resource_id = DavResourceId {
account_id,
collection: collection.into(),
};
if let Some(files) = self
.inner
.cache
.dav
.get(&resource_id)
.filter(|x| x.modseq == change_id)
{
Ok(files)
} else {
let mut files = if !is_files {
let files = build_hierarchy(self, account_id, collection).await?;
if files.paths.is_empty() {
match collection {
Collection::Calendar => {
self.create_default_calendar(access_token, account_id)
.await?
}
Collection::AddressBook => {
self.create_default_addressbook(access_token, account_id)
.await?
}
_ => unreachable!(),
}
build_hierarchy(self, account_id, collection).await?
} else {
files
}
} else {
build_file_hierarchy(self, account_id).await?
};
files.modseq = change_id;
let files = Arc::new(files);
self.inner.cache.dav.insert(resource_id, files.clone());
Ok(files)
}
}
async fn create_default_addressbook(
&self,
access_token: &AccessToken,
account_id: u32,
) -> trc::Result<()> {
if let Some(name) = &self.core.groupware.default_addressbook_name {
let mut batch = BatchBuilder::new();
let document_id = self
.store()
.assign_document_ids(account_id, Collection::AddressBook, 1)
.await?;
AddressBook {
name: name.clone(),
display_name: self.core.groupware.default_addressbook_display_name.clone(),
is_default: true,
..Default::default()
}
.insert(access_token, account_id, document_id, &mut batch)?;
self.commit_batch(batch).await?;
}
Ok(())
}
async fn create_default_calendar(
&self,
access_token: &AccessToken,
account_id: u32,
) -> trc::Result<()> {
if let Some(name) = &self.core.groupware.default_calendar_name {
let mut batch = BatchBuilder::new();
let document_id = self
.store()
.assign_document_ids(account_id, Collection::Calendar, 1)
.await?;
Calendar {
name: name.clone(),
preferences: vec![CalendarPreferences {
account_id,
name: name.clone(),
description: self.core.groupware.default_calendar_display_name.clone(),
..Default::default()
}],
..Default::default()
}
.insert(access_token, account_id, document_id, &mut batch)?;
self.commit_batch(batch).await?;
}
Ok(())
}
fn cached_dav_resources(
&self,
account_id: u32,
collection: Collection,
) -> Option<Arc<DavResources>> {
self.inner
.cache
.dav
.get(&DavResourceId {
account_id,
collection: collection.into(),
})
.clone()
}
}
async fn build_hierarchy(
server: &Server,
account_id: u32,
collection_: Collection,
) -> trc::Result<DavResources> {
let base_path = DavResourceName::from(collection_).base_path();
let collection = u8::from(collection_);
let mut containers: AHashMap<u32, String> = AHashMap::with_capacity(16);
let mut resources: AHashMap<u32, Vec<DavName>> = AHashMap::with_capacity(16);
let mut time_ranges: AHashMap<u32, (i64, u32)> = AHashMap::new();
let mut time_zones: AHashMap<u32, Tz> = AHashMap::new();
server
.store()
.iterate(
IterateParams::new(
IndexKey {
account_id,
collection,
document_id: 0,
field: IDX_NAME,
key: 0u32.serialize(),
},
IndexKey {
account_id,
collection: collection + 1,
document_id: u32::MAX,
field: IDX_TIME,
key: u32::MAX.serialize(),
},
)
.no_values()
.ascending(),
|key, _| {
let document_id = key.deserialize_be_u32(key.len() - U32_LEN)?;
let value = key
.get(IndexKeyPrefix::len()..key.len() - U32_LEN)
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
let key_collection = key
.get(U32_LEN)
.copied()
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
let field = key
.get(U32_LEN + 1)
.copied()
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
if key_collection == collection {
if field == IDX_NAME {
containers.insert(
document_id,
std::str::from_utf8(value)
.map_err(|_| {
trc::Error::corrupted_key(key, None, trc::location!())
})?
.to_string(),
);
} else if field == IDX_TIME {
let tz = Tz::from_id(key.deserialize_be_u16(IndexKeyPrefix::len())?)
.ok_or_else(|| {
trc::Error::corrupted_key(key, None, trc::location!())
})?;
time_zones.insert(document_id, tz);
}
} else if field == IDX_NAME {
resources.entry(document_id).or_default().push(
DavName::deserialize(value)
.map_err(|_| trc::Error::corrupted_key(key, None, trc::location!()))?,
);
} else if field == IDX_TIME {
let start_time = key.deserialize_be_u64(IndexKeyPrefix::len())?;
let duration = key.deserialize_be_u32(IndexKeyPrefix::len() + U64_LEN)?;
time_ranges.insert(document_id, (start_time as i64, duration));
}
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
let name = server
.store()
.get_principal_name(account_id)
.await
.caused_by(trc::location!())?
.unwrap_or_else(|| format!("_{account_id}"));
let mut files = DavResources {
paths: IdBimap::with_capacity(containers.len() + resources.len()),
size: std::mem::size_of::<DavResources>() as u64,
modseq: None,
base_path: format!(
"{}/{}/",
base_path,
percent_encoding::utf8_percent_encode(&name, NON_ALPHANUMERIC),
),
};
for (document_id, dav_names) in resources {
for dav_name in dav_names {
if let Some(container) = containers.get(&dav_name.parent_id) {
let name = format!("{}/{}", container, dav_name.name);
files.size += (std::mem::size_of::<u32>()
+ std::mem::size_of::<String>()
+ name.len()) as u64;
files.paths.insert(DavResource {
document_id,
parent_id: dav_name.parent_id.into(),
name,
data: time_ranges
.get(&document_id)
.map(|(start, duration)| DavResourceMetadata::CalendarEvent {
start: *start,
duration: *duration,
})
.unwrap_or(DavResourceMetadata::None),
});
}
}
}
for (document_id, name) in containers {
files.size +=
(std::mem::size_of::<u32>() + std::mem::size_of::<String>() + name.len()) as u64;
files.paths.insert(DavResource {
document_id,
parent_id: None,
name,
data: time_zones
.get(&document_id)
.map(|tz| DavResourceMetadata::Calendar { tz: *tz })
.unwrap_or(DavResourceMetadata::None),
});
}
Ok(files)
}
async fn build_file_hierarchy(server: &Server, account_id: u32) -> trc::Result<DavResources> {
let list = server
.fetch_folders::<FileNode>(account_id, Collection::FileNode)
.await
.caused_by(trc::location!())?;
let name = server
.store()
.get_principal_name(account_id)
.await
.caused_by(trc::location!())?
.unwrap_or_else(|| format!("_{account_id}"));
let mut files = DavResources {
base_path: format!(
"{}/{}/",
DavResourceName::File.base_path(),
percent_encoding::utf8_percent_encode(&name, NON_ALPHANUMERIC),
),
paths: IdBimap::with_capacity(list.len()),
size: std::mem::size_of::<DavResources>() as u64,
modseq: None,
};
for expanded in list.into_iterator() {
files.size += (std::mem::size_of::<u32>()
+ std::mem::size_of::<String>()
+ expanded.name.len()) as u64;
files.paths.insert(DavResource {
document_id: expanded.document_id,
parent_id: expanded.parent_id,
name: expanded.name,
data: DavResourceMetadata::File {
size: expanded.size,
hierarchy_sequence: expanded.hierarchy_sequence,
is_container: expanded.is_container,
},
});
}
Ok(files)
}
+3 -73
View File
@@ -7,17 +7,13 @@
use calcard::common::timezone::Tz;
use common::DavResources;
use jmap_proto::types::collection::Collection;
use store::{Deserialize, SerializeInfallible, write::key::KeySerializer};
use utils::codec::leb128::Leb128Reader;
pub mod cache;
pub mod calendar;
pub mod contact;
pub mod file;
pub mod hierarchy;
pub const IDX_NAME: u8 = 0;
pub const IDX_TIME: u8 = 1;
pub const IDX_UID: u8 = 2;
pub const IDX_UID: u8 = 0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DavResourceName {
@@ -29,61 +25,6 @@ pub enum DavResourceName {
pub struct DestroyArchive<T>(pub T);
#[derive(
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
)]
#[rkyv(derive(Debug))]
pub struct DavName {
pub name: String,
pub parent_id: u32,
}
impl SerializeInfallible for DavName {
fn serialize(&self) -> Vec<u8> {
KeySerializer::new(self.name.len() + std::mem::size_of::<u32>())
.write_leb128(self.parent_id)
.write(self.name.as_bytes())
.finalize()
}
}
impl SerializeInfallible for ArchivedDavName {
fn serialize(&self) -> Vec<u8> {
KeySerializer::new(self.name.len() + std::mem::size_of::<u32>())
.write_leb128(self.parent_id.to_native())
.write(self.name.as_bytes())
.finalize()
}
}
impl DavName {
pub fn new(name: String, parent_id: u32) -> Self {
Self { name, parent_id }
}
}
impl Deserialize for DavName {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
let (parent_id, bytes_read) = bytes.read_leb128::<u32>().ok_or_else(|| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes)
})?;
let name = bytes
.get(bytes_read..)
.and_then(|bytes| std::str::from_utf8(bytes).ok())
.ok_or_else(|| {
trc::StoreEvent::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes)
})?
.into();
Ok(DavName { name, parent_id })
}
}
impl DavResourceName {
pub fn parse(service: &str) -> Option<Self> {
hashify::tiny_map!(service.as_bytes(),
@@ -138,22 +79,11 @@ impl From<Collection> for DavResourceName {
pub trait DavCalendarResource {
fn calendar_default_tz(&self, calendar_id: u32) -> Option<Tz>;
fn event_default_tz(&self, event_id: u32) -> Option<Tz>;
}
impl DavCalendarResource for DavResources {
fn calendar_default_tz(&self, calendar_id: u32) -> Option<Tz> {
self.paths
.iter()
.find(|c| c.is_container() && c.document_id == calendar_id)
self.container_resource_by_id(calendar_id)
.and_then(|c| c.timezone())
}
fn event_default_tz(&self, event_id: u32) -> Option<Tz> {
self.paths
.iter()
.find(|c| !c.is_container() && c.document_id == event_id)
.and_then(|c| c.parent_id)
.and_then(|parent_id| self.calendar_default_tz(parent_id))
}
}
+55
View File
@@ -90,6 +90,61 @@ impl Collection {
}
}
impl SyncCollection {
pub fn collection(&self, is_container: bool) -> Collection {
match self {
SyncCollection::Email => {
if is_container {
Collection::Mailbox
} else {
Collection::Email
}
}
SyncCollection::Thread => Collection::Thread,
SyncCollection::Calendar => {
if is_container {
Collection::Calendar
} else {
Collection::CalendarEvent
}
}
SyncCollection::AddressBook => {
if is_container {
Collection::AddressBook
} else {
Collection::ContactCard
}
}
SyncCollection::FileNode => Collection::FileNode,
SyncCollection::Identity => Collection::Identity,
SyncCollection::EmailSubmission => Collection::EmailSubmission,
SyncCollection::SieveScript => Collection::SieveScript,
SyncCollection::None => Collection::None,
}
}
}
impl From<Collection> for SyncCollection {
fn from(v: Collection) -> Self {
match v {
Collection::Email => SyncCollection::Email,
Collection::Mailbox => SyncCollection::Email,
Collection::Thread => SyncCollection::Thread,
Collection::Identity => SyncCollection::Identity,
Collection::EmailSubmission => SyncCollection::EmailSubmission,
Collection::SieveScript => SyncCollection::SieveScript,
Collection::PushSubscription => SyncCollection::None,
Collection::Principal => SyncCollection::None,
Collection::Calendar => SyncCollection::Calendar,
Collection::CalendarEvent => SyncCollection::Calendar,
Collection::AddressBook => SyncCollection::AddressBook,
Collection::ContactCard => SyncCollection::AddressBook,
Collection::FileNode => SyncCollection::FileNode,
_ => SyncCollection::None,
}
}
}
impl From<u8> for Collection {
fn from(v: u8) -> Self {
match v {
+10 -7
View File
@@ -8,11 +8,11 @@ use super::WebDavTest;
pub async fn test(test: &WebDavTest) {
println!("Running basic tests...");
let client = test.client("john");
let john = test.client("john");
let jane = test.client("jane");
// Test OPTIONS request
client
.request("OPTIONS", "/dav/file", "")
john.request("OPTIONS", "/dav/file", "")
.await
.with_header(
"dav",
@@ -27,18 +27,21 @@ pub async fn test(test: &WebDavTest) {
);
// Test Discovery
client
.request("PROPFIND", "/.well-known/carddav", "")
john.request("PROPFIND", "/.well-known/carddav", "")
.await
.with_values(
"D:multistatus.D:response.D:href",
["/dav/card/", "/dav/card/john/"],
);
test.client("jane")
.request("PROPFIND", "/.well-known/caldav", "")
jane.request("PROPFIND", "/.well-known/caldav", "")
.await
.with_values(
"D:multistatus.D:response.D:href",
["/dav/cal/", "/dav/cal/jane/", "/dav/cal/support/"],
);
john.delete_default_containers().await;
jane.delete_default_containers().await;
jane.delete_default_containers_by_account("support").await;
test.assert_is_empty().await;
}
+11 -3
View File
@@ -22,7 +22,7 @@ use common::{
manager::boot::build_ipc,
};
use dav_proto::schema::property::{DavProperty, WebDavProperty};
use groupware::{DavResourceName, hierarchy::DavHierarchy};
use groupware::{DavResourceName, cache::GroupwareCache};
use http::HttpSessionManager;
use hyper::{HeaderMap, Method, StatusCode, header::AUTHORIZATION};
use imap::core::ImapSessionManager;
@@ -227,17 +227,25 @@ impl WebDavTest {
let account_id = self.client(name).account_id;
let access_token = self.server.get_access_token(account_id).await.unwrap();
self.server
.fetch_dav_resources(&access_token, account_id, collection)
.fetch_dav_resources(&access_token, account_id, collection.into())
.await
.unwrap()
}
pub async fn assert_is_empty(&self) {
assert_is_empty(self.server.clone()).await;
for cache in [
&self.server.inner.cache.events,
&self.server.inner.cache.contacts,
&self.server.inner.cache.files,
] {
cache.clear();
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct DummyWebDavClient {
account_id: u32,
name: &'static str,
@@ -653,7 +661,7 @@ pub trait DavResourcesTest {
impl DavResourcesTest for DavResources {
fn items(&self) -> Vec<DavResource> {
self.paths.iter().cloned().collect()
self.resources.clone()
}
}
+1
View File
@@ -328,6 +328,7 @@ pub async fn test(test: &WebDavTest) {
.with_hrefs([format!("{}/support/", DavResourceName::Principal.base_path()).as_str()]);
client.delete_default_containers().await;
client.delete_default_containers_by_account("support").await;
test.assert_is_empty().await;
}
+18 -6
View File
@@ -197,9 +197,15 @@ pub async fn test(test: &WebDavTest) {
// PUT precondition enforcement
let modseq = [
test.resources("john", Collection::FileNode).await.modseq,
test.resources("john", Collection::Calendar).await.modseq,
test.resources("john", Collection::AddressBook).await.modseq,
test.resources("john", Collection::FileNode)
.await
.highest_change_id,
test.resources("john", Collection::Calendar)
.await
.highest_change_id,
test.resources("john", Collection::AddressBook)
.await
.highest_change_id,
];
for (path, ct, content) in [
("/dav/file/john/file1.txt", "text/plain", TEST_FILE_1),
@@ -263,9 +269,15 @@ pub async fn test(test: &WebDavTest) {
}
assert_eq!(
[
test.resources("john", Collection::FileNode).await.modseq,
test.resources("john", Collection::Calendar).await.modseq,
test.resources("john", Collection::AddressBook).await.modseq,
test.resources("john", Collection::FileNode)
.await
.highest_change_id,
test.resources("john", Collection::Calendar)
.await
.highest_change_id,
test.resources("john", Collection::AddressBook)
.await
.highest_change_id,
],
modseq
);