Bug 1914442 - Support copying messages from file for EWS. r=leftmostcat
Differential Revision: https://phabricator.services.mozilla.com/D219902 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
8af40f1b9e
Коммит
85def2b630
|
@ -4,11 +4,15 @@
|
|||
|
||||
#include "EwsFolder.h"
|
||||
|
||||
#include "ErrorList.h"
|
||||
#include "IEwsClient.h"
|
||||
#include "IEwsIncomingServer.h"
|
||||
#include "MailNewsTypes.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIMsgWindow.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nscore.h"
|
||||
|
||||
#define kEWSRootURI "ews:/"
|
||||
#define kEWSMessageRootURI "ews-message:/"
|
||||
|
@ -16,25 +20,25 @@
|
|||
#define ID_PROPERTY "ewsId"
|
||||
#define SYNC_STATE_PROPERTY "ewsSyncStateToken"
|
||||
|
||||
class MessageSyncListener : public IEwsMessageCallbacks {
|
||||
class MessageOperationCallbacks : public IEwsMessageCallbacks {
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_IEWSMESSAGECALLBACKS
|
||||
|
||||
MessageSyncListener(EwsFolder* folder, nsIMsgWindow* window)
|
||||
MessageOperationCallbacks(EwsFolder* folder, nsIMsgWindow* window)
|
||||
: mFolder(folder), mWindow(window) {}
|
||||
|
||||
protected:
|
||||
virtual ~MessageSyncListener() = default;
|
||||
virtual ~MessageOperationCallbacks() = default;
|
||||
|
||||
private:
|
||||
RefPtr<EwsFolder> mFolder;
|
||||
RefPtr<nsIMsgWindow> mWindow;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(MessageSyncListener, IEwsMessageCallbacks)
|
||||
NS_IMPL_ISUPPORTS(MessageOperationCallbacks, IEwsMessageCallbacks)
|
||||
|
||||
NS_IMETHODIMP MessageSyncListener::CommitHeader(nsIMsgDBHdr* hdr) {
|
||||
NS_IMETHODIMP MessageOperationCallbacks::CommitHeader(nsIMsgDBHdr* hdr) {
|
||||
RefPtr<nsIMsgDatabase> db;
|
||||
nsresult rv = mFolder->GetMsgDatabase(getter_AddRefs(db));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -42,7 +46,7 @@ NS_IMETHODIMP MessageSyncListener::CommitHeader(nsIMsgDBHdr* hdr) {
|
|||
return db->AddNewHdrToDB(hdr, true);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP MessageSyncListener::CreateNewHeaderForItem(
|
||||
NS_IMETHODIMP MessageOperationCallbacks::CreateNewHeaderForItem(
|
||||
const nsACString& ewsId, nsIMsgDBHdr** _retval) {
|
||||
RefPtr<nsIMsgDatabase> db;
|
||||
nsresult rv = mFolder->GetMsgDatabase(getter_AddRefs(db));
|
||||
|
@ -69,13 +73,13 @@ NS_IMETHODIMP MessageSyncListener::CreateNewHeaderForItem(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP MessageSyncListener::UpdateSyncState(
|
||||
NS_IMETHODIMP MessageOperationCallbacks::UpdateSyncState(
|
||||
const nsACString& syncStateToken) {
|
||||
return mFolder->SetStringProperty(SYNC_STATE_PROPERTY, syncStateToken);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP MessageSyncListener::OnError(IEwsClient::Error err,
|
||||
const nsACString& desc) {
|
||||
NS_IMETHODIMP MessageOperationCallbacks::OnError(IEwsClient::Error err,
|
||||
const nsACString& desc) {
|
||||
NS_ERROR("Error occurred while syncing EWS messages");
|
||||
|
||||
return NS_OK;
|
||||
|
@ -218,18 +222,49 @@ NS_IMETHODIMP EwsFolder::RenameSubFolders(nsIMsgWindow* msgWindow,
|
|||
}
|
||||
|
||||
NS_IMETHODIMP EwsFolder::UpdateFolder(nsIMsgWindow* aWindow) {
|
||||
nsCOMPtr<nsIMsgIncomingServer> server;
|
||||
nsresult rv = GetServer(getter_AddRefs(server));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<IEwsIncomingServer> ewsServer(do_QueryInterface(server));
|
||||
|
||||
nsCOMPtr<IEwsClient> client;
|
||||
rv = ewsServer->GetEwsClient(getter_AddRefs(client));
|
||||
nsresult rv = GetEwsClient(getter_AddRefs(client));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCString ewsId;
|
||||
rv = GetStringProperty(ID_PROPERTY, ewsId);
|
||||
rv = GetEwsId(ewsId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// EWS provides us an opaque value which specifies the last version of
|
||||
// upstream messages we received. Provide that to simplify sync.
|
||||
nsCString syncStateToken;
|
||||
rv = GetStringProperty(SYNC_STATE_PROPERTY, syncStateToken);
|
||||
if (NS_FAILED(rv)) {
|
||||
syncStateToken = EmptyCString();
|
||||
}
|
||||
|
||||
auto listener = RefPtr(new MessageOperationCallbacks(this, aWindow));
|
||||
return client->SyncMessagesForFolder(listener, ewsId, syncStateToken);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP EwsFolder::CopyFileMessage(
|
||||
nsIFile* aFile, nsIMsgDBHdr* msgToReplace, bool isDraftOrTemplate,
|
||||
uint32_t newMsgFlags, const nsACString& aNewMsgKeywords,
|
||||
nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* copyListener) {
|
||||
nsCString ewsId;
|
||||
nsresult rv = GetEwsId(ewsId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<IEwsClient> client;
|
||||
rv = GetEwsClient(getter_AddRefs(client));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
auto ewsMsgListener = RefPtr(new MessageOperationCallbacks(this, msgWindow));
|
||||
return client->SaveMessage(ewsId, isDraftOrTemplate, inputStream,
|
||||
copyListener, ewsMsgListener);
|
||||
}
|
||||
|
||||
nsresult EwsFolder::GetEwsId(nsACString& ewsId) {
|
||||
nsresult rv = GetStringProperty(ID_PROPERTY, ewsId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (ewsId.IsEmpty()) {
|
||||
|
@ -240,14 +275,15 @@ NS_IMETHODIMP EwsFolder::UpdateFolder(nsIMsgWindow* aWindow) {
|
|||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// EWS provides us an opaque value which specifies the last version of
|
||||
// upstream messages we received. Provide that to simplify sync.
|
||||
nsCString syncStateToken;
|
||||
rv = GetStringProperty(SYNC_STATE_PROPERTY, syncStateToken);
|
||||
if (NS_FAILED(rv)) {
|
||||
syncStateToken = EmptyCString();
|
||||
}
|
||||
|
||||
auto listener = RefPtr(new MessageSyncListener(this, aWindow));
|
||||
return client->SyncMessagesForFolder(listener, ewsId, syncStateToken);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult EwsFolder::GetEwsClient(IEwsClient** ewsClient) {
|
||||
nsCOMPtr<nsIMsgIncomingServer> server;
|
||||
nsresult rv = GetServer(getter_AddRefs(server));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<IEwsIncomingServer> ewsServer(do_QueryInterface(server));
|
||||
|
||||
return ewsServer->GetEwsClient(ewsClient);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#ifndef __COMM_MAILNEWS_PROTOCOLS_EWS_FOLDER_H
|
||||
#define __COMM_MAILNEWS_PROTOCOLS_EWS_FOLDER_H
|
||||
|
||||
#include "IEwsClient.h"
|
||||
#include "nsMsgDBFolder.h"
|
||||
#include "nscore.h"
|
||||
|
||||
class EwsFolder : public nsMsgDBFolder {
|
||||
public:
|
||||
|
@ -22,6 +24,11 @@ class EwsFolder : public nsMsgDBFolder {
|
|||
NS_IMETHOD CreateStorageIfMissing(nsIUrlListener* urlListener) override;
|
||||
NS_IMETHOD CreateSubfolder(const nsAString& folderName,
|
||||
nsIMsgWindow* msgWindow) override;
|
||||
NS_IMETHOD CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* msgToReplace,
|
||||
bool isDraftOrTemplate, uint32_t newMsgFlags,
|
||||
const nsACString& aNewMsgKeywords,
|
||||
nsIMsgWindow* msgWindow,
|
||||
nsIMsgCopyServiceListener* listener) override;
|
||||
NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
|
||||
nsIMsgDatabase** _retval) override;
|
||||
|
||||
|
@ -37,6 +44,13 @@ class EwsFolder : public nsMsgDBFolder {
|
|||
|
||||
private:
|
||||
bool mHasLoadedSubfolders;
|
||||
|
||||
// Generate or retrieve an EWS API client capable of interacting with the EWS
|
||||
// server this folder depends from.
|
||||
nsresult GetEwsClient(IEwsClient** ewsClient);
|
||||
|
||||
// Locally look up the EWS ID for the current folder.
|
||||
nsresult GetEwsId(nsACString& ewsId);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -8,6 +8,8 @@ interface nsIMsgDBHdr;
|
|||
interface nsIRequest;
|
||||
interface nsIStreamListener;
|
||||
interface nsIMsgIncomingServer;
|
||||
interface nsIInputStream;
|
||||
interface nsIMsgCopyServiceListener;
|
||||
|
||||
interface IEwsFolderCallbacks;
|
||||
interface IEwsMessageCallbacks;
|
||||
|
@ -25,6 +27,25 @@ interface IEwsClient : nsISupports
|
|||
void syncFolderHierarchy(in IEwsFolderCallbacks callbacks, in AUTF8String syncStateToken);
|
||||
void syncMessagesForFolder(in IEwsMessageCallbacks callbacks, in AUTF8String folderId, in AUTF8String syncStateToken);
|
||||
void getMessage(in AUTF8String id, in nsIRequest request, in nsIStreamListener listener);
|
||||
|
||||
/**
|
||||
* Create a new message on the server using the data read from the stream.
|
||||
*
|
||||
* @param folderId The EWS ID of the folder.
|
||||
* @param isDraft Whether the message being created is an unsent
|
||||
* draft.
|
||||
* @param messageStream The input stream to read the message from.
|
||||
* @param copyListener A listener to provide updates on copying of
|
||||
* the message from the stream.
|
||||
* @param messageCallbacks Callbacks to use to communicate between the EWS
|
||||
* client and the EWS folder (e.g. to access its
|
||||
* database).
|
||||
*/
|
||||
void saveMessage(in AUTF8String folderId,
|
||||
in boolean isDraft,
|
||||
in nsIInputStream messageStream,
|
||||
in nsIMsgCopyServiceListener copyListener,
|
||||
in IEwsMessageCallbacks messageCallbacks);
|
||||
};
|
||||
|
||||
[uuid(5dacc994-30e0-42f7-94c8-52756638add5)]
|
||||
|
|
|
@ -96,9 +96,9 @@ git = "https://github.com/servo/unicode-bidi"
|
|||
rev = "ca612daf1c08c53abe07327cb3e6ef6e0a760f0c"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/thunderbird/ews-rs.git?rev=c080aad0b37b6f4f0ba74165f4605e70f5740641"]
|
||||
[source."git+https://github.com/thunderbird/ews-rs.git?rev=9a54b74bb655f91c9f629437b253c448595ce8d2"]
|
||||
git = "https://github.com/thunderbird/ews-rs.git"
|
||||
rev = "c080aad0b37b6f4f0ba74165f4605e70f5740641"
|
||||
rev = "9a54b74bb655f91c9f629437b253c448595ce8d2"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/thunderbird/xml-struct-rs.git?rev=87723b90425d474fd29095d8b710baefd7c9b13a"]
|
||||
|
|
|
@ -1605,7 +1605,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ews"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/thunderbird/ews-rs.git?rev=c080aad0b37b6f4f0ba74165f4605e70f5740641#c080aad0b37b6f4f0ba74165f4605e70f5740641"
|
||||
source = "git+https://github.com/thunderbird/ews-rs.git?rev=9a54b74bb655f91c9f629437b253c448595ce8d2#9a54b74bb655f91c9f629437b253c448595ce8d2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"quick-xml",
|
||||
|
@ -1626,6 +1626,7 @@ dependencies = [
|
|||
"fxhash",
|
||||
"log",
|
||||
"mail-builder",
|
||||
"mail-parser",
|
||||
"moz_http",
|
||||
"moz_task",
|
||||
"nserror",
|
||||
|
@ -3174,6 +3175,15 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f5871d5270ed80f2ee750b95600c8d69b05f8653ad3be913b2ad2e924fefcb"
|
||||
|
||||
[[package]]
|
||||
name = "mail-parser"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93c3b9e5d8b17faf573330bbc43b37d6e918c0a3bf8a88e7d0a220ebc84af9fc"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
|
|
|
@ -6,9 +6,10 @@ edition = "2021"
|
|||
[dependencies]
|
||||
base64 = "0.21.3"
|
||||
cstr = "0.2"
|
||||
ews = { git = "https://github.com/thunderbird/ews-rs.git", rev = "c080aad0b37b6f4f0ba74165f4605e70f5740641", version = "0.1.0" }
|
||||
ews = { git = "https://github.com/thunderbird/ews-rs.git", rev = "9a54b74bb655f91c9f629437b253c448595ce8d2", version = "0.1.0" }
|
||||
fxhash = "0.2.1"
|
||||
log = "0.4.21"
|
||||
mail-parser = "0.9.3"
|
||||
moz_http = { version = "0.1.0", path = "../moz_http" }
|
||||
moz_task = { version = "0.1.0", path = "../../../xpcom/rust/moz_task" }
|
||||
nserror = { version = "0.1.0", path = "../../../xpcom/rust/nserror" }
|
||||
|
|
|
@ -9,17 +9,20 @@ use std::{
|
|||
|
||||
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||
use ews::{
|
||||
create_item::{self, CreateItem, MessageDisposition},
|
||||
create_item::{
|
||||
self, CreateItem, CreateItemResponseMessage, ExtendedProperty, MessageDisposition,
|
||||
},
|
||||
get_folder::GetFolder,
|
||||
get_item::GetItem,
|
||||
soap,
|
||||
sync_folder_hierarchy::{self, SyncFolderHierarchy},
|
||||
sync_folder_items::{self, SyncFolderItems},
|
||||
ArrayOfRecipients, BaseFolderId, BaseItemId, BaseShape, Folder, FolderShape, Importance,
|
||||
ItemShape, Message, MimeContent, Operation, PathToElement, RealItem, Recipient, ResponseClass,
|
||||
ArrayOfRecipients, BaseFolderId, BaseItemId, BaseShape, ExtendedFieldURI, Folder, FolderShape,
|
||||
ItemShape, MimeContent, Operation, PathToElement, RealItem, Recipient, ResponseClass,
|
||||
ResponseCode,
|
||||
};
|
||||
use fxhash::FxHashMap;
|
||||
use mail_parser::MessageParser;
|
||||
use moz_http::StatusCode;
|
||||
use nserror::nsresult;
|
||||
use nsstring::{nsCString, nsString};
|
||||
|
@ -29,15 +32,30 @@ use uuid::Uuid;
|
|||
use xpcom::{
|
||||
getter_addrefs,
|
||||
interfaces::{
|
||||
nsIMsgDBHdr, nsIRequest, nsIRequestObserver, nsIStreamListener, nsIStringInputStream,
|
||||
nsMsgFolderFlagType, nsMsgFolderFlags, nsMsgMessageFlags, nsMsgPriority, IEwsClient,
|
||||
IEwsFolderCallbacks, IEwsMessageCallbacks,
|
||||
nsIMsgCopyServiceListener, nsIMsgDBHdr, nsIRequest, nsIRequestObserver, nsIStreamListener,
|
||||
nsIStringInputStream, nsMsgFolderFlagType, nsMsgFolderFlags, nsMsgKey, nsMsgMessageFlags,
|
||||
IEwsClient, IEwsFolderCallbacks, IEwsMessageCallbacks,
|
||||
},
|
||||
RefPtr,
|
||||
};
|
||||
|
||||
use crate::headers::MessageHeaders;
|
||||
use crate::{authentication::credentials::Credentials, cancellable_request::CancellableRequest};
|
||||
|
||||
// Flags to use for setting the `PR_MESSAGE_FLAGS` MAPI property.
|
||||
//
|
||||
// See
|
||||
// <https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/pidtagmessageflags-canonical-property>,
|
||||
// although the specific values are set in `Mapidefs.h` from the Windows SDK:
|
||||
// <https://github.com/microsoft/MAPIStubLibrary/blob/1d30c31ebf05ef444371520cd4268d6e1fda8a3b/include/MAPIDefS.h#L2143-L2154>
|
||||
//
|
||||
// Message flags are of type `PT_LONG`, which corresponds to i32 (signed 32-bit
|
||||
// integers) according to
|
||||
// https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/property-types
|
||||
const MSGFLAG_READ: i32 = 0x00000001;
|
||||
const MSGFLAG_UNMODIFIED: i32 = 0x00000002;
|
||||
const MSGFLAG_UNSENT: i32 = 0x00000008;
|
||||
|
||||
pub(crate) struct XpComEwsClient {
|
||||
pub endpoint: Url,
|
||||
pub credentials: Credentials,
|
||||
|
@ -300,7 +318,7 @@ impl XpComEwsClient {
|
|||
}
|
||||
|
||||
let header = result?;
|
||||
populate_message_header_from_item(&header, &msg)?;
|
||||
populate_message_header_from_item(&header, msg)?;
|
||||
|
||||
unsafe { callbacks.CommitHeader(&*header) }.to_result()?;
|
||||
}
|
||||
|
@ -757,9 +775,9 @@ impl XpComEwsClient {
|
|||
for response_message in response.response_messages.get_item_response_message {
|
||||
process_response_message_class(
|
||||
"GetItem",
|
||||
response_message.response_class,
|
||||
response_message.response_code,
|
||||
response_message.message_text,
|
||||
&response_message.response_class,
|
||||
&response_message.response_code,
|
||||
&response_message.message_text,
|
||||
)?;
|
||||
|
||||
// The expected shape of the list of response messages is
|
||||
|
@ -817,7 +835,7 @@ impl XpComEwsClient {
|
|||
Err(err) => {
|
||||
log::error!("an error occurred while attempting to send the message: {err:?}");
|
||||
|
||||
nsresult::from(err)
|
||||
err.into()
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -830,7 +848,7 @@ impl XpComEwsClient {
|
|||
}
|
||||
|
||||
async fn send_message_inner(
|
||||
&self,
|
||||
self,
|
||||
mime_content: String,
|
||||
message_id: String,
|
||||
should_request_dsn: bool,
|
||||
|
@ -847,7 +865,7 @@ impl XpComEwsClient {
|
|||
let message = create_item::Message {
|
||||
mime_content: Some(MimeContent {
|
||||
character_set: None,
|
||||
content: BASE64_STANDARD.encode(mime_content),
|
||||
content: BASE64_STANDARD.encode(&mime_content),
|
||||
}),
|
||||
is_delivery_receipt_requested: Some(should_request_dsn),
|
||||
internet_message_id: Some(message_id),
|
||||
|
@ -860,12 +878,143 @@ impl XpComEwsClient {
|
|||
|
||||
// We don't need EWS to copy messages to the Sent folder after
|
||||
// they've been sent, because the internal MessageSend module
|
||||
// already takes care of it, and will include additional headers we
|
||||
// already takes care of it and will include additional headers we
|
||||
// don't send to EWS (such as Bcc).
|
||||
message_disposition: MessageDisposition::SendOnly,
|
||||
message_disposition: Some(MessageDisposition::SendOnly),
|
||||
saved_item_folder_id: None,
|
||||
};
|
||||
|
||||
self.make_create_item_request(create_item).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a message on the server by performing a [`CreateItem`] operation
|
||||
/// via EWS.
|
||||
///
|
||||
/// All headers are expected to be included in the provided MIME content.
|
||||
///
|
||||
/// [`CreateItem`] https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/createitem-operation-email-message
|
||||
pub async fn save_message(
|
||||
self,
|
||||
folder_id: String,
|
||||
is_draft: bool,
|
||||
content: Vec<u8>,
|
||||
copy_listener: RefPtr<nsIMsgCopyServiceListener>,
|
||||
message_callbacks: RefPtr<IEwsMessageCallbacks>,
|
||||
) {
|
||||
if let Err(status) = unsafe { copy_listener.OnStartCopy().to_result() } {
|
||||
log::error!("aborting copy: an error occurred while starting the listener: {status}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the request, using an inner method to more easily handle errors.
|
||||
// Use the return value to determine which status we should use when
|
||||
// notifying the end of the request.
|
||||
let status = match self
|
||||
.save_message_inner(
|
||||
folder_id,
|
||||
is_draft,
|
||||
content,
|
||||
copy_listener.clone(),
|
||||
message_callbacks,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => nserror::NS_OK,
|
||||
Err(err) => {
|
||||
log::error!("an error occurred while attempting to copy the message: {err:?}");
|
||||
|
||||
err.into()
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = unsafe { copy_listener.OnStopCopy(status) }.to_result() {
|
||||
log::error!("aborting copy: an error occurred while stopping the listener: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_message_inner(
|
||||
&self,
|
||||
folder_id: String,
|
||||
is_draft: bool,
|
||||
content: Vec<u8>,
|
||||
copy_listener: RefPtr<nsIMsgCopyServiceListener>,
|
||||
message_callbacks: RefPtr<IEwsMessageCallbacks>,
|
||||
) -> Result<(), XpComEwsError> {
|
||||
// Create a new message from the binary content we got.
|
||||
let mut message = create_item::Message {
|
||||
mime_content: Some(MimeContent {
|
||||
character_set: None,
|
||||
content: BASE64_STANDARD.encode(&content),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Set the `PR_MESSAGE_FLAGS` MAPI property. If not set, the EWS server
|
||||
// uses `MSGFLAG_UNSENT` | `MSGFLAG_UNMODIFIED` as the default value,
|
||||
// which is not what we want.
|
||||
//
|
||||
// See
|
||||
// https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/pidtagmessageflags-canonical-property
|
||||
let mut mapi_flags = MSGFLAG_READ;
|
||||
if is_draft {
|
||||
mapi_flags |= MSGFLAG_UNSENT;
|
||||
} else {
|
||||
mapi_flags |= MSGFLAG_UNMODIFIED;
|
||||
}
|
||||
|
||||
message.extended_property = Some(vec![ExtendedProperty {
|
||||
extended_field_URI: ExtendedFieldURI {
|
||||
distinguished_property_set_id: None,
|
||||
property_set_id: None,
|
||||
property_name: None,
|
||||
property_id: None,
|
||||
|
||||
// 3591 (0x0E07) is the `PR_MESSAGE_FLAGS` MAPI property.
|
||||
property_tag: Some("3591".into()),
|
||||
property_type: ews::PropertyType::Integer,
|
||||
},
|
||||
value: mapi_flags.to_string(),
|
||||
}]);
|
||||
|
||||
let create_item = CreateItem {
|
||||
items: vec![create_item::Item::Message(message)],
|
||||
message_disposition: Some(MessageDisposition::SaveOnly),
|
||||
saved_item_folder_id: Some(BaseFolderId::FolderId {
|
||||
id: folder_id,
|
||||
change_key: None,
|
||||
}),
|
||||
};
|
||||
|
||||
let response_message = self.make_create_item_request(create_item).await?;
|
||||
|
||||
let hdr = create_and_populate_header_from_save_response(
|
||||
response_message,
|
||||
&content,
|
||||
message_callbacks,
|
||||
)?;
|
||||
|
||||
if is_draft {
|
||||
// If we're dealing with a draft message, copy the message key to
|
||||
// the listener so that the draft can be replaced if a newer draft
|
||||
// of the message is saved.
|
||||
let mut key: nsMsgKey = 0;
|
||||
|
||||
unsafe { hdr.GetMessageKey(&mut key) }.to_result()?;
|
||||
unsafe { copy_listener.SetMessageKey(key) }.to_result()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs a [`CreateItem`] operation and processes its response.
|
||||
///
|
||||
/// [`CreateItem`] https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/createitem-operation-email-message
|
||||
async fn make_create_item_request(
|
||||
&self,
|
||||
create_item: CreateItem,
|
||||
) -> Result<CreateItemResponseMessage, XpComEwsError> {
|
||||
let response = self.make_operation_request(create_item).await?;
|
||||
|
||||
// We have only sent one message, therefore the response should only
|
||||
|
@ -882,10 +1031,12 @@ impl XpComEwsClient {
|
|||
let response_message = response_messages.into_iter().next().unwrap();
|
||||
process_response_message_class(
|
||||
"CreateItem",
|
||||
response_message.response_class,
|
||||
response_message.response_code,
|
||||
response_message.message_text,
|
||||
)
|
||||
&response_message.response_class,
|
||||
&response_message.response_code,
|
||||
&response_message.message_text,
|
||||
)?;
|
||||
|
||||
Ok(response_message)
|
||||
}
|
||||
|
||||
/// Makes a request to the EWS endpoint to perform an operation.
|
||||
|
@ -954,9 +1105,9 @@ impl XpComEwsClient {
|
|||
/// Sets the fields of a database message header object from an EWS `Message`.
|
||||
fn populate_message_header_from_item(
|
||||
header: &nsIMsgDBHdr,
|
||||
msg: &Message,
|
||||
msg: impl MessageHeaders,
|
||||
) -> Result<(), XpComEwsError> {
|
||||
let internet_message_id = if let Some(internet_message_id) = msg.internet_message_id.as_ref() {
|
||||
let internet_message_id = if let Some(internet_message_id) = msg.internet_message_id() {
|
||||
nsCString::from(internet_message_id)
|
||||
} else {
|
||||
// Lots of code assumes Message-ID is set and unique, so we need to
|
||||
|
@ -976,14 +1127,14 @@ fn populate_message_header_from_item(
|
|||
let mut header_flags = 0;
|
||||
unsafe { header.GetFlags(&mut header_flags) }.to_result()?;
|
||||
|
||||
if let Some(is_read) = msg.is_read {
|
||||
if let Some(is_read) = msg.is_read() {
|
||||
if is_read {
|
||||
should_write_flags = true;
|
||||
header_flags |= nsMsgMessageFlags::Read;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(has_attachments) = msg.has_attachments {
|
||||
if let Some(has_attachments) = msg.has_attachments() {
|
||||
if has_attachments {
|
||||
should_write_flags = true;
|
||||
header_flags |= nsMsgMessageFlags::Attachment;
|
||||
|
@ -994,64 +1145,42 @@ fn populate_message_header_from_item(
|
|||
unsafe { header.SetFlags(header_flags) }.to_result()?;
|
||||
}
|
||||
|
||||
let sent_time_in_micros = msg.date_time_sent.as_ref().and_then(|date_time| {
|
||||
// `time` gives Unix timestamps in seconds. `PRTime` is an `i64`
|
||||
// representing Unix timestamps in microseconds. `PRTime` won't overflow
|
||||
// for over 500,000 years, but we use `checked_mul()` to guard against
|
||||
// receiving nonsensical values.
|
||||
let time_in_micros = date_time.0.unix_timestamp().checked_mul(1_000 * 1_000);
|
||||
if time_in_micros.is_none() {
|
||||
log::warn!(
|
||||
"message with ID {item_id} sent date {date_time:?} too big for `i64`, ignoring",
|
||||
item_id = msg.item_id.id
|
||||
);
|
||||
}
|
||||
|
||||
time_in_micros
|
||||
});
|
||||
|
||||
if let Some(sent) = sent_time_in_micros {
|
||||
if let Some(sent) = msg.sent_timestamp_ms() {
|
||||
unsafe { header.SetDate(sent) }.to_result()?;
|
||||
}
|
||||
|
||||
if let Some(author) = msg.from.as_ref().or(msg.sender.as_ref()) {
|
||||
let author = nsCString::from(make_header_string_for_mailbox(&author.mailbox));
|
||||
if let Some(author) = msg.author() {
|
||||
let author = nsCString::from(make_header_string_for_mailbox(&author));
|
||||
unsafe { header.SetAuthor(&*author) }.to_result()?;
|
||||
}
|
||||
|
||||
if let Some(reply_to) = msg.reply_to.as_ref() {
|
||||
let reply_to = nsCString::from(make_header_string_for_mailbox(&reply_to.mailbox));
|
||||
if let Some(reply_to) = msg.reply_to_recipient() {
|
||||
let reply_to = nsCString::from(make_header_string_for_mailbox(&reply_to));
|
||||
unsafe { header.SetStringProperty(cstr::cstr!("replyTo").as_ptr(), &*reply_to) }
|
||||
.to_result()?;
|
||||
}
|
||||
|
||||
if let Some(to) = msg.to_recipients.as_ref() {
|
||||
if let Some(to) = msg.to_recipients() {
|
||||
let to = nsCString::from(make_header_string_for_mailbox_list(to));
|
||||
unsafe { header.SetRecipients(&*to) }.to_result()?;
|
||||
}
|
||||
|
||||
if let Some(cc) = msg.cc_recipients.as_ref() {
|
||||
if let Some(cc) = msg.cc_recipients() {
|
||||
let cc = nsCString::from(make_header_string_for_mailbox_list(cc));
|
||||
unsafe { header.SetCcList(&*cc) }.to_result()?;
|
||||
}
|
||||
|
||||
if let Some(bcc) = msg.bcc_recipients.as_ref() {
|
||||
if let Some(bcc) = msg.bcc_recipients() {
|
||||
let bcc = nsCString::from(make_header_string_for_mailbox_list(bcc));
|
||||
unsafe { header.SetBccList(&*bcc) }.to_result()?;
|
||||
}
|
||||
|
||||
if let Some(subject) = msg.subject.as_ref() {
|
||||
if let Some(subject) = msg.message_subject() {
|
||||
let subject = nsCString::from(subject);
|
||||
unsafe { header.SetSubject(&*subject) }.to_result()?;
|
||||
}
|
||||
|
||||
if let Some(importance) = msg.importance {
|
||||
let priority = match importance {
|
||||
Importance::Low => nsMsgPriority::low,
|
||||
Importance::Normal => nsMsgPriority::normal,
|
||||
Importance::High => nsMsgPriority::high,
|
||||
};
|
||||
|
||||
if let Some(priority) = msg.priority() {
|
||||
unsafe { header.SetPriority(priority) }.to_result()?;
|
||||
}
|
||||
|
||||
|
@ -1084,10 +1213,12 @@ fn maybe_get_backoff_delay_ms(err: &ews::Error) -> Option<u32> {
|
|||
|
||||
/// Creates a string representation of a list of mailboxes, suitable for use as
|
||||
/// the value of an Internet Message Format header.
|
||||
fn make_header_string_for_mailbox_list(mailboxes: &ArrayOfRecipients) -> String {
|
||||
fn make_header_string_for_mailbox_list(
|
||||
mailboxes: impl IntoIterator<Item = ews::Mailbox>,
|
||||
) -> String {
|
||||
let strings: Vec<_> = mailboxes
|
||||
.iter()
|
||||
.map(|item| make_header_string_for_mailbox(&item.mailbox))
|
||||
.into_iter()
|
||||
.map(|mailbox| make_header_string_for_mailbox(&mailbox))
|
||||
.collect();
|
||||
|
||||
strings.join(", ")
|
||||
|
@ -1203,9 +1334,9 @@ where
|
|||
/// return an error accordingly.
|
||||
fn process_response_message_class(
|
||||
op_name: &str,
|
||||
response_class: ResponseClass,
|
||||
response_code: Option<ResponseCode>,
|
||||
message_text: Option<String>,
|
||||
response_class: &ResponseClass,
|
||||
response_code: &Option<ResponseCode>,
|
||||
message_text: &Option<String>,
|
||||
) -> Result<(), XpComEwsError> {
|
||||
match response_class {
|
||||
ResponseClass::Success => Ok(()),
|
||||
|
@ -1245,3 +1376,44 @@ fn process_response_message_class(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses the provided `CreateItemResponseMessage` to create, populate and commit
|
||||
/// an `nsIMsgDBHdr` for a newly created message.
|
||||
fn create_and_populate_header_from_save_response(
|
||||
response_message: CreateItemResponseMessage,
|
||||
content: &[u8],
|
||||
message_callbacks: RefPtr<IEwsMessageCallbacks>,
|
||||
) -> Result<RefPtr<nsIMsgDBHdr>, XpComEwsError> {
|
||||
// If we're saving the message (rather than sending it), we must create a
|
||||
// new database entry for it and associate it with the message's EWS ID.
|
||||
let items = &response_message.items.inner;
|
||||
if items.len() != 1 {
|
||||
return Err(XpComEwsError::Processing {
|
||||
message: String::from("expected only one item in CreateItem response"),
|
||||
});
|
||||
}
|
||||
|
||||
let message = match &items[0] {
|
||||
RealItem::Message(message) => message,
|
||||
};
|
||||
let ews_id = nsCString::from(message.item_id.id.clone());
|
||||
|
||||
let hdr =
|
||||
getter_addrefs(|hdr| unsafe { message_callbacks.CreateNewHeaderForItem(&*ews_id, hdr) })?;
|
||||
|
||||
// Parse the message and use its headers to populate the `nsIMsgDBHdr`
|
||||
// before committing it to the database. We parse the original content
|
||||
// rather than use the `Message` from the `CreateItemResponse` because the
|
||||
// latter only contains the item's ID, and so is missing the required
|
||||
// fields.
|
||||
let message = MessageParser::default()
|
||||
.parse(content)
|
||||
.ok_or(XpComEwsError::Processing {
|
||||
message: String::from("failed to parse message"),
|
||||
})?;
|
||||
|
||||
populate_message_header_from_item(&hdr, &message)?;
|
||||
unsafe { message_callbacks.CommitHeader(&*hdr) }.to_result()?;
|
||||
|
||||
Ok(hdr)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::iter::IntoIterator;
|
||||
|
||||
use xpcom::interfaces::{nsMsgPriority, nsMsgPriorityValue};
|
||||
|
||||
/// A message from which email headers can be retrieved.
|
||||
pub(crate) trait MessageHeaders {
|
||||
/// The value of the `Message-ID` header for this message.
|
||||
fn internet_message_id(&self) -> Option<String>;
|
||||
|
||||
/// Whether the message has already been read.
|
||||
fn is_read(&self) -> Option<bool>;
|
||||
|
||||
/// Whether the message has any attachment.
|
||||
fn has_attachments(&self) -> Option<bool>;
|
||||
|
||||
/// The time the message was sent, as a Unix timestamp converted to
|
||||
/// milliseconds.
|
||||
fn sent_timestamp_ms(&self) -> Option<i64>;
|
||||
|
||||
/// The author for this message. This can be the value of either the `From`
|
||||
/// or `Sender` header (in order of preference).
|
||||
fn author(&self) -> Option<ews::Mailbox>;
|
||||
|
||||
/// The `Reply-To` header for this message.
|
||||
fn reply_to_recipient(&self) -> Option<ews::Mailbox>;
|
||||
|
||||
/// The `To` header for this message.
|
||||
fn to_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>>;
|
||||
|
||||
/// The `Cc` header for this message.
|
||||
fn cc_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>>;
|
||||
|
||||
/// The `Bcc` header for this message.
|
||||
fn bcc_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>>;
|
||||
|
||||
/// The `Subject` header for this message.
|
||||
fn message_subject(&self) -> Option<String>;
|
||||
|
||||
/// The message's priority/importance. Might be represented by its
|
||||
/// `X-Priority` header.
|
||||
fn priority(&self) -> Option<nsMsgPriorityValue>;
|
||||
}
|
||||
|
||||
impl MessageHeaders for &ews::Message {
|
||||
fn internet_message_id(&self) -> Option<String> {
|
||||
self.internet_message_id.clone()
|
||||
}
|
||||
|
||||
fn is_read(&self) -> Option<bool> {
|
||||
self.is_read
|
||||
}
|
||||
|
||||
fn has_attachments(&self) -> Option<bool> {
|
||||
self.has_attachments
|
||||
}
|
||||
|
||||
fn sent_timestamp_ms(&self) -> Option<i64> {
|
||||
self.date_time_sent.as_ref().and_then(|date_time| {
|
||||
// `time` gives Unix timestamps in seconds. `PRTime` is an `i64`
|
||||
// representing Unix timestamps in microseconds. `PRTime` won't overflow
|
||||
// for over 500,000 years, but we use `checked_mul()` to guard against
|
||||
// receiving nonsensical values.
|
||||
let time_in_micros = date_time.0.unix_timestamp().checked_mul(1_000 * 1_000);
|
||||
if time_in_micros.is_none() {
|
||||
log::warn!(
|
||||
"message with ID {item_id} sent date {date_time:?} too big for `i64`, ignoring",
|
||||
item_id = self.item_id.id
|
||||
);
|
||||
}
|
||||
|
||||
time_in_micros
|
||||
})
|
||||
}
|
||||
|
||||
fn author(&self) -> Option<ews::Mailbox> {
|
||||
self.from
|
||||
.as_ref()
|
||||
.or(self.sender.as_ref())
|
||||
.and_then(|recipient| Some(recipient.mailbox.clone()))
|
||||
}
|
||||
|
||||
fn reply_to_recipient(&self) -> Option<ews::Mailbox> {
|
||||
self.reply_to
|
||||
.as_ref()
|
||||
.and_then(|recipient| Some(recipient.mailbox.clone()))
|
||||
}
|
||||
|
||||
fn to_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>> {
|
||||
self.to_recipients
|
||||
.as_ref()
|
||||
.and_then(|recipients| Some(array_of_recipients_to_mailboxes(recipients)))
|
||||
}
|
||||
|
||||
fn cc_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>> {
|
||||
self.cc_recipients
|
||||
.as_ref()
|
||||
.and_then(|recipients| Some(array_of_recipients_to_mailboxes(recipients)))
|
||||
}
|
||||
|
||||
fn bcc_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>> {
|
||||
self.bcc_recipients
|
||||
.as_ref()
|
||||
.and_then(|recipients| Some(array_of_recipients_to_mailboxes(recipients)))
|
||||
}
|
||||
|
||||
fn message_subject(&self) -> Option<String> {
|
||||
self.subject.clone()
|
||||
}
|
||||
|
||||
fn priority(&self) -> Option<nsMsgPriorityValue> {
|
||||
self.importance.and_then(|importance| {
|
||||
Some(match importance {
|
||||
ews::Importance::Low => nsMsgPriority::low,
|
||||
ews::Importance::Normal => nsMsgPriority::normal,
|
||||
ews::Importance::High => nsMsgPriority::high,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHeaders for &mail_parser::Message<'_> {
|
||||
fn internet_message_id(&self) -> Option<String> {
|
||||
self.message_id()
|
||||
.and_then(|message_id| Some(message_id.to_string()))
|
||||
}
|
||||
|
||||
fn is_read(&self) -> Option<bool> {
|
||||
// TODO: read this value from the X-Mozilla-Status header
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn has_attachments(&self) -> Option<bool> {
|
||||
Some(self.attachment_count() > 0)
|
||||
}
|
||||
|
||||
fn sent_timestamp_ms(&self) -> Option<i64> {
|
||||
self.date()
|
||||
.and_then(|date_time| match date_time.to_timestamp().checked_mul(1_000 * 1_000) {
|
||||
Some(timestamp_ms) => Some(timestamp_ms),
|
||||
None => {
|
||||
log::warn!(
|
||||
"message with ID {item_id} sent date {date_time:?} too big for `i64`, ignoring",
|
||||
item_id=self.message_id().or(Some("<none>")).unwrap()
|
||||
);
|
||||
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn author(&self) -> Option<ews::Mailbox> {
|
||||
self.to()
|
||||
.or(self.sender())
|
||||
.and_then(|author| author.first())
|
||||
.and_then(addr_to_maybe_mailbox)
|
||||
}
|
||||
|
||||
fn reply_to_recipient(&self) -> Option<ews::Mailbox> {
|
||||
self.reply_to()
|
||||
.and_then(|reply_to| reply_to.first())
|
||||
.and_then(addr_to_maybe_mailbox)
|
||||
}
|
||||
|
||||
fn to_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>> {
|
||||
self.to()
|
||||
.and_then(|address| Some(address_to_mailboxes(address)))
|
||||
}
|
||||
|
||||
fn cc_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>> {
|
||||
self.cc()
|
||||
.and_then(|address| Some(address_to_mailboxes(address)))
|
||||
}
|
||||
|
||||
fn bcc_recipients(&self) -> Option<impl IntoIterator<Item = ews::Mailbox>> {
|
||||
self.bcc()
|
||||
.and_then(|address| Some(address_to_mailboxes(address)))
|
||||
}
|
||||
|
||||
fn message_subject(&self) -> Option<String> {
|
||||
self.subject().and_then(|subject| Some(subject.to_string()))
|
||||
}
|
||||
|
||||
fn priority(&self) -> Option<nsMsgPriorityValue> {
|
||||
self.header("X-Priority")
|
||||
.and_then(|value| value.as_text())
|
||||
.and_then(|value| value.trim().chars().nth(0))
|
||||
.and_then(|first_char| {
|
||||
Some(match first_char {
|
||||
// Annoyingly, the indices in nsMsgPriority don't match with the
|
||||
// integer values in the header. These pairings come from
|
||||
// https://people.dsv.su.se/~jpalme/ietf/ietf-mail-attributes.html#Heading14,
|
||||
// and `NS_MsgGetPriorityFromString`.
|
||||
'1' => nsMsgPriority::highest,
|
||||
'2' => nsMsgPriority::high,
|
||||
'3' => nsMsgPriority::normal,
|
||||
'4' => nsMsgPriority::low,
|
||||
'5' => nsMsgPriority::lowest,
|
||||
_ => nsMsgPriority::Default,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns a `mail_parser::Address` into a vector of `ews::Mailbox`es, filtering
|
||||
/// out any that does not have an e-mail address.
|
||||
fn address_to_mailboxes(address: &mail_parser::Address) -> Vec<ews::Mailbox> {
|
||||
address
|
||||
.clone()
|
||||
.into_list()
|
||||
.iter()
|
||||
.filter_map(addr_to_maybe_mailbox)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Turns the given `mail_parser::Addr` into an `ews::Mailbox`, if its email
|
||||
/// address is not `None`.
|
||||
fn addr_to_maybe_mailbox(addr: &mail_parser::Addr) -> Option<ews::Mailbox> {
|
||||
match addr.address() {
|
||||
Some(address) => Some(ews::Mailbox {
|
||||
name: addr
|
||||
.name
|
||||
.as_ref()
|
||||
.and_then(|name| Some(name.clone().into_owned())),
|
||||
email_address: address.into(),
|
||||
..Default::default()
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns an `ews::ArrayOfRecipients` into a vector of `ews::Mailbox`es.
|
||||
fn array_of_recipients_to_mailboxes(recipients: &ews::ArrayOfRecipients) -> Vec<ews::Mailbox> {
|
||||
recipients
|
||||
.iter()
|
||||
.map(|recipient| recipient.mailbox.clone())
|
||||
.collect()
|
||||
}
|
|
@ -6,25 +6,29 @@ extern crate xpcom;
|
|||
|
||||
use std::{cell::OnceCell, ffi::c_void};
|
||||
|
||||
use authentication::credentials::{AuthenticationProvider, Credentials};
|
||||
use client::XpComEwsClient;
|
||||
use url::Url;
|
||||
|
||||
use nserror::{
|
||||
nsresult, NS_ERROR_ALREADY_INITIALIZED, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_INITIALIZED, NS_OK,
|
||||
};
|
||||
use nsstring::nsACString;
|
||||
use url::Url;
|
||||
use xpcom::{
|
||||
interfaces::{
|
||||
nsIMsgIncomingServer, nsIRequest, nsIStreamListener, IEwsFolderCallbacks,
|
||||
IEwsMessageCallbacks,
|
||||
nsIInputStream, nsIMsgCopyServiceListener, nsIMsgIncomingServer, nsIRequest,
|
||||
nsIStreamListener, IEwsFolderCallbacks, IEwsMessageCallbacks,
|
||||
},
|
||||
nsIID, xpcom_method, RefPtr,
|
||||
};
|
||||
|
||||
use authentication::credentials::{AuthenticationProvider, Credentials};
|
||||
use client::XpComEwsClient;
|
||||
|
||||
mod authentication;
|
||||
mod cancellable_request;
|
||||
mod client;
|
||||
mod headers;
|
||||
mod outgoing;
|
||||
mod xpcom_io;
|
||||
|
||||
/// Creates a new instance of the XPCOM/EWS bridge interface [`XpcomEwsBridge`].
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -154,6 +158,36 @@ impl XpcomEwsBridge {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
xpcom_method!(save_message => SaveMessage(folder_id: *const nsACString, isDraft: bool, messageStream: *const nsIInputStream, copyListener: *const nsIMsgCopyServiceListener, messageCallbaks: *const IEwsMessageCallbacks));
|
||||
fn save_message(
|
||||
&self,
|
||||
folder_id: &nsACString,
|
||||
is_draft: bool,
|
||||
message_stream: &nsIInputStream,
|
||||
copy_listener: &nsIMsgCopyServiceListener,
|
||||
message_callbacks: &IEwsMessageCallbacks,
|
||||
) -> Result<(), nsresult> {
|
||||
let content = crate::xpcom_io::read_stream(message_stream)?;
|
||||
|
||||
let client = self.try_new_client()?;
|
||||
|
||||
// The client operation is async and we want it to survive the end of
|
||||
// this scope, so spawn it as a detached `moz_task`.
|
||||
moz_task::spawn_local(
|
||||
"save_message",
|
||||
client.save_message(
|
||||
folder_id.to_utf8().into(),
|
||||
is_draft,
|
||||
content,
|
||||
RefPtr::new(copy_listener),
|
||||
RefPtr::new(message_callbacks),
|
||||
),
|
||||
)
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets a new EWS client if initialized.
|
||||
fn try_new_client(&self) -> Result<XpComEwsClient, nsresult> {
|
||||
// We only get a reference out of the cell, but we need ownership in
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::{c_char, c_void};
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr;
|
||||
|
||||
use ews::{Mailbox, Recipient};
|
||||
|
@ -18,16 +18,16 @@ use url::Url;
|
|||
use xpcom::{create_instance, get_service, getter_addrefs, nsIID};
|
||||
use xpcom::{
|
||||
interfaces::{
|
||||
msgIAddressObject, msgIOAuth2Module, nsIFile, nsIFileInputStream, nsIIOService,
|
||||
nsIMsgIdentity, nsIMsgStatusFeedback, nsIMsgWindow, nsIPrefBranch, nsIPrefService,
|
||||
nsIRequestObserver, nsIURI, nsIUrlListener, nsMsgAuthMethodValue, nsMsgSocketType,
|
||||
nsMsgSocketTypeValue,
|
||||
msgIAddressObject, msgIOAuth2Module, nsIFile, nsIIOService, nsIMsgIdentity,
|
||||
nsIMsgStatusFeedback, nsIMsgWindow, nsIPrefBranch, nsIPrefService, nsIRequestObserver,
|
||||
nsIURI, nsIUrlListener, nsMsgAuthMethodValue, nsMsgSocketType, nsMsgSocketTypeValue,
|
||||
},
|
||||
xpcom_method, RefPtr,
|
||||
};
|
||||
|
||||
use crate::authentication::credentials::AuthenticationProvider;
|
||||
use crate::client::XpComEwsClient;
|
||||
use crate::xpcom_io;
|
||||
|
||||
/// Whether a field is required to have a value (either in memory or in a pref)
|
||||
/// upon access.
|
||||
|
@ -544,7 +544,7 @@ impl EwsOutgoingServer {
|
|||
message_id: &nsACString,
|
||||
observer: &nsIRequestObserver,
|
||||
) -> Result<(), nsresult> {
|
||||
let message_content = read_file(file_path)?;
|
||||
let message_content = xpcom_io::read_file(file_path)?;
|
||||
let message_content =
|
||||
String::from_utf8(message_content).or(Err(nserror::NS_ERROR_FAILURE))?;
|
||||
|
||||
|
@ -685,47 +685,3 @@ impl AuthenticationProvider for &EwsOutgoingServer {
|
|||
Ok(oauth2_supported.then_some(oauth2_module))
|
||||
}
|
||||
}
|
||||
|
||||
/// Open the file provided and read its content into a vector of bytes.
|
||||
fn read_file(file: &nsIFile) -> Result<Vec<u8>, nsresult> {
|
||||
let file_stream =
|
||||
create_instance::<nsIFileInputStream>(cstr!("@mozilla.org/network/file-input-stream;1"))
|
||||
.ok_or(nserror::NS_ERROR_FAILURE)?;
|
||||
|
||||
// Open a stream from the file, and figure out how many bytes can be read
|
||||
// from it.
|
||||
let mut bytes_available = 0;
|
||||
unsafe {
|
||||
file_stream
|
||||
.Init(file, -1, -1, nsIFileInputStream::CLOSE_ON_EOF)
|
||||
.to_result()?;
|
||||
|
||||
file_stream.Available(&mut bytes_available)
|
||||
}
|
||||
.to_result()?;
|
||||
|
||||
// `nsIInputStream::Available` reads into a u64, but `nsIInputStream::Read`
|
||||
// takes a u32.
|
||||
let bytes_available = <u32>::try_from(bytes_available).or(Err(nserror::NS_ERROR_FAILURE))?;
|
||||
|
||||
let mut read_sink: Vec<u8> =
|
||||
vec![0; <usize>::try_from(bytes_available).or(Err(nserror::NS_ERROR_FAILURE))?];
|
||||
|
||||
// The amount of bytes actually read from the stream.
|
||||
let mut bytes_read: u32 = 0;
|
||||
|
||||
// SAFETY: The call contract from `nsIInputStream::Read` guarantees that the
|
||||
// bytes written into the provided buffer is of type c_char (char* in
|
||||
// C-land) and is contiguous for the length it writes in `bytes_read`; and
|
||||
// that `bytes_read` is not greater than `bytes_available`.
|
||||
unsafe {
|
||||
let read_ptr = read_sink.as_mut_ptr();
|
||||
|
||||
file_stream
|
||||
.Read(read_ptr as *mut c_char, bytes_available, &mut bytes_read)
|
||||
.to_result()?;
|
||||
};
|
||||
|
||||
let bytes_read = <usize>::try_from(bytes_read).or(Err(nserror::NS_ERROR_FAILURE))?;
|
||||
Ok(Vec::from(&read_sink[..bytes_read]))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use cstr::cstr;
|
||||
|
||||
use nserror::nsresult;
|
||||
use xpcom::create_instance;
|
||||
use xpcom::interfaces::{nsIFile, nsIFileInputStream, nsIInputStream};
|
||||
|
||||
/// Open the file provided and read its content into a vector of bytes.
|
||||
pub(crate) fn read_file(file: &nsIFile) -> Result<Vec<u8>, nsresult> {
|
||||
// Open a stream from the file.
|
||||
let file_stream =
|
||||
create_instance::<nsIFileInputStream>(cstr!("@mozilla.org/network/file-input-stream;1"))
|
||||
.ok_or(nserror::NS_ERROR_FAILURE)?;
|
||||
|
||||
unsafe { file_stream.Init(file, -1, -1, nsIFileInputStream::CLOSE_ON_EOF) }.to_result()?;
|
||||
|
||||
// Read as many bytes as available from the stream.
|
||||
read_stream(file_stream.coerce())
|
||||
}
|
||||
|
||||
pub(crate) fn read_stream(stream: &nsIInputStream) -> Result<Vec<u8>, nsresult> {
|
||||
let mut bytes_available = 0;
|
||||
unsafe { stream.Available(&mut bytes_available) }.to_result()?;
|
||||
|
||||
// `nsIInputStream::Available` reads into a u64, but `nsIInputStream::Read`
|
||||
// takes a u32.
|
||||
let bytes_available = <u32>::try_from(bytes_available).or(Err(nserror::NS_ERROR_FAILURE))?;
|
||||
|
||||
let mut read_sink: Vec<u8> =
|
||||
vec![0; <usize>::try_from(bytes_available).or(Err(nserror::NS_ERROR_FAILURE))?];
|
||||
|
||||
// The amount of bytes actually read from the stream.
|
||||
let mut bytes_read: u32 = 0;
|
||||
|
||||
// SAFETY: The call contract from `nsIInputStream::Read` guarantees that the
|
||||
// bytes written into the provided buffer is of type c_char (char* in
|
||||
// C-land) and is contiguous for the length it writes in `bytes_read`; and
|
||||
// that `bytes_read` is not greater than `bytes_available`.
|
||||
unsafe {
|
||||
let read_ptr = read_sink.as_mut_ptr();
|
||||
|
||||
stream
|
||||
.Read(read_ptr as *mut c_char, bytes_available, &mut bytes_read)
|
||||
.to_result()?;
|
||||
};
|
||||
|
||||
// TODO: We currently assume all of the data we care about is in the stream
|
||||
// when we read, which might not be the case if we're copying multiple
|
||||
// messages.
|
||||
let bytes_read = <usize>::try_from(bytes_read).or(Err(nserror::NS_ERROR_FAILURE))?;
|
||||
Ok(Vec::from(&read_sink[..bytes_read]))
|
||||
}
|
|
@ -1 +1 @@
|
|||
{"files":{".github/workflows/ci.yaml":"86a41c10a1b90620bb4548c8f18d082e906c2274e8f1d951568e4c070b10d8cb","Cargo.toml":"ea5a29dc84fb226255c22e0aa2bafb83353d671970f2990c19226a61e52a1d05","LICENSE":"3f3d9e0024b1921b067d6f7f88deb4a60cbe7a78e76c64e3f1d7fc3b779b9d04","README.md":"eebefef86e483df98c6b1104101348293fbacbd16639f15044ca37d2949b68f1","src/lib.rs":"20e850b188c53ad1c5d533a4a7d797fd88a18b0aa1ca1486b8cd839ca94749b6","src/types.rs":"afa4bc3d11fdfdfd25fa23d50d2541d04087f0918609fad5db737b68cc7a73d1","src/types/common.rs":"b96b84ef16bc268762ae478e0d5634f270017dda4e107f82ca83f475eda9d3bb","src/types/create_item.rs":"6db9e7d7b0d9bdf1ecb4a5ceae187cf6eb76488214ddf64be58567a5d5536339","src/types/get_folder.rs":"4b26621d2efd40a406afabbd6e4c092c7aafd73e07c11e8cbdad19bed205d238","src/types/get_item.rs":"401f60da2ffb7ccda1dda56e25876c640851597bb137ce34a7f7eb0fc94508d4","src/types/operations.rs":"69a1f976f5fca24bff77765003dc8345df2aebd3f632f3480f41fd9d2011c3c1","src/types/soap.rs":"640b49ecd88d06054e71b8c464e13f34e1010e8bce942158e9d8ede1fd35e212","src/types/soap/de.rs":"6fb603f521a73984e5707988379e562018b179df54647cff89d8ab03c406cff2","src/types/sync_folder_hierarchy.rs":"1f219d9bda6f4685ba962ff0cb891a9925e6a5c7d159d0a3f4561ca8439a71d5","src/types/sync_folder_items.rs":"e8548d6e9b6b847bfafc399b7168092875b7bebf5fca74b47d082a0dc7ef53d1"},"package":null}
|
||||
{"files":{".github/workflows/ci.yaml":"86a41c10a1b90620bb4548c8f18d082e906c2274e8f1d951568e4c070b10d8cb","Cargo.toml":"ea5a29dc84fb226255c22e0aa2bafb83353d671970f2990c19226a61e52a1d05","LICENSE":"3f3d9e0024b1921b067d6f7f88deb4a60cbe7a78e76c64e3f1d7fc3b779b9d04","README.md":"eebefef86e483df98c6b1104101348293fbacbd16639f15044ca37d2949b68f1","src/lib.rs":"20e850b188c53ad1c5d533a4a7d797fd88a18b0aa1ca1486b8cd839ca94749b6","src/types.rs":"afa4bc3d11fdfdfd25fa23d50d2541d04087f0918609fad5db737b68cc7a73d1","src/types/common.rs":"b96b84ef16bc268762ae478e0d5634f270017dda4e107f82ca83f475eda9d3bb","src/types/create_item.rs":"a50a1ae6ee62c6d2d4003e13b72e5a8e194c0df9f1454c5f7c4ae7f3929ccae6","src/types/get_folder.rs":"4b26621d2efd40a406afabbd6e4c092c7aafd73e07c11e8cbdad19bed205d238","src/types/get_item.rs":"401f60da2ffb7ccda1dda56e25876c640851597bb137ce34a7f7eb0fc94508d4","src/types/operations.rs":"69a1f976f5fca24bff77765003dc8345df2aebd3f632f3480f41fd9d2011c3c1","src/types/soap.rs":"640b49ecd88d06054e71b8c464e13f34e1010e8bce942158e9d8ede1fd35e212","src/types/soap/de.rs":"6fb603f521a73984e5707988379e562018b179df54647cff89d8ab03c406cff2","src/types/sync_folder_hierarchy.rs":"1f219d9bda6f4685ba962ff0cb891a9925e6a5c7d159d0a3f4561ca8439a71d5","src/types/sync_folder_items.rs":"e8548d6e9b6b847bfafc399b7168092875b7bebf5fca74b47d082a0dc7ef53d1"},"package":null}
|
|
@ -35,7 +35,7 @@ pub struct CreateItem {
|
|||
///
|
||||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/createitem#messagedisposition-attribute>
|
||||
#[xml_struct(attribute)]
|
||||
pub message_disposition: MessageDisposition,
|
||||
pub message_disposition: Option<MessageDisposition>,
|
||||
|
||||
/// The folder in which to store an item once it has been created.
|
||||
///
|
||||
|
|
Загрузка…
Ссылка в новой задаче