/* 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 "ExchangeIncomingServer.h" #include #include "ExchangeFolder.h" #include "ExchangeListeners.h" #include "ExchangeOAuth2CustomDetails.h" #include "IExchangeClient.h" #include "nsIMsgFolderNotificationService.h" #include "nsIFeedbackService.h" #include "nsIMsgWindow.h" #include "nsIProgressEventSink.h" #include "nsMsgFolderFlags.h" #include "nsMsgUtils.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" #include "OfflineStorage.h" #include "mozilla/Components.h" #include "mozilla/intl/Localization.h" #include "mozilla/StaticPrefs_mail.h" using namespace mozilla; static constexpr auto kDeleteModelPreferenceName = "delete_model"; static constexpr auto kTrashFolderPreferenceName = "trash_folder_path"; constexpr auto kSyncStateTokenProperty = "ewsSyncStateToken"; namespace { class ExchangeBiffUrlListener : public nsIUrlListener { public: static nsresult ForFolders(RefPtr server, nsTArray> folders, ExchangeBiffUrlListener** newListener) { NS_ENSURE_ARG_POINTER(newListener); nsresult rv = NS_OK; nsTArray uris(folders.Length()); for (auto&& folder : folders) { nsAutoCString folderUri; rv = folder->GetURI(folderUri); NS_ENSURE_SUCCESS(rv, rv); uris.AppendElement(std::move(folderUri)); } RefPtr listener = new ExchangeBiffUrlListener(std::move(server), std::move(uris)); listener.forget(newListener); return NS_OK; } NS_DECL_ISUPPORTS; NS_DECL_NSIURLLISTENER; protected: virtual ~ExchangeBiffUrlListener() = default; private: ExchangeBiffUrlListener(RefPtr server, nsTArray syncFolderUris) : mServer(std::move(server)) { for (auto&& folderUri : syncFolderUris) { mCompletionStates.InsertOrUpdate(folderUri, false); } } RefPtr mServer; nsTHashMap mCompletionStates; }; NS_IMPL_ISUPPORTS(ExchangeBiffUrlListener, nsIUrlListener); NS_IMETHODIMP ExchangeBiffUrlListener::OnStartRunningUrl(nsIURI* uri) { return NS_OK; } NS_IMETHODIMP ExchangeBiffUrlListener::OnStopRunningUrl(nsIURI* uri, nsresult exitCode) { nsAutoCString uriString; nsresult rv = uri->GetSpec(uriString); NS_ENSURE_SUCCESS(rv, rv); if (auto lookup = mCompletionStates.Lookup(uriString); lookup) { lookup.Data() = true; } bool allDone = true; for (auto&& entry : mCompletionStates) { if (!entry.GetData()) { allDone = false; break; } } if (allDone) { mServer->SetPerformingBiff(false); } return NS_OK; } } // namespace NS_IMPL_ADDREF_INHERITED(ExchangeIncomingServer, nsMsgIncomingServer) NS_IMPL_RELEASE_INHERITED(ExchangeIncomingServer, nsMsgIncomingServer) NS_IMPL_QUERY_HEAD(ExchangeIncomingServer) NS_IMPL_QUERY_BODY(IExchangeIncomingServer) NS_IMPL_QUERY_TAIL_INHERITING(nsMsgIncomingServer) ExchangeIncomingServer::ExchangeIncomingServer() = default; ExchangeIncomingServer::~ExchangeIncomingServer() {} /** * Creates a new folder with the specified parent, name, and flags. * * If a folder with the specified Exchange ID already exists, then succeed * without creating the folder, assuming the folder has already been created * locally and we are processing the corresponding Exchange message. If a folder * with the same name, but a different Exchange ID exists, then return an error. */ nsresult ExchangeIncomingServer::MaybeCreateFolderWithDetails( const nsACString& id, const nsACString& parentId, const nsACString& name, uint32_t flags) { // Check to see if a folder with the same id already exists. nsCOMPtr existingFolder; nsresult rv = FindFolderWithId(id, getter_AddRefs(existingFolder)); if (NS_SUCCEEDED(rv)) { // We found the folder with the specified ID, which means it's already been // created locally. This can happen during the normal course of operations, // including the most common case in which the user uses thunderbird to // create a folder and the next sync includes the record of folder creation // from Exchange. return NS_OK; } nsCOMPtr parent; rv = FindFolderWithId(parentId, getter_AddRefs(parent)); NS_ENSURE_SUCCESS(rv, rv); // Check that the parent doesn't already contain a folder with the requested // name. In the case where we have a folder with a duplicate name, but either // a differing or no Exchange ID, we can't sync with the server since the // server believes that a folder with the requested name should map to the // requested Exchange ID, so we signal an error. bool containsChildWithRequestedName; rv = parent->ContainsChildNamed(name, &containsChildWithRequestedName); NS_ENSURE_SUCCESS(rv, rv); if (containsChildWithRequestedName) { return NS_MSG_CANT_CREATE_FOLDER; } // In order to persist the folder, we need to create new storage for it with // the message store. This will also take care of adding it as a subfolder of // the parent. nsCOMPtr msgStore; rv = GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr newFolder; rv = msgStore->CreateFolder(parent, name, getter_AddRefs(newFolder)); NS_ENSURE_SUCCESS(rv, rv); // Record the Exchange ID of the folder so that we can translate between local // path and remote ID when needed. rv = newFolder->SetStringProperty(kExchangeIdProperty, id); NS_ENSURE_SUCCESS(rv, rv); // The flags we get from the XPCOM code indicate whether this is a well-known // folder, such as Inbox, Sent Mail, Trash, etc. rv = newFolder->SetFlags(flags); NS_ENSURE_SUCCESS(rv, rv); rv = newFolder->SetName(name); NS_ENSURE_SUCCESS(rv, rv); if (flags & nsMsgFolderFlags::Trash) { nsAutoCString folderPath; rv = FolderPathInServer(newFolder, folderPath); NS_ENSURE_SUCCESS(rv, rv); rv = SetTrashFolderPath(folderPath); NS_ENSURE_SUCCESS(rv, rv); } // Notify any consumers listening for updates regarding the folder's creation. nsCOMPtr notifier = mozilla::components::FolderNotification::Service(); notifier->NotifyFolderAdded(newFolder); rv = parent->NotifyFolderAdded(newFolder); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult ExchangeIncomingServer::UpdateFolderWithDetails( const nsACString& id, const nsACString& parentId, const nsACString& name, nsIMsgWindow* msgWindow) { nsCOMPtr folder; nsresult rv = FindFolderWithId(id, getter_AddRefs(folder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr parentFolder; rv = folder->GetParent(getter_AddRefs(parentFolder)); NS_ENSURE_SUCCESS(rv, rv); // Only initiate the move operation if either the name or the parent of the // updated folder changed. nsAutoCString currentName; MOZ_TRY(folder->GetName(currentName)); nsAutoCString currentParentId; MOZ_TRY( parentFolder->GetStringProperty(kExchangeIdProperty, currentParentId)); // If either the parent or the name of the folder changed, then we have to // initiate a move of the data for the folder, so we rely on the fact that a // move and a rename are the same, except for in the case in which a folder // is solely renamed, it doesn't need to be reparented. However, there is no // performance difference between a rename and a reparent, so we call the // general logic that handles both move and rename here for simplicity. nsCOMPtr newParentFolder; rv = FindFolderWithId(parentId, getter_AddRefs(newParentFolder)); NS_ENSURE_SUCCESS(rv, rv); return LocalRenameOrReparentFolder(folder, newParentFolder, name, msgWindow); } /** * Deletes the folder with the given remote Exchange id. */ nsresult ExchangeIncomingServer::DeleteFolderWithId(const nsACString& id) { nsCOMPtr folder; nsresult rv = FindFolderWithId(id, getter_AddRefs(folder)); // If we found the folder locally, then delete it. Otherwise, assume it's // already been deleted locally. if (NS_SUCCEEDED(rv)) { // We can't use `DeleteSelf` here because the implementation of // `ExchangeFolder` (which we know is the concrete implementation of // `nsIMsgFolder` we are using in the Exchange case) will trigger a remote // delete on the server. Sync is responding to a remote delete, so we have // to get the parent and call `PropagateDelete` directly. nsCOMPtr parentFolder; rv = folder->GetParent(getter_AddRefs(parentFolder)); NS_ENSURE_SUCCESS(rv, rv); rv = parentFolder->PropagateDelete(folder, true); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } /** * Locates the folder associated with this server which has the remote * (Exchange) ID specified, if any. * * This function returns `NS_OK` if, and only if, the folder with the specified * `id` is found. */ nsresult ExchangeIncomingServer::FindFolderWithId(const nsACString& id, nsIMsgFolder** _retval) { // Fail by default; only return success if we actually find the folder we're // looking for. nsresult failureStatus{NS_MSG_ERROR_FOLDER_MISSING}; // We do a breadth-first search on subfolders of the root. RefPtr root; nsresult rv = GetRootFolder(getter_AddRefs(root)); NS_ENSURE_SUCCESS(rv, rv); nsTArray> foldersToScan; foldersToScan.AppendElement(root); while (foldersToScan.Length() != 0) { nsTArray> nextFoldersToScan; for (auto folder : foldersToScan) { // Exchange folder ID is stored as a custom property in the folder store. nsCString folderId; rv = folder->GetStringProperty(kExchangeIdProperty, folderId); if (NS_SUCCEEDED(rv) && folderId.Equals(id)) { folder.forget(_retval); return NS_OK; } if (NS_FAILED(rv)) { // Every Exchange folder should have an Exchange ID, so we've hit a bug // either in recording the IDs on folder creation or in retrieving them // from storage. // We don't want to fail now in case a properly-constructed subfolder // matches the requested ID. Note the failure in case we don't find a // match, then continue the search. failureStatus = rv; // Retrieve the folder's URI as an identifier for logging. nsCString uri; rv = folder->GetURI(uri); if (NS_FAILED(rv)) { // If we can't get the URI either, something is seriously wrong. NS_ERROR("failed to get ewsId property or URI for folder"); failureStatus = rv; } else { NS_WARNING( nsPrintfCString("failed to get ewsId property for folder %s", uri.get()) .get()); } } // This folder didn't match the ID we want. We'll check any subfolders // after we've finished checking everything at the current depth. nsTArray> subfolders; rv = folder->GetSubFolders(subfolders); if (NS_SUCCEEDED(rv)) { nextFoldersToScan.AppendElements(subfolders); } else { NS_WARNING("failed to get subfolders for folder"); failureStatus = rv; } } foldersToScan = std::move(nextFoldersToScan); } return failureStatus; } NS_IMETHODIMP ExchangeIncomingServer::GetPort(int32_t* aPort) { NS_ENSURE_ARG_POINTER(aPort); nsCString ewsURL; nsresult rv = GetExchangeUrl(ewsURL); NS_ENSURE_SUCCESS(rv, rv); int32_t port = -1; // We might not have a valid URL yet. if (!ewsURL.IsEmpty()) { nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), ewsURL); // We might have a URL that's invalid (e.g. set by a test). if (NS_SUCCEEDED(rv)) { rv = uri->GetPort(&port); NS_ENSURE_SUCCESS(rv, rv); } } if (port < 0) { // If we don't have a valid URL yet, or it doesn't specify a port, default // to the relevant one (as per the socket type). nsMsgSocketTypeValue socketType; rv = GetSocketType(&socketType); NS_ENSURE_SUCCESS(rv, rv); port = socketType == nsMsgSocketType::SSL ? 443 : 80; } *aPort = port; return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::SyncFolderHierarchy( IExchangeSimpleOperationListener* listener, nsIMsgWindow* window) { const auto refListener = RefPtr{listener}; return SyncFolderList(window, [refListener]() { return refListener->OnOperationSuccess({}, false); }); } nsresult ExchangeIncomingServer::SyncFolderList( nsIMsgWindow* aMsgWindow, std::function postSyncCallback) { // Exchange provides us an opaque value which specifies the last version of // upstream folders we received. Provide that to simplify sync. nsCString syncStateToken; nsresult rv = GetStringValue(kSyncStateTokenProperty, syncStateToken); if (NS_FAILED(rv)) { syncStateToken = EmptyCString(); } // Format the message we'll show the user while we wait for the remote // operation to complete. // // If `postSyncCallback` also involves syncing the message list of each // folder, we'll also trigger messages for individual folders, but the // status bar implementation ensures messages stay up long enough that they // don't "flicker" too quickly. So the resulting UX will be the following // messages appearing ~1s apart: // * Looking for new messages for [account name]… // * Looking for new messages in [folder 1 name]… // * Looking for new messages in [folder 2 name]… // * etc. RefPtr l10n = intl::Localization::Create({"messenger/activityFeedback.ftl"_ns}, true); auto l10nArgs = dom::Optional(); l10nArgs.Construct(); nsCString accountName; rv = GetPrettyName(accountName); NS_ENSURE_SUCCESS(rv, rv); auto idArg = l10nArgs.Value().Entries().AppendElement(); idArg->mKey = "accountName"_ns; idArg->mValue.SetValue().SetAsUTF8String().Assign(accountName); ErrorResult error; nsCString message; l10n->FormatValueSync("looking-for-messages-account"_ns, l10nArgs, message, error); // Show the message in the status bar. nsCOMPtr feedback = mozilla::components::Feedback::Service(); if (feedback) { feedback->ReportStatus(message, "start-meteors"_ns); } // Define the listener and its callbacks. auto onNewRootFolder = [self = RefPtr(this)](const nsACString& id) { RefPtr root; nsresult rv = self->GetRootFolder(getter_AddRefs(root)); NS_ENSURE_SUCCESS(rv, rv); return root->SetStringProperty(kExchangeIdProperty, id); }; auto onFolderCreated = [self = RefPtr(this)]( const nsACString& id, const nsACString& parentId, const nsACString& name, uint32_t flags) { return self->MaybeCreateFolderWithDetails(id, parentId, name, flags); }; nsCOMPtr msgWindow = aMsgWindow; auto onFolderUpdated = [self = RefPtr(this), msgWindow]( const nsACString& id, const nsACString& parentId, const nsACString& name) { return self->UpdateFolderWithDetails(id, parentId, name, msgWindow); }; auto onFolderDeleted = [self = RefPtr(this)](const nsACString& id) { return self->DeleteFolderWithId(id); }; auto onSyncStateTokenChanged = [self = RefPtr(this)](const nsACString& syncStateToken) { return self->SetStringValue(kSyncStateTokenProperty, syncStateToken); }; auto onSuccess = [postSyncCallback]() { // Reset the status bar since the remote operation has finished. nsCOMPtr feedback = mozilla::components::Feedback::Service(); if (feedback) { feedback->ReportStatus(""_ns, "stop-meteors"_ns); } return postSyncCallback(); }; auto onError = [](nsresult _status) { // Reset the status bar since the remote operation has finished. nsCOMPtr feedback = mozilla::components::Feedback::Service(); if (feedback) { feedback->ReportStatus(""_ns, "stop-meteors"_ns); } return NS_OK; }; RefPtr listener = new ExchangeFolderSyncListener( onNewRootFolder, onFolderCreated, onFolderUpdated, onFolderDeleted, onSyncStateTokenChanged, onSuccess, onError); // Sync the folder tree for the whole account. RefPtr client; MOZ_TRY(GetProtocolClient(getter_AddRefs(client))); return client->SyncFolderHierarchy(listener, syncStateToken); } nsresult ExchangeIncomingServer::SyncFolders( const nsTArray>& folders, nsIMsgWindow* aMsgWindow, nsIUrlListener* urlListener) { // TODO: For now, we sync every folder at once, but obviously that's not an // amazing solution. In the future, we should probably try to maintain some // kind of queue so we can properly batch and sync folders. In the meantime, // though, the Exchange client should handle any kind of rate limiting well // enough, so this improvement can come later. for (const auto& folder : folders) { nsresult rv = folder->GetNewMessages(aMsgWindow, urlListener); if (NS_FAILED(rv)) { // If we encounter an error, just log it rather than fail the whole sync. nsCString name; folder->GetName(name); NS_ERROR(nsPrintfCString("failed to get new messages for folder %s: %s", name.get(), mozilla::GetStaticErrorName(rv)) .get()); } } return NS_OK; } nsresult ExchangeIncomingServer::SyncAllFolders(nsIMsgWindow* aMsgWindow, nsIUrlListener* urlListener) { nsCOMPtr rootFolder; MOZ_TRY(GetRootFolder(getter_AddRefs(rootFolder))); nsTArray> msgFolders; MOZ_TRY(rootFolder->GetDescendants(msgFolders)); return SyncFolders(msgFolders, aMsgWindow, urlListener); } NS_IMETHODIMP ExchangeIncomingServer::GetPassword(nsAString& password) { nsMsgAuthMethodValue authMethod; MOZ_TRY(GetAuthMethod(&authMethod)); // `nsMsgIncomingServer` doesn't read the password at startup, so we want to // ensure we have read its value from the logins manager at least once. If it // changes, `GetPasswordWithUI` (implemented in `nsMsgIncomingServer` too) // takes care of updating `m_password`. nsAutoCString value; MOZ_TRY(mPasswordModule->GetCachedPassword(value)); if (value.IsEmpty() && authMethod == nsMsgAuthMethod::passwordCleartext) { MOZ_TRY(GetPasswordWithoutUI()); } return nsMsgIncomingServer::GetPassword(password); } NS_IMETHODIMP ExchangeIncomingServer::GetLocalStoreType( nsACString& aLocalStoreType) { if (StaticPrefs::mail_graph_enabled()) { // For Exchange and graph, the local store type is the same value as the // server type. return GetType(aLocalStoreType); } aLocalStoreType.Assign("ews"); return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::GetLocalDatabaseType( nsACString& aLocalDatabaseType) { aLocalDatabaseType.AssignLiteral("mailbox"); return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::GetCanBeDefaultServer( bool* canBeDefaultServer) { NS_ENSURE_ARG_POINTER(canBeDefaultServer); *canBeDefaultServer = true; return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::GetOfflineSupportLevel( int32_t* aSupportLevel) { NS_ENSURE_ARG_POINTER(aSupportLevel); *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE; return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::GetNewMessages( nsIMsgFolder* aFolder, nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener) { // Explicitly make the parameters to the lambda `nsCOMPtr`s, otherwise the // clang plugin will think we're trying to bypass the ref counting. nsCOMPtr folder = aFolder; nsCOMPtr window = aMsgWindow; nsCOMPtr urlListener = aUrlListener; // Sync the folder list for the account, then sync the message list for the // specific folder. return SyncFolderList( aMsgWindow, [self = RefPtr(this), folder, window, urlListener]() { // Check if we're getting messages for the whole // folder here. If so, the intent is likely that the // user wants to synchronize all the folders on the // account. bool isServer; nsresult rv = folder->GetIsServer(&isServer); NS_ENSURE_SUCCESS(rv, rv); if (isServer) { return self->SyncAllFolders(window, urlListener); } // Synchronizing the folder list may have invalidated the folder that // sync was selected for by moving the folder to a new location. If that // is the case, then the Exchange ID will be invalidated and we can no // longer sync that folder. nsAutoCString originalExchangeId; rv = folder->GetStringProperty(kExchangeIdProperty, originalExchangeId); if (NS_FAILED(rv)) { // Assume the original folder moved and return success. return NS_OK; } // If this is not the root folder, synchronize its // message list normally. return folder->GetNewMessages(window, urlListener); }); } NS_IMETHODIMP ExchangeIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow) { nsCOMPtr window = aMsgWindow; nsresult rv = SetPerformingBiff(true); NS_ENSURE_SUCCESS(rv, rv); // Sync the folder list for the account. Then sync the message list of each // folder in the tree. return SyncFolderList(aMsgWindow, [self = RefPtr(this), window]() { nsCOMPtr rootFolder; nsresult rv = self->GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); nsTArray> msgFolders; rv = rootFolder->GetDescendants(msgFolders); NS_ENSURE_SUCCESS(rv, rv); RefPtr listener; rv = ExchangeBiffUrlListener::ForFolders(self, msgFolders.Clone(), getter_AddRefs(listener)); NS_ENSURE_SUCCESS(rv, rv); return self->SyncFolders(msgFolders, window, listener); }); } NS_IMETHODIMP ExchangeIncomingServer::PerformExpand(nsIMsgWindow* aMsgWindow) { // Sync the folder list; we don't want to do anything after that so we just // pass a no-op lambda. return SyncFolderList(aMsgWindow, []() { return NS_OK; }); } NS_IMETHODIMP ExchangeIncomingServer::VerifyLogon(nsIUrlListener* aUrlListener, nsIMsgWindow* aMsgWindow, nsIURI** _retval) { NS_ENSURE_ARG_POINTER(aUrlListener); // Perform a connectivity check via an Exchange client. Ideally we should set // `_retval` to something non-null. But we don't have a good value for it, and // the `ConfigVerifier` (which this call very likely originates from) will // only be doing `nsIMsgMailNewsUrl`-related operations to it, which doesn't // apply to us. RefPtr client; MOZ_TRY(GetProtocolClient(getter_AddRefs(client))); return client->CheckConnectivity(aUrlListener, _retval); } /** * Creates an instance of the Exchange client interface, allowing us to perform * operations against the relevant Exchange instance. */ NS_IMETHODIMP ExchangeIncomingServer::GetProtocolClient( IExchangeClient** ewsClient) { NS_ENSURE_ARG_POINTER(ewsClient); // Only create a new client if we don't already have one - otherwise reuse the // one we have. if (!mClient) { nsresult rv = NS_OK; if (StaticPrefs::mail_graph_enabled()) { nsAutoCString type; rv = GetType(type); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString contractId{"@mozilla.org/messenger/"}; contractId.Append(type); contractId.Append("-client;1"); mClient = do_CreateInstance(contractId.Data(), &rv); NS_ENSURE_SUCCESS(rv, rv); } else { mClient = do_CreateInstance("@mozilla.org/messenger/ews-client;1", &rv); NS_ENSURE_SUCCESS(rv, rv); } nsAutoCString endpoint; rv = GetExchangeUrl(endpoint); NS_ENSURE_SUCCESS(rv, rv); bool overrideOAuth; rv = GetExchangeOverrideOAuthDetails(&overrideOAuth); NS_ENSURE_SUCCESS(rv, rv); // Set up the client object with access details. rv = mClient->Initialize(endpoint, this); NS_ENSURE_SUCCESS(rv, rv); } NS_IF_ADDREF(*ewsClient = mClient); return NS_OK; } nsresult ExchangeIncomingServer::GetTrashFolder(nsIMsgFolder** trashFolder) { NS_ENSURE_ARG_POINTER(trashFolder); *trashFolder = nullptr; nsAutoCString trashFolderPath; nsresult rv = GetTrashFolderPath(trashFolderPath); NS_ENSURE_SUCCESS(rv, rv); if (trashFolderPath.IsEmpty()) { return NS_OK; } nsCOMPtr rootFolder; rv = GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr foundTrashFolder; rv = GetExistingFolder(rootFolder, trashFolderPath, getter_AddRefs(foundTrashFolder)); NS_ENSURE_SUCCESS(rv, rv); foundTrashFolder.forget(trashFolder); return NS_OK; } nsresult ExchangeIncomingServer::UpdateTrashFolder() { nsCOMPtr trashFolder; nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder)); NS_ENSURE_SUCCESS(rv, rv); if (trashFolder) { rv = trashFolder->SetFlag(nsMsgFolderFlags::Trash); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::SetDeleteModel( IExchangeIncomingServer::DeleteModel value) { using DeleteModel = IExchangeIncomingServer::DeleteModel; if (value != DeleteModel::PERMANENTLY_DELETE && value != DeleteModel::MOVE_TO_TRASH) { return NS_ERROR_ILLEGAL_VALUE; } if (value == DeleteModel::MOVE_TO_TRASH) { nsresult rv = UpdateTrashFolder(); NS_ENSURE_SUCCESS(rv, rv); } return SetIntValue(kDeleteModelPreferenceName, value); } NS_IMETHODIMP ExchangeIncomingServer::GetDeleteModel( IExchangeIncomingServer::DeleteModel* returnValue) { NS_ENSURE_ARG(returnValue); int32_t modelCode; nsresult rv = GetIntValue(kDeleteModelPreferenceName, &modelCode); NS_ENSURE_SUCCESS(rv, rv); if (modelCode != 0 && modelCode != 1) { return NS_ERROR_UNEXPECTED; } *returnValue = static_cast(modelCode); return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::SetTrashFolderPath( const nsACString& path) { if (path.IsEmpty()) { return NS_OK; } // Check that the path exists. nsCOMPtr rootFolder; nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); // Make sure that the path to the new trash folder exists. nsCOMPtr newTrashFolder; rv = GetExistingFolder(rootFolder, path, getter_AddRefs(newTrashFolder)); NS_ENSURE_SUCCESS(rv, rv); // Clear the flag on the current trash folder. nsCOMPtr currentTrashFolder; rv = GetTrashFolder(getter_AddRefs(currentTrashFolder)); NS_ENSURE_SUCCESS(rv, rv); if (currentTrashFolder) { rv = currentTrashFolder->ClearFlag(nsMsgFolderFlags::Trash); NS_ENSURE_SUCCESS(rv, rv); } rv = SetStringValue(kTrashFolderPreferenceName, path); NS_ENSURE_SUCCESS(rv, rv); return UpdateTrashFolder(); } NS_IMETHODIMP ExchangeIncomingServer::GetTrashFolderPath( nsACString& returnValue) { return GetStringValue(kTrashFolderPreferenceName, returnValue); } NS_IMETHODIMP ExchangeIncomingServer::GetCanSearchMessages( bool* canSearchMessages) { NS_ENSURE_ARG_POINTER(canSearchMessages); *canSearchMessages = true; return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::GetExchangeUrl(nsACString& value) { return GetStringValue("ews_url", value); } NS_IMETHODIMP ExchangeIncomingServer::SetExchangeUrl(const nsACString& value) { return SetStringValue("ews_url", value); } NS_IMETHODIMP ExchangeIncomingServer::Shutdown() { if (mClient) { // The server is being removed, either because the user has removed the // account, or because Thunderbird is shutting down. If we have a client, we // need to let it know it must shut down as well. // // We're not doing this in `CloseCachedConnections()` because the // expectation (at least from tests) is that the server/client can be reused // afterwards. To illustrate with the case of Exchange, this would be closer // to stopping all of the operation queue's runners but not stopping the // queue itself (so more runners can be started afterwards). return mClient->Shutdown(); } return nsMsgIncomingServer::Shutdown(); } NS_IMETHODIMP ExchangeIncomingServer::GetProtocolClientRunning(bool* running) { NS_ENSURE_ARG_POINTER(running); if (!mClient) { *running = false; return NS_OK; } return mClient->GetRunning(running); } NS_IMETHODIMP ExchangeIncomingServer::GetProtocolClientIdle(bool* idle) { NS_ENSURE_ARG_POINTER(idle); if (!mClient) { *idle = false; return NS_OK; } return mClient->GetIdle(idle); } namespace { nsresult GetDetailsForHostname(ExchangeIncomingServer* server, ExchangeOAuth2CustomDetails** details) { NS_ENSURE_ARG_POINTER(details); nsAutoCString hostname; nsresult rv = server->GetHostName(hostname); NS_ENSURE_SUCCESS(rv, rv); RefPtr result; rv = ExchangeOAuth2CustomDetails::ForHostname(hostname, getter_AddRefs(result)); NS_ENSURE_SUCCESS(rv, rv); result.forget(details); return NS_OK; } template nsresult GetOAuthProperty(ExchangeIncomingServer* server, nsACString& value, F&& accessor) { RefPtr details; nsresult rv = GetDetailsForHostname(server, getter_AddRefs(details)); NS_ENSURE_SUCCESS(rv, rv); const std::optional found = accessor(*details); if (found) { value.Assign(*found); } else { value.Assign(""); } return NS_OK; } template nsresult SetOAuthProperty(ExchangeIncomingServer* server, const nsACString& value, F&& accessor) { RefPtr details; nsresult rv = GetDetailsForHostname(server, getter_AddRefs(details)); NS_ENSURE_SUCCESS(rv, rv); return accessor(*details, value); } } // namespace NS_IMETHODIMP ExchangeIncomingServer::GetExchangeOverrideOAuthDetails( bool* value) { NS_ENSURE_ARG(value); RefPtr details; nsresult rv = GetDetailsForHostname(this, getter_AddRefs(details)); NS_ENSURE_SUCCESS(rv, rv); *value = details->GetConfiguredUseCustomDetails(); return NS_OK; } NS_IMETHODIMP ExchangeIncomingServer::SetExchangeOverrideOAuthDetails( bool value) { NS_ENSURE_ARG(value); RefPtr details; nsresult rv = GetDetailsForHostname(this, getter_AddRefs(details)); NS_ENSURE_SUCCESS(rv, rv); rv = details->SetConfiguredUseCustomDetails(value); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } #define DEFINE_OAUTH_PROPERTY_ACCESSORS(PropertyName, OAuthValueName) \ NS_IMETHODIMP ExchangeIncomingServer::Get##PropertyName(nsACString& value) { \ return GetOAuthProperty(this, value, \ [](const ExchangeOAuth2CustomDetails& details) { \ return details.Get##OAuthValueName(); \ }); \ } \ NS_IMETHODIMP ExchangeIncomingServer::Set##PropertyName( \ const nsACString& value) { \ return SetOAuthProperty( \ this, value, \ [](ExchangeOAuth2CustomDetails& details, const nsACString& value) { \ return details.Set##OAuthValueName(value); \ }); \ } DEFINE_OAUTH_PROPERTY_ACCESSORS(ExchangeApplicationId, ConfiguredApplicationId); DEFINE_OAUTH_PROPERTY_ACCESSORS(ExchangeTenantId, ConfiguredTenant); DEFINE_OAUTH_PROPERTY_ACCESSORS(ExchangeRedirectUri, ConfiguredRedirectUri); DEFINE_OAUTH_PROPERTY_ACCESSORS(ExchangeEndpointHost, ConfiguredEndpointHost); DEFINE_OAUTH_PROPERTY_ACCESSORS(ExchangeOAuthScopes, ConfiguredOAuthScopes); #undef DEFINE_OAUTH_PROPERTY_ACCESSORS