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 "chrome/browser/importer/firefox2_importer.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/file_path.h"
11 #include "base/file_util.h"
12 #include "base/i18n/icu_string_conversions.h"
13 #include "base/message_loop.h"
14 #include "base/path_service.h"
15 #include "base/stl_util-inl.h"
16 #include "base/string_number_conversions.h"
17 #include "base/string_split.h"
18 #include "base/string_util.h"
19 #include "base/utf_string_conversions.h"
20 #include "chrome/browser/history/history_types.h"
21 #include "chrome/browser/importer/firefox_importer_utils.h"
22 #include "chrome/browser/importer/importer_bridge.h"
23 #include "chrome/browser/importer/mork_reader.h"
24 #include "chrome/browser/importer/nss_decryptor.h"
25 #include "chrome/browser/search_engines/template_url.h"
26 #include "chrome/browser/search_engines/template_url_parser.h"
27 #include "chrome/common/time_format.h"
28 #include "chrome/common/url_constants.h"
29 #include "googleurl/src/gurl.h"
30 #include "grit/generated_resources.h"
31 #include "net/base/data_url.h"
32 #include "webkit/glue/password_form.h"
33
34 namespace {
35 const char kItemOpen[] = "<DT><A";
36 const char kItemClose[] = "</A>";
37 const char kFeedURLAttribute[] = "FEEDURL";
38 const char kHrefAttribute[] = "HREF";
39 const char kIconAttribute[] = "ICON";
40 const char kShortcutURLAttribute[] = "SHORTCUTURL";
41 const char kAddDateAttribute[] = "ADD_DATE";
42 const char kPostDataAttribute[] = "POST_DATA";
43 }
44
Firefox2Importer()45 Firefox2Importer::Firefox2Importer() : parsing_bookmarks_html_file_(false) {}
46
~Firefox2Importer()47 Firefox2Importer::~Firefox2Importer() {}
48
StartImport(const importer::SourceProfile & source_profile,uint16 items,ImporterBridge * bridge)49 void Firefox2Importer::StartImport(
50 const importer::SourceProfile& source_profile,
51 uint16 items,
52 ImporterBridge* bridge) {
53 bridge_ = bridge;
54 source_path_ = source_profile.source_path;
55 app_path_ = source_profile.app_path;
56
57 parsing_bookmarks_html_file_ =
58 (source_profile.importer_type == importer::BOOKMARKS_HTML);
59
60 // The order here is important!
61 bridge_->NotifyStarted();
62 if ((items & importer::HOME_PAGE) && !cancelled())
63 ImportHomepage(); // Doesn't have a UI item.
64
65 // Note history should be imported before bookmarks because bookmark import
66 // will also import favicons and we store favicon for a URL only if the URL
67 // exist in history or bookmarks.
68 if ((items & importer::HISTORY) && !cancelled()) {
69 bridge_->NotifyItemStarted(importer::HISTORY);
70 ImportHistory();
71 bridge_->NotifyItemEnded(importer::HISTORY);
72 }
73
74 if ((items & importer::FAVORITES) && !cancelled()) {
75 bridge_->NotifyItemStarted(importer::FAVORITES);
76 ImportBookmarks();
77 bridge_->NotifyItemEnded(importer::FAVORITES);
78 }
79 if ((items & importer::SEARCH_ENGINES) && !cancelled()) {
80 bridge_->NotifyItemStarted(importer::SEARCH_ENGINES);
81 ImportSearchEngines();
82 bridge_->NotifyItemEnded(importer::SEARCH_ENGINES);
83 }
84 if ((items & importer::PASSWORDS) && !cancelled()) {
85 bridge_->NotifyItemStarted(importer::PASSWORDS);
86 ImportPasswords();
87 bridge_->NotifyItemEnded(importer::PASSWORDS);
88 }
89 bridge_->NotifyEnded();
90 }
91
92 // static
LoadDefaultBookmarks(const FilePath & app_path,std::set<GURL> * urls)93 void Firefox2Importer::LoadDefaultBookmarks(const FilePath& app_path,
94 std::set<GURL> *urls) {
95 FilePath file = app_path.AppendASCII("defaults")
96 .AppendASCII("profile")
97 .AppendASCII("bookmarks.html");
98
99 urls->clear();
100
101 // Read the whole file.
102 std::string content;
103 file_util::ReadFileToString(file, &content);
104 std::vector<std::string> lines;
105 base::SplitString(content, '\n', &lines);
106
107 std::string charset;
108 for (size_t i = 0; i < lines.size(); ++i) {
109 std::string line;
110 TrimString(lines[i], " ", &line);
111
112 // Get the encoding of the bookmark file.
113 if (ParseCharsetFromLine(line, &charset))
114 continue;
115
116 // Get the bookmark.
117 string16 title;
118 GURL url, favicon;
119 string16 shortcut;
120 base::Time add_date;
121 string16 post_data;
122 if (ParseBookmarkFromLine(line, charset, &title, &url,
123 &favicon, &shortcut, &add_date,
124 &post_data))
125 urls->insert(url);
126 }
127 }
128
129 // static
CreateTemplateURL(const string16 & title,const string16 & keyword,const GURL & url)130 TemplateURL* Firefox2Importer::CreateTemplateURL(const string16& title,
131 const string16& keyword,
132 const GURL& url) {
133 // Skip if the keyword or url is invalid.
134 if (keyword.empty() && url.is_valid())
135 return NULL;
136
137 TemplateURL* t_url = new TemplateURL();
138 // We set short name by using the title if it exists.
139 // Otherwise, we use the shortcut.
140 t_url->set_short_name(!title.empty() ? title : keyword);
141 t_url->set_keyword(keyword);
142 t_url->SetURL(TemplateURLRef::DisplayURLToURLRef(UTF8ToUTF16(url.spec())),
143 0, 0);
144 return t_url;
145 }
146
147 // static
ImportBookmarksFile(const FilePath & file_path,const std::set<GURL> & default_urls,bool import_to_bookmark_bar,const string16 & first_folder_name,Importer * importer,std::vector<ProfileWriter::BookmarkEntry> * bookmarks,std::vector<TemplateURL * > * template_urls,std::vector<history::ImportedFaviconUsage> * favicons)148 void Firefox2Importer::ImportBookmarksFile(
149 const FilePath& file_path,
150 const std::set<GURL>& default_urls,
151 bool import_to_bookmark_bar,
152 const string16& first_folder_name,
153 Importer* importer,
154 std::vector<ProfileWriter::BookmarkEntry>* bookmarks,
155 std::vector<TemplateURL*>* template_urls,
156 std::vector<history::ImportedFaviconUsage>* favicons) {
157 std::string content;
158 file_util::ReadFileToString(file_path, &content);
159 std::vector<std::string> lines;
160 base::SplitString(content, '\n', &lines);
161
162 std::vector<ProfileWriter::BookmarkEntry> toolbar_bookmarks;
163 string16 last_folder = first_folder_name;
164 bool last_folder_on_toolbar = false;
165 bool last_folder_is_empty = true;
166 base::Time last_folder_add_date;
167 std::vector<string16> path;
168 size_t toolbar_folder = 0;
169 std::string charset;
170 for (size_t i = 0; i < lines.size() && (!importer || !importer->cancelled());
171 ++i) {
172 std::string line;
173 TrimString(lines[i], " ", &line);
174
175 // Get the encoding of the bookmark file.
176 if (ParseCharsetFromLine(line, &charset))
177 continue;
178
179 // Get the folder name.
180 if (ParseFolderNameFromLine(line, charset, &last_folder,
181 &last_folder_on_toolbar,
182 &last_folder_add_date))
183 continue;
184
185 // Get the bookmark entry.
186 string16 title;
187 string16 shortcut;
188 GURL url, favicon;
189 base::Time add_date;
190 string16 post_data;
191 bool is_bookmark;
192 // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based
193 // keywords yet.
194 is_bookmark = ParseBookmarkFromLine(line, charset, &title,
195 &url, &favicon, &shortcut, &add_date,
196 &post_data) ||
197 ParseMinimumBookmarkFromLine(line, charset, &title, &url);
198
199 if (is_bookmark)
200 last_folder_is_empty = false;
201
202 if (is_bookmark &&
203 post_data.empty() &&
204 CanImportURL(GURL(url)) &&
205 default_urls.find(url) == default_urls.end()) {
206 if (toolbar_folder > path.size() && !path.empty()) {
207 NOTREACHED(); // error in parsing.
208 break;
209 }
210
211 ProfileWriter::BookmarkEntry entry;
212 entry.creation_time = add_date;
213 entry.url = url;
214 entry.title = title;
215
216 if (import_to_bookmark_bar && toolbar_folder) {
217 // Flatten the items in toolbar.
218 entry.in_toolbar = true;
219 entry.path.assign(path.begin() + toolbar_folder, path.end());
220 toolbar_bookmarks.push_back(entry);
221 } else {
222 // Insert the item into the "Imported from Firefox" folder.
223 entry.path.assign(path.begin(), path.end());
224 if (import_to_bookmark_bar)
225 entry.path.erase(entry.path.begin());
226 bookmarks->push_back(entry);
227 }
228
229 // Save the favicon. DataURLToFaviconUsage will handle the case where
230 // there is no favicon.
231 if (favicons)
232 DataURLToFaviconUsage(url, favicon, favicons);
233
234 if (template_urls) {
235 // If there is a SHORTCUT attribute for this bookmark, we
236 // add it as our keywords.
237 TemplateURL* t_url = CreateTemplateURL(title, shortcut, url);
238 if (t_url)
239 template_urls->push_back(t_url);
240 }
241
242 continue;
243 }
244
245 // Bookmarks in sub-folder are encapsulated with <DL> tag.
246 if (StartsWithASCII(line, "<DL>", false)) {
247 path.push_back(last_folder);
248 last_folder.clear();
249 if (last_folder_on_toolbar && !toolbar_folder)
250 toolbar_folder = path.size();
251
252 // Mark next folder empty as initial state.
253 last_folder_is_empty = true;
254 } else if (StartsWithASCII(line, "</DL>", false)) {
255 if (path.empty())
256 break; // Mismatch <DL>.
257
258 string16 folder_title = path.back();
259 path.pop_back();
260
261 if (last_folder_is_empty) {
262 // Empty folder should be added explicitly.
263 ProfileWriter::BookmarkEntry entry;
264 entry.is_folder = true;
265 entry.creation_time = last_folder_add_date;
266 entry.title = folder_title;
267 if (import_to_bookmark_bar && toolbar_folder) {
268 // Flatten the folder in toolbar.
269 if (toolbar_folder <= path.size()) {
270 entry.in_toolbar = true;
271 entry.path.assign(path.begin() + toolbar_folder, path.end());
272 toolbar_bookmarks.push_back(entry);
273 }
274 } else {
275 // Insert the folder into the "Imported from Firefox" folder.
276 entry.path.assign(path.begin(), path.end());
277 if (import_to_bookmark_bar)
278 entry.path.erase(entry.path.begin());
279 bookmarks->push_back(entry);
280 }
281
282 // Parent folder include current one, so it's not empty.
283 last_folder_is_empty = false;
284 }
285
286 if (toolbar_folder > path.size())
287 toolbar_folder = 0;
288 }
289 }
290
291 bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(),
292 toolbar_bookmarks.end());
293 }
294
ImportBookmarks()295 void Firefox2Importer::ImportBookmarks() {
296 // Load the default bookmarks.
297 std::set<GURL> default_urls;
298 if (!parsing_bookmarks_html_file_)
299 LoadDefaultBookmarks(app_path_, &default_urls);
300
301 // Parse the bookmarks.html file.
302 std::vector<ProfileWriter::BookmarkEntry> bookmarks, toolbar_bookmarks;
303 std::vector<TemplateURL*> template_urls;
304 std::vector<history::ImportedFaviconUsage> favicons;
305 FilePath file = source_path_;
306 if (!parsing_bookmarks_html_file_)
307 file = file.AppendASCII("bookmarks.html");
308 string16 first_folder_name = bridge_->GetLocalizedString(
309 parsing_bookmarks_html_file_ ? IDS_BOOKMARK_GROUP :
310 IDS_BOOKMARK_GROUP_FROM_FIREFOX);
311
312 ImportBookmarksFile(file, default_urls, import_to_bookmark_bar(),
313 first_folder_name, this, &bookmarks, &template_urls,
314 &favicons);
315
316 // Write data into profile.
317 if (!bookmarks.empty() && !cancelled()) {
318 int options = 0;
319 if (import_to_bookmark_bar())
320 options |= ProfileWriter::IMPORT_TO_BOOKMARK_BAR;
321 if (bookmark_bar_disabled())
322 options |= ProfileWriter::BOOKMARK_BAR_DISABLED;
323 bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options);
324 }
325 if (!parsing_bookmarks_html_file_ && !template_urls.empty() &&
326 !cancelled()) {
327 bridge_->SetKeywords(template_urls, -1, false);
328 } else {
329 STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
330 }
331 if (!favicons.empty()) {
332 bridge_->SetFavicons(favicons);
333 }
334 }
335
ImportPasswords()336 void Firefox2Importer::ImportPasswords() {
337 // Initializes NSS3.
338 NSSDecryptor decryptor;
339 if (!decryptor.Init(source_path_, source_path_) &&
340 !decryptor.Init(app_path_, source_path_)) {
341 return;
342 }
343
344 // Firefox 2 uses signons2.txt to store the pssswords. If it doesn't
345 // exist, we try to find its older version.
346 FilePath file = source_path_.AppendASCII("signons2.txt");
347 if (!file_util::PathExists(file)) {
348 file = source_path_.AppendASCII("signons.txt");
349 }
350
351 std::string content;
352 file_util::ReadFileToString(file, &content);
353 std::vector<webkit_glue::PasswordForm> forms;
354 decryptor.ParseSignons(content, &forms);
355
356 if (!cancelled()) {
357 for (size_t i = 0; i < forms.size(); ++i) {
358 bridge_->SetPasswordForm(forms[i]);
359 }
360 }
361 }
362
ImportHistory()363 void Firefox2Importer::ImportHistory() {
364 FilePath file = source_path_.AppendASCII("history.dat");
365 ImportHistoryFromFirefox2(file, bridge_);
366 }
367
ImportSearchEngines()368 void Firefox2Importer::ImportSearchEngines() {
369 std::vector<FilePath> files;
370 GetSearchEnginesXMLFiles(&files);
371
372 std::vector<TemplateURL*> search_engines;
373 ParseSearchEnginesFromXMLFiles(files, &search_engines);
374
375 int default_index =
376 GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_);
377 bridge_->SetKeywords(search_engines, default_index, true);
378 }
379
ImportHomepage()380 void Firefox2Importer::ImportHomepage() {
381 GURL home_page = GetHomepage(source_path_);
382 if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) {
383 bridge_->AddHomePage(home_page);
384 }
385 }
386
GetSearchEnginesXMLFiles(std::vector<FilePath> * files)387 void Firefox2Importer::GetSearchEnginesXMLFiles(
388 std::vector<FilePath>* files) {
389 // Search engines are contained in XML files in a searchplugins directory that
390 // can be found in 2 locations:
391 // - Firefox install dir (default search engines)
392 // - the profile dir (user added search engines)
393 FilePath dir = app_path_.AppendASCII("searchplugins");
394 FindXMLFilesInDir(dir, files);
395
396 FilePath profile_dir = source_path_.AppendASCII("searchplugins");
397 FindXMLFilesInDir(profile_dir, files);
398 }
399
400 // static
ParseCharsetFromLine(const std::string & line,std::string * charset)401 bool Firefox2Importer::ParseCharsetFromLine(const std::string& line,
402 std::string* charset) {
403 const char kCharset[] = "charset=";
404 if (StartsWithASCII(line, "<META", false) &&
405 (line.find("CONTENT=\"") != std::string::npos ||
406 line.find("content=\"") != std::string::npos)) {
407 size_t begin = line.find(kCharset);
408 if (begin == std::string::npos)
409 return false;
410 begin += std::string(kCharset).size();
411 size_t end = line.find_first_of('\"', begin);
412 *charset = line.substr(begin, end - begin);
413 return true;
414 }
415 return false;
416 }
417
418 // static
ParseFolderNameFromLine(const std::string & line,const std::string & charset,string16 * folder_name,bool * is_toolbar_folder,base::Time * add_date)419 bool Firefox2Importer::ParseFolderNameFromLine(const std::string& line,
420 const std::string& charset,
421 string16* folder_name,
422 bool* is_toolbar_folder,
423 base::Time* add_date) {
424 const char kFolderOpen[] = "<DT><H3";
425 const char kFolderClose[] = "</H3>";
426 const char kToolbarFolderAttribute[] = "PERSONAL_TOOLBAR_FOLDER";
427 const char kAddDateAttribute[] = "ADD_DATE";
428
429 if (!StartsWithASCII(line, kFolderOpen, true))
430 return false;
431
432 size_t end = line.find(kFolderClose);
433 size_t tag_end = line.rfind('>', end) + 1;
434 // If no end tag or start tag is broken, we skip to find the folder name.
435 if (end == std::string::npos || tag_end < arraysize(kFolderOpen))
436 return false;
437
438 base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(),
439 base::OnStringConversionError::SKIP, folder_name);
440 HTMLUnescape(folder_name);
441
442 std::string attribute_list = line.substr(arraysize(kFolderOpen),
443 tag_end - arraysize(kFolderOpen) - 1);
444 std::string value;
445
446 // Add date
447 if (GetAttribute(attribute_list, kAddDateAttribute, &value)) {
448 int64 time;
449 base::StringToInt64(value, &time);
450 // Upper bound it at 32 bits.
451 if (0 < time && time < (1LL << 32))
452 *add_date = base::Time::FromTimeT(time);
453 }
454
455 if (GetAttribute(attribute_list, kToolbarFolderAttribute, &value) &&
456 LowerCaseEqualsASCII(value, "true"))
457 *is_toolbar_folder = true;
458 else
459 *is_toolbar_folder = false;
460
461 return true;
462 }
463
464 // static
ParseBookmarkFromLine(const std::string & line,const std::string & charset,string16 * title,GURL * url,GURL * favicon,string16 * shortcut,base::Time * add_date,string16 * post_data)465 bool Firefox2Importer::ParseBookmarkFromLine(const std::string& line,
466 const std::string& charset,
467 string16* title,
468 GURL* url,
469 GURL* favicon,
470 string16* shortcut,
471 base::Time* add_date,
472 string16* post_data) {
473 title->clear();
474 *url = GURL();
475 *favicon = GURL();
476 shortcut->clear();
477 post_data->clear();
478 *add_date = base::Time();
479
480 if (!StartsWithASCII(line, kItemOpen, true))
481 return false;
482
483 size_t end = line.find(kItemClose);
484 size_t tag_end = line.rfind('>', end) + 1;
485 if (end == std::string::npos || tag_end < arraysize(kItemOpen))
486 return false; // No end tag or start tag is broken.
487
488 std::string attribute_list = line.substr(arraysize(kItemOpen),
489 tag_end - arraysize(kItemOpen) - 1);
490
491 // We don't import Live Bookmark folders, which is Firefox's RSS reading
492 // feature, since the user never necessarily bookmarked them and we don't
493 // have this feature to update their contents.
494 std::string value;
495 if (GetAttribute(attribute_list, kFeedURLAttribute, &value))
496 return false;
497
498 // Title
499 base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(),
500 base::OnStringConversionError::SKIP, title);
501 HTMLUnescape(title);
502
503 // URL
504 if (GetAttribute(attribute_list, kHrefAttribute, &value)) {
505 string16 url16;
506 base::CodepageToUTF16(value, charset.c_str(),
507 base::OnStringConversionError::SKIP, &url16);
508 HTMLUnescape(&url16);
509
510 *url = GURL(url16);
511 }
512
513 // Favicon
514 if (GetAttribute(attribute_list, kIconAttribute, &value))
515 *favicon = GURL(value);
516
517 // Keyword
518 if (GetAttribute(attribute_list, kShortcutURLAttribute, &value)) {
519 base::CodepageToUTF16(value, charset.c_str(),
520 base::OnStringConversionError::SKIP, shortcut);
521 HTMLUnescape(shortcut);
522 }
523
524 // Add date
525 if (GetAttribute(attribute_list, kAddDateAttribute, &value)) {
526 int64 time;
527 base::StringToInt64(value, &time);
528 // Upper bound it at 32 bits.
529 if (0 < time && time < (1LL << 32))
530 *add_date = base::Time::FromTimeT(time);
531 }
532
533 // Post data.
534 if (GetAttribute(attribute_list, kPostDataAttribute, &value)) {
535 base::CodepageToUTF16(value, charset.c_str(),
536 base::OnStringConversionError::SKIP, post_data);
537 HTMLUnescape(post_data);
538 }
539
540 return true;
541 }
542
543 // static
ParseMinimumBookmarkFromLine(const std::string & line,const std::string & charset,string16 * title,GURL * url)544 bool Firefox2Importer::ParseMinimumBookmarkFromLine(const std::string& line,
545 const std::string& charset,
546 string16* title,
547 GURL* url) {
548 const char kItemOpen[] = "<DT><A";
549 const char kItemClose[] = "</";
550 const char kHrefAttributeUpper[] = "HREF";
551 const char kHrefAttributeLower[] = "href";
552
553 title->clear();
554 *url = GURL();
555
556 // Case-insensitive check of open tag.
557 if (!StartsWithASCII(line, kItemOpen, false))
558 return false;
559
560 // Find any close tag.
561 size_t end = line.find(kItemClose);
562 size_t tag_end = line.rfind('>', end) + 1;
563 if (end == std::string::npos || tag_end < arraysize(kItemOpen))
564 return false; // No end tag or start tag is broken.
565
566 std::string attribute_list = line.substr(arraysize(kItemOpen),
567 tag_end - arraysize(kItemOpen) - 1);
568
569 // Title
570 base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(),
571 base::OnStringConversionError::SKIP, title);
572 HTMLUnescape(title);
573
574 // URL
575 std::string value;
576 if (GetAttribute(attribute_list, kHrefAttributeUpper, &value) ||
577 GetAttribute(attribute_list, kHrefAttributeLower, &value)) {
578 if (charset.length() != 0) {
579 string16 url16;
580 base::CodepageToUTF16(value, charset.c_str(),
581 base::OnStringConversionError::SKIP, &url16);
582 HTMLUnescape(&url16);
583
584 *url = GURL(url16);
585 } else {
586 *url = GURL(value);
587 }
588 }
589
590 return true;
591 }
592
593 // static
GetAttribute(const std::string & attribute_list,const std::string & attribute,std::string * value)594 bool Firefox2Importer::GetAttribute(const std::string& attribute_list,
595 const std::string& attribute,
596 std::string* value) {
597 const char kQuote[] = "\"";
598
599 size_t begin = attribute_list.find(attribute + "=" + kQuote);
600 if (begin == std::string::npos)
601 return false; // Can't find the attribute.
602
603 begin = attribute_list.find(kQuote, begin) + 1;
604
605 size_t end = begin + 1;
606 while (end < attribute_list.size()) {
607 if (attribute_list[end] == '"' &&
608 attribute_list[end - 1] != '\\') {
609 break;
610 }
611 end++;
612 }
613
614 if (end == attribute_list.size())
615 return false; // The value is not quoted.
616
617 *value = attribute_list.substr(begin, end - begin);
618 return true;
619 }
620
621 // static
HTMLUnescape(string16 * text)622 void Firefox2Importer::HTMLUnescape(string16* text) {
623 string16 text16 = *text;
624 ReplaceSubstringsAfterOffset(
625 &text16, 0, ASCIIToUTF16("<"), ASCIIToUTF16("<"));
626 ReplaceSubstringsAfterOffset(
627 &text16, 0, ASCIIToUTF16(">"), ASCIIToUTF16(">"));
628 ReplaceSubstringsAfterOffset(
629 &text16, 0, ASCIIToUTF16("&"), ASCIIToUTF16("&"));
630 ReplaceSubstringsAfterOffset(
631 &text16, 0, ASCIIToUTF16("""), ASCIIToUTF16("\""));
632 ReplaceSubstringsAfterOffset(
633 &text16, 0, ASCIIToUTF16("'"), ASCIIToUTF16("\'"));
634 text->assign(text16);
635 }
636
637 // static
FindXMLFilesInDir(const FilePath & dir,std::vector<FilePath> * xml_files)638 void Firefox2Importer::FindXMLFilesInDir(
639 const FilePath& dir,
640 std::vector<FilePath>* xml_files) {
641 file_util::FileEnumerator file_enum(dir, false,
642 file_util::FileEnumerator::FILES,
643 FILE_PATH_LITERAL("*.xml"));
644 FilePath file(file_enum.Next());
645 while (!file.empty()) {
646 xml_files->push_back(file);
647 file = file_enum.Next();
648 }
649 }
650
651 // static
DataURLToFaviconUsage(const GURL & link_url,const GURL & favicon_data,std::vector<history::ImportedFaviconUsage> * favicons)652 void Firefox2Importer::DataURLToFaviconUsage(
653 const GURL& link_url,
654 const GURL& favicon_data,
655 std::vector<history::ImportedFaviconUsage>* favicons) {
656 if (!link_url.is_valid() || !favicon_data.is_valid() ||
657 !favicon_data.SchemeIs(chrome::kDataScheme))
658 return;
659
660 // Parse the data URL.
661 std::string mime_type, char_set, data;
662 if (!net::DataURL::Parse(favicon_data, &mime_type, &char_set, &data) ||
663 data.empty())
664 return;
665
666 history::ImportedFaviconUsage usage;
667 if (!ReencodeFavicon(reinterpret_cast<const unsigned char*>(&data[0]),
668 data.size(), &usage.png_data))
669 return; // Unable to decode.
670
671 // We need to make up a URL for the favicon. We use a version of the page's
672 // URL so that we can be sure it will not collide.
673 usage.favicon_url = GURL(std::string("made-up-favicon:") + link_url.spec());
674
675 // We only have one URL per favicon for Firefox 2 bookmarks.
676 usage.urls.insert(link_url);
677
678 favicons->push_back(usage);
679 }
680