ie_importer.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/importer/ie_importer.h" 6 7#include <ole2.h> 8#include <intshcut.h> 9#include <pstore.h> 10#include <shlobj.h> 11#include <urlhist.h> 12 13#include <algorithm> 14#include <map> 15#include <string> 16#include <vector> 17 18#include "app/l10n_util.h" 19#include "app/win_util.h" 20#include "base/file_path.h" 21#include "base/file_util.h" 22#include "base/registry.h" 23#include "base/scoped_comptr_win.h" 24#include "base/string_split.h" 25#include "base/string_util.h" 26#include "base/time.h" 27#include "base/values.h" 28#include "base/utf_string_conversions.h" 29#include "base/win_util.h" 30#include "chrome/browser/bookmarks/bookmark_model.h" 31#include "chrome/browser/importer/importer_bridge.h" 32#include "chrome/browser/importer/importer_data_types.h" 33#include "chrome/browser/password_manager/ie7_password.h" 34#include "chrome/browser/search_engines/template_url.h" 35#include "chrome/browser/search_engines/template_url_model.h" 36#include "chrome/common/time_format.h" 37#include "chrome/common/url_constants.h" 38#include "googleurl/src/gurl.h" 39#include "grit/generated_resources.h" 40#include "webkit/glue/password_form.h" 41 42using base::Time; 43using webkit_glue::PasswordForm; 44 45namespace { 46 47// Gets the creation time of the given file or directory. 48static Time GetFileCreationTime(const std::wstring& file) { 49 Time creation_time; 50 ScopedHandle file_handle( 51 CreateFile(file.c_str(), GENERIC_READ, 52 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 53 NULL, OPEN_EXISTING, 54 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL)); 55 FILETIME creation_filetime; 56 if (GetFileTime(file_handle, &creation_filetime, NULL, NULL)) 57 creation_time = Time::FromFileTime(creation_filetime); 58 return creation_time; 59} 60 61} // namespace 62 63// static 64// {E161255A-37C3-11D2-BCAA-00C04fD929DB} 65const GUID IEImporter::kPStoreAutocompleteGUID = {0xe161255a, 0x37c3, 0x11d2, 66 {0xbc, 0xaa, 0x00, 0xc0, 0x4f, 0xd9, 0x29, 0xdb}}; 67// {A79029D6-753E-4e27-B807-3D46AB1545DF} 68const GUID IEImporter::kUnittestGUID = { 0xa79029d6, 0x753e, 0x4e27, 69 {0xb8, 0x7, 0x3d, 0x46, 0xab, 0x15, 0x45, 0xdf}}; 70 71void IEImporter::StartImport(ProfileInfo profile_info, 72 uint16 items, 73 ImporterBridge* bridge) { 74 bridge_ = bridge; 75 source_path_ = profile_info.source_path.ToWStringHack(); 76 77 bridge_->NotifyStarted(); 78 79 // Some IE settings (such as Protected Storage) are obtained via COM APIs. 80 win_util::ScopedCOMInitializer com_initializer; 81 82 if ((items & importer::HOME_PAGE) && !cancelled()) 83 ImportHomepage(); // Doesn't have a UI item. 84 // The order here is important! 85 if ((items & importer::HISTORY) && !cancelled()) { 86 bridge_->NotifyItemStarted(importer::HISTORY); 87 ImportHistory(); 88 bridge_->NotifyItemEnded(importer::HISTORY); 89 } 90 if ((items & importer::FAVORITES) && !cancelled()) { 91 bridge_->NotifyItemStarted(importer::FAVORITES); 92 ImportFavorites(); 93 bridge_->NotifyItemEnded(importer::FAVORITES); 94 } 95 if ((items & importer::SEARCH_ENGINES) && !cancelled()) { 96 bridge_->NotifyItemStarted(importer::SEARCH_ENGINES); 97 ImportSearchEngines(); 98 bridge_->NotifyItemEnded(importer::SEARCH_ENGINES); 99 } 100 if ((items & importer::PASSWORDS) && !cancelled()) { 101 bridge_->NotifyItemStarted(importer::PASSWORDS); 102 // Always import IE6 passwords. 103 ImportPasswordsIE6(); 104 105 if (CurrentIEVersion() >= 7) 106 ImportPasswordsIE7(); 107 bridge_->NotifyItemEnded(importer::PASSWORDS); 108 } 109 bridge_->NotifyEnded(); 110} 111 112void IEImporter::ImportFavorites() { 113 std::wstring path; 114 115 FavoritesInfo info; 116 if (!GetFavoritesInfo(&info)) 117 return; 118 119 BookmarkVector bookmarks; 120 ParseFavoritesFolder(info, &bookmarks); 121 122 if (!bookmarks.empty() && !cancelled()) { 123 const std::wstring& first_folder_name = 124 l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_IE); 125 int options = 0; 126 if (import_to_bookmark_bar()) 127 options = ProfileWriter::IMPORT_TO_BOOKMARK_BAR; 128 bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); 129 } 130} 131 132void IEImporter::ImportPasswordsIE6() { 133 GUID AutocompleteGUID = kPStoreAutocompleteGUID; 134 if (!source_path_.empty()) { 135 // We supply a fake GUID for testting. 136 AutocompleteGUID = kUnittestGUID; 137 } 138 139 // The PStoreCreateInstance function retrieves an interface pointer 140 // to a storage provider. But this function has no associated import 141 // library or header file, we must call it using the LoadLibrary() 142 // and GetProcAddress() functions. 143 typedef HRESULT (WINAPI *PStoreCreateFunc)(IPStore**, DWORD, DWORD, DWORD); 144 HMODULE pstorec_dll = LoadLibrary(L"pstorec.dll"); 145 if (!pstorec_dll) 146 return; 147 PStoreCreateFunc PStoreCreateInstance = 148 (PStoreCreateFunc)GetProcAddress(pstorec_dll, "PStoreCreateInstance"); 149 if (!PStoreCreateInstance) { 150 FreeLibrary(pstorec_dll); 151 return; 152 } 153 154 ScopedComPtr<IPStore, &IID_IPStore> pstore; 155 HRESULT result = PStoreCreateInstance(pstore.Receive(), 0, 0, 0); 156 if (result != S_OK) { 157 FreeLibrary(pstorec_dll); 158 return; 159 } 160 161 std::vector<AutoCompleteInfo> ac_list; 162 163 // Enumerates AutoComplete items in the protected database. 164 ScopedComPtr<IEnumPStoreItems, &IID_IEnumPStoreItems> item; 165 result = pstore->EnumItems(0, &AutocompleteGUID, 166 &AutocompleteGUID, 0, item.Receive()); 167 if (result != PST_E_OK) { 168 pstore.Release(); 169 FreeLibrary(pstorec_dll); 170 return; 171 } 172 173 wchar_t* item_name; 174 while (!cancelled() && SUCCEEDED(item->Next(1, &item_name, 0))) { 175 DWORD length = 0; 176 unsigned char* buffer = NULL; 177 result = pstore->ReadItem(0, &AutocompleteGUID, &AutocompleteGUID, 178 item_name, &length, &buffer, NULL, 0); 179 if (SUCCEEDED(result)) { 180 AutoCompleteInfo ac; 181 ac.key = item_name; 182 std::wstring data; 183 data.insert(0, reinterpret_cast<wchar_t*>(buffer), 184 length / sizeof(wchar_t)); 185 186 // The key name is always ended with ":StringData". 187 const wchar_t kDataSuffix[] = L":StringData"; 188 size_t i = ac.key.rfind(kDataSuffix); 189 if (i != std::wstring::npos && ac.key.substr(i) == kDataSuffix) { 190 ac.key.erase(i); 191 ac.is_url = (ac.key.find(L"://") != std::wstring::npos); 192 ac_list.push_back(ac); 193 SplitString(data, L'\0', &ac_list[ac_list.size() - 1].data); 194 } 195 CoTaskMemFree(buffer); 196 } 197 CoTaskMemFree(item_name); 198 } 199 // Releases them before unload the dll. 200 item.Release(); 201 pstore.Release(); 202 FreeLibrary(pstorec_dll); 203 204 size_t i; 205 for (i = 0; i < ac_list.size(); i++) { 206 if (!ac_list[i].is_url || ac_list[i].data.size() < 2) 207 continue; 208 209 GURL url(ac_list[i].key.c_str()); 210 if (!(LowerCaseEqualsASCII(url.scheme(), chrome::kHttpScheme) || 211 LowerCaseEqualsASCII(url.scheme(), chrome::kHttpsScheme))) { 212 continue; 213 } 214 215 PasswordForm form; 216 GURL::Replacements rp; 217 rp.ClearUsername(); 218 rp.ClearPassword(); 219 rp.ClearQuery(); 220 rp.ClearRef(); 221 form.origin = url.ReplaceComponents(rp); 222 form.username_value = ac_list[i].data[0]; 223 form.password_value = ac_list[i].data[1]; 224 form.signon_realm = url.GetOrigin().spec(); 225 226 // This is not precise, because a scheme of https does not imply a valid 227 // certificate was presented; however we assign it this way so that if we 228 // import a password from IE whose scheme is https, we give it the benefit 229 // of the doubt and DONT auto-fill it unless the form appears under 230 // valid SSL conditions. 231 form.ssl_valid = url.SchemeIsSecure(); 232 233 // Goes through the list to find out the username field 234 // of the web page. 235 size_t list_it, item_it; 236 for (list_it = 0; list_it < ac_list.size(); ++list_it) { 237 if (ac_list[list_it].is_url) 238 continue; 239 240 for (item_it = 0; item_it < ac_list[list_it].data.size(); ++item_it) 241 if (ac_list[list_it].data[item_it] == form.username_value) { 242 form.username_element = ac_list[list_it].key; 243 break; 244 } 245 } 246 247 bridge_->SetPasswordForm(form); 248 } 249} 250 251void IEImporter::ImportPasswordsIE7() { 252 if (!source_path_.empty()) { 253 // We have been called from the unit tests. Don't import real passwords. 254 return; 255 } 256 257 const wchar_t kStorage2Path[] = 258 L"Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2"; 259 260 RegKey key(HKEY_CURRENT_USER, kStorage2Path, KEY_READ); 261 RegistryValueIterator reg_iterator(HKEY_CURRENT_USER, kStorage2Path); 262 while (reg_iterator.Valid() && !cancelled()) { 263 // Get the size of the encrypted data. 264 DWORD value_len = 0; 265 if (key.ReadValue(reg_iterator.Name(), NULL, &value_len, NULL) && 266 value_len) { 267 // Query the encrypted data. 268 std::vector<unsigned char> value; 269 value.resize(value_len); 270 if (key.ReadValue(reg_iterator.Name(), &value.front(), &value_len, 271 NULL)) { 272 IE7PasswordInfo password_info; 273 password_info.url_hash = reg_iterator.Name(); 274 password_info.encrypted_data = value; 275 password_info.date_created = Time::Now(); 276 277 bridge_->AddIE7PasswordInfo(password_info); 278 } 279 } 280 281 ++reg_iterator; 282 } 283} 284 285// Reads history information from COM interface. 286void IEImporter::ImportHistory() { 287 const std::string kSchemes[] = {chrome::kHttpScheme, 288 chrome::kHttpsScheme, 289 chrome::kFtpScheme, 290 chrome::kFileScheme}; 291 int total_schemes = arraysize(kSchemes); 292 293 ScopedComPtr<IUrlHistoryStg2> url_history_stg2; 294 HRESULT result; 295 result = url_history_stg2.CreateInstance(CLSID_CUrlHistory, NULL, 296 CLSCTX_INPROC_SERVER); 297 if (FAILED(result)) 298 return; 299 ScopedComPtr<IEnumSTATURL> enum_url; 300 if (SUCCEEDED(result = url_history_stg2->EnumUrls(enum_url.Receive()))) { 301 std::vector<history::URLRow> rows; 302 STATURL stat_url; 303 ULONG fetched; 304 while (!cancelled() && 305 (result = enum_url->Next(1, &stat_url, &fetched)) == S_OK) { 306 std::wstring url_string; 307 std::wstring title_string; 308 if (stat_url.pwcsUrl) { 309 url_string = stat_url.pwcsUrl; 310 CoTaskMemFree(stat_url.pwcsUrl); 311 } 312 if (stat_url.pwcsTitle) { 313 title_string = stat_url.pwcsTitle; 314 CoTaskMemFree(stat_url.pwcsTitle); 315 } 316 317 GURL url(url_string); 318 // Skips the URLs that are invalid or have other schemes. 319 if (!url.is_valid() || 320 (std::find(kSchemes, kSchemes + total_schemes, url.scheme()) == 321 kSchemes + total_schemes)) 322 continue; 323 324 history::URLRow row(url); 325 row.set_title(title_string); 326 row.set_last_visit(Time::FromFileTime(stat_url.ftLastVisited)); 327 if (stat_url.dwFlags == STATURL_QUERYFLAG_TOPLEVEL) { 328 row.set_visit_count(1); 329 row.set_hidden(false); 330 } else { 331 row.set_hidden(true); 332 } 333 334 rows.push_back(row); 335 } 336 337 if (!rows.empty() && !cancelled()) { 338 bridge_->SetHistoryItems(rows, history::SOURCE_IE_IMPORTED); 339 } 340 } 341} 342 343void IEImporter::ImportSearchEngines() { 344 // On IE, search engines are stored in the registry, under: 345 // Software\Microsoft\Internet Explorer\SearchScopes 346 // Each key represents a search engine. The URL value contains the URL and 347 // the DisplayName the name. 348 // The default key's name is contained under DefaultScope. 349 const wchar_t kSearchScopePath[] = 350 L"Software\\Microsoft\\Internet Explorer\\SearchScopes"; 351 352 RegKey key(HKEY_CURRENT_USER, kSearchScopePath, KEY_READ); 353 std::wstring default_search_engine_name; 354 const TemplateURL* default_search_engine = NULL; 355 std::map<std::string, TemplateURL*> search_engines_map; 356 key.ReadValue(L"DefaultScope", &default_search_engine_name); 357 RegistryKeyIterator key_iterator(HKEY_CURRENT_USER, kSearchScopePath); 358 while (key_iterator.Valid()) { 359 std::wstring sub_key_name = kSearchScopePath; 360 sub_key_name.append(L"\\").append(key_iterator.Name()); 361 RegKey sub_key(HKEY_CURRENT_USER, sub_key_name.c_str(), KEY_READ); 362 std::wstring wide_url; 363 if (!sub_key.ReadValue(L"URL", &wide_url) || wide_url.empty()) { 364 LOG(INFO) << "No URL for IE search engine at " << key_iterator.Name(); 365 ++key_iterator; 366 continue; 367 } 368 // For the name, we try the default value first (as Live Search uses a 369 // non displayable name in DisplayName, and the readable name under the 370 // default value). 371 std::wstring name; 372 if (!sub_key.ReadValue(NULL, &name) || name.empty()) { 373 // Try the displayable name. 374 if (!sub_key.ReadValue(L"DisplayName", &name) || name.empty()) { 375 LOG(INFO) << "No name for IE search engine at " << key_iterator.Name(); 376 ++key_iterator; 377 continue; 378 } 379 } 380 381 std::string url(WideToUTF8(wide_url)); 382 std::map<std::string, TemplateURL*>::iterator t_iter = 383 search_engines_map.find(url); 384 TemplateURL* template_url = 385 (t_iter != search_engines_map.end()) ? t_iter->second : NULL; 386 if (!template_url) { 387 // First time we see that URL. 388 template_url = new TemplateURL(); 389 template_url->set_short_name(name); 390 template_url->SetURL(url, 0, 0); 391 // Give this a keyword to facilitate tab-to-search, if possible. 392 template_url->set_keyword(TemplateURLModel::GenerateKeyword(GURL(url), 393 false)); 394 template_url->set_show_in_default_list(true); 395 search_engines_map[url] = template_url; 396 } 397 if (template_url && key_iterator.Name() == default_search_engine_name) { 398 DCHECK(!default_search_engine); 399 default_search_engine = template_url; 400 } 401 ++key_iterator; 402 } 403 404 // ProfileWriter::AddKeywords() requires a vector and we have a map. 405 std::map<std::string, TemplateURL*>::iterator t_iter; 406 std::vector<TemplateURL*> search_engines; 407 int default_search_engine_index = -1; 408 for (t_iter = search_engines_map.begin(); t_iter != search_engines_map.end(); 409 ++t_iter) { 410 search_engines.push_back(t_iter->second); 411 if (default_search_engine == t_iter->second) { 412 default_search_engine_index = 413 static_cast<int>(search_engines.size()) - 1; 414 } 415 } 416 bridge_->SetKeywords(search_engines, default_search_engine_index, true); 417} 418 419void IEImporter::ImportHomepage() { 420 const wchar_t kIESettingsMain[] = 421 L"Software\\Microsoft\\Internet Explorer\\Main"; 422 const wchar_t kIEHomepage[] = L"Start Page"; 423 const wchar_t kIEDefaultHomepage[] = L"Default_Page_URL"; 424 425 RegKey key(HKEY_CURRENT_USER, kIESettingsMain, KEY_READ); 426 std::wstring homepage_url; 427 if (!key.ReadValue(kIEHomepage, &homepage_url) || homepage_url.empty()) 428 return; 429 430 GURL homepage = GURL(homepage_url); 431 if (!homepage.is_valid()) 432 return; 433 434 // Check to see if this is the default website and skip import. 435 RegKey keyDefault(HKEY_LOCAL_MACHINE, kIESettingsMain, KEY_READ); 436 std::wstring default_homepage_url; 437 if (keyDefault.ReadValue(kIEDefaultHomepage, &default_homepage_url) && 438 !default_homepage_url.empty()) { 439 if (homepage.spec() == GURL(default_homepage_url).spec()) 440 return; 441 } 442 443 bridge_->AddHomePage(homepage); 444} 445 446bool IEImporter::GetFavoritesInfo(IEImporter::FavoritesInfo *info) { 447 if (!source_path_.empty()) { 448 // Source path exists during testing. 449 info->path = source_path_; 450 file_util::AppendToPath(&info->path, L"Favorites"); 451 info->links_folder = L"Links"; 452 return true; 453 } 454 455 // IE stores the favorites in the Favorites under user profile's folder. 456 wchar_t buffer[MAX_PATH]; 457 if (FAILED(SHGetFolderPath(NULL, CSIDL_FAVORITES, NULL, 458 SHGFP_TYPE_CURRENT, buffer))) 459 return false; 460 info->path = buffer; 461 462 // There is a Links folder under Favorites folder in Windows Vista, but it 463 // is not recording in Vista's registry. So in Vista, we assume the Links 464 // folder is under Favorites folder since it looks like there is not name 465 // different in every language version of Windows Vista. 466 if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) { 467 // The Link folder name is stored in the registry. 468 DWORD buffer_length = sizeof(buffer); 469 RegKey reg_key(HKEY_CURRENT_USER, 470 L"Software\\Microsoft\\Internet Explorer\\Toolbar", 471 KEY_READ); 472 if (!reg_key.ReadValue(L"LinksFolderName", buffer, &buffer_length, NULL)) 473 return false; 474 info->links_folder = buffer; 475 } else { 476 info->links_folder = L"Links"; 477 } 478 479 return true; 480} 481 482void IEImporter::ParseFavoritesFolder(const FavoritesInfo& info, 483 BookmarkVector* bookmarks) { 484 std::wstring ie_folder = l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_IE); 485 BookmarkVector toolbar_bookmarks; 486 FilePath file; 487 std::vector<FilePath::StringType> file_list; 488 FilePath favorites_path(info.path); 489 // Favorites path length. Make sure it doesn't include the trailing \. 490 size_t favorites_path_len = 491 favorites_path.StripTrailingSeparators().value().size(); 492 file_util::FileEnumerator file_enumerator( 493 favorites_path, true, file_util::FileEnumerator::FILES); 494 while (!(file = file_enumerator.Next()).value().empty() && !cancelled()) 495 file_list.push_back(file.value()); 496 497 // Keep the bookmarks in alphabetical order. 498 std::sort(file_list.begin(), file_list.end()); 499 500 for (std::vector<FilePath::StringType>::iterator it = file_list.begin(); 501 it != file_list.end(); ++it) { 502 FilePath shortcut(*it); 503 if (!LowerCaseEqualsASCII(shortcut.Extension(), ".url")) 504 continue; 505 506 // Skip the bookmark with invalid URL. 507 GURL url = GURL(ResolveInternetShortcut(*it)); 508 if (!url.is_valid()) 509 continue; 510 511 // Make the relative path from the Favorites folder, without the basename. 512 // ex. Suppose that the Favorites folder is C:\Users\Foo\Favorites. 513 // C:\Users\Foo\Favorites\Foo.url -> "" 514 // C:\Users\Foo\Favorites\Links\Bar\Baz.url -> "Links\Bar" 515 FilePath::StringType relative_string = 516 shortcut.DirName().value().substr(favorites_path_len); 517 if (relative_string.size() > 0 && FilePath::IsSeparator(relative_string[0])) 518 relative_string = relative_string.substr(1); 519 FilePath relative_path(relative_string); 520 521 ProfileWriter::BookmarkEntry entry; 522 // Remove the dot, the file extension, and the directory path. 523 entry.title = shortcut.RemoveExtension().BaseName().value(); 524 entry.url = url; 525 entry.creation_time = GetFileCreationTime(*it); 526 if (!relative_path.empty()) 527 relative_path.GetComponents(&entry.path); 528 529 // Flatten the bookmarks in Link folder onto bookmark toolbar. Otherwise, 530 // put it into "Other bookmarks". 531 if (import_to_bookmark_bar() && 532 (entry.path.size() > 0 && entry.path[0] == info.links_folder)) { 533 entry.in_toolbar = true; 534 entry.path.erase(entry.path.begin()); 535 toolbar_bookmarks.push_back(entry); 536 } else { 537 // We put the bookmarks in a "Imported From IE" 538 // folder, so that we don't mess up the "Other bookmarks". 539 if (!import_to_bookmark_bar()) 540 entry.path.insert(entry.path.begin(), ie_folder); 541 bookmarks->push_back(entry); 542 } 543 } 544 bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(), 545 toolbar_bookmarks.end()); 546} 547 548std::wstring IEImporter::ResolveInternetShortcut(const std::wstring& file) { 549 win_util::CoMemReleaser<wchar_t> url; 550 ScopedComPtr<IUniformResourceLocator> url_locator; 551 HRESULT result = url_locator.CreateInstance(CLSID_InternetShortcut, NULL, 552 CLSCTX_INPROC_SERVER); 553 if (FAILED(result)) 554 return std::wstring(); 555 556 ScopedComPtr<IPersistFile> persist_file; 557 result = persist_file.QueryFrom(url_locator); 558 if (FAILED(result)) 559 return std::wstring(); 560 561 // Loads the Internet Shortcut from persistent storage. 562 result = persist_file->Load(file.c_str(), STGM_READ); 563 if (FAILED(result)) 564 return std::wstring(); 565 566 result = url_locator->GetURL(&url); 567 // GetURL can return S_FALSE (FAILED(S_FALSE) is false) when url == NULL. 568 if (FAILED(result) || (url == NULL)) 569 return std::wstring(); 570 571 return std::wstring(url); 572} 573 574int IEImporter::CurrentIEVersion() const { 575 static int version = -1; 576 if (version < 0) { 577 wchar_t buffer[128]; 578 DWORD buffer_length = sizeof(buffer); 579 RegKey reg_key(HKEY_LOCAL_MACHINE, 580 L"Software\\Microsoft\\Internet Explorer", KEY_READ); 581 bool result = reg_key.ReadValue(L"Version", buffer, &buffer_length, NULL); 582 version = (result ? _wtoi(buffer) : 0); 583 } 584 return version; 585} 586