mirror of
https://github.com/stalwartlabs/stalwart.git
synced 2026-10-29 08:12:30 +00:00
JMAP for Contacts implementation (part 1)
This commit is contained in:
+2
-2
@@ -12,12 +12,12 @@ If you are upgrading from v0.11.x or v0.12.x, this version includes **breaking c
|
||||
- JMAP: Protocol layer rewrite for zero-copy deserialization and architectural improvements.
|
||||
|
||||
## Fixed
|
||||
- IMAP: Unbounded memory allocation in request parser ([CVE-2025-61600 ](https://github.com/stalwartlabs/stalwart/security/advisories/GHSA-8jqj-qj5p-v5rr)).
|
||||
- IMAP: Wrong permission checked for GETACL.
|
||||
- JMAP: References to previous method fail when there are no results (#1507).
|
||||
- JMAP: Enforce quota checks on `Blob/copy`.
|
||||
- JMAP: `Mailbox/get` fails without `accountId` argument (#1936).
|
||||
- JMAP: Do not return `invalidProperties` when email update doesn't contain changes (#1139)
|
||||
- IMAP: Unbounded memory allocation in request parser.
|
||||
- IMAP: Wrong permission checked for GETACL.
|
||||
- iTIP: Include date properties in `REPLY` (#2102).
|
||||
- OIDC: Do not set `username` field if it is the same as the `email` field.
|
||||
- Telemetry: Fix `calculateMetrics` housekeeper task (#2155).
|
||||
|
||||
Generated
+2
-1
@@ -963,7 +963,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "calcard"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"chrono",
|
||||
@@ -3951,6 +3951,7 @@ name = "jmap_proto"
|
||||
version = "0.13.4"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"calcard",
|
||||
"compact_str",
|
||||
"hashify",
|
||||
"jmap-tools",
|
||||
|
||||
@@ -251,6 +251,18 @@ impl Permission {
|
||||
Permission::CalendarSchedulingReceive => {
|
||||
"Receive calendar scheduling requests via e-mail"
|
||||
}
|
||||
Permission::JmapAddressBookGet => "Retrieve address books via JMAP",
|
||||
Permission::JmapAddressBookSet => "Create or update address books via JMAP",
|
||||
Permission::JmapAddressBookChanges => "Track address book changes via JMAP",
|
||||
Permission::JmapContactCardGet => "Retrieve contact cards via JMAP",
|
||||
Permission::JmapContactCardChanges => "Track contact card changes via JMAP",
|
||||
Permission::JmapContactCardQuery => {
|
||||
"Search for contact cards matching criteria via JMAP"
|
||||
}
|
||||
Permission::JmapContactCardQueryChanges => "Track contact card query changes via JMAP",
|
||||
Permission::JmapContactCardSet => "Create or update contact cards via JMAP",
|
||||
Permission::JmapContactCardCopy => "Copy contact cards to new locations via JMAP",
|
||||
Permission::JmapContactCardParse => "Parse contact cards via JMAP",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1465,6 +1465,16 @@ impl Permission {
|
||||
| Permission::CalendarAlarms
|
||||
| Permission::CalendarSchedulingSend
|
||||
| Permission::CalendarSchedulingReceive
|
||||
| Permission::JmapAddressBookGet
|
||||
| Permission::JmapAddressBookSet
|
||||
| Permission::JmapAddressBookChanges
|
||||
| Permission::JmapContactCardGet
|
||||
| Permission::JmapContactCardChanges
|
||||
| Permission::JmapContactCardQuery
|
||||
| Permission::JmapContactCardQueryChanges
|
||||
| Permission::JmapContactCardSet
|
||||
| Permission::JmapContactCardCopy
|
||||
| Permission::JmapContactCardParse
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -379,6 +379,18 @@ pub enum Permission {
|
||||
CalendarAlarms,
|
||||
CalendarSchedulingSend,
|
||||
CalendarSchedulingReceive,
|
||||
|
||||
JmapAddressBookGet,
|
||||
JmapAddressBookSet,
|
||||
JmapAddressBookChanges,
|
||||
|
||||
JmapContactCardGet,
|
||||
JmapContactCardChanges,
|
||||
JmapContactCardQuery,
|
||||
JmapContactCardQueryChanges,
|
||||
JmapContactCardSet,
|
||||
JmapContactCardCopy,
|
||||
JmapContactCardParse,
|
||||
// WARNING: add new ids at the end (TODO: use static ids)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ utils = { path = "../utils" }
|
||||
types = { path = "../types" }
|
||||
trc = { path = "../trc" }
|
||||
jmap-tools = { version = "0.1" }
|
||||
calcard = { path = "/Users/me/code/calcard" }
|
||||
mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] }
|
||||
serde = { version = "1.0", features = ["derive"]}
|
||||
ahash = { version = "0.8.2", features = ["serde"] }
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::borrow::Cow;
|
||||
use types::id::Id;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(bound(serialize = "InvalidProperty<P>: serde::Serialize"))]
|
||||
pub struct SetError<P: Property> {
|
||||
#[serde(rename = "type")]
|
||||
pub type_: SetErrorType,
|
||||
@@ -82,6 +83,8 @@ pub enum SetErrorType {
|
||||
InvalidScript,
|
||||
#[serde(rename = "scriptIsActive")]
|
||||
ScriptIsActive,
|
||||
#[serde(rename = "addressBookHasContents")]
|
||||
AddressBookHasContents,
|
||||
}
|
||||
|
||||
impl SetErrorType {
|
||||
@@ -112,6 +115,7 @@ impl SetErrorType {
|
||||
SetErrorType::AlreadyExists => "alreadyExists",
|
||||
SetErrorType::InvalidScript => "invalidScript",
|
||||
SetErrorType::ScriptIsActive => "scriptIsActive",
|
||||
SetErrorType::AddressBookHasContents => "addressBookHasContents",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use ahash::AHashMap;
|
||||
use jmap_tools::Property;
|
||||
use serde::{
|
||||
Deserialize, Deserializer,
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
de::{self, MapAccess, Visitor},
|
||||
};
|
||||
use std::{borrow::Cow, fmt, str::FromStr};
|
||||
@@ -30,19 +30,25 @@ fn ahash_is_empty<K, V>(map: &AHashMap<K, V>) -> bool {
|
||||
map.is_empty()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
#[serde(transparent)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct PropertyWrapper<T>(pub T)
|
||||
where
|
||||
T: serde::Serialize + Property;
|
||||
pub struct PropertyWrapper<T: Property>(pub T);
|
||||
|
||||
impl<T: Property + serde::Serialize> From<T> for PropertyWrapper<T> {
|
||||
impl<T: Property> From<T> for PropertyWrapper<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Property> Serialize for PropertyWrapper<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.0.to_cow().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct JmapDict<T: FromStr>(pub Vec<T>);
|
||||
|
||||
struct JmapDictVisitor<'de, T: FromStr> {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::email::{EmailProperty, EmailValue},
|
||||
object::JmapObject,
|
||||
request::{
|
||||
MaybeInvalid,
|
||||
deserialize::{DeserializeArguments, deserialize_request},
|
||||
@@ -16,26 +16,22 @@ use serde::{Deserialize, Deserializer};
|
||||
use types::{blob::BlobId, id::Id};
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ParseEmailRequest {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseRequest<T: JmapObject> {
|
||||
pub account_id: Id,
|
||||
pub blob_ids: Vec<MaybeInvalid<BlobId>>,
|
||||
pub properties: Option<Vec<MaybeInvalid<EmailProperty>>>,
|
||||
pub body_properties: Option<Vec<MaybeInvalid<EmailProperty>>>,
|
||||
pub fetch_text_body_values: Option<bool>,
|
||||
pub fetch_html_body_values: Option<bool>,
|
||||
pub fetch_all_body_values: Option<bool>,
|
||||
pub max_body_value_bytes: Option<usize>,
|
||||
pub properties: Option<Vec<MaybeInvalid<T::Property>>>,
|
||||
pub arguments: T::ParseArguments,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct ParseEmailResponse {
|
||||
pub struct ParseResponse<T: JmapObject> {
|
||||
#[serde(rename = "accountId")]
|
||||
pub account_id: Id,
|
||||
|
||||
#[serde(rename = "parsed")]
|
||||
#[serde(skip_serializing_if = "VecMap::is_empty")]
|
||||
pub parsed: VecMap<BlobId, Value<'static, EmailProperty, EmailValue>>,
|
||||
pub parsed: VecMap<BlobId, Value<'static, T::Property, T::Element>>,
|
||||
|
||||
#[serde(rename = "notParsable")]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
@@ -46,7 +42,7 @@ pub struct ParseEmailResponse {
|
||||
pub not_found: Vec<BlobId>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ParseEmailRequest {
|
||||
impl<'de, T: JmapObject> DeserializeArguments<'de> for ParseRequest<T> {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
@@ -61,23 +57,8 @@ impl<'de> DeserializeArguments<'de> for ParseEmailRequest {
|
||||
b"properties" => {
|
||||
self.properties = map.next_value()?;
|
||||
},
|
||||
b"bodyProperties" => {
|
||||
self.body_properties = map.next_value()?;
|
||||
},
|
||||
b"fetchTextBodyValues" => {
|
||||
self.fetch_text_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchHTMLBodyValues" => {
|
||||
self.fetch_html_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchAllBodyValues" => {
|
||||
self.fetch_all_body_values = map.next_value()?;
|
||||
},
|
||||
b"maxBodyValueBytes" => {
|
||||
self.max_body_value_bytes = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
self.arguments.deserialize_argument(key, map)?;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -85,7 +66,7 @@ impl<'de> DeserializeArguments<'de> for ParseEmailRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ParseEmailRequest {
|
||||
impl<'de, T: JmapObject> Deserialize<'de> for ParseRequest<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
@@ -93,3 +74,14 @@ impl<'de> Deserialize<'de> for ParseEmailRequest {
|
||||
deserialize_request(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JmapObject> Default for ParseRequest<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
account_id: Id::default(),
|
||||
blob_ids: Vec::default(),
|
||||
properties: None,
|
||||
arguments: T::ParseArguments::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
|
||||
},
|
||||
request::deserialize::DeserializeArguments,
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{acl::Acl, id::Id, special_use::SpecialUse};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AddressBook;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum AddressBookProperty {
|
||||
Id,
|
||||
Name,
|
||||
Description,
|
||||
SortOrder,
|
||||
IsDefault,
|
||||
IsSubscribed,
|
||||
ShareWith,
|
||||
MyRights,
|
||||
|
||||
// Other
|
||||
IdValue(Id),
|
||||
Rights(AddressBookRight),
|
||||
Pointer(JsonPointer<AddressBookProperty>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum AddressBookRight {
|
||||
MayRead,
|
||||
MayWrite,
|
||||
MayShare,
|
||||
MayDelete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum AddressBookValue {
|
||||
Id(Id),
|
||||
IdReference(String),
|
||||
Role(SpecialUse),
|
||||
}
|
||||
|
||||
impl Property for AddressBookProperty {
|
||||
fn try_parse(key: Option<&Key<'_, Self>>, value: &str) -> Option<Self> {
|
||||
let allow_patch = key.is_none();
|
||||
if let Some(Key::Property(key)) = key {
|
||||
match key.patch_or_prop() {
|
||||
AddressBookProperty::ShareWith => {
|
||||
Id::from_str(value).ok().map(AddressBookProperty::IdValue)
|
||||
}
|
||||
_ => AddressBookProperty::parse(value, allow_patch),
|
||||
}
|
||||
} else {
|
||||
AddressBookProperty::parse(value, allow_patch)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
AddressBookProperty::Id => "id",
|
||||
AddressBookProperty::Name => "name",
|
||||
AddressBookProperty::Description => "description",
|
||||
AddressBookProperty::SortOrder => "sortOrder",
|
||||
AddressBookProperty::IsDefault => "isDefault",
|
||||
AddressBookProperty::IsSubscribed => "isSubscribed",
|
||||
AddressBookProperty::ShareWith => "shareWith",
|
||||
AddressBookProperty::MyRights => "myRights",
|
||||
AddressBookProperty::Rights(addressbook_right) => addressbook_right.as_str(),
|
||||
AddressBookProperty::Pointer(json_pointer) => return json_pointer.to_string().into(),
|
||||
AddressBookProperty::IdValue(id) => return id.to_string().into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressBookRight {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
AddressBookRight::MayRead => "mayRead",
|
||||
AddressBookRight::MayWrite => "mayWrite",
|
||||
AddressBookRight::MayShare => "mayShare",
|
||||
AddressBookRight::MayDelete => "mayDelete",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for AddressBookValue {
|
||||
type Property = AddressBookProperty;
|
||||
|
||||
fn try_parse<P>(key: &Key<'_, Self::Property>, value: &str) -> Option<Self> {
|
||||
if let Key::Property(prop) = key {
|
||||
match prop.patch_or_prop() {
|
||||
AddressBookProperty::Id => match parse_ref(value) {
|
||||
MaybeReference::Value(v) => Some(AddressBookValue::Id(v)),
|
||||
MaybeReference::Reference(v) => Some(AddressBookValue::IdReference(v)),
|
||||
MaybeReference::ParseError => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_cow(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
AddressBookValue::Id(id) => id.to_string().into(),
|
||||
AddressBookValue::IdReference(r) => format!("#{r}").into(),
|
||||
AddressBookValue::Role(special_use) => special_use.as_str().unwrap_or_default().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressBookProperty {
|
||||
fn parse(value: &str, allow_patch: bool) -> Option<Self> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
b"id" => AddressBookProperty::Id,
|
||||
b"name" => AddressBookProperty::Name,
|
||||
b"description" => AddressBookProperty::Description,
|
||||
b"sortOrder" => AddressBookProperty::SortOrder,
|
||||
b"isDefault" => AddressBookProperty::IsDefault,
|
||||
b"isSubscribed" => AddressBookProperty::IsSubscribed,
|
||||
b"shareWith" => AddressBookProperty::ShareWith,
|
||||
b"myRights" => AddressBookProperty::MyRights,
|
||||
b"mayRead" => AddressBookProperty::Rights(AddressBookRight::MayRead),
|
||||
b"mayWrite" => AddressBookProperty::Rights(AddressBookRight::MayWrite),
|
||||
b"mayShare" => AddressBookProperty::Rights(AddressBookRight::MayShare),
|
||||
b"mayDelete" => AddressBookProperty::Rights(AddressBookRight::MayDelete)
|
||||
)
|
||||
.or_else(|| {
|
||||
if allow_patch && value.contains('/') {
|
||||
AddressBookProperty::Pointer(JsonPointer::parse(value)).into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn patch_or_prop(&self) -> &AddressBookProperty {
|
||||
if let AddressBookProperty::Pointer(ptr) = self
|
||||
&& let Some(JsonPointerItem::Key(Key::Property(prop))) = ptr.last()
|
||||
{
|
||||
prop
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AddressBookSetArguments {
|
||||
pub on_destroy_remove_contents: Option<bool>,
|
||||
pub on_success_set_is_default: Option<Id>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for AddressBookSetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"onDestroyRemoveContents" => {
|
||||
self.on_destroy_remove_contents = map.next_value()?;
|
||||
},
|
||||
b"onSuccessSetIsDefault" => {
|
||||
self.on_success_set_is_default = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AddressBookProperty {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
AddressBookProperty::parse(s, false).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObject for AddressBook {
|
||||
type Property = AddressBookProperty;
|
||||
|
||||
type Element = AddressBookValue;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
type Comparator = ();
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = AddressBookSetArguments;
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = AddressBookProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapSharedObject for AddressBook {
|
||||
type Right = AddressBookRight;
|
||||
|
||||
const SHARE_WITH_PROPERTY: Self::Property = AddressBookProperty::ShareWith;
|
||||
}
|
||||
|
||||
impl From<Id> for AddressBookProperty {
|
||||
fn from(id: Id) -> Self {
|
||||
AddressBookProperty::IdValue(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AddressBookProperty> for Id {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AddressBookProperty) -> Result<Self, Self::Error> {
|
||||
if let AddressBookProperty::IdValue(id) = value {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AddressBookProperty> for AddressBookRight {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AddressBookProperty) -> Result<Self, Self::Error> {
|
||||
if let AddressBookProperty::Rights(right) = value {
|
||||
Ok(right)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for AddressBookValue {
|
||||
fn from(id: Id) -> Self {
|
||||
AddressBookValue::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapObjectId for AddressBookValue {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let AddressBookValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
if let AddressBookValue::Id(id) = self {
|
||||
Some(AnyId::Id(*id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
if let AddressBookValue::IdReference(r) = self {
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyId> for AddressBookValue {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AnyId) -> Result<Self, Self::Error> {
|
||||
if let AnyId::Id(id) = value {
|
||||
Ok(AddressBookValue::Id(id))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JmapRight for AddressBookRight {
|
||||
fn from_acl(acl: Acl) -> &'static [Self] {
|
||||
match acl {
|
||||
Acl::ReadItems => &[AddressBookRight::MayRead],
|
||||
Acl::RemoveItems => &[AddressBookRight::MayDelete],
|
||||
Acl::ModifyItems => &[AddressBookRight::MayWrite],
|
||||
Acl::Delete => &[AddressBookRight::MayDelete],
|
||||
Acl::Administer => &[AddressBookRight::MayShare],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
fn to_acl(&self) -> &'static [Acl] {
|
||||
match self {
|
||||
AddressBookRight::MayDelete => &[Acl::Delete, Acl::RemoveItems],
|
||||
AddressBookRight::MayShare => &[Acl::Administer],
|
||||
AddressBookRight::MayRead => &[Acl::Read, Acl::ReadItems],
|
||||
AddressBookRight::MayWrite => &[Acl::Modify, Acl::AddItems, Acl::ModifyItems],
|
||||
}
|
||||
}
|
||||
|
||||
fn all_rights() -> &'static [Self] {
|
||||
&[
|
||||
AddressBookRight::MayRead,
|
||||
AddressBookRight::MayWrite,
|
||||
AddressBookRight::MayDelete,
|
||||
AddressBookRight::MayShare,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddressBookRight> for AddressBookProperty {
|
||||
fn from(right: AddressBookRight) -> Self {
|
||||
AddressBookProperty::Rights(right)
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,8 @@ impl JmapObject for Blob {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = BlobProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
object::{AnyId, JmapObject, JmapObjectId},
|
||||
request::deserialize::DeserializeArguments,
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use calcard::jscontact::{JSContactProperty, JSContactValue};
|
||||
use std::borrow::Cow;
|
||||
use types::{blob::BlobId, id::Id};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ContactCard;
|
||||
|
||||
impl JmapObject for ContactCard {
|
||||
type Property = JSContactProperty<Id>;
|
||||
|
||||
type Element = JSContactValue<Id, BlobId>;
|
||||
|
||||
type Id = Id;
|
||||
|
||||
type Filter = ContactFilter;
|
||||
|
||||
type Comparator = ContactComparator;
|
||||
|
||||
type GetArguments = ();
|
||||
|
||||
type SetArguments<'de> = ();
|
||||
|
||||
type QueryArguments = ();
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = JSContactProperty::Id;
|
||||
}
|
||||
|
||||
impl JmapObjectId for JSContactValue<Id, BlobId> {
|
||||
fn as_id(&self) -> Option<Id> {
|
||||
if let JSContactValue::Id(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_id(&self) -> Option<AnyId> {
|
||||
match self {
|
||||
JSContactValue::Id(id) => Some(AnyId::Id(*id)),
|
||||
JSContactValue::BlobId(id) => Some(AnyId::BlobId(id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_id_ref(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyId> for JSContactValue<Id, BlobId> {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: AnyId) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
AnyId::Id(id) => Ok(JSContactValue::Id(id)),
|
||||
AnyId::BlobId(id) => Ok(JSContactValue::BlobId(id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ContactFilter {
|
||||
InAddressBook(Id),
|
||||
Uid(String),
|
||||
HasMember(String),
|
||||
Kind(String),
|
||||
CreatedBefore(UTCDate),
|
||||
CreatedAfter(UTCDate),
|
||||
UpdatedBefore(UTCDate),
|
||||
UpdatedAfter(UTCDate),
|
||||
Text(String),
|
||||
Name(String),
|
||||
NameGiven(String),
|
||||
NameSurname(String),
|
||||
NameSurname2(String),
|
||||
Nickname(String),
|
||||
Organization(String),
|
||||
Email(String),
|
||||
Phone(String),
|
||||
OnlineService(String),
|
||||
Address(String),
|
||||
Note(String),
|
||||
_T(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ContactComparator {
|
||||
Created,
|
||||
Updated,
|
||||
NameGiven,
|
||||
NameSurname,
|
||||
NameSurname2,
|
||||
_T(String),
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ContactFilter {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"inAddressBook" => {
|
||||
*self = ContactFilter::InAddressBook(map.next_value()?);
|
||||
},
|
||||
b"uid" => {
|
||||
*self = ContactFilter::Uid(map.next_value()?);
|
||||
},
|
||||
b"hasMember" => {
|
||||
*self = ContactFilter::HasMember(map.next_value()?);
|
||||
},
|
||||
b"kind" => {
|
||||
*self = ContactFilter::Kind(map.next_value()?);
|
||||
},
|
||||
b"createdBefore" => {
|
||||
*self = ContactFilter::CreatedBefore(map.next_value()?);
|
||||
},
|
||||
b"createdAfter" => {
|
||||
*self = ContactFilter::CreatedAfter(map.next_value()?);
|
||||
},
|
||||
b"updatedBefore" => {
|
||||
*self = ContactFilter::UpdatedBefore(map.next_value()?);
|
||||
},
|
||||
b"updatedAfter" => {
|
||||
*self = ContactFilter::UpdatedAfter(map.next_value()?);
|
||||
},
|
||||
b"text" => {
|
||||
*self = ContactFilter::Text(map.next_value()?);
|
||||
},
|
||||
b"name" => {
|
||||
*self = ContactFilter::Name(map.next_value()?);
|
||||
},
|
||||
b"name/given" => {
|
||||
*self = ContactFilter::NameGiven(map.next_value()?);
|
||||
},
|
||||
b"name/surname" => {
|
||||
*self = ContactFilter::NameSurname(map.next_value()?);
|
||||
},
|
||||
b"name/surname2" => {
|
||||
*self = ContactFilter::NameSurname2(map.next_value()?);
|
||||
},
|
||||
b"nickname" => {
|
||||
*self = ContactFilter::Nickname(map.next_value()?);
|
||||
},
|
||||
b"organization" => {
|
||||
*self = ContactFilter::Organization(map.next_value()?);
|
||||
},
|
||||
b"email" => {
|
||||
*self = ContactFilter::Email(map.next_value()?);
|
||||
},
|
||||
b"phone" => {
|
||||
*self = ContactFilter::Phone(map.next_value()?);
|
||||
},
|
||||
b"onlineService" => {
|
||||
*self = ContactFilter::OnlineService(map.next_value()?);
|
||||
},
|
||||
b"address" => {
|
||||
*self = ContactFilter::Address(map.next_value()?);
|
||||
},
|
||||
b"note" => {
|
||||
*self = ContactFilter::Note(map.next_value()?);
|
||||
},
|
||||
_ => {
|
||||
*self = ContactFilter::_T(key.to_string());
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for ContactComparator {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
if key == "property" {
|
||||
let value = map.next_value::<Cow<str>>()?;
|
||||
hashify::fnc_map!(value.as_bytes(),
|
||||
b"created" => {
|
||||
*self = ContactComparator::Created;
|
||||
},
|
||||
b"updated" => {
|
||||
*self = ContactComparator::Updated;
|
||||
},
|
||||
b"name/given" => {
|
||||
*self = ContactComparator::NameGiven;
|
||||
},
|
||||
b"name/surname" => {
|
||||
*self = ContactComparator::NameSurname;
|
||||
},
|
||||
b"name/surname2" => {
|
||||
*self = ContactComparator::NameSurname2;
|
||||
},
|
||||
_ => {
|
||||
*self = ContactComparator::_T(value.to_string());
|
||||
}
|
||||
);
|
||||
} else {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContactFilter {
|
||||
fn default() -> Self {
|
||||
ContactFilter::_T(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ContactComparator {
|
||||
fn default() -> Self {
|
||||
ContactComparator::_T(String::new())
|
||||
}
|
||||
}
|
||||
@@ -396,6 +396,15 @@ pub struct EmailQueryArguments {
|
||||
pub collapse_threads: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EmailParseArguments {
|
||||
pub body_properties: Option<Vec<MaybeInvalid<EmailProperty>>>,
|
||||
pub fetch_text_body_values: Option<bool>,
|
||||
pub fetch_html_body_values: Option<bool>,
|
||||
pub fetch_all_body_values: Option<bool>,
|
||||
pub max_body_value_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailGetArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
@@ -441,6 +450,36 @@ impl<'de> DeserializeArguments<'de> for EmailQueryArguments {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeArguments<'de> for EmailParseArguments {
|
||||
fn deserialize_argument<A>(&mut self, key: &str, map: &mut A) -> Result<(), A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
hashify::fnc_map!(key.as_bytes(),
|
||||
b"bodyProperties" => {
|
||||
self.body_properties = map.next_value()?;
|
||||
},
|
||||
b"fetchTextBodyValues" => {
|
||||
self.fetch_text_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchHTMLBodyValues" => {
|
||||
self.fetch_html_body_values = map.next_value()?;
|
||||
},
|
||||
b"fetchAllBodyValues" => {
|
||||
self.fetch_all_body_values = map.next_value()?;
|
||||
},
|
||||
b"maxBodyValueBytes" => {
|
||||
self.max_body_value_bytes = map.next_value()?;
|
||||
},
|
||||
_ => {
|
||||
let _ = map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for EmailProperty {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
@@ -469,6 +508,8 @@ impl JmapObject for Email {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = EmailParseArguments;
|
||||
|
||||
const ID_PROPERTY: Self::Property = EmailProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -330,6 +330,8 @@ impl JmapObject for EmailSubmission {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = EmailSubmissionProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::object::{AnyId, JmapObject, JmapObjectId};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::id::Id;
|
||||
|
||||
use crate::object::{AnyId, JmapObject, JmapObjectId};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Identity;
|
||||
|
||||
@@ -143,6 +142,8 @@ impl JmapObject for Identity {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = IdentityProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,15 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use types::{acl::Acl, id::Id, special_use::SpecialUse};
|
||||
|
||||
use crate::{
|
||||
object::{
|
||||
AnyId, JmapObject, JmapObjectId, JmapRight, JmapSharedObject, MaybeReference, parse_ref,
|
||||
},
|
||||
request::deserialize::DeserializeArguments,
|
||||
};
|
||||
use jmap_tools::{Element, JsonPointer, JsonPointerItem, Key, Property};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
use types::{acl::Acl, id::Id, special_use::SpecialUse};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Mailbox;
|
||||
@@ -272,6 +270,8 @@ impl JmapObject for Mailbox {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = MailboxProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ use serde::Serialize;
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
use types::{acl::Acl, blob::BlobId, id::Id};
|
||||
|
||||
pub mod addressbook;
|
||||
pub mod blob;
|
||||
pub mod contact;
|
||||
pub mod email;
|
||||
pub mod email_submission;
|
||||
pub mod identity;
|
||||
@@ -24,7 +26,7 @@ pub mod thread;
|
||||
pub mod vacation_response;
|
||||
|
||||
pub trait JmapObject: std::fmt::Debug {
|
||||
type Property: Property + FromStr + Serialize + Debug + Sync + Send;
|
||||
type Property: Property + FromStr + Debug + Sync + Send;
|
||||
type Element: Element<Property = Self::Property>
|
||||
+ From<Self::Id>
|
||||
+ JmapObjectId
|
||||
@@ -40,6 +42,7 @@ pub trait JmapObject: std::fmt::Debug {
|
||||
type SetArguments<'de>: Default + DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type QueryArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type CopyArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
type ParseArguments: Default + for<'de> DeserializeArguments<'de> + Debug + Sync + Send;
|
||||
|
||||
const ID_PROPERTY: Self::Property;
|
||||
}
|
||||
@@ -156,6 +159,7 @@ impl JmapObject for NullObject {
|
||||
type SetArguments<'de> = ();
|
||||
type QueryArguments = ();
|
||||
type CopyArguments = ();
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = Null;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,8 @@ impl JmapObject for Principal {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = PrincipalProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,8 @@ impl JmapObject for PushSubscription {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = PushSubscriptionProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,8 @@ impl JmapObject for Quota {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = QuotaProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,8 @@ impl JmapObject for Sieve {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = SieveProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,8 @@ impl JmapObject for Thread {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = ThreadProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +130,8 @@ impl JmapObject for VacationResponse {
|
||||
|
||||
type CopyArguments = ();
|
||||
|
||||
type ParseArguments = ();
|
||||
|
||||
const ID_PROPERTY: Self::Property = VacationResponseProperty::Id;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,12 @@ impl Response<'_> {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::Blob(response) => response.eval_jptr(path, &mut results),
|
||||
GetResponseMethod::AddressBook(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
GetResponseMethod::ContactCard(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
},
|
||||
ResponseMethod::Changes(response) => match response {
|
||||
ChangesResponseMethod::Email(response) => {
|
||||
@@ -77,6 +83,12 @@ impl Response<'_> {
|
||||
ChangesResponseMethod::Quota(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::AddressBook(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
ChangesResponseMethod::ContactCard(response) => {
|
||||
response.eval_jptr(path, &mut results)
|
||||
}
|
||||
},
|
||||
ResponseMethod::Query(response) => response.eval_jptr(path, &mut results),
|
||||
ResponseMethod::QueryChanges(response) => {
|
||||
|
||||
@@ -136,7 +136,7 @@ impl ResponsePtr for BlobId {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Property + serde::Serialize> ResponsePtr for PropertyWrapper<T> {
|
||||
impl<T: Property> ResponsePtr for PropertyWrapper<T> {
|
||||
fn eval_jptr(&self, _: JsonPointerIter<'_, Null>, results: &mut EvalResults) -> bool {
|
||||
results.0.push(EvalResult::Property(self.0.to_cow()));
|
||||
true
|
||||
|
||||
@@ -42,6 +42,8 @@ impl Response<'_> {
|
||||
GetRequestMethod::Principal(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Quota(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::Blob(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::AddressBook(request) => request.resolve_references(self)?,
|
||||
GetRequestMethod::ContactCard(request) => request.resolve_references(self)?,
|
||||
},
|
||||
RequestMethod::Set(request) => match request {
|
||||
SetRequestMethod::Email(request) => request.resolve_references(self)?,
|
||||
@@ -51,9 +53,12 @@ impl Response<'_> {
|
||||
SetRequestMethod::PushSubscription(request) => request.resolve_references(self)?,
|
||||
SetRequestMethod::Sieve(request) => request.resolve_references(self)?,
|
||||
SetRequestMethod::VacationResponse(request) => request.resolve_references(self)?,
|
||||
SetRequestMethod::AddressBook(request) => request.resolve_references(self)?,
|
||||
SetRequestMethod::ContactCard(request) => request.resolve_references(self)?,
|
||||
},
|
||||
RequestMethod::Copy(request) => match request {
|
||||
CopyRequestMethod::Email(request) => request.resolve_references(self)?,
|
||||
CopyRequestMethod::ContactCard(request) => request.resolve_references(self)?,
|
||||
CopyRequestMethod::Blob(_) => (),
|
||||
},
|
||||
RequestMethod::ImportEmail(request) => request.resolve_references(self)?,
|
||||
|
||||
@@ -70,6 +70,10 @@ pub enum Capability {
|
||||
Blob = 1 << 8,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:quota"))]
|
||||
Quota = 1 << 9,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:principals"))]
|
||||
Principals = 1 << 10,
|
||||
#[serde(rename(serialize = "urn:ietf:params:jmap:principals:owner"))]
|
||||
PrincipalsOwner = 1 << 11,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
@@ -87,6 +91,9 @@ pub enum Capabilities {
|
||||
SieveAccount(SieveAccountCapabilities),
|
||||
SieveSession(SieveSessionCapabilities),
|
||||
Blob(BlobCapabilities),
|
||||
Contacts(ContactsCapabilities),
|
||||
Principals(PrincipalsCapabilities),
|
||||
PrincipalsOwner(PrincipalsOwnerCapabilities),
|
||||
Empty(EmptyCapabilities),
|
||||
}
|
||||
|
||||
@@ -178,6 +185,29 @@ pub struct BlobCapabilities {
|
||||
pub supported_digest_algorithms: Vec<&'static str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct ContactsCapabilities {
|
||||
#[serde(rename(serialize = "maxAddressBooksPerCard"))]
|
||||
pub max_address_books_per_card: Option<usize>,
|
||||
#[serde(rename(serialize = "mayCreateAddressBook"))]
|
||||
pub may_create_address_book: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct PrincipalsCapabilities {
|
||||
#[serde(rename(serialize = "currentUserPrincipalId"))]
|
||||
pub current_user_principal_id: Option<Id>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct PrincipalsOwnerCapabilities {
|
||||
#[serde(rename(serialize = "accountIdForPrincipal"))]
|
||||
pub account_id_for_principal: Id,
|
||||
|
||||
#[serde(rename(serialize = "principalId"))]
|
||||
pub principal_id: Id,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize)]
|
||||
pub struct EmptyCapabilities {}
|
||||
|
||||
@@ -332,6 +362,8 @@ impl Capability {
|
||||
"urn:ietf:params:jmap:sieve" => Capability::Sieve,
|
||||
"urn:ietf:params:jmap:blob" => Capability::Blob,
|
||||
"urn:ietf:params:jmap:quota" => Capability::Quota,
|
||||
"urn:ietf:params:jmap:principals" => Capability::Principals,
|
||||
"urn:ietf:params:jmap:principals:owner" => Capability::PrincipalsOwner
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ pub enum MethodObject {
|
||||
SieveScript,
|
||||
Principal,
|
||||
Quota,
|
||||
AddressBook,
|
||||
ContactCard,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -122,6 +124,18 @@ impl MethodName {
|
||||
(MethodFunction::Lookup, MethodObject::Blob) => "Blob/lookup",
|
||||
(MethodFunction::Upload, MethodObject::Blob) => "Blob/upload",
|
||||
|
||||
(MethodFunction::Get, MethodObject::AddressBook) => "AddressBook/get",
|
||||
(MethodFunction::Changes, MethodObject::AddressBook) => "AddressBook/changes",
|
||||
(MethodFunction::Set, MethodObject::AddressBook) => "AddressBook/set",
|
||||
|
||||
(MethodFunction::Get, MethodObject::ContactCard) => "ContactCard/get",
|
||||
(MethodFunction::Changes, MethodObject::ContactCard) => "ContactCard/changes",
|
||||
(MethodFunction::Query, MethodObject::ContactCard) => "ContactCard/query",
|
||||
(MethodFunction::QueryChanges, MethodObject::ContactCard) => "ContactCard/queryChanges",
|
||||
(MethodFunction::Set, MethodObject::ContactCard) => "ContactCard/set",
|
||||
(MethodFunction::Copy, MethodObject::ContactCard) => "ContactCard/copy",
|
||||
(MethodFunction::Parse, MethodObject::ContactCard) => "ContactCard/parse",
|
||||
|
||||
(MethodFunction::Echo, MethodObject::Core) => "Core/echo",
|
||||
_ => "error",
|
||||
}
|
||||
@@ -184,6 +198,18 @@ impl MethodName {
|
||||
"Blob/lookup" => (MethodObject::Blob, MethodFunction::Lookup),
|
||||
"Blob/upload" => (MethodObject::Blob, MethodFunction::Upload),
|
||||
|
||||
"AddressBook/get" => (MethodObject::AddressBook, MethodFunction::Get),
|
||||
"AddressBook/changes" => (MethodObject::AddressBook, MethodFunction::Changes),
|
||||
"AddressBook/set" => (MethodObject::AddressBook, MethodFunction::Set),
|
||||
|
||||
"ContactCard/get" => (MethodObject::ContactCard, MethodFunction::Get),
|
||||
"ContactCard/changes" => (MethodObject::ContactCard, MethodFunction::Changes),
|
||||
"ContactCard/query" => (MethodObject::ContactCard, MethodFunction::Query),
|
||||
"ContactCard/queryChanges" => (MethodObject::ContactCard, MethodFunction::QueryChanges),
|
||||
"ContactCard/set" => (MethodObject::ContactCard, MethodFunction::Set),
|
||||
"ContactCard/copy" => (MethodObject::ContactCard, MethodFunction::Copy),
|
||||
"ContactCard/parse" => (MethodObject::ContactCard, MethodFunction::Parse),
|
||||
|
||||
"Core/echo" => (MethodObject::Core, MethodFunction::Echo),
|
||||
|
||||
).map(|(obj, fnc)| MethodName { obj, fnc })
|
||||
@@ -207,6 +233,8 @@ impl Display for MethodObject {
|
||||
MethodObject::Thread => "Thread",
|
||||
MethodObject::Email => "Email",
|
||||
MethodObject::Quota => "Quota",
|
||||
MethodObject::AddressBook => "AddressBook",
|
||||
MethodObject::ContactCard => "ContactCard",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::{
|
||||
get::GetRequest,
|
||||
import::ImportEmailRequest,
|
||||
lookup::BlobLookupRequest,
|
||||
parse::ParseEmailRequest,
|
||||
parse::ParseRequest,
|
||||
query::QueryRequest,
|
||||
query_changes::QueryChangesRequest,
|
||||
search_snippet::GetSearchSnippetRequest,
|
||||
@@ -28,9 +28,10 @@ use crate::{
|
||||
validate::ValidateSieveScriptRequest,
|
||||
},
|
||||
object::{
|
||||
AnyId, blob::Blob, email::Email, email_submission::EmailSubmission, identity::Identity,
|
||||
mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, quota::Quota,
|
||||
sieve::Sieve, thread::Thread, vacation_response::VacationResponse,
|
||||
AnyId, addressbook::AddressBook, blob::Blob, contact::ContactCard, email::Email,
|
||||
email_submission::EmailSubmission, identity::Identity, mailbox::Mailbox,
|
||||
principal::Principal, push_subscription::PushSubscription, quota::Quota, sieve::Sieve,
|
||||
thread::Thread, vacation_response::VacationResponse,
|
||||
},
|
||||
request::{capability::CapabilityIds, reference::MaybeIdReference},
|
||||
};
|
||||
@@ -59,7 +60,7 @@ pub enum RequestMethod<'x> {
|
||||
Changes(ChangesRequest),
|
||||
Copy(CopyRequestMethod<'x>),
|
||||
ImportEmail(ImportEmailRequest),
|
||||
ParseEmail(ParseEmailRequest),
|
||||
Parse(ParseRequestMethod),
|
||||
Query(QueryRequestMethod),
|
||||
QueryChanges(QueryChangesRequestMethod),
|
||||
SearchSnippet(GetSearchSnippetRequest),
|
||||
@@ -83,6 +84,8 @@ pub enum GetRequestMethod {
|
||||
Principal(GetRequest<Principal>),
|
||||
Quota(GetRequest<Quota>),
|
||||
Blob(GetRequest<Blob>),
|
||||
AddressBook(GetRequest<AddressBook>),
|
||||
ContactCard(GetRequest<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -94,11 +97,14 @@ pub enum SetRequestMethod<'x> {
|
||||
PushSubscription(SetRequest<'x, PushSubscription>),
|
||||
Sieve(SetRequest<'x, Sieve>),
|
||||
VacationResponse(SetRequest<'x, VacationResponse>),
|
||||
AddressBook(SetRequest<'x, AddressBook>),
|
||||
ContactCard(SetRequest<'x, ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CopyRequestMethod<'x> {
|
||||
Email(CopyRequest<'x, Email>),
|
||||
ContactCard(CopyRequest<'x, ContactCard>),
|
||||
Blob(CopyBlobRequest),
|
||||
}
|
||||
|
||||
@@ -110,6 +116,7 @@ pub enum QueryRequestMethod {
|
||||
Sieve(QueryRequest<Sieve>),
|
||||
Principal(QueryRequest<Principal>),
|
||||
Quota(QueryRequest<Quota>),
|
||||
ContactCard(QueryRequest<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -120,6 +127,13 @@ pub enum QueryChangesRequestMethod {
|
||||
Sieve(QueryChangesRequest<Sieve>),
|
||||
Principal(QueryChangesRequest<Principal>),
|
||||
Quota(QueryChangesRequest<Quota>),
|
||||
ContactCard(QueryChangesRequest<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseRequestMethod {
|
||||
Email(ParseRequest<Email>),
|
||||
ContactCard(ParseRequest<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -9,8 +9,8 @@ use super::{
|
||||
method::{MethodFunction, MethodName, MethodObject},
|
||||
};
|
||||
use crate::request::{
|
||||
CopyRequestMethod, GetRequestMethod, QueryChangesRequestMethod, QueryRequestMethod,
|
||||
SetRequestMethod,
|
||||
CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryChangesRequestMethod,
|
||||
QueryRequestMethod, SetRequestMethod,
|
||||
deserialize::{DeserializeArguments, deserialize_request},
|
||||
};
|
||||
use serde::{
|
||||
@@ -181,6 +181,20 @@ impl<'de> Visitor<'de> for CallVisitor {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::AddressBook) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::AddressBook(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Get(GetRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Get, MethodObject::SearchSnippet) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::SearchSnippet(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
@@ -237,6 +251,20 @@ impl<'de> Visitor<'de> for CallVisitor {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::AddressBook) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::AddressBook(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Set, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Set(SetRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::Email(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
@@ -279,6 +307,13 @@ impl<'de> Visitor<'de> for CallVisitor {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Query, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Query(QueryRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::Email(value))
|
||||
@@ -335,6 +370,15 @@ impl<'de> Visitor<'de> for CallVisitor {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::QueryChanges, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => {
|
||||
RequestMethod::QueryChanges(QueryChangesRequestMethod::ContactCard(value))
|
||||
}
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Changes, _) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Changes(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
@@ -356,6 +400,13 @@ impl<'de> Visitor<'de> for CallVisitor {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Copy, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Copy(CopyRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Lookup, MethodObject::Blob) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::LookupBlob(value),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
@@ -378,7 +429,14 @@ impl<'de> Visitor<'de> for CallVisitor {
|
||||
}
|
||||
},
|
||||
(MethodFunction::Parse, MethodObject::Email) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::ParseEmail(value),
|
||||
Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::Email(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
}
|
||||
},
|
||||
(MethodFunction::Parse, MethodObject::ContactCard) => match seq.next_element() {
|
||||
Ok(Some(value)) => RequestMethod::Parse(ParseRequestMethod::ContactCard(value)),
|
||||
Err(err) => RequestMethod::invalid(err),
|
||||
Ok(None) => {
|
||||
return Err(de::Error::invalid_length(1, &self));
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
get::GetResponse,
|
||||
import::ImportEmailResponse,
|
||||
lookup::BlobLookupResponse,
|
||||
parse::ParseEmailResponse,
|
||||
parse::ParseResponse,
|
||||
query::QueryResponse,
|
||||
query_changes::QueryChangesResponse,
|
||||
search_snippet::GetSearchSnippetResponse,
|
||||
@@ -25,9 +25,10 @@ use crate::{
|
||||
validate::ValidateSieveScriptResponse,
|
||||
},
|
||||
object::{
|
||||
AnyId, blob::Blob, email::Email, email_submission::EmailSubmission, identity::Identity,
|
||||
mailbox::Mailbox, principal::Principal, push_subscription::PushSubscription, quota::Quota,
|
||||
sieve::Sieve, thread::Thread, vacation_response::VacationResponse,
|
||||
AnyId, addressbook::AddressBook, blob::Blob, contact::ContactCard, email::Email,
|
||||
email_submission::EmailSubmission, identity::Identity, mailbox::Mailbox,
|
||||
principal::Principal, push_subscription::PushSubscription, quota::Quota, sieve::Sieve,
|
||||
thread::Thread, vacation_response::VacationResponse,
|
||||
},
|
||||
request::{Call, method::MethodName},
|
||||
};
|
||||
@@ -42,7 +43,7 @@ pub enum ResponseMethod<'x> {
|
||||
Changes(ChangesResponseMethod),
|
||||
Copy(CopyResponseMethod),
|
||||
ImportEmail(ImportEmailResponse),
|
||||
ParseEmail(ParseEmailResponse),
|
||||
Parse(ParseResponseMethod),
|
||||
QueryChanges(QueryChangesResponse),
|
||||
Query(QueryResponse),
|
||||
SearchSnippet(GetSearchSnippetResponse),
|
||||
@@ -67,6 +68,8 @@ pub enum GetResponseMethod {
|
||||
Principal(GetResponse<Principal>),
|
||||
Quota(GetResponse<Quota>),
|
||||
Blob(GetResponse<Blob>),
|
||||
AddressBook(GetResponse<AddressBook>),
|
||||
ContactCard(GetResponse<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
@@ -79,6 +82,8 @@ pub enum SetResponseMethod {
|
||||
PushSubscription(SetResponse<PushSubscription>),
|
||||
Sieve(SetResponse<Sieve>),
|
||||
VacationResponse(SetResponse<VacationResponse>),
|
||||
AddressBook(SetResponse<AddressBook>),
|
||||
ContactCard(SetResponse<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
@@ -90,15 +95,25 @@ pub enum ChangesResponseMethod {
|
||||
Identity(ChangesResponse<Identity>),
|
||||
EmailSubmission(ChangesResponse<EmailSubmission>),
|
||||
Quota(ChangesResponse<Quota>),
|
||||
AddressBook(ChangesResponse<AddressBook>),
|
||||
ContactCard(ChangesResponse<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CopyResponseMethod {
|
||||
Email(CopyResponse<Email>),
|
||||
ContactCard(CopyResponse<ContactCard>),
|
||||
Blob(CopyBlobResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ParseResponseMethod {
|
||||
Email(ParseResponse<Email>),
|
||||
ContactCard(ParseResponse<ContactCard>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct Response<'x> {
|
||||
#[serde(rename = "methodResponses")]
|
||||
@@ -229,7 +244,18 @@ impl<'x> From<GetResponse<Blob>> for ResponseMethod<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
// Direct SetResponse conversions to ResponseMethod
|
||||
impl<'x> From<GetResponse<ContactCard>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<ContactCard>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::ContactCard(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<GetResponse<AddressBook>> for ResponseMethod<'x> {
|
||||
fn from(value: GetResponse<AddressBook>) -> Self {
|
||||
ResponseMethod::Get(GetResponseMethod::AddressBook(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<Email>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::Email(value))
|
||||
@@ -272,7 +298,18 @@ impl<'x> From<SetResponse<VacationResponse>> for ResponseMethod<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
// Direct ChangesResponse conversions to ResponseMethod
|
||||
impl<'x> From<SetResponse<AddressBook>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<AddressBook>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::AddressBook(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<SetResponse<ContactCard>> for ResponseMethod<'x> {
|
||||
fn from(value: SetResponse<ContactCard>) -> Self {
|
||||
ResponseMethod::Set(SetResponseMethod::ContactCard(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ChangesResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<Email>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::Email(value))
|
||||
@@ -309,7 +346,12 @@ impl<'x> From<ChangesResponse<Quota>> for ResponseMethod<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
// Direct CopyResponse conversions to ResponseMethod
|
||||
impl<'x> From<ChangesResponse<AddressBook>> for ResponseMethod<'x> {
|
||||
fn from(value: ChangesResponse<AddressBook>) -> Self {
|
||||
ResponseMethod::Changes(ChangesResponseMethod::AddressBook(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<CopyResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: CopyResponse<Email>) -> Self {
|
||||
ResponseMethod::Copy(CopyResponseMethod::Email(value))
|
||||
@@ -322,16 +364,27 @@ impl<'x> From<CopyBlobResponse> for ResponseMethod<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
// Other direct conversions
|
||||
impl<'x> From<CopyResponse<ContactCard>> for ResponseMethod<'x> {
|
||||
fn from(value: CopyResponse<ContactCard>) -> Self {
|
||||
ResponseMethod::Copy(CopyResponseMethod::ContactCard(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ImportEmailResponse> for ResponseMethod<'x> {
|
||||
fn from(value: ImportEmailResponse) -> Self {
|
||||
ResponseMethod::ImportEmail(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ParseEmailResponse> for ResponseMethod<'x> {
|
||||
fn from(value: ParseEmailResponse) -> Self {
|
||||
ResponseMethod::ParseEmail(value)
|
||||
impl<'x> From<ParseResponse<Email>> for ResponseMethod<'x> {
|
||||
fn from(value: ParseResponse<Email>) -> Self {
|
||||
ResponseMethod::Parse(ParseResponseMethod::Email(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> From<ParseResponse<ContactCard>> for ResponseMethod<'x> {
|
||||
fn from(value: ParseResponse<ContactCard>) -> Self {
|
||||
ResponseMethod::Parse(ParseResponseMethod::ContactCard(value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use jmap_proto::{
|
||||
method::get::{GetRequest, GetResponse},
|
||||
object::addressbook::AddressBook,
|
||||
};
|
||||
|
||||
pub trait AddressBookGet: Sync + Send {
|
||||
fn address_book_get(
|
||||
&self,
|
||||
request: GetRequest<AddressBook>,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<GetResponse<AddressBook>>> + Send;
|
||||
}
|
||||
|
||||
impl AddressBookGet for Server {
|
||||
async fn address_book_get(
|
||||
&self,
|
||||
mut request: GetRequest<AddressBook>,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<GetResponse<AddressBook>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod get;
|
||||
pub mod set;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use http_proto::HttpSessionData;
|
||||
use jmap_proto::{
|
||||
method::set::{SetRequest, SetResponse},
|
||||
object::addressbook::AddressBook,
|
||||
};
|
||||
|
||||
pub trait AddressBookSet: Sync + Send {
|
||||
fn address_book_set(
|
||||
&self,
|
||||
request: SetRequest<'_, AddressBook>,
|
||||
access_token: &AccessToken,
|
||||
session: &HttpSessionData,
|
||||
) -> impl Future<Output = trc::Result<SetResponse<AddressBook>>> + Send;
|
||||
}
|
||||
|
||||
impl AddressBookSet for Server {
|
||||
async fn address_book_set(
|
||||
&self,
|
||||
mut request: SetRequest<'_, AddressBook>,
|
||||
access_token: &AccessToken,
|
||||
session: &HttpSessionData,
|
||||
) -> trc::Result<SetResponse<AddressBook>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
use common::auth::AccessToken;
|
||||
use directory::Permission;
|
||||
use jmap_proto::request::{
|
||||
CopyRequestMethod, GetRequestMethod, QueryChangesRequestMethod, QueryRequestMethod,
|
||||
RequestMethod, SetRequestMethod, method::MethodObject,
|
||||
CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryChangesRequestMethod,
|
||||
QueryRequestMethod, RequestMethod, SetRequestMethod, method::MethodObject,
|
||||
};
|
||||
use types::{collection::Collection, id::Id};
|
||||
|
||||
@@ -67,6 +67,8 @@ impl JmapAuthorization for AccessToken {
|
||||
GetRequestMethod::Principal(_) => Permission::JmapPrincipalGet,
|
||||
GetRequestMethod::Quota(_) => Permission::JmapQuotaGet,
|
||||
GetRequestMethod::Blob(_) => Permission::JmapBlobGet,
|
||||
GetRequestMethod::AddressBook(_) => Permission::JmapAddressBookGet,
|
||||
GetRequestMethod::ContactCard(_) => Permission::JmapContactCardGet,
|
||||
},
|
||||
RequestMethod::Set(m) => match &m {
|
||||
SetRequestMethod::Email(_) => Permission::JmapEmailSet,
|
||||
@@ -76,6 +78,8 @@ impl JmapAuthorization for AccessToken {
|
||||
SetRequestMethod::PushSubscription(_) => Permission::JmapPushSubscriptionSet,
|
||||
SetRequestMethod::Sieve(_) => Permission::JmapSieveScriptSet,
|
||||
SetRequestMethod::VacationResponse(_) => Permission::JmapVacationResponseSet,
|
||||
SetRequestMethod::AddressBook(_) => Permission::JmapAddressBookSet,
|
||||
SetRequestMethod::ContactCard(_) => Permission::JmapContactCardSet,
|
||||
},
|
||||
RequestMethod::Changes(_) => match object {
|
||||
MethodObject::Email => Permission::JmapEmailChanges,
|
||||
@@ -84,20 +88,26 @@ impl JmapAuthorization for AccessToken {
|
||||
MethodObject::Identity => Permission::JmapIdentityChanges,
|
||||
MethodObject::EmailSubmission => Permission::JmapEmailSubmissionChanges,
|
||||
MethodObject::Quota => Permission::JmapQuotaChanges,
|
||||
MethodObject::ContactCard => Permission::JmapContactCardChanges,
|
||||
MethodObject::Core
|
||||
| MethodObject::Blob
|
||||
| MethodObject::PushSubscription
|
||||
| MethodObject::SearchSnippet
|
||||
| MethodObject::VacationResponse
|
||||
| MethodObject::SieveScript
|
||||
| MethodObject::Principal => Permission::JmapEmailChanges, // Unimplemented
|
||||
| MethodObject::Principal
|
||||
| MethodObject::AddressBook => Permission::JmapEmailChanges,
|
||||
},
|
||||
RequestMethod::Copy(m) => match &m {
|
||||
CopyRequestMethod::Email(_) => Permission::JmapEmailCopy,
|
||||
CopyRequestMethod::Blob(_) => Permission::JmapBlobCopy,
|
||||
CopyRequestMethod::ContactCard(_) => Permission::JmapContactCardCopy,
|
||||
},
|
||||
RequestMethod::ImportEmail(_) => Permission::JmapEmailImport,
|
||||
RequestMethod::ParseEmail(_) => Permission::JmapEmailParse,
|
||||
RequestMethod::Parse(m) => match &m {
|
||||
ParseRequestMethod::Email(_) => Permission::JmapEmailParse,
|
||||
ParseRequestMethod::ContactCard(_) => Permission::JmapContactCardParse,
|
||||
},
|
||||
RequestMethod::QueryChanges(m) => match m {
|
||||
QueryChangesRequestMethod::Email(_) => Permission::JmapEmailQueryChanges,
|
||||
QueryChangesRequestMethod::Mailbox(_) => Permission::JmapMailboxQueryChanges,
|
||||
@@ -107,6 +117,9 @@ impl JmapAuthorization for AccessToken {
|
||||
QueryChangesRequestMethod::Sieve(_) => Permission::JmapSieveScriptQueryChanges,
|
||||
QueryChangesRequestMethod::Principal(_) => Permission::JmapPrincipalQueryChanges,
|
||||
QueryChangesRequestMethod::Quota(_) => Permission::JmapQuotaQueryChanges,
|
||||
QueryChangesRequestMethod::ContactCard(_) => {
|
||||
Permission::JmapContactCardQueryChanges
|
||||
}
|
||||
},
|
||||
RequestMethod::Query(m) => match m {
|
||||
QueryRequestMethod::Email(_) => Permission::JmapEmailQuery,
|
||||
@@ -115,6 +128,7 @@ impl JmapAuthorization for AccessToken {
|
||||
QueryRequestMethod::Sieve(_) => Permission::JmapSieveScriptQuery,
|
||||
QueryRequestMethod::Principal(_) => Permission::JmapPrincipalQuery,
|
||||
QueryRequestMethod::Quota(_) => Permission::JmapQuotaQuery,
|
||||
QueryRequestMethod::ContactCard(_) => Permission::JmapContactCardQuery,
|
||||
},
|
||||
RequestMethod::SearchSnippet(_) => Permission::JmapSearchSnippet,
|
||||
RequestMethod::ValidateScript(_) => Permission::JmapSieveScriptValidate,
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use crate::{
|
||||
addressbook::{get::AddressBookGet, set::AddressBookSet},
|
||||
api::auth::JmapAuthorization,
|
||||
blob::{copy::BlobCopy, get::BlobOperations, upload::BlobUpload},
|
||||
changes::{get::ChangesLookup, query::QueryChanges},
|
||||
contact::{
|
||||
copy::JmapContactCardCopy, get::ContactCardGet, parse::ContactCardParse,
|
||||
query::ContactCardQuery, set::ContactCardSet,
|
||||
},
|
||||
email::{
|
||||
copy::JmapEmailCopy, get::EmailGet, import::EmailImport, parse::EmailParse,
|
||||
query::EmailQuery, set::EmailSet, snippet::EmailSearchSnippet,
|
||||
@@ -31,16 +34,16 @@ use common::{Server, auth::AccessToken};
|
||||
use http_proto::HttpSessionData;
|
||||
use jmap_proto::{
|
||||
request::{
|
||||
Call, CopyRequestMethod, GetRequestMethod, QueryRequestMethod, Request, RequestMethod,
|
||||
SetRequestMethod, method::MethodName,
|
||||
Call, CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryRequestMethod, Request,
|
||||
RequestMethod, SetRequestMethod, method::MethodName,
|
||||
},
|
||||
response::{Response, ResponseMethod, SetResponseMethod},
|
||||
};
|
||||
use std::future::Future;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use trc::JmapEvent;
|
||||
use types::{collection::Collection, id::Id};
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
pub trait RequestHandler: Sync + Send {
|
||||
fn handle_jmap_request<'x>(
|
||||
&self,
|
||||
@@ -126,6 +129,12 @@ impl RequestHandler for Server {
|
||||
SetResponseMethod::VacationResponse(set_response) => {
|
||||
set_response.update_created_ids(&mut response);
|
||||
}
|
||||
SetResponseMethod::AddressBook(set_response) => {
|
||||
set_response.update_created_ids(&mut response);
|
||||
}
|
||||
SetResponseMethod::ContactCard(set_response) => {
|
||||
set_response.update_created_ids(&mut response);
|
||||
}
|
||||
}
|
||||
}
|
||||
ResponseMethod::ImportEmail(import_response) => {
|
||||
@@ -248,6 +257,18 @@ impl RequestHandler for Server {
|
||||
|
||||
self.blob_get(req, access_token).await?.into()
|
||||
}
|
||||
GetRequestMethod::AddressBook(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::AddressBook)?;
|
||||
|
||||
self.address_book_get(req, access_token).await?.into()
|
||||
}
|
||||
GetRequestMethod::ContactCard(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::ContactCard)?;
|
||||
|
||||
self.contact_card_get(req, access_token).await?.into()
|
||||
}
|
||||
},
|
||||
RequestMethod::Query(req) => match req {
|
||||
QueryRequestMethod::Email(mut req) => {
|
||||
@@ -284,6 +305,12 @@ impl RequestHandler for Server {
|
||||
|
||||
self.quota_query(req, access_token).await?.into()
|
||||
}
|
||||
QueryRequestMethod::ContactCard(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::ContactCard)?;
|
||||
|
||||
self.contact_card_query(req, access_token).await?.into()
|
||||
}
|
||||
},
|
||||
RequestMethod::Set(req) => match req {
|
||||
SetRequestMethod::Email(mut req) => {
|
||||
@@ -330,6 +357,22 @@ impl RequestHandler for Server {
|
||||
|
||||
self.vacation_response_set(req, access_token).await?.into()
|
||||
}
|
||||
SetRequestMethod::AddressBook(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::AddressBook)?;
|
||||
|
||||
self.address_book_set(req, access_token, session)
|
||||
.await?
|
||||
.into()
|
||||
}
|
||||
SetRequestMethod::ContactCard(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::ContactCard)?;
|
||||
|
||||
self.contact_card_set(req, access_token, session)
|
||||
.await?
|
||||
.into()
|
||||
}
|
||||
},
|
||||
RequestMethod::Changes(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
@@ -356,6 +399,16 @@ impl RequestHandler for Server {
|
||||
|
||||
self.blob_copy(req, access_token).await?.into()
|
||||
}
|
||||
CopyRequestMethod::ContactCard(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token
|
||||
.assert_has_access(req.account_id, Collection::ContactCard)?
|
||||
.assert_has_access(req.from_account_id, Collection::ContactCard)?;
|
||||
|
||||
self.contact_card_copy(req, access_token, next_call, session)
|
||||
.await?
|
||||
.into()
|
||||
}
|
||||
},
|
||||
RequestMethod::ImportEmail(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
@@ -363,12 +416,20 @@ impl RequestHandler for Server {
|
||||
|
||||
self.email_import(req, access_token, session).await?.into()
|
||||
}
|
||||
RequestMethod::ParseEmail(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::Email)?;
|
||||
RequestMethod::Parse(req) => match req {
|
||||
ParseRequestMethod::Email(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::Email)?;
|
||||
|
||||
self.email_parse(req, access_token).await?.into()
|
||||
}
|
||||
self.email_parse(req, access_token).await?.into()
|
||||
}
|
||||
ParseRequestMethod::ContactCard(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
access_token.assert_has_access(req.account_id, Collection::ContactCard)?;
|
||||
|
||||
self.contact_card_parse(req, access_token).await?.into()
|
||||
}
|
||||
},
|
||||
RequestMethod::QueryChanges(req) => self.query_changes(req, access_token).await?.into(),
|
||||
RequestMethod::SearchSnippet(mut req) => {
|
||||
set_account_id_if_missing(&mut req.account_id, access_token);
|
||||
|
||||
@@ -230,6 +230,9 @@ impl IntermediateChangesResponse {
|
||||
MethodObject::EmailSubmission => {
|
||||
ChangesResponseMethod::EmailSubmission(transmute_response(self.response))
|
||||
}
|
||||
MethodObject::ContactCard => {
|
||||
ChangesResponseMethod::ContactCard(transmute_response(self.response))
|
||||
}
|
||||
MethodObject::Core
|
||||
| MethodObject::Blob
|
||||
| MethodObject::PushSubscription
|
||||
@@ -237,7 +240,8 @@ impl IntermediateChangesResponse {
|
||||
| MethodObject::VacationResponse
|
||||
| MethodObject::SieveScript
|
||||
| MethodObject::Principal
|
||||
| MethodObject::Quota => unreachable!(),
|
||||
| MethodObject::Quota
|
||||
| MethodObject::AddressBook => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
use super::get::ChangesLookup;
|
||||
use crate::{
|
||||
api::request::set_account_id_if_missing, email::query::EmailQuery,
|
||||
mailbox::query::MailboxQuery, sieve::query::SieveScriptQuery,
|
||||
api::request::set_account_id_if_missing, contact::query::ContactCardQuery,
|
||||
email::query::EmailQuery, mailbox::query::MailboxQuery, sieve::query::SieveScriptQuery,
|
||||
submission::query::EmailSubmissionQuery,
|
||||
};
|
||||
use common::{Server, auth::AccessToken};
|
||||
@@ -137,6 +137,30 @@ impl QueryChanges for Server {
|
||||
up_to_id = request.up_to_id;
|
||||
results = self.sieve_script_query(request.into()).await?;
|
||||
}
|
||||
QueryChangesRequestMethod::ContactCard(mut request) => {
|
||||
// Query changes
|
||||
set_account_id_if_missing(&mut request.account_id, access_token);
|
||||
changes = self
|
||||
.changes(
|
||||
build_changes_request(&request),
|
||||
MethodObject::ContactCard,
|
||||
access_token,
|
||||
)
|
||||
.await?
|
||||
.response;
|
||||
let calculate_total = request.calculate_total.unwrap_or(false);
|
||||
has_changes = changes.has_changes();
|
||||
response = build_query_changes_response(&request, &changes);
|
||||
|
||||
if !has_changes && !calculate_total {
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
up_to_id = request.up_to_id;
|
||||
results = self
|
||||
.contact_card_query(request.into(), access_token)
|
||||
.await?;
|
||||
}
|
||||
QueryChangesRequestMethod::Principal(_) => {
|
||||
return Err(trc::JmapEvent::CannotCalculateChanges.into_err());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use http_proto::HttpSessionData;
|
||||
use jmap_proto::{
|
||||
method::copy::{CopyRequest, CopyResponse},
|
||||
object::contact::ContactCard,
|
||||
request::{Call, RequestMethod},
|
||||
};
|
||||
|
||||
pub trait JmapContactCardCopy: Sync + Send {
|
||||
fn contact_card_copy<'x>(
|
||||
&self,
|
||||
request: CopyRequest<'x, ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
next_call: &mut Option<Call<RequestMethod<'x>>>,
|
||||
session: &HttpSessionData,
|
||||
) -> impl Future<Output = trc::Result<CopyResponse<ContactCard>>> + Send;
|
||||
}
|
||||
|
||||
impl JmapContactCardCopy for Server {
|
||||
async fn contact_card_copy<'x>(
|
||||
&self,
|
||||
request: CopyRequest<'x, ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
next_call: &mut Option<Call<RequestMethod<'x>>>,
|
||||
session: &HttpSessionData,
|
||||
) -> trc::Result<CopyResponse<ContactCard>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use jmap_proto::{
|
||||
method::get::{GetRequest, GetResponse},
|
||||
object::contact::ContactCard,
|
||||
};
|
||||
|
||||
pub trait ContactCardGet: Sync + Send {
|
||||
fn contact_card_get(
|
||||
&self,
|
||||
request: GetRequest<ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<GetResponse<ContactCard>>> + Send;
|
||||
}
|
||||
|
||||
impl ContactCardGet for Server {
|
||||
async fn contact_card_get(
|
||||
&self,
|
||||
mut request: GetRequest<ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<GetResponse<ContactCard>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod copy;
|
||||
pub mod get;
|
||||
pub mod parse;
|
||||
pub mod query;
|
||||
pub mod set;
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use jmap_proto::{
|
||||
method::parse::{ParseRequest, ParseResponse},
|
||||
object::contact::ContactCard,
|
||||
};
|
||||
|
||||
pub trait ContactCardParse: Sync + Send {
|
||||
fn contact_card_parse(
|
||||
&self,
|
||||
request: ParseRequest<ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<ParseResponse<ContactCard>>> + Send;
|
||||
}
|
||||
|
||||
impl ContactCardParse for Server {
|
||||
async fn contact_card_parse(
|
||||
&self,
|
||||
request: ParseRequest<ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<ParseResponse<ContactCard>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use jmap_proto::{
|
||||
method::query::{QueryRequest, QueryResponse},
|
||||
object::contact::ContactCard,
|
||||
};
|
||||
|
||||
pub trait ContactCardQuery: Sync + Send {
|
||||
fn contact_card_query(
|
||||
&self,
|
||||
request: QueryRequest<ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<QueryResponse>> + Send;
|
||||
}
|
||||
|
||||
impl ContactCardQuery for Server {
|
||||
async fn contact_card_query(
|
||||
&self,
|
||||
mut request: QueryRequest<ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<QueryResponse> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use http_proto::HttpSessionData;
|
||||
use jmap_proto::{
|
||||
method::set::{SetRequest, SetResponse},
|
||||
object::contact::ContactCard,
|
||||
};
|
||||
|
||||
pub trait ContactCardSet: Sync + Send {
|
||||
fn contact_card_set(
|
||||
&self,
|
||||
request: SetRequest<'_, ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
session: &HttpSessionData,
|
||||
) -> impl Future<Output = trc::Result<SetResponse<ContactCard>>> + Send;
|
||||
}
|
||||
|
||||
impl ContactCardSet for Server {
|
||||
async fn contact_card_set(
|
||||
&self,
|
||||
mut request: SetRequest<'_, ContactCard>,
|
||||
access_token: &AccessToken,
|
||||
session: &HttpSessionData,
|
||||
) -> trc::Result<SetResponse<ContactCard>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,16 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
body::{ToBodyPart, TruncateBody},
|
||||
headers::HeaderToValue,
|
||||
};
|
||||
use crate::blob::download::BlobDownload;
|
||||
use common::{Server, auth::AccessToken};
|
||||
use email::message::index::PREVIEW_LENGTH;
|
||||
use jmap_proto::{
|
||||
method::parse::{ParseEmailRequest, ParseEmailResponse},
|
||||
object::email::EmailProperty,
|
||||
method::parse::{ParseRequest, ParseResponse},
|
||||
object::email::{Email, EmailProperty},
|
||||
request::IntoValid,
|
||||
};
|
||||
use jmap_tools::{Key, Map, Value};
|
||||
@@ -18,27 +23,20 @@ use mail_parser::{
|
||||
use std::future::Future;
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
use crate::blob::download::BlobDownload;
|
||||
|
||||
use super::{
|
||||
body::{ToBodyPart, TruncateBody},
|
||||
headers::HeaderToValue,
|
||||
};
|
||||
|
||||
pub trait EmailParse: Sync + Send {
|
||||
fn email_parse(
|
||||
&self,
|
||||
request: ParseEmailRequest,
|
||||
request: ParseRequest<Email>,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<ParseEmailResponse>> + Send;
|
||||
) -> impl Future<Output = trc::Result<ParseResponse<Email>>> + Send;
|
||||
}
|
||||
|
||||
impl EmailParse for Server {
|
||||
async fn email_parse(
|
||||
&self,
|
||||
request: ParseEmailRequest,
|
||||
request: ParseRequest<Email>,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<ParseEmailResponse> {
|
||||
) -> trc::Result<ParseResponse<Email>> {
|
||||
if request.blob_ids.len() > self.core.jmap.mail_parse_max_items {
|
||||
return Err(trc::JmapEvent::RequestTooLarge.into_err());
|
||||
}
|
||||
@@ -70,6 +68,7 @@ impl EmailParse for Server {
|
||||
]
|
||||
});
|
||||
let body_properties = request
|
||||
.arguments
|
||||
.body_properties
|
||||
.map(|v| v.into_valid().collect())
|
||||
.unwrap_or_else(|| {
|
||||
@@ -86,12 +85,12 @@ impl EmailParse for Server {
|
||||
EmailProperty::Location,
|
||||
]
|
||||
});
|
||||
let fetch_text_body_values = request.fetch_text_body_values.unwrap_or(false);
|
||||
let fetch_html_body_values = request.fetch_html_body_values.unwrap_or(false);
|
||||
let fetch_all_body_values = request.fetch_all_body_values.unwrap_or(false);
|
||||
let max_body_value_bytes = request.max_body_value_bytes.unwrap_or(0);
|
||||
let fetch_text_body_values = request.arguments.fetch_text_body_values.unwrap_or(false);
|
||||
let fetch_html_body_values = request.arguments.fetch_html_body_values.unwrap_or(false);
|
||||
let fetch_all_body_values = request.arguments.fetch_all_body_values.unwrap_or(false);
|
||||
let max_body_value_bytes = request.arguments.max_body_value_bytes.unwrap_or(0);
|
||||
|
||||
let mut response = ParseEmailResponse {
|
||||
let mut response = ParseResponse {
|
||||
account_id: request.account_id,
|
||||
parsed: VecMap::with_capacity(request.blob_ids.len()),
|
||||
not_parsable: vec![],
|
||||
|
||||
@@ -24,9 +24,11 @@ use store::{
|
||||
use trc::AddContext;
|
||||
use types::collection::Collection;
|
||||
|
||||
pub mod addressbook;
|
||||
pub mod api;
|
||||
pub mod blob;
|
||||
pub mod changes;
|
||||
pub mod contact;
|
||||
pub mod email;
|
||||
pub mod identity;
|
||||
pub mod mailbox;
|
||||
|
||||
Reference in New Issue
Block a user