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