• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(), &param_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