forked from mirrors/gecko-dev
		
	 21ab31bac4
			
		
	
	
		21ab31bac4
		
	
	
	
	
		
			
			The value of this wrapper folder diminishes quickly over time and its presence is just a reminder of a users former browser. Differential Revision: https://phabricator.services.mozilla.com/D93278
		
			
				
	
	
		
			388 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			388 lines
		
	
	
	
		
			11 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 { XPCOMUtils } = ChromeUtils.import(
 | |
|   "resource://gre/modules/XPCOMUtils.jsm"
 | |
| );
 | |
| const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 | |
| const { FileUtils } = ChromeUtils.import(
 | |
|   "resource://gre/modules/FileUtils.jsm"
 | |
| );
 | |
| const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 | |
| const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| const { MigrationUtils, MigratorPrototype } = ChromeUtils.import(
 | |
|   "resource:///modules/MigrationUtils.jsm"
 | |
| );
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "PlacesUtils",
 | |
|   "resource://gre/modules/PlacesUtils.jsm"
 | |
| );
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "PlacesUIUtils",
 | |
|   "resource:///modules/PlacesUIUtils.jsm"
 | |
| );
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   this,
 | |
|   "Sqlite",
 | |
|   "resource://gre/modules/Sqlite.jsm"
 | |
| );
 | |
| 
 | |
| XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 | |
| 
 | |
| const kBookmarksFileName = "360sefav.db";
 | |
| 
 | |
| function copyToTempUTF8File(file, charset) {
 | |
|   let inputStream = Cc[
 | |
|     "@mozilla.org/network/file-input-stream;1"
 | |
|   ].createInstance(Ci.nsIFileInputStream);
 | |
|   inputStream.init(file, -1, -1, 0);
 | |
|   let inputStr = NetUtil.readInputStreamToString(
 | |
|     inputStream,
 | |
|     inputStream.available(),
 | |
|     { charset }
 | |
|   );
 | |
| 
 | |
|   // Use random to reduce the likelihood of a name collision in createUnique.
 | |
|   let rand = Math.floor(Math.random() * Math.pow(2, 15));
 | |
|   let leafName = "mozilla-temp-" + rand;
 | |
|   let tempUTF8File = FileUtils.getFile(
 | |
|     "TmpD",
 | |
|     ["mozilla-temp-files", leafName],
 | |
|     true
 | |
|   );
 | |
|   tempUTF8File.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
 | |
| 
 | |
|   let out = FileUtils.openAtomicFileOutputStream(tempUTF8File);
 | |
|   try {
 | |
|     let bufferedOut = Cc[
 | |
|       "@mozilla.org/network/buffered-output-stream;1"
 | |
|     ].createInstance(Ci.nsIBufferedOutputStream);
 | |
|     bufferedOut.init(out, 4096);
 | |
|     try {
 | |
|       let converterOut = Cc[
 | |
|         "@mozilla.org/intl/converter-output-stream;1"
 | |
|       ].createInstance(Ci.nsIConverterOutputStream);
 | |
|       converterOut.init(bufferedOut, "utf-8");
 | |
|       try {
 | |
|         converterOut.writeString(inputStr || "");
 | |
|         bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
 | |
|       } finally {
 | |
|         converterOut.close();
 | |
|       }
 | |
|     } finally {
 | |
|       bufferedOut.close();
 | |
|     }
 | |
|   } finally {
 | |
|     out.close();
 | |
|   }
 | |
| 
 | |
|   return tempUTF8File;
 | |
| }
 | |
| 
 | |
| function parseINIStrings(file) {
 | |
|   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
 | |
|     Ci.nsIINIParserFactory
 | |
|   );
 | |
|   let parser = factory.createINIParser(file);
 | |
|   let obj = {};
 | |
|   for (let section of parser.getSections()) {
 | |
|     obj[section] = {};
 | |
| 
 | |
|     for (let key of parser.getKeys(section)) {
 | |
|       obj[section][key] = parser.getString(section, key);
 | |
|     }
 | |
|   }
 | |
|   return obj;
 | |
| }
 | |
| 
 | |
| function getHash(aStr) {
 | |
|   // return the two-digit hexadecimal code for a byte
 | |
|   let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);
 | |
| 
 | |
|   let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
 | |
|     Ci.nsICryptoHash
 | |
|   );
 | |
|   hasher.init(Ci.nsICryptoHash.MD5);
 | |
|   let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
 | |
|     Ci.nsIStringInputStream
 | |
|   );
 | |
|   stringStream.data = aStr;
 | |
|   hasher.updateFromStream(stringStream, -1);
 | |
| 
 | |
|   // convert the binary hash data to a hex string.
 | |
|   let binary = hasher.finish(false);
 | |
|   return Array.from(binary, (c, i) => toHexString(binary.charCodeAt(i)))
 | |
|     .join("")
 | |
|     .toLowerCase();
 | |
| }
 | |
| 
 | |
| function Bookmarks(aProfileFolder) {
 | |
|   let file = aProfileFolder.clone();
 | |
|   file.append(kBookmarksFileName);
 | |
| 
 | |
|   this._file = file;
 | |
| }
 | |
| Bookmarks.prototype = {
 | |
|   type: MigrationUtils.resourceTypes.BOOKMARKS,
 | |
| 
 | |
|   get exists() {
 | |
|     return this._file.exists() && this._file.isReadable();
 | |
|   },
 | |
| 
 | |
|   migrate(aCallback) {
 | |
|     return (async () => {
 | |
|       let folderMap = new Map();
 | |
|       let toolbarBMs = [];
 | |
| 
 | |
|       let connection = await Sqlite.openConnection({
 | |
|         path: this._file.path,
 | |
|       });
 | |
| 
 | |
|       let histogramBookmarkRoots = 0;
 | |
|       try {
 | |
|         let rows = await connection.execute(
 | |
|           `WITH RECURSIVE
 | |
|            bookmark(id, parent_id, is_folder, title, url, pos) AS (
 | |
|              VALUES(0, -1, 1, '', '', 0)
 | |
|              UNION
 | |
|              SELECT f.id, f.parent_id, f.is_folder, f.title, f.url, f.pos
 | |
|              FROM tb_fav AS f
 | |
|              JOIN bookmark AS b ON f.parent_id = b.id
 | |
|              ORDER BY f.pos ASC
 | |
|            )
 | |
|            SELECT id, parent_id, is_folder, title, url FROM bookmark WHERE id`
 | |
|         );
 | |
| 
 | |
|         for (let row of rows) {
 | |
|           let id = parseInt(row.getResultByName("id"), 10);
 | |
|           let parent_id = parseInt(row.getResultByName("parent_id"), 10);
 | |
|           let is_folder = parseInt(row.getResultByName("is_folder"), 10);
 | |
|           let title = row.getResultByName("title");
 | |
|           let url = row.getResultByName("url");
 | |
| 
 | |
|           let bmToInsert;
 | |
| 
 | |
|           if (is_folder) {
 | |
|             bmToInsert = {
 | |
|               children: [],
 | |
|               title,
 | |
|               type: PlacesUtils.bookmarks.TYPE_FOLDER,
 | |
|             };
 | |
|             folderMap.set(id, bmToInsert);
 | |
|           } else {
 | |
|             try {
 | |
|               new URL(url);
 | |
|             } catch (ex) {
 | |
|               Cu.reportError(
 | |
|                 `Ignoring ${url} when importing from 360se because of exception: ${ex}`
 | |
|               );
 | |
|               continue;
 | |
|             }
 | |
| 
 | |
|             bmToInsert = {
 | |
|               title,
 | |
|               url,
 | |
|             };
 | |
|           }
 | |
| 
 | |
|           if (folderMap.has(parent_id)) {
 | |
|             folderMap.get(parent_id).children.push(bmToInsert);
 | |
|           } else if (parent_id === 0) {
 | |
|             toolbarBMs.push(bmToInsert);
 | |
|           }
 | |
|         }
 | |
|       } finally {
 | |
|         await connection.close();
 | |
|       }
 | |
| 
 | |
|       if (toolbarBMs.length) {
 | |
|         histogramBookmarkRoots |=
 | |
|           MigrationUtils.SOURCE_BOOKMARK_ROOTS_BOOKMARKS_TOOLBAR;
 | |
|         let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
 | |
|         if (
 | |
|           !Services.prefs.getBoolPref("browser.toolbars.bookmarks.2h2020") &&
 | |
|           !MigrationUtils.isStartupMigration &&
 | |
|           PlacesUtils.getChildCountForFolder(
 | |
|             PlacesUtils.bookmarks.toolbarGuid
 | |
|           ) > PlacesUIUtils.NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE
 | |
|         ) {
 | |
|           parentGuid = await MigrationUtils.createImportedBookmarksFolder(
 | |
|             "360se",
 | |
|             parentGuid
 | |
|           );
 | |
|         }
 | |
|         await MigrationUtils.insertManyBookmarksWrapper(toolbarBMs, parentGuid);
 | |
|         PlacesUIUtils.maybeToggleBookmarkToolbarVisibilityAfterMigration();
 | |
|       }
 | |
|       Services.telemetry
 | |
|         .getKeyedHistogramById("FX_MIGRATION_BOOKMARKS_ROOTS")
 | |
|         .add("360se", histogramBookmarkRoots);
 | |
|     })().then(
 | |
|       () => aCallback(true),
 | |
|       e => {
 | |
|         Cu.reportError(e);
 | |
|         aCallback(false);
 | |
|       }
 | |
|     );
 | |
|   },
 | |
| };
 | |
| 
 | |
| function Qihoo360seProfileMigrator() {
 | |
|   let paths = [
 | |
|     // for v6 and above
 | |
|     {
 | |
|       users: ["360se6", "apps", "data", "users"],
 | |
|       defaultUser: "default",
 | |
|     },
 | |
|     // for earlier versions
 | |
|     {
 | |
|       users: ["360se"],
 | |
|       defaultUser: "data",
 | |
|     },
 | |
|   ];
 | |
|   this._usersDir = null;
 | |
|   this._defaultUserPath = null;
 | |
|   for (let path of paths) {
 | |
|     let usersDir = FileUtils.getDir("AppData", path.users, false);
 | |
|     if (usersDir.exists()) {
 | |
|       this._usersDir = usersDir;
 | |
|       this._defaultUserPath = path.defaultUser;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| Qihoo360seProfileMigrator.prototype = Object.create(MigratorPrototype);
 | |
| 
 | |
| Qihoo360seProfileMigrator.prototype.getSourceProfiles = function() {
 | |
|   if ("__sourceProfiles" in this) {
 | |
|     return this.__sourceProfiles;
 | |
|   }
 | |
| 
 | |
|   if (!this._usersDir) {
 | |
|     this.__sourceProfiles = [];
 | |
|     return this.__sourceProfiles;
 | |
|   }
 | |
| 
 | |
|   let profiles = [];
 | |
|   let noLoggedInUser = true;
 | |
|   try {
 | |
|     let loginIni = this._usersDir.clone();
 | |
|     loginIni.append("login.ini");
 | |
|     if (!loginIni.exists()) {
 | |
|       throw new Error("360 Secure Browser's 'login.ini' does not exist.");
 | |
|     }
 | |
|     if (!loginIni.isReadable()) {
 | |
|       throw new Error(
 | |
|         "360 Secure Browser's 'login.ini' file could not be read."
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
 | |
|     let loginIniObj = parseINIStrings(loginIniInUtf8);
 | |
|     try {
 | |
|       loginIniInUtf8.remove(false);
 | |
|     } catch (ex) {}
 | |
| 
 | |
|     let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
 | |
| 
 | |
|     /*
 | |
|      * NowLogin section may:
 | |
|      * 1. be missing or without email, before any user logs in.
 | |
|      * 2. represents the current logged in user
 | |
|      * 3. represents the most recent logged in user
 | |
|      *
 | |
|      * In the second case, user represented by NowLogin should be the first
 | |
|      * profile; otherwise the default user should be selected by default.
 | |
|      */
 | |
|     if (nowLoginEmail) {
 | |
|       if (loginIniObj.NowLogin.IsLogined === "1") {
 | |
|         noLoggedInUser = false;
 | |
|       }
 | |
| 
 | |
|       profiles.push({
 | |
|         id: this._getIdFromConfig(loginIniObj.NowLogin),
 | |
|         name: nowLoginEmail,
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     for (let section in loginIniObj) {
 | |
|       if (
 | |
|         !loginIniObj[section].email ||
 | |
|         (nowLoginEmail && loginIniObj[section].email == nowLoginEmail)
 | |
|       ) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       profiles.push({
 | |
|         id: this._getIdFromConfig(loginIniObj[section]),
 | |
|         name: loginIniObj[section].email,
 | |
|       });
 | |
|     }
 | |
|   } catch (e) {
 | |
|     Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
 | |
|   } finally {
 | |
|     profiles[noLoggedInUser ? "unshift" : "push"]({
 | |
|       id: this._defaultUserPath,
 | |
|       name: "Default",
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   this.__sourceProfiles = profiles.filter(profile => {
 | |
|     let resources = this.getResources(profile);
 | |
|     return resources && !!resources.length;
 | |
|   });
 | |
|   return this.__sourceProfiles;
 | |
| };
 | |
| 
 | |
| Qihoo360seProfileMigrator.prototype._getIdFromConfig = function(aConfig) {
 | |
|   return aConfig.UserMd5 || getHash(aConfig.email);
 | |
| };
 | |
| 
 | |
| Qihoo360seProfileMigrator.prototype.getResources = function(aProfile) {
 | |
|   let profileFolder = this._usersDir.clone();
 | |
|   profileFolder.append(aProfile.id);
 | |
| 
 | |
|   if (!profileFolder.exists()) {
 | |
|     return [];
 | |
|   }
 | |
| 
 | |
|   let resources = [new Bookmarks(profileFolder)];
 | |
|   return resources.filter(r => r.exists);
 | |
| };
 | |
| 
 | |
| Qihoo360seProfileMigrator.prototype.getLastUsedDate = async function() {
 | |
|   let sourceProfiles = await this.getSourceProfiles();
 | |
|   let bookmarksPaths = sourceProfiles.map(({ id }) => {
 | |
|     return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
 | |
|   });
 | |
|   if (!bookmarksPaths.length) {
 | |
|     return new Date(0);
 | |
|   }
 | |
|   let datePromises = bookmarksPaths.map(path => {
 | |
|     return OS.File.stat(path)
 | |
|       .catch(() => null)
 | |
|       .then(info => {
 | |
|         return info ? info.lastModificationDate : 0;
 | |
|       });
 | |
|   });
 | |
|   return Promise.all(datePromises).then(dates => {
 | |
|     return new Date(Math.max.apply(Math, dates));
 | |
|   });
 | |
| };
 | |
| 
 | |
| Qihoo360seProfileMigrator.prototype.classDescription =
 | |
|   "360 Secure Browser Profile Migrator";
 | |
| Qihoo360seProfileMigrator.prototype.contractID =
 | |
|   "@mozilla.org/profile/migrator;1?app=browser&type=360se";
 | |
| Qihoo360seProfileMigrator.prototype.classID = Components.ID(
 | |
|   "{d0037b95-296a-4a4e-94b2-c3d075d20ab1}"
 | |
| );
 | |
| 
 | |
| var EXPORTED_SYMBOLS = ["Qihoo360seProfileMigrator"];
 |