Bug 423460 - Saved Passwords are not imported from IE6 to Firefox on XP. r=gavin

This commit is contained in:
dolske@mozilla.com 2008-05-06 00:45:34 -07:00
parent ef158127f2
commit 7d7dd4aa97
5 changed files with 320 additions and 13 deletions

View File

@ -81,7 +81,7 @@
#include "nsIGlobalHistory.h"
#include "nsIRDFRemoteDataSource.h"
#include "nsIURI.h"
#include "nsILoginManager.h"
#include "nsILoginManagerIEMigrationHelper.h"
#include "nsILoginInfo.h"
#include "nsIFormHistory.h"
#include "nsIRDFService.h"
@ -907,7 +907,8 @@ nsIEProfileMigrator::MigrateSiteAuthSignons(IPStore* aPStore)
NS_ENSURE_ARG_POINTER(aPStore);
nsCOMPtr<nsILoginManager> pwmgr(do_GetService("@mozilla.org/login-manager;1"));
nsCOMPtr<nsILoginManagerIEMigrationHelper> pwmgr(
do_GetService("@mozilla.org/login-manager/storage/legacy;1"));
if (!pwmgr)
return NS_OK;
@ -941,7 +942,7 @@ nsIEProfileMigrator::MigrateSiteAuthSignons(IPStore* aPStore)
int idx;
idx = host.FindChar('/');
if (idx) {
realm.Assign(Substring(host, idx));
realm.Assign(Substring(host, idx + 1));
host.Assign(Substring(host, 0, idx));
}
// XXX: username and password are always ASCII in IPStore?
@ -958,8 +959,10 @@ nsIEProfileMigrator::MigrateSiteAuthSignons(IPStore* aPStore)
aLogin->SetHttpRealm(realm);
aLogin->SetUsername(NS_ConvertUTF8toUTF16((char *)data));
aLogin->SetPassword(NS_ConvertUTF8toUTF16((char *)password));
aLogin->SetUsernameField(EmptyString());
aLogin->SetPasswordField(EmptyString());
pwmgr->AddLogin(aLogin);
pwmgr->MigrateAndAddLogin(aLogin);
}
::CoTaskMemFree(data);
}
@ -1101,7 +1104,8 @@ nsIEProfileMigrator::ResolveAndMigrateSignons(IPStore* aPStore, nsVoidArray* aSi
void
nsIEProfileMigrator::EnumerateUsernames(const nsAString& aKey, PRUnichar* aData, unsigned long aCount, nsVoidArray* aSignonsFound)
{
nsCOMPtr<nsILoginManager> pwmgr(do_GetService("@mozilla.org/login-manager;1"));
nsCOMPtr<nsILoginManagerIEMigrationHelper> pwmgr(
do_GetService("@mozilla.org/login-manager/storage/legacy;1"));
if (!pwmgr)
return;
@ -1118,17 +1122,27 @@ nsIEProfileMigrator::EnumerateUsernames(const nsAString& aKey, PRUnichar* aData,
if (curr.Equals(sd->user)) {
// Bingo! Found a username in the saved data for this item. Now, add a Signon.
nsDependentString usernameStr(sd->user), passStr(sd->pass);
nsDependentCString realm(sd->realm);
nsAutoString realm(NS_ConvertUTF8toUTF16(sd->realm));
nsresult rv;
nsCOMPtr<nsILoginInfo> aLogin (do_CreateInstance(NS_LOGININFO_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, /* */);
// XXX Init() needs to treat EmptyString()s the same as void strings, disable for now
//aLogin->Init(realm, EmptyString(), nsnull, usernameStr, passStr, aKey, EmptyString());
// nsStringAPI doesn't let us create void strings, so we won't
// use Init() here.
// IE doesn't have the form submit URL, so set to empty-string,
// which the login manager uses as a wildcard value.
// IE doesn't store the password field name either, so just set it
// to an empty string.
aLogin->SetHostname(realm);
aLogin->SetFormSubmitURL(EmptyString());
aLogin->SetUsername(usernameStr);
aLogin->SetPassword(passStr);
aLogin->SetUsernameField(aKey);
aLogin->SetPasswordField(EmptyString());
pwmgr->AddLogin(aLogin);
pwmgr->MigrateAndAddLogin(aLogin);
}
}

View File

@ -49,6 +49,7 @@ XPIDLSRCS = \
nsILoginManager.idl \
nsILoginManagerStorage.idl \
nsILoginManagerPrompter.idl \
nsILoginManagerIEMigrationHelper.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,57 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Justin Dolske <dolske@mozilla.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsILoginInfo;
[scriptable, uuid(8a59ea3d-b8d0-48af-a3e2-63e27a02cde7)]
interface nsILoginManagerIEMigrationHelper : nsISupports {
/**
* Takes a login provided from nsIEProfileMigrator, migrates it to the
* current login manager format, and adds it to the list of stored logins.
*
* @param aLogin
* The login to be migrated.
*
* Note: In some cases, multiple logins may be created from a single input
* when the format is ambigious.
*
*/
void migrateAndAddLogin(in nsILoginInfo aLogin);
};

View File

@ -47,7 +47,8 @@ LoginManagerStorage_legacy.prototype = {
classDescription : "LoginManagerStorage_legacy",
contractID : "@mozilla.org/login-manager/storage/legacy;1",
classID : Components.ID("{e09e4ca6-276b-4bb4-8b71-0635a3a2a007}"),
QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),
QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage,
Ci.nsILoginManagerIEMigrationHelper]),
__logService : null, // Console logging service, used for debugging.
get _logService() {
@ -655,9 +656,9 @@ LoginManagerStorage_legacy.prototype = {
createInstance(Ci.nsILoginInfo);
extraLogin.init("https://" + host + ":" + port,
null, aLogin.httpRealm,
null, null, "", "");
// We don't have decrypted values, so clone the encrypted
// bits into the new entry.
aLogin.username, aLogin.password, "", "");
// We don't have decrypted values, unless we're importing from IE,
// so clone the encrypted bits into the new entry.
extraLogin.wrappedJSObject.encryptedPassword =
aLogin.wrappedJSObject.encryptedPassword;
extraLogin.wrappedJSObject.encryptedUsername =
@ -1293,6 +1294,89 @@ LoginManagerStorage_legacy.prototype = {
return [plainText, userCanceled];
},
/* ================== nsILoginManagerIEMigratorHelper ================== */
_migrationLoginManager : null,
/*
* migrateAndAddLogin
*
* Given a login with IE6-formatted fields, migrates it to the new format
* and adds it to the login manager.
*
* Experimentally derived format of IE6 logins, see:
* https://bugzilla.mozilla.org/attachment.cgi?id=319346
*
* HTTP AUTH:
* - hostname is always "example.com:123"
* * "example.com", "http://example.com", "http://example.com:80" all
* end up as just "example.com:80"
* * Entering "example.com:80" in the URL bar isn't recognized as a
* valid URL by IE6.
* * "https://example.com" is saved as "example.com:443"
* * "https://example.com:666" is saved as "example.com:666". Thus, for
* non-standard ports we don't know the right scheme, so create both.
*
* - an empty or missing "realm" in the WWW-Authenticate reply is stored
* as just an empty string by IE6.
*
* - IE6 will store logins where one or both (!) of the username/password
* is left blank. We don't support logins without a password, so these
* logins won't be added [addLogin() will throw].
*
* - IE6 won't recognize a URL with and embedded username/password (eg
* http://user@example.com, http://user:pass@example.com), so these
* shouldn't be encountered.
*
* - Our migration code doesn't extract non-HTTP logins (eg, FTP). So
* they shouldn't be encountered here. (Verified by saving FTP logins
* in IE and then importing in Firefox.)
*
*
* FORM LOGINS:
* - hostname is "http://site.com" or "https://site.com".
* * scheme always included
* * default port not included
* - port numbers, even for non-standard posts, are never present!
* unfortunately, this means logins will only work on the default
* port, because we don't know what the original was (or even that
* it wasn't originally stored for the original port).
* - Logins are stored without a field name by IE, but we look one up
* in the migrator for the username. The password field name will
* always be empty-string.
*/
migrateAndAddLogin : function (aLogin) {
// Initialize outself on the first call
if (!this._migrationLoginManager) {
// Connect to the correct preferences branch.
this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
this._prefBranch = this._prefBranch.getBranch("signon.");
this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
this._debug = this._prefBranch.getBoolPref("debug");
this._migrationLoginManager = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
}
this.log("Migrating login for " + aLogin.hostname);
// The IE login is in the same format as the old password
// manager entries, so just reuse that code.
var logins = this._upgrade_entry_to_2E(aLogin);
// Add logins via the login manager (and not this.addLogin),
// lest an alternative storage module be in use.
for each (var login in logins)
this._migrationLoginManager.addLogin(login);
}
}; // end of nsLoginManagerStorage_legacy implementation
var component = [LoginManagerStorage_legacy];

View File

@ -0,0 +1,151 @@
/*
* Test suite for storage-Legacy.js -- IE Migration Helper.
*
* This test exercises the migrateAndAddLogin helper interface.
* Although in the real-world it would only be invoked on Windows
* platforms, there's no point in limiting what platforms run
* this test.
*
*/
function cloneLogin(src, dst) {
dst.hostname = src.hostname;
dst.formSubmitURL = src.formSubmitURL;
dst.httpRealm = src.httpRealm;
dst.username = src.username;
dst.password = src.password;
dst.usernameField = src.usernameField;
dst.passwordField = src.passwordField;
}
function run_test() {
try {
/* ========== 0 ========== */
var testnum = 0;
var testdesc = "Initial connection to storage module"
var storage = Cc["@mozilla.org/login-manager/storage/legacy;1"].
getService(Ci.nsILoginManagerIEMigrationHelper);
if (!storage)
throw "Couldn't create storage instance.";
var pwmgr = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
if (!pwmgr)
throw "Couldn't create pwmgr instance.";
// Start with a clean slate
pwmgr.removeAllLogins();
/* ========== 1 ========== */
testnum++;
var testdesc = "Create nsILoginInfo instances for testing with"
var testlogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
var reflogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
var reflogin2 = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
/* ========== 2 ========== */
testnum++;
testdesc = "http auth, port 80";
testlogin.init("example.com:80", null, "Port 80",
"username", "password", "", "");
cloneLogin(testlogin, reflogin);
reflogin.hostname = "http://example.com";
storage.migrateAndAddLogin(testlogin);
do_check_eq(testlogin.hostname, "http://example.com");
LoginTest.checkStorageData(pwmgr, [], [reflogin]);
pwmgr.removeAllLogins();
/* ========== 3 ========== */
testnum++;
testdesc = "http auth, port 443";
testlogin.init("example.com:443", null, "Port 443",
"username", "password", "", "");
cloneLogin(testlogin, reflogin);
reflogin.hostname = "https://example.com";
storage.migrateAndAddLogin(testlogin);
do_check_eq(testlogin.hostname, "https://example.com");
LoginTest.checkStorageData(pwmgr, [], [reflogin]);
pwmgr.removeAllLogins();
/* ========== 4 ========== */
testnum++;
testdesc = "http auth, port 4242";
testlogin.init("example.com:4242", null, "Port 4242",
"username", "password", "", "");
cloneLogin(testlogin, reflogin);
cloneLogin(testlogin, reflogin2);
reflogin.hostname = "http://example.com:4242";
reflogin2.hostname = "https://example.com:4242";
storage.migrateAndAddLogin(testlogin);
// scheme is ambigious, so 2 logins are created.
LoginTest.checkStorageData(pwmgr, [], [reflogin, reflogin2]);
pwmgr.removeAllLogins();
/* ========== 5 ========== */
testnum++;
testdesc = "http auth, port 80, no realm";
testlogin.init("example.com:80", null, "",
"username", "password", "", "");
cloneLogin(testlogin, reflogin);
reflogin.hostname = "http://example.com";
reflogin.httpRealm = "http://example.com";
storage.migrateAndAddLogin(testlogin);
do_check_eq(testlogin.httpRealm, "http://example.com");
LoginTest.checkStorageData(pwmgr, [], [reflogin]);
pwmgr.removeAllLogins();
/* ========== 6 ========== */
testnum++;
testdesc = "form auth, http";
testlogin.init("http://example.com", "", null,
"username", "password", "uname", "");
cloneLogin(testlogin, reflogin);
// nothing changes, so no need to edit reflogin
storage.migrateAndAddLogin(testlogin);
LoginTest.checkStorageData(pwmgr, [], [reflogin]);
pwmgr.removeAllLogins();
/* ========== 7 ========== */
testnum++;
testdesc = "form auth, https";
testlogin.init("https://example.com", "", null,
"username", "password", "uname", "");
cloneLogin(testlogin, reflogin);
// nothing changes, so no need to edit reflogin
storage.migrateAndAddLogin(testlogin);
LoginTest.checkStorageData(pwmgr, [], [reflogin]);
pwmgr.removeAllLogins();
/* ========== end ========== */
} catch (e) {
throw ("FAILED in test #" + testnum + " -- " + testdesc + ": " + e);
}
};