summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap/src/ImapService.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/imap/src/ImapService.jsm')
-rw-r--r--comm/mailnews/imap/src/ImapService.jsm518
1 files changed, 518 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/ImapService.jsm b/comm/mailnews/imap/src/ImapService.jsm
new file mode 100644
index 0000000000..abc4036300
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapService.jsm
@@ -0,0 +1,518 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["ImapService"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ ImapChannel: "resource:///modules/ImapChannel.jsm",
+ MailStringUtils: "resource:///modules/MailStringUtils.jsm",
+});
+
+/**
+ * Set mailnews.imap.jsmodule to true to use this module.
+ *
+ * @implements {nsIImapService}
+ */
+class ImapService {
+ QueryInterface = ChromeUtils.generateQI(["nsIImapService"]);
+
+ constructor() {
+ // Initialize nsIAutoSyncManager.
+ Cc["@mozilla.org/imap/autosyncmgr;1"].getService(Ci.nsIAutoSyncManager);
+ }
+
+ get cacheStorage() {
+ if (!this._cacheStorage) {
+ this._cacheStorage = Services.cache2.memoryCacheStorage(
+ Services.loadContextInfo.custom(false, {})
+ );
+ }
+ return this._cacheStorage;
+ }
+
+ selectFolder(folder, urlListener, msgWindow) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(
+ urlListener || folder.QueryInterface(Ci.nsIUrlListener),
+ msgWindow,
+ runningUrl
+ );
+ runningUrl.updatingFolder = true;
+ client.onReady = () => {
+ client.selectFolder(folder);
+ };
+ });
+ }
+
+ liteSelectFolder(folder, urlListener, msgWindow) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(
+ urlListener || folder.QueryInterface(Ci.nsIUrlListener),
+ msgWindow,
+ runningUrl
+ );
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapLiteSelectFolder;
+ client.onReady = () => {
+ client.selectFolder(folder);
+ };
+ });
+ }
+
+ discoverAllFolders(folder, urlListener, msgWindow) {
+ let server = folder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).imapIncomingServer;
+ if (server.wrappedJSObject.hasDiscoveredFolders) {
+ return;
+ }
+ server.wrappedJSObject.hasDiscoveredFolders = true;
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.discoverAllFolders(folder);
+ };
+ });
+ }
+
+ discoverAllAndSubscribedFolders(folder, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapDiscoverAllAndSubscribedBoxesUrl;
+ client.onReady = () => {
+ client.discoverAllAndSubscribedFolders(folder);
+ };
+ });
+ }
+
+ getListOfFoldersOnServer(server, msgWindow) {
+ this.discoverAllAndSubscribedFolders(
+ server.rootMsgFolder,
+ server.QueryInterface(Ci.nsIUrlListener),
+ msgWindow
+ );
+ }
+
+ subscribeFolder(folder, name, urlListener) {
+ return this._withClient(folder, client => {
+ client.startRunningUrl(urlListener);
+ client.onReady = () => {
+ client.subscribeFolder(folder, name);
+ };
+ });
+ }
+
+ unsubscribeFolder(folder, name, urlListener) {
+ return this._withClient(folder, client => {
+ client.startRunningUrl(urlListener);
+ client.onReady = () => {
+ client.unsubscribeFolder(folder, name);
+ };
+ });
+ }
+
+ addMessageFlags(folder, urlListener, messageIds, flags, messageIdsAreUID) {
+ this._updateMessageFlags("+", folder, urlListener, messageIds, flags);
+ }
+
+ subtractMessageFlags(
+ folder,
+ urlListener,
+ messageIds,
+ flags,
+ messageIdsAreUID
+ ) {
+ this._updateMessageFlags("-", folder, urlListener, messageIds, flags);
+ }
+
+ setMessageFlags(
+ folder,
+ urlListener,
+ outURL,
+ messageIds,
+ flags,
+ messageIdsAreUID
+ ) {
+ outURL.value = this._updateMessageFlags(
+ "",
+ folder,
+ urlListener,
+ messageIds,
+ flags
+ );
+ }
+
+ _updateMessageFlags(action, folder, urlListener, messageIds, flags) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(urlListener, null, runningUrl);
+ client.onReady = () => {
+ client.updateMessageFlags(action, folder, messageIds, flags);
+ };
+ });
+ }
+
+ renameLeaf(folder, newName, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.renameFolder(folder, newName);
+ };
+ });
+ }
+
+ fetchMessage(
+ imapUrl,
+ imapAction,
+ folder,
+ msgSink,
+ msgWindow,
+ displayConsumer,
+ msgIds,
+ convertDataToText
+ ) {
+ imapUrl.imapAction = imapAction;
+ imapUrl.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = msgWindow;
+ if (displayConsumer instanceof Ci.nsIDocShell) {
+ imapUrl
+ .QueryInterface(Ci.nsIMsgMailNewsUrl)
+ .loadURI(
+ displayConsumer.QueryInterface(Ci.nsIDocShell),
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE
+ );
+ } else {
+ let streamListener = displayConsumer.QueryInterface(Ci.nsIStreamListener);
+ let channel = new lazy.ImapChannel(imapUrl, {
+ QueryInterface: ChromeUtils.generateQI(["nsILoadInfo"]),
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ securityFlags:
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ internalContentPolicy: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+ let listener = streamListener;
+ if (convertDataToText) {
+ let converter = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ listener = converter.asyncConvertData(
+ "message/rfc822",
+ "*/*",
+ streamListener,
+ channel
+ );
+ }
+ channel.asyncOpen(listener);
+ }
+ }
+
+ fetchCustomMsgAttribute(folder, msgWindow, attribute, uids) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(null, msgWindow, runningUrl);
+ client.onReady = () => {
+ client.fetchMsgAttribute(folder, uids, attribute);
+ };
+ });
+ }
+
+ expunge(folder, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.expunge(folder);
+ };
+ });
+ }
+
+ onlineMessageCopy(
+ folder,
+ messageIds,
+ dstFolder,
+ idsAreUids,
+ isMove,
+ urlListener,
+ outURL,
+ copyState,
+ msgWindow
+ ) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction = isMove
+ ? Ci.nsIImapUrl.nsImapOnlineMove
+ : Ci.nsIImapUrl.nsImapOnlineCopy;
+ client.onReady = () => {
+ client.copy(folder, dstFolder, messageIds, idsAreUids, isMove);
+ };
+ });
+ }
+
+ appendMessageFromFile(
+ file,
+ dstFolder,
+ messageId,
+ idsAreUids,
+ inSelectedState,
+ urlListener,
+ copyState,
+ msgWindow
+ ) {
+ let server = dstFolder.server;
+ let imapUrl = Services.io
+ .newURI(
+ `imap://${server.hostName}:${server.port}/fetch>UID>/${dstFolder.name}>${messageId}`
+ )
+ .QueryInterface(Ci.nsIImapUrl);
+ imapUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapAppendMsgFromFile;
+ imapUrl.copyState = copyState;
+ if (Services.io.offline) {
+ this._offlineAppendMessageFile(file, imapUrl, dstFolder, urlListener);
+ return;
+ }
+ this._withClient(dstFolder, client => {
+ client.startRunningUrl(urlListener, msgWindow, imapUrl);
+ client.onReady = () => {
+ client.uploadMessageFromFile(
+ file,
+ dstFolder,
+ copyState,
+ inSelectedState
+ );
+ };
+ });
+ }
+
+ /**
+ * Append a message file to a folder locally.
+ *
+ * @param {nsIFile} file - The message file to append.
+ * @param {nsIURI} url - The imap url to run.
+ * @param {nsIMsgFolder} dstFolder - The target message folder.
+ * @param {nsIUrlListener} urlListener - Callback for the request.
+ */
+ async _offlineAppendMessageFile(file, url, dstFolder, urlListener) {
+ if (dstFolder.locked) {
+ const NS_MSG_FOLDER_BUSY = 2153054218;
+ throw Components.Exception(
+ "Destination folder locked",
+ NS_MSG_FOLDER_BUSY
+ );
+ }
+
+ let db = dstFolder.msgDatabase.QueryInterface(Ci.nsIMsgOfflineOpsDatabase);
+ let fakeKey = db.nextFakeOfflineMsgKey;
+ let op = db.getOfflineOpForKey(fakeKey, true);
+ op.operation = Ci.nsIMsgOfflineImapOperation.kAppendDraft;
+ op.destinationFolderURI = dstFolder.URI;
+ // Release op eagerly, to make test_offlineDraftDataloss happy in debug build.
+ op = null;
+ Cu.forceGC();
+
+ let server = dstFolder.server;
+ let newMsgHdr = db.createNewHdr(fakeKey);
+ let outputStream = dstFolder.getOfflineStoreOutputStream(newMsgHdr);
+ let content = lazy.MailStringUtils.uint8ArrayToByteString(
+ await IOUtils.read(file.path)
+ );
+
+ let msgParser = Cc[
+ "@mozilla.org/messenger/messagestateparser;1"
+ ].createInstance(Ci.nsIMsgParseMailMsgState);
+ msgParser.SetMailDB(db);
+ msgParser.state = Ci.nsIMsgParseMailMsgState.ParseHeadersState;
+ msgParser.newMsgHdr = newMsgHdr;
+ msgParser.setNewKey(fakeKey);
+
+ for (let line of content.split("\r\n")) {
+ line += "\r\n";
+ msgParser.ParseAFolderLine(line, line.length);
+ outputStream.write(line, line.length);
+ }
+ msgParser.FinishHeader();
+
+ newMsgHdr.orFlags(Ci.nsMsgMessageFlags.Offline | Ci.nsMsgMessageFlags.Read);
+ newMsgHdr.offlineMessageSize = content.length;
+ db.addNewHdrToDB(newMsgHdr, true);
+ dstFolder.setFlag(Ci.nsMsgFolderFlags.OfflineEvents);
+ if (server.msgStore) {
+ server.msgStore.finishNewMessage(outputStream, newMsgHdr);
+ }
+
+ urlListener.OnStopRunningUrl(url, Cr.NS_OK);
+ outputStream.close();
+ db.close(true);
+ }
+
+ ensureFolderExists(parent, folderName, msgWindow, urlListener) {
+ this._withClient(parent, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapEnsureExistsFolder;
+ client.onReady = () => {
+ client.ensureFolderExists(parent, folderName);
+ };
+ });
+ }
+
+ updateFolderStatus(folder, urlListener) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapFolderStatus;
+ client.onReady = () => {
+ client.updateFolderStatus(folder);
+ };
+ });
+ }
+
+ createFolder(parent, folderName, urlListener) {
+ return this._withClient(parent, (client, runningUrl) => {
+ client.startRunningUrl(urlListener, null, runningUrl);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapCreateFolder;
+ client.onReady = () => {
+ client.createFolder(parent, folderName);
+ };
+ });
+ }
+
+ moveFolder(srcFolder, dstFolder, urlListener, msgWindow) {
+ this._withClient(srcFolder, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMoveFolderHierarchy;
+ client.onReady = () => {
+ client.moveFolder(srcFolder, dstFolder);
+ };
+ });
+ }
+
+ listFolder(folder, urlListener) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapListFolder;
+ client.onReady = () => {
+ client.listFolder(folder);
+ };
+ });
+ }
+
+ deleteFolder(folder, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.deleteFolder(folder);
+ };
+ });
+ }
+
+ storeCustomKeywords(folder, msgWindow, flagsToAdd, flagsToSubtract, uids) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(null, msgWindow, runningUrl);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMsgStoreCustomKeywords;
+ client.onReady = () => {
+ client.storeCustomKeywords(folder, flagsToAdd, flagsToSubtract, uids);
+ };
+ });
+ }
+
+ downloadMessagesForOffline(messageIds, folder, urlListener, msgWindow) {
+ let server = folder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).imapIncomingServer;
+ let imapUrl = Services.io
+ .newURI(
+ `imap://${server.hostName}:${server.port}/fetch>UID>/${folder.name}>${messageIds}`
+ )
+ .QueryInterface(Ci.nsIImapUrl);
+ imapUrl.storeResultsOffline = true;
+ if (urlListener) {
+ imapUrl
+ .QueryInterface(Ci.nsIMsgMailNewsUrl)
+ .RegisterListener(urlListener);
+ }
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow, imapUrl);
+ client.onReady = () => {
+ client.fetchMessage(folder, messageIds);
+ };
+ });
+ }
+
+ playbackAllOfflineOperations(msgWindow, urlListener) {
+ let offlineSync = Cc["@mozilla.org/imap/offlinesync;1"].createInstance(
+ Ci.nsIImapOfflineSync
+ );
+ offlineSync.init(msgWindow, urlListener, null, false);
+ offlineSync.processNextOperation();
+ }
+
+ getHeaders(folder, urlListener, outURL, messageIds, messageIdsAreUID) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(urlListener, null, runningUrl);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMsgFetch;
+ client.onReady = () => {
+ client.getHeaders(folder, messageIds);
+ };
+ });
+ }
+
+ getBodyStart(folder, urlListener, messageIds, numBytes) {
+ return this._withClient(folder, (client, runningUrl) => {
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMsgPreview;
+ client.startRunningUrl(urlListener, null, runningUrl);
+ client.onReady = () => {
+ client.fetchMessage(folder, messageIds, numBytes);
+ };
+ });
+ }
+
+ deleteAllMessages(folder, urlListener) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener);
+ client.onReady = () => {
+ client.deleteAllMessages(folder);
+ };
+ });
+ }
+
+ verifyLogon(folder, urlListener, msgWindow) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.verifyLogon = true;
+ client.startRunningUrl(urlListener, msgWindow, runningUrl);
+ client.onReady = () => {};
+ });
+ }
+
+ /**
+ * Do some actions with a connection.
+ *
+ * @param {nsIMsgFolder} folder - The associated folder.
+ * @param {Function} handler - A callback function to take a ImapClient
+ * instance, and do some actions.
+ */
+ _withClient(folder, handler) {
+ let server = folder.server.QueryInterface(Ci.nsIMsgIncomingServer);
+ let runningUrl = Services.io
+ .newURI(`imap://${server.hostName}:${server.port}`)
+ .QueryInterface(Ci.nsIMsgMailNewsUrl);
+ server.wrappedJSObject.withClient(folder, client =>
+ handler(client, runningUrl)
+ );
+ return runningUrl;
+ }
+}
+
+ImapService.prototype.classID = Components.ID(
+ "{2ea8fbe6-029b-4bff-ae05-b794cf955afb}"
+);