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/firefox_importer_utils.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <string>
10
11 #include "base/file_util.h"
12 #include "base/logging.h"
13 #include "base/string_split.h"
14 #include "base/string_util.h"
15 #include "base/string_number_conversions.h"
16 #include "base/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "chrome/browser/search_engines/template_url.h"
19 #include "chrome/browser/search_engines/template_url_model.h"
20 #include "chrome/browser/search_engines/template_url_parser.h"
21 #include "chrome/browser/search_engines/template_url_prepopulate_data.h"
22 #include "googleurl/src/gurl.h"
23
24 namespace {
25
26 // FirefoxURLParameterFilter is used to remove parameter mentioning Firefox from
27 // the search URL when importing search engines.
28 class FirefoxURLParameterFilter : public TemplateURLParser::ParameterFilter {
29 public:
FirefoxURLParameterFilter()30 FirefoxURLParameterFilter() {}
~FirefoxURLParameterFilter()31 virtual ~FirefoxURLParameterFilter() {}
32
33 // TemplateURLParser::ParameterFilter method.
KeepParameter(const std::string & key,const std::string & value)34 virtual bool KeepParameter(const std::string& key,
35 const std::string& value) {
36 std::string low_value = StringToLowerASCII(value);
37 if (low_value.find("mozilla") != std::string::npos ||
38 low_value.find("firefox") != std::string::npos ||
39 low_value.find("moz:") != std::string::npos )
40 return false;
41 return true;
42 }
43
44 private:
45 DISALLOW_COPY_AND_ASSIGN(FirefoxURLParameterFilter);
46 };
47 } // namespace
48
GetFirefoxProfilePath()49 FilePath GetFirefoxProfilePath() {
50 DictionaryValue root;
51 FilePath ini_file = GetProfilesINI();
52 ParseProfileINI(ini_file, &root);
53
54 FilePath source_path;
55 for (int i = 0; ; ++i) {
56 std::string current_profile = StringPrintf("Profile%d", i);
57 if (!root.HasKey(current_profile)) {
58 // Profiles are continuously numbered. So we exit when we can't
59 // find the i-th one.
60 break;
61 }
62 std::string is_relative;
63 string16 path16;
64 if (root.GetStringASCII(current_profile + ".IsRelative", &is_relative) &&
65 root.GetString(current_profile + ".Path", &path16)) {
66 #if defined(OS_WIN)
67 ReplaceSubstringsAfterOffset(
68 &path16, 0, ASCIIToUTF16("/"), ASCIIToUTF16("\\"));
69 #endif
70 FilePath path = FilePath::FromWStringHack(UTF16ToWide(path16));
71
72 // IsRelative=1 means the folder path would be relative to the
73 // path of profiles.ini. IsRelative=0 refers to a custom profile
74 // location.
75 if (is_relative == "1") {
76 path = ini_file.DirName().Append(path);
77 }
78
79 // We only import the default profile when multiple profiles exist,
80 // since the other profiles are used mostly by developers for testing.
81 // Otherwise, Profile0 will be imported.
82 std::string is_default;
83 if ((root.GetStringASCII(current_profile + ".Default", &is_default) &&
84 is_default == "1") || i == 0) {
85 // We have found the default profile.
86 return path;
87 }
88 }
89 }
90 return FilePath();
91 }
92
93
GetFirefoxVersionAndPathFromProfile(const FilePath & profile_path,int * version,FilePath * app_path)94 bool GetFirefoxVersionAndPathFromProfile(const FilePath& profile_path,
95 int* version,
96 FilePath* app_path) {
97 bool ret = false;
98 FilePath compatibility_file = profile_path.AppendASCII("compatibility.ini");
99 std::string content;
100 file_util::ReadFileToString(compatibility_file, &content);
101 ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n");
102 std::vector<std::string> lines;
103 base::SplitString(content, '\n', &lines);
104
105 for (size_t i = 0; i < lines.size(); ++i) {
106 const std::string& line = lines[i];
107 if (line.empty() || line[0] == '#' || line[0] == ';')
108 continue;
109 size_t equal = line.find('=');
110 if (equal != std::string::npos) {
111 std::string key = line.substr(0, equal);
112 if (key == "LastVersion") {
113 *version = line.substr(equal + 1)[0] - '0';
114 ret = true;
115 } else if (key == "LastAppDir") {
116 // TODO(evanm): If the path in question isn't convertible to
117 // UTF-8, what does Firefox do? If it puts raw bytes in the
118 // file, we could go straight from bytes -> filepath;
119 // otherwise, we're out of luck here.
120 *app_path = FilePath::FromWStringHack(
121 UTF8ToWide(line.substr(equal + 1)));
122 }
123 }
124 }
125 return ret;
126 }
127
ParseProfileINI(const FilePath & file,DictionaryValue * root)128 void ParseProfileINI(const FilePath& file, DictionaryValue* root) {
129 // Reads the whole INI file.
130 std::string content;
131 file_util::ReadFileToString(file, &content);
132 ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n");
133 std::vector<std::string> lines;
134 base::SplitString(content, '\n', &lines);
135
136 // Parses the file.
137 root->Clear();
138 std::string current_section;
139 for (size_t i = 0; i < lines.size(); ++i) {
140 std::string line = lines[i];
141 if (line.empty()) {
142 // Skips the empty line.
143 continue;
144 }
145 if (line[0] == '#' || line[0] == ';') {
146 // This line is a comment.
147 continue;
148 }
149 if (line[0] == '[') {
150 // It is a section header.
151 current_section = line.substr(1);
152 size_t end = current_section.rfind(']');
153 if (end != std::string::npos)
154 current_section.erase(end);
155 } else {
156 std::string key, value;
157 size_t equal = line.find('=');
158 if (equal != std::string::npos) {
159 key = line.substr(0, equal);
160 value = line.substr(equal + 1);
161 // Checks whether the section and key contain a '.' character.
162 // Those sections and keys break DictionaryValue's path format,
163 // so we discard them.
164 if (current_section.find('.') == std::string::npos &&
165 key.find('.') == std::string::npos)
166 root->SetString(current_section + "." + key, value);
167 }
168 }
169 }
170 }
171
CanImportURL(const GURL & url)172 bool CanImportURL(const GURL& url) {
173 const char* kInvalidSchemes[] = {"wyciwyg", "place", "about", "chrome"};
174
175 // The URL is not valid.
176 if (!url.is_valid())
177 return false;
178
179 // Filter out the URLs with unsupported schemes.
180 for (size_t i = 0; i < arraysize(kInvalidSchemes); ++i) {
181 if (url.SchemeIs(kInvalidSchemes[i]))
182 return false;
183 }
184
185 return true;
186 }
187
ParseSearchEnginesFromXMLFiles(const std::vector<FilePath> & xml_files,std::vector<TemplateURL * > * search_engines)188 void ParseSearchEnginesFromXMLFiles(const std::vector<FilePath>& xml_files,
189 std::vector<TemplateURL*>* search_engines) {
190 DCHECK(search_engines);
191
192 std::map<std::string, TemplateURL*> search_engine_for_url;
193 std::string content;
194 // The first XML file represents the default search engine in Firefox 3, so we
195 // need to keep it on top of the list.
196 TemplateURL* default_turl = NULL;
197 for (std::vector<FilePath>::const_iterator file_iter = xml_files.begin();
198 file_iter != xml_files.end(); ++file_iter) {
199 file_util::ReadFileToString(*file_iter, &content);
200 TemplateURL* template_url = new TemplateURL();
201 FirefoxURLParameterFilter param_filter;
202 if (TemplateURLParser::Parse(
203 reinterpret_cast<const unsigned char*>(content.data()),
204 content.length(), ¶m_filter, template_url) &&
205 template_url->url()) {
206 std::string url = template_url->url()->url();
207 std::map<std::string, TemplateURL*>::iterator iter =
208 search_engine_for_url.find(url);
209 if (iter != search_engine_for_url.end()) {
210 // We have already found a search engine with the same URL. We give
211 // priority to the latest one found, as GetSearchEnginesXMLFiles()
212 // returns a vector with first Firefox default search engines and then
213 // the user's ones. We want to give priority to the user ones.
214 delete iter->second;
215 search_engine_for_url.erase(iter);
216 }
217 // Give this a keyword to facilitate tab-to-search, if possible.
218 GURL gurl = GURL(url);
219 template_url->set_keyword(
220 TemplateURLModel::GenerateKeyword(gurl, false));
221 template_url->set_logo_id(
222 TemplateURLPrepopulateData::GetSearchEngineLogo(gurl));
223 template_url->set_show_in_default_list(true);
224 search_engine_for_url[url] = template_url;
225 if (!default_turl)
226 default_turl = template_url;
227 } else {
228 delete template_url;
229 }
230 content.clear();
231 }
232
233 // Put the results in the |search_engines| vector.
234 std::map<std::string, TemplateURL*>::iterator t_iter;
235 for (t_iter = search_engine_for_url.begin();
236 t_iter != search_engine_for_url.end(); ++t_iter) {
237 if (t_iter->second == default_turl)
238 search_engines->insert(search_engines->begin(), default_turl);
239 else
240 search_engines->push_back(t_iter->second);
241 }
242 }
243
ReadPrefFile(const FilePath & path,std::string * content)244 bool ReadPrefFile(const FilePath& path, std::string* content) {
245 if (content == NULL)
246 return false;
247
248 file_util::ReadFileToString(path, content);
249
250 if (content->empty()) {
251 LOG(WARNING) << "Firefox preference file " << path.value() << " is empty.";
252 return false;
253 }
254
255 return true;
256 }
257
ReadBrowserConfigProp(const FilePath & app_path,const std::string & pref_key)258 std::string ReadBrowserConfigProp(const FilePath& app_path,
259 const std::string& pref_key) {
260 std::string content;
261 if (!ReadPrefFile(app_path.AppendASCII("browserconfig.properties"), &content))
262 return "";
263
264 // This file has the syntax: key=value.
265 size_t prop_index = content.find(pref_key + "=");
266 if (prop_index == std::string::npos)
267 return "";
268
269 size_t start = prop_index + pref_key.length();
270 size_t stop = std::string::npos;
271 if (start != std::string::npos)
272 stop = content.find("\n", start + 1);
273
274 if (start == std::string::npos ||
275 stop == std::string::npos || (start == stop)) {
276 LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
277 return "";
278 }
279
280 return content.substr(start + 1, stop - start - 1);
281 }
282
ReadPrefsJsValue(const FilePath & profile_path,const std::string & pref_key)283 std::string ReadPrefsJsValue(const FilePath& profile_path,
284 const std::string& pref_key) {
285 std::string content;
286 if (!ReadPrefFile(profile_path.AppendASCII("prefs.js"), &content))
287 return "";
288
289 return GetPrefsJsValue(content, pref_key);
290 }
291
GetFirefoxDefaultSearchEngineIndex(const std::vector<TemplateURL * > & search_engines,const FilePath & profile_path)292 int GetFirefoxDefaultSearchEngineIndex(
293 const std::vector<TemplateURL*>& search_engines,
294 const FilePath& profile_path) {
295 // The default search engine is contained in the file prefs.js found in the
296 // profile directory.
297 // It is the "browser.search.selectedEngine" property.
298 if (search_engines.empty())
299 return -1;
300
301 std::string default_se_name =
302 ReadPrefsJsValue(profile_path, "browser.search.selectedEngine");
303
304 if (default_se_name.empty()) {
305 // browser.search.selectedEngine does not exist if the user has not changed
306 // from the default (or has selected the default).
307 // TODO: should fallback to 'browser.search.defaultengine' if selectedEngine
308 // is empty.
309 return -1;
310 }
311
312 int default_se_index = -1;
313 for (std::vector<TemplateURL*>::const_iterator iter = search_engines.begin();
314 iter != search_engines.end(); ++iter) {
315 if (default_se_name == UTF16ToUTF8((*iter)->short_name())) {
316 default_se_index = static_cast<int>(iter - search_engines.begin());
317 break;
318 }
319 }
320 if (default_se_index == -1) {
321 LOG(WARNING) <<
322 "Firefox default search engine not found in search engine list";
323 }
324
325 return default_se_index;
326 }
327
GetHomepage(const FilePath & profile_path)328 GURL GetHomepage(const FilePath& profile_path) {
329 std::string home_page_list =
330 ReadPrefsJsValue(profile_path, "browser.startup.homepage");
331
332 size_t seperator = home_page_list.find_first_of('|');
333 if (seperator == std::string::npos)
334 return GURL(home_page_list);
335
336 return GURL(home_page_list.substr(0, seperator));
337 }
338
IsDefaultHomepage(const GURL & homepage,const FilePath & app_path)339 bool IsDefaultHomepage(const GURL& homepage, const FilePath& app_path) {
340 if (!homepage.is_valid())
341 return false;
342
343 std::string default_homepages =
344 ReadBrowserConfigProp(app_path, "browser.startup.homepage");
345
346 size_t seperator = default_homepages.find_first_of('|');
347 if (seperator == std::string::npos)
348 return homepage.spec() == GURL(default_homepages).spec();
349
350 // Crack the string into separate homepage urls.
351 std::vector<std::string> urls;
352 base::SplitString(default_homepages, '|', &urls);
353
354 for (size_t i = 0; i < urls.size(); ++i) {
355 if (homepage.spec() == GURL(urls[i]).spec())
356 return true;
357 }
358
359 return false;
360 }
361
ParsePrefFile(const FilePath & pref_file,DictionaryValue * prefs)362 bool ParsePrefFile(const FilePath& pref_file, DictionaryValue* prefs) {
363 // The string that is before a pref key.
364 const std::string kUserPrefString = "user_pref(\"";
365 std::string contents;
366 if (!file_util::ReadFileToString(pref_file, &contents))
367 return false;
368
369 std::vector<std::string> lines;
370 Tokenize(contents, "\n", &lines);
371
372 for (std::vector<std::string>::const_iterator iter = lines.begin();
373 iter != lines.end(); ++iter) {
374 const std::string& line = *iter;
375 size_t start_key = line.find(kUserPrefString);
376 if (start_key == std::string::npos)
377 continue; // Could be a comment or a blank line.
378 start_key += kUserPrefString.length();
379 size_t stop_key = line.find('"', start_key);
380 if (stop_key == std::string::npos) {
381 LOG(ERROR) << "Invalid key found in Firefox pref file '" <<
382 pref_file.value() << "' line is '" << line << "'.";
383 continue;
384 }
385 std::string key = line.substr(start_key, stop_key - start_key);
386 size_t start_value = line.find(',', stop_key + 1);
387 if (start_value == std::string::npos) {
388 LOG(ERROR) << "Invalid value found in Firefox pref file '" <<
389 pref_file.value() << "' line is '" << line << "'.";
390 continue;
391 }
392 size_t stop_value = line.find(");", start_value + 1);
393 if (stop_value == std::string::npos) {
394 LOG(ERROR) << "Invalid value found in Firefox pref file '" <<
395 pref_file.value() << "' line is '" << line << "'.";
396 continue;
397 }
398 std::string value = line.substr(start_value + 1,
399 stop_value - start_value - 1);
400 TrimWhitespace(value, TRIM_ALL, &value);
401 // Value could be a boolean.
402 bool is_value_true = LowerCaseEqualsASCII(value, "true");
403 if (is_value_true || LowerCaseEqualsASCII(value, "false")) {
404 prefs->SetBoolean(key, is_value_true);
405 continue;
406 }
407
408 // Value could be a string.
409 if (value.size() >= 2U &&
410 value[0] == '"' && value[value.size() - 1] == '"') {
411 value = value.substr(1, value.size() - 2);
412 // ValueString only accept valid UTF-8. Simply ignore that entry if it is
413 // not UTF-8.
414 if (IsStringUTF8(value))
415 prefs->SetString(key, value);
416 else
417 VLOG(1) << "Non UTF8 value for key " << key << ", ignored.";
418 continue;
419 }
420
421 // Or value could be an integer.
422 int int_value = 0;
423 if (base::StringToInt(value, &int_value)) {
424 prefs->SetInteger(key, int_value);
425 continue;
426 }
427
428 LOG(ERROR) << "Invalid value found in Firefox pref file '"
429 << pref_file.value() << "' value is '" << value << "'.";
430 }
431 return true;
432 }
433
GetPrefsJsValue(const std::string & content,const std::string & pref_key)434 std::string GetPrefsJsValue(const std::string& content,
435 const std::string& pref_key) {
436 // This file has the syntax: user_pref("key", value);
437 std::string search_for = std::string("user_pref(\"") + pref_key +
438 std::string("\", ");
439 size_t prop_index = content.find(search_for);
440 if (prop_index == std::string::npos)
441 return std::string();
442
443 size_t start = prop_index + search_for.length();
444 size_t stop = std::string::npos;
445 if (start != std::string::npos) {
446 // Stop at the last ')' on this line.
447 stop = content.find("\n", start + 1);
448 stop = content.rfind(")", stop);
449 }
450
451 if (start == std::string::npos || stop == std::string::npos ||
452 stop < start) {
453 LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
454 return "";
455 }
456
457 // String values have double quotes we don't need to return to the caller.
458 if (content[start] == '\"' && content[stop - 1] == '\"') {
459 ++start;
460 --stop;
461 }
462
463 return content.substr(start, stop - start);
464 }
465