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
53class 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
122const int kMaxPathSize = 5;
123
124struct 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|.
133bool 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)
155static 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
179static const wchar_t* kIEIdentifyUrl =
180    L"http://A79029D6-753E-4e27-B807-3D46AB1545DF.com:8080/path?key=value";
181static const wchar_t* kIEIdentifyTitle =
182    L"Unittest GUID";
183
184class 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
268bool 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
287void 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
301void 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
327TEST_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
418TEST_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
467static 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
491struct 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
502static 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
515struct KeywordList {
516  const wchar_t* keyword;
517  const char* url;
518};
519
520static 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
548static const int kDefaultFirefox2KeywordIndex = 8;
549
550class 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
661TEST_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
703static 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
712static 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
719static 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
747static const int kDefaultFirefox3KeywordIndex = 8;
748
749class 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
872TEST_F(ImporterTest, MAYBE(Firefox30Importer)) {
873  scoped_refptr<Firefox3Observer> observer(new Firefox3Observer());
874  Firefox3xImporterTest("firefox3_profile", observer.get(), observer.get(),
875                        true);
876}
877
878TEST_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