blob: fce46ddb369d7646c1402e0b4c13ee543dad278a [file] [log] [blame] [edit]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/extensions/extension_file_util.h"
#include <map>
#include <vector>
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/scoped_temp_dir.h"
#include "base/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "base/utf_string_conversions.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_action.h"
#include "chrome/common/extensions/extension_l10n_util.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_resource.h"
#include "chrome/common/extensions/message_bundle.h"
#include "grit/generated_resources.h"
#include "net/base/escape.h"
#include "net/base/file_stream.h"
#include "ui/base/l10n/l10n_util.h"
using extensions::Extension;
namespace errors = extension_manifest_errors;
namespace {
bool ValidateExtensionIconSet(const ExtensionIconSet* icon_set,
const Extension* extension,
int error_message_id,
std::string* error) {
for (ExtensionIconSet::IconMap::const_iterator iter = icon_set->map().begin();
iter != icon_set->map().end();
++iter) {
const FilePath path = extension->GetResource(iter->second).GetFilePath();
if (!extension_file_util::ValidateFilePath(path)) {
*error = l10n_util::GetStringFUTF8(error_message_id,
UTF8ToUTF16(iter->second));
return false;
}
}
return true;
}
} // namespace
namespace extension_file_util {
// Validates locale info. Doesn't check if messages.json files are valid.
static bool ValidateLocaleInfo(const Extension& extension,
std::string* error);
// Returns false and sets the error if script file can't be loaded,
// or if it's not UTF-8 encoded.
static bool IsScriptValid(const FilePath& path, const FilePath& relative_path,
int message_id, std::string* error);
const char kInstallDirectoryName[] = "Extensions";
FilePath InstallExtension(const FilePath& unpacked_source_dir,
const std::string& id,
const std::string& version,
const FilePath& all_extensions_dir) {
FilePath extension_dir = all_extensions_dir.AppendASCII(id);
FilePath version_dir;
// Create the extension directory if it doesn't exist already.
if (!file_util::PathExists(extension_dir)) {
if (!file_util::CreateDirectory(extension_dir))
return FilePath();
}
FilePath profile_temp_dir = GetUserDataTempDir();
// Move the extracted extension to a temp folder under the profile which will
// then be moved to the final destination to ensure integrity of the installed
// extension. The first move is actually a copy+delete to ensure proper
// behavor in case we are moving a folder inside another folder on the same
// level because Move will attempt rename in this case instead of proper move.
// PLEASE NOTE: This issue has been observed in extension unit tests that try
// to install user exnteions (not crx files but unpacked ones) from subfolder
// of the temp folder. In that case a move will only rename the folder insted
// of miving it into the destination folder as expected. That is the reason we
// do copy+delete instead of a plain delete here! It can happen in the wild
// with say autounpacked archive going to the temp folder and the user tries
// to install it from there.
ScopedTempDir extension_temp_dir;
if (profile_temp_dir.empty() ||
!extension_temp_dir.CreateUniqueTempDirUnderPath(profile_temp_dir)) {
LOG(ERROR) << "Creating of temp dir under in the profile failed.";
return FilePath();
}
if (!file_util::CopyDirectory(unpacked_source_dir,
extension_temp_dir.path(), true)) {
LOG(ERROR) << "Moving extension from : " << unpacked_source_dir.value()
<< " to : " << extension_temp_dir.path().value() << " failed.";
return FilePath();
}
file_util::Delete(unpacked_source_dir, true);
FilePath crx_temp_source =
extension_temp_dir.path().Append(unpacked_source_dir.BaseName());
// Try to find a free directory. There can be legitimate conflicts in the case
// of overinstallation of the same version.
const int kMaxAttempts = 100;
for (int i = 0; i < kMaxAttempts; ++i) {
FilePath candidate = extension_dir.AppendASCII(
base::StringPrintf("%s_%u", version.c_str(), i));
if (!file_util::PathExists(candidate)) {
version_dir = candidate;
break;
}
}
if (version_dir.empty()) {
LOG(ERROR) << "Could not find a home for extension " << id << " with "
<< "version " << version << ".";
return FilePath();
}
if (!file_util::Move(crx_temp_source, version_dir)) {
LOG(ERROR) << "Installing extension from : " << crx_temp_source.value()
<< " into : " << version_dir.value() << " failed.";
return FilePath();
}
return version_dir;
}
void UninstallExtension(const FilePath& extensions_dir,
const std::string& id) {
// We don't care about the return value. If this fails (and it can, due to
// plugins that aren't unloaded yet, it will get cleaned up by
// ExtensionService::GarbageCollectExtensions).
file_util::Delete(extensions_dir.AppendASCII(id), true); // recursive.
}
scoped_refptr<Extension> LoadExtension(const FilePath& extension_path,
Extension::Location location,
int flags,
std::string* error) {
return LoadExtension(extension_path, std::string(), location, flags, error);
}
scoped_refptr<Extension> LoadExtension(const FilePath& extension_path,
const std::string& extension_id,
Extension::Location location,
int flags,
std::string* error) {
scoped_ptr<DictionaryValue> manifest(LoadManifest(extension_path, error));
if (!manifest.get())
return NULL;
if (!extension_l10n_util::LocalizeExtension(extension_path, manifest.get(),
error)) {
return NULL;
}
scoped_refptr<Extension> extension(Extension::Create(extension_path,
location,
*manifest,
flags,
extension_id,
error));
if (!extension.get())
return NULL;
Extension::InstallWarningVector warnings;
if (!ValidateExtension(extension.get(), error, &warnings))
return NULL;
extension->AddInstallWarnings(warnings);
return extension;
}
DictionaryValue* LoadManifest(const FilePath& extension_path,
std::string* error) {
FilePath manifest_path =
extension_path.Append(Extension::kManifestFilename);
if (!file_util::PathExists(manifest_path)) {
*error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
return NULL;
}
JSONFileValueSerializer serializer(manifest_path);
scoped_ptr<Value> root(serializer.Deserialize(NULL, error));
if (!root.get()) {
if (error->empty()) {
// If |error| is empty, than the file could not be read.
// It would be cleaner to have the JSON reader give a specific error
// in this case, but other code tests for a file error with
// error->empty(). For now, be consistent.
*error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
} else {
*error = base::StringPrintf("%s %s",
errors::kManifestParseError,
error->c_str());
}
return NULL;
}
if (!root->IsType(Value::TYPE_DICTIONARY)) {
*error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID);
return NULL;
}
return static_cast<DictionaryValue*>(root.release());
}
std::vector<FilePath> FindPrivateKeyFiles(const FilePath& extension_dir) {
std::vector<FilePath> result;
// Pattern matching only works at the root level, so filter manually.
file_util::FileEnumerator traversal(extension_dir, /*recursive=*/true,
file_util::FileEnumerator::FILES);
for (FilePath current = traversal.Next(); !current.empty();
current = traversal.Next()) {
if (!current.MatchesExtension(chrome::kExtensionKeyFileExtension))
continue;
std::string key_contents;
if (!file_util::ReadFileToString(current, &key_contents)) {
// If we can't read the file, assume it's not a private key.
continue;
}
std::string key_bytes;
if (!Extension::ParsePEMKeyBytes(key_contents, &key_bytes)) {
// If we can't parse the key, assume it's ok too.
continue;
}
result.push_back(current);
}
return result;
}
bool ValidateFilePath(const FilePath& path) {
int64 size = 0;
if (!file_util::PathExists(path) ||
!file_util::GetFileSize(path, &size) ||
size == 0) {
return false;
}
return true;
}
bool ValidateExtension(const Extension* extension,
std::string* error,
Extension::InstallWarningVector* warnings) {
// Validate icons exist.
for (ExtensionIconSet::IconMap::const_iterator iter =
extension->icons().map().begin();
iter != extension->icons().map().end();
++iter) {
const FilePath path = extension->GetResource(iter->second).GetFilePath();
if (!ValidateFilePath(path)) {
*error =
l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_ICON_FAILED,
UTF8ToUTF16(iter->second));
return false;
}
}
// Theme resource validation.
if (extension->is_theme()) {
DictionaryValue* images_value = extension->GetThemeImages();
if (images_value) {
for (DictionaryValue::key_iterator iter = images_value->begin_keys();
iter != images_value->end_keys(); ++iter) {
std::string val;
if (images_value->GetStringWithoutPathExpansion(*iter, &val)) {
FilePath image_path = extension->path().Append(
FilePath::FromUTF8Unsafe(val));
if (!file_util::PathExists(image_path)) {
*error =
l10n_util::GetStringFUTF8(IDS_EXTENSION_INVALID_IMAGE_PATH,
image_path.LossyDisplayName());
return false;
}
}
}
}
// Themes cannot contain other extension types.
return true;
}
// Validate that claimed script resources actually exist,
// and are UTF-8 encoded.
ExtensionResource::SymlinkPolicy symlink_policy;
if ((extension->creation_flags() &
Extension::FOLLOW_SYMLINKS_ANYWHERE) != 0) {
symlink_policy = ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE;
} else {
symlink_policy = ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT;
}
for (size_t i = 0; i < extension->content_scripts().size(); ++i) {
const extensions::UserScript& script = extension->content_scripts()[i];
for (size_t j = 0; j < script.js_scripts().size(); j++) {
const extensions::UserScript::File& js_script = script.js_scripts()[j];
const FilePath& path = ExtensionResource::GetFilePath(
js_script.extension_root(), js_script.relative_path(),
symlink_policy);
if (!IsScriptValid(path, js_script.relative_path(),
IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
return false;
}
for (size_t j = 0; j < script.css_scripts().size(); j++) {
const extensions::UserScript::File& css_script = script.css_scripts()[j];
const FilePath& path = ExtensionResource::GetFilePath(
css_script.extension_root(), css_script.relative_path(),
symlink_policy);
if (!IsScriptValid(path, css_script.relative_path(),
IDS_EXTENSION_LOAD_CSS_FAILED, error))
return false;
}
}
// Validate claimed plugin paths.
for (size_t i = 0; i < extension->plugins().size(); ++i) {
const Extension::PluginInfo& plugin = extension->plugins()[i];
if (!file_util::PathExists(plugin.path)) {
*error =
l10n_util::GetStringFUTF8(
IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED,
plugin.path.LossyDisplayName());
return false;
}
}
const ExtensionAction* action = extension->page_action();
if (action && action->default_icon() &&
!ValidateExtensionIconSet(action->default_icon(), extension,
IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED, error)) {
return false;
}
action = extension->browser_action();
if (action && action->default_icon() &&
!ValidateExtensionIconSet(action->default_icon(), extension,
IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED, error)) {
return false;
}
// Validate that background scripts exist.
for (size_t i = 0; i < extension->background_scripts().size(); ++i) {
if (!file_util::PathExists(
extension->GetResource(
extension->background_scripts()[i]).GetFilePath())) {
*error = l10n_util::GetStringFUTF8(
IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
UTF8ToUTF16(extension->background_scripts()[i]));
return false;
}
}
// Validate background page location, except for hosted apps, which should use
// an external URL. Background page for hosted apps are verified when the
// extension is created (in Extension::InitFromValue)
if (extension->has_background_page() &&
!extension->is_hosted_app() &&
extension->background_scripts().empty()) {
FilePath page_path = ExtensionURLToRelativeFilePath(
extension->GetBackgroundURL());
const FilePath path = extension->GetResource(page_path).GetFilePath();
if (path.empty() || !file_util::PathExists(path)) {
*error =
l10n_util::GetStringFUTF8(
IDS_EXTENSION_LOAD_BACKGROUND_PAGE_FAILED,
page_path.LossyDisplayName());
return false;
}
}
// Validate path to the options page. Don't check the URL for hosted apps,
// because they are expected to refer to an external URL.
if (!extension->options_url().is_empty() && !extension->is_hosted_app()) {
const FilePath options_path = ExtensionURLToRelativeFilePath(
extension->options_url());
const FilePath path = extension->GetResource(options_path).GetFilePath();
if (path.empty() || !file_util::PathExists(path)) {
*error =
l10n_util::GetStringFUTF8(
IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED,
options_path.LossyDisplayName());
return false;
}
}
// Validate locale info.
if (!ValidateLocaleInfo(*extension, error))
return false;
// Check children of extension root to see if any of them start with _ and is
// not on the reserved list.
if (!CheckForIllegalFilenames(extension->path(), error)) {
return false;
}
// Check that extensions don't include private key files.
std::vector<FilePath> private_keys = FindPrivateKeyFiles(extension->path());
if (extension->creation_flags() & Extension::ERROR_ON_PRIVATE_KEY) {
if (!private_keys.empty()) {
// Only print one of the private keys because l10n_util doesn't have a way
// to translate a list of strings.
*error = l10n_util::GetStringFUTF8(
IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
private_keys.front().LossyDisplayName());
return false;
}
} else {
for (size_t i = 0; i < private_keys.size(); ++i) {
warnings->push_back(Extension::InstallWarning(
Extension::InstallWarning::FORMAT_TEXT,
l10n_util::GetStringFUTF8(
IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
private_keys[i].LossyDisplayName())));
}
// Only warn; don't block loading the extension.
}
return true;
}
void GarbageCollectExtensions(
const FilePath& install_directory,
const std::map<std::string, FilePath>& extension_paths) {
// Nothing to clean up if it doesn't exist.
if (!file_util::DirectoryExists(install_directory))
return;
DVLOG(1) << "Garbage collecting extensions...";
file_util::FileEnumerator enumerator(install_directory,
false, // Not recursive.
file_util::FileEnumerator::DIRECTORIES);
FilePath extension_path;
for (extension_path = enumerator.Next(); !extension_path.value().empty();
extension_path = enumerator.Next()) {
std::string extension_id;
FilePath basename = extension_path.BaseName();
if (IsStringASCII(basename.value())) {
extension_id = UTF16ToASCII(basename.LossyDisplayName());
if (!Extension::IdIsValid(extension_id))
extension_id.clear();
}
// Delete directories that aren't valid IDs.
if (extension_id.empty()) {
DLOG(WARNING) << "Invalid extension ID encountered in extensions "
"directory: " << basename.value();
DVLOG(1) << "Deleting invalid extension directory "
<< extension_path.value() << ".";
file_util::Delete(extension_path, true); // Recursive.
continue;
}
std::map<std::string, FilePath>::const_iterator iter =
extension_paths.find(extension_id);
// If there is no entry in the prefs file, just delete the directory and
// move on. This can legitimately happen when an uninstall does not
// complete, for example, when a plugin is in use at uninstall time.
if (iter == extension_paths.end()) {
DVLOG(1) << "Deleting unreferenced install for directory "
<< extension_path.LossyDisplayName() << ".";
file_util::Delete(extension_path, true); // Recursive.
continue;
}
// Clean up old version directories.
file_util::FileEnumerator versions_enumerator(
extension_path,
false, // Not recursive.
file_util::FileEnumerator::DIRECTORIES);
for (FilePath version_dir = versions_enumerator.Next();
!version_dir.value().empty();
version_dir = versions_enumerator.Next()) {
if (version_dir.BaseName() != iter->second.BaseName()) {
DVLOG(1) << "Deleting old version for directory "
<< version_dir.LossyDisplayName() << ".";
file_util::Delete(version_dir, true); // Recursive.
}
}
}
}
extensions::MessageBundle* LoadMessageBundle(
const FilePath& extension_path,
const std::string& default_locale,
std::string* error) {
error->clear();
// Load locale information if available.
FilePath locale_path = extension_path.Append(
Extension::kLocaleFolder);
if (!file_util::PathExists(locale_path))
return NULL;
std::set<std::string> locales;
if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error))
return NULL;
if (default_locale.empty() ||
locales.find(default_locale) == locales.end()) {
*error = l10n_util::GetStringUTF8(
IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
return NULL;
}
extensions::MessageBundle* message_bundle =
extension_l10n_util::LoadMessageCatalogs(
locale_path,
default_locale,
extension_l10n_util::CurrentLocaleOrDefault(),
locales,
error);
return message_bundle;
}
SubstitutionMap* LoadMessageBundleSubstitutionMap(
const FilePath& extension_path,
const std::string& extension_id,
const std::string& default_locale) {
SubstitutionMap* returnValue = new SubstitutionMap();
if (!default_locale.empty()) {
// Touch disk only if extension is localized.
std::string error;
scoped_ptr<extensions::MessageBundle> bundle(
LoadMessageBundle(extension_path, default_locale, &error));
if (bundle.get())
*returnValue = *bundle->dictionary();
}
// Add @@extension_id reserved message here, so it's available to
// non-localized extensions too.
returnValue->insert(
std::make_pair(extensions::MessageBundle::kExtensionIdKey, extension_id));
return returnValue;
}
static bool ValidateLocaleInfo(const Extension& extension,
std::string* error) {
// default_locale and _locales have to be both present or both missing.
const FilePath path = extension.path().Append(
Extension::kLocaleFolder);
bool path_exists = file_util::PathExists(path);
std::string default_locale = extension.default_locale();
// If both default locale and _locales folder are empty, skip verification.
if (default_locale.empty() && !path_exists)
return true;
if (default_locale.empty() && path_exists) {
*error = l10n_util::GetStringUTF8(
IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
return false;
} else if (!default_locale.empty() && !path_exists) {
*error = errors::kLocalesTreeMissing;
return false;
}
// Treat all folders under _locales as valid locales.
file_util::FileEnumerator locales(path,
false,
file_util::FileEnumerator::DIRECTORIES);
std::set<std::string> all_locales;
extension_l10n_util::GetAllLocales(&all_locales);
const FilePath default_locale_path = path.AppendASCII(default_locale);
bool has_default_locale_message_file = false;
FilePath locale_path;
while (!(locale_path = locales.Next()).empty()) {
if (extension_l10n_util::ShouldSkipValidation(path, locale_path,
all_locales))
continue;
FilePath messages_path =
locale_path.Append(Extension::kMessagesFilename);
if (!file_util::PathExists(messages_path)) {
*error = base::StringPrintf(
"%s %s", errors::kLocalesMessagesFileMissing,
UTF16ToUTF8(messages_path.LossyDisplayName()).c_str());
return false;
}
if (locale_path == default_locale_path)
has_default_locale_message_file = true;
}
// Only message file for default locale has to exist.
if (!has_default_locale_message_file) {
*error = errors::kLocalesNoDefaultMessages;
return false;
}
return true;
}
static bool IsScriptValid(const FilePath& path,
const FilePath& relative_path,
int message_id,
std::string* error) {
std::string content;
if (!file_util::PathExists(path) ||
!file_util::ReadFileToString(path, &content)) {
*error = l10n_util::GetStringFUTF8(
message_id,
relative_path.LossyDisplayName());
return false;
}
if (!IsStringUTF8(content)) {
*error = l10n_util::GetStringFUTF8(
IDS_EXTENSION_BAD_FILE_ENCODING,
relative_path.LossyDisplayName());
return false;
}
return true;
}
bool CheckForIllegalFilenames(const FilePath& extension_path,
std::string* error) {
// Reserved underscore names.
static const FilePath::CharType* reserved_names[] = {
Extension::kLocaleFolder,
FILE_PATH_LITERAL("__MACOSX"),
};
CR_DEFINE_STATIC_LOCAL(
std::set<FilePath::StringType>, reserved_underscore_names,
(reserved_names, reserved_names + arraysize(reserved_names)));
// Enumerate all files and directories in the extension root.
// There is a problem when using pattern "_*" with FileEnumerator, so we have
// to cheat with find_first_of and match all.
const int kFilesAndDirectories =
file_util::FileEnumerator::DIRECTORIES | file_util::FileEnumerator::FILES;
file_util::FileEnumerator all_files(
extension_path, false, kFilesAndDirectories);
FilePath file;
while (!(file = all_files.Next()).empty()) {
FilePath::StringType filename = file.BaseName().value();
// Skip all that don't start with "_".
if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue;
if (reserved_underscore_names.find(filename) ==
reserved_underscore_names.end()) {
*error = base::StringPrintf(
"Cannot load extension with file or directory name %s. "
"Filenames starting with \"_\" are reserved for use by the system.",
filename.c_str());
return false;
}
}
return true;
}
FilePath ExtensionURLToRelativeFilePath(const GURL& url) {
std::string url_path = url.path();
if (url_path.empty() || url_path[0] != '/')
return FilePath();
// Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
std::string file_path = net::UnescapeURLComponent(url_path,
net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
size_t skip = file_path.find_first_not_of("/\\");
if (skip != file_path.npos)
file_path = file_path.substr(skip);
FilePath path =
#if defined(OS_POSIX)
FilePath(file_path);
#elif defined(OS_WIN)
FilePath(UTF8ToWide(file_path));
#else
FilePath();
NOTIMPLEMENTED();
#endif
// It's still possible for someone to construct an annoying URL whose path
// would still wind up not being considered relative at this point.
// For example: chrome-extension://id/c:////foo.html
if (path.IsAbsolute())
return FilePath();
return path;
}
FilePath ExtensionResourceURLToFilePath(const GURL& url, const FilePath& root) {
std::string host = net::UnescapeURLComponent(url.host(),
net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
if (host.empty())
return FilePath();
FilePath relative_path = ExtensionURLToRelativeFilePath(url);
if (relative_path.empty())
return FilePath();
FilePath path = root.AppendASCII(host).Append(relative_path);
if (!file_util::PathExists(path) ||
!file_util::AbsolutePath(&path) ||
!root.IsParent(path)) {
return FilePath();
}
return path;
}
FilePath GetUserDataTempDir() {
// We do file IO in this function, but only when the current profile's
// Temp directory has never been used before, or in a rare error case.
// Developers are not likely to see these situations often, so do an
// explicit thread check.
base::ThreadRestrictions::AssertIOAllowed();
// The following enum used to be sent as a histogram to diagnose issues
// accessing the temp path (crbug/70056). The histogram is gone, but
// the enum makes it clear exactly why the temp directory can not be
// accessed, which may aid debugging in the future.
enum DirectoryCreationResult {
SUCCESS = 0,
CANT_GET_PARENT_PATH,
CANT_GET_UDT_PATH,
NOT_A_DIRECTORY,
CANT_CREATE_DIR,
CANT_WRITE_TO_PATH,
UNSET,
NUM_DIRECTORY_CREATION_RESULTS
};
// All paths should set |result|.
DirectoryCreationResult result = UNSET;
FilePath temp_path;
if (!PathService::Get(chrome::DIR_USER_DATA_TEMP, &temp_path)) {
FilePath parent_path;
if (!PathService::Get(chrome::DIR_USER_DATA, &parent_path))
result = CANT_GET_PARENT_PATH;
else
result = CANT_GET_UDT_PATH;
} else if (file_util::PathExists(temp_path)) {
// Path exists. Check that it is a directory we can write to.
if (!file_util::DirectoryExists(temp_path)) {
result = NOT_A_DIRECTORY;
} else if (!file_util::PathIsWritable(temp_path)) {
result = CANT_WRITE_TO_PATH;
} else {
// Temp is a writable directory.
result = SUCCESS;
}
} else if (!file_util::CreateDirectory(temp_path)) {
// Path doesn't exist, and we failed to create it.
result = CANT_CREATE_DIR;
} else {
// Successfully created the Temp directory.
result = SUCCESS;
}
if (result == SUCCESS)
return temp_path;
return FilePath();
}
void DeleteFile(const FilePath& path, bool recursive) {
file_util::Delete(path, recursive);
}
} // namespace extension_file_util