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