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/common/extensions/extension_file_util.h" 6 7 #include <map> 8 #include <vector> 9 10 #include "base/file_util.h" 11 #include "base/logging.h" 12 #include "base/memory/scoped_temp_dir.h" 13 #include "base/metrics/histogram.h" 14 #include "base/path_service.h" 15 #include "base/threading/thread_restrictions.h" 16 #include "base/utf_string_conversions.h" 17 #include "chrome/common/chrome_paths.h" 18 #include "chrome/common/extensions/extension.h" 19 #include "chrome/common/extensions/extension_action.h" 20 #include "chrome/common/extensions/extension_l10n_util.h" 21 #include "chrome/common/extensions/extension_constants.h" 22 #include "chrome/common/extensions/extension_resource.h" 23 #include "chrome/common/extensions/extension_sidebar_defaults.h" 24 #include "content/common/json_value_serializer.h" 25 #include "grit/generated_resources.h" 26 #include "net/base/escape.h" 27 #include "net/base/file_stream.h" 28 #include "ui/base/l10n/l10n_util.h" 29 30 namespace errors = extension_manifest_errors; 31 32 namespace extension_file_util { 33 34 // Validates locale info. Doesn't check if messages.json files are valid. 35 static bool ValidateLocaleInfo(const Extension& extension, std::string* error); 36 37 // Returns false and sets the error if script file can't be loaded, 38 // or if it's not UTF-8 encoded. 39 static bool IsScriptValid(const FilePath& path, const FilePath& relative_path, 40 int message_id, std::string* error); 41 42 const char kInstallDirectoryName[] = "Extensions"; 43 44 FilePath InstallExtension(const FilePath& unpacked_source_dir, 45 const std::string& id, 46 const std::string& version, 47 const FilePath& all_extensions_dir) { 48 FilePath extension_dir = all_extensions_dir.AppendASCII(id); 49 FilePath version_dir; 50 51 // Create the extension directory if it doesn't exist already. 52 if (!file_util::PathExists(extension_dir)) { 53 if (!file_util::CreateDirectory(extension_dir)) 54 return FilePath(); 55 } 56 57 // Try to find a free directory. There can be legitimate conflicts in the case 58 // of overinstallation of the same version. 59 const int kMaxAttempts = 100; 60 for (int i = 0; i < kMaxAttempts; ++i) { 61 FilePath candidate = extension_dir.AppendASCII( 62 base::StringPrintf("%s_%u", version.c_str(), i)); 63 if (!file_util::PathExists(candidate)) { 64 version_dir = candidate; 65 break; 66 } 67 } 68 69 if (version_dir.empty()) { 70 LOG(ERROR) << "Could not find a home for extension " << id << " with " 71 << "version " << version << "."; 72 return FilePath(); 73 } 74 75 if (!file_util::Move(unpacked_source_dir, version_dir)) 76 return FilePath(); 77 78 return version_dir; 79 } 80 81 void UninstallExtension(const FilePath& extensions_dir, 82 const std::string& id) { 83 // We don't care about the return value. If this fails (and it can, due to 84 // plugins that aren't unloaded yet, it will get cleaned up by 85 // ExtensionService::GarbageCollectExtensions). 86 file_util::Delete(extensions_dir.AppendASCII(id), true); // recursive. 87 } 88 89 scoped_refptr<Extension> LoadExtension(const FilePath& extension_path, 90 Extension::Location location, 91 int flags, 92 std::string* error) { 93 FilePath manifest_path = 94 extension_path.Append(Extension::kManifestFilename); 95 if (!file_util::PathExists(manifest_path)) { 96 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); 97 return NULL; 98 } 99 100 JSONFileValueSerializer serializer(manifest_path); 101 scoped_ptr<Value> root(serializer.Deserialize(NULL, error)); 102 if (!root.get()) { 103 if (error->empty()) { 104 // If |error| is empty, than the file could not be read. 105 // It would be cleaner to have the JSON reader give a specific error 106 // in this case, but other code tests for a file error with 107 // error->empty(). For now, be consistent. 108 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); 109 } else { 110 *error = base::StringPrintf("%s %s", 111 errors::kManifestParseError, 112 error->c_str()); 113 } 114 return NULL; 115 } 116 117 if (!root->IsType(Value::TYPE_DICTIONARY)) { 118 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID); 119 return NULL; 120 } 121 122 DictionaryValue* manifest = static_cast<DictionaryValue*>(root.get()); 123 if (!extension_l10n_util::LocalizeExtension(extension_path, manifest, error)) 124 return NULL; 125 126 scoped_refptr<Extension> extension(Extension::Create( 127 extension_path, 128 location, 129 *manifest, 130 flags, 131 error)); 132 if (!extension.get()) 133 return NULL; 134 135 if (!ValidateExtension(extension.get(), error)) 136 return NULL; 137 138 return extension; 139 } 140 141 bool ValidateExtension(Extension* extension, std::string* error) { 142 // Validate icons exist. 143 for (ExtensionIconSet::IconMap::const_iterator iter = 144 extension->icons().map().begin(); 145 iter != extension->icons().map().end(); 146 ++iter) { 147 const FilePath path = extension->GetResource(iter->second).GetFilePath(); 148 if (!file_util::PathExists(path)) { 149 *error = 150 l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_ICON_FAILED, 151 UTF8ToUTF16(iter->second)); 152 return false; 153 } 154 } 155 156 // Theme resource validation. 157 if (extension->is_theme()) { 158 DictionaryValue* images_value = extension->GetThemeImages(); 159 if (images_value) { 160 for (DictionaryValue::key_iterator iter = images_value->begin_keys(); 161 iter != images_value->end_keys(); ++iter) { 162 std::string val; 163 if (images_value->GetStringWithoutPathExpansion(*iter, &val)) { 164 FilePath image_path = extension->path().AppendASCII(val); 165 if (!file_util::PathExists(image_path)) { 166 *error = 167 l10n_util::GetStringFUTF8(IDS_EXTENSION_INVALID_IMAGE_PATH, 168 image_path.LossyDisplayName()); 169 return false; 170 } 171 } 172 } 173 } 174 175 // Themes cannot contain other extension types. 176 return true; 177 } 178 179 // Validate that claimed script resources actually exist, 180 // and are UTF-8 encoded. 181 for (size_t i = 0; i < extension->content_scripts().size(); ++i) { 182 const UserScript& script = extension->content_scripts()[i]; 183 184 for (size_t j = 0; j < script.js_scripts().size(); j++) { 185 const UserScript::File& js_script = script.js_scripts()[j]; 186 const FilePath& path = ExtensionResource::GetFilePath( 187 js_script.extension_root(), js_script.relative_path()); 188 if (!IsScriptValid(path, js_script.relative_path(), 189 IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error)) 190 return false; 191 } 192 193 for (size_t j = 0; j < script.css_scripts().size(); j++) { 194 const UserScript::File& css_script = script.css_scripts()[j]; 195 const FilePath& path = ExtensionResource::GetFilePath( 196 css_script.extension_root(), css_script.relative_path()); 197 if (!IsScriptValid(path, css_script.relative_path(), 198 IDS_EXTENSION_LOAD_CSS_FAILED, error)) 199 return false; 200 } 201 } 202 203 // Validate claimed plugin paths. 204 for (size_t i = 0; i < extension->plugins().size(); ++i) { 205 const Extension::PluginInfo& plugin = extension->plugins()[i]; 206 if (!file_util::PathExists(plugin.path)) { 207 *error = 208 l10n_util::GetStringFUTF8( 209 IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED, 210 plugin.path.LossyDisplayName()); 211 return false; 212 } 213 } 214 215 // Validate icon location for page actions. 216 ExtensionAction* page_action = extension->page_action(); 217 if (page_action) { 218 std::vector<std::string> icon_paths(*page_action->icon_paths()); 219 if (!page_action->default_icon_path().empty()) 220 icon_paths.push_back(page_action->default_icon_path()); 221 for (std::vector<std::string>::iterator iter = icon_paths.begin(); 222 iter != icon_paths.end(); ++iter) { 223 if (!file_util::PathExists(extension->GetResource(*iter).GetFilePath())) { 224 *error = 225 l10n_util::GetStringFUTF8( 226 IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED, 227 UTF8ToUTF16(*iter)); 228 return false; 229 } 230 } 231 } 232 233 // Validate icon location for browser actions. 234 // Note: browser actions don't use the icon_paths(). 235 ExtensionAction* browser_action = extension->browser_action(); 236 if (browser_action) { 237 std::string path = browser_action->default_icon_path(); 238 if (!path.empty() && 239 !file_util::PathExists(extension->GetResource(path).GetFilePath())) { 240 *error = 241 l10n_util::GetStringFUTF8( 242 IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED, 243 UTF8ToUTF16(path)); 244 return false; 245 } 246 } 247 248 // Validate background page location, except for hosted apps, which should use 249 // an external URL. Background page for hosted apps are verified when the 250 // extension is created (in Extension::InitFromValue) 251 if (!extension->background_url().is_empty() && !extension->is_hosted_app()) { 252 FilePath page_path = ExtensionURLToRelativeFilePath( 253 extension->background_url()); 254 const FilePath path = extension->GetResource(page_path).GetFilePath(); 255 if (path.empty() || !file_util::PathExists(path)) { 256 *error = 257 l10n_util::GetStringFUTF8( 258 IDS_EXTENSION_LOAD_BACKGROUND_PAGE_FAILED, 259 page_path.LossyDisplayName()); 260 return false; 261 } 262 } 263 264 // Validate path to the options page. Don't check the URL for hosted apps, 265 // because they are expected to refer to an external URL. 266 if (!extension->options_url().is_empty() && !extension->is_hosted_app()) { 267 const FilePath options_path = ExtensionURLToRelativeFilePath( 268 extension->options_url()); 269 const FilePath path = extension->GetResource(options_path).GetFilePath(); 270 if (path.empty() || !file_util::PathExists(path)) { 271 *error = 272 l10n_util::GetStringFUTF8( 273 IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED, 274 options_path.LossyDisplayName()); 275 return false; 276 } 277 } 278 279 // Validate sidebar default page location. 280 ExtensionSidebarDefaults* sidebar_defaults = extension->sidebar_defaults(); 281 if (sidebar_defaults && sidebar_defaults->default_page().is_valid()) { 282 FilePath page_path = ExtensionURLToRelativeFilePath( 283 sidebar_defaults->default_page()); 284 const FilePath path = extension->GetResource(page_path).GetFilePath(); 285 if (path.empty() || !file_util::PathExists(path)) { 286 *error = 287 l10n_util::GetStringFUTF8( 288 IDS_EXTENSION_LOAD_SIDEBAR_PAGE_FAILED, 289 page_path.LossyDisplayName()); 290 return false; 291 } 292 } 293 294 // Validate locale info. 295 if (!ValidateLocaleInfo(*extension, error)) 296 return false; 297 298 // Check children of extension root to see if any of them start with _ and is 299 // not on the reserved list. 300 if (!CheckForIllegalFilenames(extension->path(), error)) { 301 return false; 302 } 303 304 return true; 305 } 306 307 void GarbageCollectExtensions( 308 const FilePath& install_directory, 309 const std::map<std::string, FilePath>& extension_paths) { 310 // Nothing to clean up if it doesn't exist. 311 if (!file_util::DirectoryExists(install_directory)) 312 return; 313 314 VLOG(1) << "Garbage collecting extensions..."; 315 file_util::FileEnumerator enumerator(install_directory, 316 false, // Not recursive. 317 file_util::FileEnumerator::DIRECTORIES); 318 FilePath extension_path; 319 for (extension_path = enumerator.Next(); !extension_path.value().empty(); 320 extension_path = enumerator.Next()) { 321 std::string extension_id; 322 323 FilePath basename = extension_path.BaseName(); 324 if (IsStringASCII(basename.value())) { 325 extension_id = UTF16ToASCII(basename.LossyDisplayName()); 326 if (!Extension::IdIsValid(extension_id)) 327 extension_id.clear(); 328 } 329 330 // Delete directories that aren't valid IDs. 331 if (extension_id.empty()) { 332 LOG(WARNING) << "Invalid extension ID encountered in extensions " 333 "directory: " << basename.value(); 334 VLOG(1) << "Deleting invalid extension directory " 335 << extension_path.value() << "."; 336 file_util::Delete(extension_path, true); // Recursive. 337 continue; 338 } 339 340 std::map<std::string, FilePath>::const_iterator iter = 341 extension_paths.find(extension_id); 342 343 // If there is no entry in the prefs file, just delete the directory and 344 // move on. This can legitimately happen when an uninstall does not 345 // complete, for example, when a plugin is in use at uninstall time. 346 if (iter == extension_paths.end()) { 347 VLOG(1) << "Deleting unreferenced install for directory " 348 << extension_path.LossyDisplayName() << "."; 349 file_util::Delete(extension_path, true); // Recursive. 350 continue; 351 } 352 353 // Clean up old version directories. 354 file_util::FileEnumerator versions_enumerator( 355 extension_path, 356 false, // Not recursive. 357 file_util::FileEnumerator::DIRECTORIES); 358 for (FilePath version_dir = versions_enumerator.Next(); 359 !version_dir.value().empty(); 360 version_dir = versions_enumerator.Next()) { 361 if (version_dir.BaseName() != iter->second.BaseName()) { 362 VLOG(1) << "Deleting old version for directory " 363 << version_dir.LossyDisplayName() << "."; 364 file_util::Delete(version_dir, true); // Recursive. 365 } 366 } 367 } 368 } 369 370 ExtensionMessageBundle* LoadExtensionMessageBundle( 371 const FilePath& extension_path, 372 const std::string& default_locale, 373 std::string* error) { 374 error->clear(); 375 // Load locale information if available. 376 FilePath locale_path = extension_path.Append(Extension::kLocaleFolder); 377 if (!file_util::PathExists(locale_path)) 378 return NULL; 379 380 std::set<std::string> locales; 381 if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error)) 382 return NULL; 383 384 if (default_locale.empty() || 385 locales.find(default_locale) == locales.end()) { 386 *error = l10n_util::GetStringUTF8( 387 IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED); 388 return NULL; 389 } 390 391 ExtensionMessageBundle* message_bundle = 392 extension_l10n_util::LoadMessageCatalogs( 393 locale_path, 394 default_locale, 395 extension_l10n_util::CurrentLocaleOrDefault(), 396 locales, 397 error); 398 399 return message_bundle; 400 } 401 402 static bool ValidateLocaleInfo(const Extension& extension, std::string* error) { 403 // default_locale and _locales have to be both present or both missing. 404 const FilePath path = extension.path().Append(Extension::kLocaleFolder); 405 bool path_exists = file_util::PathExists(path); 406 std::string default_locale = extension.default_locale(); 407 408 // If both default locale and _locales folder are empty, skip verification. 409 if (default_locale.empty() && !path_exists) 410 return true; 411 412 if (default_locale.empty() && path_exists) { 413 *error = l10n_util::GetStringUTF8( 414 IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED); 415 return false; 416 } else if (!default_locale.empty() && !path_exists) { 417 *error = errors::kLocalesTreeMissing; 418 return false; 419 } 420 421 // Treat all folders under _locales as valid locales. 422 file_util::FileEnumerator locales(path, 423 false, 424 file_util::FileEnumerator::DIRECTORIES); 425 426 std::set<std::string> all_locales; 427 extension_l10n_util::GetAllLocales(&all_locales); 428 const FilePath default_locale_path = path.AppendASCII(default_locale); 429 bool has_default_locale_message_file = false; 430 431 FilePath locale_path; 432 while (!(locale_path = locales.Next()).empty()) { 433 if (extension_l10n_util::ShouldSkipValidation(path, locale_path, 434 all_locales)) 435 continue; 436 437 FilePath messages_path = 438 locale_path.Append(Extension::kMessagesFilename); 439 440 if (!file_util::PathExists(messages_path)) { 441 *error = base::StringPrintf( 442 "%s %s", errors::kLocalesMessagesFileMissing, 443 UTF16ToUTF8(messages_path.LossyDisplayName()).c_str()); 444 return false; 445 } 446 447 if (locale_path == default_locale_path) 448 has_default_locale_message_file = true; 449 } 450 451 // Only message file for default locale has to exist. 452 if (!has_default_locale_message_file) { 453 *error = errors::kLocalesNoDefaultMessages; 454 return false; 455 } 456 457 return true; 458 } 459 460 static bool IsScriptValid(const FilePath& path, 461 const FilePath& relative_path, 462 int message_id, 463 std::string* error) { 464 std::string content; 465 if (!file_util::PathExists(path) || 466 !file_util::ReadFileToString(path, &content)) { 467 *error = l10n_util::GetStringFUTF8( 468 message_id, 469 relative_path.LossyDisplayName()); 470 return false; 471 } 472 473 if (!IsStringUTF8(content)) { 474 *error = l10n_util::GetStringFUTF8( 475 IDS_EXTENSION_BAD_FILE_ENCODING, 476 relative_path.LossyDisplayName()); 477 return false; 478 } 479 480 return true; 481 } 482 483 bool CheckForIllegalFilenames(const FilePath& extension_path, 484 std::string* error) { 485 // Reserved underscore names. 486 static const FilePath::CharType* reserved_names[] = { 487 Extension::kLocaleFolder, 488 FILE_PATH_LITERAL("__MACOSX"), 489 }; 490 static std::set<FilePath::StringType> reserved_underscore_names( 491 reserved_names, reserved_names + arraysize(reserved_names)); 492 493 // Enumerate all files and directories in the extension root. 494 // There is a problem when using pattern "_*" with FileEnumerator, so we have 495 // to cheat with find_first_of and match all. 496 file_util::FileEnumerator all_files( 497 extension_path, 498 false, 499 static_cast<file_util::FileEnumerator::FILE_TYPE>( 500 file_util::FileEnumerator::DIRECTORIES | 501 file_util::FileEnumerator::FILES)); 502 503 FilePath file; 504 while (!(file = all_files.Next()).empty()) { 505 FilePath::StringType filename = file.BaseName().value(); 506 // Skip all that don't start with "_". 507 if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue; 508 if (reserved_underscore_names.find(filename) == 509 reserved_underscore_names.end()) { 510 *error = base::StringPrintf( 511 "Cannot load extension with file or directory name %s. " 512 "Filenames starting with \"_\" are reserved for use by the system.", 513 filename.c_str()); 514 return false; 515 } 516 } 517 518 return true; 519 } 520 521 FilePath ExtensionURLToRelativeFilePath(const GURL& url) { 522 std::string url_path = url.path(); 523 if (url_path.empty() || url_path[0] != '/') 524 return FilePath(); 525 526 // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8. 527 std::string file_path = UnescapeURLComponent(url_path, 528 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); 529 size_t skip = file_path.find_first_not_of("/\\"); 530 if (skip != file_path.npos) 531 file_path = file_path.substr(skip); 532 533 FilePath path = 534 #if defined(OS_POSIX) 535 FilePath(file_path); 536 #elif defined(OS_WIN) 537 FilePath(UTF8ToWide(file_path)); 538 #else 539 FilePath(); 540 NOTIMPLEMENTED(); 541 #endif 542 543 // It's still possible for someone to construct an annoying URL whose path 544 // would still wind up not being considered relative at this point. 545 // For example: chrome-extension://id/c:////foo.html 546 if (path.IsAbsolute()) 547 return FilePath(); 548 549 return path; 550 } 551 552 FilePath GetUserDataTempDir() { 553 // We do file IO in this function, but only when the current profile's 554 // Temp directory has never been used before, or in a rare error case. 555 // Developers are not likely to see these situations often, so do an 556 // explicit thread check. 557 base::ThreadRestrictions::AssertIOAllowed(); 558 559 // Getting chrome::DIR_USER_DATA_TEMP is failing. Use histogram to see why. 560 // TODO(skerner): Fix the problem, and remove this code. crbug.com/70056 561 enum DirectoryCreationResult { 562 SUCCESS = 0, 563 564 CANT_GET_PARENT_PATH, 565 CANT_GET_UDT_PATH, 566 NOT_A_DIRECTORY, 567 CANT_CREATE_DIR, 568 CANT_WRITE_TO_PATH, 569 570 UNSET, 571 NUM_DIRECTORY_CREATION_RESULTS 572 }; 573 574 // All paths should set |result|. 575 DirectoryCreationResult result = UNSET; 576 577 FilePath temp_path; 578 if (!PathService::Get(chrome::DIR_USER_DATA_TEMP, &temp_path)) { 579 FilePath parent_path; 580 if (!PathService::Get(chrome::DIR_USER_DATA, &parent_path)) 581 result = CANT_GET_PARENT_PATH; 582 else 583 result = CANT_GET_UDT_PATH; 584 585 } else if (file_util::PathExists(temp_path)) { 586 587 // Path exists. Check that it is a directory we can write to. 588 if (!file_util::DirectoryExists(temp_path)) { 589 result = NOT_A_DIRECTORY; 590 591 } else if (!file_util::PathIsWritable(temp_path)) { 592 result = CANT_WRITE_TO_PATH; 593 594 } else { 595 // Temp is a writable directory. 596 result = SUCCESS; 597 } 598 599 } else if (!file_util::CreateDirectory(temp_path)) { 600 // Path doesn't exist, and we failed to create it. 601 result = CANT_CREATE_DIR; 602 603 } else { 604 // Successfully created the Temp directory. 605 result = SUCCESS; 606 } 607 608 UMA_HISTOGRAM_ENUMERATION("Extensions.GetUserDataTempDir", 609 result, 610 NUM_DIRECTORY_CREATION_RESULTS); 611 612 if (result == SUCCESS) 613 return temp_path; 614 615 return FilePath(); 616 } 617 618 void DeleteFile(const FilePath& path, bool recursive) { 619 file_util::Delete(path, recursive); 620 } 621 622 } // namespace extension_file_util 623