Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
10 const Cr = Components.results;
12 const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
15 Cu.import("resource://gre/modules/Services.jsm");
16 Cu.import("resource://gre/modules/NetUtil.jsm");
17 Cu.import("resource:///modules/MigrationUtils.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
20 "resource://gre/modules/PlacesUtils.jsm");
21 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
22 "resource://gre/modules/ctypes.jsm");
23 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
24 "resource://gre/modules/WindowsRegistry.jsm");
26 ////////////////////////////////////////////////////////////////////////////////
27 //// Helpers.
29 let CtypesHelpers = {
30 _structs: {},
31 _functions: {},
32 _libs: {},
34 /**
35 * Must be invoked once before first use of any of the provided helpers.
36 */
37 initialize: function CH_initialize() {
38 const WORD = ctypes.uint16_t;
39 const DWORD = ctypes.uint32_t;
40 const BOOL = ctypes.int;
42 this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
43 {wYear: WORD},
44 {wMonth: WORD},
45 {wDayOfWeek: WORD},
46 {wDay: WORD},
47 {wHour: WORD},
48 {wMinute: WORD},
49 {wSecond: WORD},
50 {wMilliseconds: WORD}
51 ]);
53 this._structs.FILETIME = new ctypes.StructType('FILETIME', [
54 {dwLowDateTime: DWORD},
55 {dwHighDateTime: DWORD}
56 ]);
58 try {
59 this._libs.kernel32 = ctypes.open("Kernel32");
60 this._functions.FileTimeToSystemTime =
61 this._libs.kernel32.declare("FileTimeToSystemTime",
62 ctypes.default_abi,
63 BOOL,
64 this._structs.FILETIME.ptr,
65 this._structs.SYSTEMTIME.ptr);
66 } catch (ex) {
67 this.finalize();
68 }
69 },
71 /**
72 * Must be invoked once after last use of any of the provided helpers.
73 */
74 finalize: function CH_finalize() {
75 this._structs = {};
76 this._functions = {};
77 for each (let lib in this._libs) {
78 try {
79 lib.close();
80 } catch (ex) {}
81 }
82 this._libs = {};
83 },
85 /**
86 * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct.
87 *
88 * @param aTimeHi
89 * Least significant DWORD.
90 * @param aTimeLo
91 * Most significant DWORD.
92 * @return a Date object representing the converted datetime.
93 */
94 fileTimeToDate: function CH_fileTimeToDate(aTimeHi, aTimeLo) {
95 let fileTime = this._structs.FILETIME();
96 fileTime.dwLowDateTime = aTimeLo;
97 fileTime.dwHighDateTime = aTimeHi;
98 let systemTime = this._structs.SYSTEMTIME();
99 let result = this._functions.FileTimeToSystemTime(fileTime.address(),
100 systemTime.address());
101 if (result == 0)
102 throw new Error(ctypes.winLastError);
104 return new Date(systemTime.wYear,
105 systemTime.wMonth - 1,
106 systemTime.wDay,
107 systemTime.wHour,
108 systemTime.wMinute,
109 systemTime.wSecond,
110 systemTime.wMilliseconds);
111 }
112 };
114 /**
115 * Checks whether an host is an IP (v4 or v6) address.
116 *
117 * @param aHost
118 * The host to check.
119 * @return whether aHost is an IP address.
120 */
121 function hostIsIPAddress(aHost) {
122 try {
123 Services.eTLD.getBaseDomainFromHost(aHost);
124 } catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
125 return true;
126 } catch (e) {}
127 return false;
128 }
130 ////////////////////////////////////////////////////////////////////////////////
131 //// Resources
133 function Bookmarks() {
134 }
136 Bookmarks.prototype = {
137 type: MigrationUtils.resourceTypes.BOOKMARKS,
139 get exists() !!this._favoritesFolder,
141 __favoritesFolder: null,
142 get _favoritesFolder() {
143 if (!this.__favoritesFolder) {
144 let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
145 if (favoritesFolder.exists() && favoritesFolder.isReadable())
146 this.__favoritesFolder = favoritesFolder;
147 }
148 return this.__favoritesFolder;
149 },
151 __toolbarFolderName: null,
152 get _toolbarFolderName() {
153 if (!this.__toolbarFolderName) {
154 // Retrieve the name of IE's favorites subfolder that holds the bookmarks
155 // in the toolbar. This was previously stored in the registry and changed
156 // in IE7 to always be called "Links".
157 let folderName = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
158 "Software\\Microsoft\\Internet Explorer\\Toolbar",
159 "LinksFolderName");
160 this.__toolbarFolderName = folderName || "Links";
161 }
162 return this.__toolbarFolderName;
163 },
165 migrate: function B_migrate(aCallback) {
166 PlacesUtils.bookmarks.runInBatchMode({
167 runBatched: (function migrateBatched() {
168 // Import to the bookmarks menu.
169 let destFolderId = PlacesUtils.bookmarksMenuFolderId;
170 if (!MigrationUtils.isStartupMigration) {
171 destFolderId =
172 MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
173 }
175 this._migrateFolder(this._favoritesFolder, destFolderId);
177 aCallback(true);
178 }).bind(this)
179 }, null);
180 },
182 _migrateFolder: function B__migrateFolder(aSourceFolder, aDestFolderId) {
183 // TODO (bug 741993): the favorites order is stored in the Registry, at
184 // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
185 // Until we support it, bookmarks are imported in alphabetical order.
186 let entries = aSourceFolder.directoryEntries;
187 while (entries.hasMoreElements()) {
188 let entry = entries.getNext().QueryInterface(Ci.nsIFile);
189 try {
190 // Make sure that entry.path == entry.target to not follow .lnk folder
191 // shortcuts which could lead to infinite cycles.
192 // Don't use isSymlink(), since it would throw for invalid
193 // lnk files pointing to URLs or to unresolvable paths.
194 if (entry.path == entry.target && entry.isDirectory()) {
195 let destFolderId;
196 if (entry.leafName == this._toolbarFolderName &&
197 entry.parent.equals(this._favoritesFolder)) {
198 // Import to the bookmarks toolbar.
199 destFolderId = PlacesUtils.toolbarFolderId;
200 if (!MigrationUtils.isStartupMigration) {
201 destFolderId =
202 MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
203 }
204 }
205 else {
206 // Import to a new folder.
207 destFolderId =
208 PlacesUtils.bookmarks.createFolder(aDestFolderId, entry.leafName,
209 PlacesUtils.bookmarks.DEFAULT_INDEX);
210 }
212 if (entry.isReadable()) {
213 // Recursively import the folder.
214 this._migrateFolder(entry, destFolderId);
215 }
216 }
217 else {
218 // Strip the .url extension, to both check this is a valid link file,
219 // and get the associated title.
220 let matches = entry.leafName.match(/(.+)\.url$/i);
221 if (matches) {
222 let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
223 getService(Ci.nsIFileProtocolHandler);
224 let uri = fileHandler.readURLFile(entry);
225 let title = matches[1];
227 PlacesUtils.bookmarks.insertBookmark(aDestFolderId,
228 uri,
229 PlacesUtils.bookmarks.DEFAULT_INDEX,
230 title);
231 }
232 }
233 } catch (ex) {
234 Components.utils.reportError("Unable to import IE favorite (" + entry.leafName + "): " + ex);
235 }
236 }
237 }
238 };
240 function History() {
241 }
243 History.prototype = {
244 type: MigrationUtils.resourceTypes.HISTORY,
246 get exists() true,
248 __typedURLs: null,
249 get _typedURLs() {
250 if (!this.__typedURLs) {
251 // The list of typed URLs is a sort of annotation stored in the registry.
252 // Currently, IE stores 25 entries and this value is not configurable,
253 // but we just keep reading up to the first non-existing entry to support
254 // possible future bumps of this limit.
255 this.__typedURLs = {};
256 let registry = Cc["@mozilla.org/windows-registry-key;1"].
257 createInstance(Ci.nsIWindowsRegKey);
258 try {
259 registry.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
260 "Software\\Microsoft\\Internet Explorer\\TypedURLs",
261 Ci.nsIWindowsRegKey.ACCESS_READ);
262 for (let entry = 1; registry.hasValue("url" + entry); entry++) {
263 let url = registry.readStringValue("url" + entry);
264 this.__typedURLs[url] = true;
265 }
266 } catch (ex) {
267 } finally {
268 registry.close();
269 }
270 }
271 return this.__typedURLs;
272 },
274 migrate: function H_migrate(aCallback) {
275 let places = [];
276 let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
277 createInstance(Ci.nsISimpleEnumerator);
278 while (historyEnumerator.hasMoreElements()) {
279 let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
280 let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
281 // MSIE stores some types of URLs in its history that we don't handle,
282 // like HTMLHelp and others. Since we don't properly map handling for
283 // all of them we just avoid importing them.
284 if (["http", "https", "ftp", "file"].indexOf(uri.scheme) == -1) {
285 continue;
286 }
288 let title = entry.get("title");
289 // Embed visits have no title and don't need to be imported.
290 if (title.length == 0) {
291 continue;
292 }
294 // The typed urls are already fixed-up, so we can use them for comparison.
295 let transitionType = this._typedURLs[uri.spec] ?
296 Ci.nsINavHistoryService.TRANSITION_TYPED :
297 Ci.nsINavHistoryService.TRANSITION_LINK;
298 let lastVisitTime = entry.get("time");
300 places.push(
301 { uri: uri,
302 title: title,
303 visits: [{ transitionType: transitionType,
304 visitDate: lastVisitTime }]
305 }
306 );
307 }
309 // Check whether there is any history to import.
310 if (places.length == 0) {
311 aCallback(true);
312 return;
313 }
315 PlacesUtils.asyncHistory.updatePlaces(places, {
316 _success: false,
317 handleResult: function() {
318 // Importing any entry is considered a successful import.
319 this._success = true;
320 },
321 handleError: function() {},
322 handleCompletion: function() {
323 aCallback(this._success);
324 }
325 });
326 }
327 };
329 function Cookies() {
330 }
332 Cookies.prototype = {
333 type: MigrationUtils.resourceTypes.COOKIES,
335 get exists() !!this._cookiesFolder,
337 __cookiesFolder: null,
338 get _cookiesFolder() {
339 // Cookies are stored in txt files, in a Cookies folder whose path varies
340 // across the different OS versions. CookD takes care of most of these
341 // cases, though, in Windows Vista/7, UAC makes a difference.
342 // If UAC is enabled, the most common destination is CookD/Low. Though,
343 // if the user runs the application in administrator mode or disables UAC,
344 // cookies are stored in the original CookD destination. Cause running the
345 // browser in administrator mode is unsafe and discouraged, we just care
346 // about the UAC state.
347 if (!this.__cookiesFolder) {
348 let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
349 if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
350 // Check if UAC is enabled.
351 if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
352 cookiesFolder.append("Low");
353 }
354 this.__cookiesFolder = cookiesFolder;
355 }
356 }
357 return this.__cookiesFolder;
358 },
360 migrate: function C_migrate(aCallback) {
361 CtypesHelpers.initialize();
363 let cookiesGenerator = (function genCookie() {
364 let success = false;
365 let entries = this._cookiesFolder.directoryEntries;
366 while (entries.hasMoreElements()) {
367 let entry = entries.getNext().QueryInterface(Ci.nsIFile);
368 // Skip eventual bogus entries.
369 if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
370 continue;
372 this._readCookieFile(entry, function(aSuccess) {
373 // Importing even a single cookie file is considered a success.
374 if (aSuccess)
375 success = true;
376 try {
377 cookiesGenerator.next();
378 } catch (ex) {}
379 });
381 yield undefined;
382 }
384 CtypesHelpers.finalize();
386 aCallback(success);
387 }).apply(this);
388 cookiesGenerator.next();
389 },
391 _readCookieFile: function C__readCookieFile(aFile, aCallback) {
392 let fileReader = Cc["@mozilla.org/files/filereader;1"].
393 createInstance(Ci.nsIDOMFileReader);
394 fileReader.addEventListener("loadend", (function onLoadEnd() {
395 fileReader.removeEventListener("loadend", onLoadEnd, false);
397 if (fileReader.readyState != fileReader.DONE) {
398 Cu.reportError("Could not read cookie contents: " + fileReader.error);
399 aCallback(false);
400 return;
401 }
403 let success = true;
404 try {
405 this._parseCookieBuffer(fileReader.result);
406 } catch (ex) {
407 Components.utils.reportError("Unable to migrate cookie: " + ex);
408 success = false;
409 } finally {
410 aCallback(success);
411 }
412 }).bind(this), false);
413 fileReader.readAsText(File(aFile));
414 },
416 /**
417 * Parses a cookie file buffer and returns an array of the contained cookies.
418 *
419 * The cookie file format is a newline-separated-values with a "*" used as
420 * delimeter between multiple records.
421 * Each cookie has the following fields:
422 * - name
423 * - value
424 * - host/path
425 * - flags
426 * - Expiration time most significant integer
427 * - Expiration time least significant integer
428 * - Creation time most significant integer
429 * - Creation time least significant integer
430 * - Record delimiter "*"
431 *
432 * @note All the times are in FILETIME format.
433 */
434 _parseCookieBuffer: function C__parseCookieBuffer(aTextBuffer) {
435 // Note the last record is an empty string.
436 let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
437 for (let record of records) {
438 let [name, value, hostpath, flags,
439 expireTimeLo, expireTimeHi] = record.split("\n");
441 // IE stores deleted cookies with a zero-length value, skip them.
442 if (value.length == 0)
443 continue;
445 let hostLen = hostpath.indexOf("/");
446 let host = hostpath.substr(0, hostLen);
448 // For a non-null domain, assume it's what Mozilla considers
449 // a domain cookie. See bug 222343.
450 if (host.length > 0) {
451 // Fist delete any possible extant matching host cookie.
452 Services.cookies.remove(host, name, path, false);
453 // Now make it a domain cookie.
454 if (host[0] != "." && !hostIsIPAddress(host))
455 host = "." + host;
456 }
458 let path = hostpath.substr(hostLen);
459 let expireTime = CtypesHelpers.fileTimeToDate(Number(expireTimeHi),
460 Number(expireTimeLo));
461 Services.cookies.add(host,
462 path,
463 name,
464 value,
465 Number(flags) & 0x1, // secure
466 false, // httpOnly
467 false, // session
468 expireTime);
469 }
470 }
471 };
473 function Settings() {
474 }
476 Settings.prototype = {
477 type: MigrationUtils.resourceTypes.SETTINGS,
479 get exists() true,
481 migrate: function S_migrate(aCallback) {
482 // Converts from yes/no to a boolean.
483 function yesNoToBoolean(v) v == "yes";
485 // Converts source format like "en-us,ar-kw;q=0.7,ar-om;q=0.3" into
486 // destination format like "en-us, ar-kw, ar-om".
487 // Final string is sorted by quality (q=) param.
488 function parseAcceptLanguageList(v) {
489 return v.match(/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/gi)
490 .sort(function (a , b) {
491 let qA = parseFloat(a.split(";q=")[1]) || 1.0;
492 let qB = parseFloat(b.split(";q=")[1]) || 1.0;
493 return qA < qB ? 1 : qA == qB ? 0 : -1;
494 })
495 .map(function(a) a.split(";")[0]);
496 }
498 // For reference on some of the available IE Registry settings:
499 // * http://msdn.microsoft.com/en-us/library/cc980058%28v=prot.13%29.aspx
500 // * http://msdn.microsoft.com/en-us/library/cc980059%28v=prot.13%29.aspx
502 // Note that only settings exposed in our UI should be migrated.
504 this._set("Software\\Microsoft\\Internet Explorer\\International",
505 "AcceptLanguage",
506 "intl.accept_languages",
507 parseAcceptLanguageList);
508 // TODO (bug 745853): For now, only x-western font is translated.
509 this._set("Software\\Microsoft\\Internet Explorer\\International\\Scripts\\3",
510 "IEFixedFontName",
511 "font.name.monospace.x-western");
512 this._set(kMainKey,
513 "Use FormSuggest",
514 "browser.formfill.enable",
515 yesNoToBoolean);
516 this._set(kMainKey,
517 "FormSuggest Passwords",
518 "signon.rememberSignons",
519 yesNoToBoolean);
520 this._set(kMainKey,
521 "Anchor Underline",
522 "browser.underline_anchors",
523 yesNoToBoolean);
524 this._set(kMainKey,
525 "Display Inline Images",
526 "permissions.default.image",
527 function (v) yesNoToBoolean(v) ? 1 : 2);
528 this._set(kMainKey,
529 "Move System Caret",
530 "accessibility.browsewithcaret",
531 yesNoToBoolean);
532 this._set("Software\\Microsoft\\Internet Explorer\\Settings",
533 "Always Use My Colors",
534 "browser.display.use_document_colors",
535 function (v) !Boolean(v));
536 this._set("Software\\Microsoft\\Internet Explorer\\Settings",
537 "Always Use My Font Face",
538 "browser.display.use_document_fonts",
539 function (v) !Boolean(v));
540 this._set(kMainKey,
541 "SmoothScroll",
542 "general.smoothScroll",
543 Boolean);
544 this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
545 "WarnOnClose",
546 "browser.tabs.warnOnClose",
547 Boolean);
548 this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
549 "OpenInForeground",
550 "browser.tabs.loadInBackground",
551 function (v) !Boolean(v));
553 aCallback(true);
554 },
556 /**
557 * Reads a setting from the Registry and stores the converted result into
558 * the appropriate Firefox preference.
559 *
560 * @param aPath
561 * Registry path under HKCU.
562 * @param aKey
563 * Name of the key.
564 * @param aPref
565 * Firefox preference.
566 * @param [optional] aTransformFn
567 * Conversion function from the Registry format to the pref format.
568 */
569 _set: function S__set(aPath, aKey, aPref, aTransformFn) {
570 let value = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
571 aPath, aKey);
572 // Don't import settings that have never been flipped.
573 if (value === undefined)
574 return;
576 if (aTransformFn)
577 value = aTransformFn(value);
579 switch (typeof(value)) {
580 case "string":
581 Services.prefs.setCharPref(aPref, value);
582 break;
583 case "number":
584 Services.prefs.setIntPref(aPref, value);
585 break;
586 case "boolean":
587 Services.prefs.setBoolPref(aPref, value);
588 break;
589 default:
590 throw new Error("Unexpected value type: " + typeof(value));
591 }
592 }
593 };
595 ////////////////////////////////////////////////////////////////////////////////
596 //// Migrator
598 function IEProfileMigrator()
599 {
600 }
602 IEProfileMigrator.prototype = Object.create(MigratorPrototype);
604 IEProfileMigrator.prototype.getResources = function IE_getResources() {
605 let resources = [
606 new Bookmarks()
607 , new History()
608 , new Cookies()
609 , new Settings()
610 ];
611 return [r for each (r in resources) if (r.exists)];
612 };
614 Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
615 get: function IE_get_sourceHomePageURL() {
616 let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
617 kMainKey, "Default_Page_URL");
618 let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
619 kMainKey, "Start Page");
620 // If the user didn't customize the Start Page, he is still on the default
621 // page, that may be considered the equivalent of our about:home. There's
622 // no reason to retain it, since it is heavily targeted to IE.
623 let homepage = startPage != defaultStartPage ? startPage : "";
625 // IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
626 // are in addition to the Start Page, and no empty entries are possible,
627 // thus a Start Page is always defined if any of these exists, though it
628 // may be the default one.
629 let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
630 kMainKey, "Secondary Start Pages");
631 if (secondaryPages) {
632 if (homepage)
633 secondaryPages.unshift(homepage);
634 homepage = secondaryPages.join("|");
635 }
637 return homepage;
638 }
639 });
641 IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
642 IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";
643 IEProfileMigrator.prototype.classID = Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
645 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IEProfileMigrator]);