Mozilla/mozilla/browser/components/migration/src/nsIEProfileMigrator.cpp
roc+%cs.cmu.edu ce1a6ed504 Bug 95227. Allow serif/sansserif defaults to be set per-language. Patch by Masayuki Nakano, r=dbaron,amadare,danm,smontagu,rbs,neil sr=roc
git-svn-id: svn://10.0.0.236/trunk@168216 18797224-902f-48f8-a5cc-f745e15eee43
2005-01-23 23:58:32 +00:00

1902 lines
65 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Matejka
* (Original Author)
* Ben Goodger <ben@bengoodger.com>
* (History, Favorites, Passwords, Form Data, some settings)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "nsAppDirectoryServiceDefs.h"
#include "nsBrowserProfileMigratorUtils.h"
#include "nsCOMPtr.h"
#include "nsNetCID.h"
#include "nsDebug.h"
#include "nsDependentString.h"
#include "nsDirectoryServiceDefs.h"
#include "nsString.h"
#include "plstr.h"
#include "prio.h"
#include "prmem.h"
#include "prlong.h"
#include "nsICookieManager2.h"
#include "nsIEProfileMigrator.h"
#include "nsIFile.h"
#include "nsILocalFile.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsISimpleEnumerator.h"
#include "nsISupportsArray.h"
#include "nsIProfileMigrator.h"
#include "nsIBrowserProfileMigrator.h"
#include "nsIObserverService.h"
#include <objbase.h>
#include <shlguid.h>
#include <urlhist.h>
#include <comdef.h>
#include <shlobj.h>
#include <intshcut.h>
#include "nsIBrowserHistory.h"
#include "nsIGlobalHistory.h"
#include "nsIRDFRemoteDataSource.h"
#include "nsIURI.h"
#include "nsIPasswordManagerInternal.h"
#include "nsIFormHistory.h"
#include "nsIRDFService.h"
#include "nsIRDFContainer.h"
#include "nsIURL.h"
#include "nsIBookmarksService.h"
#include "nsIStringBundle.h"
#include "nsCRT.h"
#include "nsNetUtil.h"
#include "nsToolkitCompsCID.h"
#include "nsUnicharUtils.h"
static NS_DEFINE_CID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID);
static NS_DEFINE_CID(kGlobalHistoryCID, NS_GLOBALHISTORY_CID);
#define TRIDENTPROFILE_BUNDLE "chrome://browser/locale/migration/migration.properties"
#define LARGE_BUFFER 1024
const int sInitialCookieBufferSize = 1024; // but it can grow
const int sUsernameLengthLimit = 80;
const int sHostnameLengthLimit = 255;
//***********************************************************************
//*** windows registry to mozilla prefs data type translation functions
//***********************************************************************
typedef void (*regEntryHandler)(unsigned char *, DWORD, DWORD,
nsIPrefBranch *, char *);
// yes/no string to T/F boolean
void
TranslateYNtoTF(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
PRInt32 prefIntValue = 0;
// input type is a string, lowercase "yes" or "no"
if (aRegValueType != REG_SZ)
NS_WARNING("unexpected yes/no data type");
if (aRegValueType == REG_SZ && aRegValue[0] != 0) {
// strcmp is safe; it's bounded by its second parameter
prefIntValue = strcmp(NS_REINTERPRET_CAST(char *, aRegValue), "yes") == 0;
prefs->SetBoolPref(aPrefKeyName, prefIntValue);
}
}
// yes/no string to F/T boolean
void
TranslateYNtoFT(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
PRInt32 prefIntValue = 0;
// input type is a string, lowercase "yes" or "no"
if (aRegValueType != REG_SZ)
NS_WARNING("unexpected yes/no data type");
if (aRegValueType == REG_SZ && aRegValue[0] != 0) {
// strcmp is safe; it's bounded by its second parameter
prefIntValue = strcmp(NS_REINTERPRET_CAST(char *, aRegValue), "yes") != 0;
prefs->SetBoolPref(aPrefKeyName, prefIntValue);
}
}
void
TranslateYNtoImageBehavior(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
// input type is a string, lowercase "yes" or "no"
if (aRegValueType != REG_SZ)
NS_WARNING("unexpected yes/no data type");
if (aRegValueType == REG_SZ && aRegValue[0] != 0) {
// strcmp is safe; it's bounded by its second parameter
if (!strcmp(NS_REINTERPRET_CAST(char *, aRegValue), "yes"))
prefs->SetIntPref(aPrefKeyName, 0);
else
prefs->SetIntPref(aPrefKeyName, 2);
}
}
void
TranslateDWORDtoHTTPVersion(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
// input type is a string, lowercase "yes" or "no"
if (aRegValueType != REG_DWORD)
NS_WARNING("unexpected signed int data type");
if (aRegValueType == REG_DWORD) {
DWORD val = *(NS_REINTERPRET_CAST(DWORD *, aRegValue));
if (val & 0x1)
prefs->SetCharPref(aPrefKeyName, "1.1");
else
prefs->SetCharPref(aPrefKeyName, "1.0");
}
}
// decimal RGB (1,2,3) to hex RGB (#010203)
void
TranslateDRGBtoHRGB(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
// clear previous settings with defaults
char prefStringValue[10];
// input type is a string
if (aRegValueType != REG_SZ)
NS_WARNING("unexpected RGB data type");
if (aRegValueType == REG_SZ && aRegValue[0] != 0) {
int red, green, blue;
::sscanf(NS_REINTERPRET_CAST(char *, aRegValue), "%d,%d,%d",
&red, &green, &blue);
::sprintf(prefStringValue, "#%02X%02X%02X", red, green, blue);
prefs->SetCharPref(aPrefKeyName, prefStringValue);
}
}
// translate a windows registry DWORD int to a mozilla prefs PRInt32
void
TranslateDWORDtoPRInt32(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
// clear previous settings with defaults
PRInt32 prefIntValue = 0;
if (aRegValueType != REG_DWORD)
NS_WARNING("unexpected signed int data type");
if (aRegValueType == REG_DWORD) {
prefIntValue = *(NS_REINTERPRET_CAST(DWORD *, aRegValue));
prefs->SetIntPref(aPrefKeyName, prefIntValue);
}
}
// string copy
void
TranslateString(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
if (aRegValueType != REG_SZ)
NS_WARNING("unexpected string data type");
if (aRegValueType == REG_SZ)
prefs->SetCharPref(aPrefKeyName,
NS_REINTERPRET_CAST(char *, aRegValue));
}
// translate accepted language character set formats
// (modified string copy)
void
TranslateLanglist(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
char prefStringValue[MAX_PATH]; // a convenient size, one hopes
if (aRegValueType != REG_SZ)
NS_WARNING("unexpected string data type");
if (aRegValueType != REG_SZ)
return;
// copy source format like "en-us,ar-kw;q=0.7,ar-om;q=0.3" into
// destination format like "en-us, ar-kw, ar-om"
char *source = NS_REINTERPRET_CAST(char *, aRegValue),
*dest = prefStringValue,
*sourceEnd = source + aRegValueLength,
*destEnd = dest + (MAX_PATH-2); // room for " \0"
PRBool skip = PR_FALSE,
comma = PR_FALSE;
while (source < sourceEnd && *source && dest < destEnd) {
if (*source == ',')
skip = PR_FALSE;
else if (*source == ';')
skip = PR_TRUE;
if (!skip) {
if (comma && *source != ' ')
*dest++ = ' ';
*dest = *source;
}
comma = *source == ',';
++source;
}
*dest = 0;
prefs->SetCharPref(aPrefKeyName, prefStringValue);
}
static int CALLBACK
fontEnumProc(const LOGFONT *aLogFont, const TEXTMETRIC *aMetric,
DWORD aFontType, LPARAM aClosure) {
*((int *) aClosure) = aLogFont->lfPitchAndFamily & FF_ROMAN;
return 0;
}
void
TranslatePropFont(unsigned char *aRegValue, DWORD aRegValueLength,
DWORD aRegValueType,
nsIPrefBranch *prefs, char *aPrefKeyName) {
HDC dc = ::GetDC(0);
LOGFONT lf;
int isSerif = 1;
// serif or sans-serif font?
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfPitchAndFamily = 0;
PL_strncpyz(lf.lfFaceName, NS_REINTERPRET_CAST(char *, aRegValue),
LF_FACESIZE);
::EnumFontFamiliesEx(dc, &lf, fontEnumProc, (LPARAM) &isSerif, 0);
::ReleaseDC(0, dc);
if (isSerif) {
prefs->SetCharPref("font.name.serif.x-western",
NS_REINTERPRET_CAST(char *, aRegValue));
prefs->SetCharPref("font.default.x-western", "serif");
} else {
prefs->SetCharPref("font.name.sans-serif.x-western",
NS_REINTERPRET_CAST(char *, aRegValue));
prefs->SetCharPref("font.default.x-western", "sans-serif");
}
}
//***********************************************************************
//*** master table of registry-to-gecko-pref translations
//***********************************************************************
struct regEntry {
char *regKeyName, // registry key (HKCU\Software ...)
*regValueName; // registry key leaf
char *prefKeyName; // pref name ("javascript.enabled" ...)
regEntryHandler entryHandler; // processing func
};
struct regEntry gRegEntries[] = {
{ "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\AutoComplete",
"AutoSuggest",
"browser.urlbar.autocomplete.enabled",
TranslateYNtoTF },
{ "Software\\Microsoft\\Internet Explorer\\International",
"AcceptLanguage",
"intl.accept_languages",
TranslateLanglist },
{ "Software\\Microsoft\\Internet Explorer\\International\\Scripts\\3",
"IEFixedFontName",
"font.name.monospace.x-western",
TranslateString },
{ 0, // an optimization: 0 means use the previous key
"IEPropFontName",
"", // special-cased in the translation function
TranslatePropFont },
{ "Software\\Microsoft\\Internet Explorer\\Main",
"Use_DlgBox_Colors",
"browser.display.use_system_colors",
TranslateYNtoTF },
{ 0,
"Use FormSuggest",
"browser.formfill.enable",
TranslateYNtoTF },
{ 0,
"FormSuggest Passwords",
"signon.rememberSignons",
TranslateYNtoTF },
#if 0
// Firefox supplies its own home page.
{ 0,
"Start Page",
"browser.startup.homepage",
TranslateString },
#endif
{ 0,
"Anchor Underline",
"browser.underline_anchors",
TranslateYNtoTF },
{ 0,
"Display Inline Images",
"network.image.imageBehavior",
TranslateYNtoImageBehavior },
{ 0,
"Enable AutoImageResize",
"browser.enable_automatic_image_resizing",
TranslateYNtoTF },
{ 0,
"Move System Caret",
"accessibility.browsewithcaret",
TranslateYNtoTF },
{ 0,
"NotifyDownloadComplete",
"browser.download.manager.showAlertOnComplete",
TranslateYNtoTF },
{ 0,
"SmoothScroll",
"general.smoothScroll",
TranslateYNtoTF },
{ "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"EnableHttp1_1",
"network.http.version",
TranslateDWORDtoHTTPVersion },
{ 0,
"ProxyHttp1.1",
"network.http.proxy.version",
TranslateDWORDtoHTTPVersion },
// SecureProtocols
{ "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Url History",
"DaysToKeep",
"browser.history_expire_days",
TranslateDWORDtoPRInt32 },
{ "Software\\Microsoft\\Internet Explorer\\Settings",
"Always Use My Colors",
"browser.display.use_document_colors",
TranslateYNtoFT },
{ 0,
"Text Color",
"browser.display.foreground_color",
TranslateDRGBtoHRGB },
{ 0,
"Background Color",
"browser.display.background_color",
TranslateDRGBtoHRGB },
{ 0,
"Anchor Color",
"browser.anchor_color",
TranslateDRGBtoHRGB },
{ 0,
"Anchor Color Visited",
"browser.visited_color",
TranslateDRGBtoHRGB },
{ 0,
"Always Use My Font Face",
"browser.display.use_document_fonts",
TranslateYNtoFT },
{ "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Url History",
"DaysToKeep",
"browser.history_expire_days",
TranslateDWORDtoPRInt32 }
};
#if 0
user_pref("font.size.fixed.x-western", 14);
user_pref("font.size.variable.x-western", 15);
#endif
///////////////////////////////////////////////////////////////////////////////
// nsIBrowserProfileMigrator
NS_IMETHODIMP
nsIEProfileMigrator::Migrate(PRUint16 aItems, nsIProfileStartup* aStartup, const PRUnichar* aProfile)
{
nsresult rv = NS_OK;
PRBool aReplace = PR_FALSE;
if (aStartup) {
aReplace = PR_TRUE;
rv = aStartup->DoStartup();
NS_ENSURE_SUCCESS(rv, rv);
}
NOTIFY_OBSERVERS(MIGRATION_STARTED, nsnull);
COPY_DATA(CopyPreferences, aReplace, nsIBrowserProfileMigrator::SETTINGS);
COPY_DATA(CopyCookies, aReplace, nsIBrowserProfileMigrator::COOKIES);
COPY_DATA(CopyHistory, aReplace, nsIBrowserProfileMigrator::HISTORY);
COPY_DATA(CopyFormData, aReplace, nsIBrowserProfileMigrator::FORMDATA);
COPY_DATA(CopyPasswords, aReplace, nsIBrowserProfileMigrator::PASSWORDS);
COPY_DATA(CopyFavorites, aReplace, nsIBrowserProfileMigrator::BOOKMARKS);
NOTIFY_OBSERVERS(MIGRATION_ENDED, nsnull);
return rv;
}
NS_IMETHODIMP
nsIEProfileMigrator::GetMigrateData(const PRUnichar* aProfile,
PRBool aReplace,
PRUint16* aResult)
{
// There's no harm in assuming everything is available.
*aResult = nsIBrowserProfileMigrator::SETTINGS | nsIBrowserProfileMigrator::COOKIES |
nsIBrowserProfileMigrator::HISTORY | nsIBrowserProfileMigrator::FORMDATA |
nsIBrowserProfileMigrator::PASSWORDS | nsIBrowserProfileMigrator::BOOKMARKS;
return NS_OK;
}
NS_IMETHODIMP
nsIEProfileMigrator::GetSourceExists(PRBool* aResult)
{
// IE always exists.
*aResult = PR_TRUE;
return NS_OK;
}
NS_IMETHODIMP
nsIEProfileMigrator::GetSourceHasMultipleProfiles(PRBool* aResult)
{
*aResult = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsIEProfileMigrator::GetSourceProfiles(nsISupportsArray** aResult)
{
*aResult = nsnull;
return NS_OK;
}
///////////////////////////////////////////////////////////////////////////////
// nsIEProfileMigrator
NS_IMPL_ISUPPORTS1(nsIEProfileMigrator, nsIBrowserProfileMigrator);
nsIEProfileMigrator::nsIEProfileMigrator()
{
mObserverService = do_GetService("@mozilla.org/observer-service;1");
}
nsIEProfileMigrator::~nsIEProfileMigrator()
{
}
nsresult
nsIEProfileMigrator::CopyHistory(PRBool aReplace)
{
nsCOMPtr<nsIBrowserHistory> hist(do_GetService(kGlobalHistoryCID));
nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID));
// First, Migrate standard IE History entries...
::CoInitialize(NULL);
IUrlHistoryStg2* ieHistory;
HRESULT hr = ::CoCreateInstance(CLSID_CUrlHistory,
NULL,
CLSCTX_INPROC_SERVER,
IID_IUrlHistoryStg2,
reinterpret_cast<void**>(&ieHistory));
if (SUCCEEDED(hr)) {
IEnumSTATURL* enumURLs;
hr = ieHistory->EnumUrls(&enumURLs);
if (SUCCEEDED(hr)) {
STATURL statURL;
ULONG fetched;
_bstr_t url, title;
nsCAutoString scheme;
SYSTEMTIME st;
PRBool validScheme = PR_FALSE;
PRUnichar* tempTitle = nsnull;
for (int count = 0; (hr = enumURLs->Next(1, &statURL, &fetched)) == S_OK; ++count) {
if (statURL.pwcsUrl) {
// 1 - Page Title
tempTitle = statURL.pwcsTitle ? (PRUnichar*)((wchar_t*)(statURL.pwcsTitle)) : nsnull;
// 2 - Last Visit Date
::FileTimeToSystemTime(&(statURL.ftLastVisited), &st);
PRExplodedTime prt;
prt.tm_year = st.wYear;
prt.tm_month = st.wMonth - 1; // SYSTEMTIME's day-of-month parameter is 1-based, PRExplodedTime's is 0-based.
prt.tm_mday = st.wDay;
prt.tm_hour = st.wHour;
prt.tm_min = st.wMinute;
prt.tm_sec = st.wSecond;
prt.tm_usec = st.wMilliseconds * 1000;
prt.tm_wday = 0;
prt.tm_yday = 0;
prt.tm_params.tp_gmt_offset = 0;
prt.tm_params.tp_dst_offset = 0;
PRTime lastVisited = PR_ImplodeTime(&prt);
// 3 - URL
url = statURL.pwcsUrl;
nsDependentCString urlStr((const char *) url);
if (NS_FAILED(ios->ExtractScheme(urlStr, scheme)))
continue;
ToLowerCase(scheme);
// XXXben -
// MSIE stores some types of URLs in its history that we can't handle, like HTMLHelp
// and others. At present Necko isn't clever enough to delegate handling of these types
// to the system, so we should just avoid importing them.
const char* schemes[] = { "http", "https", "ftp", "file" };
for (int i = 0; i < 4; ++i) {
if (validScheme = scheme.Equals(schemes[i]))
break;
}
// 4 - Now add the page
if (validScheme) {
nsCOMPtr<nsIURI> uri;
ios->NewURI(urlStr, nsnull, nsnull, getter_AddRefs(uri));
if (uri) {
if (tempTitle)
hist->AddPageWithDetails(uri, tempTitle, lastVisited);
else {
NS_ConvertUTF8toUTF16 urlTitle(urlStr);
hist->AddPageWithDetails(uri, urlTitle.get(), lastVisited);
}
}
}
}
}
nsCOMPtr<nsIRDFRemoteDataSource> ds(do_QueryInterface(hist));
if (ds)
ds->Flush();
enumURLs->Release();
}
ieHistory->Release();
}
::CoUninitialize();
// Now, find out what URLs were typed in by the user
HKEY key;
if (::RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Microsoft\\Internet Explorer\\TypedURLs",
0, KEY_READ, &key) == ERROR_SUCCESS) {
int offset = 0;
#define KEY_NAME_LENGTH 20
while (1) {
char valueName[KEY_NAME_LENGTH];
DWORD valueSize = KEY_NAME_LENGTH;
unsigned char data[MAX_PATH];
DWORD dataSize = MAX_PATH;
DWORD type;
LONG rv = ::RegEnumValue(key, offset, valueName, &valueSize,
NULL, &type, data, &dataSize);
if (rv == ERROR_NO_MORE_ITEMS)
break;
nsCAutoString valueNameStr(valueName);
const nsACString& prefix = Substring(valueNameStr, 0, 3);
if (prefix.Equals("url")) {
nsCOMPtr<nsIURI> uri;
ios->NewURI(nsDependentCString((const char *) data), nsnull, nsnull,
getter_AddRefs(uri));
if (uri)
hist->MarkPageAsTyped(uri);
}
++offset;
}
}
return NS_OK;
}
///////////////////////////////////////////////////////////////////////////////
// IE PASSWORDS AND FORM DATA - A PROTECTED STORAGE SYSTEM PRIMER
//
// Internet Explorer 4.0 and up store sensitive form data (saved through form
// autocomplete) and saved passwords in a special area of the Registry called
// the Protected Storage System. The data IE stores in the Protected Storage
// System is located under:
//
// HKEY_CURRENT_USER\Software\Microsoft\Protected Storage System Provider\
// <USER_ID>\Data\<IE_PSS_GUID>\<IE_PSS_GUID>\
//
// <USER_ID> is a long string that uniquely identifies the current user
// <IE_PSS_GUID> is a GUID that identifies a subsection of the Protected Storage
// System specific to MSIE. This GUID is defined below ("IEPStoreGUID").
//
// Data is stored in the Protected Strage System ("PStore") in the following
// format:
//
// <IE_PStore_Key> \
// fieldName1:StringData \ ItemData = <REG_BINARY>
// fieldName2:StringData \ ItemData = <REG_BINARY>
// http://foo.com/login.php:StringData \ ItemData = <REG_BINARY>
// ... etc ...
//
// Each key represents either the identifier of a web page text field that
// data was saved from (e.g. <input name="fieldName1">), or a URL that a login
// (username + password) was saved at (e.g. "http://foo.com/login.php")
//
// Data is stored for each of these cases in the following format:
//
// for both types of data, the Value ItemData is REG_BINARY data format encrypted with
// a 3DES cipher.
//
// for FormData: the decrypted data is in the form:
// value1\0value2\0value3\0value4 ...
// for Signons: the decrypted data is in the form:
// username\0password
//
// We cannot read the PStore directly by using Registry functions because the
// keys have limited read access such that only System process can read from them.
// In order to read from the PStore we need to use Microsoft's undocumented PStore
// API(*).
//
// (* Sparse documentation became available as of the January 2004 MSDN Library
// release)
//
// The PStore API lets us read decrypted data from the PStore. Encryption does not
// appear to be strong.
//
// Details on how each type of data is read from the PStore and migrated appropriately
// is discussed in more detail below.
//
// For more information about the PStore, read:
//
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/devnotes/winprog/pstore.asp
//
typedef HRESULT (WINAPI *PStoreCreateInstancePtr)(IPStore**, DWORD, DWORD, DWORD);
struct SignonData {
PRUnichar* user;
PRUnichar* pass;
char* realm;
};
// The IEPStore GUID is the registry key under which IE Protected Store data lives.
// {e161255a-37c3-11d2-bcaa-00c04fd929db}
static GUID IEPStoreGUID = { 0xe161255a, 0x37c3, 0x11d2, { 0xbc, 0xaa, 0x00, 0xc0, 0x4f, 0xd9, 0x29, 0xdb } };
///////////////////////////////////////////////////////////////////////////////
// IMPORTING PASSWORDS
//
// This is tricky, and requires 2 passes through the subkeys in IE's PStore
// section.
//
// First, we walk IE's PStore section, looking for subkeys that are prefixed
// with a URI. As mentioned above, we assume all such subkeys are stored
// passwords.
//
// http://foo.com/login.php:StringData username\0password
//
// We can't add this item to the Password Manager just yet though.
//
// The password manager requires the uniquifier of the username field (that is,
// the value of the "name" or "id" attribute) from the webpage so that it can
// prefill the value automatically. IE doesn't store this information with
// the password entry, but it DOES store this information independently as a
// separate Form Data entry.
//
// In other words, if you go to foo.com above and log in with "username" and
// "password" as your details in a form where the username field is uniquified
// as "un" (<input type="text" name="un">), when you login and elect to have IE
// save the password for the site, the following TWO entries are created in IE's
// PStore section:
//
// http://foo.com/login.php:StringData username\0password
// un:StringData username
//
// Thus to discover the field name for each login we need to first gather up
// all the signons (collecting usernames in the process), then walk the list
// again, looking ONLY at non-URI prefixed subkeys, and searching for each
// username as a value in each such subkey's value list. If we have a match,
// we assume that the subkey (with its uniquifier prefix) is a login field.
//
// With this information, we call Password Manager's "AddUserFull" method
// providing this detail. We don't need to provide the password field name,
// we have no means of retrieving this info from IE, and the Password Manager
// knows to hunt for a password field near the login field if none is specified.
//
// IMPLICATIONS:
// 1) redundant signon entries for non-login forms might be created, but these
// should be benign.
// 2) if the IE user ever clears his Form AutoComplete cache but doesn't clear
// his passwords, we will be hosed, as we have no means of locating the
// username field. Maybe someday the Password Manager will become
// artificially intelligent and be able to guess where the login fields are,
// but I'm not holding my breath.
//
nsresult
nsIEProfileMigrator::CopyPasswords(PRBool aReplace)
{
HRESULT hr;
nsresult rv;
nsVoidArray signonsFound;
HMODULE pstoreDLL = ::LoadLibrary("pstorec.dll");
if (!pstoreDLL) {
// XXXben TODO
// Need to figure out what to do here on Windows 98 etc... it may be that the key is universal read
// and we can just blunder into the registry and use CryptUnprotect to get the data out.
return NS_ERROR_FAILURE;
}
PStoreCreateInstancePtr PStoreCreateInstance = (PStoreCreateInstancePtr)::GetProcAddress(pstoreDLL, "PStoreCreateInstance");
IPStorePtr PStore;
hr = PStoreCreateInstance(&PStore, 0, 0, 0);
rv = GetSignonsListFromPStore(PStore, &signonsFound);
if (NS_SUCCEEDED(rv))
ResolveAndMigrateSignons(PStore, &signonsFound);
return NS_OK;
}
nsresult
nsIEProfileMigrator::GetSignonsListFromPStore(IPStore* aPStore, nsVoidArray* aSignonsFound)
{
HRESULT hr;
NS_ENSURE_ARG_POINTER(aPStore);
IEnumPStoreItemsPtr enumItems = NULL;
hr = aPStore->EnumItems(0, &IEPStoreGUID, &IEPStoreGUID, 0, &enumItems);
if (SUCCEEDED(hr) && enumItems != NULL) {
LPWSTR itemName = NULL;
while ((enumItems->Next(1, &itemName, 0) == S_OK) && itemName) {
unsigned long count = 0;
unsigned char* data = NULL;
// We are responsible for freeing |data| using |CoTaskMemFree|!!
// But we don't do it here...
hr = aPStore->ReadItem(0, &IEPStoreGUID, &IEPStoreGUID, itemName, &count, &data, NULL, 0);
if (SUCCEEDED(hr) && data) {
nsAutoString itemNameString(itemName);
nsAutoString suffix;
itemNameString.Right(suffix, 11);
if (suffix.EqualsIgnoreCase(":StringData")) {
// :StringData contains the saved data
const nsAString& key = Substring(itemNameString, 0, itemNameString.Length() - 11);
char* realm = nsnull;
if (KeyIsURI(key, &realm)) {
// This looks like a URL and could be a password. If it has username and password data, then we'll treat
// it as one and add it to the password manager
unsigned char* username = NULL;
unsigned char* pass = NULL;
GetUserNameAndPass(data, count, &username, &pass);
if (username && pass) {
// username and pass are pointers into the data buffer allocated by IPStore's ReadItem
// method, and we own that buffer. We don't free it here, since we're going to be using
// it after the password harvesting stage to locate the username field. Only after the second
// phase is complete do we free the buffer.
SignonData* d = new SignonData;
if (!d)
return NS_ERROR_OUT_OF_MEMORY;
d->user = (PRUnichar*)username;
d->pass = (PRUnichar*)pass;
d->realm = realm; // freed in ResolveAndMigrateSignons
aSignonsFound->AppendElement(d);
}
}
}
}
}
}
return NS_OK;
}
PRBool
nsIEProfileMigrator::KeyIsURI(const nsAString& aKey, char** aRealm)
{
*aRealm = nsnull;
nsCOMPtr<nsIURI> uri(do_CreateInstance("@mozilla.org/network/standard-url;1"));
if (!uri)
return PR_FALSE;
nsCAutoString keyCStr; keyCStr.AssignWithConversion(aKey);
uri->SetSpec(keyCStr);
PRBool validScheme = PR_FALSE;
const char* schemes[] = { "http", "https" };
for (int i = 0; i < 2; ++i) {
uri->SchemeIs(schemes[i], &validScheme);
if (validScheme) {
nsCAutoString realm;
uri->GetScheme(realm);
realm.Append(NS_LITERAL_CSTRING("://"));
nsCAutoString host;
uri->GetHost(host);
realm.Append(host);
*aRealm = nsCRT::strdup(realm.get());
return validScheme;
}
}
return PR_FALSE;
}
nsresult
nsIEProfileMigrator::ResolveAndMigrateSignons(IPStore* aPStore, nsVoidArray* aSignonsFound)
{
HRESULT hr;
IEnumPStoreItemsPtr enumItems = NULL;
hr = aPStore->EnumItems(0, &IEPStoreGUID, &IEPStoreGUID, 0, &enumItems);
if (SUCCEEDED(hr) && enumItems != NULL) {
LPWSTR itemName = NULL;
while ((enumItems->Next(1, &itemName, 0) == S_OK) && itemName) {
unsigned long count = 0;
unsigned char* data = NULL;
hr = aPStore->ReadItem(0, &IEPStoreGUID, &IEPStoreGUID, itemName, &count, &data, NULL, 0);
if (SUCCEEDED(hr) && data) {
nsAutoString itemNameString(itemName);
nsAutoString suffix;
itemNameString.Right(suffix, 11);
if (suffix.EqualsIgnoreCase(":StringData")) {
// :StringData contains the saved data
const nsAString& key = Substring(itemNameString, 0, itemNameString.Length() - 11);
// Assume all keys that are valid URIs are signons, not saved form data, and that
// all keys that aren't valid URIs are form field names (containing form data).
nsXPIDLCString realm;
if (!KeyIsURI(key, getter_Copies(realm))) {
// Search the data for a username that matches one of the found signons.
EnumerateUsernames(key, (PRUnichar*)data, (count/sizeof(PRUnichar)), aSignonsFound);
}
}
::CoTaskMemFree(data);
}
}
// Now that we've done resolving signons, we need to walk the signons list, freeing the data buffers
// for each SignonData entry, since these buffers were allocated by the system back in |GetSignonListFromPStore|
// but never freed.
PRInt32 signonCount = aSignonsFound->Count();
for (PRInt32 i = 0; i < signonCount; ++i) {
SignonData* sd = (SignonData*)aSignonsFound->ElementAt(i);
::CoTaskMemFree(sd->user); // |sd->user| is a pointer to the start of a buffer that also contains sd->pass
nsCRT::free(sd->realm);
delete sd;
}
}
return NS_OK;
}
void
nsIEProfileMigrator::EnumerateUsernames(const nsAString& aKey, PRUnichar* aData, unsigned long aCount, nsVoidArray* aSignonsFound)
{
nsCOMPtr<nsIPasswordManagerInternal> pwmgr(do_GetService("@mozilla.org/passwordmanager;1"));
if (!pwmgr)
return;
PRUnichar* cursor = aData;
PRInt32 offset = 0;
PRInt32 signonCount = aSignonsFound->Count();
while (offset < aCount) {
nsAutoString curr; curr = cursor;
// Compare the value at the current cursor position with the collected list of signons
for (PRInt32 i = 0; i < signonCount; ++i) {
SignonData* sd = (SignonData*)aSignonsFound->ElementAt(i);
if (curr.Equals(sd->user)) {
// Bingo! Found a username in the saved data for this item. Now, add a Signon.
nsDependentString usernameStr(sd->user), passStr(sd->pass);
nsDependentCString realm(sd->realm);
pwmgr->AddUserFull(realm, usernameStr, passStr, aKey, EmptyString());
}
}
// Advance the cursor
PRInt32 advance = curr.Length() + 1;
cursor += advance; // Advance to next string (length of curr string + 1 PRUnichar for null separator)
offset += advance;
}
}
void
nsIEProfileMigrator::GetUserNameAndPass(unsigned char* data, unsigned long len, unsigned char** username, unsigned char** pass)
{
*username = data;
*pass = NULL;
unsigned char* temp = data;
for (unsigned int i = 0; i < len; i += 2, temp += 2*sizeof(unsigned char)) {
if (*temp == '\0') {
*pass = temp + 2*sizeof(unsigned char);
break;
}
}
}
///////////////////////////////////////////////////////////////////////////////
// IMPORTING FORM DATA
//
// This is a much simpler task as all we need is the field name and that's part
// of the key used to identify each data set in the PStore. The algorithm here
// is as follows:
//
// fieldName1:StringData value1\0value2\0value3\0
// fieldName2:StringData value1\0value2\0value3\0
// fieldName3:StringData value1\0value2\0value3\0
//
// Walk each non-URI prefixed key in IE's PStore section, split the value provided
// into chunks (\0 delimited) and use nsIFormHistory's |addEntry| method to add
// an entry for the fieldName prefix and each value.
//
// "Quite Easily Done". ;-)
//
nsresult
nsIEProfileMigrator::CopyFormData(PRBool aReplace)
{
HRESULT hr;
HMODULE pstoreDLL = ::LoadLibrary("pstorec.dll");
if (!pstoreDLL) {
// XXXben TODO
// Need to figure out what to do here on Windows 98 etc... it may be that the key is universal read
// and we can just blunder into the registry and use CryptUnprotect to get the data out.
return NS_ERROR_FAILURE;
}
PStoreCreateInstancePtr PStoreCreateInstance = (PStoreCreateInstancePtr)::GetProcAddress(pstoreDLL, "PStoreCreateInstance");
IPStorePtr PStore = NULL;
hr = PStoreCreateInstance(&PStore, 0, 0, 0);
if (FAILED(hr) || PStore == NULL)
return NS_OK;
IEnumPStoreItemsPtr enumItems = NULL;
hr = PStore->EnumItems(0, &IEPStoreGUID, &IEPStoreGUID, 0, &enumItems);
if (SUCCEEDED(hr) && enumItems != NULL) {
LPWSTR itemName = NULL;
while ((enumItems->Next(1, &itemName, 0) == S_OK) && itemName) {
unsigned long count = 0;
unsigned char* data = NULL;
// We are responsible for freeing |data| using |CoTaskMemFree|!!
hr = PStore->ReadItem(0, &IEPStoreGUID, &IEPStoreGUID, itemName, &count, &data, NULL, 0);
if (SUCCEEDED(hr) && data) {
nsAutoString itemNameString(itemName);
nsAutoString suffix;
itemNameString.Right(suffix, 11);
if (suffix.EqualsIgnoreCase(":StringData")) {
// :StringData contains the saved data
const nsAString& key = Substring(itemNameString, 0, itemNameString.Length() - 11);
nsXPIDLCString realm;
if (!KeyIsURI(key, getter_Copies(realm))) {
nsresult rv = AddDataToFormHistory(key, (PRUnichar*)data, (count/sizeof(PRUnichar)));
if (NS_FAILED(rv)) return rv;
}
}
}
}
}
return NS_OK;
}
nsresult
nsIEProfileMigrator::AddDataToFormHistory(const nsAString& aKey, PRUnichar* aData, unsigned long aCount)
{
nsCOMPtr<nsIFormHistory> formHistory(do_GetService("@mozilla.org/satchel/form-history;1"));
if (!formHistory)
return NS_ERROR_OUT_OF_MEMORY;
PRUnichar* cursor = aData;
PRInt32 offset = 0;
while (offset < aCount) {
nsAutoString curr; curr = cursor;
formHistory->AddEntry(aKey, curr);
// Advance the cursor
PRInt32 advance = curr.Length() + 1;
cursor += advance; // Advance to next string (length of curr string + 1 PRUnichar for null separator)
offset += advance;
}
return NS_OK;
}
///////////////////////////////////////////////////////////////////////////////
//
// favorites
// search keywords
//
nsresult
nsIEProfileMigrator::CopyFavorites(PRBool aReplace) {
// If "aReplace" is true, merge into the root level of bookmarks. Otherwise, create
// a folder called "Imported IE Favorites" and place all the Bookmarks there.
nsresult rv;
nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1"));
nsCOMPtr<nsIRDFResource> root;
rdf->GetResource(NS_LITERAL_CSTRING("NC:BookmarksRoot"), getter_AddRefs(root));
nsCOMPtr<nsIBookmarksService> bms(do_GetService("@mozilla.org/browser/bookmarks-service;1"));
NS_ENSURE_TRUE(bms, NS_ERROR_FAILURE);
PRBool dummy;
bms->ReadBookmarks(&dummy);
nsAutoString personalToolbarFolderName;
nsCOMPtr<nsIRDFResource> folder;
if (!aReplace) {
nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(kStringBundleServiceCID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIStringBundle> bundle;
bundleService->CreateBundle(TRIDENTPROFILE_BUNDLE, getter_AddRefs(bundle));
nsXPIDLString sourceNameIE;
bundle->GetStringFromName(NS_LITERAL_STRING("sourceNameIE").get(),
getter_Copies(sourceNameIE));
const PRUnichar* sourceNameStrings[] = { sourceNameIE.get() };
nsXPIDLString importedIEFavsTitle;
bundle->FormatStringFromName(NS_LITERAL_STRING("importedBookmarksFolder").get(),
sourceNameStrings, 1, getter_Copies(importedIEFavsTitle));
bms->CreateFolderInContainer(importedIEFavsTitle.get(), root, -1, getter_AddRefs(folder));
}
else {
// Locate the Links toolbar folder, we want to replace the Personal Toolbar content with
// Favorites in this folder.
HKEY key;
if (::RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Microsoft\\Internet Explorer\\Toolbar",
0, KEY_READ, &key) == ERROR_SUCCESS) {
DWORD type, length;
unsigned char linksFolderName[MAX_PATH];
if (::RegQueryValueEx(key, "LinksFolderName", 0, &type, linksFolderName, &length) == ERROR_SUCCESS)
personalToolbarFolderName.AssignWithConversion((char*)linksFolderName);
}
folder = root;
}
nsCOMPtr<nsIProperties> fileLocator(do_GetService("@mozilla.org/file/directory_service;1", &rv));
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIFile> favoritesDirectory;
fileLocator->Get("Favs", NS_GET_IID(nsIFile), getter_AddRefs(favoritesDirectory));
// If |favoritesDirectory| is null, it means that we're on a Windows
// platform that does not have a Favorites folder, e.g. Windows 95
// (early SRs, before IE integrated with the shell). Only try to
// read Favorites folder if it exists on the machine.
if (favoritesDirectory) {
rv = ParseFavoritesFolder(favoritesDirectory, folder, bms, personalToolbarFolderName, PR_TRUE);
if (NS_FAILED(rv)) return rv;
}
return CopySmartKeywords(root);
}
nsresult
nsIEProfileMigrator::CopySmartKeywords(nsIRDFResource* aParentFolder)
{
HKEY key;
if (::RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Microsoft\\Internet Explorer\\SearchUrl",
0, KEY_READ, &key) == ERROR_SUCCESS) {
nsCOMPtr<nsIBookmarksService> bms(do_GetService("@mozilla.org/browser/bookmarks-service;1"));
nsCOMPtr<nsIRDFResource> keywordsFolder, bookmark;
nsCOMPtr<nsIURI> uri(do_CreateInstance("@mozilla.org/network/standard-url;1"));
nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(kStringBundleServiceCID);
nsCOMPtr<nsIStringBundle> bundle;
bundleService->CreateBundle(TRIDENTPROFILE_BUNDLE, getter_AddRefs(bundle));
int offset = 0;
while (1) {
char keyName[MAX_PATH];
DWORD keySize = MAX_PATH;
LONG rv = ::RegEnumKey(key, offset, keyName, keySize);
if (rv == ERROR_NO_MORE_ITEMS)
break;
if (!keywordsFolder) {
nsXPIDLString sourceNameIE;
bundle->GetStringFromName(NS_LITERAL_STRING("sourceNameIE").get(),
getter_Copies(sourceNameIE));
const PRUnichar* sourceNameStrings[] = { sourceNameIE.get() };
nsXPIDLString importedIESearchUrlsTitle;
bundle->FormatStringFromName(NS_LITERAL_STRING("importedSearchURLsFolder").get(),
sourceNameStrings, 1, getter_Copies(importedIESearchUrlsTitle));
bms->CreateFolderInContainer(importedIESearchUrlsTitle.get(), aParentFolder, -1,
getter_AddRefs(keywordsFolder));
}
nsCAutoString keyNameStr(keyName);
nsCAutoString smartKeywordKey(NS_LITERAL_CSTRING("Software\\Microsoft\\Internet Explorer\\SearchUrl\\"));
smartKeywordKey += keyNameStr;
HKEY skey;
if (::RegOpenKeyEx(HKEY_CURRENT_USER,
smartKeywordKey.get(),
0, KEY_READ, &skey) == ERROR_SUCCESS) {
LONG length = LARGE_BUFFER;
char url[LARGE_BUFFER];
if (::RegQueryValue(skey, NULL, (char*)url, &length) == ERROR_SUCCESS) {
uri->SetSpec(nsDependentCString((char*)url));
nsCAutoString hostCStr;
uri->GetHost(hostCStr);
nsAutoString host; host.AssignWithConversion(hostCStr.get());
const PRUnichar* nameStrings[] = { host.get() };
nsXPIDLString keywordName;
rv = bundle->FormatStringFromName(NS_LITERAL_STRING("importedSearchURLsTitle").get(),
nameStrings, 1, getter_Copies(keywordName));
nsAutoString keyword; keyword.AssignWithConversion(keyName);
const PRUnichar* descStrings[] = { keyword.get(), host.get() };
nsXPIDLString keywordDesc;
rv = bundle->FormatStringFromName(NS_LITERAL_STRING("importedSearchUrlDesc").get(),
descStrings, 2, getter_Copies(keywordDesc));
// XXX: Assume Windows stores URLs in ASCII (with everything escaped).
// It may change in the future, but we can't assume that it's UTF-8
// here. Unlike other places, it's likely to be in a legacy encoding.
// Assuming ASCII at least works for Latin-1 if URLs are stored
// without being escaped and Win32 supports IDN (it doesn't at the
// moment.) Eventually, we have to use 'W' APIs when available.
bms->CreateBookmarkInContainer(keywordName.get(),
NS_ConvertASCIItoUTF16(url).get(),
keyword.get(),
keywordDesc.get(),
nsnull,
nsnull,
keywordsFolder,
-1,
getter_AddRefs(bookmark));
}
}
++offset;
}
}
return NS_OK;
}
void
nsIEProfileMigrator::ResolveShortcut(const nsAFlatString &aFileName, char** aOutURL)
{
HRESULT result;
IUniformResourceLocator* urlLink = nsnull;
result = ::CoCreateInstance(CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER,
IID_IUniformResourceLocator, (void**)&urlLink);
if (SUCCEEDED(result) && urlLink) {
IPersistFile* urlFile = nsnull;
result = urlLink->QueryInterface(IID_IPersistFile, (void**)&urlFile);
if (SUCCEEDED(result) && urlFile) {
result = urlFile->Load(aFileName.get(), STGM_READ);
if (SUCCEEDED(result) ) {
LPSTR lpTemp = nsnull;
result = urlLink->GetURL(&lpTemp);
if (SUCCEEDED(result) && lpTemp) {
*aOutURL = PL_strdup(lpTemp);
// free the string that GetURL alloc'd
IMalloc* pMalloc;
result = SHGetMalloc(&pMalloc);
if (SUCCEEDED(result)) {
pMalloc->Free(lpTemp);
pMalloc->Release();
}
}
}
urlFile->Release();
}
urlLink->Release();
}
}
nsresult
nsIEProfileMigrator::ParseFavoritesFolder(nsIFile* aDirectory,
nsIRDFResource* aParentResource,
nsIBookmarksService* aBookmarksService,
const nsAString& aPersonalToolbarFolderName,
PRBool aIsAtRootLevel)
{
nsresult rv;
nsCOMPtr<nsISimpleEnumerator> entries;
rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
if (NS_FAILED(rv)) return rv;
do {
PRBool hasMore = PR_FALSE;
rv = entries->HasMoreElements(&hasMore);
if (NS_FAILED(rv) || !hasMore) break;
nsCOMPtr<nsISupports> supp;
rv = entries->GetNext(getter_AddRefs(supp));
if (NS_FAILED(rv)) break;
nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
nsCOMPtr<nsIURI> uri;
rv = NS_NewFileURI(getter_AddRefs(uri), currFile);
if (NS_FAILED(rv)) break;
nsAutoString bookmarkName;
currFile->GetLeafName(bookmarkName);
PRBool isSymlink = PR_FALSE;
PRBool isDir = PR_FALSE;
currFile->IsSymlink(&isSymlink);
currFile->IsDirectory(&isDir);
if (isSymlink) {
// It's a .lnk file. Get the native path and check to see if it's
// a dir. If so, create a bookmark for the dir. If not, then
// simply do nothing and continue.
// Get the native path that the .lnk file is pointing to.
nsCAutoString path;
rv = currFile->GetNativeTarget(path);
if (NS_FAILED(rv)) continue;
nsCOMPtr<nsILocalFile> localFile;
rv = NS_NewNativeLocalFile(path, PR_TRUE, getter_AddRefs(localFile));
if (NS_FAILED(rv)) continue;
// Check for dir here. If path is not a dir, just continue with
// next import.
rv = localFile->IsDirectory(&isDir);
NS_ENSURE_SUCCESS(rv, rv);
if (!isDir) continue;
nsCAutoString spec;
nsCOMPtr<nsIFile> filePath(localFile);
// Get the file url format (file:///...) of the native file path.
rv = NS_GetURLSpecFromFile(filePath, spec);
if (NS_FAILED(rv)) continue;
// Look for and strip out the .lnk extension.
NS_NAMED_LITERAL_STRING(lnkExt, ".lnk");
PRInt32 lnkExtStart = bookmarkName.Length() - lnkExt.Length();
if (StringEndsWith(bookmarkName, lnkExt,
nsCaseInsensitiveStringComparator()))
bookmarkName.Truncate(lnkExtStart);
nsCOMPtr<nsIRDFResource> bookmark;
// Here it's assumed that NS_GetURLSpecFromFile returns spec in UTF-8.
// It is very likely to be ASCII (with everything escaped beyond file://),
// but we don't lose much assuming that it's UTF-8. This is not perf.
// critical.
aBookmarksService->CreateBookmarkInContainer(bookmarkName.get(),
NS_ConvertUTF8toUTF16(spec).get(),
nsnull,
nsnull,
nsnull,
nsnull,
aParentResource,
-1,
getter_AddRefs(bookmark));
if (NS_FAILED(rv)) continue;
}
else if (isDir) {
nsCOMPtr<nsIRDFResource> folder;
if (bookmarkName.Equals(aPersonalToolbarFolderName)) {
aBookmarksService->GetBookmarksToolbarFolder(getter_AddRefs(folder));
// If we're here, it means the user's doing a _replace_ import which means
// clear out the content of this folder, and replace it with the new content
nsCOMPtr<nsIRDFContainer> ctr(do_CreateInstance("@mozilla.org/rdf/container;1"));
nsCOMPtr<nsIRDFDataSource> bmds(do_QueryInterface(aBookmarksService));
ctr->Init(bmds, folder);
nsCOMPtr<nsISimpleEnumerator> e;
ctr->GetElements(getter_AddRefs(e));
PRBool hasMore;
e->HasMoreElements(&hasMore);
while (hasMore) {
nsCOMPtr<nsIRDFResource> b;
e->GetNext(getter_AddRefs(b));
ctr->RemoveElement(b, PR_FALSE);
e->HasMoreElements(&hasMore);
}
}
else {
rv = aBookmarksService->CreateFolderInContainer(bookmarkName.get(),
aParentResource,
-1,
getter_AddRefs(folder));
if (NS_FAILED(rv)) continue;
}
rv = ParseFavoritesFolder(currFile, folder, aBookmarksService, aPersonalToolbarFolderName, PR_FALSE);
if (NS_FAILED(rv)) continue;
}
else {
nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
nsCAutoString extension;
url->GetFileExtension(extension);
if (!extension.Equals(NS_LITERAL_CSTRING("url"),
nsCaseInsensitiveCStringComparator()))
continue;
nsAutoString name(Substring(bookmarkName, 0,
bookmarkName.Length() - extension.Length() - 1));
nsAutoString path;
currFile->GetPath(path);
nsXPIDLCString resolvedURL;
ResolveShortcut(path, getter_Copies(resolvedURL));
nsCOMPtr<nsIRDFResource> bookmark;
// As far as I can tell reading the MSDN API document,
// IUniformResourceLocator::GetURL (used by ResolveShortcut) returns a
// URL in ASCII (with non-ASCII characters escaped) and it doesn't yet
// support IDN (i18n) hostname. However, it may in the future so that
// using UTF8toUTF16 wouldn't be a bad idea.
rv = aBookmarksService->CreateBookmarkInContainer(name.get(),
NS_ConvertUTF8toUTF16(resolvedURL).get(),
nsnull,
nsnull,
nsnull,
nsnull,
aParentResource,
-1,
getter_AddRefs(bookmark));
if (NS_FAILED(rv)) continue;
}
}
while (1);
return rv;
}
nsresult
nsIEProfileMigrator::CopyPreferences(PRBool aReplace)
{
HKEY regKey;
PRBool regKeyOpen = PR_FALSE;
struct regEntry *entry,
*endEntry = gRegEntries +
sizeof(gRegEntries)/sizeof(struct regEntry);
DWORD regType;
DWORD regLength;
unsigned char regValue[MAX_PATH]; // a convenient size
nsCOMPtr<nsIPrefBranch> prefs;
{ // scope pserve why not
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (pserve)
pserve->GetBranch("", getter_AddRefs(prefs));
}
if (!prefs)
return NS_ERROR_FAILURE;
// step through gRegEntries table
for (entry = gRegEntries; entry < endEntry; ++entry) {
// a new keyname? close any previous one and open the new one
if (entry->regKeyName) {
if (regKeyOpen) {
::RegCloseKey(regKey);
regKeyOpen = PR_FALSE;
}
regKeyOpen = ::RegOpenKeyEx(HKEY_CURRENT_USER, entry->regKeyName, 0,
KEY_READ, &regKey) == ERROR_SUCCESS;
}
if (regKeyOpen) {
// read registry data
regLength = MAX_PATH;
if (::RegQueryValueEx(regKey, entry->regValueName, 0,
&regType, regValue, &regLength) == ERROR_SUCCESS)
entry->entryHandler(regValue, regLength, regType,
prefs, entry->prefKeyName);
}
}
if (regKeyOpen)
::RegCloseKey(regKey);
nsresult rv = CopySecurityPrefs(prefs);
if (NS_FAILED(rv)) return rv;
rv = CopyProxyPreferences(prefs);
if (NS_FAILED(rv)) return rv;
return CopyStyleSheet(aReplace);
}
/* Fetch and translate the current user's cookies.
Return true if successful. */
nsresult
nsIEProfileMigrator::CopyCookies(PRBool aReplace)
{
// IE cookies are stored in files named <username>@domain[n].txt
// (in <username>'s Cookies folder. isn't the naming redundant?)
PRBool rv = NS_OK;
nsCOMPtr<nsIFile> cookiesDir;
nsCOMPtr<nsISimpleEnumerator> cookieFiles;
nsCOMPtr<nsICookieManager2> cookieManager(do_GetService(NS_COOKIEMANAGER_CONTRACTID));
if (!cookieManager)
return NS_ERROR_FAILURE;
// find the cookies directory
NS_GetSpecialDirectory(NS_WIN_COOKIES_DIR, getter_AddRefs(cookiesDir));
if (cookiesDir)
cookiesDir->GetDirectoryEntries(getter_AddRefs(cookieFiles));
if (!cookieFiles)
return NS_ERROR_FAILURE;
// fetch the current user's name from the environment
char username[sUsernameLengthLimit+2];
::GetEnvironmentVariable("USERNAME", username, sizeof(username));
username[sizeof(username)-2] = '\0';
PL_strcat(username, "@");
int usernameLength = PL_strlen(username);
// allocate a buffer into which to read each cookie file
char *fileContents = (char *) PR_Malloc(sInitialCookieBufferSize);
if (!fileContents)
return NS_ERROR_OUT_OF_MEMORY;
PRUint32 fileContentsSize = sInitialCookieBufferSize;
do { // for each file in the cookies directory
// get the next file
PRBool moreFiles = PR_FALSE;
if (NS_FAILED(cookieFiles->HasMoreElements(&moreFiles)) || !moreFiles)
break;
nsCOMPtr<nsISupports> supFile;
cookieFiles->GetNext(getter_AddRefs(supFile));
nsCOMPtr<nsIFile> cookieFile(do_QueryInterface(supFile));
if (!cookieFile) {
rv = NS_ERROR_FAILURE;
break; // unexpected! punt!
}
// is it a cookie file for the current user?
nsCAutoString fileName;
cookieFile->GetNativeLeafName(fileName);
const nsACString &fileOwner = Substring(fileName, 0, usernameLength);
if (!fileOwner.Equals(username, nsCaseInsensitiveCStringComparator()))
continue;
// ensure the contents buffer is large enough to hold the entire file
// plus one byte (see DelimitField())
PRInt64 llFileSize;
if (NS_FAILED(cookieFile->GetFileSize(&llFileSize)))
continue;
PRUint32 fileSize, readSize;
LL_L2UI(fileSize, llFileSize);
if (fileSize >= fileContentsSize) {
PR_Free(fileContents);
fileContents = (char *) PR_Malloc(fileSize+1);
if (!fileContents) {
rv = NS_ERROR_FAILURE;
break; // fatal error
}
fileContentsSize = fileSize;
}
// read the entire cookie file
PRFileDesc *fd;
nsCOMPtr<nsILocalFile> localCookieFile(do_QueryInterface(cookieFile));
if (localCookieFile &&
NS_SUCCEEDED(localCookieFile->OpenNSPRFileDesc(PR_RDONLY, 0444, &fd))) {
readSize = PR_Read(fd, fileContents, fileSize);
PR_Close(fd);
if (fileSize == readSize) { // translate this file's cookies
nsresult onerv;
onerv = CopyCookiesFromBuffer(fileContents, readSize, cookieManager);
if (NS_FAILED(onerv))
rv = onerv;
}
}
} while(1);
if (fileContents)
PR_Free(fileContents);
return rv;
}
/* Fetch cookies from a single IE cookie file.
Return true if successful. */
nsresult
nsIEProfileMigrator::CopyCookiesFromBuffer(char *aBuffer,
PRUint32 aBufferLength,
nsICookieManager2 *aCookieManager)
{
nsresult rv = NS_OK;
const char *bufferEnd = aBuffer + aBufferLength;
// cookie fields:
char *name,
*value,
*host,
*path,
*flags,
*expirationDate1, *expirationDate2,
*creationDate1, *creationDate2,
*terminator;
int flagsValue;
time_t expirationDate,
creationDate;
char hostCopy[sHostnameLengthLimit+1],
*hostCopyConstructor,
*hostCopyEnd = hostCopy + sHostnameLengthLimit;
do { // for each cookie in the buffer
DelimitField(&aBuffer, bufferEnd, &name);
DelimitField(&aBuffer, bufferEnd, &value);
DelimitField(&aBuffer, bufferEnd, &host);
DelimitField(&aBuffer, bufferEnd, &flags);
DelimitField(&aBuffer, bufferEnd, &expirationDate1);
DelimitField(&aBuffer, bufferEnd, &expirationDate2);
DelimitField(&aBuffer, bufferEnd, &creationDate1);
DelimitField(&aBuffer, bufferEnd, &creationDate2);
DelimitField(&aBuffer, bufferEnd, &terminator);
// it's a cookie if we got one of each
if (terminator >= bufferEnd)
break;
// IE stores deleted cookies with a zero-length value
if (*value == '\0')
continue;
// convert flags to an int, date numbers to useable dates
::sscanf(flags, "%d", &flagsValue);
expirationDate = FileTimeToTimeT(expirationDate1, expirationDate2);
creationDate = FileTimeToTimeT(creationDate1, creationDate2);
// munge host, and separate host from path
hostCopyConstructor = hostCopy;
// first, with a non-null domain, assume it's what Mozilla considers
// a domain cookie. see bug 222343.
if (*host && *host != '.' && *host != '/')
*hostCopyConstructor++ = '.';
// copy the host part and leave path pointing to the path part
for (path = host; *path && *path != '/'; ++path)
;
int hostLength = path - host;
if (hostLength > hostCopyEnd - hostCopyConstructor)
hostLength = hostCopyEnd - hostCopyConstructor;
PL_strncpy(hostCopyConstructor, host, hostLength);
hostCopyConstructor += hostLength;
*hostCopyConstructor = '\0';
nsDependentCString stringName(name),
stringPath(path);
// delete any possible extant matching host cookie
if (hostCopy[0] == '.')
aCookieManager->Remove(nsDependentCString(hostCopy+1),
stringName, stringPath, PR_FALSE);
nsresult onerv;
// Add() makes a new domain cookie
onerv = aCookieManager->Add(nsDependentCString(hostCopy),
stringPath,
stringName,
nsDependentCString(value),
flagsValue & 0x1,
PR_FALSE,
PRInt64(expirationDate));
if (NS_FAILED(onerv)) {
rv = onerv;
break;
}
} while(aBuffer < bufferEnd);
return rv;
}
/* Delimit the next field in the IE cookie buffer.
when called:
aBuffer: at the beginning of the next field or preceding whitespace
aBufferEnd: one past the last valid character in the buffer
on return:
aField: at the beginning of the next field, which is null delimited
aBuffer: after the null at the end of the field
the character at which aBufferEnd points must be part of the buffer
so we can set it to \0.
*/
void
nsIEProfileMigrator::DelimitField(char **aBuffer,
const char *aBufferEnd,
char **aField)
{
char *scan = *aBuffer;
*aField = scan;
while (scan < aBufferEnd && (*scan != '\r' && *scan != '\n'))
++scan;
if (scan+1 < aBufferEnd && (*(scan+1) == '\r' || *(scan+1) == '\n') &&
*scan != *(scan+1)) {
*scan = '\0';
scan += 2;
} else {
if (scan <= aBufferEnd) // (1 byte past bufferEnd is guaranteed allocated)
*scan = '\0';
++scan;
}
*aBuffer = scan;
}
// conversion routine. returns 0 (epoch date) if the input is out of range
time_t
nsIEProfileMigrator::FileTimeToTimeT(const char *aLowDateIntString,
const char *aHighDateIntString)
{
FILETIME fileTime;
SYSTEMTIME systemTime;
tm tTime;
time_t rv;
::sscanf(aLowDateIntString, "%ld", &fileTime.dwLowDateTime);
::sscanf(aHighDateIntString, "%ld", &fileTime.dwHighDateTime);
::FileTimeToSystemTime(&fileTime, &systemTime);
tTime.tm_year = systemTime.wYear - 1900;
tTime.tm_mon = systemTime.wMonth-1;
tTime.tm_mday = systemTime.wDay;
tTime.tm_hour = systemTime.wHour;
tTime.tm_min = systemTime.wMinute;
tTime.tm_sec = systemTime.wSecond;
tTime.tm_isdst = -1;
rv = ::mktime(&tTime);
return rv < 0 ? 0 : rv;
}
/* Find the accessibility stylesheet if it exists and replace Mozilla's
with it. Return true if we found and copied a stylesheet. */
nsresult
nsIEProfileMigrator::CopyStyleSheet(PRBool aReplace)
{
nsresult rv = NS_OK; // return failure only if filecopy fails
HKEY regKey;
DWORD regType,
regLength,
regDWordValue;
char tridentFilename[MAX_PATH];
// is there a trident user stylesheet?
if (::RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Microsoft\\Internet Explorer\\Styles", 0,
KEY_READ, &regKey) != ERROR_SUCCESS)
return NS_OK;
regLength = sizeof(DWORD);
if (::RegQueryValueEx(regKey, "Use My Stylesheet", 0, &regType,
NS_REINTERPRET_CAST(unsigned char *, &regDWordValue),
&regLength) == ERROR_SUCCESS &&
regType == REG_DWORD && regDWordValue == 1) {
regLength = MAX_PATH;
if (::RegQueryValueEx(regKey, "User Stylesheet", 0, &regType,
NS_REINTERPRET_CAST(unsigned char *, tridentFilename),
&regLength)
== ERROR_SUCCESS) {
// tridentFilename is a native path to the specified stylesheet file
// point an nsIFile at it
nsCOMPtr<nsILocalFile> tridentFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
if (tridentFile) {
PRBool exists;
tridentFile->InitWithNativePath(nsDependentCString(tridentFilename));
tridentFile->Exists(&exists);
if (exists) {
// now establish our file (userContent.css in the profile/chrome dir)
nsCOMPtr<nsIFile> chromeDir;
NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR,
getter_AddRefs(chromeDir));
if (chromeDir)
rv = tridentFile->CopyToNative(chromeDir,
nsDependentCString("userContent.css"));
}
}
}
}
::RegCloseKey(regKey);
return rv;
}
void
nsIEProfileMigrator::GetUserStyleSheetFile(nsIFile **aUserFile)
{
nsCOMPtr<nsIFile> userChrome;
*aUserFile = 0;
// establish the chrome directory
NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR, getter_AddRefs(userChrome));
if (userChrome) {
PRBool exists;
userChrome->Exists(&exists);
if (!exists &&
NS_FAILED(userChrome->Create(nsIFile::DIRECTORY_TYPE, 0755)))
return;
// establish the user content stylesheet file
userChrome->AppendNative(NS_LITERAL_CSTRING("userContent.css"));
*aUserFile = userChrome;
NS_ADDREF(*aUserFile);
}
}
nsresult
nsIEProfileMigrator::CopySecurityPrefs(nsIPrefBranch* aPrefs)
{
HKEY key;
if (::RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
0, KEY_READ, &key) == ERROR_SUCCESS) {
DWORD value, type;
DWORD length = sizeof DWORD;
if (::RegQueryValueEx(key, "SecureProtocols", 0, &type, (LPBYTE)&value, &length) == ERROR_SUCCESS) {
aPrefs->SetBoolPref("security.enable_ssl2", value & 0x08);
aPrefs->SetBoolPref("security.enable_ssl3", value & 0x20);
aPrefs->SetBoolPref("security.enable_tls", value & 0x80);
}
}
return NS_OK;
}
struct ProxyData {
char* prefix;
PRInt32 prefixLength;
PRBool proxyConfigured;
char* hostPref;
char* portPref;
};
nsresult
nsIEProfileMigrator::CopyProxyPreferences(nsIPrefBranch* aPrefs)
{
HKEY key;
if (::RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
0, KEY_READ, &key) == ERROR_SUCCESS) {
DWORD type, length = LARGE_BUFFER;
char buf[LARGE_BUFFER];
// If there's an autoconfig URL specified in the registry at all, it is being used.
if (::RegQueryValueEx(key, "AutoConfigURL", 0, &type, (LPBYTE)&buf, &length) == ERROR_SUCCESS) {
aPrefs->SetCharPref("network.proxy.autoconfig_url", buf);
aPrefs->SetIntPref("network.proxy.type", 2);
}
// ProxyEnable
DWORD enabled;
length = LARGE_BUFFER;
if (::RegQueryValueEx(key, "ProxyEnable", 0, &type, (LPBYTE)&enabled, &length) == ERROR_SUCCESS) {
if (enabled & 0x1)
aPrefs->SetIntPref("network.proxy.type", 1);
else
aPrefs->SetIntPref("network.proxy.type", 0);
}
length = LARGE_BUFFER;
LONG r = ::RegQueryValueEx(key, "ProxyOverride", 0, &type, (LPBYTE)&buf, &length);
if (r == ERROR_SUCCESS)
ParseOverrideServers(buf, aPrefs);
length = LARGE_BUFFER;
r = ::RegQueryValueEx(key, "ProxyServer", 0, &type, (LPBYTE)&buf, &length);
if (r == ERROR_SUCCESS) {
nsCAutoString bufStr; bufStr = (char*)buf;
ProxyData data[] = {
{ "ftp=", 4, PR_FALSE, "network.proxy.ftp", "network.proxy.ftp_port" },
{ "gopher=", 7, PR_FALSE, "network.proxy.gopher", "network.proxy.gopher_port" },
{ "http=", 5, PR_FALSE, "network.proxy.http", "network.proxy.http_port" },
{ "https=", 6, PR_FALSE, "network.proxy.ssl", "network.proxy.ssl_port" },
{ "socks=", 6, PR_FALSE, "network.proxy.socks", "network.proxy.socks_port" },
};
PRInt32 startIndex = 0, count = 0;
PRBool foundSpecificProxy = PR_FALSE;
for (PRUint32 i = 0; i < 5; ++i) {
PRInt32 offset = bufStr.Find(data[i].prefix);
if (offset >= 0) {
foundSpecificProxy = PR_TRUE;
data[i].proxyConfigured = PR_TRUE;
startIndex = offset + data[i].prefixLength;
PRInt32 terminal = bufStr.FindChar(';', offset);
count = terminal > startIndex ? terminal - startIndex : bufStr.Length() - startIndex;
// hostPort now contains host:port
SetProxyPref(Substring(bufStr, startIndex, count), data[i].hostPref, data[i].portPref, aPrefs);
}
}
if (!foundSpecificProxy) {
// No proxy config for any specific type was found, assume the ProxyServer value
// is of the form host:port and that it applies to all protocols.
for (PRUint32 i = 0; i < 5; ++i)
SetProxyPref(bufStr, data[i].hostPref, data[i].portPref, aPrefs);
aPrefs->SetBoolPref("network.proxy.share_proxy_settings", PR_TRUE);
}
}
}
return NS_OK;
}