353 lines
13 KiB
Rust
353 lines
13 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use crate::{
|
|
method::{
|
|
changes::ChangesRequest,
|
|
copy::{CopyBlobRequest, CopyRequest},
|
|
get::GetRequest,
|
|
import::ImportEmailRequest,
|
|
lookup::BlobLookupRequest,
|
|
parse::ParseEmailRequest,
|
|
query::QueryRequest,
|
|
query_changes::QueryChangesRequest,
|
|
search_snippet::GetSearchSnippetRequest,
|
|
set::SetRequest,
|
|
upload::BlobUploadRequest,
|
|
validate::ValidateSieveScriptRequest,
|
|
},
|
|
parser::{json::Parser, Ignore, JsonObjectParser, Token},
|
|
types::any_id::AnyId,
|
|
};
|
|
|
|
use super::{
|
|
capability::Capability,
|
|
echo::Echo,
|
|
method::{MethodFunction, MethodName, MethodObject},
|
|
Call, Request, RequestMethod,
|
|
};
|
|
|
|
impl Request {
|
|
pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
|
|
if json.len() <= max_size {
|
|
let mut request = Request {
|
|
using: 0,
|
|
method_calls: Vec::new(),
|
|
created_ids: None,
|
|
};
|
|
let mut found_valid_keys = false;
|
|
let mut parser = Parser::new(json);
|
|
parser.next_token::<String>()?.assert(Token::DictStart)?;
|
|
while let Some(key) = parser.next_dict_key::<u128>()? {
|
|
found_valid_keys |= request.parse_key(&mut parser, max_calls, key)?;
|
|
}
|
|
|
|
if found_valid_keys {
|
|
Ok(request)
|
|
} else {
|
|
Err(trc::JmapEvent::NotRequest
|
|
.into_err()
|
|
.details("Invalid JMAP request"))
|
|
}
|
|
} else {
|
|
Err(trc::LimitEvent::SizeRequest.into_err())
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse_key(
|
|
&mut self,
|
|
parser: &mut Parser,
|
|
max_calls: usize,
|
|
key: u128,
|
|
) -> trc::Result<bool> {
|
|
match key {
|
|
0x0067_6e69_7375 => {
|
|
parser.next_token::<Ignore>()?.assert(Token::ArrayStart)?;
|
|
loop {
|
|
match parser.next_token::<Capability>()? {
|
|
Token::String(capability) => {
|
|
self.using |= capability as u32;
|
|
}
|
|
Token::Comma => (),
|
|
Token::ArrayEnd => break,
|
|
token => return Err(token.error("capability", &token.to_string())),
|
|
}
|
|
}
|
|
Ok(true)
|
|
}
|
|
0x0073_6c6c_6143_646f_6874_656d => {
|
|
parser
|
|
.next_token::<Ignore>()?
|
|
.assert_jmap(Token::ArrayStart)?;
|
|
loop {
|
|
match parser.next_token::<Ignore>()? {
|
|
Token::ArrayStart => (),
|
|
Token::Comma => continue,
|
|
Token::ArrayEnd => break,
|
|
_ => {
|
|
return Err(trc::JmapEvent::NotRequest
|
|
.into_err()
|
|
.details("Invalid JMAP request"));
|
|
}
|
|
};
|
|
if self.method_calls.len() < max_calls {
|
|
let method_name = match parser.next_token::<MethodName>() {
|
|
Ok(Token::String(method)) => method,
|
|
Ok(_) => {
|
|
return Err(trc::JmapEvent::NotRequest
|
|
.into_err()
|
|
.details("Invalid JMAP request"));
|
|
}
|
|
Err(err)
|
|
if err.matches(trc::EventType::Jmap(
|
|
trc::JmapEvent::InvalidArguments,
|
|
)) =>
|
|
{
|
|
MethodName::error()
|
|
}
|
|
Err(err) => {
|
|
return Err(err);
|
|
}
|
|
};
|
|
parser.next_token::<Ignore>()?.assert_jmap(Token::Comma)?;
|
|
parser.ctx = method_name.obj;
|
|
let start_depth_array = parser.depth_array;
|
|
let start_depth_dict = parser.depth_dict;
|
|
|
|
let method = match (&method_name.fnc, &method_name.obj) {
|
|
(
|
|
MethodFunction::Get,
|
|
MethodObject::Email
|
|
| MethodObject::Mailbox
|
|
| MethodObject::Thread
|
|
| MethodObject::Identity
|
|
| MethodObject::EmailSubmission
|
|
| MethodObject::PushSubscription
|
|
| MethodObject::VacationResponse
|
|
| MethodObject::SieveScript
|
|
| MethodObject::Principal
|
|
| MethodObject::Quota
|
|
| MethodObject::Blob,
|
|
) => GetRequest::parse(parser).map(RequestMethod::Get),
|
|
(MethodFunction::Get, MethodObject::SearchSnippet) => {
|
|
GetSearchSnippetRequest::parse(parser)
|
|
.map(RequestMethod::SearchSnippet)
|
|
}
|
|
(MethodFunction::Query, _) => {
|
|
QueryRequest::parse(parser).map(RequestMethod::Query)
|
|
}
|
|
(MethodFunction::Set, _) => {
|
|
SetRequest::parse(parser).map(RequestMethod::Set)
|
|
}
|
|
(MethodFunction::Changes, _) => {
|
|
ChangesRequest::parse(parser).map(RequestMethod::Changes)
|
|
}
|
|
(MethodFunction::QueryChanges, _) => {
|
|
QueryChangesRequest::parse(parser).map(RequestMethod::QueryChanges)
|
|
}
|
|
(MethodFunction::Copy, MethodObject::Email) => {
|
|
CopyRequest::parse(parser).map(RequestMethod::Copy)
|
|
}
|
|
(MethodFunction::Copy, MethodObject::Blob) => {
|
|
CopyBlobRequest::parse(parser).map(RequestMethod::CopyBlob)
|
|
}
|
|
(MethodFunction::Lookup, MethodObject::Blob) => {
|
|
BlobLookupRequest::parse(parser).map(RequestMethod::LookupBlob)
|
|
}
|
|
(MethodFunction::Upload, MethodObject::Blob) => {
|
|
BlobUploadRequest::parse(parser).map(RequestMethod::UploadBlob)
|
|
}
|
|
(MethodFunction::Import, MethodObject::Email) => {
|
|
ImportEmailRequest::parse(parser).map(RequestMethod::ImportEmail)
|
|
}
|
|
(MethodFunction::Parse, MethodObject::Email) => {
|
|
ParseEmailRequest::parse(parser).map(RequestMethod::ParseEmail)
|
|
}
|
|
(MethodFunction::Validate, MethodObject::SieveScript) => {
|
|
ValidateSieveScriptRequest::parse(parser)
|
|
.map(RequestMethod::ValidateScript)
|
|
}
|
|
(MethodFunction::Echo, MethodObject::Core) => {
|
|
Echo::parse(parser).map(RequestMethod::Echo)
|
|
}
|
|
_ => Err(trc::JmapEvent::UnknownMethod
|
|
.into_err()
|
|
.details(method_name.to_string())),
|
|
};
|
|
|
|
let method = match method {
|
|
Ok(method) => method,
|
|
Err(err) if !err.is_jmap_method_error() => {
|
|
parser.skip_token(start_depth_array, start_depth_dict)?;
|
|
RequestMethod::Error(err)
|
|
}
|
|
Err(err) => {
|
|
return Err(err);
|
|
}
|
|
};
|
|
|
|
parser.next_token::<Ignore>()?.assert_jmap(Token::Comma)?;
|
|
let id = parser.next_token::<String>()?.unwrap_string("")?;
|
|
parser
|
|
.next_token::<Ignore>()?
|
|
.assert_jmap(Token::ArrayEnd)?;
|
|
self.method_calls.push(Call {
|
|
id,
|
|
method,
|
|
name: method_name,
|
|
});
|
|
} else {
|
|
return Err(trc::LimitEvent::CallsIn.into_err());
|
|
}
|
|
}
|
|
Ok(true)
|
|
}
|
|
0x7364_4964_6574_6165_7263 => {
|
|
let mut created_ids = HashMap::new();
|
|
parser.next_token::<Ignore>()?.assert(Token::DictStart)?;
|
|
while let Some(key) = parser.next_dict_key::<String>()? {
|
|
created_ids.insert(
|
|
key,
|
|
parser.next_token::<AnyId>()?.unwrap_string("createdIds")?,
|
|
);
|
|
}
|
|
self.created_ids = Some(created_ids);
|
|
Ok(true)
|
|
}
|
|
_ => {
|
|
parser.skip_token(parser.depth_array, parser.depth_dict)?;
|
|
Ok(false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::request::Request;
|
|
|
|
const TEST: &str = r#"
|
|
{
|
|
"using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
|
|
"methodCalls": [
|
|
[ "method1", {
|
|
"arg1": "arg1data",
|
|
"arg2": "arg2data"
|
|
}, "c1" ],
|
|
[ "Core/echo", {
|
|
"hello": true,
|
|
"high": 5
|
|
}, "c2" ],
|
|
[ "method3", {"hello": [{"a": {"b": true}}]}, "c3" ]
|
|
],
|
|
"createdIds": {
|
|
"c1": "m1",
|
|
"c2": "m2"
|
|
}
|
|
}
|
|
"#;
|
|
|
|
const TEST2: &str = r##"
|
|
{
|
|
"using": [
|
|
"urn:ietf:params:jmap:submission",
|
|
"urn:ietf:params:jmap:mail",
|
|
"urn:ietf:params:jmap:core"
|
|
],
|
|
"methodCalls": [
|
|
[
|
|
"Email/set",
|
|
{
|
|
"accountId": "c",
|
|
"create": {
|
|
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
|
"mailboxIds": {
|
|
"9": true
|
|
},
|
|
"subject": "test",
|
|
"from": [
|
|
{
|
|
"name": "Foo",
|
|
"email": "foo@bar.com"
|
|
}
|
|
],
|
|
"to": [
|
|
{
|
|
"name": null,
|
|
"email": "bar@foo.com"
|
|
}
|
|
],
|
|
"cc": [],
|
|
"bcc": [],
|
|
"replyTo": [
|
|
{
|
|
"name": null,
|
|
"email": "foo@bar.com"
|
|
}
|
|
],
|
|
"htmlBody": [
|
|
{
|
|
"partId": "c37ee58b-e224-4799-88e6-1d7484e3b782",
|
|
"type": "text/html"
|
|
}
|
|
],
|
|
"bodyValues": {
|
|
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
|
"value": "<p>test email<br></p>",
|
|
"isEncodingProblem": false,
|
|
"isTruncated": false
|
|
}
|
|
},
|
|
"header:User-Agent:asText": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0"
|
|
}
|
|
}
|
|
},
|
|
"c0"
|
|
],
|
|
[
|
|
"EmailSubmission/set",
|
|
{
|
|
"accountId": "c",
|
|
"create": {
|
|
"c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
|
"identityId": "a",
|
|
"emailId": "#c37ee58b-e224-4799-88e6-1d7484e3b782",
|
|
"envelope": {
|
|
"mailFrom": {
|
|
"email": "foo@bar.com"
|
|
},
|
|
"rcptTo": [
|
|
{
|
|
"email": "bar@foo.com"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"onSuccessUpdateEmail": {
|
|
"#c37ee58b-e224-4799-88e6-1d7484e3b782": {
|
|
"mailboxIds/d": true,
|
|
"mailboxIds/9": null,
|
|
"keywords/$seen": true,
|
|
"keywords/$draft": null
|
|
}
|
|
}
|
|
},
|
|
"c1"
|
|
]
|
|
]
|
|
}
|
|
"##;
|
|
|
|
#[test]
|
|
fn parse_request() {
|
|
println!("{:?}", Request::parse(TEST.as_bytes(), 10, 10240));
|
|
println!("{:?}", Request::parse(TEST2.as_bytes(), 10, 10240));
|
|
}
|
|
}
|