blob: a694c7af94761dfe40bc72530a44aafb68180593 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/extension_resource.h"
#include "base/check.h"
#include "base/files/file_util.h"
namespace extensions {
ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) {}
ExtensionResource::ExtensionResource(const ExtensionId& extension_id,
const base::FilePath& extension_root,
const base::FilePath& relative_path)
: extension_id_(extension_id),
extension_root_(extension_root),
relative_path_(relative_path),
follow_symlinks_anywhere_(false) {}
ExtensionResource::ExtensionResource(const ExtensionResource& other) = default;
ExtensionResource::ExtensionResource(ExtensionResource&& other) = default;
ExtensionResource& ExtensionResource::operator=(ExtensionResource&& other) =
default;
ExtensionResource::~ExtensionResource() = default;
void ExtensionResource::set_follow_symlinks_anywhere() {
follow_symlinks_anywhere_ = true;
}
const base::FilePath& ExtensionResource::GetFilePath() const {
if (extension_root_.empty() || relative_path_.empty()) {
DCHECK(full_resource_path_.empty());
return full_resource_path_;
}
// We've already checked, just return last value.
if (!full_resource_path_.empty())
return full_resource_path_;
full_resource_path_ = GetFilePath(
extension_root_, relative_path_,
follow_symlinks_anywhere_ ?
FOLLOW_SYMLINKS_ANYWHERE : SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
return full_resource_path_;
}
// static
base::FilePath ExtensionResource::GetFilePath(
const base::FilePath& extension_root,
const base::FilePath& relative_path,
SymlinkPolicy symlink_policy) {
// We need to normalize `extension_root` on its own because `IsParent` doesn't
// normalize file paths. Without normalization parent references, Windows
// short paths, or different path capitalization will cause `IsParent` to
// return false.
bool extension_root_normalization_skipped = false;
base::FilePath normalized_extension_root;
if (!base::NormalizeFilePath(extension_root, &normalized_extension_root)) {
#if BUILDFLAG(IS_WIN)
// On Windows, `NormalizeFilePath` is implemented via
// `GetFinalPathNameByHandle`, which can fail in some cases. One such case
// which was reported by users is with paths on a ramdisk. For example, from
// the author of ImDisk:
//
// > ImDisk is a rather old product. It is designed to be as small and
// > simple as possible and compatible with Windows versions as old as NT
// > 3.51. By design it lacks support for certain "modern" OS features like
// > plug-and-play and Volume Mount Manager. The mentioned API function,
// > GetFinalPathNameByHandle, uses Volume Mount Manager and is therefore
// > not supported for ImDisk virtual disks.
//
// Since we can't normalize the path, fall back to `MakeAbsoluteFilePath`
// and proceed if the file exists.
normalized_extension_root = base::MakeAbsoluteFilePath(extension_root);
if (normalized_extension_root.empty() ||
!base::PathExists(normalized_extension_root)) {
return base::FilePath();
}
extension_root_normalization_skipped = true;
#else
return base::FilePath();
#endif
}
base::FilePath full_path = normalized_extension_root.Append(relative_path);
// If we are allowing the file to be a symlink outside of the root, then the
// path before resolving the symlink must still be within it.
if (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE) {
int depth = 0;
for (const auto& component : relative_path.GetComponents()) {
if (component == base::FilePath::kParentDirectory) {
depth--;
} else if (component != base::FilePath::kCurrentDirectory) {
depth++;
}
if (depth < 0) {
return base::FilePath();
}
}
}
// We must resolve the absolute path of the combined path when
// the relative path contains references to a parent folder (i.e., '..').
// NormalizeFilePath will fail if the path doesn't exist.
if (base::FilePath full_path_normalized;
!extension_root_normalization_skipped &&
base::NormalizeFilePath(full_path, &full_path_normalized)) {
full_path = std::move(full_path_normalized);
} else {
#if BUILDFLAG(IS_WIN)
// On Windows, if `NormalizeFilePath` fails, fall back to
// `MakeAbsoluteFilePath` and proceed if the file exists. This can happen
// if, for example, the file isn't accessible due to permissions.
full_path = base::MakeAbsoluteFilePath(full_path);
if (full_path.empty() || !base::PathExists(full_path)) {
return base::FilePath();
}
#else
return base::FilePath();
#endif
}
if (symlink_policy != FOLLOW_SYMLINKS_ANYWHERE &&
!normalized_extension_root.IsParent(full_path)) {
return base::FilePath();
}
#if BUILDFLAG(IS_MAC)
// Reject file paths ending with a separator. Unlike other platforms, macOS
// strips the trailing separator when `realpath` is used, which causes
// inconsistencies. See https://crbug.com/356878412.
if (relative_path.EndsWithSeparator() && !base::DirectoryExists(full_path)) {
return base::FilePath();
}
#endif
#if BUILDFLAG(IS_WIN)
// Reject paths ending with '.' or ' '. Such suffix is ignored when accessing
// files on Windows, which causes inconsistencies. See
// https://crbug.com/400119351.
if (!relative_path.empty()) {
const char last_char = relative_path.value().back();
if (last_char == '.' || last_char == ' ') {
return base::FilePath();
}
}
#endif
return full_path;
}
} // namespace extensions