Bug 1738175 - Support downloading the rest of a partial message in pop3-js. r=mkmelin
Add Pop3ProtocolHandler.jsm and Pop3Channel.jsm.
Depends on D135312.
Differential Revision:
https://phabricator.services.mozilla.com/D135313
new file mode 100644
--- /dev/null
+++ b/mailnews/local/src/Pop3Channel.jsm
@@ -0,0 +1,85 @@
+/* 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 = ["Pop3Channel"];
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ MailServices: "resource:///modules/MailServices.jsm",
+ Pop3Client: "resource:///modules/Pop3Client.jsm",
+});
+
+/**
+ * A channel to interact with POP3 server.
+ * @implements {nsIChannel}
+ * @implements {nsIRequest}
+ */
+class Pop3Channel {
+ QueryInterface = ChromeUtils.generateQI(["nsIChannel", "nsIRequest"]);
+
+ /**
+ * @param {nsIURI} uri - The uri to construct the channel from.
+ * @param {nsILoadInfo} loadInfo - The loadInfo associated with the channel.
+ */
+ constructor(uri, loadInfo) {
+ this._server = MailServices.accounts
+ .findServerByURI(uri, false)
+ .QueryInterface(Ci.nsIPop3IncomingServer);
+
+ // nsIChannel attributes.
+ this.originalURI = uri;
+ this.URI = uri;
+ this.loadInfo = loadInfo;
+ this.contentLength = 0;
+ }
+
+ /**
+ * @see nsIRequest
+ */
+ get status() {
+ return Cr.NS_OK;
+ }
+
+ /**
+ * @see nsIChannel
+ */
+ get contentType() {
+ return this._contentType || "message/rfc822";
+ }
+
+ set contentType(value) {
+ this._contentType = value;
+ }
+
+ get isDocument() {
+ return true;
+ }
+
+ open() {
+ throw Components.Exception(
+ "Pop3Channel.open() not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ asyncOpen(listener) {
+ let match = this.URI.spec.match(/pop3?:\/\/.+\/\?uidl=(\w+)/);
+ let uidl = match?.[1];
+ if (!uidl) {
+ return;
+ }
+
+ let client = new Pop3Client(this._server);
+ client.connect();
+ client.onOpen = () => {
+ client.fetchBodyForUidl(
+ this.URI.QueryInterface(Ci.nsIPop3URL).pop3Sink.folder,
+ uidl
+ );
+ };
+ }
+}
--- a/mailnews/local/src/Pop3Client.jsm
+++ b/mailnews/local/src/Pop3Client.jsm
@@ -143,16 +143,39 @@ class Pop3Client {
this._msgWindow = msgWindow;
this._urlListener = urlListener;
this._verifyLogon = true;
this._actionAfterAuth = this._actionDone;
this._actionCapa();
}
/**
+ * Fetch the full message of a uidl.
+ * @param {nsIMsgFolder} folder - The folder to save the messages to.
+ * @param {string} uidl - The uidl of the message to fetch.
+ */
+ async fetchBodyForUidl(folder, uidl) {
+ this._sink.folder = folder;
+ await this._loadUidlState();
+
+ let uidlState = this._uidlMap.get(uidl);
+ if (!uidlState || uidlState.status != "b") {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+
+ this._uidlMap.set(uidl, {
+ ...uidlState,
+ status: "f",
+ });
+ this._actionAfterAuth = this._actionStat;
+ this._actionCapa();
+ }
+
+ /**
* Send `QUIT` request to the server.
*/
quit() {
this._send("QUIT");
this._nextAction = this.close;
}
/**
@@ -728,16 +751,17 @@ class Pop3Client {
*/
_actionStatResponse = res => {
if (!Number.parseInt(res.statusText)) {
// Finish if there is no message.
this._actionDone();
return;
}
if (res.success) {
+ this._sink.beginMailDelivery(false, this._msgWindow);
this._actionList();
}
};
/**
* Send `LIST` request to the server.
*/
_actionList = () => {
@@ -791,16 +815,23 @@ class Pop3Client {
uidlState.receivedAt < this._cutOffTimestamp)
) {
// Delete this message.
this._messagesToHandle.push({
messageNumber,
messageUidl,
status: "d",
});
+ } else if (uidlState.status == "f") {
+ // Fetch the full message.
+ this._messagesToHandle.push({
+ messageNumber,
+ messageUidl,
+ status: "f",
+ });
} else {
// Do nothing to this message.
this._newUidlMap.set(messageUidl, uidlState);
}
} else {
// Fetch the full message or only headers depending on server settings
// and message size.
let status =
@@ -843,16 +874,17 @@ class Pop3Client {
break;
case "d":
this._actionDelete();
break;
default:
break;
}
} else {
+ this._sink.endMailDelivery(this);
this._actionDone();
}
};
/**
* Send `TOP` request to the server.
*/
_actionTop = () => {
@@ -957,11 +989,14 @@ class Pop3Client {
_actionDeleteResponse = res => {
this._actionHandleMessage();
};
_actionDone = (status = Cr.NS_OK) => {
this._authenticating = false;
this.quit();
this._writeUidlState();
- this._urlListener.OnStopRunningUrl(this.runningUri, status);
+ this._urlListener?.OnStopRunningUrl(this.runningUri, status);
+ if (status != Cr.NS_OK) {
+ this._sink.abortMailDelivery(this);
+ }
};
}
--- a/mailnews/local/src/Pop3ModuleLoader.jsm
+++ b/mailnews/local/src/Pop3ModuleLoader.jsm
@@ -28,16 +28,21 @@ var pop3JSModules = [
"{f99fdbf7-2e79-4ce3-9d94-7af3763b82fc}",
"@mozilla.org/messenger/server;1?type=pop3",
],
[
"Pop3Service",
"{1e8f21c3-32c3-4114-9ea4-3d74006fb351}",
"@mozilla.org/messenger/popservice;1",
],
+ [
+ "Pop3ProtocolHandler",
+ "{eed38573-d01b-4c13-9f9d-f69963095a4d}",
+ "@mozilla.org/network/protocol;1?name=pop",
+ ],
];
Pop3ModuleLoader.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
observe() {
// Nothing to do here, just need the entry so this is instantiated.
},
new file mode 100644
--- /dev/null
+++ b/mailnews/local/src/Pop3ProtocolHandler.jsm
@@ -0,0 +1,46 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["Pop3ProtocolHandler"];
+
+var { Pop3Channel } = ChromeUtils.import("resource:///modules/Pop3Channel.jsm");
+
+/**
+ * @implements {nsIProtocolHandler}
+ */
+class Pop3ProtocolHandler {
+ QueryInterface = ChromeUtils.generateQI(["nsIProtocolHandler"]);
+
+ scheme = "pop3";
+ defaultPort = Ci.nsIPop3URL.DEFAULT_POP3_PORT;
+ protocolFlags =
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.ALLOWS_PROXY |
+ Ci.nsIProtocolHandler.URI_FORBIDS_COOKIE_ACCESS;
+
+ newChannel(uri, loadInfo) {
+ let channel = new Pop3Channel(uri, loadInfo);
+ let spec = uri.spec;
+ if (
+ spec.includes("part=") &&
+ !spec.includes("type=message/rfc822") &&
+ !spec.includes("type=application/x-message-display") &&
+ !spec.includes("type=application/pdf")
+ ) {
+ channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
+ } else {
+ channel.contentDisposition = Ci.nsIChannel.DISPOSITION_INLINE;
+ }
+ return channel;
+ }
+
+ allowPort(port, scheme) {
+ return true;
+ }
+}
+
+Pop3ProtocolHandler.prototype.classID = Components.ID(
+ "{eed38573-d01b-4c13-9f9d-f69963095a4d}"
+);
--- a/mailnews/local/src/moz.build
+++ b/mailnews/local/src/moz.build
@@ -24,17 +24,19 @@ SOURCES += [
"nsPop3URL.cpp",
"nsRssIncomingServer.cpp",
"nsRssService.cpp",
]
FINAL_LIBRARY = "mail"
EXTRA_JS_MODULES += [
+ "Pop3Channel.jsm",
"Pop3Client.jsm",
"Pop3IncomingServer.jsm",
"Pop3ModuleLoader.jsm",
+ "Pop3ProtocolHandler.jsm",
"Pop3Service.jsm",
]
XPCOM_MANIFESTS += [
"components.conf",
]
--- a/mailnews/local/src/nsMailboxService.cpp
+++ b/mailnews/local/src/nsMailboxService.cpp
@@ -525,17 +525,17 @@ NS_IMETHODIMP nsMailboxService::NewChann
MOZ_ASSERT(aLoadInfo);
nsresult rv = NS_OK;
nsAutoCString spec;
rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
if (spec.Find("?uidl=") >= 0 || spec.Find("&uidl=") >= 0) {
nsCOMPtr<nsIProtocolHandler> handler =
- do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv);
+ do_GetService(NS_POP3SERVICE_CONTRACTID2, &rv);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIURI> pop3Uri;
rv = nsPop3Service::NewURI(spec, "" /* ignored */, aURI,
getter_AddRefs(pop3Uri));
NS_ENSURE_SUCCESS(rv, rv);
return handler->NewChannel(pop3Uri, aLoadInfo, _retval);
}