From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/imap/src/ImapService.jsm | 518 +++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 comm/mailnews/imap/src/ImapService.jsm (limited to 'comm/mailnews/imap/src/ImapService.jsm') 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}" +); -- cgit v1.2.3