| // 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 |