Skip to content

Instantly share code, notes, and snippets.

@babolivier
Created November 3, 2025 15:56
Show Gist options
  • Select an option

  • Save babolivier/d9f1b0198112d0dd194d69a3525eed51 to your computer and use it in GitHub Desktop.

Select an option

Save babolivier/d9f1b0198112d0dd194d69a3525eed51 to your computer and use it in GitHub Desktop.
diff --git a/mailnews/protocols/ews/src/EwsIncomingServer.cpp b/mailnews/protocols/ews/src/EwsIncomingServer.cpp
--- a/mailnews/protocols/ews/src/EwsIncomingServer.cpp
+++ b/mailnews/protocols/ews/src/EwsIncomingServer.cpp
@@ -104,6 +104,38 @@ NS_IMETHODIMP EwsBiffUrlListener::OnStop
} // namespace
+/**
+ * A timer used to delay requests when syncing multiple folders at once. See the
+ * comment in `EwsIncomingServer::SyncFolders` for more information.
+ */
+class EwsNTLMSyncTimer : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit EwsNTLMSyncTimer(std::function<void()> done)
+ : mDone(std::move(done)) {};
+
+ protected:
+ virtual ~EwsNTLMSyncTimer() = default;
+
+ private:
+ std::function<void()> mDone;
+};
+
+NS_IMPL_ISUPPORTS(EwsNTLMSyncTimer, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP EwsNTLMSyncTimer::GetName(nsACString& name) {
+ name.Assign("EwsNTLMSyncTimer");
+ return NS_OK;
+}
+
+NS_IMETHODIMP EwsNTLMSyncTimer::Notify(nsITimer* timer) {
+ mDone();
+ return NS_OK;
+}
+
NS_IMPL_ADDREF_INHERITED(EwsIncomingServer, nsMsgIncomingServer)
NS_IMPL_RELEASE_INHERITED(EwsIncomingServer, nsMsgIncomingServer)
NS_IMPL_QUERY_HEAD(EwsIncomingServer)
@@ -498,20 +530,65 @@ nsresult EwsIncomingServer::SyncFolderLi
nsresult EwsIncomingServer::SyncFolders(
const nsTArray<RefPtr<nsIMsgFolder>>& folders, nsIMsgWindow* aMsgWindow,
nsIUrlListener* urlListener) {
+ nsMsgAuthMethodValue authMethod;
+ MOZ_TRY(GetAuthMethod(&authMethod));
+
// 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 EWS client should handle any kind of rate limiting well enough,
// so this improvement can come later.
+ uint32_t delay = 0;
+ nsCOMPtr<nsIMsgWindow> window = aMsgWindow;
+ nsCOMPtr<nsIUrlListener> listener = urlListener;
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());
+ nsCString name;
+ folder->GetName(name);
+
+ auto syncFn = [folder, window, listener, name]() {
+ nsresult rv = folder->GetNewMessages(window, listener);
+ if (NS_FAILED(rv)) {
+ // If we encounter an error, just log it rather than fail the whole
+ // sync.
+ NS_ERROR(nsPrintfCString("failed to get new messages for folder %s: %s",
+ name.get(), mozilla::GetStaticErrorName(rv))
+ .get());
+ }
+ };
+
+ // FIXME: NTLM works in a fairly different way than other supported
+ // authentication methods: request are authenticated by a cookie created
+ // from a challenge-based flow spanning multiple requests. This means that
+ // if an authentication error happens while we're trying to update a bunch
+ // of folders at once, properly handling it with the current EWS client
+ // architecture gets really difficult.
+ //
+ // So, as a temporary solution, we delay each request from each other in an
+ // attempt to ensure authentication errors are resolved before the next
+ // request. The plan is to improve the current EWS client architecture to
+ // address this situation, at which point this workaround will be removed,
+ // before we update the frontend to offer NTLM to EWS users.
+ if (authMethod == nsMsgAuthMethod::NTLM) {
+ delay += 500;
+
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+ nsresult rv = timer->InitWithCallback(new EwsNTLMSyncTimer(syncFn), delay,
+ nsITimer::TYPE_ONE_SHOT);
+
+ if (NS_FAILED(rv)) {
+ // If we encounter an error, just log it rather than fail the whole
+ // sync.
+ NS_ERROR(
+ nsPrintfCString("failed to schedule new messages for folder %s: %s",
+ name.get(), mozilla::GetStaticErrorName(rv))
+ .get());
+ }
+
+ // Add the timer to the member array to prevent it from being
+ // automatically dropped.
+ mNTLMSyncTimers.AppendElement(timer);
+ } else {
+ syncFn();
}
}
diff --git a/mailnews/protocols/ews/src/EwsIncomingServer.h b/mailnews/protocols/ews/src/EwsIncomingServer.h
--- a/mailnews/protocols/ews/src/EwsIncomingServer.h
+++ b/mailnews/protocols/ews/src/EwsIncomingServer.h
@@ -7,6 +7,7 @@
#include "IEwsIncomingServer.h"
#include "msgIOAuth2Module.h"
+#include "nsITimer.h"
#include "nsMsgIncomingServer.h"
#define EWS_INCOMING_SERVER_IID \
@@ -94,6 +95,12 @@ class EwsIncomingServer : public nsMsgIn
nsresult UpdateTrashFolder();
nsCOMPtr<msgIOAuth2Module> mOAuth2Module;
+
+ // An array of timers used when syncing the account, if the account uses NTLM
+ // (see the comment in `EwsIncomingServer::SyncFolders` for why we're using
+ // timers). We need to store them here to prevent them from being dropped
+ // after `EwsIncomingServer::SyncFolders` returns.
+ nsTArray<nsCOMPtr<nsITimer>> mNTLMSyncTimers;
};
#endif // COMM_MAILNEWS_PROTOCOLS_EWS_SRC_EWSINCOMINGSERVER_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment