1 // Copyright 2013 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 "extensions/common/file_util.h" 6 7 #include <map> 8 #include <set> 9 #include <string> 10 #include <utility> 11 #include <vector> 12 13 #include "base/files/file_enumerator.h" 14 #include "base/files/file_path.h" 15 #include "base/files/file_util.h" 16 #include "base/files/scoped_temp_dir.h" 17 #include "base/json/json_file_value_serializer.h" 18 #include "base/logging.h" 19 #include "base/memory/scoped_ptr.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "base/threading/thread_restrictions.h" 23 #include "extensions/common/constants.h" 24 #include "extensions/common/extension.h" 25 #include "extensions/common/extension_icon_set.h" 26 #include "extensions/common/extension_l10n_util.h" 27 #include "extensions/common/install_warning.h" 28 #include "extensions/common/manifest.h" 29 #include "extensions/common/manifest_constants.h" 30 #include "extensions/common/manifest_handler.h" 31 #include "extensions/common/manifest_handlers/icons_handler.h" 32 #include "extensions/common/message_bundle.h" 33 #include "grit/extensions_strings.h" 34 #include "net/base/escape.h" 35 #include "ui/base/l10n/l10n_util.h" 36 #include "url/gurl.h" 37 38 namespace extensions { 39 namespace file_util { 40 namespace { 41 42 // Returns true if the given file path exists and is not zero-length. 43 bool ValidateFilePath(const base::FilePath& path) { 44 int64 size = 0; 45 if (!base::PathExists(path) || 46 !base::GetFileSize(path, &size) || 47 size == 0) { 48 return false; 49 } 50 51 return true; 52 } 53 54 } // namespace 55 56 const base::FilePath::CharType kTempDirectoryName[] = FILE_PATH_LITERAL("Temp"); 57 58 base::FilePath InstallExtension(const base::FilePath& unpacked_source_dir, 59 const std::string& id, 60 const std::string& version, 61 const base::FilePath& extensions_dir) { 62 base::FilePath extension_dir = extensions_dir.AppendASCII(id); 63 base::FilePath version_dir; 64 65 // Create the extension directory if it doesn't exist already. 66 if (!base::PathExists(extension_dir)) { 67 if (!base::CreateDirectory(extension_dir)) 68 return base::FilePath(); 69 } 70 71 // Get a temp directory on the same file system as the profile. 72 base::FilePath install_temp_dir = GetInstallTempDir(extensions_dir); 73 base::ScopedTempDir extension_temp_dir; 74 if (install_temp_dir.empty() || 75 !extension_temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) { 76 LOG(ERROR) << "Creating of temp dir under in the profile failed."; 77 return base::FilePath(); 78 } 79 base::FilePath crx_temp_source = 80 extension_temp_dir.path().Append(unpacked_source_dir.BaseName()); 81 if (!base::Move(unpacked_source_dir, crx_temp_source)) { 82 LOG(ERROR) << "Moving extension from : " << unpacked_source_dir.value() 83 << " to : " << crx_temp_source.value() << " failed."; 84 return base::FilePath(); 85 } 86 87 // Try to find a free directory. There can be legitimate conflicts in the case 88 // of overinstallation of the same version. 89 const int kMaxAttempts = 100; 90 for (int i = 0; i < kMaxAttempts; ++i) { 91 base::FilePath candidate = extension_dir.AppendASCII( 92 base::StringPrintf("%s_%u", version.c_str(), i)); 93 if (!base::PathExists(candidate)) { 94 version_dir = candidate; 95 break; 96 } 97 } 98 99 if (version_dir.empty()) { 100 LOG(ERROR) << "Could not find a home for extension " << id << " with " 101 << "version " << version << "."; 102 return base::FilePath(); 103 } 104 105 if (!base::Move(crx_temp_source, version_dir)) { 106 LOG(ERROR) << "Installing extension from : " << crx_temp_source.value() 107 << " into : " << version_dir.value() << " failed."; 108 return base::FilePath(); 109 } 110 111 return version_dir; 112 } 113 114 void UninstallExtension(const base::FilePath& extensions_dir, 115 const std::string& id) { 116 // We don't care about the return value. If this fails (and it can, due to 117 // plugins that aren't unloaded yet), it will get cleaned up by 118 // ExtensionGarbageCollector::GarbageCollectExtensions. 119 base::DeleteFile(extensions_dir.AppendASCII(id), true); // recursive. 120 } 121 122 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path, 123 Manifest::Location location, 124 int flags, 125 std::string* error) { 126 return LoadExtension(extension_path, std::string(), location, flags, error); 127 } 128 129 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path, 130 const std::string& extension_id, 131 Manifest::Location location, 132 int flags, 133 std::string* error) { 134 scoped_ptr<base::DictionaryValue> manifest( 135 LoadManifest(extension_path, error)); 136 if (!manifest.get()) 137 return NULL; 138 if (!extension_l10n_util::LocalizeExtension( 139 extension_path, manifest.get(), error)) { 140 return NULL; 141 } 142 143 scoped_refptr<Extension> extension(Extension::Create( 144 extension_path, location, *manifest, flags, extension_id, error)); 145 if (!extension.get()) 146 return NULL; 147 148 std::vector<InstallWarning> warnings; 149 if (!ValidateExtension(extension.get(), error, &warnings)) 150 return NULL; 151 extension->AddInstallWarnings(warnings); 152 153 return extension; 154 } 155 156 base::DictionaryValue* LoadManifest(const base::FilePath& extension_path, 157 std::string* error) { 158 return LoadManifest(extension_path, kManifestFilename, error); 159 } 160 161 base::DictionaryValue* LoadManifest( 162 const base::FilePath& extension_path, 163 const base::FilePath::CharType* manifest_filename, 164 std::string* error) { 165 base::FilePath manifest_path = extension_path.Append(manifest_filename); 166 if (!base::PathExists(manifest_path)) { 167 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); 168 return NULL; 169 } 170 171 JSONFileValueSerializer serializer(manifest_path); 172 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, error)); 173 if (!root.get()) { 174 if (error->empty()) { 175 // If |error| is empty, than the file could not be read. 176 // It would be cleaner to have the JSON reader give a specific error 177 // in this case, but other code tests for a file error with 178 // error->empty(). For now, be consistent. 179 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); 180 } else { 181 *error = base::StringPrintf( 182 "%s %s", manifest_errors::kManifestParseError, error->c_str()); 183 } 184 return NULL; 185 } 186 187 if (!root->IsType(base::Value::TYPE_DICTIONARY)) { 188 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID); 189 return NULL; 190 } 191 192 return static_cast<base::DictionaryValue*>(root.release()); 193 } 194 195 bool ValidateExtension(const Extension* extension, 196 std::string* error, 197 std::vector<InstallWarning>* warnings) { 198 // Ask registered manifest handlers to validate their paths. 199 if (!ManifestHandler::ValidateExtension(extension, error, warnings)) 200 return false; 201 202 // Check children of extension root to see if any of them start with _ and is 203 // not on the reserved list. We only warn, and do not block the loading of the 204 // extension. 205 std::string warning; 206 if (!CheckForIllegalFilenames(extension->path(), &warning)) 207 warnings->push_back(InstallWarning(warning)); 208 209 // Check that extensions don't include private key files. 210 std::vector<base::FilePath> private_keys = 211 FindPrivateKeyFiles(extension->path()); 212 if (extension->creation_flags() & Extension::ERROR_ON_PRIVATE_KEY) { 213 if (!private_keys.empty()) { 214 // Only print one of the private keys because l10n_util doesn't have a way 215 // to translate a list of strings. 216 *error = 217 l10n_util::GetStringFUTF8(IDS_EXTENSION_CONTAINS_PRIVATE_KEY, 218 private_keys.front().LossyDisplayName()); 219 return false; 220 } 221 } else { 222 for (size_t i = 0; i < private_keys.size(); ++i) { 223 warnings->push_back(InstallWarning( 224 l10n_util::GetStringFUTF8(IDS_EXTENSION_CONTAINS_PRIVATE_KEY, 225 private_keys[i].LossyDisplayName()))); 226 } 227 // Only warn; don't block loading the extension. 228 } 229 return true; 230 } 231 232 std::vector<base::FilePath> FindPrivateKeyFiles( 233 const base::FilePath& extension_dir) { 234 std::vector<base::FilePath> result; 235 // Pattern matching only works at the root level, so filter manually. 236 base::FileEnumerator traversal( 237 extension_dir, /*recursive=*/true, base::FileEnumerator::FILES); 238 for (base::FilePath current = traversal.Next(); !current.empty(); 239 current = traversal.Next()) { 240 if (!current.MatchesExtension(kExtensionKeyFileExtension)) 241 continue; 242 243 std::string key_contents; 244 if (!base::ReadFileToString(current, &key_contents)) { 245 // If we can't read the file, assume it's not a private key. 246 continue; 247 } 248 std::string key_bytes; 249 if (!Extension::ParsePEMKeyBytes(key_contents, &key_bytes)) { 250 // If we can't parse the key, assume it's ok too. 251 continue; 252 } 253 254 result.push_back(current); 255 } 256 return result; 257 } 258 259 bool CheckForIllegalFilenames(const base::FilePath& extension_path, 260 std::string* error) { 261 // Reserved underscore names. 262 static const base::FilePath::CharType* reserved_names[] = { 263 kLocaleFolder, kPlatformSpecificFolder, FILE_PATH_LITERAL("__MACOSX"), }; 264 CR_DEFINE_STATIC_LOCAL( 265 std::set<base::FilePath::StringType>, 266 reserved_underscore_names, 267 (reserved_names, reserved_names + arraysize(reserved_names))); 268 269 // Enumerate all files and directories in the extension root. 270 // There is a problem when using pattern "_*" with FileEnumerator, so we have 271 // to cheat with find_first_of and match all. 272 const int kFilesAndDirectories = 273 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES; 274 base::FileEnumerator all_files(extension_path, false, kFilesAndDirectories); 275 276 base::FilePath file; 277 while (!(file = all_files.Next()).empty()) { 278 base::FilePath::StringType filename = file.BaseName().value(); 279 // Skip all that don't start with "_". 280 if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) 281 continue; 282 if (reserved_underscore_names.find(filename) == 283 reserved_underscore_names.end()) { 284 *error = base::StringPrintf( 285 "Cannot load extension with file or directory name %s. " 286 "Filenames starting with \"_\" are reserved for use by the system.", 287 file.BaseName().AsUTF8Unsafe().c_str()); 288 return false; 289 } 290 } 291 292 return true; 293 } 294 295 base::FilePath GetInstallTempDir(const base::FilePath& extensions_dir) { 296 // We do file IO in this function, but only when the current profile's 297 // Temp directory has never been used before, or in a rare error case. 298 // Developers are not likely to see these situations often, so do an 299 // explicit thread check. 300 base::ThreadRestrictions::AssertIOAllowed(); 301 302 // Create the temp directory as a sub-directory of the Extensions directory. 303 // This guarantees it is on the same file system as the extension's eventual 304 // install target. 305 base::FilePath temp_path = extensions_dir.Append(kTempDirectoryName); 306 if (base::PathExists(temp_path)) { 307 if (!base::DirectoryExists(temp_path)) { 308 DLOG(WARNING) << "Not a directory: " << temp_path.value(); 309 return base::FilePath(); 310 } 311 if (!base::PathIsWritable(temp_path)) { 312 DLOG(WARNING) << "Can't write to path: " << temp_path.value(); 313 return base::FilePath(); 314 } 315 // This is a directory we can write to. 316 return temp_path; 317 } 318 319 // Directory doesn't exist, so create it. 320 if (!base::CreateDirectory(temp_path)) { 321 DLOG(WARNING) << "Couldn't create directory: " << temp_path.value(); 322 return base::FilePath(); 323 } 324 return temp_path; 325 } 326 327 void DeleteFile(const base::FilePath& path, bool recursive) { 328 base::DeleteFile(path, recursive); 329 } 330 331 base::FilePath ExtensionURLToRelativeFilePath(const GURL& url) { 332 std::string url_path = url.path(); 333 if (url_path.empty() || url_path[0] != '/') 334 return base::FilePath(); 335 336 // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8. 337 std::string file_path = net::UnescapeURLComponent(url_path, 338 net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS); 339 size_t skip = file_path.find_first_not_of("/\\"); 340 if (skip != file_path.npos) 341 file_path = file_path.substr(skip); 342 343 base::FilePath path = base::FilePath::FromUTF8Unsafe(file_path); 344 345 // It's still possible for someone to construct an annoying URL whose path 346 // would still wind up not being considered relative at this point. 347 // For example: chrome-extension://id/c:////foo.html 348 if (path.IsAbsolute()) 349 return base::FilePath(); 350 351 return path; 352 } 353 354 base::FilePath ExtensionResourceURLToFilePath(const GURL& url, 355 const base::FilePath& root) { 356 std::string host = net::UnescapeURLComponent(url.host(), 357 net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS); 358 if (host.empty()) 359 return base::FilePath(); 360 361 base::FilePath relative_path = ExtensionURLToRelativeFilePath(url); 362 if (relative_path.empty()) 363 return base::FilePath(); 364 365 base::FilePath path = root.AppendASCII(host).Append(relative_path); 366 if (!base::PathExists(path)) 367 return base::FilePath(); 368 path = base::MakeAbsoluteFilePath(path); 369 if (path.empty() || !root.IsParent(path)) 370 return base::FilePath(); 371 return path; 372 } 373 374 bool ValidateExtensionIconSet(const ExtensionIconSet& icon_set, 375 const Extension* extension, 376 int error_message_id, 377 std::string* error) { 378 for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin(); 379 iter != icon_set.map().end(); 380 ++iter) { 381 const base::FilePath path = 382 extension->GetResource(iter->second).GetFilePath(); 383 if (!ValidateFilePath(path)) { 384 *error = l10n_util::GetStringFUTF8(error_message_id, 385 base::UTF8ToUTF16(iter->second)); 386 return false; 387 } 388 } 389 return true; 390 } 391 392 MessageBundle* LoadMessageBundle( 393 const base::FilePath& extension_path, 394 const std::string& default_locale, 395 std::string* error) { 396 error->clear(); 397 // Load locale information if available. 398 base::FilePath locale_path = extension_path.Append(kLocaleFolder); 399 if (!base::PathExists(locale_path)) 400 return NULL; 401 402 std::set<std::string> locales; 403 if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error)) 404 return NULL; 405 406 if (default_locale.empty() || locales.find(default_locale) == locales.end()) { 407 *error = l10n_util::GetStringUTF8( 408 IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED); 409 return NULL; 410 } 411 412 MessageBundle* message_bundle = 413 extension_l10n_util::LoadMessageCatalogs( 414 locale_path, 415 default_locale, 416 extension_l10n_util::CurrentLocaleOrDefault(), 417 locales, 418 error); 419 420 return message_bundle; 421 } 422 423 std::map<std::string, std::string>* LoadMessageBundleSubstitutionMap( 424 const base::FilePath& extension_path, 425 const std::string& extension_id, 426 const std::string& default_locale) { 427 std::map<std::string, std::string>* return_value = 428 new std::map<std::string, std::string>(); 429 if (!default_locale.empty()) { 430 // Touch disk only if extension is localized. 431 std::string error; 432 scoped_ptr<MessageBundle> bundle( 433 LoadMessageBundle(extension_path, default_locale, &error)); 434 435 if (bundle.get()) 436 *return_value = *bundle->dictionary(); 437 } 438 439 // Add @@extension_id reserved message here, so it's available to 440 // non-localized extensions too. 441 return_value->insert( 442 std::make_pair(MessageBundle::kExtensionIdKey, extension_id)); 443 444 return return_value; 445 } 446 447 base::FilePath GetVerifiedContentsPath(const base::FilePath& extension_path) { 448 return extension_path.Append(kMetadataFolder) 449 .Append(kVerifiedContentsFilename); 450 } 451 base::FilePath GetComputedHashesPath(const base::FilePath& extension_path) { 452 return extension_path.Append(kMetadataFolder).Append(kComputedHashesFilename); 453 } 454 455 } // namespace file_util 456 } // namespace extensions 457