/* * Copyright (c) 2023 Stalwart Labs Ltd. * * This file is part of the Stalwart JMAP Server. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * in the LICENSE file at the top-level directory of this distribution. * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * You can be released from the requirements of the AGPLv3 license by * purchasing a commercial license. Please contact licensing@stalw.art * for more details. */ use std::fmt::Display; use crate::{ error::method::MethodError, object::{email, mailbox}, parser::{json::Parser, Error, Ignore, JsonObjectParser, Token}, request::{method::MethodObject, RequestProperty, RequestPropertyParser}, types::{date::UTCDate, id::Id, keyword::Keyword, state::State}, }; #[derive(Debug, Clone)] pub struct QueryRequest { pub account_id: Id, pub filter: Vec, pub sort: Option>, pub position: Option, pub anchor: Option, pub anchor_offset: Option, pub limit: Option, pub calculate_total: Option, pub arguments: T, } #[derive(Debug, Clone, serde::Serialize)] pub struct QueryResponse { #[serde(rename = "accountId")] pub account_id: Id, #[serde(rename = "queryState")] pub query_state: State, #[serde(rename = "canCalculateChanges")] pub can_calculate_changes: bool, #[serde(rename = "position")] pub position: i32, #[serde(rename = "ids")] pub ids: Vec, #[serde(rename = "total")] #[serde(skip_serializing_if = "Option::is_none")] pub total: Option, #[serde(rename = "limit")] #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, } #[derive(Clone, Debug)] pub enum Filter { Email(String), Name(String), DomainName(String), Text(String), Type(String), Timezone(String), Members(Id), QuotaLt(u32), QuotaGt(u32), IdentityIds(Vec), EmailIds(Vec), ThreadIds(Vec), UndoStatus(String), Before(UTCDate), After(UTCDate), InMailbox(Id), InMailboxOtherThan(Vec), MinSize(u32), MaxSize(u32), AllInThreadHaveKeyword(Keyword), SomeInThreadHaveKeyword(Keyword), NoneInThreadHaveKeyword(Keyword), HasKeyword(Keyword), NotKeyword(Keyword), HasAttachment(bool), From(String), To(String), Cc(String), Bcc(String), Subject(String), Body(String), Header(Vec), Id(Vec), SentBefore(UTCDate), SentAfter(UTCDate), InThread(Id), ParentId(Option), Role(Option), HasAnyRole(bool), IsSubscribed(bool), IsActive(bool), _T(String), And, Or, Not, Close, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Comparator { pub is_ascending: bool, pub collation: Option, pub property: SortProperty, pub keyword: Option, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum SortProperty { Type, Name, Email, EmailId, ThreadId, SentAt, ReceivedAt, Size, From, To, Subject, Cc, SortOrder, ParentId, IsActive, HasKeyword, AllInThreadHaveKeyword, SomeInThreadHaveKeyword, _T(String), } #[derive(Debug, Clone)] pub enum RequestArguments { Email(email::QueryArguments), Mailbox(mailbox::QueryArguments), EmailSubmission, SieveScript, } impl JsonObjectParser for QueryRequest { fn parse(parser: &mut Parser<'_>) -> crate::parser::Result where Self: Sized, { let mut request = QueryRequest { arguments: match &parser.ctx { MethodObject::Email => RequestArguments::Email(Default::default()), MethodObject::Mailbox => RequestArguments::Mailbox(Default::default()), MethodObject::EmailSubmission => RequestArguments::EmailSubmission, MethodObject::SieveScript => RequestArguments::SieveScript, _ => { return Err(Error::Method(MethodError::UnknownMethod(format!( "{}/query", parser.ctx )))) } }, filter: vec![], sort: None, position: None, anchor: None, anchor_offset: None, limit: None, calculate_total: None, account_id: Id::default(), }; parser .next_token::()? .assert_jmap(Token::DictStart)?; while let Some(key) = parser.next_dict_key::()? { match &key.hash[0] { 0x0064_4974_6e75_6f63_6361 => { request.account_id = parser.next_token::()?.unwrap_string("accountId")?; } 0x7265_746c_6966 => match parser.next_token::()? { Token::DictStart => { request.filter = parse_filter(parser)?; } Token::Null => (), token => { return Err(token.error("filter", "object or null")); } }, 0x7472_6f73 => match parser.next_token::()? { Token::ArrayStart => { request.sort = parse_sort(parser)?.into(); } Token::Null => (), token => { return Err(token.error("sort", "array or null")); } }, 0x6e6f_6974_6973_6f70 => { request.position = parser .next_token::()? .unwrap_ints_or_null("position")?; } 0x726f_6863_6e61 => { request.anchor = parser.next_token::()?.unwrap_string_or_null("anchor")?; } 0x7465_7366_664f_726f_6863_6e61 => { request.anchor_offset = parser .next_token::()? .unwrap_ints_or_null("anchorOffset")?; } 0x0074_696d_696c => { request.limit = parser .next_token::()? .unwrap_usize_or_null("limit")?; } 0x6c61_746f_5465_7461_6c75_636c_6163 => { request.calculate_total = parser .next_token::()? .unwrap_bool_or_null("calculateTotal")?; } _ => { if !request.arguments.parse(parser, key)? { parser.skip_token(parser.depth_array, parser.depth_dict)?; } } } } Ok(request) } } pub fn parse_filter(parser: &mut Parser) -> crate::parser::Result> { let mut filter = vec![Filter::Close]; let mut pos_stack = vec![0]; loop { match parser.next_token::()? { Token::String(property) => { parser.next_token::()?.assert(Token::Colon)?; filter[*pos_stack.last().unwrap()] = match &property.hash[0] { 0x726f_7461_7265_706f => { match parser.next_token::()?.unwrap_string("operator")? { 0x0044_4e41 => Filter::And, 0x524f => Filter::Or, 0x0054_4f4e => Filter::Not, _ => return Err(parser.error_value()), } } 0x736e_6f69_7469_646e_6f63 => { parser.next_token::()?.assert(Token::ArrayStart)?; continue; } _ => match (&property.hash[0], &property.hash[1]) { (0x006c_6961_6d65, _) => { Filter::Email(parser.next_token::()?.unwrap_string("email")?) } (0x656d_616e, _) => { Filter::Name(parser.next_token::()?.unwrap_string("name")?) } (0x656d_614e_6e69_616d_6f64, _) => Filter::DomainName( parser.next_token::()?.unwrap_string("domainName")?, ), (0x7478_6574, _) => { Filter::Text(parser.next_token::()?.unwrap_string("text")?) } (0x6570_7974, _) => { Filter::Type(parser.next_token::()?.unwrap_string("type")?) } (0x656e_6f7a_656d_6974, _) => Filter::Timezone( parser.next_token::()?.unwrap_string("timezone")?, ), (0x0073_7265_626d_656d, _) => { Filter::Members(parser.next_token::()?.unwrap_string("members")?) } (0x6e61_6854_7265_776f_4c61_746f_7571, _) => Filter::QuotaLt( parser .next_token::()? .unwrap_uint_or_null("quotaLowerThan")? .unwrap_or_default() as u32, ), (0x6e61_6854_7265_7461_6572_4761_746f_7571, _) => Filter::QuotaGt( parser .next_token::()? .unwrap_uint_or_null("quotaGreaterThan")? .unwrap_or_default() as u32, ), (0x0073_6449_7974_6974_6e65_6469, _) => { Filter::IdentityIds(>::parse(parser)?) } (0x7364_496c_6961_6d65, _) => Filter::EmailIds(>::parse(parser)?), (0x0073_6449_6461_6572_6874, _) => { Filter::ThreadIds(>::parse(parser)?) } (0x7375_7461_7453_6f64_6e75, _) => Filter::UndoStatus( parser.next_token::()?.unwrap_string("undoStatus")?, ), (0x6572_6f66_6562, _) => { Filter::Before(parser.next_token::()?.unwrap_string("before")?) } (0x0072_6574_6661, _) => { Filter::After(parser.next_token::()?.unwrap_string("after")?) } (0x0078_6f62_6c69_614d_6e69, _) => Filter::InMailbox( parser.next_token::()?.unwrap_string("inMailbox")?, ), (0x6854_7265_6874_4f78_6f62_6c69_614d_6e69, 0x6e61) => { Filter::InMailboxOtherThan(>::parse(parser)?) } (0x0065_7a69_536e_696d, _) => Filter::MinSize( parser .next_token::()? .unwrap_uint_or_null("minSize")? .unwrap_or_default() as u32, ), (0x0065_7a69_5378_616d, _) => Filter::MaxSize( parser .next_token::()? .unwrap_uint_or_null("maxSize")? .unwrap_or_default() as u32, ), (0x4b65_7661_4864_6165_7268_546e_496c_6c61, 0x6472_6f77_7965) => { Filter::AllInThreadHaveKeyword( parser .next_token::()? .unwrap_string("allInThreadHaveKeyword")?, ) } (0x6576_6148_6461_6572_6854_6e49_656d_6f73, 0x0064_726f_7779_654b) => { Filter::SomeInThreadHaveKeyword( parser .next_token::()? .unwrap_string("someInThreadHaveKeyword")?, ) } (0x6576_6148_6461_6572_6854_6e49_656e_6f6e, 0x0064_726f_7779_654b) => { Filter::NoneInThreadHaveKeyword( parser .next_token::()? .unwrap_string("noneInThreadHaveKeyword")?, ) } (0x6472_6f77_7965_4b73_6168, _) => Filter::HasKeyword( parser .next_token::()? .unwrap_string("hasKeyword")?, ), (0x6472_6f77_7965_4b74_6f6e, _) => Filter::NotKeyword( parser .next_token::()? .unwrap_string("notKeyword")?, ), (0x0074_6e65_6d68_6361_7474_4173_6168, _) => Filter::HasAttachment( parser .next_token::()? .unwrap_bool("hasAttachment")?, ), (0x6d6f_7266, _) => { Filter::From(parser.next_token::()?.unwrap_string("from")?) } (0x6f74, _) => { Filter::To(parser.next_token::()?.unwrap_string("to")?) } (0x6363, _) => { Filter::Cc(parser.next_token::()?.unwrap_string("cc")?) } (0x0063_6362, _) => { Filter::Bcc(parser.next_token::()?.unwrap_string("bcc")?) } (0x0074_6365_6a62_7573, _) => Filter::Subject( parser.next_token::()?.unwrap_string("subject")?, ), (0x7964_6f62, _) => { Filter::Body(parser.next_token::()?.unwrap_string("body")?) } (0x7265_6461_6568, _) => Filter::Header(>::parse(parser)?), (0x6469, _) => Filter::Id(>::parse(parser)?), (0x6572_6f66_6542_746e_6573, _) => Filter::SentBefore( parser .next_token::()? .unwrap_string("sentBefore")?, ), (0x0072_6574_6641_746e_6573, _) => Filter::SentAfter( parser.next_token::()?.unwrap_string("sentAfter")?, ), (0x6461_6572_6854_6e69, _) => { Filter::InThread(parser.next_token::()?.unwrap_string("inThread")?) } (0x6449_746e_6572_6170, _) => Filter::ParentId( parser .next_token::()? .unwrap_string_or_null("parentId")?, ), (0x656c_6f72, _) => Filter::Role( parser .next_token::()? .unwrap_string_or_null("role")?, ), (0x656c_6f52_796e_4173_6168, _) => Filter::HasAnyRole( parser.next_token::()?.unwrap_bool("hasAnyRole")?, ), (0x6465_6269_7263_7362_7553_7369, _) => Filter::IsSubscribed( parser.next_token::()?.unwrap_bool("isSubscribed")?, ), (0x6576_6974_6341_7369, _) => Filter::IsActive( parser.next_token::()?.unwrap_bool("isActive")?, ), _ => { if parser.is_eof || parser.skip_string() { let filter = Filter::_T( String::from_utf8_lossy( parser.bytes[parser.pos_marker..parser.pos - 1].as_ref(), ) .into_owned(), ); parser.skip_token(parser.depth_array, parser.depth_dict)?; filter } else { return Err(parser.error_unterminated()); } } }, }; } Token::DictStart => { pos_stack.push(filter.len()); filter.push(Filter::Close); } Token::DictEnd => { if !matches!(filter[pos_stack.pop().unwrap()], Filter::Close) { if pos_stack.is_empty() { break; } } else { return Err(Error::Method(MethodError::InvalidArguments( "Malformed filter".to_string(), ))); } } Token::ArrayEnd => { filter.push(Filter::Close); } Token::Comma => (), token => { return Err(token.error("filter", "object or array")); } } } Ok(filter) } pub fn parse_sort(parser: &mut Parser) -> crate::parser::Result> { let mut sort = vec![]; loop { match parser.next_token::()? { Token::DictStart => { let mut comp = Comparator { is_ascending: true, collation: None, property: SortProperty::Type, keyword: None, }; while let Some(key) = parser.next_dict_key::()? { match key { 0x0067_6e69_646e_6563_7341_7369 => { comp.is_ascending = parser .next_token::()? .unwrap_bool_or_null("isAscending")? .unwrap_or_default(); } 0x006e_6f69_7461_6c6c_6f63 => { comp.collation = parser .next_token::()? .unwrap_string_or_null("collation")?; } 0x7974_7265_706f_7270 => { comp.property = parser .next_token::()? .unwrap_string("property")?; } 0x0064_726f_7779_656b => { comp.keyword = parser .next_token::()? .unwrap_string_or_null("keyword")?; } _ => { parser.skip_token(parser.depth_array, parser.depth_dict)?; } } } sort.push(comp); } Token::Comma => (), Token::ArrayEnd => { break; } token => { return Err(token.error("sort", "object")); } } } Ok(sort) } impl JsonObjectParser for SortProperty { fn parse(parser: &mut Parser<'_>) -> crate::parser::Result where Self: Sized, { let mut hash = 0; let mut shift = 0; while let Some(ch) = parser.next_unescaped()? { if ch.is_ascii_alphabetic() { if shift < 128 { hash |= (ch as u128) << shift; shift += 8; } else { break; } } else { hash = 0; break; } } match hash { 0x6570_7974 => Ok(SortProperty::Type), 0x656d_616e => Ok(SortProperty::Name), 0x006c_6961_6d65 => Ok(SortProperty::Email), 0x0064_496c_6961_6d65 => Ok(SortProperty::EmailId), 0x6449_6461_6572_6874 => Ok(SortProperty::ThreadId), 0x7441_746e_6573 => Ok(SortProperty::SentAt), 0x7441_6465_7669_6563_6572 => Ok(SortProperty::ReceivedAt), 0x657a_6973 => Ok(SortProperty::Size), 0x6d6f_7266 => Ok(SortProperty::From), 0x6f74 => Ok(SortProperty::To), 0x0074_6365_6a62_7573 => Ok(SortProperty::Subject), 0x6363 => Ok(SortProperty::Cc), 0x0072_6564_724f_7472_6f73 => Ok(SortProperty::SortOrder), 0x6449_746e_6572_6170 => Ok(SortProperty::ParentId), 0x6576_6974_6341_7369 => Ok(SortProperty::IsActive), 0x6472_6f77_7965_4b73_6168 => Ok(SortProperty::HasKeyword), 0x4b65_7661_4864_6165_7268_546e_496c_6c61 => Ok(SortProperty::AllInThreadHaveKeyword), 0x6576_6148_6461_6572_6854_6e49_656d_6f73 => Ok(SortProperty::SomeInThreadHaveKeyword), _ => { if parser.is_eof || parser.skip_string() { Ok(SortProperty::_T( String::from_utf8_lossy( parser.bytes[parser.pos_marker..parser.pos - 1].as_ref(), ) .into_owned(), )) } else { Err(parser.error_unterminated()) } } } } } impl Display for Filter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Filter::Email(_) => "email", Filter::Name(_) => "name", Filter::DomainName(_) => "domainName", Filter::Text(_) => "text", Filter::Type(_) => "type", Filter::Timezone(_) => "timezone", Filter::Members(_) => "members", Filter::QuotaLt(_) => "quotaLt", Filter::QuotaGt(_) => "quotaGt", Filter::IdentityIds(_) => "identityIds", Filter::EmailIds(_) => "emailIds", Filter::ThreadIds(_) => "threadIds", Filter::UndoStatus(_) => "undoStatus", Filter::Before(_) => "before", Filter::After(_) => "after", Filter::InMailbox(_) => "inMailbox", Filter::InMailboxOtherThan(_) => "inMailboxOtherThan", Filter::MinSize(_) => "minSize", Filter::MaxSize(_) => "maxSize", Filter::AllInThreadHaveKeyword(_) => "allInThreadHaveKeyword", Filter::SomeInThreadHaveKeyword(_) => "someInThreadHaveKeyword", Filter::NoneInThreadHaveKeyword(_) => "noneInThreadHaveKeyword", Filter::HasKeyword(_) => "hasKeyword", Filter::NotKeyword(_) => "notKeyword", Filter::HasAttachment(_) => "hasAttachment", Filter::From(_) => "from", Filter::To(_) => "to", Filter::Cc(_) => "cc", Filter::Bcc(_) => "bcc", Filter::Subject(_) => "subject", Filter::Body(_) => "body", Filter::Header(_) => "header", Filter::Id(_) => "id", Filter::SentBefore(_) => "sentBefore", Filter::SentAfter(_) => "sentAfter", Filter::InThread(_) => "inThread", Filter::ParentId(_) => "parentId", Filter::Role(_) => "role", Filter::HasAnyRole(_) => "hasAnyRole", Filter::IsSubscribed(_) => "isSubscribed", Filter::IsActive(_) => "isActive", Filter::_T(v) => v.as_str(), Filter::And => "and", Filter::Or => "or", Filter::Not => "not", Filter::Close => "close", }) } } impl Display for SortProperty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { SortProperty::Type => "type", SortProperty::Name => "name", SortProperty::Email => "email", SortProperty::EmailId => "emailId", SortProperty::ThreadId => "threadId", SortProperty::SentAt => "sentAt", SortProperty::ReceivedAt => "receivedAt", SortProperty::Size => "size", SortProperty::From => "from", SortProperty::To => "to", SortProperty::Subject => "subject", SortProperty::Cc => "cc", SortProperty::SortOrder => "sortOrder", SortProperty::ParentId => "parentId", SortProperty::IsActive => "isActive", SortProperty::HasKeyword => "hasKeyword", SortProperty::AllInThreadHaveKeyword => "allInThreadHaveKeyword", SortProperty::SomeInThreadHaveKeyword => "someInThreadHaveKeyword", SortProperty::_T(s) => s, }) } } impl RequestPropertyParser for RequestArguments { fn parse( &mut self, parser: &mut Parser, property: RequestProperty, ) -> crate::parser::Result { match self { RequestArguments::Email(args) => args.parse(parser, property), RequestArguments::Mailbox(args) => args.parse(parser, property), _ => Ok(false), } } } impl Filter { pub fn is_immutable(&self) -> bool { matches!( self, Filter::Before(_) | Filter::After(_) | Filter::MinSize(_) | Filter::MaxSize(_) | Filter::Text(_) | Filter::HasAttachment(_) | Filter::From(_) | Filter::To(_) | Filter::Cc(_) | Filter::Bcc(_) | Filter::Subject(_) | Filter::Body(_) | Filter::Header(_) | Filter::Id(_) | Filter::SentBefore(_) | Filter::SentAfter(_) ) } } impl Comparator { pub fn descending(property: SortProperty) -> Self { Self { property, is_ascending: false, collation: None, keyword: None, } } pub fn ascending(property: SortProperty) -> Self { Self { property, is_ascending: true, collation: None, keyword: None, } } pub fn is_immutable(&self) -> bool { matches!( &self.property, SortProperty::SentAt | SortProperty::ReceivedAt | SortProperty::Size | SortProperty::From | SortProperty::To | SortProperty::Subject | SortProperty::Cc ) } } impl QueryRequest { pub fn take_arguments(&mut self) -> RequestArguments { std::mem::replace(&mut self.arguments, RequestArguments::SieveScript) } } impl QueryRequest { pub fn with_arguments(self, arguments: A) -> QueryRequest { QueryRequest { arguments, account_id: self.account_id, filter: self.filter, sort: self.sort, position: self.position, anchor: self.anchor, anchor_offset: self.anchor_offset, limit: self.limit, calculate_total: self.calculate_total, } } } impl From for store::query::Filter { fn from(value: Filter) -> Self { match value { Filter::And => Self::And, Filter::Or => Self::Or, Filter::Not => Self::Not, Filter::Close => Self::End, _ => unreachable!(), } } }