Bug 403790 Password manager needs to be able to migrate mailnews logins. r=dolske,gavin,a=blocking-ff3+
authorbugzilla@standard8.plus.com
Wed, 12 Mar 2008 11:17:59 -0700
changeset 12957 eda42c3870f5a815c75507d92e8a22c0e4199c42
parent 12956 86adc2eaf1ca1c9e4942963e26a73342a9fbb2d2
child 12958 53dea32196b947f4786a8ef1795cb52541c1a4d0
child 12959 27f7b56a9604ea9277c816268f7220c170bd6e66
push idunknown
push userunknown
push dateunknown
reviewersdolske, gavin, blocking-ff3
bugs403790
milestone1.9b5pre
Bug 403790 Password manager needs to be able to migrate mailnews logins. r=dolske,gavin,a=blocking-ff3+
toolkit/components/passwordmgr/src/storage-Legacy.js
toolkit/components/passwordmgr/test/unit/data/signons-403790.txt
toolkit/components/passwordmgr/test/unit/test_storage_legacy_4.js
--- a/toolkit/components/passwordmgr/src/storage-Legacy.js
+++ b/toolkit/components/passwordmgr/src/storage-Legacy.js
@@ -695,17 +695,17 @@ LoginManagerStorage_legacy.prototype = {
          *       aLogin.password.
          */
 
         // closures in cleanupURL
         var ioService = this._ioService;
         var log = this.log;
 
         function cleanupURL(aURL) {
-            var newURL, username = null;
+            var newURL, username = null, pathname = "";
 
             try {
                 var uri = ioService.newURI(aURL, null, null);
 
                 var scheme = uri.scheme;
                 newURL = scheme + "://" + uri.host;
 
                 // If the URL explicitly specified a port, only include it when
@@ -715,71 +715,89 @@ LoginManagerStorage_legacy.prototype = {
                     var handler = ioService.getProtocolHandler(scheme);
                     if (port != handler.defaultPort)
                         newURL += ":" + port;
                 }
 
                 // Could be a channel login with a username. 
                 if (scheme != "http" && scheme != "https" && uri.username)
                     username = uri.username;
-                
+
+                if (uri.path != "/")
+                    pathname = uri.path;
+
             } catch (e) {
-                log("Can't cleanup URL: " + aURL);
+                log("Can't cleanup URL: " + aURL + " e: " + e);
                 newURL = aURL;
             }
 
             if (newURL != aURL)
                 log("2E upgrade: " + aURL + " ---> " + newURL);
 
-            return [newURL, username];
+            return [newURL, username, pathname];
         }
 
+        const isMailNews = /^(ldaps?|smtp|imap|news|mailbox):\/\//;
+
+        // Old mailnews logins were protocol logins with a username/password
+        // field name set.
         var isFormLogin = (aLogin.formSubmitURL ||
                            aLogin.usernameField ||
-                           aLogin.passwordField);
+                           aLogin.passwordField) &&
+                          !isMailNews.test(aLogin.hostname);
 
-        var [hostname, username] = cleanupURL(aLogin.hostname);
+        var [hostname, username, pathname] = cleanupURL(aLogin.hostname);
         aLogin.hostname = hostname;
 
         // If a non-HTTP URL contained a username, it wasn't stored in the
         // encrypted username field (which contains an encrypted empty value)
         // (Don't do this if it's a form login, though.)
         if (username && !isFormLogin) {
             var [encUsername, userCanceled] = this._encrypt(username);
             if (!userCanceled)
                 aLogin.wrappedJSObject.encryptedUsername = encUsername;
         }
 
 
         if (aLogin.formSubmitURL) {
-            [hostname, username] = cleanupURL(aLogin.formSubmitURL);
+            [hostname, username, pathname] = cleanupURL(aLogin.formSubmitURL);
             aLogin.formSubmitURL = hostname;
             // username, if any, ignored.
         }
 
 
         /*
          * For logins stored from non-HTTP channels
          *    - Set httpRealm so they don't look like form logins
          *     "ftp://site.com" --> "ftp://site.com (ftp://site.com)"
          *
          * Tricky: Form logins and non-HTTP channel logins are stored in the
          * same format, and we don't want to add a realm to a form login.
          * Form logins have field names, so only update the realm if there are
          * no field names set. [Any login with a http[s]:// hostname is always
          * a form login, so explicitly ignore those just to be safe.]
-         *
-         * Bug 403790: mail entries (imap://, ldaps://, mailbox:// smtp:// have
-         * fieldnames set to "\=username=\" and "\=password=\" (non-escaping
-         * backslash). More work is needed to upgrade these properly.
          */
         const isHTTP = /^https?:\/\//;
+        const isLDAP = /^ldaps?:\/\//;
         if (!isHTTP.test(aLogin.hostname) && !isFormLogin) {
-            aLogin.httpRealm = aLogin.hostname;
+            // LDAP logins need to keep the path.
+            if (isLDAP.test(aLogin.hostname))
+                aLogin.httpRealm = aLogin.hostname + pathname;
+            else
+                aLogin.httpRealm = aLogin.hostname;
+
             aLogin.formSubmitURL = null;
+
+            // Null out the form items because mailnews will no longer treat
+            // or expect these as form logins
+            if (isMailNews.test(aLogin.hostname)) {
+                aLogin.usernameField = "";
+                aLogin.passwordField = "";
+            }
+
             this.log("2E upgrade: set empty realm to " + aLogin.httpRealm);
         }
 
         return upgradedLogins;
     },
 
 
     /*
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/unit/data/signons-403790.txt
@@ -0,0 +1,14 @@
+#2c
+.
+mailbox://bugzilla@localhost
+\=username=\
+~
+*\=password=\
+~dGVzdHBhc3Mx
+.
+ldap://localhost1:389/dc=test
+\=username=\
+~
+*\=password=\
+~dGVzdHBhc3My
+.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/unit/test_storage_legacy_4.js
@@ -0,0 +1,131 @@
+/*
+ * Test suite for storage-Legacy.js -- mailnews specific tests.
+ *
+ * This test interfaces directly with the legacy login storage module,
+ * bypassing the normal login manager usage.
+ *
+ */
+
+const Cm = Components.manager;
+const BASE_CONTRACTID = "@mozilla.org/network/protocol;1?name=";
+const LDAPPH_CID = Components.ID("{08eebb58-8d1a-4ab5-9fca-e35372697828}");
+const MAILBOXPH_CID = Components.ID("{edb1dea3-b226-405a-b93d-2a678a68a198}");
+
+function genericProtocolHandler(scheme, defaultPort) {
+    this.scheme = scheme;
+    this.defaultPort = defaultPort;
+}
+
+genericProtocolHandler.prototype = {
+    scheme: "",
+    defaultPort: 0,
+
+    QueryInterface: function gph_QueryInterface(aIID) {
+        if (!aIID.equals(Ci.nsISupports) &&
+            !aIID.equals(Ci.nsIProtocolHandler)) {
+            throw Cr.NS_ERROR_NO_INTERFACE;
+        }
+        return this;
+    },
+
+    get protocolFlags() {
+        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    },
+
+    newURI: function gph_newURI(aSpec, anOriginalCharset, aBaseURI) {
+        var uri = Components.classes["@mozilla.org/network/standard-url;1"].
+                             createInstance(Ci.nsIStandardURL);
+
+        uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD, this.defaultPort, aSpec,
+                  anOriginalCharset, aBaseURI);
+
+        return uri;
+    },
+
+    newChannel: function gph_newChannel(aUri) {
+        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    },
+
+    allowPort: function gph_allowPort(aPort, aScheme) {
+        return false;
+    }
+}
+
+function generateFactory(protocol, defaultPort)
+{
+    return {
+        createInstance: function (outer, iid) {
+            if (outer != null)
+                throw Components.results.NS_ERROR_NO_AGGREGATION;
+
+            return (new genericProtocolHandler(protocol, defaultPort)).
+                    QueryInterface(iid);
+        }
+    };
+}
+
+function run_test() {
+Cm.nsIComponentRegistrar.registerFactory(LDAPPH_CID, "LDAPProtocolFactory",
+                                         BASE_CONTRACTID + "ldap",
+                                         generateFactory("ldap", 389));
+Cm.nsIComponentRegistrar.registerFactory(MAILBOXPH_CID,
+                                         "MailboxProtocolFactory",
+                                         BASE_CONTRACTID + "mailbox",
+                                         generateFactory("mailbox", 0));
+
+try {
+/* ========== 0 ========== */
+var testnum = 0;
+var testdesc = "Initial connection to storage module"
+
+var storage = Cc["@mozilla.org/login-manager/storage/legacy;1"].
+              createInstance(Ci.nsILoginManagerStorage);
+if (!storage)
+    throw "Couldn't create storage instance.";
+
+// Create a couple of dummy users to match what we expect to be translated
+// from the input file.
+var dummyuser1 = Cc["@mozilla.org/login-manager/loginInfo;1"].
+                 createInstance(Ci.nsILoginInfo);
+var dummyuser2 = Cc["@mozilla.org/login-manager/loginInfo;1"].
+                 createInstance(Ci.nsILoginInfo);
+var dummyuser3 = Cc["@mozilla.org/login-manager/loginInfo;1"].
+                 createInstance(Ci.nsILoginInfo);
+
+
+dummyuser1.init("mailbox://localhost", null, "mailbox://localhost",
+    "bugzilla", "testpass1", "", "");
+
+dummyuser2.init("ldap://localhost1", null,
+    "ldap://localhost1/dc=test",
+    "", "testpass2", "", "");
+
+dummyuser3.init("http://dummyhost.mozilla.org", "", null,
+    "testuser1", "testpass1", "put_user_here", "put_pw_here");
+
+/*
+ * ---------------------- Bug 403790 ----------------------
+ * Migrating mailnews style username/passwords
+ */
+
+/* ========== 1 ========== */
+testnum++;
+
+testdesc = "checking reading of mailnews-like old logins";
+LoginTest.initStorage(storage, INDIR, "signons-403790.txt",
+                      OUTDIR, "output-403790.txt");
+LoginTest.checkStorageData(storage, [], [dummyuser1, dummyuser2]);
+
+storage.addLogin(dummyuser3); // trigger a write
+LoginTest.checkStorageData(storage, [], [dummyuser1, dummyuser2, dummyuser3]);
+
+testdesc = "[flush and reload for verification]";
+LoginTest.initStorage(storage, OUTDIR, "output-403790.txt");
+LoginTest.checkStorageData(storage, [], [dummyuser1, dummyuser2, dummyuser3]);
+
+/* ========== end ========== */
+} catch (e) {
+    throw ("FAILED in test #" + testnum + " -- " + testdesc + ": " + e);
+}
+
+};