forked from mirrors/gecko-dev
		
	 9141469edf
			
		
	
	
		9141469edf
		
	
	
	
	
		
			
			MozReview-Commit-ID: FuVu8skcqOe --HG-- extra : rebase_source : 8ab34c4e46a7c3075b459bf44786ec184d10d203
		
			
				
	
	
		
			416 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			416 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const Cc = Components.classes;
 | |
| const Ci = Components.interfaces;
 | |
| const Cu = Components.utils;
 | |
| const Cr = Components.results;
 | |
| 
 | |
| const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
 | |
| const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
 | |
| 
 | |
| Cu.import("resource://gre/modules/AppConstants.jsm");
 | |
| Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
 | |
| Cu.import("resource://gre/modules/Services.jsm");
 | |
| Cu.import("resource://gre/modules/Task.jsm");
 | |
| Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
 | |
| Cu.import("resource:///modules/MSMigrationUtils.jsm");
 | |
| 
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
 | |
|                                   "resource://gre/modules/ctypes.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
 | |
|                                   "resource://gre/modules/PlacesUtils.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
 | |
|                                   "resource://gre/modules/OSCrypto.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
 | |
|                                   "resource://gre/modules/WindowsRegistry.jsm");
 | |
| 
 | |
| Cu.importGlobalProperties(["URL"]);
 | |
| 
 | |
| // Resources
 | |
| 
 | |
| function History() {
 | |
| }
 | |
| 
 | |
| History.prototype = {
 | |
|   type: MigrationUtils.resourceTypes.HISTORY,
 | |
| 
 | |
|   get exists() {
 | |
|     return true;
 | |
|   },
 | |
| 
 | |
|   migrate: function H_migrate(aCallback) {
 | |
|     let places = [];
 | |
|     let typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
 | |
|     let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
 | |
|                             createInstance(Ci.nsISimpleEnumerator);
 | |
|     while (historyEnumerator.hasMoreElements()) {
 | |
|       let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
 | |
|       let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
 | |
|       // MSIE stores some types of URLs in its history that we don't handle,
 | |
|       // like HTMLHelp and others.  Since we don't properly map handling for
 | |
|       // all of them we just avoid importing them.
 | |
|       if (["http", "https", "ftp", "file"].indexOf(uri.scheme) == -1) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let title = entry.get("title");
 | |
|       // Embed visits have no title and don't need to be imported.
 | |
|       if (title.length == 0) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // The typed urls are already fixed-up, so we can use them for comparison.
 | |
|       let transitionType = typedURLs.has(uri.spec) ?
 | |
|                              Ci.nsINavHistoryService.TRANSITION_TYPED :
 | |
|                              Ci.nsINavHistoryService.TRANSITION_LINK;
 | |
|       // use the current date if we have no visits for this entry.
 | |
|       // Note that the entry will have a time in microseconds (PRTime),
 | |
|       // and Date.now() returns milliseconds. Places expects PRTime,
 | |
|       // so we multiply the Date.now return value to make up the difference.
 | |
|       let lastVisitTime = entry.get("time") || (Date.now() * 1000);
 | |
| 
 | |
|       places.push(
 | |
|         { uri,
 | |
|           title,
 | |
|           visits: [{ transitionType,
 | |
|                      visitDate: lastVisitTime }]
 | |
|         }
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     // Check whether there is any history to import.
 | |
|     if (places.length == 0) {
 | |
|       aCallback(true);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     MigrationUtils.insertVisitsWrapper(places, {
 | |
|       _success: false,
 | |
|       handleResult() {
 | |
|         // Importing any entry is considered a successful import.
 | |
|         this._success = true;
 | |
|       },
 | |
|       handleError() {},
 | |
|       handleCompletion() {
 | |
|         aCallback(this._success);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| };
 | |
| 
 | |
| // IE form password migrator supporting windows from XP until 7 and IE from 7 until 11
 | |
| function IE7FormPasswords() {
 | |
|   // used to distinguish between this migrator and other passwords migrators in tests.
 | |
|   this.name = "IE7FormPasswords";
 | |
| }
 | |
| 
 | |
| IE7FormPasswords.prototype = {
 | |
|   type: MigrationUtils.resourceTypes.PASSWORDS,
 | |
| 
 | |
|   get exists() {
 | |
|     // work only on windows until 7
 | |
|     if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
 | |
|       let key = Cc["@mozilla.org/windows-registry-key;1"].
 | |
|                 createInstance(nsIWindowsRegKey);
 | |
|       key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
 | |
|                nsIWindowsRegKey.ACCESS_READ);
 | |
|       let count = key.valueCount;
 | |
|       key.close();
 | |
|       return count > 0;
 | |
|     } catch (e) {
 | |
|       return false;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   migrate(aCallback) {
 | |
|     let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
 | |
|                             createInstance(Ci.nsISimpleEnumerator);
 | |
|     let uris = []; // the uris of the websites that are going to be migrated
 | |
|     while (historyEnumerator.hasMoreElements()) {
 | |
|       let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
 | |
|       let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
 | |
|       // MSIE stores some types of URLs in its history that we don't handle, like HTMLHelp
 | |
|       // and others. Since we are not going to import the logins that are performed in these URLs
 | |
|       // we can just skip them.
 | |
|       if (["http", "https", "ftp"].indexOf(uri.scheme) == -1) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       uris.push(uri);
 | |
|     }
 | |
|     this._migrateURIs(uris);
 | |
|     aCallback(true);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Migrate the logins that were saved for the uris arguments.
 | |
|    * @param {nsIURI[]} uris - the uris that are going to be migrated.
 | |
|    */
 | |
|   _migrateURIs(uris) {
 | |
|     this.ctypesKernelHelpers = new MSMigrationUtils.CtypesKernelHelpers();
 | |
|     this._crypto = new OSCrypto();
 | |
|     let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
 | |
|     let key = Cc["@mozilla.org/windows-registry-key;1"].
 | |
|               createInstance(nsIWindowsRegKey);
 | |
|     key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
 | |
|              nsIWindowsRegKey.ACCESS_READ);
 | |
| 
 | |
|     let urlsSet = new Set(); // set of the already processed urls.
 | |
|     // number of the successfully decrypted registry values
 | |
|     let successfullyDecryptedValues = 0;
 | |
|     /* The logins are stored in the registry, where the key is a hashed URL and its
 | |
|      * value contains the encrypted details for all logins for that URL.
 | |
|      *
 | |
|      * First iterate through IE history, hashing each URL and looking for a match. If
 | |
|      * found, decrypt the value, using the URL as a salt. Finally add any found logins
 | |
|      * to the Firefox password manager.
 | |
|      */
 | |
| 
 | |
|     for (let uri of uris) {
 | |
|       try {
 | |
|         // remove the query and the ref parts of the URL
 | |
|         let urlObject = new URL(uri.spec);
 | |
|         let url = urlObject.origin + urlObject.pathname;
 | |
|         // if the current url is already processed, it should be skipped
 | |
|         if (urlsSet.has(url)) {
 | |
|           continue;
 | |
|         }
 | |
|         urlsSet.add(url);
 | |
|         // hash value of the current uri
 | |
|         let hashStr = this._crypto.getIELoginHash(url);
 | |
|         if (!key.hasValue(hashStr)) {
 | |
|           continue;
 | |
|         }
 | |
|         let value = key.readBinaryValue(hashStr);
 | |
|         // if no value was found, the uri is skipped
 | |
|         if (value == null) {
 | |
|           continue;
 | |
|         }
 | |
|         let data;
 | |
|         try {
 | |
|           // the url is used as salt to decrypt the registry value
 | |
|           data = this._crypto.decryptData(value, url, true);
 | |
|         } catch (e) {
 | |
|           continue;
 | |
|         }
 | |
|         // extract the login details from the decrypted data
 | |
|         let ieLogins = this._extractDetails(data, uri);
 | |
|         // if at least a credential was found in the current data, successfullyDecryptedValues should
 | |
|         // be incremented by one
 | |
|         if (ieLogins.length) {
 | |
|           successfullyDecryptedValues++;
 | |
|         }
 | |
|         this._addLogins(ieLogins);
 | |
|       } catch (e) {
 | |
|         Cu.reportError("Error while importing logins for " + uri.spec + ": " + e);
 | |
|       }
 | |
|     }
 | |
|     // if the number of the imported values is less than the number of values in the key, it means
 | |
|     // that not all the values were imported and an error should be reported
 | |
|     if (successfullyDecryptedValues < key.valueCount) {
 | |
|       Cu.reportError("We failed to decrypt and import some logins. " +
 | |
|                      "This is likely because we didn't find the URLs where these " +
 | |
|                      "passwords were submitted in the IE history and which are needed to be used " +
 | |
|                      "as keys in the decryption.");
 | |
|     }
 | |
| 
 | |
|     key.close();
 | |
|     this._crypto.finalize();
 | |
|     this.ctypesKernelHelpers.finalize();
 | |
|   },
 | |
| 
 | |
|   _crypto: null,
 | |
| 
 | |
|   /**
 | |
|    * Add the logins to the password manager.
 | |
|    * @param {Object[]} logins - array of the login details.
 | |
|    */
 | |
|   _addLogins(ieLogins) {
 | |
|     for (let ieLogin of ieLogins) {
 | |
|       try {
 | |
|         // create a new login
 | |
|         let login = {
 | |
|           username: ieLogin.username,
 | |
|           password: ieLogin.password,
 | |
|           hostname: ieLogin.url,
 | |
|           timeCreated: ieLogin.creation,
 | |
|         };
 | |
|         MigrationUtils.insertLoginWrapper(login);
 | |
|       } catch (e) {
 | |
|         Cu.reportError(e);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Extract the details of one or more logins from the raw decrypted data.
 | |
|    * @param {string} data - the decrypted data containing raw information.
 | |
|    * @param {nsURI} uri - the nsURI of page where the login has occur.
 | |
|    * @returns {Object[]} array of objects where each of them contains the username, password, URL,
 | |
|    * and creation time representing all the logins found in the data arguments.
 | |
|    */
 | |
|   _extractDetails(data, uri) {
 | |
|     // the structure of the header of the IE7 decrypted data for all the logins sharing the same URL
 | |
|     let loginData = new ctypes.StructType("loginData", [
 | |
|       // Bytes 0-3 are not needed and not documented
 | |
|       {"unknown1": ctypes.uint32_t},
 | |
|       // Bytes 4-7 are the header size
 | |
|       {"headerSize": ctypes.uint32_t},
 | |
|       // Bytes 8-11 are the data size
 | |
|       {"dataSize": ctypes.uint32_t},
 | |
|       // Bytes 12-19 are not needed and not documented
 | |
|       {"unknown2": ctypes.uint32_t},
 | |
|       {"unknown3": ctypes.uint32_t},
 | |
|       // Bytes 20-23 are the data count: each username and password is considered as a data
 | |
|       {"dataMax": ctypes.uint32_t},
 | |
|       // Bytes 24-35 are not needed and not documented
 | |
|       {"unknown4": ctypes.uint32_t},
 | |
|       {"unknown5": ctypes.uint32_t},
 | |
|       {"unknown6": ctypes.uint32_t}
 | |
|     ]);
 | |
| 
 | |
|     // the structure of a IE7 decrypted login item
 | |
|     let loginItem = new ctypes.StructType("loginItem", [
 | |
|       // Bytes 0-3 are the offset of the username
 | |
|       {"usernameOffset": ctypes.uint32_t},
 | |
|       // Bytes 4-11 are the date
 | |
|       {"loDateTime": ctypes.uint32_t},
 | |
|       {"hiDateTime": ctypes.uint32_t},
 | |
|       // Bytes 12-15 are not needed and not documented
 | |
|       {"foo": ctypes.uint32_t},
 | |
|       // Bytes 16-19 are the offset of the password
 | |
|       {"passwordOffset": ctypes.uint32_t},
 | |
|       // Bytes 20-31 are not needed and not documented
 | |
|       {"unknown1": ctypes.uint32_t},
 | |
|       {"unknown2": ctypes.uint32_t},
 | |
|       {"unknown3": ctypes.uint32_t}
 | |
|     ]);
 | |
| 
 | |
|     let url = uri.prePath;
 | |
|     let results = [];
 | |
|     let arr = this._crypto.stringToArray(data);
 | |
|     // convert data to ctypes.unsigned_char.array(arr.length)
 | |
|     let cdata = ctypes.unsigned_char.array(arr.length)(arr);
 | |
|     // Bytes 0-35 contain the loginData data structure for all the logins sharing the same URL
 | |
|     let currentLoginData = ctypes.cast(cdata, loginData);
 | |
|     let headerSize = currentLoginData.headerSize;
 | |
|     let currentInfoIndex = loginData.size;
 | |
|     // pointer to the current login item
 | |
|     let currentLoginItemPointer = ctypes.cast(cdata.addressOfElement(currentInfoIndex),
 | |
|                                               loginItem.ptr);
 | |
|     // currentLoginData.dataMax is the data count: each username and password is considered as
 | |
|     // a data. So, the number of logins is the number of data dived by 2
 | |
|     let numLogins = currentLoginData.dataMax / 2;
 | |
|     for (let n = 0; n < numLogins; n++) {
 | |
|       // Bytes 0-31 starting from currentInfoIndex contain the loginItem data structure for the
 | |
|       // current login
 | |
|       let currentLoginItem = currentLoginItemPointer.contents;
 | |
|       let creation = this.ctypesKernelHelpers.
 | |
|                      fileTimeToSecondsSinceEpoch(currentLoginItem.hiDateTime,
 | |
|                                                  currentLoginItem.loDateTime) * 1000;
 | |
|       let currentResult = {
 | |
|         creation,
 | |
|         url,
 | |
|       };
 | |
|       // The username is UTF-16 and null-terminated.
 | |
|       currentResult.username =
 | |
|         ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.usernameOffset),
 | |
|                                           ctypes.char16_t.ptr).readString();
 | |
|       // The password is UTF-16 and null-terminated.
 | |
|       currentResult.password =
 | |
|         ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.passwordOffset),
 | |
|                                           ctypes.char16_t.ptr).readString();
 | |
|       results.push(currentResult);
 | |
|       // move to the next login item
 | |
|       currentLoginItemPointer = currentLoginItemPointer.increment();
 | |
|     }
 | |
|     return results;
 | |
|   },
 | |
| };
 | |
| 
 | |
| function IEProfileMigrator() {
 | |
|   this.wrappedJSObject = this; // export this to be able to use it in the unittest.
 | |
| }
 | |
| 
 | |
| IEProfileMigrator.prototype = Object.create(MigratorPrototype);
 | |
| 
 | |
| IEProfileMigrator.prototype.getResources = function IE_getResources() {
 | |
|   let resources = [
 | |
|     MSMigrationUtils.getBookmarksMigrator(),
 | |
|     new History(),
 | |
|     MSMigrationUtils.getCookiesMigrator(),
 | |
|   ];
 | |
|   // Only support the form password migrator for Windows XP to 7.
 | |
|   if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
 | |
|     resources.push(new IE7FormPasswords());
 | |
|   }
 | |
|   let windowsVaultFormPasswordsMigrator =
 | |
|     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
 | |
|   windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
 | |
|   resources.push(windowsVaultFormPasswordsMigrator);
 | |
|   return resources.filter(r => r.exists);
 | |
| };
 | |
| 
 | |
| IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
 | |
|   let datePromises = ["Favs", "CookD"].map(dirId => {
 | |
|     let {path} = Services.dirsvc.get(dirId, Ci.nsIFile);
 | |
|     return OS.File.stat(path).catch(() => null).then(info => {
 | |
|       return info ? info.lastModificationDate : 0;
 | |
|     });
 | |
|   });
 | |
|   datePromises.push(new Promise(resolve => {
 | |
|     let typedURLs = new Map();
 | |
|     try {
 | |
|       typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
 | |
|     } catch (ex) {}
 | |
|     let dates = [0, ...typedURLs.values()];
 | |
|     resolve(Math.max.apply(Math, dates));
 | |
|   }));
 | |
|   return Promise.all(datePromises).then(dates => {
 | |
|     return new Date(Math.max.apply(Math, dates));
 | |
|   });
 | |
| };
 | |
| 
 | |
| Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
 | |
|   get: function IE_get_sourceHomePageURL() {
 | |
|     let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
 | |
|                                                       kMainKey, "Default_Page_URL");
 | |
|     let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
 | |
|                                                kMainKey, "Start Page");
 | |
|     // If the user didn't customize the Start Page, he is still on the default
 | |
|     // page, that may be considered the equivalent of our about:home.  There's
 | |
|     // no reason to retain it, since it is heavily targeted to IE.
 | |
|     let homepage = startPage != defaultStartPage ? startPage : "";
 | |
| 
 | |
|     // IE7+ supports secondary home pages located in a REG_MULTI_SZ key.  These
 | |
|     // are in addition to the Start Page, and no empty entries are possible,
 | |
|     // thus a Start Page is always defined if any of these exists, though it
 | |
|     // may be the default one.
 | |
|     let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
 | |
|                                                     kMainKey, "Secondary Start Pages");
 | |
|     if (secondaryPages) {
 | |
|       if (homepage)
 | |
|         secondaryPages.unshift(homepage);
 | |
|       homepage = secondaryPages.join("|");
 | |
|     }
 | |
| 
 | |
|     return homepage;
 | |
|   }
 | |
| });
 | |
| 
 | |
| IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
 | |
| IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";
 | |
| IEProfileMigrator.prototype.classID = Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
 | |
| 
 | |
| this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IEProfileMigrator]);
 |