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(), ¶m_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