This commit is contained in:
Mauro D
2023-04-12 09:25:38 +00:00
parent 3250cbc443
commit 2918bffd3b
11 changed files with 281 additions and 20 deletions

View File

@@ -0,0 +1,43 @@
use mail_parser::{
decoders::{base64::base64_decode, quoted_printable::quoted_printable_decode},
Encoding,
};
use protocol::types::blob::BlobId;
use crate::JMAP;
impl JMAP {
pub async fn blob_retrieve(
&self,
blob_id: &BlobId,
account_id: u32,
) -> store::Result<Option<Vec<u8>>> {
if !self
.store
.has_blob_access(&blob_id.hash, vec![account_id])
.await?
{
// TODO: validate ACL
let acl = "true";
return Ok(None);
}
if let Some(section) = &blob_id.section {
Ok(self
.store
.get_blob(
&blob_id.hash,
(section.offset_start as u32)
..(section.offset_start.saturating_add(section.size) as u32),
)
.await?
.and_then(|bytes| match Encoding::from(section.encoding) {
Encoding::None => Some(bytes),
Encoding::Base64 => base64_decode(&bytes),
Encoding::QuotedPrintable => quoted_printable_decode(&bytes),
}))
} else {
self.store.get_blob(&blob_id.hash, 0..u32::MAX).await
}
}
}

View File

@@ -0,0 +1 @@
pub mod get;

View File

@@ -1,17 +1,133 @@
use protocol::{
error::method::MethodError,
error::{
method::MethodError,
set::{SetError, SetErrorType},
},
method::import::{ImportEmailRequest, ImportEmailResponse},
types::{collection::Collection, property::Property, state::State},
};
use store::BitmapKey;
use utils::map::vec_map::VecMap;
use crate::JMAP;
use crate::{MaybeError, JMAP};
impl JMAP {
pub async fn email_import(
&self,
request: ImportEmailRequest,
) -> Result<ImportEmailResponse, MethodError> {
for (id, email) in request.emails {}
// Validate state
let account_id = request.account_id.document_id();
let old_state: State = self
.store
.get_last_change_id(account_id, Collection::Email)
.await?
.into();
if let Some(if_in_state) = request.if_in_state {
if old_state != if_in_state {
return Err(MethodError::StateMismatch);
}
}
todo!()
let cococ = "implement ACLS";
let valid_mailbox_ids = self
.store
.get_bitmap(BitmapKey::document_ids(account_id, Collection::Mailbox))
.await?
.unwrap_or_default();
let mut created = VecMap::with_capacity(request.emails.len());
let mut not_created = VecMap::with_capacity(request.emails.len());
'outer: for (id, email) in request.emails {
// Validate mailboxIds
let mailbox_ids = email
.mailbox_ids
.unwrap()
.into_iter()
.map(|m| m.unwrap().document_id())
.collect::<Vec<_>>();
if mailbox_ids.is_empty() {
not_created.append(
id,
SetError::invalid_properties()
.with_property(Property::MailboxIds)
.with_description("Message must belong to at least one mailbox."),
);
continue;
}
for mailbox_id in &mailbox_ids {
if !valid_mailbox_ids.contains(*mailbox_id) {
not_created.append(
id,
SetError::invalid_properties()
.with_property(Property::MailboxIds)
.with_description(format!("Mailbox {} does not exist.", mailbox_id)),
);
continue 'outer;
}
}
// Fetch raw message to import
let raw_message =
if let Some(raw_message) = self.blob_retrieve(&email.blob_id, account_id).await? {
raw_message
} else {
not_created.append(
id,
SetError::new(SetErrorType::BlobNotFound)
.with_description(format!("BlobId {} not found.", email.blob_id)),
);
continue;
};
// Import message
match self
.email_ingest(
&raw_message,
account_id,
mailbox_ids,
email.keywords,
email.received_at.map(|r| r.into()),
)
.await
{
Ok(email) => {
created.append(id, email.into());
}
Err(MaybeError::Permanent(reason)) => {
not_created.append(
id,
SetError::new(SetErrorType::InvalidEmail).with_description(reason),
);
}
Err(MaybeError::Temporary(_)) => {
return Err(MethodError::ServerPartialFail);
}
}
}
Ok(ImportEmailResponse {
account_id: request.account_id,
new_state: if !created.is_empty() {
self.store
.get_last_change_id(account_id, Collection::Email)
.await?
.into()
} else {
old_state.clone()
},
old_state: old_state.into(),
created: if !created.is_empty() {
created.into()
} else {
None
},
not_created: if !not_created.is_empty() {
not_created.into()
} else {
None
},
})
}
}

View File

@@ -1,7 +1,13 @@
use mail_parser::{
parsers::fields::thread::thread_name, HeaderName, HeaderValue, Message, RfcHeader,
};
use protocol::types::{collection::Collection, id::Id, keyword::Keyword, property::Property};
use protocol::{
object::Object,
types::{
blob::BlobId, collection::Collection, id::Id, keyword::Keyword, property::Property,
value::Value,
},
};
use store::{
query::Filter,
write::{log::ChangeLogBuilder, now, BatchBuilder, F_BITMAP, F_CLEAR, F_VALUE},
@@ -247,3 +253,12 @@ impl JMAP {
}
}
}
impl From<IngestedEmail> for Object<Value> {
fn from(email: IngestedEmail) -> Self {
Object::with_capacity(3)
.with_property(Property::Id, email.id)
.with_property(Property::ThreadId, email.id.prefix_id())
.with_property(Property::BlobId, BlobId::new(email.blob_hash))
}
}

View File

@@ -1,6 +1,8 @@
use protocol::error::method::MethodError;
use store::{fts::Language, Store};
pub mod api;
pub mod blob;
pub mod email;
pub struct JMAP {
@@ -33,3 +35,15 @@ impl From<store::Error> for MaybeError {
}
}
}
impl From<MaybeError> for MethodError {
fn from(value: MaybeError) -> Self {
match value {
MaybeError::Temporary(msg) => {
let log = "true";
MethodError::ServerPartialFail
}
MaybeError::Permanent(msg) => MethodError::InvalidArguments(msg),
}
}
}