1 // Copyright (c) 2013 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.
5 // The order of these includes is important.
9 #include <propvarutil.h>
17 #include "base/bind.h"
18 #include "base/compiler_specific.h"
19 #include "base/files/file_util.h"
20 #include "base/files/scoped_temp_dir.h"
21 #include "base/message_loop/message_loop.h"
22 #include "base/stl_util.h"
23 #include "base/strings/string16.h"
24 #include "base/strings/string_util.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "base/win/registry.h"
27 #include "base/win/scoped_comptr.h"
28 #include "base/win/scoped_propvariant.h"
29 #include "base/win/windows_version.h"
30 #include "chrome/browser/importer/external_process_importer_host.h"
31 #include "chrome/browser/importer/importer_progress_observer.h"
32 #include "chrome/browser/importer/importer_unittest_utils.h"
33 #include "chrome/browser/ui/browser.h"
34 #include "chrome/common/chrome_paths.h"
35 #include "chrome/common/importer/ie_importer_test_registry_overrider_win.h"
36 #include "chrome/common/importer/ie_importer_utils_win.h"
37 #include "chrome/common/importer/imported_bookmark_entry.h"
38 #include "chrome/common/importer/imported_favicon_usage.h"
39 #include "chrome/common/importer/importer_bridge.h"
40 #include "chrome/common/importer/importer_data_types.h"
41 #include "chrome/test/base/in_process_browser_test.h"
42 #include "chrome/test/base/testing_profile.h"
43 #include "components/autofill/core/common/password_form.h"
44 #include "components/os_crypt/ie7_password_win.h"
45 #include "components/search_engines/template_url.h"
46 #include "testing/gtest/include/gtest/gtest.h"
50 const BookmarkInfo kIEBookmarks[] = {
51 {true, 2, {"Links", "SubFolderOfLinks"},
53 "http://www.links-sublink.com/"},
56 "http://www.links-thelink.com/"},
59 "http://www.google.com/"},
62 "http://www.links-thelink.com/"},
63 {false, 1, {"SubFolder"},
65 "http://www.link.com/"},
68 "http://host:8080/cgi?q=query"},
71 "http://chinese-title-favorite/"},
74 "http://www.subfolder.com/"},
77 const BookmarkInfo kIESortedBookmarks[] = {
78 {false, 0, {}, L"a", "http://www.google.com/0"},
79 {false, 1, {"b"}, L"a", "http://www.google.com/1"},
80 {false, 1, {"b"}, L"b", "http://www.google.com/2"},
81 {false, 0, {}, L"c", "http://www.google.com/3"},
84 const base::char16 kIEIdentifyUrl[] =
85 L"http://A79029D6-753E-4e27-B807-3D46AB1545DF.com:8080/path?key=value";
86 const base::char16 kIEIdentifyTitle[] =
89 const base::char16 kFaviconStreamSuffix[] = L"url:favicon:$DATA";
90 const char kDummyFaviconImageData[] =
91 "\x42\x4D" // Magic signature 'BM'
92 "\x1E\x00\x00\x00" // File size
93 "\x00\x00\x00\x00" // Reserved
94 "\x1A\x00\x00\x00" // Offset of the pixel data
95 "\x0C\x00\x00\x00" // Header Size
96 "\x01\x00\x01\x00" // Size: 1x1
97 "\x01\x00" // Reserved
99 "\x00\xFF\x00\x00"; // The pixel
101 struct FaviconGroup {
102 const base::char16* favicon_url;
103 const base::char16* site_url[2];
106 const FaviconGroup kIEFaviconGroup[2] = {
107 {L"http://www.google.com/favicon.ico",
108 {L"http://www.google.com/",
109 L"http://www.subfolder.com/"}},
110 {L"http://example.com/favicon.ico",
111 {L"http://host:8080/cgi?q=query",
112 L"http://chinese-title-favorite/"}},
115 bool CreateOrderBlob(const base::FilePath& favorites_folder,
116 const base::string16& path,
117 const std::vector<base::string16>& entries) {
118 if (entries.size() > 255)
121 // Create a binary sequence for setting a specific order of favorites.
122 // The format depends on the version of Shell32.dll, so we cannot embed
123 // a binary constant here.
124 std::vector<uint8> blob(20, 0);
125 blob[16] = static_cast<uint8>(entries.size());
127 for (size_t i = 0; i < entries.size(); ++i) {
128 PIDLIST_ABSOLUTE id_list_full = ILCreateFromPath(
129 favorites_folder.Append(path).Append(entries[i]).value().c_str());
130 PUITEMID_CHILD id_list = ILFindLastID(id_list_full);
131 // Include the trailing zero-length item id. Don't include the single
133 size_t id_list_size = id_list->mkid.cb + sizeof(id_list->mkid.cb);
135 blob.resize(blob.size() + 8);
136 uint32 total_size = id_list_size + 8;
137 memcpy(&blob[blob.size() - 8], &total_size, 4);
138 uint32 sort_index = i;
139 memcpy(&blob[blob.size() - 4], &sort_index, 4);
140 blob.resize(blob.size() + id_list_size);
141 memcpy(&blob[blob.size() - id_list_size], id_list, id_list_size);
142 ILFree(id_list_full);
145 base::string16 key_path(importer::GetIEFavoritesOrderKey());
147 key_path += L"\\" + path;
148 base::win::RegKey key;
149 if (key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_WRITE) !=
153 if (key.WriteValue(L"Order", &blob[0], blob.size(), REG_BINARY) !=
160 bool CreateUrlFileWithFavicon(const base::FilePath& file,
161 const base::string16& url,
162 const base::string16& favicon_url) {
163 base::win::ScopedComPtr<IUniformResourceLocator> locator;
164 HRESULT result = locator.CreateInstance(CLSID_InternetShortcut, NULL,
165 CLSCTX_INPROC_SERVER);
168 base::win::ScopedComPtr<IPersistFile> persist_file;
169 result = persist_file.QueryFrom(locator);
172 result = locator->SetURL(url.c_str(), 0);
176 // Write favicon url if specified.
177 if (!favicon_url.empty()) {
178 base::win::ScopedComPtr<IPropertySetStorage> property_set_storage;
179 if (FAILED(property_set_storage.QueryFrom(locator)))
181 base::win::ScopedComPtr<IPropertyStorage> property_storage;
182 if (FAILED(property_set_storage->Open(FMTID_Intshcut,
184 property_storage.Receive()))) {
187 PROPSPEC properties[] = {{PRSPEC_PROPID, PID_IS_ICONFILE}};
188 // WriteMultiple takes an array of PROPVARIANTs, but since this code only
189 // needs an array of size 1: a pointer to |pv_icon| is equivalent.
190 base::win::ScopedPropVariant pv_icon;
191 if (FAILED(InitPropVariantFromString(favicon_url.c_str(),
192 pv_icon.Receive())) ||
193 FAILED(property_storage->WriteMultiple(1, properties, &pv_icon, 0))) {
198 // Save the .url file.
199 result = persist_file->Save(file.value().c_str(), TRUE);
203 // Write dummy favicon image data in NTFS alternate data stream.
204 return favicon_url.empty() || (base::WriteFile(
205 file.ReplaceExtension(kFaviconStreamSuffix), kDummyFaviconImageData,
206 sizeof kDummyFaviconImageData) != -1);
209 bool CreateUrlFile(const base::FilePath& file, const base::string16& url) {
210 return CreateUrlFileWithFavicon(file, url, base::string16());
213 class TestObserver : public ProfileWriter,
214 public importer::ImporterProgressObserver {
221 explicit TestObserver(uint16 importer_items, TestIEVersion ie_version)
222 : ProfileWriter(NULL),
228 ie7_password_count_(0),
229 importer_items_(importer_items),
230 ie_version_(ie_version) {
233 // importer::ImporterProgressObserver:
234 virtual void ImportStarted() override {}
235 virtual void ImportItemStarted(importer::ImportItem item) override {}
236 virtual void ImportItemEnded(importer::ImportItem item) override {}
237 virtual void ImportEnded() override {
238 base::MessageLoop::current()->Quit();
239 if (importer_items_ & importer::FAVORITES) {
240 EXPECT_EQ(arraysize(kIEBookmarks), bookmark_count_);
241 EXPECT_EQ(arraysize(kIEFaviconGroup), favicon_count_);
243 if (importer_items_ & importer::HISTORY)
244 EXPECT_EQ(1, history_count_);
245 if (importer_items_ & importer::HOME_PAGE)
246 EXPECT_EQ(1, homepage_count_);
247 if ((importer_items_ & importer::PASSWORDS) && (ie_version_ == IE7))
248 EXPECT_EQ(1, ie7_password_count_);
249 // We need to test the IE6 password importer code.
250 // https://crbug.com/257100
251 // EXPECT_EQ(1, password_count_);
254 virtual bool BookmarkModelIsLoaded() const {
255 // Profile is ready for writing.
259 virtual bool TemplateURLServiceIsLoaded() const {
263 virtual void AddPasswordForm(const autofill::PasswordForm& form) {
264 // Importer should obtain this password form only.
265 EXPECT_EQ(GURL("http://localhost:8080/security/index.htm"), form.origin);
266 EXPECT_EQ("http://localhost:8080/", form.signon_realm);
267 EXPECT_EQ(L"user", form.username_element);
268 EXPECT_EQ(L"1", form.username_value);
269 EXPECT_EQ(L"", form.password_element);
270 EXPECT_EQ(L"2", form.password_value);
271 EXPECT_EQ("", form.action.spec());
275 virtual void AddHistoryPage(const history::URLRows& page,
276 history::VisitSource visit_source) {
277 // Importer should read the specified URL.
278 for (size_t i = 0; i < page.size(); ++i) {
279 if (page[i].title() == kIEIdentifyTitle &&
280 page[i].url() == GURL(kIEIdentifyUrl))
283 EXPECT_EQ(history::SOURCE_IE_IMPORTED, visit_source);
286 virtual void AddBookmarks(
287 const std::vector<ImportedBookmarkEntry>& bookmarks,
288 const base::string16& top_level_folder_name) override {
289 ASSERT_LE(bookmark_count_ + bookmarks.size(), arraysize(kIEBookmarks));
290 // Importer should import the IE Favorites folder the same as the list,
291 // in the same order.
292 for (size_t i = 0; i < bookmarks.size(); ++i) {
293 EXPECT_NO_FATAL_FAILURE(
294 TestEqualBookmarkEntry(bookmarks[i],
295 kIEBookmarks[bookmark_count_])) << i;
300 virtual void AddKeyword(std::vector<TemplateURL*> template_url,
301 int default_keyword_index) {
302 // TODO(jcampan): bug 1169230: we should test keyword importing for IE.
303 // In order to do that we'll probably need to mock the Windows registry.
305 STLDeleteContainerPointers(template_url.begin(), template_url.end());
308 virtual void AddFavicons(
309 const std::vector<ImportedFaviconUsage>& usage) override {
310 // Importer should group the favicon information for each favicon URL.
311 for (size_t i = 0; i < arraysize(kIEFaviconGroup); ++i) {
312 GURL favicon_url(kIEFaviconGroup[i].favicon_url);
314 for (size_t j = 0; j < arraysize(kIEFaviconGroup[i].site_url); ++j)
315 urls.insert(GURL(kIEFaviconGroup[i].site_url[j]));
317 SCOPED_TRACE(testing::Message() << "Expected Favicon: " << favicon_url);
319 bool expected_favicon_url_found = false;
320 for (size_t j = 0; j < usage.size(); ++j) {
321 if (usage[j].favicon_url == favicon_url) {
322 EXPECT_EQ(urls, usage[j].urls);
323 expected_favicon_url_found = true;
327 EXPECT_TRUE(expected_favicon_url_found);
330 favicon_count_ += usage.size();
333 virtual void AddIE7PasswordInfo(const IE7PasswordInfo& info) {
334 // This function also gets called for the IEImporter test. Ignore.
335 if (ie_version_ == IE7) {
336 EXPECT_EQ(L"Test1", info.url_hash);
337 EXPECT_EQ(1, info.encrypted_data[0]);
338 EXPECT_EQ(4, info.encrypted_data.size());
339 ++ie7_password_count_;
343 virtual void AddHomepage(const GURL& homepage) {
344 EXPECT_EQ(homepage.spec(), "http://www.test.com/");
351 size_t bookmark_count_;
352 size_t history_count_;
353 size_t password_count_;
354 size_t favicon_count_;
355 size_t homepage_count_;
356 size_t ie7_password_count_;
357 uint16 importer_items_;
358 TestIEVersion ie_version_;
361 class MalformedFavoritesRegistryTestObserver
362 : public ProfileWriter,
363 public importer::ImporterProgressObserver {
365 MalformedFavoritesRegistryTestObserver() : ProfileWriter(NULL) {
369 // importer::ImporterProgressObserver:
370 virtual void ImportStarted() override {}
371 virtual void ImportItemStarted(importer::ImportItem item) override {}
372 virtual void ImportItemEnded(importer::ImportItem item) override {}
373 virtual void ImportEnded() override {
374 base::MessageLoop::current()->Quit();
375 EXPECT_EQ(arraysize(kIESortedBookmarks), bookmark_count_);
378 virtual bool BookmarkModelIsLoaded() const { return true; }
379 virtual bool TemplateURLServiceIsLoaded() const { return true; }
381 virtual void AddPasswordForm(const autofill::PasswordForm& form) {}
382 virtual void AddHistoryPage(const history::URLRows& page,
383 history::VisitSource visit_source) {}
384 virtual void AddKeyword(std::vector<TemplateURL*> template_url,
385 int default_keyword_index) {}
386 virtual void AddBookmarks(
387 const std::vector<ImportedBookmarkEntry>& bookmarks,
388 const base::string16& top_level_folder_name) override {
389 ASSERT_LE(bookmark_count_ + bookmarks.size(),
390 arraysize(kIESortedBookmarks));
391 for (size_t i = 0; i < bookmarks.size(); ++i) {
392 EXPECT_NO_FATAL_FAILURE(
393 TestEqualBookmarkEntry(bookmarks[i],
394 kIESortedBookmarks[bookmark_count_])) << i;
400 ~MalformedFavoritesRegistryTestObserver() {}
402 size_t bookmark_count_;
407 // These tests need to be browser tests in order to be able to run the OOP
408 // import (via ExternalProcessImporterHost) which launches a utility process.
409 class IEImporterBrowserTest : public InProcessBrowserTest {
411 virtual void SetUp() override {
412 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
414 // This will launch the browser test and thus needs to happen last.
415 InProcessBrowserTest::SetUp();
418 base::ScopedTempDir temp_dir_;
420 // Overrides the default registry key for IE registry keys like favorites,
421 // settings, password store, etc.
422 IEImporterTestRegistryOverrider test_registry_overrider_;
425 IN_PROC_BROWSER_TEST_F(IEImporterBrowserTest, IEImporter) {
426 // Sets up a favorites folder.
427 base::FilePath path = temp_dir_.path().AppendASCII("Favorites");
428 CreateDirectory(path.value().c_str(), NULL);
429 CreateDirectory(path.AppendASCII("SubFolder").value().c_str(), NULL);
430 base::FilePath links_path = path.AppendASCII("Links");
431 CreateDirectory(links_path.value().c_str(), NULL);
432 CreateDirectory(links_path.AppendASCII("SubFolderOfLinks").value().c_str(),
434 CreateDirectory(path.AppendASCII("\x0061").value().c_str(), NULL);
435 ASSERT_TRUE(CreateUrlFileWithFavicon(path.AppendASCII("Google Home Page.url"),
436 L"http://www.google.com/",
437 L"http://www.google.com/favicon.ico"));
438 ASSERT_TRUE(CreateUrlFile(path.AppendASCII("SubFolder\\Title.url"),
439 L"http://www.link.com/"));
440 ASSERT_TRUE(CreateUrlFileWithFavicon(path.AppendASCII("SubFolder.url"),
441 L"http://www.subfolder.com/",
442 L"http://www.google.com/favicon.ico"));
443 ASSERT_TRUE(CreateUrlFile(path.AppendASCII("TheLink.url"),
444 L"http://www.links-thelink.com/"));
445 ASSERT_TRUE(CreateUrlFileWithFavicon(path.AppendASCII("WithPortAndQuery.url"),
446 L"http://host:8080/cgi?q=query",
447 L"http://example.com/favicon.ico"));
448 ASSERT_TRUE(CreateUrlFileWithFavicon(
449 path.AppendASCII("\x0061").Append(L"\x4E2D\x6587.url"),
450 L"http://chinese-title-favorite/",
451 L"http://example.com/favicon.ico"));
452 ASSERT_TRUE(CreateUrlFile(links_path.AppendASCII("TheLink.url"),
453 L"http://www.links-thelink.com/"));
454 ASSERT_TRUE(CreateUrlFile(
455 links_path.AppendASCII("SubFolderOfLinks").AppendASCII("SubLink.url"),
456 L"http://www.links-sublink.com/"));
457 ASSERT_TRUE(CreateUrlFile(path.AppendASCII("IEDefaultLink.url"),
458 L"http://go.microsoft.com/fwlink/?linkid=140813"));
459 base::WriteFile(path.AppendASCII("InvalidUrlFile.url"), "x", 1);
460 base::WriteFile(path.AppendASCII("PlainTextFile.txt"), "x", 1);
462 const base::char16* root_links[] = {
464 L"Google Home Page.url",
467 L"WithPortAndQuery.url",
471 ASSERT_TRUE(CreateOrderBlob(
472 base::FilePath(path), L"",
473 std::vector<base::string16>(root_links,
474 root_links + arraysize(root_links))));
478 // Sets up a special history link.
479 base::win::ScopedComPtr<IUrlHistoryStg2> url_history_stg2;
480 res = url_history_stg2.CreateInstance(CLSID_CUrlHistory, NULL,
481 CLSCTX_INPROC_SERVER);
482 ASSERT_TRUE(res == S_OK);
483 res = url_history_stg2->AddUrl(kIEIdentifyUrl, kIEIdentifyTitle, 0);
484 ASSERT_TRUE(res == S_OK);
486 // Starts to import the above settings.
488 ExternalProcessImporterHost* host = new ExternalProcessImporterHost;
489 TestObserver* observer = new TestObserver(
490 importer::HISTORY | importer::PASSWORDS | importer::FAVORITES,
492 host->set_observer(observer);
494 importer::SourceProfile source_profile;
495 source_profile.importer_type = importer::TYPE_IE;
496 source_profile.source_path = temp_dir_.path();
498 host->StartImportSettings(
500 browser()->profile(),
501 importer::HISTORY | importer::PASSWORDS | importer::FAVORITES,
503 base::MessageLoop::current()->Run();
506 url_history_stg2->DeleteUrl(kIEIdentifyUrl, 0);
507 url_history_stg2.Release();
510 IN_PROC_BROWSER_TEST_F(IEImporterBrowserTest,
511 IEImporterMalformedFavoritesRegistry) {
512 // Sets up a favorites folder.
513 base::FilePath path = temp_dir_.path().AppendASCII("Favorites");
514 CreateDirectory(path.value().c_str(), NULL);
515 CreateDirectory(path.AppendASCII("b").value().c_str(), NULL);
516 ASSERT_TRUE(CreateUrlFile(path.AppendASCII("a.url"),
517 L"http://www.google.com/0"));
518 ASSERT_TRUE(CreateUrlFile(path.AppendASCII("b").AppendASCII("a.url"),
519 L"http://www.google.com/1"));
520 ASSERT_TRUE(CreateUrlFile(path.AppendASCII("b").AppendASCII("b.url"),
521 L"http://www.google.com/2"));
522 ASSERT_TRUE(CreateUrlFile(path.AppendASCII("c.url"),
523 L"http://www.google.com/3"));
525 struct BadBinaryData {
529 static const BadBinaryData kBadBinary[] = {
530 // number_of_items field is truncated
531 {"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
532 "\x00\xff\xff\xff", 17},
533 // number_of_items = 0xffff, but the byte sequence is too short.
534 {"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
535 "\xff\xff\x00\x00", 20},
536 // number_of_items = 1, size_of_item is too big.
537 {"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
539 "\xff\xff\x00\x00\x00\x00\x00\x00"
540 "\x00\x00\x00\x00", 32},
541 // number_of_items = 1, size_of_item = 16, size_of_shid is too big.
542 {"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
544 "\x10\x00\x00\x00\x00\x00\x00\x00"
545 "\xff\x7f\x00\x00" "\x00\x00\x00\x00", 36},
546 // number_of_items = 1, size_of_item = 16, size_of_shid is too big.
547 {"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
549 "\x10\x00\x00\x00\x00\x00\x00\x00"
550 "\x06\x00\x00\x00" "\x00\x00\x00\x00", 36},
553 // Verify malformed registry data are safely ignored and alphabetical
554 // sort is performed.
555 for (size_t i = 0; i < arraysize(kBadBinary); ++i) {
556 base::string16 key_path(importer::GetIEFavoritesOrderKey());
557 base::win::RegKey key;
558 ASSERT_EQ(ERROR_SUCCESS,
559 key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_WRITE));
560 ASSERT_EQ(ERROR_SUCCESS,
561 key.WriteValue(L"Order", kBadBinary[i].data, kBadBinary[i].length,
564 // Starts to import the above settings.
566 ExternalProcessImporterHost* host = new ExternalProcessImporterHost;
567 MalformedFavoritesRegistryTestObserver* observer =
568 new MalformedFavoritesRegistryTestObserver();
569 host->set_observer(observer);
571 importer::SourceProfile source_profile;
572 source_profile.importer_type = importer::TYPE_IE;
573 source_profile.source_path = temp_dir_.path();
575 host->StartImportSettings(
577 browser()->profile(),
580 base::MessageLoop::current()->Run();
584 IN_PROC_BROWSER_TEST_F(IEImporterBrowserTest, IE7ImporterPasswordsTest) {
585 // Starts to import the IE7 passwords.
587 ExternalProcessImporterHost* host = new ExternalProcessImporterHost;
588 TestObserver* observer = new TestObserver(importer::PASSWORDS,
590 host->set_observer(observer);
592 base::string16 key_path(importer::GetIE7PasswordsKey());
593 base::win::RegKey key;
594 ASSERT_EQ(ERROR_SUCCESS,
595 key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_WRITE));
596 key.WriteValue(L"Test1", 1);
598 importer::SourceProfile source_profile;
599 source_profile.importer_type = importer::TYPE_IE;
600 source_profile.source_path = temp_dir_.path();
602 host->StartImportSettings(
604 browser()->profile(),
607 base::MessageLoop::current()->Run();
610 IN_PROC_BROWSER_TEST_F(IEImporterBrowserTest, IEImporterHomePageTest) {
611 // Starts to import the IE home page.
613 ExternalProcessImporterHost* host = new ExternalProcessImporterHost;
614 TestObserver* observer = new TestObserver(importer::HOME_PAGE,
616 host->set_observer(observer);
618 base::string16 key_path(importer::GetIESettingsKey());
619 base::win::RegKey key;
620 ASSERT_EQ(ERROR_SUCCESS,
621 key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_WRITE));
622 key.WriteValue(L"Start Page", L"http://www.test.com/");
624 importer::SourceProfile source_profile;
625 source_profile.importer_type = importer::TYPE_IE;
626 source_profile.source_path = temp_dir_.path();
628 host->StartImportSettings(
630 browser()->profile(),
633 base::MessageLoop::current()->Run();