Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccountCache, sharing::notification::ShareNotification};
|
||||
use jmap_proto::{
|
||||
method::get::{GetRequest, GetResponse},
|
||||
object::{
|
||||
JmapRight,
|
||||
addressbook::AddressBookRight,
|
||||
calendar::CalendarRight,
|
||||
file_node::FileNodeRight,
|
||||
mailbox::MailboxRight,
|
||||
share_notification::{self, ShareNotificationProperty, ShareNotificationValue},
|
||||
},
|
||||
request::IntoValid,
|
||||
types::{date::UTCDate, state::State},
|
||||
};
|
||||
use jmap_tools::{Key, Map, Value};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use store::{
|
||||
Deserialize, IterateParams, LogKey, U64_LEN,
|
||||
ahash::{AHashMap, AHashSet},
|
||||
write::key::DeserializeBigEndian,
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
acl::Acl,
|
||||
collection::{Collection, SyncCollection},
|
||||
id::Id,
|
||||
type_state::DataType,
|
||||
};
|
||||
use utils::{map::bitmap::Bitmap, snowflake::SnowflakeIdGenerator};
|
||||
|
||||
pub trait ShareNotificationGet: Sync + Send {
|
||||
fn share_notification_get(
|
||||
&self,
|
||||
request: GetRequest<share_notification::ShareNotification>,
|
||||
) -> impl Future<Output = trc::Result<GetResponse<share_notification::ShareNotification>>> + Send;
|
||||
}
|
||||
|
||||
impl ShareNotificationGet for Server {
|
||||
async fn share_notification_get(
|
||||
&self,
|
||||
mut request: GetRequest<share_notification::ShareNotification>,
|
||||
) -> trc::Result<GetResponse<share_notification::ShareNotification>> {
|
||||
let properties = request.unwrap_properties(&[
|
||||
ShareNotificationProperty::Id,
|
||||
ShareNotificationProperty::Name,
|
||||
ShareNotificationProperty::ChangedBy,
|
||||
ShareNotificationProperty::Created,
|
||||
ShareNotificationProperty::ObjectAccountId,
|
||||
ShareNotificationProperty::ObjectId,
|
||||
ShareNotificationProperty::ObjectType,
|
||||
ShareNotificationProperty::OldRights,
|
||||
ShareNotificationProperty::NewRights,
|
||||
ShareNotificationProperty::Name,
|
||||
]);
|
||||
|
||||
let account_id = request.account_id.document_id();
|
||||
let mut min_id = u64::MAX;
|
||||
let mut max_id = 0u64;
|
||||
|
||||
let mut account_cache: AHashMap<u32, Arc<AccountCache>> = AHashMap::new();
|
||||
|
||||
let mut ids = if let Some(ids) = request.ids.take() {
|
||||
let ids = ids.unwrap();
|
||||
if ids.len() <= self.core.jmap.get_max_objects {
|
||||
ids.into_valid()
|
||||
.map(|id| {
|
||||
let id_num = *id.as_ref();
|
||||
if id_num < min_id {
|
||||
min_id = id_num;
|
||||
}
|
||||
if id_num > max_id {
|
||||
max_id = id_num;
|
||||
}
|
||||
id_num
|
||||
})
|
||||
.collect::<AHashSet<_>>()
|
||||
} else {
|
||||
return Err(trc::JmapEvent::RequestTooLarge.into_err());
|
||||
}
|
||||
} else {
|
||||
AHashSet::new()
|
||||
};
|
||||
let has_ids = !ids.is_empty();
|
||||
|
||||
if min_id == u64::MAX {
|
||||
min_id = SnowflakeIdGenerator::from_duration(
|
||||
self.core
|
||||
.email
|
||||
.share_notification_max_history
|
||||
.unwrap_or(Duration::from_secs(30 * 86400)),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
if max_id == 0 {
|
||||
max_id = u64::MAX;
|
||||
}
|
||||
|
||||
let mut response = GetResponse {
|
||||
account_id: request.account_id.into(),
|
||||
state: None,
|
||||
list: Vec::with_capacity(ids.len()),
|
||||
not_found: vec![],
|
||||
};
|
||||
let mut notifications = Vec::new();
|
||||
|
||||
self.store()
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
LogKey {
|
||||
account_id,
|
||||
collection: SyncCollection::ShareNotification.into(),
|
||||
change_id: min_id,
|
||||
},
|
||||
LogKey {
|
||||
account_id,
|
||||
collection: SyncCollection::ShareNotification.into(),
|
||||
change_id: max_id.saturating_add(1),
|
||||
},
|
||||
)
|
||||
.descending(),
|
||||
|key, value| {
|
||||
let change_id = key.deserialize_be_u64(key.len() - U64_LEN)?;
|
||||
if response.state.is_none() {
|
||||
response.state = Some(State::Exact(change_id));
|
||||
}
|
||||
|
||||
if !has_ids || ids.remove(&change_id) {
|
||||
notifications.push((
|
||||
change_id,
|
||||
ShareNotification::deserialize(value).caused_by(trc::location!())?,
|
||||
));
|
||||
}
|
||||
|
||||
Ok((!has_ids || !ids.is_empty())
|
||||
&& notifications.len() < self.core.jmap.get_max_objects)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
for (change_id, notification) in notifications {
|
||||
let changed_by_account =
|
||||
if let Some(account) = account_cache.get(¬ification.changed_by) {
|
||||
account.clone()
|
||||
} else {
|
||||
let account = if let Ok(account) = self.account(notification.changed_by).await {
|
||||
account
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
account_cache.insert(notification.changed_by, account.clone());
|
||||
account
|
||||
};
|
||||
|
||||
response.list.push(build_share_notification(
|
||||
change_id,
|
||||
notification,
|
||||
&changed_by_account,
|
||||
&properties,
|
||||
));
|
||||
}
|
||||
|
||||
if response.state.is_none() {
|
||||
response.state = Some(State::Initial);
|
||||
}
|
||||
|
||||
for id in ids {
|
||||
response.push_not_found(Id::from(id));
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_share_notification(
|
||||
id: u64,
|
||||
mut notification: ShareNotification,
|
||||
changed_by: &AccountCache,
|
||||
properties: &[ShareNotificationProperty],
|
||||
) -> Value<'static, ShareNotificationProperty, ShareNotificationValue> {
|
||||
let mut result = Map::with_capacity(properties.len());
|
||||
for property in properties {
|
||||
let value = match property {
|
||||
ShareNotificationProperty::Id => Value::Element(ShareNotificationValue::Id(id.into())),
|
||||
ShareNotificationProperty::Created => Value::Element(ShareNotificationValue::Date(
|
||||
UTCDate::from_timestamp(SnowflakeIdGenerator::to_timestamp(id) as i64),
|
||||
)),
|
||||
ShareNotificationProperty::ChangedBy => Value::Object(Map::from(vec![
|
||||
(
|
||||
Key::Property(ShareNotificationProperty::ChangedByPrincipalId),
|
||||
Value::Element(ShareNotificationValue::Id(notification.changed_by.into())),
|
||||
),
|
||||
(
|
||||
Key::Property(ShareNotificationProperty::ChangedByName),
|
||||
Value::Str(
|
||||
changed_by
|
||||
.description()
|
||||
.unwrap_or(changed_by.name())
|
||||
.to_string()
|
||||
.into(),
|
||||
),
|
||||
),
|
||||
(
|
||||
Key::Property(ShareNotificationProperty::ChangedByEmail),
|
||||
Value::Str(changed_by.name().to_string().into()),
|
||||
),
|
||||
])),
|
||||
ShareNotificationProperty::ObjectType => DataType::try_from(notification.object_type)
|
||||
.ok()
|
||||
.map(|typ| Value::Element(ShareNotificationValue::ObjectType(typ)))
|
||||
.unwrap_or(Value::Null),
|
||||
ShareNotificationProperty::ObjectAccountId => Value::Element(
|
||||
ShareNotificationValue::Id(notification.object_account_id.into()),
|
||||
),
|
||||
ShareNotificationProperty::ObjectId => {
|
||||
Value::Element(ShareNotificationValue::Id(notification.object_id.into()))
|
||||
}
|
||||
ShareNotificationProperty::OldRights => {
|
||||
map_rights(notification.object_type, notification.old_rights)
|
||||
}
|
||||
ShareNotificationProperty::NewRights => {
|
||||
map_rights(notification.object_type, notification.new_rights)
|
||||
}
|
||||
ShareNotificationProperty::Name => {
|
||||
Value::Str(std::mem::take(&mut notification.name).into())
|
||||
}
|
||||
_ => Value::Null,
|
||||
};
|
||||
|
||||
result.insert_unchecked(property.clone(), value);
|
||||
}
|
||||
|
||||
Value::Object(result)
|
||||
}
|
||||
|
||||
fn map_rights(
|
||||
object_type: Collection,
|
||||
rights: Bitmap<Acl>,
|
||||
) -> Value<'static, ShareNotificationProperty, ShareNotificationValue> {
|
||||
let mut obj = Map::with_capacity(3);
|
||||
|
||||
match object_type {
|
||||
Collection::Calendar | Collection::CalendarEvent => {
|
||||
for right in CalendarRight::all_rights() {
|
||||
obj.insert_unchecked(
|
||||
Key::Borrowed(right.as_str()),
|
||||
Value::Bool(right.to_acl().iter().all(|acl| rights.contains(*acl))),
|
||||
);
|
||||
}
|
||||
}
|
||||
Collection::AddressBook | Collection::ContactCard => {
|
||||
for right in AddressBookRight::all_rights() {
|
||||
obj.insert_unchecked(
|
||||
Key::Borrowed(right.as_str()),
|
||||
Value::Bool(right.to_acl().iter().all(|acl| rights.contains(*acl))),
|
||||
);
|
||||
}
|
||||
}
|
||||
Collection::FileNode => {
|
||||
for right in FileNodeRight::all_rights() {
|
||||
obj.insert_unchecked(
|
||||
Key::Borrowed(right.as_str()),
|
||||
Value::Bool(right.to_acl().iter().all(|acl| rights.contains(*acl))),
|
||||
);
|
||||
}
|
||||
}
|
||||
Collection::Mailbox | Collection::Email => {
|
||||
for right in MailboxRight::all_rights() {
|
||||
obj.insert_unchecked(
|
||||
Key::Borrowed(right.as_str()),
|
||||
Value::Bool(right.to_acl().iter().all(|acl| rights.contains(*acl))),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Value::Object(obj)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod get;
|
||||
pub mod query;
|
||||
pub mod set;
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::api::query::QueryResponseBuilder;
|
||||
use common::{Server, sharing::notification::ShareNotification};
|
||||
use jmap_proto::{
|
||||
method::query::{Filter, QueryRequest, QueryResponse},
|
||||
object::share_notification::{self, ShareNotificationFilter},
|
||||
types::state::State,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use store::{Deserialize, IterateParams, LogKey, U64_LEN, write::key::DeserializeBigEndian};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
collection::{Collection, SyncCollection},
|
||||
id::Id,
|
||||
};
|
||||
use utils::snowflake::SnowflakeIdGenerator;
|
||||
|
||||
pub trait ShareNotificationQuery: Sync + Send {
|
||||
fn share_notification_query(
|
||||
&self,
|
||||
request: QueryRequest<share_notification::ShareNotification>,
|
||||
) -> impl Future<Output = trc::Result<QueryResponse>> + Send;
|
||||
}
|
||||
|
||||
impl ShareNotificationQuery for Server {
|
||||
async fn share_notification_query(
|
||||
&self,
|
||||
mut request: QueryRequest<share_notification::ShareNotification>,
|
||||
) -> trc::Result<QueryResponse> {
|
||||
let account_id = request.account_id.document_id();
|
||||
let mut from_change_id = SnowflakeIdGenerator::from_duration(
|
||||
self.core
|
||||
.email
|
||||
.share_notification_max_history
|
||||
.unwrap_or(Duration::from_secs(30 * 86400)),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
let mut to_change_id = u64::MAX;
|
||||
let mut collection = None;
|
||||
let mut object_type = None;
|
||||
|
||||
for cond in std::mem::take(&mut request.filter) {
|
||||
match cond {
|
||||
Filter::Property(cond) => match cond {
|
||||
ShareNotificationFilter::After(utcdate) => {
|
||||
from_change_id =
|
||||
SnowflakeIdGenerator::from_timestamp(utcdate.timestamp() as u64)
|
||||
.unwrap_or(0);
|
||||
}
|
||||
ShareNotificationFilter::Before(utcdate) => {
|
||||
to_change_id =
|
||||
SnowflakeIdGenerator::from_timestamp(utcdate.timestamp() as u64)
|
||||
.unwrap_or(u64::MAX);
|
||||
}
|
||||
ShareNotificationFilter::ObjectType(typ) => {
|
||||
collection = Collection::try_from(typ).ok();
|
||||
}
|
||||
ShareNotificationFilter::ObjectAccountId(id) => {
|
||||
object_type = Some(id.document_id());
|
||||
}
|
||||
ShareNotificationFilter::_T(other) => {
|
||||
return Err(trc::JmapEvent::UnsupportedFilter.into_err().details(other));
|
||||
}
|
||||
},
|
||||
Filter::And | Filter::Or | Filter::Not | Filter::Close => {
|
||||
return Err(trc::JmapEvent::UnsupportedFilter
|
||||
.into_err()
|
||||
.details("Logical operators are not supported"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
self.store()
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
LogKey {
|
||||
account_id,
|
||||
collection: SyncCollection::ShareNotification.into(),
|
||||
change_id: from_change_id,
|
||||
},
|
||||
LogKey {
|
||||
account_id,
|
||||
collection: SyncCollection::ShareNotification.into(),
|
||||
change_id: to_change_id,
|
||||
},
|
||||
)
|
||||
.descending(),
|
||||
|key, value| {
|
||||
let change_id = key.deserialize_be_u64(key.len() - U64_LEN)?;
|
||||
|
||||
if collection.is_some() || object_type.is_some() {
|
||||
let notification =
|
||||
ShareNotification::deserialize(value).caused_by(trc::location!())?;
|
||||
if collection.is_some_and(|c| c != notification.object_type)
|
||||
|| object_type.is_some_and(|o| o != notification.object_account_id)
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
results.push(Id::from(change_id));
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
let mut response = QueryResponseBuilder::new(
|
||||
results.len(),
|
||||
self.core.jmap.query_max_results,
|
||||
State::Initial,
|
||||
&request,
|
||||
);
|
||||
|
||||
for id in results {
|
||||
if !response.add_id(id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
response.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::Server;
|
||||
use jmap_proto::{
|
||||
error::set::SetError,
|
||||
method::set::{SetRequest, SetResponse},
|
||||
object::share_notification::ShareNotification,
|
||||
request::IntoValid,
|
||||
};
|
||||
use store::write::{BatchBuilder, ValueClass};
|
||||
use trc::AddContext;
|
||||
|
||||
pub trait ShareNotificationSet: Sync + Send {
|
||||
fn share_notification_set(
|
||||
&self,
|
||||
request: SetRequest<'_, ShareNotification>,
|
||||
) -> impl Future<Output = trc::Result<SetResponse<ShareNotification>>> + Send;
|
||||
}
|
||||
|
||||
impl ShareNotificationSet for Server {
|
||||
async fn share_notification_set(
|
||||
&self,
|
||||
mut request: SetRequest<'_, ShareNotification>,
|
||||
) -> trc::Result<SetResponse<ShareNotification>> {
|
||||
let account_id = request.account_id.document_id();
|
||||
let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?;
|
||||
|
||||
for (id, _) in request.unwrap_create() {
|
||||
response.not_created.append(
|
||||
id,
|
||||
SetError::forbidden().with_description("Cannot create share notifications."),
|
||||
);
|
||||
}
|
||||
|
||||
// Process updates
|
||||
for (id, _) in request.unwrap_update().into_valid() {
|
||||
response.not_updated.append(
|
||||
id,
|
||||
SetError::forbidden().with_description("Cannot update share notifications."),
|
||||
);
|
||||
}
|
||||
|
||||
// Process deletions
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.with_account_id(account_id);
|
||||
for id in request.unwrap_destroy().into_valid() {
|
||||
batch.clear(ValueClass::ShareNotification {
|
||||
notification_id: id.id(),
|
||||
notify_account_id: account_id,
|
||||
});
|
||||
response.destroyed.push(id);
|
||||
}
|
||||
|
||||
// Write changes
|
||||
if !batch.is_empty() {
|
||||
self.commit_batch(batch).await.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user