Bug 1738175 - Support downloading the rest of a partial message in pop3-js. r=mkmelin BETA_97_BASE
authorPing Chen <remotenonsense@gmail.com>
Mon, 10 Jan 2022 12:40:52 +0200
changeset 34709 0010317fa5bdc422a57f0640bf29828ce3655794
parent 34708 dcc04339a078d898219f1a187ebd066ccfd2bddc
child 34710 0df2ed23ce5b093cfe1daedb0fd8d7ff6a95836f
push id19536
push usermkmelin@iki.fi
push dateMon, 10 Jan 2022 10:42:43 +0000
treeherdercomm-central@0010317fa5bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1738175
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
mailnews/local/src/Pop3Channel.jsm
mailnews/local/src/Pop3Client.jsm
mailnews/local/src/Pop3ModuleLoader.jsm
mailnews/local/src/Pop3ProtocolHandler.jsm
mailnews/local/src/moz.build
mailnews/local/src/nsMailboxService.cpp
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);
     }