Bug 1164752 - Include 360 secure browser in migration. r=mak
authorHector Zhao <bzhao@mozilla.com>
Wed, 13 May 2015 18:00:55 +0800
changeset 248486 eeeaa094d312
parent 248485 d83d1212b239
child 248487 e420871145cb
push id13521
push userryanvm@gmail.com
push date2015-06-12 14:16 +0000
treeherderfx-team@eeeaa094d312 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1164752
milestone41.0a1
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