Bug 1929610 - Implement storage support around EWS's message fetching. r=#thunderbird-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D228378
This commit is contained in:
Brendan Abolivier 2024-12-09 19:15:52 +00:00
Родитель 32f75c6e4a
Коммит c5d6eb0c67
12 изменённых файлов: 873 добавлений и 436 удалений

Просмотреть файл

@ -1,304 +0,0 @@
/* 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/. */
#include "EwsMessageChannel.h"
#include "IEwsClient.h"
#include "IEwsIncomingServer.h"
#include "nsIMailChannel.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgIncomingServer.h"
#include "nsIStreamConverterService.h"
#include "nsIStreamListener.h"
#include "nsIURIMutator.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
NS_IMPL_ISUPPORTS(EwsMessageChannel, nsIMailChannel, nsIChannel, nsIRequest)
EwsMessageChannel::EwsMessageChannel(nsIURI* uri, bool shouldConvert)
: m_isPending(true),
m_status(NS_OK),
m_loadFlags(nsIRequest::LOAD_NORMAL),
m_uri(uri) {
if (shouldConvert) {
m_contentType.AssignLiteral(TEXT_HTML);
} else {
m_contentType.AssignLiteral(MESSAGE_RFC822);
}
m_charset.AssignLiteral("UTF-8");
}
EwsMessageChannel::~EwsMessageChannel() = default;
NS_IMETHODIMP EwsMessageChannel::GetName(nsACString& aName) {
NS_WARNING("GetName");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::IsPending(bool* _retval) {
*_retval = m_isPending;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetStatus(nsresult* aStatus) {
*aStatus = m_status;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::Cancel(nsresult aStatus) {
NS_WARNING("Cancel");
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP EwsMessageChannel::Suspend(void) {
NS_WARNING("Suspend");
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP EwsMessageChannel::Resume(void) {
NS_WARNING("Resume");
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP EwsMessageChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
m_loadGroup = aLoadGroup;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
*aLoadFlags = m_loadFlags;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
m_loadFlags = aLoadFlags;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetTRRMode(nsIRequest::TRRMode* _retval) {
return GetTRRModeImpl(_retval);
}
NS_IMETHODIMP EwsMessageChannel::SetTRRMode(nsIRequest::TRRMode mode) {
return SetTRRModeImpl(mode);
}
NS_IMETHODIMP EwsMessageChannel::CancelWithReason(nsresult aStatus,
const nsACString& aReason) {
return CancelWithReasonImpl(aStatus, aReason);
}
NS_IMETHODIMP EwsMessageChannel::GetCanceledReason(
nsACString& aCanceledReason) {
return GetCanceledReasonImpl(aCanceledReason);
}
NS_IMETHODIMP EwsMessageChannel::SetCanceledReason(
const nsACString& aCanceledReason) {
return SetCanceledReasonImpl(aCanceledReason);
}
NS_IMETHODIMP EwsMessageChannel::GetOriginalURI(nsIURI** aOriginalURI) {
NS_IF_ADDREF(*aOriginalURI = m_uri);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetOriginalURI(nsIURI* aOriginalURI) {
// There's no meaningful "original URI" for these requests.
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetURI(nsIURI** aURI) {
NS_IF_ADDREF(*aURI = m_uri);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetOwner(nsISupports** aOwner) {
NS_IF_ADDREF(*aOwner = m_owner);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetOwner(nsISupports* aOwner) {
m_owner = aOwner;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetNotificationCallbacks(
nsIInterfaceRequestor** aNotificationCallbacks) {
NS_IF_ADDREF(*aNotificationCallbacks = m_notificationCallbacks);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetNotificationCallbacks(
nsIInterfaceRequestor* aNotificationCallbacks) {
m_notificationCallbacks = aNotificationCallbacks;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetSecurityInfo(
nsITransportSecurityInfo** aSecurityInfo) {
NS_WARNING("GetSecurityInfo");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::GetContentType(nsACString& aContentType) {
aContentType.Assign(m_contentType);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetContentType(
const nsACString& aContentType) {
nsresult rv =
NS_ParseResponseContentType(aContentType, m_contentType, m_charset);
if (NS_FAILED(rv) || m_contentType.IsEmpty()) {
m_contentType.AssignLiteral(MESSAGE_RFC822);
}
if (NS_FAILED(rv) || m_charset.IsEmpty()) {
m_charset.AssignLiteral("UTF-8");
}
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetContentCharset(
nsACString& aContentCharset) {
aContentCharset.Assign(m_charset);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetContentCharset(
const nsACString& aContentCharset) {
m_charset.Assign(aContentCharset);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetContentLength(int64_t* aContentLength) {
NS_WARNING("GetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::SetContentLength(int64_t aContentLength) {
NS_WARNING("SetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::Open(nsIInputStream** _retval) {
return NS_ImplementChannelOpen(this, _retval);
}
NS_IMETHODIMP EwsMessageChannel::AsyncOpen(nsIStreamListener* aListener) {
nsAutoCString path;
nsresult rv = m_uri->GetFilePath(path);
NS_ENSURE_SUCCESS(rv, rv);
// Trim the leading '/' to get the EWS ID alone.
auto ewsId = Substring(path, 1);
nsCOMPtr<nsIMsgAccountManager> accountManager =
do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
// `FindServerByURI()` expects that the URI passed in has a scheme matching
// the value returned by an incoming server's `GetType()` method. In our case,
// that should be `ews`.
nsCOMPtr<nsIURI> serverUri;
rv = NS_MutateURI(m_uri).SetScheme("ews"_ns).SetPathQueryRef(ewsId).Finalize(
serverUri);
nsCOMPtr<nsIMsgIncomingServer> server;
rv = accountManager->FindServerByURI(serverUri, getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<IEwsIncomingServer> ewsServer = do_QueryInterface(server, &rv);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<IEwsClient> client;
rv = ewsServer->GetEwsClient(getter_AddRefs(client));
NS_ENSURE_SUCCESS(rv, rv);
// If the consumer requests HTML, we want to render the message (from its raw
// Internet Message Format text) for display. Otherwise, we will return the
// raw Internet Message Format (RFC 822/2822/5322).
RefPtr<nsIStreamListener> listenerToUse = aListener;
if (m_contentType.Equals(TEXT_HTML)) {
nsresult rv;
nsCOMPtr<nsIStreamConverterService> converter =
do_GetService("@mozilla.org/streamConverters;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Wrap the consumer-provided listener in a stream converter and use the
// listener it creates for further operations.
rv = converter->AsyncConvertData(MESSAGE_RFC822, ANY_WILDCARD, aListener,
static_cast<nsIChannel*>(this),
getter_AddRefs(listenerToUse));
NS_ENSURE_SUCCESS(rv, rv);
}
m_status = client->GetMessage(ewsId, this, listenerToUse);
NS_ENSURE_SUCCESS(m_status, m_status);
m_isPending = false;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetCanceled(bool* aCanceled) {
NS_WARNING("GetCanceled");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::GetContentDisposition(
uint32_t* aContentDisposition) {
*aContentDisposition = nsIChannel::DISPOSITION_INLINE;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetContentDisposition(
uint32_t aContentDisposition) {
NS_WARNING("SetContentDisposition");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::GetContentDispositionFilename(
nsAString& aContentDispositionFilename) {
NS_WARNING("GetContentDispositionFilename");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::SetContentDispositionFilename(
const nsAString& aContentDispositionFilename) {
NS_WARNING("SetContentDispositionFilename");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::GetContentDispositionHeader(
nsACString& aContentDispositionHeader) {
NS_WARNING("GetContentDispositionHeader");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsMessageChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
m_loadInfo = aLoadInfo;
return NS_OK;
}
NS_IMETHODIMP EwsMessageChannel::GetIsDocument(bool* aIsDocument) {
NS_WARNING("GetIsDocument");
return NS_ERROR_NOT_IMPLEMENTED;
}

Просмотреть файл

@ -1,49 +0,0 @@
/* 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/. */
#ifndef __COMM_MAILNEWS_PROTOCOLS_EWS_MESSAGE_CHANNEL_H
#define __COMM_MAILNEWS_PROTOCOLS_EWS_MESSAGE_CHANNEL_H
#include "nsIChannel.h"
#include "nsMailChannel.h"
// A channel for loading email messages from Exchange Web Services.
//
// URIs are expected to be of the form `{scheme}://{host_string}/{message_id}`,
// where `host_string` matches the host string of the associated account and
// `message_id` is the EWS identifier for the requested message.
//
// If `text/html` is the requested content type, the channel will return the
// message as a rendered HTML document. Otherwise, it will return the message in
// its raw Internet Message Format form.
class EwsMessageChannel : public nsMailChannel, public nsIChannel {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUEST
NS_DECL_NSICHANNEL
// Constructs a new message channel.
//
// If `shouldConvert` is `true`, the content type of the channel will be set
// to `text/html` and the channel will return a rendered HTML document fit for
// display.
explicit EwsMessageChannel(nsIURI* uri, bool shouldConvert = true);
protected:
virtual ~EwsMessageChannel();
private:
bool m_isPending;
RefPtr<nsILoadGroup> m_loadGroup;
nsresult m_status;
nsLoadFlags m_loadFlags;
RefPtr<nsIInterfaceRequestor> m_notificationCallbacks;
RefPtr<nsIURI> m_uri;
RefPtr<nsISupports> m_owner;
nsCString m_contentType;
nsCString m_charset;
RefPtr<nsILoadInfo> m_loadInfo;
};
#endif

Просмотреть файл

@ -0,0 +1,392 @@
/* 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/. */
#include "EwsOfflineMessageChannel.h"
#include "IEwsClient.h"
#include "nsIInputStream.h"
#include "nsIInputStreamPump.h"
#include "nsIMailChannel.h"
#include "nsIMsgFolder.h"
#include "nsIMsgHdr.h"
#include "nsIMsgIncomingServer.h"
#include "nsIMsgMessageService.h"
#include "nsIStreamConverterService.h"
#include "nsIStreamListener.h"
#include "nsIURIMutator.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
/**
* A stream listener that proxies method calls to another stream listener, while
* substituting the request argument with the provided channel.
*
* `EwsOfflineMessageChannel` can be called from an `nsIDocShell` to render the
* message. The stream listener that `nsIDocShell` calls `AsyncOpen` with
* expects the request used in method calls to be channel-like (i.e. it can be
* QI'd as an `nsIChannel`). Additionally, we want to use `nsIInputStreamPump`
* to pump the data from the message content's input stream (which we get from
* the message store) into the provided stream listener. However, the default
* `nsIInputStreamPump` implementation calls the stream listener methods with
* itself as the request argument, but only implements `nsIRequest` (and not
* `nsIChannel`), causing the operation to fail.
*
* Therefore we need this "proxy" listener to forward the method calls to the
* listener `AsyncOpen` is originally provided with, while subsituting the
* request arguments with an actual channel.
*/
class EwsDisplayProxyListener : public nsIStreamListener {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
EwsDisplayProxyListener(nsIStreamListener* destination, nsIChannel* channel)
: mDestination(destination), mChannel(channel) {};
protected:
virtual ~EwsDisplayProxyListener();
private:
nsCOMPtr<nsIStreamListener> mDestination;
nsCOMPtr<nsIChannel> mChannel;
};
NS_IMPL_ISUPPORTS(EwsDisplayProxyListener, nsIStreamListener)
EwsDisplayProxyListener::~EwsDisplayProxyListener() = default;
NS_IMETHODIMP EwsDisplayProxyListener::OnStartRequest(nsIRequest* request) {
return mDestination->OnStartRequest(mChannel);
}
NS_IMETHODIMP EwsDisplayProxyListener::OnStopRequest(nsIRequest* request,
nsresult aStatus) {
return mDestination->OnStopRequest(mChannel, aStatus);
}
NS_IMETHODIMP EwsDisplayProxyListener::OnDataAvailable(
nsIRequest* request, nsIInputStream* aInStream, uint64_t aSourceOffset,
uint32_t aCount) {
return mDestination->OnDataAvailable(mChannel, aInStream, aSourceOffset,
aCount);
}
/**
* nsIChannel/nsIRequest impl for EwsOfflineMessageChannel
*/
NS_IMPL_ISUPPORTS(EwsOfflineMessageChannel, nsIMailChannel, nsIChannel,
nsIRequest)
EwsOfflineMessageChannel::EwsOfflineMessageChannel(nsIURI* uri)
: mURI(uri), mPump(nullptr), mLoadFlags(nsIRequest::LOAD_NORMAL) {
mContentType.AssignLiteral(MESSAGE_RFC822);
mCharset.AssignLiteral("UTF-8");
}
EwsOfflineMessageChannel::~EwsOfflineMessageChannel() = default;
NS_IMETHODIMP EwsOfflineMessageChannel::GetName(nsACString& aName) {
if (mURI) {
return mURI->GetSpec(aName);
}
aName.Truncate();
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::IsPending(bool* aPending) {
if (mPump) {
*aPending = false;
} else {
*aPending = true;
}
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetStatus(nsresult* aStatus) {
if (!mPump) {
return NS_ERROR_NOT_INITIALIZED;
}
return mPump->GetStatus(aStatus);
}
NS_IMETHODIMP EwsOfflineMessageChannel::Cancel(nsresult aStatus) {
if (!mPump) {
return NS_ERROR_NOT_INITIALIZED;
}
return mPump->Cancel(aStatus);
}
NS_IMETHODIMP EwsOfflineMessageChannel::Suspend(void) {
if (!mPump) {
return NS_ERROR_NOT_INITIALIZED;
}
return mPump->Suspend();
}
NS_IMETHODIMP EwsOfflineMessageChannel::Resume(void) {
if (!mPump) {
return NS_ERROR_NOT_INITIALIZED;
}
return mPump->Resume();
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetLoadGroup(
nsILoadGroup** aLoadGroup) {
NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
mLoadGroup = aLoadGroup;
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
*aLoadFlags = mLoadFlags;
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
mLoadFlags = aLoadFlags;
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetTRRMode(nsIRequest::TRRMode* mode) {
return GetTRRModeImpl(mode);
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetTRRMode(nsIRequest::TRRMode mode) {
return SetTRRModeImpl(mode);
}
NS_IMETHODIMP EwsOfflineMessageChannel::CancelWithReason(
nsresult aStatus, const nsACString& aReason) {
if (!mPump) {
return NS_ERROR_NOT_INITIALIZED;
}
return mPump->CancelWithReason(aStatus, aReason);
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetCanceledReason(
nsACString& aCanceledReason) {
if (!mPump) {
return NS_ERROR_NOT_INITIALIZED;
}
return mPump->GetCanceledReason(aCanceledReason);
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetCanceledReason(
const nsACString& aCanceledReason) {
if (!mPump) {
return NS_ERROR_NOT_INITIALIZED;
}
return mPump->SetCanceledReason(aCanceledReason);
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetOriginalURI(nsIURI** aOriginalURI) {
NS_IF_ADDREF(*aOriginalURI = mURI);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetOriginalURI(nsIURI* aOriginalURI) {
// There's no meaningful "original URI" for these requests.
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetURI(nsIURI** aURI) {
NS_IF_ADDREF(*aURI = mURI);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetOwner(nsISupports** aOwner) {
NS_IF_ADDREF(*aOwner = mOwner);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetOwner(nsISupports* aOwner) {
mOwner = aOwner;
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetNotificationCallbacks(
nsIInterfaceRequestor** aNotificationCallbacks) {
NS_IF_ADDREF(*aNotificationCallbacks = mNotificationCallbacks);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetNotificationCallbacks(
nsIInterfaceRequestor* aNotificationCallbacks) {
mNotificationCallbacks = aNotificationCallbacks;
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetSecurityInfo(
nsITransportSecurityInfo** aSecurityInfo) {
// Security info does not make sense here since we're only pulling messages
// from storage.
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetContentType(
nsACString& aContentType) {
aContentType.Assign(mContentType);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetContentType(
const nsACString& aContentType) {
nsresult rv =
NS_ParseResponseContentType(aContentType, mContentType, mCharset);
if (NS_FAILED(rv) || mContentType.IsEmpty()) {
mContentType.AssignLiteral(MESSAGE_RFC822);
}
if (NS_FAILED(rv) || mCharset.IsEmpty()) {
mCharset.AssignLiteral("UTF-8");
}
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetContentCharset(
nsACString& aContentCharset) {
aContentCharset.Assign(mCharset);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetContentCharset(
const nsACString& aContentCharset) {
mCharset.Assign(aContentCharset);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetContentLength(
int64_t* aContentLength) {
NS_WARNING("GetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetContentLength(
int64_t aContentLength) {
NS_WARNING("SetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsOfflineMessageChannel::Open(nsIInputStream** _retval) {
return NS_ImplementChannelOpen(this, _retval);
}
NS_IMETHODIMP EwsOfflineMessageChannel::AsyncOpen(
nsIStreamListener* aListener) {
// Get the header and folder matching the URI.
nsresult rv;
nsCOMPtr<nsIMsgMessageService> msgService =
do_GetService("@mozilla.org/messenger/messageservice;1?type=ews", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCString spec;
rv = mURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgDBHdr> hdr;
rv = msgService->MessageURIToMsgHdr(spec, getter_AddRefs(hdr));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgFolder> folder;
rv = hdr->GetFolder(getter_AddRefs(folder));
NS_ENSURE_SUCCESS(rv, rv);
// Make sure the message exists in the offline store.
nsMsgKey msgKey;
rv = hdr->GetMessageKey(&msgKey);
NS_ENSURE_SUCCESS(rv, rv);
bool hasOffline;
rv = folder->HasMsgOffline(msgKey, &hasOffline);
NS_ENSURE_SUCCESS(rv, rv);
if (!hasOffline) {
return NS_ERROR_NOT_AVAILABLE;
}
// Stream the message from the store into the stream listener. This is also
// where we instantiate and initialize `mPump`.
nsCOMPtr<nsIInputStream> msgStream;
rv = folder->GetMsgInputStream(hdr, getter_AddRefs(msgStream));
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_NewInputStreamPump(getter_AddRefs(mPump), msgStream.forget());
NS_ENSURE_SUCCESS(rv, rv);
// We don't need to use our RFC822->HTML converter here because `nsDocShell`
// will run it for us.
nsCOMPtr<nsIStreamListener> listener =
new EwsDisplayProxyListener(aListener, this);
return mPump->AsyncRead(listener);
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetCanceled(bool* aCanceled) {
nsCString canceledReason;
nsresult rv = mPump->GetCanceledReason(canceledReason);
NS_ENSURE_SUCCESS(rv, rv);
*aCanceled = canceledReason.IsEmpty();
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetContentDisposition(
uint32_t* aContentDisposition) {
*aContentDisposition = nsIChannel::DISPOSITION_INLINE;
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetContentDisposition(
uint32_t aContentDisposition) {
NS_WARNING("SetContentDisposition");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetContentDispositionFilename(
nsAString& aContentDispositionFilename) {
NS_WARNING("GetContentDispositionFilename");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetContentDispositionFilename(
const nsAString& aContentDispositionFilename) {
NS_WARNING("SetContentDispositionFilename");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetContentDispositionHeader(
nsACString& aContentDispositionHeader) {
NS_WARNING("GetContentDispositionHeader");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
mLoadInfo = aLoadInfo;
return NS_OK;
}
NS_IMETHODIMP EwsOfflineMessageChannel::GetIsDocument(bool* aIsDocument) {
NS_WARNING("GetIsDocument");
return NS_ERROR_NOT_IMPLEMENTED;
}

Просмотреть файл

@ -0,0 +1,68 @@
/* 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/. */
#ifndef __COMM_MAILNEWS_PROTOCOLS_EWS_MESSAGE_CHANNEL_H
#define __COMM_MAILNEWS_PROTOCOLS_EWS_MESSAGE_CHANNEL_H
#include "nsIChannel.h"
#include "nsIInputStreamPump.h"
#include "nsMailChannel.h"
/**
* A channel for streaming an EWS message out of the relevant message store.
*
* The message URI is expected either of these two forms:
* * ews-message://{user}@{server}/{Path/To/Folder}#{MessageKey}
* * x-moz-ews://{user}@{server}/{Path/To/Folder}/{MessageKey}
*
* Note that no specific encoding is applied to the folder path (besides the
* standard URL path encoding).
*
* Translating this URI into a message header (which we can use to stream the
* message's content from the message store) is done through calling
* `EwsService::MessageURIToMsgHdr`.
*
* Design considerations on the form of "x-moz-ews" URIs: it includes the
* message key in the path to follow RESTful semantics. The message is the
* resource the URI is pointing to, "located" to the path preceding its key
* (i.e. the path to the folder). An alternative form could have been setting
* the key as a query parameter, however we seem to use query parameters to
* control how the message is fetched and/or rendered/streamed, not which
* message the operation is about, so it would be inconsistent with the rest of
* the code.
*/
class EwsOfflineMessageChannel : public nsMailChannel, public nsIChannel {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUEST
NS_DECL_NSICHANNEL
explicit EwsOfflineMessageChannel(nsIURI* uri);
protected:
virtual ~EwsOfflineMessageChannel();
private:
// The URI for the message which content we want to stream.
nsCOMPtr<nsIURI> mURI;
// The pump we're using to stream the message content. `nsIInputStreamPump`
// inherits from `nsIRequest`, so we use it to handle some methods (such as
// `Cancel`, `Suspend`, `GetStatus`, etc) we need to implement as part of
// implementing the latter interface.
nsCOMPtr<nsIInputStreamPump> mPump;
// These attributes mostly exist to allow a basic implementation of most
// `nsIChannel` methods. The content type (`mContentType`) will default to
// "message/rfc822" and the content charset (`mCharset`) to "UTF-8".
nsCString mContentType;
nsCString mCharset;
RefPtr<nsILoadGroup> mLoadGroup;
nsLoadFlags mLoadFlags;
nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;
nsCOMPtr<nsISupports> mOwner;
nsCOMPtr<nsILoadInfo> mLoadInfo;
};
#endif

Просмотреть файл

@ -4,7 +4,7 @@
#include "EwsProtocolHandler.h"
#include "EwsMessageChannel.h"
#include "EwsOfflineMessageChannel.h"
#include "nsIMsgIncomingServer.h"
NS_IMPL_ISUPPORTS(EwsProtocolHandler, nsIProtocolHandler)
@ -22,7 +22,7 @@ NS_IMETHODIMP EwsProtocolHandler::GetScheme(nsACString& aScheme) {
NS_IMETHODIMP EwsProtocolHandler::NewChannel(nsIURI* aURI,
nsILoadInfo* aLoadinfo,
nsIChannel** _retval) {
RefPtr<EwsMessageChannel> channel = new EwsMessageChannel(aURI);
RefPtr<EwsOfflineMessageChannel> channel = new EwsOfflineMessageChannel(aURI);
nsresult rv = channel->SetLoadInfo(aLoadinfo);
NS_ENSURE_SUCCESS(rv, rv);

Просмотреть файл

@ -4,19 +4,180 @@
#include "EwsService.h"
#include "EwsMessageChannel.h"
#include "EwsOfflineMessageChannel.h"
#include "IEwsIncomingServer.h"
#include "IEwsClient.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgDatabase.h"
#include "nsIMsgHdr.h"
#include "nsIMsgFolder.h"
#include "nsIMsgMailNewsUrl.h"
#include "nsIMsgPluggableStore.h"
#include "nsIOutputStream.h"
#include "nsIStreamListener.h"
#include "nsIURIMutator.h"
#include "nsIWebNavigation.h"
#include "nsContentUtils.h"
#include "nsDocShellLoadState.h"
#include "nsMsgMessageFlags.h"
#include "nsMsgUtils.h"
#include "nsNetUtil.h"
#define ID_PROPERTY "ewsId"
// Displays the given URI via the given docshell.
nsresult DisplayMessage(nsIURI* messageURI, nsIDocShell* docShell);
/**
* A listener for a message download, which writes the message's content into
* the relevant message store.
*
* Once the message has been downloaded and written into the store, this
* listener will also use the provided docshell or stream listener, if any, to
* display or stream the message's content from the store (using
* `EwsOfflineMessageChannel`). If both a docshell and a stream listener are
* provided, only the docshell is used.
*/
class MessageFetchListener : public IEWSMessageFetchCallbacks {
public:
NS_DECL_ISUPPORTS
NS_DECL_IEWSMESSAGEFETCHCALLBACKS
MessageFetchListener(nsIURI* messageURI, nsIMsgDBHdr* hdr,
nsIDocShell* docShell, nsIStreamListener* streamListener)
: mMessageURI(messageURI),
mHdr(hdr),
mDisplayDocShell(docShell),
mStreamListener(streamListener) {};
protected:
virtual ~MessageFetchListener();
private:
// The `ews-message` URI referring to the message to fetch.
nsCOMPtr<nsIURI> mMessageURI;
// The header for the message to fetch.
nsCOMPtr<nsIMsgDBHdr> mHdr;
// The message database for the message header, for committing the offline
// flag and message size once the message content has been downloaded.
nsCOMPtr<nsIMsgDatabase> mDB;
// The offline store in which to write the message content.
nsCOMPtr<nsIMsgPluggableStore> mStore;
// The output stream in which to write the message content as it is being
// downloaded.
nsCOMPtr<nsIOutputStream> mStoreOutStream;
// The size of the message in the offline store, updated as the content is
// being downloaded. Once the download finishes, this size is written to the
// message header and committed to the message database.
uint64_t mOfflineSize = 0;
// If provided, the message URI is loaded using this docshell (via
// `DisplayMessage`) once the full message content has been written to the
// message store. This triggers the docshell to stream the message content
// (and convert it to HTML on the fly) via `EwsOfflineMessageChannel`.
nsCOMPtr<nsIDocShell> mDisplayDocShell;
// If provided (and no docshell was provided), the message content is streamed
// through this stream listener (via `EwsOfflineMessageChannel`) once the it
// has been fully written to the message store.
nsCOMPtr<nsIStreamListener> mStreamListener;
};
NS_IMPL_ISUPPORTS(MessageFetchListener, IEWSMessageFetchCallbacks)
MessageFetchListener::~MessageFetchListener() = default;
NS_IMETHODIMP MessageFetchListener::OnFetchStart() {
// Instantiate the attributes we'll need to write the message and pass it on
// to the right consumer.
nsCOMPtr<nsIMsgFolder> folder;
nsresult rv = mHdr->GetFolder(getter_AddRefs(folder));
NS_ENSURE_SUCCESS(rv, rv);
rv = folder->GetMsgDatabase(getter_AddRefs(mDB));
NS_ENSURE_SUCCESS(rv, rv);
rv = folder->GetMsgStore(getter_AddRefs(mStore));
NS_ENSURE_SUCCESS(rv, rv);
rv = folder->GetOfflineStoreOutputStream(mHdr,
getter_AddRefs(mStoreOutStream));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP MessageFetchListener::OnDataAvailable(
nsIInputStream* aInputStream, uint32_t aCount) {
NS_ENSURE_ARG_POINTER(mStoreOutStream);
// Copy the message from the provided stream to the output stream provided by
// the store.
uint64_t bytesCopied;
nsresult rv = SyncCopyStream(aInputStream, mStoreOutStream, bytesCopied);
NS_ENSURE_SUCCESS(rv, rv);
mOfflineSize += bytesCopied;
return NS_OK;
}
NS_IMETHODIMP MessageFetchListener::OnFetchStop(nsresult status) {
NS_ENSURE_ARG_POINTER(mStore);
NS_ENSURE_ARG_POINTER(mStoreOutStream);
NS_ENSURE_ARG_POINTER(mDB);
nsresult rv;
if (NS_SUCCEEDED(status)) {
rv = mStore->FinishNewMessage(mStoreOutStream, mHdr);
NS_ENSURE_SUCCESS(rv, rv);
// Mark the message as downloaded in the database record and record its
// size.
uint32_t unused;
rv = mHdr->OrFlags(nsMsgMessageFlags::Offline, &unused);
NS_ENSURE_SUCCESS(rv, rv);
// In the future, we should use the size provided by the server, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1930127. In the meantime, we
// update it here since some areas of the code seem to use `messageSize` as
// the offline message size (see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1930003).
rv = mHdr->SetMessageSize(mOfflineSize);
NS_ENSURE_SUCCESS(rv, rv);
rv = mHdr->SetOfflineMessageSize(mOfflineSize);
NS_ENSURE_SUCCESS(rv, rv);
// Commit the changes to the folder's database.
rv = mDB->Commit(nsMsgDBCommitType::kLargeCommit);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// Fetch has failed, discard the new message in the store.
rv = mStore->DiscardNewMessage(mStoreOutStream, mHdr);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mDisplayDocShell) {
// If a docshell was provided, use it to display the message.
return DisplayMessage(mMessageURI, mDisplayDocShell);
}
if (mStreamListener) {
// If a stream listener was provided, use it to stream the message from the
// offline store.
nsCOMPtr<nsIChannel> channel = new EwsOfflineMessageChannel(mMessageURI);
return channel->AsyncOpen(mStreamListener);
}
return NS_OK;
}
NS_IMPL_ISUPPORTS(EwsService, nsIMsgMessageService)
EwsService::EwsService() = default;
@ -29,15 +190,7 @@ NS_IMETHODIMP EwsService::CopyMessage(const nsACString& aSrcURI,
nsIUrlListener* aUrlListener,
nsIMsgWindow* aMsgWindow) {
NS_ENSURE_ARG_POINTER(aCopyListener);
// The message service interface gives us URIs as strings, but we want
// structured, queryable/transformable data.
nsCOMPtr<nsIURI> uri;
nsresult rv = NewURIForChannel(aSrcURI, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<EwsMessageChannel> channel = new EwsMessageChannel(uri, false);
return channel->AsyncOpen(aCopyListener);
return GetMessageContent(aSrcURI, nullptr, aCopyListener);
}
NS_IMETHODIMP EwsService::CopyMessages(
@ -53,17 +206,8 @@ NS_IMETHODIMP EwsService::LoadMessage(const nsACString& aMessageURI,
nsIMsgWindow* aMsgWindow,
nsIUrlListener* aUrlListener,
bool aAutodetectCharset) {
// The message service interface gives us URIs as strings, but we want
// structured, queryable/transformable data.
nsCOMPtr<nsIURI> uri;
nsresult rv = NewURIForChannel(aMessageURI, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(uri);
loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
loadState->SetFirstParty(false);
loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
return aDisplayConsumer->LoadURI(loadState, false);
NS_ENSURE_ARG_POINTER(aDisplayConsumer);
return GetMessageContent(aMessageURI, aDisplayConsumer, nullptr);
}
NS_IMETHODIMP EwsService::SaveMessageToDisk(const nsACString& aMessageURI,
@ -121,21 +265,65 @@ NS_IMETHODIMP EwsService::MessageURIToMsgHdr(const nsACString& uri,
return MsgHdrFromUri(uriObj, _retval);
}
// Retrieves the `nsIMsgDBHdr` associated with an internal `ews-message://` URI.
nsresult EwsService::MsgHdrFromUri(nsIURI* uri, nsIMsgDBHdr** _retval) {
//
nsresult EwsService::MsgKeyStringFromMessageURI(nsIURI* uri,
nsACString& msgKey) {
// We expect the provided URI to be of the following form:
// `ews-message://{username}@{host}/{folder_path}#{msg_key}`.
// Note that `ews-message` is not a registered scheme and URIs which use it
// cannot be loaded through the standard URI loading mechanisms.
nsCString keyStr;
nsresult rv = uri->GetRef(keyStr);
nsresult rv = uri->GetRef(msgKey);
NS_ENSURE_SUCCESS(rv, rv);
if (keyStr.IsEmpty()) {
if (msgKey.IsEmpty()) {
NS_ERROR("message URI has no message key ref");
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
nsresult EwsService::MsgKeyStringFromChannelURI(nsIURI* uri, nsACString& msgKey,
nsACString& folderURIPath) {
nsresult rv = uri->GetFilePath(folderURIPath);
NS_ENSURE_SUCCESS(rv, rv);
// Iterate over each slash-separated word.
for (const auto& word : folderURIPath.Split('/')) {
// The last word is our message key.
msgKey.Assign(word);
}
// Cut the message key out of the path. We want to cut the length of the key +
// 1, to also remove the `/` character between the folder and the key.
auto keyStartIndex = folderURIPath.Length() - msgKey.Length() - 1;
auto keyLengthInURI = msgKey.Length() + 1;
folderURIPath.Cut(keyStartIndex, keyLengthInURI);
return NS_OK;
}
nsresult EwsService::MsgHdrFromUri(nsIURI* uri, nsIMsgDBHdr** _retval) {
nsCString keyStr;
nsCString folderURIPath;
// Extract the message key and folder path from the URI depending on its
// scheme.
nsCString scheme;
nsresult rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
if (scheme.EqualsLiteral("ews-message")) {
rv = MsgKeyStringFromMessageURI(uri, keyStr);
NS_ENSURE_SUCCESS(rv, rv);
rv = uri->GetFilePath(folderURIPath);
NS_ENSURE_SUCCESS(rv, rv);
} else if (scheme.EqualsLiteral("x-moz-ews")) {
rv = MsgKeyStringFromChannelURI(uri, keyStr, folderURIPath);
NS_ENSURE_SUCCESS(rv, rv);
}
nsMsgKey key =
msgKeyFromInt(ParseUint64Str(PromiseFlatCString(keyStr).get()));
@ -143,10 +331,16 @@ nsresult EwsService::MsgHdrFromUri(nsIURI* uri, nsIMsgDBHdr** _retval) {
// must match one it has in its database. Folders are created with an `ews`
// scheme, and we need to remove the message key from the ref.
RefPtr<nsIURI> folderUri;
rv = NS_MutateURI(uri).SetScheme("ews"_ns).SetRef(""_ns).Finalize(
getter_AddRefs(folderUri));
rv = NS_MutateURI(uri)
.SetScheme("ews"_ns)
.SetFilePath(folderURIPath)
.SetQuery(""_ns)
.SetRef(""_ns)
.Finalize(getter_AddRefs(folderUri));
NS_ENSURE_SUCCESS(rv, rv);
// Look up the folder at this URI and use it to retrieve the rgith message
// header.
nsCString folderSpec;
rv = folderUri->GetSpec(folderSpec);
NS_ENSURE_SUCCESS(rv, rv);
@ -158,38 +352,126 @@ nsresult EwsService::MsgHdrFromUri(nsIURI* uri, nsIMsgDBHdr** _retval) {
return folder->GetMessageHeader(key, _retval);
}
// Creates a URI that can be used to retrieve a message via an
// `EwsMessageChannel` from the provided spec string.
//
// The URI spec is assumed to always use the `ews-message` scheme.
nsresult EwsService::NewURIForChannel(const nsACString& spec,
nsIURI** channelUri) {
nsCOMPtr<nsIURI> hdrUri;
nsresult rv = NS_NewURI(getter_AddRefs(hdrUri), spec);
nsresult EwsService::GetMessageContent(const nsACString& messageURI,
nsIDocShell* displayDocShell,
nsIStreamListener* streamListener) {
// Get the matching nsIMsgDBHdr for the message URI.
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), messageURI);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgDBHdr> hdr;
rv = MsgHdrFromUri(hdrUri, getter_AddRefs(hdr));
rv = MsgHdrFromUri(uri, getter_AddRefs(hdr));
NS_ENSURE_SUCCESS(rv, rv);
nsCString ewsId;
rv = hdr->GetStringProperty(ID_PROPERTY, ewsId);
// Check if the folder the message is in has a local ("offline") copy of the
// message's content.
nsCOMPtr<nsIMsgFolder> folder;
rv = hdr->GetFolder(getter_AddRefs(folder));
NS_ENSURE_SUCCESS(rv, rv);
if (ewsId.IsEmpty()) {
NS_ERROR(nsPrintfCString("message %s in EWS folder has no EWS ID",
nsPromiseFlatCString(spec).get())
.get());
return NS_ERROR_UNEXPECTED;
nsMsgKey msgKey;
rv = hdr->GetMessageKey(&msgKey);
NS_ENSURE_SUCCESS(rv, rv);
bool offlineAvailable;
rv = folder->HasMsgOffline(msgKey, &offlineAvailable);
NS_ENSURE_SUCCESS(rv, rv);
if (offlineAvailable) {
// If the message content is available locally, serve it to the docshell or
// stream listener if one is provided.
if (displayDocShell) {
return DisplayMessage(uri, displayDocShell);
}
if (streamListener) {
RefPtr<EwsOfflineMessageChannel> channel =
new EwsOfflineMessageChannel(uri);
return channel->AsyncOpen(streamListener);
}
} else {
// Otherwise, download the message from the server.
return DownloadMessage(uri, hdr, displayDocShell, streamListener);
}
// We want to provide the channel with a URI containing sufficient information
// to identify the associated incoming mail account in addition to the message
// ID. We expect that the URI passed to this method is an `ews-message://` URI
// with username, hostname, and any distinguishing port, so we retain that
// data in producing a loadable URI.
return NS_MutateURI(hdrUri)
.SetScheme("x-moz-ews"_ns)
.SetPathQueryRef(ewsId)
.Finalize(channelUri);
return NS_OK;
}
nsresult EwsService::DownloadMessage(nsIURI* messageURI, nsIMsgDBHdr* hdr,
nsIDocShell* displayDocShell,
nsIStreamListener* streamListener) {
// Retrieve the EWS ID of the message we want to download.
nsCString ewsId;
nsresult rv = hdr->GetStringProperty(ID_PROPERTY, ewsId);
NS_ENSURE_SUCCESS(rv, rv);
// Look up the incoming server for this message, from which we can get an EWS
// client.
nsCOMPtr<nsIMsgAccountManager> accountManager =
do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
// `FindServerByURI()` expects that the URI passed in has a scheme matching
// the value returned by an incoming server's `GetType()` method. In our case,
// that should be `ews`.
nsCOMPtr<nsIURI> serverUri;
rv = NS_MutateURI(messageURI).SetScheme("ews"_ns).Finalize(serverUri);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgIncomingServer> server;
rv = accountManager->FindServerByURI(serverUri, getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
// Get an EWS client from the incoming server, and start downloading the
// message content.
nsCOMPtr<IEwsIncomingServer> ewsServer = do_QueryInterface(server, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<IEwsClient> client;
rv = ewsServer->GetEwsClient(getter_AddRefs(client));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<MessageFetchListener> listener = new MessageFetchListener(
messageURI, hdr, displayDocShell, streamListener);
return client->GetMessage(ewsId, listener);
}
nsresult DisplayMessage(nsIURI* messageURI, nsIDocShell* docShell) {
NS_ENSURE_ARG_POINTER(messageURI);
NS_ENSURE_ARG_POINTER(docShell);
// At this point, the path to the message URI is expected to look like
// /Path/To/Folder#MessageKey. With this format, if the user switches between
// messages in the same folder, the docshell believes we're still in the same
// document (because only the fragment/ref changed), and skip creating a
// channel for any message except the first one. So we need to transform the
// path into /Path/To/Folder/MessageKey.
nsCString ref;
nsresult rv = messageURI->GetRef(ref);
NS_ENSURE_SUCCESS(rv, rv);
nsCString path;
rv = messageURI->GetFilePath(path);
NS_ENSURE_SUCCESS(rv, rv);
path.Append("/");
path.Append(ref);
// "x-moz-ews" is the scheme we use for URIs that must be used for channels
// opened via a protocol handler consumer (such as a docshell or the I/O
// service). These channels are expected to serve the raw content RFC822
// content of the message referred to by the URI.
nsCOMPtr<nsIURI> channelURI;
rv = NS_MutateURI(messageURI)
.SetScheme("x-moz-ews"_ns)
.SetPathQueryRef(path)
.Finalize(channelURI);
NS_ENSURE_SUCCESS(rv, rv);
// Load the message through the provided docshell.
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(channelURI);
loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
loadState->SetFirstParty(false);
loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
return docShell->LoadURI(loadState, false);
}

Просмотреть файл

@ -6,6 +6,7 @@
#define __COMM_MAILNEWS_PROTOCOLS_EWS_SERVICE_H
#include "nsIMsgMessageService.h"
#include "nsIMsgHdr.h"
class EwsService : public nsIMsgMessageService {
public:
@ -18,8 +19,45 @@ class EwsService : public nsIMsgMessageService {
virtual ~EwsService();
private:
// Extracts the message key as a string from a message URI. Message URIs are
// expected in the form:
// ews-message://{user}@{server}/{Path/To/Folder}#{MessageKey}
nsresult MsgKeyStringFromMessageURI(nsIURI* uri, nsACString& msgKey);
// Extracts the message key as a string from a URI used by an EWS message
// channel. Such URIs are expected in the form:
// x-moz-ews://{user}@{server}/{Path/To/Folder}/{MessageKey}
// This method also returns the URI path to the folder, i.e. the path from the
// original URI without the message key.
nsresult MsgKeyStringFromChannelURI(nsIURI* uri, nsACString& msgKey,
nsACString& folderURIPath);
// Retrieves the message header matching the provided URI.
//
// The URI is expected to be either a message URI or one used by an EWS
// message channel, see the documentation for `MsgKeyStringFromMessageURI` and
// `MsgKeyStringFromChannelURI` respectively for the expected form of each
// supported URI.
nsresult MsgHdrFromUri(nsIURI* uri, nsIMsgDBHdr** _retval);
nsresult NewURIForChannel(const nsACString& spec, nsIURI** channelUri);
// Retrieves the content of the message referenced by the provided message
// URI. If the message content does not already exist in the offline store, it
// is downloaded, stored, and then served.
//
// If `displayDocShell` is not null, then it is used to render the message.
// Otherwise, if `streamListener` is not null, the message content is streamed
// to it.
nsresult GetMessageContent(const nsACString& messageURI,
nsIDocShell* displayDocShell,
nsIStreamListener* streamListener);
// Downloads the content of the message referenced by the given message URI.
// Once the message content has been downloaded, it is stored to the relevant
// offline store, and passed onto the provided docshell or stream listener
// similarly to `GetMessageContent`.
nsresult DownloadMessage(nsIURI* messageURI, nsIMsgDBHdr* hdr,
nsIDocShell* displayDocShell,
nsIStreamListener* streamListener);
};
#endif

Просмотреть файл

@ -17,6 +17,7 @@ interface nsIMsgCopyServiceListener;
interface IEwsFolderCallbacks;
interface IEwsMessageCallbacks;
interface IEwsMessageDeleteCallbacks;
interface IEWSMessageFetchCallbacks;
[uuid(4a117361-653b-48a5-9ddb-588482ef9dbb)]
interface IEwsClient : nsISupports
@ -30,7 +31,7 @@ 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);
void getMessage(in AUTF8String id, in IEWSMessageFetchCallbacks callbacks);
void changeReadStatus(in Array<AUTF8String> messageIds, in boolean readStatus);
/**
@ -84,3 +85,19 @@ interface IEwsMessageDeleteCallbacks : nsISupports
void onRemoteDeleteSuccessful();
void onError(in IEwsClient_Error err, in AUTF8String desc);
};
/**
* A listener used when downloading message content.
*
* Its shape is loosely based on `nsIStreamListener`, which cannot be used in
* this instance because we don't always have a request/channel that can be used
* in method calls when fetching a message's content (and using `nullptr`
* everywhere is quite ugly and potentially unsafe).
*/
[uuid(027150b1-d127-41a9-8945-18f9374755b3)]
interface IEWSMessageFetchCallbacks : nsISupports
{
void onFetchStart();
void onDataAvailable(in nsIInputStream aInputStream, in unsigned long aCount);
void onFetchStop(in nsresult status);
};

Просмотреть файл

@ -25,7 +25,10 @@ Classes = [
},
{
"cid": "{c3d44b68-11f1-421f-8f4b-706df13df64a}",
"contract_ids": ["@mozilla.org/messenger/messageservice;1?type=ews-message"],
"contract_ids": [
"@mozilla.org/messenger/messageservice;1?type=ews-message",
"@mozilla.org/messenger/messageservice;1?type=ews",
],
"type": "EwsService",
"headers": ["/comm/mailnews/protocols/ews/src/EwsService.h"],
},

Просмотреть файл

@ -5,7 +5,7 @@
SOURCES += [
"EwsFolder.cpp",
"EwsIncomingServer.cpp",
"EwsMessageChannel.cpp",
"EwsOfflineMessageChannel.cpp",
"EwsProtocolHandler.cpp",
"EwsProtocolInfo.cpp",
"EwsService.cpp",

Просмотреть файл

@ -35,9 +35,9 @@ use uuid::Uuid;
use xpcom::{
getter_addrefs,
interfaces::{
nsIMsgCopyServiceListener, nsIMsgDBHdr, nsIMsgOutgoingListener, nsIRequest,
nsIStreamListener, nsIStringInputStream, nsIURI, nsMsgFolderFlagType, nsMsgFolderFlags,
nsMsgKey, nsMsgMessageFlags, IEwsClient, IEwsFolderCallbacks, IEwsMessageCallbacks,
nsIMsgCopyServiceListener, nsIMsgDBHdr, nsIMsgOutgoingListener, nsIStringInputStream,
nsIURI, nsMsgFolderFlagType, nsMsgFolderFlags, nsMsgKey, nsMsgMessageFlags,
IEWSMessageFetchCallbacks, IEwsClient, IEwsFolderCallbacks, IEwsMessageCallbacks,
IEwsMessageDeleteCallbacks,
},
RefPtr,
@ -422,16 +422,13 @@ impl XpComEwsClient {
pub(crate) async fn get_message(
self,
id: String,
request: RefPtr<nsIRequest>,
listener: RefPtr<nsIStreamListener>,
callbacks: RefPtr<IEWSMessageFetchCallbacks>,
) {
unsafe { listener.OnStartRequest(&*request) };
unsafe { callbacks.OnFetchStart() };
// Call an inner function to perform the operation in order to allow us
// to handle errors while letting the inner function simply propagate.
let result = self
.get_message_inner(id.clone(), &request, &listener)
.await;
let result = self.get_message_inner(id.clone(), &callbacks).await;
let status = match result {
Ok(_) => nserror::NS_OK,
@ -442,14 +439,13 @@ impl XpComEwsClient {
}
};
unsafe { listener.OnStopRequest(&*request, status) };
unsafe { callbacks.OnFetchStop(status) };
}
async fn get_message_inner(
self,
id: String,
request: &nsIRequest,
listener: &nsIStreamListener,
callbacks: &IEWSMessageFetchCallbacks,
) -> Result<(), XpComEwsError> {
let items = self.get_items([id], &[], true).await?;
if items.len() != 1 {
@ -505,8 +501,7 @@ impl XpComEwsClient {
// before the stream is dropped.
unsafe { stream.SetData(mime_content.as_ptr() as *const c_char, len) }.to_result()?;
unsafe { listener.OnDataAvailable(&*request, &*stream.coerce(), 0, len as u32) }
.to_result()?;
unsafe { callbacks.OnDataAvailable(&*stream.coerce(), len as u32) }.to_result()?;
Ok(())
}

Просмотреть файл

@ -14,8 +14,8 @@ use thin_vec::ThinVec;
use url::Url;
use xpcom::{
interfaces::{
nsIInputStream, nsIMsgCopyServiceListener, nsIMsgIncomingServer, nsIRequest,
nsIStreamListener, IEwsFolderCallbacks, IEwsMessageCallbacks, IEwsMessageDeleteCallbacks,
nsIInputStream, nsIMsgCopyServiceListener, nsIMsgIncomingServer, IEWSMessageFetchCallbacks,
IEwsFolderCallbacks, IEwsMessageCallbacks, IEwsMessageDeleteCallbacks,
},
nsIID, xpcom_method, RefPtr,
};
@ -162,12 +162,11 @@ impl XpcomEwsBridge {
Ok(())
}
xpcom_method!(get_message => GetMessage(id: *const nsACString, request: *const nsIRequest, listener: *const nsIStreamListener));
xpcom_method!(get_message => GetMessage(id: *const nsACString, callbacks: *const IEWSMessageFetchCallbacks));
fn get_message(
&self,
id: &nsACString,
request: &nsIRequest,
listener: &nsIStreamListener,
callbacks: &IEWSMessageFetchCallbacks,
) -> Result<(), nsresult> {
let client = self.try_new_client()?;
@ -175,11 +174,7 @@ impl XpcomEwsBridge {
// this scope, so spawn it as a detached `moz_task`.
moz_task::spawn_local(
"get_message",
client.get_message(
id.to_utf8().into(),
RefPtr::new(request),
RefPtr::new(listener),
),
client.get_message(id.to_utf8().into(), RefPtr::new(callbacks)),
)
.detach();