Home | History | Annotate | Download | only in importer
      1 // Copyright (c) 2011 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 "build/build_config.h"
      6 
      7 #if defined(OS_WIN)
      8 // The order of these includes is important.
      9 #include <windows.h>
     10 #include <unknwn.h>
     11 #include <intshcut.h>
     12 #include <pstore.h>
     13 #include <urlhist.h>
     14 #include <shlguid.h>
     15 #endif
     16 
     17 #include <vector>
     18 
     19 #include "app/win/scoped_com_initializer.h"
     20 #include "base/compiler_specific.h"
     21 #include "base/file_util.h"
     22 #include "base/message_loop.h"
     23 #include "base/path_service.h"
     24 #include "base/stl_util-inl.h"
     25 #include "base/string_util.h"
     26 #include "base/utf_string_conversions.h"
     27 #include "base/memory/scoped_temp_dir.h"
     28 #include "chrome/browser/history/history_types.h"
     29 #include "chrome/browser/importer/importer_bridge.h"
     30 #include "chrome/browser/importer/importer_data_types.h"
     31 #include "chrome/browser/importer/importer_host.h"
     32 #include "chrome/browser/importer/importer_progress_observer.h"
     33 #include "chrome/browser/search_engines/template_url.h"
     34 #include "chrome/common/chrome_paths.h"
     35 #include "content/browser/browser_thread.h"
     36 #include "testing/gtest/include/gtest/gtest.h"
     37 #include "webkit/glue/password_form.h"
     38 
     39 #if defined(OS_WIN)
     40 #include "base/win/scoped_comptr.h"
     41 #include "base/win/windows_version.h"
     42 #include "chrome/browser/importer/ie_importer.h"
     43 #include "chrome/browser/password_manager/ie7_password.h"
     44 #endif
     45 
     46 // TODO(estade): some of these are disabled on mac. http://crbug.com/48007
     47 #if defined(OS_MACOSX)
     48 #define MAYBE(x) DISABLED_##x
     49 #else
     50 #define MAYBE(x) x
     51 #endif
     52 
     53 class ImporterTest : public testing::Test {
     54  public:
     55   ImporterTest()
     56       : ui_thread_(BrowserThread::UI, &message_loop_),
     57         file_thread_(BrowserThread::FILE, &message_loop_) {}
     58 
     59  protected:
     60   virtual void SetUp() {
     61     // Creates a new profile in a new subdirectory in the temp directory.
     62     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     63     FilePath test_path = temp_dir_.path().AppendASCII("ImporterTest");
     64     file_util::Delete(test_path, true);
     65     file_util::CreateDirectory(test_path);
     66     profile_path_ = test_path.AppendASCII("profile");
     67     app_path_ = test_path.AppendASCII("app");
     68     file_util::CreateDirectory(app_path_);
     69   }
     70 
     71   void Firefox3xImporterTest(std::string profile_dir,
     72                              importer::ImporterProgressObserver* observer,
     73                              ProfileWriter* writer,
     74                              bool import_search_plugins) {
     75     FilePath data_path;
     76     ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
     77     data_path = data_path.AppendASCII(profile_dir);
     78     ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, true));
     79     ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
     80     data_path = data_path.AppendASCII("firefox3_nss");
     81     ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, false));
     82 
     83     FilePath search_engine_path = app_path_;
     84     search_engine_path = search_engine_path.AppendASCII("searchplugins");
     85     file_util::CreateDirectory(search_engine_path);
     86     if (import_search_plugins) {
     87       ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
     88       data_path = data_path.AppendASCII("firefox3_searchplugins");
     89       if (!file_util::PathExists(data_path)) {
     90         // TODO(maruel):  Create search test data that we can open source!
     91         LOG(ERROR) << L"Missing internal test data";
     92         return;
     93       }
     94       ASSERT_TRUE(file_util::CopyDirectory(data_path,
     95                                            search_engine_path, false));
     96     }
     97 
     98     MessageLoop* loop = MessageLoop::current();
     99     importer::SourceProfile source_profile;
    100     source_profile.importer_type = importer::FIREFOX3;
    101     source_profile.app_path = app_path_;
    102     source_profile.source_path = profile_path_;
    103     scoped_refptr<ImporterHost> host(new ImporterHost);
    104     host->SetObserver(observer);
    105     int items = importer::HISTORY | importer::PASSWORDS | importer::FAVORITES;
    106     if (import_search_plugins)
    107       items = items | importer::SEARCH_ENGINES;
    108     loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(),
    109         &ImporterHost::StartImportSettings, source_profile,
    110         static_cast<Profile*>(NULL), items, make_scoped_refptr(writer), true));
    111     loop->Run();
    112   }
    113 
    114   ScopedTempDir temp_dir_;
    115   MessageLoopForUI message_loop_;
    116   BrowserThread ui_thread_;
    117   BrowserThread file_thread_;
    118   FilePath profile_path_;
    119   FilePath app_path_;
    120 };
    121 
    122 const int kMaxPathSize = 5;
    123 
    124 struct BookmarkList {
    125   const bool in_toolbar;
    126   const size_t path_size;
    127   const wchar_t* path[kMaxPathSize];
    128   const wchar_t* title;
    129   const char* url;
    130 };
    131 
    132 // Returns true if the |entry| is in the |list|.
    133 bool FindBookmarkEntry(const ProfileWriter::BookmarkEntry& entry,
    134                        const BookmarkList* list, int list_size) {
    135   for (int i = 0; i < list_size; ++i) {
    136     if (list[i].in_toolbar == entry.in_toolbar &&
    137         list[i].path_size == entry.path.size() &&
    138         list[i].url == entry.url.spec() &&
    139         WideToUTF16Hack(list[i].title) == entry.title) {
    140       bool equal = true;
    141       for (size_t k = 0; k < list[i].path_size; ++k)
    142         if (WideToUTF16Hack(list[i].path[k]) != entry.path[k]) {
    143           equal = false;
    144           break;
    145         }
    146 
    147       if (equal)
    148         return true;
    149     }
    150   }
    151   return false;
    152 }
    153 
    154 #if defined(OS_WIN)
    155 static const BookmarkList kIEBookmarks[] = {
    156   {true, 0, {},
    157    L"TheLink",
    158    "http://www.links-thelink.com/"},
    159   {true, 1, {L"SubFolderOfLinks"},
    160    L"SubLink",
    161    "http://www.links-sublink.com/"},
    162   {false, 0, {},
    163    L"Google Home Page",
    164    "http://www.google.com/"},
    165   {false, 0, {},
    166    L"TheLink",
    167    "http://www.links-thelink.com/"},
    168   {false, 1, {L"SubFolder"},
    169    L"Title",
    170    "http://www.link.com/"},
    171   {false, 0, {},
    172    L"WithPortAndQuery",
    173    "http://host:8080/cgi?q=query"},
    174   {false, 1, {L"a"},
    175    L"\x4E2D\x6587",
    176    "http://chinese-title-favorite/"},
    177 };
    178 
    179 static const wchar_t* kIEIdentifyUrl =
    180     L"http://A79029D6-753E-4e27-B807-3D46AB1545DF.com:8080/path?key=value";
    181 static const wchar_t* kIEIdentifyTitle =
    182     L"Unittest GUID";
    183 
    184 class TestObserver : public ProfileWriter,
    185                      public importer::ImporterProgressObserver {
    186  public:
    187   TestObserver() : ProfileWriter(NULL) {
    188     bookmark_count_ = 0;
    189     history_count_ = 0;
    190     password_count_ = 0;
    191   }
    192 
    193   // importer::ImporterProgressObserver:
    194   virtual void ImportStarted() OVERRIDE {}
    195   virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE {}
    196   virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE {}
    197   virtual void ImportEnded() OVERRIDE {
    198     MessageLoop::current()->Quit();
    199     EXPECT_EQ(arraysize(kIEBookmarks), bookmark_count_);
    200     EXPECT_EQ(1, history_count_);
    201 #if 0  // This part of the test is disabled. See bug #2466
    202     if (base::win::GetVersion() >= base::win::VERSION_VISTA)
    203       EXPECT_EQ(0, password_count_);
    204     else
    205       EXPECT_EQ(1, password_count_);
    206 #endif
    207   }
    208 
    209   virtual bool BookmarkModelIsLoaded() const {
    210     // Profile is ready for writing.
    211     return true;
    212   }
    213 
    214   virtual bool TemplateURLModelIsLoaded() const {
    215     return true;
    216   }
    217 
    218   virtual void AddPasswordForm(const webkit_glue::PasswordForm& form) {
    219     // Importer should obtain this password form only.
    220     EXPECT_EQ(GURL("http://localhost:8080/security/index.htm"), form.origin);
    221     EXPECT_EQ("http://localhost:8080/", form.signon_realm);
    222     EXPECT_EQ(L"user", form.username_element);
    223     EXPECT_EQ(L"1", form.username_value);
    224     EXPECT_EQ(L"", form.password_element);
    225     EXPECT_EQ(L"2", form.password_value);
    226     EXPECT_EQ("", form.action.spec());
    227     ++password_count_;
    228   }
    229 
    230   virtual void AddHistoryPage(const std::vector<history::URLRow>& page,
    231                               history::VisitSource visit_source) {
    232     // Importer should read the specified URL.
    233     for (size_t i = 0; i < page.size(); ++i) {
    234       if (page[i].title() == kIEIdentifyTitle &&
    235           page[i].url() == GURL(kIEIdentifyUrl))
    236         ++history_count_;
    237     }
    238     EXPECT_EQ(history::SOURCE_IE_IMPORTED, visit_source);
    239   }
    240 
    241   virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark,
    242                                 const string16& first_folder_name,
    243                                 int options) {
    244     // Importer should import the IE Favorites folder the same as the list.
    245     for (size_t i = 0; i < bookmark.size(); ++i) {
    246       if (FindBookmarkEntry(bookmark[i], kIEBookmarks,
    247                             arraysize(kIEBookmarks)))
    248         ++bookmark_count_;
    249     }
    250   }
    251 
    252   virtual void AddKeyword(std::vector<TemplateURL*> template_url,
    253                           int default_keyword_index) {
    254     // TODO(jcampan): bug 1169230: we should test keyword importing for IE.
    255     // In order to do that we'll probably need to mock the Windows registry.
    256     NOTREACHED();
    257     STLDeleteContainerPointers(template_url.begin(), template_url.end());
    258   }
    259 
    260  private:
    261   ~TestObserver() {}
    262 
    263   size_t bookmark_count_;
    264   size_t history_count_;
    265   size_t password_count_;
    266 };
    267 
    268 bool CreateUrlFile(std::wstring file, std::wstring url) {
    269   base::win::ScopedComPtr<IUniformResourceLocator> locator;
    270   HRESULT result = locator.CreateInstance(CLSID_InternetShortcut, NULL,
    271                                           CLSCTX_INPROC_SERVER);
    272   if (FAILED(result))
    273     return false;
    274   base::win::ScopedComPtr<IPersistFile> persist_file;
    275   result = persist_file.QueryFrom(locator);
    276   if (FAILED(result))
    277     return false;
    278   result = locator->SetURL(url.c_str(), 0);
    279   if (FAILED(result))
    280     return false;
    281   result = persist_file->Save(file.c_str(), TRUE);
    282   if (FAILED(result))
    283     return false;
    284   return true;
    285 }
    286 
    287 void ClearPStoreType(IPStore* pstore, const GUID* type, const GUID* subtype) {
    288   base::win::ScopedComPtr<IEnumPStoreItems, NULL> item;
    289   HRESULT result = pstore->EnumItems(0, type, subtype, 0, item.Receive());
    290   if (result == PST_E_OK) {
    291     wchar_t* item_name;
    292     while (SUCCEEDED(item->Next(1, &item_name, 0))) {
    293       pstore->DeleteItem(0, type, subtype, item_name, NULL, 0);
    294       CoTaskMemFree(item_name);
    295     }
    296   }
    297   pstore->DeleteSubtype(0, type, subtype, 0);
    298   pstore->DeleteType(0, type, 0);
    299 }
    300 
    301 void WritePStore(IPStore* pstore, const GUID* type, const GUID* subtype) {
    302   struct PStoreItem {
    303     wchar_t* name;
    304     int data_size;
    305     char* data;
    306   } items[] = {
    307     {L"http://localhost:8080/security/index.htm#ref:StringData", 8,
    308      "\x31\x00\x00\x00\x32\x00\x00\x00"},
    309     {L"http://localhost:8080/security/index.htm#ref:StringIndex", 20,
    310      "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00"
    311      "\x00\x00\x2f\x00\x74\x00\x01\x00\x00\x00"},
    312     {L"user:StringData", 4,
    313      "\x31\x00\x00\x00"},
    314     {L"user:StringIndex", 20,
    315      "\x57\x49\x43\x4b\x18\x00\x00\x00\x01\x00"
    316      "\x00\x00\x2f\x00\x74\x00\x00\x00\x00\x00"},
    317   };
    318 
    319   for (int i = 0; i < arraysize(items); ++i) {
    320     HRESULT res = pstore->WriteItem(0, type, subtype, items[i].name,
    321         items[i].data_size, reinterpret_cast<BYTE*>(items[i].data),
    322         NULL, 0, 0);
    323     ASSERT_TRUE(res == PST_E_OK);
    324   }
    325 }
    326 
    327 TEST_F(ImporterTest, IEImporter) {
    328   // Sets up a favorites folder.
    329   app::win::ScopedCOMInitializer com_init;
    330   std::wstring path = temp_dir_.path().AppendASCII("Favorites").value();
    331   CreateDirectory(path.c_str(), NULL);
    332   CreateDirectory((path + L"\\SubFolder").c_str(), NULL);
    333   CreateDirectory((path + L"\\Links").c_str(), NULL);
    334   CreateDirectory((path + L"\\Links\\SubFolderOfLinks").c_str(), NULL);
    335   CreateDirectory((path + L"\\\x0061").c_str(), NULL);
    336   ASSERT_TRUE(CreateUrlFile(path + L"\\Google Home Page.url",
    337                             L"http://www.google.com/"));
    338   ASSERT_TRUE(CreateUrlFile(path + L"\\SubFolder\\Title.url",
    339                             L"http://www.link.com/"));
    340   ASSERT_TRUE(CreateUrlFile(path + L"\\TheLink.url",
    341                             L"http://www.links-thelink.com/"));
    342   ASSERT_TRUE(CreateUrlFile(path + L"\\WithPortAndQuery.url",
    343                             L"http://host:8080/cgi?q=query"));
    344   ASSERT_TRUE(CreateUrlFile(path + L"\\\x0061\\\x4E2D\x6587.url",
    345                             L"http://chinese-title-favorite/"));
    346   ASSERT_TRUE(CreateUrlFile(path + L"\\Links\\TheLink.url",
    347                             L"http://www.links-thelink.com/"));
    348   ASSERT_TRUE(CreateUrlFile(path + L"\\Links\\SubFolderOfLinks\\SubLink.url",
    349                             L"http://www.links-sublink.com/"));
    350   file_util::WriteFile(path + L"\\InvalidUrlFile.url", "x", 1);
    351   file_util::WriteFile(path + L"\\PlainTextFile.txt", "x", 1);
    352 
    353   // Sets up dummy password data.
    354   HRESULT res;
    355 #if 0  // This part of the test is disabled. See bug #2466
    356   base::win::ScopedComPtr<IPStore> pstore;
    357   HMODULE pstorec_dll;
    358   GUID type = IEImporter::kUnittestGUID;
    359   GUID subtype = IEImporter::kUnittestGUID;
    360   // PStore is read-only in Windows Vista.
    361   if (base::win::GetVersion() < base::win::VERSION_VISTA) {
    362     typedef HRESULT (WINAPI *PStoreCreateFunc)(IPStore**, DWORD, DWORD, DWORD);
    363     pstorec_dll = LoadLibrary(L"pstorec.dll");
    364     PStoreCreateFunc PStoreCreateInstance =
    365         (PStoreCreateFunc)GetProcAddress(pstorec_dll, "PStoreCreateInstance");
    366     res = PStoreCreateInstance(pstore.Receive(), 0, 0, 0);
    367     ASSERT_TRUE(res == S_OK);
    368     ClearPStoreType(pstore, &type, &subtype);
    369     PST_TYPEINFO type_info;
    370     type_info.szDisplayName = L"TestType";
    371     type_info.cbSize = 8;
    372     pstore->CreateType(0, &type, &type_info, 0);
    373     pstore->CreateSubtype(0, &type, &subtype, &type_info, NULL, 0);
    374     WritePStore(pstore, &type, &subtype);
    375   }
    376 #endif
    377 
    378   // Sets up a special history link.
    379   base::win::ScopedComPtr<IUrlHistoryStg2> url_history_stg2;
    380   res = url_history_stg2.CreateInstance(CLSID_CUrlHistory, NULL,
    381                                         CLSCTX_INPROC_SERVER);
    382   ASSERT_TRUE(res == S_OK);
    383   res = url_history_stg2->AddUrl(kIEIdentifyUrl, kIEIdentifyTitle, 0);
    384   ASSERT_TRUE(res == S_OK);
    385 
    386   // Starts to import the above settings.
    387   MessageLoop* loop = MessageLoop::current();
    388   scoped_refptr<ImporterHost> host(new ImporterHost);
    389 
    390   TestObserver* observer = new TestObserver();
    391   host->SetObserver(observer);
    392   importer::SourceProfile source_profile;
    393   source_profile.importer_type = importer::MS_IE;
    394   source_profile.source_path = temp_dir_.path();
    395 
    396   loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(),
    397       &ImporterHost::StartImportSettings,
    398       source_profile,
    399       static_cast<Profile*>(NULL),
    400       importer::HISTORY | importer::PASSWORDS | importer::FAVORITES,
    401       observer,
    402       true));
    403   loop->Run();
    404 
    405   // Cleans up.
    406   url_history_stg2->DeleteUrl(kIEIdentifyUrl, 0);
    407   url_history_stg2.Release();
    408 #if 0  // This part of the test is disabled. See bug #2466
    409   if (base::win::GetVersion() < base::win::VERSION_VISTA) {
    410     ClearPStoreType(pstore, &type, &subtype);
    411     // Releases it befor unload the dll.
    412     pstore.Release();
    413     FreeLibrary(pstorec_dll);
    414   }
    415 #endif
    416 }
    417 
    418 TEST_F(ImporterTest, IE7Importer) {
    419   // This is the unencrypted values of my keys under Storage2.
    420   // The passwords have been manually changed to abcdef... but the size remains
    421   // the same.
    422   unsigned char data1[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x2c\x00\x00\x00"
    423                           "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00"
    424                           "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00"
    425                           "\x00\x00\x00\x00\x4e\xfa\x67\x76\x22\x94\xc8\x01"
    426                           "\x08\x00\x00\x00\x12\x00\x00\x00\x4e\xfa\x67\x76"
    427                           "\x22\x94\xc8\x01\x0c\x00\x00\x00\x61\x00\x62\x00"
    428                           "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00"
    429                           "\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00"
    430                           "\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00"
    431                           "\x6c\x00\x00\x00";
    432 
    433   unsigned char data2[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x24\x00\x00\x00"
    434                           "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00"
    435                           "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00"
    436                           "\x00\x00\x00\x00\xa8\xea\xf4\xe5\x9f\x9a\xc8\x01"
    437                           "\x09\x00\x00\x00\x14\x00\x00\x00\xa8\xea\xf4\xe5"
    438                           "\x9f\x9a\xc8\x01\x07\x00\x00\x00\x61\x00\x62\x00"
    439                           "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00"
    440                           "\x69\x00\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00"
    441                           "\x65\x00\x66\x00\x67\x00\x00\x00";
    442 
    443 
    444 
    445   std::vector<unsigned char> decrypted_data1;
    446   decrypted_data1.resize(arraysize(data1));
    447   memcpy(&decrypted_data1.front(), data1, sizeof(data1));
    448 
    449   std::vector<unsigned char> decrypted_data2;
    450   decrypted_data2.resize(arraysize(data2));
    451   memcpy(&decrypted_data2.front(), data2, sizeof(data2));
    452 
    453   std::wstring password;
    454   std::wstring username;
    455   ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data1, &username,
    456                                                 &password));
    457   EXPECT_EQ(L"abcdefgh", username);
    458   EXPECT_EQ(L"abcdefghijkl", password);
    459 
    460   ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data2, &username,
    461                                                 &password));
    462   EXPECT_EQ(L"abcdefghi", username);
    463   EXPECT_EQ(L"abcdefg", password);
    464 }
    465 #endif  // defined(OS_WIN)
    466 
    467 static const BookmarkList kFirefox2Bookmarks[] = {
    468   {true, 1, {L"Folder"},
    469    L"On Toolbar's Subfolder",
    470    "http://on.toolbar/bookmark/folder"},
    471   {true, 0, {},
    472    L"On Bookmark Toolbar",
    473    "http://on.toolbar/bookmark"},
    474   {false, 1, {L"Folder"},
    475    L"New Bookmark",
    476    "http://domain/"},
    477   {false, 0, {},
    478    L"<Name>",
    479    "http://domain.com/q?a=%22er%22&b=%3C%20%20%3E"},
    480   {false, 0, {},
    481    L"Google Home Page",
    482    "http://www.google.com/"},
    483   {false, 0, {},
    484    L"\x4E2D\x6587",
    485    "http://chinese.site.cn/path?query=1#ref"},
    486   {false, 0, {},
    487    L"mail",
    488    "mailto:username@host"},
    489 };
    490 
    491 struct PasswordList {
    492   const char* origin;
    493   const char* action;
    494   const char* realm;
    495   const wchar_t* username_element;
    496   const wchar_t* username;
    497   const wchar_t* password_element;
    498   const wchar_t* password;
    499   bool blacklisted;
    500 };
    501 
    502 static const PasswordList kFirefox2Passwords[] = {
    503   {"https://www.google.com/", "", "https://www.google.com/",
    504     L"", L"", L"", L"", true},
    505   {"http://localhost:8080/", "", "http://localhost:8080/corp.google.com",
    506     L"", L"http", L"", L"Http1+1abcdefg", false},
    507   {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/",
    508     L"loginuser", L"usr", L"loginpass", L"pwd", false},
    509   {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/",
    510     L"loginuser", L"firefox", L"loginpass", L"firefox", false},
    511   {"http://localhost/", "", "http://localhost/",
    512     L"loginuser", L"hello", L"", L"world", false},
    513 };
    514 
    515 struct KeywordList {
    516   const wchar_t* keyword;
    517   const char* url;
    518 };
    519 
    520 static const KeywordList kFirefox2Keywords[] = {
    521   // Searh plugins
    522   { L"amazon.com",
    523     "http://www.amazon.com/exec/obidos/external-search/?field-keywords="
    524     "{searchTerms}&mode=blended" },
    525   { L"answers.com",
    526     "http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" },
    527   { L"search.creativecommons.org",
    528     "http://search.creativecommons.org/?q={searchTerms}" },
    529   { L"search.ebay.com",
    530     "http://search.ebay.com/search/search.dll?query={searchTerms}&"
    531     "MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&"
    532     "maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" },
    533   { L"google.com",
    534     "http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" },
    535   { L"search.yahoo.com",
    536     "http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" },
    537   { L"flickr.com",
    538     "http://www.flickr.com/photos/tags/?q={searchTerms}" },
    539   { L"imdb.com",
    540     "http://www.imdb.com/find?q={searchTerms}" },
    541   { L"webster.com",
    542     "http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" },
    543   // Search keywords.
    544   { L"google", "http://www.google.com/" },
    545   { L"< > & \" ' \\ /", "http://g.cn/"},
    546 };
    547 
    548 static const int kDefaultFirefox2KeywordIndex = 8;
    549 
    550 class FirefoxObserver : public ProfileWriter,
    551                         public importer::ImporterProgressObserver {
    552  public:
    553   FirefoxObserver() : ProfileWriter(NULL) {
    554     bookmark_count_ = 0;
    555     history_count_ = 0;
    556     password_count_ = 0;
    557     keyword_count_ = 0;
    558   }
    559 
    560   // importer::ImporterProgressObserver:
    561   virtual void ImportStarted() OVERRIDE {}
    562   virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE {}
    563   virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE {}
    564   virtual void ImportEnded() OVERRIDE {
    565     MessageLoop::current()->Quit();
    566     EXPECT_EQ(arraysize(kFirefox2Bookmarks), bookmark_count_);
    567     EXPECT_EQ(1U, history_count_);
    568     EXPECT_EQ(arraysize(kFirefox2Passwords), password_count_);
    569     EXPECT_EQ(arraysize(kFirefox2Keywords), keyword_count_);
    570     EXPECT_EQ(kFirefox2Keywords[kDefaultFirefox2KeywordIndex].keyword,
    571               default_keyword_);
    572     EXPECT_EQ(kFirefox2Keywords[kDefaultFirefox2KeywordIndex].url,
    573               default_keyword_url_);
    574   }
    575 
    576   virtual bool BookmarkModelIsLoaded() const {
    577     // Profile is ready for writing.
    578     return true;
    579   }
    580 
    581   virtual bool TemplateURLModelIsLoaded() const {
    582     return true;
    583   }
    584 
    585   virtual void AddPasswordForm(const webkit_glue::PasswordForm& form) {
    586     PasswordList p = kFirefox2Passwords[password_count_];
    587     EXPECT_EQ(p.origin, form.origin.spec());
    588     EXPECT_EQ(p.realm, form.signon_realm);
    589     EXPECT_EQ(p.action, form.action.spec());
    590     EXPECT_EQ(WideToUTF16(p.username_element), form.username_element);
    591     EXPECT_EQ(WideToUTF16(p.username), form.username_value);
    592     EXPECT_EQ(WideToUTF16(p.password_element), form.password_element);
    593     EXPECT_EQ(WideToUTF16(p.password), form.password_value);
    594     EXPECT_EQ(p.blacklisted, form.blacklisted_by_user);
    595     ++password_count_;
    596   }
    597 
    598   virtual void AddHistoryPage(const std::vector<history::URLRow>& page,
    599                               history::VisitSource visit_source) {
    600     ASSERT_EQ(1U, page.size());
    601     EXPECT_EQ("http://en-us.www.mozilla.com/", page[0].url().spec());
    602     EXPECT_EQ(ASCIIToUTF16("Firefox Updated"), page[0].title());
    603     EXPECT_EQ(history::SOURCE_FIREFOX_IMPORTED, visit_source);
    604     ++history_count_;
    605   }
    606 
    607   virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark,
    608                                 const string16& first_folder_name,
    609                                 int options) {
    610     for (size_t i = 0; i < bookmark.size(); ++i) {
    611       if (FindBookmarkEntry(bookmark[i], kFirefox2Bookmarks,
    612                             arraysize(kFirefox2Bookmarks)))
    613         ++bookmark_count_;
    614     }
    615   }
    616 
    617   virtual void AddKeywords(const std::vector<TemplateURL*>& template_urls,
    618                            int default_keyword_index,
    619                            bool unique_on_host_and_path) {
    620     for (size_t i = 0; i < template_urls.size(); ++i) {
    621       // The order might not be deterministic, look in the expected list for
    622       // that template URL.
    623       bool found = false;
    624       string16 keyword = template_urls[i]->keyword();
    625       for (size_t j = 0; j < arraysize(kFirefox2Keywords); ++j) {
    626         if (template_urls[i]->keyword() ==
    627             WideToUTF16Hack(kFirefox2Keywords[j].keyword)) {
    628           EXPECT_EQ(kFirefox2Keywords[j].url, template_urls[i]->url()->url());
    629           found = true;
    630           break;
    631         }
    632       }
    633       EXPECT_TRUE(found);
    634       ++keyword_count_;
    635     }
    636 
    637     if (default_keyword_index != -1) {
    638       EXPECT_LT(default_keyword_index, static_cast<int>(template_urls.size()));
    639       TemplateURL* default_turl = template_urls[default_keyword_index];
    640       default_keyword_ = UTF16ToWideHack(default_turl->keyword());
    641       default_keyword_url_ = default_turl->url()->url();
    642     }
    643 
    644     STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
    645   }
    646 
    647   void AddFavicons(const std::vector<history::ImportedFaviconUsage>& favicons) {
    648   }
    649 
    650  private:
    651   ~FirefoxObserver() {}
    652 
    653   size_t bookmark_count_;
    654   size_t history_count_;
    655   size_t password_count_;
    656   size_t keyword_count_;
    657   std::wstring default_keyword_;
    658   std::string default_keyword_url_;
    659 };
    660 
    661 TEST_F(ImporterTest, MAYBE(Firefox2Importer)) {
    662   FilePath data_path;
    663   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
    664   data_path = data_path.AppendASCII("firefox2_profile");
    665   ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, true));
    666   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
    667   data_path = data_path.AppendASCII("firefox2_nss");
    668   ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, false));
    669 
    670   FilePath search_engine_path = app_path_;
    671   search_engine_path = search_engine_path.AppendASCII("searchplugins");
    672   file_util::CreateDirectory(search_engine_path);
    673   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
    674   data_path = data_path.AppendASCII("firefox2_searchplugins");
    675   if (!file_util::PathExists(data_path)) {
    676     // TODO(maruel):  Create test data that we can open source!
    677     LOG(ERROR) << L"Missing internal test data";
    678     return;
    679   }
    680   ASSERT_TRUE(file_util::CopyDirectory(data_path, search_engine_path, false));
    681 
    682   MessageLoop* loop = MessageLoop::current();
    683   scoped_refptr<ImporterHost> host(new ImporterHost);
    684   FirefoxObserver* observer = new FirefoxObserver();
    685   host->SetObserver(observer);
    686   importer::SourceProfile source_profile;
    687   source_profile.importer_type = importer::FIREFOX2;
    688   source_profile.app_path = app_path_;
    689   source_profile.source_path = profile_path_;
    690 
    691   loop->PostTask(FROM_HERE, NewRunnableMethod(
    692       host.get(),
    693       &ImporterHost::StartImportSettings,
    694       source_profile,
    695       static_cast<Profile*>(NULL),
    696       importer::HISTORY | importer::PASSWORDS |
    697       importer::FAVORITES | importer::SEARCH_ENGINES,
    698       make_scoped_refptr(observer),
    699       true));
    700   loop->Run();
    701 }
    702 
    703 static const BookmarkList kFirefox3Bookmarks[] = {
    704   {true, 0, {},
    705     L"Toolbar",
    706     "http://site/"},
    707   {false, 0, {},
    708     L"Title",
    709     "http://www.google.com/"},
    710 };
    711 
    712 static const PasswordList kFirefox3Passwords[] = {
    713   {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/",
    714     L"loginuser", L"abc", L"loginpass", L"123", false},
    715   {"http://localhost:8080/", "", "http://localhost:8080/localhost",
    716     L"", L"http", L"", L"Http1+1abcdefg", false},
    717 };
    718 
    719 static const KeywordList kFirefox3Keywords[] = {
    720   { L"amazon.com",
    721     "http://www.amazon.com/exec/obidos/external-search/?field-keywords="
    722     "{searchTerms}&mode=blended" },
    723   { L"answers.com",
    724     "http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" },
    725   { L"search.creativecommons.org",
    726     "http://search.creativecommons.org/?q={searchTerms}" },
    727   { L"search.ebay.com",
    728     "http://search.ebay.com/search/search.dll?query={searchTerms}&"
    729     "MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&"
    730     "maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" },
    731   { L"google.com",
    732     "http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" },
    733   { L"en.wikipedia.org",
    734     "http://en.wikipedia.org/wiki/Special:Search?search={searchTerms}" },
    735   { L"search.yahoo.com",
    736     "http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" },
    737   { L"flickr.com",
    738     "http://www.flickr.com/photos/tags/?q={searchTerms}" },
    739   { L"imdb.com",
    740     "http://www.imdb.com/find?q={searchTerms}" },
    741   { L"webster.com",
    742     "http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" },
    743   // Search keywords.
    744   { L"\x4E2D\x6587", "http://www.google.com/" },
    745 };
    746 
    747 static const int kDefaultFirefox3KeywordIndex = 8;
    748 
    749 class Firefox3Observer : public ProfileWriter,
    750                          public importer::ImporterProgressObserver {
    751  public:
    752   Firefox3Observer()
    753       : ProfileWriter(NULL), bookmark_count_(0), history_count_(0),
    754         password_count_(0), keyword_count_(0), import_search_engines_(true) {
    755   }
    756 
    757   explicit Firefox3Observer(bool import_search_engines)
    758       : ProfileWriter(NULL), bookmark_count_(0), history_count_(0),
    759         password_count_(0), keyword_count_(0),
    760         import_search_engines_(import_search_engines) {
    761   }
    762 
    763   // importer::ImporterProgressObserver:
    764   virtual void ImportStarted() OVERRIDE {}
    765   virtual void ImportItemStarted(importer::ImportItem item) OVERRIDE {}
    766   virtual void ImportItemEnded(importer::ImportItem item) OVERRIDE {}
    767   virtual void ImportEnded() OVERRIDE {
    768     MessageLoop::current()->Quit();
    769     EXPECT_EQ(arraysize(kFirefox3Bookmarks), bookmark_count_);
    770     EXPECT_EQ(1U, history_count_);
    771     EXPECT_EQ(arraysize(kFirefox3Passwords), password_count_);
    772     if (import_search_engines_) {
    773       EXPECT_EQ(arraysize(kFirefox3Keywords), keyword_count_);
    774       EXPECT_EQ(kFirefox3Keywords[kDefaultFirefox3KeywordIndex].keyword,
    775                 default_keyword_);
    776       EXPECT_EQ(kFirefox3Keywords[kDefaultFirefox3KeywordIndex].url,
    777                 default_keyword_url_);
    778     }
    779   }
    780 
    781   virtual bool BookmarkModelIsLoaded() const {
    782     // Profile is ready for writing.
    783     return true;
    784   }
    785 
    786   virtual bool TemplateURLModelIsLoaded() const {
    787     return true;
    788   }
    789 
    790   virtual void AddPasswordForm(const webkit_glue::PasswordForm& form) {
    791     PasswordList p = kFirefox3Passwords[password_count_];
    792     EXPECT_EQ(p.origin, form.origin.spec());
    793     EXPECT_EQ(p.realm, form.signon_realm);
    794     EXPECT_EQ(p.action, form.action.spec());
    795     EXPECT_EQ(WideToUTF16(p.username_element), form.username_element);
    796     EXPECT_EQ(WideToUTF16(p.username), form.username_value);
    797     EXPECT_EQ(WideToUTF16(p.password_element), form.password_element);
    798     EXPECT_EQ(WideToUTF16(p.password), form.password_value);
    799     EXPECT_EQ(p.blacklisted, form.blacklisted_by_user);
    800     ++password_count_;
    801   }
    802 
    803   virtual void AddHistoryPage(const std::vector<history::URLRow>& page,
    804                               history::VisitSource visit_source) {
    805     ASSERT_EQ(3U, page.size());
    806     EXPECT_EQ("http://www.google.com/", page[0].url().spec());
    807     EXPECT_EQ(ASCIIToUTF16("Google"), page[0].title());
    808     EXPECT_EQ("http://www.google.com/", page[1].url().spec());
    809     EXPECT_EQ(ASCIIToUTF16("Google"), page[1].title());
    810     EXPECT_EQ("http://www.cs.unc.edu/~jbs/resources/perl/perl-cgi/programs/form1-POST.html",
    811               page[2].url().spec());
    812     EXPECT_EQ(ASCIIToUTF16("example form (POST)"), page[2].title());
    813     EXPECT_EQ(history::SOURCE_FIREFOX_IMPORTED, visit_source);
    814     ++history_count_;
    815   }
    816 
    817   virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark,
    818                                 const string16& first_folder_name,
    819                                 int options) {
    820     for (size_t i = 0; i < bookmark.size(); ++i) {
    821       if (FindBookmarkEntry(bookmark[i], kFirefox3Bookmarks,
    822                             arraysize(kFirefox3Bookmarks)))
    823         ++bookmark_count_;
    824     }
    825   }
    826 
    827   void AddKeywords(const std::vector<TemplateURL*>& template_urls,
    828                   int default_keyword_index,
    829                   bool unique_on_host_and_path) {
    830     for (size_t i = 0; i < template_urls.size(); ++i) {
    831       // The order might not be deterministic, look in the expected list for
    832       // that template URL.
    833       bool found = false;
    834       string16 keyword = template_urls[i]->keyword();
    835       for (size_t j = 0; j < arraysize(kFirefox3Keywords); ++j) {
    836         if (template_urls[i]->keyword() ==
    837             WideToUTF16Hack(kFirefox3Keywords[j].keyword)) {
    838           EXPECT_EQ(kFirefox3Keywords[j].url, template_urls[i]->url()->url());
    839           found = true;
    840           break;
    841         }
    842       }
    843       EXPECT_TRUE(found);
    844       ++keyword_count_;
    845     }
    846 
    847     if (default_keyword_index != -1) {
    848       EXPECT_LT(default_keyword_index, static_cast<int>(template_urls.size()));
    849       TemplateURL* default_turl = template_urls[default_keyword_index];
    850       default_keyword_ = UTF16ToWideHack(default_turl->keyword());
    851       default_keyword_url_ = default_turl->url()->url();
    852     }
    853 
    854     STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
    855   }
    856 
    857   void AddFavicons(const std::vector<history::ImportedFaviconUsage>& favicons) {
    858   }
    859 
    860  private:
    861   ~Firefox3Observer() {}
    862 
    863   size_t bookmark_count_;
    864   size_t history_count_;
    865   size_t password_count_;
    866   size_t keyword_count_;
    867   bool import_search_engines_;
    868   std::wstring default_keyword_;
    869   std::string default_keyword_url_;
    870 };
    871 
    872 TEST_F(ImporterTest, MAYBE(Firefox30Importer)) {
    873   scoped_refptr<Firefox3Observer> observer(new Firefox3Observer());
    874   Firefox3xImporterTest("firefox3_profile", observer.get(), observer.get(),
    875                         true);
    876 }
    877 
    878 TEST_F(ImporterTest, MAYBE(Firefox35Importer)) {
    879   bool import_search_engines = false;
    880   scoped_refptr<Firefox3Observer> observer(
    881       new Firefox3Observer(import_search_engines));
    882   Firefox3xImporterTest("firefox35_profile", observer.get(), observer.get(),
    883                         import_search_engines);
    884 }
    885