diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/imap/src/ImapChannel.jsm | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/imap/src/ImapChannel.jsm')
-rw-r--r-- | comm/mailnews/imap/src/ImapChannel.jsm | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/ImapChannel.jsm b/comm/mailnews/imap/src/ImapChannel.jsm new file mode 100644 index 0000000000..0e5c333d9a --- /dev/null +++ b/comm/mailnews/imap/src/ImapChannel.jsm @@ -0,0 +1,318 @@ +/* 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 = ["ImapChannel"]; + +const { ImapUtils } = ChromeUtils.import("resource:///modules/ImapUtils.jsm"); +const { MailChannel } = ChromeUtils.importESModule( + "resource:///modules/MailChannel.sys.mjs" +); +const { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +/** + * A channel to interact with IMAP server. + * + * @implements {nsIChannel} + * @implements {nsIRequest} + * @implements {nsICacheEntryOpenCallback} + */ +class ImapChannel extends MailChannel { + QueryInterface = ChromeUtils.generateQI([ + "nsIMailChannel", + "nsIChannel", + "nsIRequest", + "nsIWritablePropertyBag", + "nsICacheEntryOpenCallback", + ]); + + _logger = ImapUtils.logger; + _status = Cr.NS_OK; + + /** + * @param {nsIURI} uri - The uri to construct the channel from. + * @param {nsILoadInfo} loadInfo - The loadInfo associated with the channel. + */ + constructor(uri, loadInfo) { + super(); + this._server = MailServices.accounts + .findServerByURI(uri) + .QueryInterface(Ci.nsIImapIncomingServer); + + // nsIChannel attributes. + this.originalURI = uri; + this.loadInfo = loadInfo; + this.contentLength = 0; + + this.uri = uri; + + uri = uri.QueryInterface(Ci.nsIMsgMessageUrl); + try { + this.contentLength = uri.messageHeader.messageSize; + } catch (e) { + // Got passed an IMAP folder URL. + this._isFolderURL = this._server && !/#(\d+)$/.test(uri.spec); + } + } + + /** + * @see nsIRequest + * @returns {string} + */ + get name() { + return this.URI?.spec; + } + + /** + * @see nsIRequest + * @returns {boolean} + */ + isPending() { + return !!this._pending; + } + + /** + * @see nsIRequest + * @returns {nsresult} + */ + get status() { + return this._status; + } + + /** + * @see nsICacheEntryOpenCallback + */ + onCacheEntryAvailable(entry, isNew, status) { + if (!Components.isSuccessCode(status)) { + // If memory cache doesn't work, read from the server. + this._readFromServer(); + return; + } + + if (isNew) { + if (Services.io.offline) { + this._status = Cr.NS_ERROR_OFFLINE; + return; + } + // It's a new entry, needs to read from the server. + let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].createInstance( + Ci.nsIStreamListenerTee + ); + let outStream = entry.openOutputStream(0, -1); + // When the tee stream receives data from the server, it writes to both + // the original listener and outStream (memory cache). + tee.init(this._listener, outStream, null); + this._listener = tee; + this._cacheEntry = entry; + this._readFromServer(); + return; + } + + // It's an old entry, read from the memory cache. + this._readFromCacheStream(entry.openInputStream(0)); + } + + onCacheEntryCheck(entry) { + return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; + } + + /** + * Get readonly URI. + * @see nsIChannel + */ + get URI() { + return this.uri; + } + + get contentType() { + return this._contentType || "message/rfc822"; + } + + set contentType(value) { + this._contentType = value; + } + + get isDocument() { + return true; + } + + open() { + throw Components.Exception( + "ImapChannel.open() not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + asyncOpen(listener) { + this._logger.debug(`asyncOpen ${this.URI.spec}`); + if (this._isFolderURL) { + let handler = Cc[ + "@mozilla.org/uriloader/content-handler;1?type=x-application-imapfolder" + ].createInstance(Ci.nsIContentHandler); + handler.handleContent("x-application-imapfolder", null, this); + return; + } + + let url = new URL(this.URI.spec); + this._listener = listener; + if (url.searchParams.get("part")) { + let converter = Cc["@mozilla.org/streamConverters;1"].getService( + Ci.nsIStreamConverterService + ); + this._listener = converter.asyncConvertData( + "message/rfc822", + "*/*", + listener, + this + ); + } + + let msgIds = this.URI.QueryInterface(Ci.nsIImapUrl).QueryInterface( + Ci.nsIMsgMailNewsUrl + ).listOfMessageIds; + this._msgKey = parseInt(msgIds); + this.contentLength = 0; + try { + if (this.readFromLocalCache()) { + this._logger.debug("Read from local cache"); + return; + } + } catch (e) { + this._logger.warn(e); + } + + try { + let uri = this.URI; + if (this.URI.spec.includes("?")) { + uri = uri.mutate().setQuery("").finalize(); + } + // Check if a memory cache is available for the current URI. + MailServices.imap.cacheStorage.asyncOpenURI( + uri, + "", + this.URI.QueryInterface(Ci.nsIImapUrl).storeResultsOffline + ? // Don't write to the memory cache if storing offline. + Ci.nsICacheStorage.OPEN_READONLY + : Ci.nsICacheStorage.OPEN_NORMALLY, + this + ); + } catch (e) { + this._logger.warn(e); + this._readFromServer(); + } + if (this._status == Cr.NS_ERROR_OFFLINE) { + throw new Components.Exception( + "The requested action could not be completed in the offline state", + Cr.NS_ERROR_OFFLINE + ); + } + } + + /** + * Try to read the message from the offline storage. + * + * @returns {boolean} True if successfully read from the offline storage. + */ + readFromLocalCache() { + if ( + !this.URI.QueryInterface(Ci.nsIImapUrl).QueryInterface( + Ci.nsIMsgMailNewsUrl + ).msgIsInLocalCache && + !this.URI.folder.hasMsgOffline(this._msgKey, null, 10) + ) { + return false; + } + + let hdr = this.URI.folder.GetMessageHeader(this._msgKey); + let stream = this.URI.folder.getLocalMsgStream(hdr); + this._readFromCacheStream(stream); + return true; + } + + /** + * Read the message from the a stream. + * + * @param {nsIInputStream} cacheStream - The input stream to read. + */ + _readFromCacheStream(stream) { + let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance( + Ci.nsIInputStreamPump + ); + this._contentType = ""; + pump.init(stream, 0, 0, true); + pump.asyncRead({ + onStartRequest: () => { + this._listener.onStartRequest(this); + this.URI.SetUrlState(true, Cr.NS_OK); + this._pending = true; + }, + onStopRequest: (request, status) => { + this._listener.onStopRequest(this, status); + this.URI.SetUrlState(false, status); + try { + this.loadGroup?.removeRequest(this, null, Cr.NS_OK); + } catch (e) {} + this._pending = false; + }, + onDataAvailable: (request, stream, offset, count) => { + this.contentLength += count; + this._listener.onDataAvailable(this, stream, offset, count); + try { + if (!stream.available()) { + stream.close(); + } + } catch (e) {} + }, + }); + } + + /** + * Retrieve the message from the server. + */ + _readFromServer() { + this._logger.debug("Read from server"); + let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + pipe.init(true, true, 0, 0); + let inputStream = pipe.inputStream; + let outputStream = pipe.outputStream; + + this._server.wrappedJSObject.withClient(this.URI.folder, client => { + client.startRunningUrl(null, null, this.URI); + client.channel = this; + this._listener.onStartRequest(this); + this._pending = true; + client.onReady = () => { + client.fetchMessage(this.URI.folder, this._msgKey); + }; + + client.onData = data => { + this.contentLength += data.length; + outputStream.write(data, data.length); + this._listener.onDataAvailable(this, inputStream, 0, data.length); + }; + + client.onDone = status => { + try { + this.loadGroup?.removeRequest(this, null, status); + } catch (e) {} + this._listener.onStopRequest(this, status); + }; + this._pending = false; + }); + } + + /** @see nsIWritablePropertyBag */ + getProperty(key) { + return this[key]; + } + + setProperty(key, value) { + this[key] = value; + } + + deleteProperty(key) { + delete this[key]; + } +} |