Bug 1164752 - Include 360 secure browser in migration. r=mak
authorHector Zhao <bzhao@mozilla.com>
Wed, 13 May 2015 18:00:55 +0800
changeset 279459 eeeaa094d312822a29f653676727b690948c1025
parent 279458 d83d1212b23926b1ea2bb59e8fb5e18d3ccf6d70
child 279460 e420871145cbc90e2574696e6060b02d799d1c57
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1164752
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1164752 - Include 360 secure browser in migration. r=mak
browser/components/migration/360seProfileMigrator.js
browser/components/migration/BrowserProfileMigrators.manifest
browser/components/migration/MigrationUtils.jsm
browser/components/migration/content/migration.js
browser/components/migration/content/migration.xul
browser/components/migration/moz.build
browser/installer/package-manifest.in
browser/locales/en-US/chrome/browser/migration/migration.dtd
browser/locales/en-US/chrome/browser/migration/migration.properties
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/360seProfileMigrator.js
@@ -0,0 +1,260 @@
+/* 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 { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+                                  "resource://gre/modules/Sqlite.jsm");
+
+function parseINIStrings(file) {
+  let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+                getService(Ci.nsIINIParserFactory);
+  let parser = factory.createINIParser(file);
+  let obj = {};
+  let sections = parser.getSections();
+  while (sections.hasMore()) {
+    let section = sections.getNext();
+    obj[section] = {};
+
+    let keys = parser.getKeys(section);
+    while (keys.hasMore()) {
+      let key = keys.getNext();
+      obj[section][key] = parser.getString(section, key);
+    }
+  }
+  return obj;
+}
+
+function getHash(aStr) {
+  // return the two-digit hexadecimal code for a byte
+  function 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 [toHexString(binary.charCodeAt(i)) for (i in binary)].join("").toLowerCase();
+}
+
+function Bookmarks(aProfileFolder) {
+  let file = aProfileFolder.clone();
+  file.append("360sefav.db");
+
+  this._file = file;
+}
+Bookmarks.prototype = {
+  type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+  get exists() {
+    return this._file.exists() && this._file.isReadable();
+  },
+
+  migrate: function (aCallback) {
+    return Task.spawn(function* () {
+      let idToGuid = new Map();
+      let folderGuid = PlacesUtils.bookmarks.toolbarGuid;
+      if (!MigrationUtils.isStartupMigration) {
+        folderGuid =
+          yield MigrationUtils.createImportedBookmarksFolder("360se", folderGuid);
+      }
+      idToGuid.set(0, folderGuid);
+
+      let connection = yield Sqlite.openConnection({
+        path: this._file.path
+      });
+
+      try {
+        let rows = yield 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),
+              parent_id = parseInt(row.getResultByName("parent_id"), 10),
+              is_folder = parseInt(row.getResultByName("is_folder"), 10),
+              title = row.getResultByName("title"),
+              url = row.getResultByName("url");
+
+          let parentGuid = idToGuid.get(parent_id) || idToGuid.get("fallback");
+          if (!parentGuid) {
+            parentGuid = PlacesUtils.bookmarks.unfiledGuid;
+            if (!MigrationUtils.isStartupMigration) {
+              parentGuid =
+                yield MigrationUtils.createImportedBookmarksFolder("360se", parentGuid);
+            }
+            idToGuid.set("fallback", parentGuid);
+          }
+
+          try {
+            if (is_folder == 1) {
+              let newFolderGuid = (yield PlacesUtils.bookmarks.insert({
+                parentGuid,
+                type: PlacesUtils.bookmarks.TYPE_FOLDER,
+                title
+              })).guid;
+
+              idToGuid.set(id, newFolderGuid);
+            } else {
+              yield PlacesUtils.bookmarks.insert({
+                parentGuid,
+                url,
+                title
+              });
+            }
+          } catch (ex) {
+            Cu.reportError(ex);
+          }
+        }
+      } finally {
+        yield connection.close();
+      }
+    }.bind(this)).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);
+
+Object.defineProperty(Qihoo360seProfileMigrator.prototype, "sourceProfiles", {
+  get: function() {
+    if ("__sourceProfiles" in this)
+      return this.__sourceProfiles;
+
+    if (!this._usersDir)
+      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 loginIniObj = parseINIStrings(loginIni);
+      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",
+      });
+    }
+
+    return this.__sourceProfiles = profiles.filter(profile => {
+      let resources = this.getResources(profile);
+      return resources && resources.length > 0;
+    });
+  }
+});
+
+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 [r for each (r in resources) if (r.exists)];
+};
+
+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}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Qihoo360seProfileMigrator]);
--- a/browser/components/migration/BrowserProfileMigrators.manifest
+++ b/browser/components/migration/BrowserProfileMigrators.manifest
@@ -7,8 +7,12 @@ contract @mozilla.org/profile/migrator;1
 #ifdef HAS_IE_MIGRATOR
 component {3d2532e3-4932-4774-b7ba-968f5899d3a4} IEProfileMigrator.js
 contract @mozilla.org/profile/migrator;1?app=browser&type=ie {3d2532e3-4932-4774-b7ba-968f5899d3a4}
 #endif
 #ifdef HAS_SAFARI_MIGRATOR
 component {4b609ecf-60b2-4655-9df4-dc149e474da1} SafariProfileMigrator.js
 contract @mozilla.org/profile/migrator;1?app=browser&type=safari {4b609ecf-60b2-4655-9df4-dc149e474da1}
 #endif
+#ifdef HAS_360SE_MIGRATOR
+component {d0037b95-296a-4a4e-94b2-c3d075d20ab1} 360seProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=360se {d0037b95-296a-4a4e-94b2-c3d075d20ab1}
+#endif
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -38,21 +38,22 @@ function getMigrationBundle() {
  * Figure out what is the default browser, and if there is a migrator
  * for it, return that migrator's internal name.
  * For the time being, the "internal name" of a migraotr is its contract-id
  * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
  * but it will soon be exposed properly.
  */
 function getMigratorKeyForDefaultBrowser() {
   const APP_DESC_TO_KEY = {
-    "Internet Explorer": "ie",
-    "Safari":            "safari",
-    "Firefox":           "firefox",
-    "Google Chrome":     "chrome",  // Windows, Linux
-    "Chrome":            "chrome",  // OS X
+    "Internet Explorer":                 "ie",
+    "Safari":                            "safari",
+    "Firefox":                           "firefox",
+    "Google Chrome":                     "chrome",  // Windows, Linux
+    "Chrome":                            "chrome",  // OS X
+    "360\u5b89\u5168\u6d4f\u89c8\u5668": "360se",
   };
 
   let browserDesc = "";
   try {
     let browserDesc =
       Cc["@mozilla.org/uriloader/external-protocol-service;1"].
       getService(Ci.nsIExternalProtocolService).
       getApplicationDescription("http");
@@ -445,16 +446,17 @@ this.MigrationUtils = Object.freeze({
   /*
    * Returns the migrator for the given source, if any data is available
    * for this source, or null otherwise.
    *
    * @param aKey internal name of the migration source.
    *             Supported values: ie (windows),
    *                               safari (mac/windows),
    *                               chrome (mac/windows/linux),
+   *                               360se (windows),
    *                               firefox.
    *
    * If null is returned,  either no data can be imported
    * for the given migrator, or aMigratorKey is invalid  (e.g. ie on mac,
    * or mosaic everywhere).  This method should be used rather than direct
    * getService for future compatibility (see bug 718280).
    *
    * @return profile migrator implementing nsIBrowserProfileMigrator, if it can
@@ -477,17 +479,17 @@ this.MigrationUtils = Object.freeze({
     return migrator && migrator.sourceExists ? migrator : null;
   },
 
   // Iterates the available migrators, in the most suitable
   // order for the running platform.
   get migrators() {
     let migratorKeysOrdered = [
 #ifdef XP_WIN
-      "firefox", "ie", "chrome", "safari"
+      "firefox", "ie", "chrome", "safari", "360se"
 #elifdef XP_MACOSX
       "firefox", "safari", "chrome"
 #elifdef XP_UNIX
       "firefox", "chrome"
 #endif
     ];
 
     // If a supported default browser is found check it first
--- a/browser/components/migration/content/migration.js
+++ b/browser/components/migration/content/migration.js
@@ -287,16 +287,19 @@ var MigrationWizard = {
         source = "sourceNameSafari";
         break;
       case "chrome":
         source = "sourceNameChrome";
         break;
       case "firefox":
         source = "sourceNameFirefox";
         break;
+      case "360se":
+        source = "sourceName360se";
+        break;
     }
 
     // semi-wallpaper for crash when multiple profiles exist, since we haven't initialized mSourceProfile in places
     this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
 
     var oldHomePageURL = this._migrator.sourceHomePageURL;
 
     if (oldHomePageURL && source) {
--- a/browser/components/migration/content/migration.xul
+++ b/browser/components/migration/content/migration.xul
@@ -32,16 +32,17 @@
     <description id="importBookmarks" control="importSourceGroup" hidden="true">&importFromBookmarks.label;</description>
 
     <radiogroup id="importSourceGroup" align="start">
       <radio id="firefox"   label="&importFromFirefox.label;"   accesskey="&importFromFirefox.accesskey;"/>
 #ifdef XP_WIN
       <radio id="ie"        label="&importFromIE.label;"        accesskey="&importFromIE.accesskey;"/>
       <radio id="chrome"    label="&importFromChrome.label;"    accesskey="&importFromChrome.accesskey;"/>
       <radio id="safari"    label="&importFromSafari.label;"    accesskey="&importFromSafari.accesskey;"/>
+      <radio id="360se"     label="&importFrom360se.label;"     accesskey="&importFrom360se.accesskey;"/>
 #elifdef XP_MACOSX
       <radio id="safari"    label="&importFromSafari.label;"    accesskey="&importFromSafari.accesskey;"/>
       <radio id="chrome"    label="&importFromChrome.label;"    accesskey="&importFromChrome.accesskey;"/>
 #elifdef XP_UNIX
       <radio id="chrome"    label="&importFromChrome.label;"    accesskey="&importFromChrome.accesskey;"/>
 #endif
       <radio id="nothing"   label="&importFromNothing.label;"   accesskey="&importFromNothing.accesskey;" hidden="true"/>
     </radiogroup>
--- a/browser/components/migration/moz.build
+++ b/browser/components/migration/moz.build
@@ -21,18 +21,20 @@ if CONFIG['OS_ARCH'] == 'WINNT':
 
 EXTRA_COMPONENTS += [
     'FirefoxProfileMigrator.js',
     'ProfileMigrator.js',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXTRA_COMPONENTS += [
+        '360seProfileMigrator.js',
         'IEProfileMigrator.js',
     ]
+    DEFINES['HAS_360SE_MIGRATOR'] = True
     DEFINES['HAS_IE_MIGRATOR'] = True
 
 EXTRA_PP_COMPONENTS += [
     'BrowserProfileMigrators.manifest',
     'ChromeProfileMigrator.js',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -484,16 +484,17 @@
 @RESPATH@/components/CSSUnprefixingService.manifest
 @RESPATH@/components/contentAreaDropListener.manifest
 @RESPATH@/components/contentAreaDropListener.js
 @RESPATH@/browser/components/BrowserProfileMigrators.manifest
 @RESPATH@/browser/components/ProfileMigrator.js
 @RESPATH@/browser/components/ChromeProfileMigrator.js
 @RESPATH@/browser/components/FirefoxProfileMigrator.js
 #ifdef XP_WIN
+@RESPATH@/browser/components/360seProfileMigrator.js
 @RESPATH@/browser/components/IEProfileMigrator.js
 @RESPATH@/browser/components/SafariProfileMigrator.js
 #endif
 #ifdef XP_MACOSX
 @RESPATH@/browser/components/SafariProfileMigrator.js
 #endif
 #ifdef MOZ_ENABLE_DBUS
 @RESPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
--- a/browser/locales/en-US/chrome/browser/migration/migration.dtd
+++ b/browser/locales/en-US/chrome/browser/migration/migration.dtd
@@ -14,16 +14,18 @@
 <!ENTITY importFromNothing.label        "Don't import anything">
 <!ENTITY importFromNothing.accesskey    "D">
 <!ENTITY importFromSafari.label         "Safari">
 <!ENTITY importFromSafari.accesskey     "S">
 <!ENTITY importFromChrome.label         "Chrome">
 <!ENTITY importFromChrome.accesskey     "C">
 <!ENTITY importFromFirefox.label        "Firefox">
 <!ENTITY importFromFirefox.accesskey    "X">
+<!ENTITY importFrom360se.label          "360 Secure Browser">
+<!ENTITY importFrom360se.accesskey      "3">
 
 <!ENTITY noMigrationSources.label       "No programs that contain bookmarks, history or password data could be found.">
 
 <!ENTITY importSource.title             "Import Settings and Data">
 <!ENTITY importItems.title              "Items to Import">
 <!ENTITY importItems.label              "Select which items to import:">
 
 <!ENTITY migrating.title                "Importing…">
--- a/browser/locales/en-US/chrome/browser/migration/migration.properties
+++ b/browser/locales/en-US/chrome/browser/migration/migration.properties
@@ -4,50 +4,58 @@
 
 profileName_format=%S %S
 
 # Browser Specific
 sourceNameIE=Internet Explorer
 sourceNameSafari=Safari
 sourceNameChrome=Google Chrome
 sourceNameFirefox=Mozilla Firefox
+sourceName360se=360 Secure Browser
 
 importedBookmarksFolder=From %S
 
 importedSafariReadingList=Reading List (From Safari)
 
 # Import Sources
 # Note: When adding an import source for profile reset, add the string name to
 # resetProfile.js if it should be listed in the reset dialog.
 1_ie=Internet Options
 1_safari=Preferences
 1_chrome=Preferences
+1_360se=Preferences
 
 2_ie=Cookies
 2_safari=Cookies
 2_chrome=Cookies
 2_firefox=Cookies
+2_360se=Cookies
 
 4_ie=Browsing History
 4_safari=Browsing History
 4_chrome=Browsing History
 4_firefox_history_and_bookmarks=Browsing History and Bookmarks
+4_360se=Browsing History
 
 8_ie=Saved Form History
 8_safari=Saved Form History
 8_chrome=Saved Form History
 8_firefox=Saved Form History
+8_360se=Saved Form History
 
 16_ie=Saved Passwords
 16_safari=Saved Passwords
 16_chrome=Saved Passwords
 16_firefox=Saved Passwords
+16_360se=Saved Passwords
 
 32_ie=Favorites
 32_safari=Bookmarks
 32_chrome=Bookmarks
+32_360se=Bookmarks
 
 64_ie=Other Data
 64_safari=Other Data
 64_chrome=Other Data
 64_firefox_other=Other Data
+64_360se=Other Data
 
 128_firefox=Windows and Tabs