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