| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/common/extensions/extension_file_util.h" | 5 #include "chrome/common/extensions/extension_file_util.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include "base/files/file_path.h" |
| 8 #include <vector> | 8 #include "base/values.h" |
| 9 | |
| 10 #include "base/file_util.h" | |
| 11 #include "base/files/file_enumerator.h" | |
| 12 #include "base/files/scoped_temp_dir.h" | |
| 13 #include "base/json/json_file_value_serializer.h" | |
| 14 #include "base/logging.h" | |
| 15 #include "base/metrics/histogram.h" | |
| 16 #include "base/path_service.h" | |
| 17 #include "base/strings/stringprintf.h" | |
| 18 #include "base/threading/thread_restrictions.h" | |
| 19 #include "chrome/common/chrome_paths.h" | |
| 20 #include "chrome/common/extensions/api/extension_action/action_info.h" | 9 #include "chrome/common/extensions/api/extension_action/action_info.h" |
| 21 #include "chrome/common/extensions/manifest_handlers/theme_handler.h" | 10 #include "chrome/common/extensions/manifest_handlers/theme_handler.h" |
| 22 #include "extensions/common/constants.h" | 11 #include "extensions/common/constants.h" |
| 23 #include "extensions/common/extension.h" | 12 #include "extensions/common/extension.h" |
| 24 #include "extensions/common/extension_icon_set.h" | 13 #include "extensions/common/extension_icon_set.h" |
| 25 #include "extensions/common/extension_l10n_util.h" | |
| 26 #include "extensions/common/extension_messages.h" | |
| 27 #include "extensions/common/extension_resource.h" | |
| 28 #include "extensions/common/install_warning.h" | |
| 29 #include "extensions/common/manifest.h" | |
| 30 #include "extensions/common/manifest_constants.h" | |
| 31 #include "extensions/common/manifest_handler.h" | |
| 32 #include "extensions/common/manifest_handlers/icons_handler.h" | 14 #include "extensions/common/manifest_handlers/icons_handler.h" |
| 33 #include "grit/generated_resources.h" | |
| 34 #include "net/base/file_stream.h" | |
| 35 #include "ui/base/l10n/l10n_util.h" | |
| 36 | 15 |
| 37 using extensions::Extension; | 16 using extensions::Extension; |
| 38 using extensions::ExtensionResource; | 17 using extensions::ExtensionResource; |
| 39 using extensions::Manifest; | 18 using extensions::Manifest; |
| 40 | 19 |
| 41 namespace errors = extensions::manifest_errors; | |
| 42 | |
| 43 namespace { | 20 namespace { |
| 44 | 21 |
| 45 // Add the image paths contained in the |icon_set| to |image_paths|. | 22 // Add the image paths contained in the |icon_set| to |image_paths|. |
| 46 void AddPathsFromIconSet(const ExtensionIconSet& icon_set, | 23 void AddPathsFromIconSet(const ExtensionIconSet& icon_set, |
| 47 std::set<base::FilePath>* image_paths) { | 24 std::set<base::FilePath>* image_paths) { |
| 48 // TODO(viettrungluu): These |FilePath::FromUTF8Unsafe()| indicate that we're | 25 // TODO(viettrungluu): These |FilePath::FromUTF8Unsafe()| indicate that we're |
| 49 // doing something wrong. | 26 // doing something wrong. |
| 50 for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin(); | 27 for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin(); |
| 51 iter != icon_set.map().end(); ++iter) { | 28 iter != icon_set.map().end(); ++iter) { |
| 52 image_paths->insert(base::FilePath::FromUTF8Unsafe(iter->second)); | 29 image_paths->insert(base::FilePath::FromUTF8Unsafe(iter->second)); |
| 53 } | 30 } |
| 54 } | 31 } |
| 55 | 32 |
| 56 } // namespace | 33 } // namespace |
| 57 | 34 |
| 58 namespace extension_file_util { | 35 namespace extension_file_util { |
| 59 | 36 |
| 60 const base::FilePath::CharType kTempDirectoryName[] = FILE_PATH_LITERAL("Temp"); | |
| 61 | |
| 62 base::FilePath InstallExtension(const base::FilePath& unpacked_source_dir, | |
| 63 const std::string& id, | |
| 64 const std::string& version, | |
| 65 const base::FilePath& extensions_dir) { | |
| 66 base::FilePath extension_dir = extensions_dir.AppendASCII(id); | |
| 67 base::FilePath version_dir; | |
| 68 | |
| 69 // Create the extension directory if it doesn't exist already. | |
| 70 if (!base::PathExists(extension_dir)) { | |
| 71 if (!base::CreateDirectory(extension_dir)) | |
| 72 return base::FilePath(); | |
| 73 } | |
| 74 | |
| 75 // Get a temp directory on the same file system as the profile. | |
| 76 base::FilePath install_temp_dir = GetInstallTempDir(extensions_dir); | |
| 77 base::ScopedTempDir extension_temp_dir; | |
| 78 if (install_temp_dir.empty() || | |
| 79 !extension_temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) { | |
| 80 LOG(ERROR) << "Creating of temp dir under in the profile failed."; | |
| 81 return base::FilePath(); | |
| 82 } | |
| 83 base::FilePath crx_temp_source = | |
| 84 extension_temp_dir.path().Append(unpacked_source_dir.BaseName()); | |
| 85 if (!base::Move(unpacked_source_dir, crx_temp_source)) { | |
| 86 LOG(ERROR) << "Moving extension from : " << unpacked_source_dir.value() | |
| 87 << " to : " << crx_temp_source.value() << " failed."; | |
| 88 return base::FilePath(); | |
| 89 } | |
| 90 | |
| 91 // Try to find a free directory. There can be legitimate conflicts in the case | |
| 92 // of overinstallation of the same version. | |
| 93 const int kMaxAttempts = 100; | |
| 94 for (int i = 0; i < kMaxAttempts; ++i) { | |
| 95 base::FilePath candidate = extension_dir.AppendASCII( | |
| 96 base::StringPrintf("%s_%u", version.c_str(), i)); | |
| 97 if (!base::PathExists(candidate)) { | |
| 98 version_dir = candidate; | |
| 99 break; | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 if (version_dir.empty()) { | |
| 104 LOG(ERROR) << "Could not find a home for extension " << id << " with " | |
| 105 << "version " << version << "."; | |
| 106 return base::FilePath(); | |
| 107 } | |
| 108 | |
| 109 if (!base::Move(crx_temp_source, version_dir)) { | |
| 110 LOG(ERROR) << "Installing extension from : " << crx_temp_source.value() | |
| 111 << " into : " << version_dir.value() << " failed."; | |
| 112 return base::FilePath(); | |
| 113 } | |
| 114 | |
| 115 return version_dir; | |
| 116 } | |
| 117 | |
| 118 void UninstallExtension(const base::FilePath& extensions_dir, | |
| 119 const std::string& id) { | |
| 120 // We don't care about the return value. If this fails (and it can, due to | |
| 121 // plugins that aren't unloaded yet), it will get cleaned up by | |
| 122 // ExtensionGarbageCollector::GarbageCollectExtensions. | |
| 123 base::DeleteFile(extensions_dir.AppendASCII(id), true); // recursive. | |
| 124 } | |
| 125 | |
| 126 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path, | |
| 127 Manifest::Location location, | |
| 128 int flags, | |
| 129 std::string* error) { | |
| 130 return LoadExtension(extension_path, std::string(), location, flags, error); | |
| 131 } | |
| 132 | |
| 133 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path, | |
| 134 const std::string& extension_id, | |
| 135 Manifest::Location location, | |
| 136 int flags, | |
| 137 std::string* error) { | |
| 138 scoped_ptr<base::DictionaryValue> manifest( | |
| 139 LoadManifest(extension_path, error)); | |
| 140 if (!manifest.get()) | |
| 141 return NULL; | |
| 142 if (!extension_l10n_util::LocalizeExtension(extension_path, manifest.get(), | |
| 143 error)) { | |
| 144 return NULL; | |
| 145 } | |
| 146 | |
| 147 scoped_refptr<Extension> extension(Extension::Create(extension_path, | |
| 148 location, | |
| 149 *manifest, | |
| 150 flags, | |
| 151 extension_id, | |
| 152 error)); | |
| 153 if (!extension.get()) | |
| 154 return NULL; | |
| 155 | |
| 156 std::vector<extensions::InstallWarning> warnings; | |
| 157 if (!ValidateExtension(extension.get(), error, &warnings)) | |
| 158 return NULL; | |
| 159 extension->AddInstallWarnings(warnings); | |
| 160 | |
| 161 return extension; | |
| 162 } | |
| 163 | |
| 164 base::DictionaryValue* LoadManifest(const base::FilePath& extension_path, | |
| 165 std::string* error) { | |
| 166 base::FilePath manifest_path = | |
| 167 extension_path.Append(extensions::kManifestFilename); | |
| 168 if (!base::PathExists(manifest_path)) { | |
| 169 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); | |
| 170 return NULL; | |
| 171 } | |
| 172 | |
| 173 JSONFileValueSerializer serializer(manifest_path); | |
| 174 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, error)); | |
| 175 if (!root.get()) { | |
| 176 if (error->empty()) { | |
| 177 // If |error| is empty, than the file could not be read. | |
| 178 // It would be cleaner to have the JSON reader give a specific error | |
| 179 // in this case, but other code tests for a file error with | |
| 180 // error->empty(). For now, be consistent. | |
| 181 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); | |
| 182 } else { | |
| 183 *error = base::StringPrintf("%s %s", | |
| 184 errors::kManifestParseError, | |
| 185 error->c_str()); | |
| 186 } | |
| 187 return NULL; | |
| 188 } | |
| 189 | |
| 190 if (!root->IsType(base::Value::TYPE_DICTIONARY)) { | |
| 191 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID); | |
| 192 return NULL; | |
| 193 } | |
| 194 | |
| 195 return static_cast<base::DictionaryValue*>(root.release()); | |
| 196 } | |
| 197 | |
| 198 std::vector<base::FilePath> FindPrivateKeyFiles( | |
| 199 const base::FilePath& extension_dir) { | |
| 200 std::vector<base::FilePath> result; | |
| 201 // Pattern matching only works at the root level, so filter manually. | |
| 202 base::FileEnumerator traversal(extension_dir, /*recursive=*/true, | |
| 203 base::FileEnumerator::FILES); | |
| 204 for (base::FilePath current = traversal.Next(); !current.empty(); | |
| 205 current = traversal.Next()) { | |
| 206 if (!current.MatchesExtension(extensions::kExtensionKeyFileExtension)) | |
| 207 continue; | |
| 208 | |
| 209 std::string key_contents; | |
| 210 if (!base::ReadFileToString(current, &key_contents)) { | |
| 211 // If we can't read the file, assume it's not a private key. | |
| 212 continue; | |
| 213 } | |
| 214 std::string key_bytes; | |
| 215 if (!Extension::ParsePEMKeyBytes(key_contents, &key_bytes)) { | |
| 216 // If we can't parse the key, assume it's ok too. | |
| 217 continue; | |
| 218 } | |
| 219 | |
| 220 result.push_back(current); | |
| 221 } | |
| 222 return result; | |
| 223 } | |
| 224 | |
| 225 bool ValidateExtension(const Extension* extension, | |
| 226 std::string* error, | |
| 227 std::vector<extensions::InstallWarning>* warnings) { | |
| 228 // Ask registered manifest handlers to validate their paths. | |
| 229 if (!extensions::ManifestHandler::ValidateExtension( | |
| 230 extension, error, warnings)) | |
| 231 return false; | |
| 232 | |
| 233 // Check children of extension root to see if any of them start with _ and is | |
| 234 // not on the reserved list. We only warn, and do not block the loading of the | |
| 235 // extension. | |
| 236 std::string warning; | |
| 237 if (!CheckForIllegalFilenames(extension->path(), &warning)) | |
| 238 warnings->push_back(extensions::InstallWarning(warning)); | |
| 239 | |
| 240 // Check that extensions don't include private key files. | |
| 241 std::vector<base::FilePath> private_keys = | |
| 242 FindPrivateKeyFiles(extension->path()); | |
| 243 if (extension->creation_flags() & Extension::ERROR_ON_PRIVATE_KEY) { | |
| 244 if (!private_keys.empty()) { | |
| 245 // Only print one of the private keys because l10n_util doesn't have a way | |
| 246 // to translate a list of strings. | |
| 247 *error = l10n_util::GetStringFUTF8( | |
| 248 IDS_EXTENSION_CONTAINS_PRIVATE_KEY, | |
| 249 private_keys.front().LossyDisplayName()); | |
| 250 return false; | |
| 251 } | |
| 252 } else { | |
| 253 for (size_t i = 0; i < private_keys.size(); ++i) { | |
| 254 warnings->push_back(extensions::InstallWarning( | |
| 255 l10n_util::GetStringFUTF8( | |
| 256 IDS_EXTENSION_CONTAINS_PRIVATE_KEY, | |
| 257 private_keys[i].LossyDisplayName()))); | |
| 258 } | |
| 259 // Only warn; don't block loading the extension. | |
| 260 } | |
| 261 return true; | |
| 262 } | |
| 263 | |
| 264 std::set<base::FilePath> GetBrowserImagePaths(const Extension* extension) { | 37 std::set<base::FilePath> GetBrowserImagePaths(const Extension* extension) { |
| 265 std::set<base::FilePath> image_paths; | 38 std::set<base::FilePath> image_paths; |
| 266 | 39 |
| 267 AddPathsFromIconSet(extensions::IconsInfo::GetIcons(extension), &image_paths); | 40 AddPathsFromIconSet(extensions::IconsInfo::GetIcons(extension), &image_paths); |
| 268 | 41 |
| 269 // Theme images | 42 // Theme images |
| 270 const base::DictionaryValue* theme_images = | 43 const base::DictionaryValue* theme_images = |
| 271 extensions::ThemeInfo::GetImages(extension); | 44 extensions::ThemeInfo::GetImages(extension); |
| 272 if (theme_images) { | 45 if (theme_images) { |
| 273 for (base::DictionaryValue::Iterator it(*theme_images); !it.IsAtEnd(); | 46 for (base::DictionaryValue::Iterator it(*theme_images); !it.IsAtEnd(); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 284 AddPathsFromIconSet(page_action->default_icon, &image_paths); | 57 AddPathsFromIconSet(page_action->default_icon, &image_paths); |
| 285 | 58 |
| 286 const extensions::ActionInfo* browser_action = | 59 const extensions::ActionInfo* browser_action = |
| 287 extensions::ActionInfo::GetBrowserActionInfo(extension); | 60 extensions::ActionInfo::GetBrowserActionInfo(extension); |
| 288 if (browser_action && !browser_action->default_icon.empty()) | 61 if (browser_action && !browser_action->default_icon.empty()) |
| 289 AddPathsFromIconSet(browser_action->default_icon, &image_paths); | 62 AddPathsFromIconSet(browser_action->default_icon, &image_paths); |
| 290 | 63 |
| 291 return image_paths; | 64 return image_paths; |
| 292 } | 65 } |
| 293 | 66 |
| 294 bool CheckForIllegalFilenames(const base::FilePath& extension_path, | |
| 295 std::string* error) { | |
| 296 // Reserved underscore names. | |
| 297 static const base::FilePath::CharType* reserved_names[] = { | |
| 298 extensions::kLocaleFolder, | |
| 299 extensions::kPlatformSpecificFolder, | |
| 300 FILE_PATH_LITERAL("__MACOSX"), | |
| 301 }; | |
| 302 CR_DEFINE_STATIC_LOCAL( | |
| 303 std::set<base::FilePath::StringType>, reserved_underscore_names, | |
| 304 (reserved_names, reserved_names + arraysize(reserved_names))); | |
| 305 | |
| 306 // Enumerate all files and directories in the extension root. | |
| 307 // There is a problem when using pattern "_*" with FileEnumerator, so we have | |
| 308 // to cheat with find_first_of and match all. | |
| 309 const int kFilesAndDirectories = | |
| 310 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES; | |
| 311 base::FileEnumerator all_files(extension_path, false, kFilesAndDirectories); | |
| 312 | |
| 313 base::FilePath file; | |
| 314 while (!(file = all_files.Next()).empty()) { | |
| 315 base::FilePath::StringType filename = file.BaseName().value(); | |
| 316 // Skip all that don't start with "_". | |
| 317 if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue; | |
| 318 if (reserved_underscore_names.find(filename) == | |
| 319 reserved_underscore_names.end()) { | |
| 320 *error = base::StringPrintf( | |
| 321 "Cannot load extension with file or directory name %s. " | |
| 322 "Filenames starting with \"_\" are reserved for use by the system.", | |
| 323 file.BaseName().AsUTF8Unsafe().c_str()); | |
| 324 return false; | |
| 325 } | |
| 326 } | |
| 327 | |
| 328 return true; | |
| 329 } | |
| 330 | |
| 331 base::FilePath GetInstallTempDir(const base::FilePath& extensions_dir) { | |
| 332 // We do file IO in this function, but only when the current profile's | |
| 333 // Temp directory has never been used before, or in a rare error case. | |
| 334 // Developers are not likely to see these situations often, so do an | |
| 335 // explicit thread check. | |
| 336 base::ThreadRestrictions::AssertIOAllowed(); | |
| 337 | |
| 338 // Create the temp directory as a sub-directory of the Extensions directory. | |
| 339 // This guarantees it is on the same file system as the extension's eventual | |
| 340 // install target. | |
| 341 base::FilePath temp_path = extensions_dir.Append(kTempDirectoryName); | |
| 342 if (base::PathExists(temp_path)) { | |
| 343 if (!base::DirectoryExists(temp_path)) { | |
| 344 DLOG(WARNING) << "Not a directory: " << temp_path.value(); | |
| 345 return base::FilePath(); | |
| 346 } | |
| 347 if (!base::PathIsWritable(temp_path)) { | |
| 348 DLOG(WARNING) << "Can't write to path: " << temp_path.value(); | |
| 349 return base::FilePath(); | |
| 350 } | |
| 351 // This is a directory we can write to. | |
| 352 return temp_path; | |
| 353 } | |
| 354 | |
| 355 // Directory doesn't exist, so create it. | |
| 356 if (!base::CreateDirectory(temp_path)) { | |
| 357 DLOG(WARNING) << "Couldn't create directory: " << temp_path.value(); | |
| 358 return base::FilePath(); | |
| 359 } | |
| 360 return temp_path; | |
| 361 } | |
| 362 | |
| 363 void DeleteFile(const base::FilePath& path, bool recursive) { | |
| 364 base::DeleteFile(path, recursive); | |
| 365 } | |
| 366 | |
| 367 } // namespace extension_file_util | 67 } // namespace extension_file_util |
| OLD | NEW |