Home | History | Annotate | Download | only in importer
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "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:
     30   FirefoxURLParameterFilter() {}
     31   virtual ~FirefoxURLParameterFilter() {}
     32 
     33   // TemplateURLParser::ParameterFilter method.
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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