Home | History | Annotate | Download | only in importer
      1 // Copyright (c) 2012 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/common/importer/firefox_importer_utils.h"
      6 
      7 #include <algorithm>
      8 #include <map>
      9 #include <string>
     10 
     11 #include "base/files/file_util.h"
     12 #include "base/logging.h"
     13 #include "base/strings/string_number_conversions.h"
     14 #include "base/strings/string_split.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/strings/stringprintf.h"
     17 #include "base/strings/utf_string_conversions.h"
     18 #include "base/values.h"
     19 #include "chrome/common/ini_parser.h"
     20 #include "chrome/grit/generated_resources.h"
     21 #include "ui/base/l10n/l10n_util.h"
     22 #include "url/gurl.h"
     23 
     24 namespace {
     25 
     26 // Retrieves the file system path of the profile name.
     27 base::FilePath GetProfilePath(const base::DictionaryValue& root,
     28                               const std::string& profile_name) {
     29   base::string16 path16;
     30   std::string is_relative;
     31   if (!root.GetStringASCII(profile_name + ".IsRelative", &is_relative) ||
     32       !root.GetString(profile_name + ".Path", &path16))
     33     return base::FilePath();
     34 
     35 #if defined(OS_WIN)
     36   ReplaceSubstringsAfterOffset(
     37       &path16, 0, base::ASCIIToUTF16("/"), base::ASCIIToUTF16("\\"));
     38 #endif
     39   base::FilePath path = base::FilePath::FromUTF16Unsafe(path16);
     40 
     41   // IsRelative=1 means the folder path would be relative to the
     42   // path of profiles.ini. IsRelative=0 refers to a custom profile
     43   // location.
     44   if (is_relative == "1")
     45     path = GetProfilesINI().DirName().Append(path);
     46 
     47   return path;
     48 }
     49 
     50 // Checks if the named profile is the default profile.
     51 bool IsDefaultProfile(const base::DictionaryValue& root,
     52                       const std::string& profile_name) {
     53   std::string is_default;
     54   root.GetStringASCII(profile_name + ".Default", &is_default);
     55   return is_default == "1";
     56 }
     57 
     58 } // namespace
     59 
     60 base::FilePath GetFirefoxProfilePath() {
     61   base::FilePath ini_file = GetProfilesINI();
     62   std::string content;
     63   base::ReadFileToString(ini_file, &content);
     64   DictionaryValueINIParser ini_parser;
     65   ini_parser.Parse(content);
     66   return GetFirefoxProfilePathFromDictionary(ini_parser.root());
     67 }
     68 
     69 base::FilePath GetFirefoxProfilePathFromDictionary(
     70     const base::DictionaryValue& root) {
     71   std::vector<std::string> profiles;
     72   for (int i = 0; ; ++i) {
     73     std::string current_profile = base::StringPrintf("Profile%d", i);
     74     if (root.HasKey(current_profile)) {
     75       profiles.push_back(current_profile);
     76     } else {
     77       // Profiles are continuously numbered. So we exit when we can't
     78       // find the i-th one.
     79       break;
     80     }
     81   }
     82 
     83   if (profiles.empty())
     84     return base::FilePath();
     85 
     86   // When multiple profiles exist, the path to the default profile is returned,
     87   // since the other profiles are used mostly by developers for testing.
     88   for (std::vector<std::string>::const_iterator it = profiles.begin();
     89        it != profiles.end(); ++it)
     90     if (IsDefaultProfile(root, *it))
     91       return GetProfilePath(root, *it);
     92 
     93   // If no default profile is found, the path to Profile0 will be returned.
     94   return GetProfilePath(root, profiles.front());
     95 }
     96 
     97 #if defined(OS_MACOSX)
     98 // Find the "*.app" component of the path and build up from there.
     99 // The resulting path will be .../Firefox.app/Contents/MacOS.
    100 // We do this because we don't trust LastAppDir to always be
    101 // this particular path, without any subdirs, and we want to make
    102 // our assumption about Firefox's root being in that path explicit.
    103 bool ComposeMacAppPath(const std::string& path_from_file,
    104                        base::FilePath* output) {
    105   base::FilePath path(path_from_file);
    106   typedef std::vector<base::FilePath::StringType> ComponentVector;
    107   ComponentVector path_components;
    108   path.GetComponents(&path_components);
    109   if (path_components.empty())
    110     return false;
    111   // The first path component is special because it may be absolute. Calling
    112   // Append with an absolute path component will trigger an assert, so we
    113   // must handle it differently and initialize output with it.
    114   *output = base::FilePath(path_components[0]);
    115   // Append next path components untill we find the *.app component. When we do,
    116   // append Contents/MacOS.
    117   for (size_t i = 1; i < path_components.size(); ++i) {
    118     *output = output->Append(path_components[i]);
    119     if (EndsWith(path_components[i], ".app", true)) {
    120       *output = output->Append("Contents");
    121       *output = output->Append("MacOS");
    122       return true;
    123     }
    124   }
    125   LOG(ERROR) << path_from_file << " doesn't look like a valid Firefox "
    126              << "installation path: missing /*.app/ directory.";
    127   return false;
    128 }
    129 #endif  // OS_MACOSX
    130 
    131 bool GetFirefoxVersionAndPathFromProfile(const base::FilePath& profile_path,
    132                                          int* version,
    133                                          base::FilePath* app_path) {
    134   bool ret = false;
    135   base::FilePath compatibility_file =
    136       profile_path.AppendASCII("compatibility.ini");
    137   std::string content;
    138   base::ReadFileToString(compatibility_file, &content);
    139   ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n");
    140   std::vector<std::string> lines;
    141   base::SplitString(content, '\n', &lines);
    142 
    143   for (size_t i = 0; i < lines.size(); ++i) {
    144     const std::string& line = lines[i];
    145     if (line.empty() || line[0] == '#' || line[0] == ';')
    146       continue;
    147     size_t equal = line.find('=');
    148     if (equal != std::string::npos) {
    149       std::string key = line.substr(0, equal);
    150       if (key == "LastVersion") {
    151         base::StringToInt(line.substr(equal + 1), version);
    152         ret = true;
    153       } else if (key == "LastAppDir") {
    154         // TODO(evanm): If the path in question isn't convertible to
    155         // UTF-8, what does Firefox do?  If it puts raw bytes in the
    156         // file, we could go straight from bytes -> filepath;
    157         // otherwise, we're out of luck here.
    158 #if defined(OS_MACOSX)
    159         // Extract path from "LastAppDir=/actual/path"
    160         size_t separator_pos = line.find_first_of('=');
    161         const std::string& path_from_ini = line.substr(separator_pos + 1);
    162         if (!ComposeMacAppPath(path_from_ini, app_path))
    163           return false;
    164 #else  // !OS_MACOSX
    165         *app_path = base::FilePath::FromUTF8Unsafe(line.substr(equal + 1));
    166 #endif  // OS_MACOSX
    167       }
    168     }
    169   }
    170   return ret;
    171 }
    172 
    173 bool ReadPrefFile(const base::FilePath& path, std::string* content) {
    174   if (content == NULL)
    175     return false;
    176 
    177   base::ReadFileToString(path, content);
    178 
    179   if (content->empty()) {
    180     LOG(WARNING) << "Firefox preference file " << path.value() << " is empty.";
    181     return false;
    182   }
    183 
    184   return true;
    185 }
    186 
    187 std::string ReadBrowserConfigProp(const base::FilePath& app_path,
    188                                   const std::string& pref_key) {
    189   std::string content;
    190   if (!ReadPrefFile(app_path.AppendASCII("browserconfig.properties"), &content))
    191     return std::string();
    192 
    193   // This file has the syntax: key=value.
    194   size_t prop_index = content.find(pref_key + "=");
    195   if (prop_index == std::string::npos)
    196     return std::string();
    197 
    198   size_t start = prop_index + pref_key.length();
    199   size_t stop = std::string::npos;
    200   if (start != std::string::npos)
    201     stop = content.find("\n", start + 1);
    202 
    203   if (start == std::string::npos ||
    204       stop == std::string::npos || (start == stop)) {
    205     LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
    206     return std::string();
    207   }
    208 
    209   return content.substr(start + 1, stop - start - 1);
    210 }
    211 
    212 std::string ReadPrefsJsValue(const base::FilePath& profile_path,
    213                              const std::string& pref_key) {
    214   std::string content;
    215   if (!ReadPrefFile(profile_path.AppendASCII("prefs.js"), &content))
    216     return std::string();
    217 
    218   return GetPrefsJsValue(content, pref_key);
    219 }
    220 
    221 GURL GetHomepage(const base::FilePath& profile_path) {
    222   std::string home_page_list =
    223       ReadPrefsJsValue(profile_path, "browser.startup.homepage");
    224 
    225   size_t seperator = home_page_list.find_first_of('|');
    226   if (seperator == std::string::npos)
    227     return GURL(home_page_list);
    228 
    229   return GURL(home_page_list.substr(0, seperator));
    230 }
    231 
    232 bool IsDefaultHomepage(const GURL& homepage, const base::FilePath& app_path) {
    233   if (!homepage.is_valid())
    234     return false;
    235 
    236   std::string default_homepages =
    237       ReadBrowserConfigProp(app_path, "browser.startup.homepage");
    238 
    239   size_t seperator = default_homepages.find_first_of('|');
    240   if (seperator == std::string::npos)
    241     return homepage.spec() == GURL(default_homepages).spec();
    242 
    243   // Crack the string into separate homepage urls.
    244   std::vector<std::string> urls;
    245   base::SplitString(default_homepages, '|', &urls);
    246 
    247   for (size_t i = 0; i < urls.size(); ++i) {
    248     if (homepage.spec() == GURL(urls[i]).spec())
    249       return true;
    250   }
    251 
    252   return false;
    253 }
    254 
    255 std::string GetPrefsJsValue(const std::string& content,
    256                             const std::string& pref_key) {
    257   // This file has the syntax: user_pref("key", value);
    258   std::string search_for = std::string("user_pref(\"") + pref_key +
    259                            std::string("\", ");
    260   size_t prop_index = content.find(search_for);
    261   if (prop_index == std::string::npos)
    262     return std::string();
    263 
    264   size_t start = prop_index + search_for.length();
    265   size_t stop = std::string::npos;
    266   if (start != std::string::npos) {
    267     // Stop at the last ')' on this line.
    268     stop = content.find("\n", start + 1);
    269     stop = content.rfind(")", stop);
    270   }
    271 
    272   if (start == std::string::npos || stop == std::string::npos ||
    273       stop < start) {
    274     LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
    275     return std::string();
    276   }
    277 
    278   // String values have double quotes we don't need to return to the caller.
    279   if (content[start] == '\"' && content[stop - 1] == '\"') {
    280     ++start;
    281     --stop;
    282   }
    283 
    284   return content.substr(start, stop - start);
    285 }
    286 
    287 // The branding name is obtained from the application.ini file from the Firefox
    288 // application directory. A sample application.ini file is the following:
    289 //   [App]
    290 //   Vendor=Mozilla
    291 //   Name=Iceweasel
    292 //   Profile=mozilla/firefox
    293 //   Version=3.5.16
    294 //   BuildID=20120421070307
    295 //   Copyright=Copyright (c) 1998 - 2010 mozilla.org
    296 //   ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
    297 //   .........................................
    298 // In this example the function returns "Iceweasel" (or a localized equivalent).
    299 base::string16 GetFirefoxImporterName(const base::FilePath& app_path) {
    300   const base::FilePath app_ini_file = app_path.AppendASCII("application.ini");
    301   std::string branding_name;
    302   if (base::PathExists(app_ini_file)) {
    303     std::string content;
    304     base::ReadFileToString(app_ini_file, &content);
    305     std::vector<std::string> lines;
    306     base::SplitString(content, '\n', &lines);
    307     const std::string name_attr("Name=");
    308     bool in_app_section = false;
    309     for (size_t i = 0; i < lines.size(); ++i) {
    310       base::TrimWhitespace(lines[i], base::TRIM_ALL, &lines[i]);
    311       if (lines[i] == "[App]") {
    312         in_app_section = true;
    313       } else if (in_app_section) {
    314         if (lines[i].find(name_attr) == 0) {
    315           branding_name = lines[i].substr(name_attr.size());
    316           break;
    317         } else if (lines[i].length() > 0 && lines[i][0] == '[') {
    318           // No longer in the [App] section.
    319           break;
    320         }
    321       }
    322     }
    323   }
    324 
    325   base::StringToLowerASCII(&branding_name);
    326   if (branding_name.find("iceweasel") != std::string::npos)
    327     return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_ICEWEASEL);
    328   return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_FIREFOX);
    329 }
    330