mirror of
https://github.com/stalwartlabs/stalwart.git
synced 2026-11-04 08:14:10 +00:00
Groupware caching improvements
This commit is contained in:
@@ -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
@@ -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());
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,5 @@
|
||||
*/
|
||||
|
||||
pub mod blob;
|
||||
pub mod folder;
|
||||
pub mod index;
|
||||
pub mod state;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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!())?;
|
||||
|
||||
@@ -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)>> =
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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_)?;
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_)?;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
+182
@@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
+414
@@ -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!(),
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
)]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user