trivy / Check (pull_request) Waiting to run
Upstream commit: 9d1c75ab68435e4417337f768291e5f947686203 Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 118 in 50 files Dangling module declarations removed: 5 Edits turning enterprise off: 25 Third-party code: 14 files, 0 not in THIRD-PARTY.md Verification: clean One snippet more than v0.16.22, in crates/common/src/auth/authentication.rs (3, was 2).
734 lines
27 KiB
Rust
734 lines
27 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use super::ImapContext;
|
|
use crate::{
|
|
core::{MailboxId, SelectedMailbox, Session, SessionData},
|
|
spawn_op,
|
|
};
|
|
use common::{ipc::PushNotification, network::SessionStream, storage::index::ObjectIndexBuilder};
|
|
use email::{
|
|
cache::{MessageCacheFetch, email::MessageCacheAccess},
|
|
mailbox::{JUNK_ID, TRASH_ID, UidMailbox},
|
|
message::{
|
|
copy::{CopyMessageError, EmailCopy},
|
|
ingest::EmailIngest,
|
|
metadata::MessageData,
|
|
},
|
|
};
|
|
use imap_proto::{
|
|
Command, ResponseCode, StatusResponse, protocol::copy_move::Arguments, receiver::Request,
|
|
};
|
|
use registry::schema::enums::Permission;
|
|
use std::{sync::Arc, time::Instant};
|
|
use store::{
|
|
ValueKey,
|
|
roaring::RoaringBitmap,
|
|
write::{AlignedBytes, Archive, BatchBuilder},
|
|
};
|
|
use trc::AddContext;
|
|
use types::{
|
|
acl::Acl,
|
|
collection::{Collection, VanishedCollection},
|
|
type_state::{DataType, StateChange},
|
|
};
|
|
|
|
impl<T: SessionStream> Session<T> {
|
|
pub async fn handle_copy_move(
|
|
&mut self,
|
|
request: Request<Command>,
|
|
is_move: bool,
|
|
is_uid: bool,
|
|
) -> trc::Result<()> {
|
|
// Validate access
|
|
self.assert_has_permission(if is_move {
|
|
Permission::ImapMove
|
|
} else {
|
|
Permission::ImapCopy
|
|
})?;
|
|
|
|
let op_start = Instant::now();
|
|
let arguments = request.parse_copy_move(self.is_utf8)?;
|
|
let (data, src_mailbox) = self.state.mailbox_state();
|
|
let use_vanished = self.is_qresync || self.is_uidonly;
|
|
// RFC 9738 places COPY under SAVELIMIT but leaves MOVE under MESSAGELIMIT
|
|
let message_limit = if is_move {
|
|
self.server.core.imap.max_messages_per_command
|
|
} else {
|
|
self.server.core.imap.max_messages_per_save
|
|
};
|
|
|
|
spawn_op!(data, {
|
|
// Refresh mailboxes
|
|
data.synchronize_mailboxes(false)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
// Make sure the mailbox exists.
|
|
let dest_mailbox =
|
|
if let Some(mailbox) = data.get_mailbox_by_name(&arguments.mailbox_name) {
|
|
mailbox
|
|
} else {
|
|
return Err(trc::ImapEvent::Error
|
|
.into_err()
|
|
.details("Destination mailbox does not exist.")
|
|
.code(ResponseCode::TryCreate)
|
|
.id(arguments.tag));
|
|
};
|
|
|
|
// Check that the destination mailbox is not the same as the source mailbox.
|
|
if src_mailbox.id.account_id == dest_mailbox.account_id
|
|
&& src_mailbox.id.mailbox_id == dest_mailbox.mailbox_id
|
|
{
|
|
return Err(trc::ImapEvent::Error
|
|
.into_err()
|
|
.details("Source and destination mailboxes are the same.")
|
|
.code(ResponseCode::Cannot)
|
|
.id(arguments.tag));
|
|
}
|
|
|
|
data.copy_move(
|
|
arguments,
|
|
src_mailbox,
|
|
dest_mailbox,
|
|
is_move,
|
|
is_uid,
|
|
use_vanished,
|
|
message_limit,
|
|
op_start,
|
|
)
|
|
.await
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: SessionStream> SessionData<T> {
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn copy_move(
|
|
&self,
|
|
arguments: Arguments,
|
|
src_mailbox: Arc<SelectedMailbox>,
|
|
dest_mailbox: MailboxId,
|
|
is_move: bool,
|
|
is_uid: bool,
|
|
use_vanished: bool,
|
|
message_limit: u32,
|
|
op_start: Instant,
|
|
) -> trc::Result<()> {
|
|
self.synchronize_messages(&src_mailbox)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
// Convert IMAP ids to JMAP ids.
|
|
let ids = src_mailbox
|
|
.sequence_to_ids(&arguments.sequence_set, is_uid)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
if ids.is_empty() {
|
|
trc::event!(
|
|
Imap(if is_move {
|
|
trc::ImapEvent::Move
|
|
} else {
|
|
trc::ImapEvent::Copy
|
|
}),
|
|
SpanId = self.session_id,
|
|
Source = src_mailbox.id.account_id,
|
|
Details = trc::Value::None,
|
|
Uid = trc::Value::None,
|
|
AccountId = dest_mailbox.account_id,
|
|
MailboxId = dest_mailbox.mailbox_id,
|
|
Elapsed = op_start.elapsed()
|
|
);
|
|
|
|
return self
|
|
.write_bytes(
|
|
StatusResponse::ok(if is_move {
|
|
"No messages were moved."
|
|
} else {
|
|
"No messages were copied."
|
|
})
|
|
.with_tag(arguments.tag)
|
|
.into_bytes(),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
// Verify that the user can delete messages from the source mailbox.
|
|
if is_move
|
|
&& !self
|
|
.check_mailbox_acl(
|
|
src_mailbox.id.account_id,
|
|
src_mailbox.id.mailbox_id,
|
|
Acl::RemoveItems,
|
|
)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
{
|
|
return Err(trc::ImapEvent::Error
|
|
.into_err()
|
|
.details(concat!(
|
|
"You do not have the required permissions to ",
|
|
"remove messages from the source mailbox."
|
|
))
|
|
.code(ResponseCode::NoPerm)
|
|
.id(arguments.tag));
|
|
}
|
|
|
|
// Verify that the user can append messages to the destination mailbox.
|
|
let dest_mailbox_id = dest_mailbox.mailbox_id;
|
|
if !self
|
|
.check_mailbox_acl(dest_mailbox.account_id, dest_mailbox_id, Acl::AddItems)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
{
|
|
return Err(trc::ImapEvent::Error
|
|
.into_err()
|
|
.details(concat!(
|
|
"You do not have the required permissions to ",
|
|
"add messages to the destination mailbox."
|
|
))
|
|
.code(ResponseCode::NoPerm)
|
|
.id(arguments.tag));
|
|
}
|
|
|
|
// RFC 9738 requires the highest UIDs to be processed first when truncating.
|
|
// COPY is atomic, so it is refused outright rather than partially applied.
|
|
let mut ids = ids;
|
|
let message_limit = message_limit as usize;
|
|
let mut limited_uid = None;
|
|
if ids.len() > message_limit {
|
|
let mut uids = ids.values().map(|imap_id| imap_id.uid).collect::<Vec<_>>();
|
|
let cutoff = uids.len() - message_limit;
|
|
let lowest_uid = *uids.select_nth_unstable(cutoff).1;
|
|
|
|
if !is_move {
|
|
return self
|
|
.write_bytes(
|
|
StatusResponse::no("Too many messages to copy, try a smaller subset.")
|
|
.with_tag(arguments.tag)
|
|
.with_code(ResponseCode::MessageLimit {
|
|
limit: message_limit as u32,
|
|
uid: lowest_uid.into(),
|
|
})
|
|
.into_bytes(),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
ids.retain(|_, imap_id| imap_id.uid >= lowest_uid);
|
|
limited_uid = Some(lowest_uid);
|
|
}
|
|
|
|
let response = StatusResponse::completed(if is_move {
|
|
Command::Move(is_uid)
|
|
} else {
|
|
Command::Copy(is_uid)
|
|
});
|
|
let mut error: Option<(ResponseCode, &'static str)> = None;
|
|
let mut did_move = false;
|
|
let mut copied_ids = Vec::with_capacity(ids.len());
|
|
|
|
if src_mailbox.id.account_id == dest_mailbox.account_id {
|
|
// Mailboxes are in the same account
|
|
let account_id = src_mailbox.id.account_id;
|
|
let dest_mailbox_id = UidMailbox::new_unassigned(dest_mailbox_id);
|
|
let mut batch = BatchBuilder::new();
|
|
|
|
for (id, imap_id) in ids {
|
|
// Obtain mailbox tags
|
|
let data_ = if let Some(result) = self
|
|
.get_message_data(account_id, id)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
{
|
|
result
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
// Deserialize
|
|
let data = data_
|
|
.to_unarchived::<MessageData>()
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
// Make sure the message still belongs to this mailbox
|
|
if !data
|
|
.inner
|
|
.mailboxes
|
|
.iter()
|
|
.any(|mailbox| mailbox.mailbox_id == src_mailbox.id.mailbox_id)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the message is already in the destination mailbox, skip it.
|
|
if let Some(mailbox) = data
|
|
.inner
|
|
.mailboxes
|
|
.iter()
|
|
.find(|mailbox| mailbox.mailbox_id == dest_mailbox_id.mailbox_id)
|
|
{
|
|
copied_ids.push((imap_id.uid, mailbox.uid.to_native()));
|
|
|
|
if is_move {
|
|
let mut new_data = data.inner.to_builder();
|
|
new_data.remove_mailbox(src_mailbox.id.mailbox_id);
|
|
batch
|
|
.with_account_id(account_id)
|
|
.with_collection(Collection::Email)
|
|
.with_document(id)
|
|
.custom(
|
|
ObjectIndexBuilder::new()
|
|
.with_current(data)
|
|
.with_changes(new_data.seal()),
|
|
)
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
.log_vanished_item(
|
|
VanishedCollection::Email,
|
|
(src_mailbox.id.mailbox_id, imap_id.uid),
|
|
)
|
|
.commit_point();
|
|
did_move = true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Prepare changes
|
|
let mut new_data = data.inner.to_builder();
|
|
|
|
// Add destination folder
|
|
new_data.add_mailbox(dest_mailbox_id);
|
|
if is_move {
|
|
new_data.remove_mailbox(src_mailbox.id.mailbox_id);
|
|
}
|
|
|
|
// Assign IMAP UIDs
|
|
let ids = self
|
|
.server
|
|
.assign_email_ids(
|
|
account_id,
|
|
new_data
|
|
.mailboxes
|
|
.iter()
|
|
.filter(|m| m.uid == 0)
|
|
.map(|m| m.mailbox_id),
|
|
false,
|
|
)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
for (uid_mailbox, uid) in new_data
|
|
.mailboxes
|
|
.iter_mut()
|
|
.filter(|m| m.uid == 0)
|
|
.zip(ids)
|
|
{
|
|
copied_ids.push((imap_id.uid, uid));
|
|
uid_mailbox.uid = uid;
|
|
}
|
|
|
|
// Prepare write batch
|
|
batch
|
|
.with_account_id(account_id)
|
|
.with_collection(Collection::Email)
|
|
.with_document(id)
|
|
.custom(
|
|
ObjectIndexBuilder::new()
|
|
.with_current(data)
|
|
.with_changes(new_data.seal()),
|
|
)
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
if is_move {
|
|
batch.log_vanished_item(
|
|
VanishedCollection::Email,
|
|
(src_mailbox.id.mailbox_id, imap_id.uid),
|
|
);
|
|
}
|
|
|
|
// Add message to training queue
|
|
if dest_mailbox_id.mailbox_id == JUNK_ID {
|
|
self.server
|
|
.add_account_spam_sample(&mut batch, account_id, id, true, self.session_id)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
} else if src_mailbox.id.mailbox_id == JUNK_ID
|
|
&& dest_mailbox_id.mailbox_id != TRASH_ID
|
|
{
|
|
self.server
|
|
.add_account_spam_sample(&mut batch, account_id, id, false, self.session_id)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
}
|
|
|
|
batch.commit_point();
|
|
|
|
// Update changelog
|
|
if is_move {
|
|
did_move = true;
|
|
}
|
|
}
|
|
|
|
// Write changes
|
|
self.server
|
|
.commit_batch(batch)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
} else {
|
|
// Obtain quota for target account
|
|
let src_account_id = src_mailbox.id.account_id;
|
|
let mut dest_change_id = None;
|
|
let dest_account_id = dest_mailbox.account_id;
|
|
let mut destroy_ids = RoaringBitmap::new();
|
|
let cache = self
|
|
.server
|
|
.get_cached_messages(src_account_id)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
let mut dest_cache = None;
|
|
let train_spam = if dest_mailbox_id == JUNK_ID {
|
|
Some(true)
|
|
} else if src_mailbox.id.mailbox_id == JUNK_ID && dest_mailbox_id != TRASH_ID {
|
|
Some(false)
|
|
} else {
|
|
None
|
|
};
|
|
let mut train_batch = BatchBuilder::new();
|
|
let mut did_train = false;
|
|
train_batch.with_account_id(src_account_id);
|
|
for (id, imap_id) in ids {
|
|
match self
|
|
.server
|
|
.copy_message(
|
|
src_account_id,
|
|
id,
|
|
dest_account_id,
|
|
vec![dest_mailbox_id],
|
|
cache
|
|
.email_by_id(&id)
|
|
.map(|e| cache.expand_keywords(e).collect())
|
|
.unwrap_or_default(),
|
|
None,
|
|
self.session_id,
|
|
)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
{
|
|
Ok(email) => {
|
|
dest_change_id = email.change_id.into();
|
|
if let Some(assigned_uid) = email.imap_uids.first() {
|
|
debug_assert!(*assigned_uid > 0);
|
|
copied_ids.push((imap_id.uid, *assigned_uid));
|
|
}
|
|
}
|
|
Err(CopyMessageError::AlreadyExists(existing_id)) => {
|
|
if dest_cache.is_none() {
|
|
dest_cache = self
|
|
.server
|
|
.get_cached_messages(dest_account_id)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
.into();
|
|
}
|
|
|
|
if let Some(uid) = dest_cache
|
|
.as_ref()
|
|
.and_then(|cache| cache.email_by_id(&existing_id))
|
|
.and_then(|message| {
|
|
message
|
|
.mailboxes
|
|
.iter()
|
|
.find(|mailbox| mailbox.mailbox_id == dest_mailbox_id)
|
|
})
|
|
.map(|mailbox| mailbox.uid)
|
|
{
|
|
copied_ids.push((imap_id.uid, uid));
|
|
} else {
|
|
let data_ = if let Some(data_) = self
|
|
.get_message_data(dest_account_id, existing_id)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
{
|
|
data_
|
|
} else {
|
|
continue;
|
|
};
|
|
let data = data_
|
|
.to_unarchived::<MessageData>()
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
if let Some(uid) = data.inner.message_uid(dest_mailbox_id) {
|
|
copied_ids.push((imap_id.uid, uid));
|
|
} else {
|
|
let mut new_data = data.inner.to_builder();
|
|
new_data.add_mailbox(UidMailbox::new_unassigned(dest_mailbox_id));
|
|
|
|
let uids = self
|
|
.server
|
|
.assign_email_ids(
|
|
dest_account_id,
|
|
new_data
|
|
.mailboxes
|
|
.iter()
|
|
.filter(|m| m.uid == 0)
|
|
.map(|m| m.mailbox_id),
|
|
false,
|
|
)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
let mut assigned_uid = 0;
|
|
for (uid_mailbox, uid) in new_data
|
|
.mailboxes
|
|
.iter_mut()
|
|
.filter(|m| m.uid == 0)
|
|
.zip(uids)
|
|
{
|
|
uid_mailbox.uid = uid;
|
|
assigned_uid = uid;
|
|
}
|
|
|
|
let mut batch = BatchBuilder::new();
|
|
batch
|
|
.with_account_id(dest_account_id)
|
|
.with_collection(Collection::Email)
|
|
.with_document(existing_id)
|
|
.custom(
|
|
ObjectIndexBuilder::new()
|
|
.with_current(data)
|
|
.with_changes(new_data.seal()),
|
|
)
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
dest_change_id = self
|
|
.server
|
|
.commit_batch(batch)
|
|
.await
|
|
.and_then(|ids| ids.last_change_id(dest_account_id))
|
|
.imap_ctx(&arguments.tag, trc::location!())?
|
|
.into();
|
|
|
|
copied_ids.push((imap_id.uid, assigned_uid));
|
|
}
|
|
}
|
|
}
|
|
Err(CopyMessageError::OverQuota) => {
|
|
error = Some((ResponseCode::OverQuota, "Mailbox quota exceeded"));
|
|
continue;
|
|
}
|
|
Err(CopyMessageError::NotFound) => {
|
|
continue;
|
|
}
|
|
};
|
|
|
|
if let Some(is_spam) = train_spam {
|
|
self.server
|
|
.add_account_spam_sample(
|
|
&mut train_batch,
|
|
src_account_id,
|
|
id,
|
|
is_spam,
|
|
self.session_id,
|
|
)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
train_batch.commit_point();
|
|
did_train = true;
|
|
}
|
|
|
|
if is_move {
|
|
destroy_ids.insert(id);
|
|
}
|
|
}
|
|
|
|
if did_train {
|
|
self.server
|
|
.commit_batch(train_batch)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
}
|
|
|
|
// Untag or delete emails
|
|
if !destroy_ids.is_empty() {
|
|
let mut batch = BatchBuilder::new();
|
|
self.email_untag_or_delete(
|
|
src_account_id,
|
|
src_mailbox.id.mailbox_id,
|
|
&destroy_ids,
|
|
&mut batch,
|
|
)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
self.server
|
|
.commit_batch(batch)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
|
|
did_move = true;
|
|
}
|
|
|
|
// Broadcast changes on destination account
|
|
if let Some(change_id) = dest_change_id {
|
|
self.server
|
|
.broadcast_push_notification(PushNotification::StateChange(
|
|
StateChange::new(dest_account_id)
|
|
.with_change_id(change_id)
|
|
.with_change(DataType::Email)
|
|
.with_change(DataType::Thread)
|
|
.with_change(DataType::Mailbox),
|
|
))
|
|
.await;
|
|
}
|
|
}
|
|
|
|
// Map copied JMAP Ids to IMAP UIDs in the destination folder.
|
|
if copied_ids.is_empty() {
|
|
return if let Some((code, message)) = error {
|
|
Err(trc::ImapEvent::Error
|
|
.into_err()
|
|
.details(message)
|
|
.ctx(trc::Key::Code, code)
|
|
.id(arguments.tag))
|
|
} else {
|
|
trc::event!(
|
|
Imap(if is_move {
|
|
trc::ImapEvent::Move
|
|
} else {
|
|
trc::ImapEvent::Copy
|
|
}),
|
|
SpanId = self.session_id,
|
|
Source = src_mailbox.id.account_id,
|
|
Details = trc::Value::None,
|
|
Uid = trc::Value::None,
|
|
AccountId = dest_mailbox.account_id,
|
|
MailboxId = dest_mailbox.mailbox_id,
|
|
Elapsed = op_start.elapsed()
|
|
);
|
|
|
|
self.write_bytes(
|
|
StatusResponse::ok(if is_move {
|
|
"No messages were moved."
|
|
} else {
|
|
"No messages were copied."
|
|
})
|
|
.with_tag(arguments.tag)
|
|
.into_bytes(),
|
|
)
|
|
.await
|
|
};
|
|
}
|
|
|
|
// Prepare response
|
|
let uid_validity = self
|
|
.mailbox_state(&dest_mailbox)
|
|
.map(|m| m.uid_validity as u32)
|
|
.unwrap_or_default();
|
|
|
|
let mut src_uids = Vec::with_capacity(copied_ids.len());
|
|
let mut dest_uids = Vec::with_capacity(copied_ids.len());
|
|
for (src_uid, dest_uid) in copied_ids {
|
|
src_uids.push(src_uid);
|
|
dest_uids.push(dest_uid);
|
|
}
|
|
src_uids.sort_unstable();
|
|
dest_uids.sort_unstable();
|
|
|
|
trc::event!(
|
|
Imap(if is_move {
|
|
trc::ImapEvent::Move
|
|
} else {
|
|
trc::ImapEvent::Copy
|
|
}),
|
|
SpanId = self.session_id,
|
|
Source = src_mailbox.id.account_id,
|
|
Details = src_uids
|
|
.iter()
|
|
.map(|r| trc::Value::from(*r))
|
|
.collect::<Vec<_>>(),
|
|
AccountId = dest_mailbox.account_id,
|
|
MailboxId = dest_mailbox.mailbox_id,
|
|
Uid = dest_uids
|
|
.iter()
|
|
.map(|r| trc::Value::from(*r))
|
|
.collect::<Vec<_>>(),
|
|
Elapsed = op_start.elapsed()
|
|
);
|
|
|
|
let response = if is_move {
|
|
self.write_bytes(
|
|
StatusResponse::ok("Copied UIDs")
|
|
.with_code(ResponseCode::CopyUid {
|
|
uid_validity,
|
|
src_uids,
|
|
dest_uids,
|
|
})
|
|
.into_bytes(),
|
|
)
|
|
.await?;
|
|
|
|
if did_move {
|
|
// Resynchronize source mailbox on a successful move
|
|
self.write_mailbox_changes(&src_mailbox, use_vanished)
|
|
.await
|
|
.imap_ctx(&arguments.tag, trc::location!())?;
|
|
}
|
|
|
|
let response = response.with_tag(arguments.tag);
|
|
match limited_uid {
|
|
Some(uid) => response.with_code(ResponseCode::MessageLimit {
|
|
limit: message_limit as u32,
|
|
uid: uid.into(),
|
|
}),
|
|
None => response,
|
|
}
|
|
.into_bytes()
|
|
} else {
|
|
response
|
|
.with_tag(arguments.tag)
|
|
.with_code(ResponseCode::CopyUid {
|
|
uid_validity,
|
|
src_uids,
|
|
dest_uids,
|
|
})
|
|
.into_bytes()
|
|
};
|
|
|
|
self.write_bytes(response).await
|
|
}
|
|
|
|
pub async fn get_message_data(
|
|
&self,
|
|
account_id: u32,
|
|
id: u32,
|
|
) -> trc::Result<Option<Archive<AlignedBytes>>> {
|
|
if let Some(data) = self
|
|
.server
|
|
.store()
|
|
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
|
|
account_id,
|
|
Collection::Email,
|
|
id,
|
|
))
|
|
.await?
|
|
{
|
|
Ok(Some(data))
|
|
} else {
|
|
trc::event!(
|
|
Store(trc::StoreEvent::NotFound),
|
|
AccountId = account_id,
|
|
Collection = Collection::Email,
|
|
MessageId = id,
|
|
SpanId = self.session_id,
|
|
Details = "Message not found"
|
|
);
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|