910e62b5创建于 1月15日历史提交
// 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 "storage/browser/file_system/isolated_context.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>

#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/notreached.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "storage/browser/file_system/file_system_url.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"

class GURL;

namespace storage {

namespace {

// The given path should not contain any '..' and should be absolute.
bool IsPathValid(const base::FilePath& path) {
#if BUILDFLAG(IS_ANDROID)
  if (path.IsContentUri()) {
    return true;
  }
#endif
  return !path.ReferencesParent() && path.IsAbsolute();
}

base::FilePath::StringType GetRegisterNameForPath(const base::FilePath& path) {
  // If it's not a root path simply return a base name.
  if (path.DirName() != path)
    return path.BaseName().value();

#if defined(FILE_PATH_USES_DRIVE_LETTERS)
  base::FilePath::StringType name;
  for (size_t i = 0;
       i < path.value().size() && !base::FilePath::IsSeparator(path.value()[i]);
       ++i) {
    if (path.value()[i] == L':') {
      name.append(L"_drive");
      break;
    }
    name.append(1, path.value()[i]);
  }
  return name;
#else
  return FILE_PATH_LITERAL("<root>");
#endif
}

bool IsSinglePathIsolatedFileSystem(FileSystemType type) {
  DCHECK_NE(kFileSystemTypeUnknown, type);
  // As of writing dragged file system is the only filesystem which could have
  // multiple top-level paths.
  return type != kFileSystemTypeDragged;
}

static base::LazyInstance<IsolatedContext>::Leaky g_isolated_context =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

IsolatedContext::FileInfoSet::FileInfoSet() = default;
IsolatedContext::FileInfoSet::~FileInfoSet() = default;

bool IsolatedContext::FileInfoSet::AddPath(const base::FilePath& path,
                                           std::string* registered_name) {
  if (!IsPathValid(path)) {
    return false;
  }
  base::FilePath::StringType name = GetRegisterNameForPath(path);
  std::string utf8name = base::FilePath(name).AsUTF8Unsafe();
  base::FilePath normalized_path = path.NormalizePathSeparators();
  bool inserted =
      fileset_.insert(MountPointInfo(utf8name, normalized_path)).second;
  if (!inserted) {
    int suffix = 1;
    std::string basepart =
        base::FilePath(name).RemoveExtension().AsUTF8Unsafe();
    std::string ext =
        base::FilePath(base::FilePath(name).Extension()).AsUTF8Unsafe();
    while (!inserted) {
      utf8name = base::StringPrintf("%s (%d)", basepart.c_str(), suffix++);
      if (!ext.empty())
        utf8name.append(ext);
      inserted =
          fileset_.insert(MountPointInfo(utf8name, normalized_path)).second;
    }
  }
  if (registered_name)
    *registered_name = utf8name;
  return true;
}

bool IsolatedContext::FileInfoSet::AddPathWithName(const base::FilePath& path,
                                                   const std::string& name) {
  if (!IsPathValid(path)) {
    return false;
  }
  return fileset_.insert(MountPointInfo(name, path.NormalizePathSeparators()))
      .second;
}

//--------------------------------------------------------------------------

IsolatedContext::ScopedFSHandle::ScopedFSHandle(std::string file_system_id)
    : file_system_id_(std::move(file_system_id)) {
  if (!file_system_id_.empty())
    IsolatedContext::GetInstance()->AddReference(file_system_id_);
}

IsolatedContext::ScopedFSHandle::~ScopedFSHandle() {
  if (!file_system_id_.empty())
    IsolatedContext::GetInstance()->RemoveReference(file_system_id_);
}

IsolatedContext::ScopedFSHandle::ScopedFSHandle(const ScopedFSHandle& other)
    : ScopedFSHandle(other.id()) {}

IsolatedContext::ScopedFSHandle::ScopedFSHandle(ScopedFSHandle&& other)
    : file_system_id_(std::move(other.file_system_id_)) {
  // Moving from a string leaves it in a unspecified state, we need to make sure
  // to leave it empty, so explicitly clear it.
  other.file_system_id_.clear();
}

IsolatedContext::ScopedFSHandle& IsolatedContext::ScopedFSHandle::operator=(
    const ScopedFSHandle& other) {
  if (!file_system_id_.empty())
    IsolatedContext::GetInstance()->RemoveReference(file_system_id_);
  file_system_id_ = other.id();
  if (!file_system_id_.empty())
    IsolatedContext::GetInstance()->AddReference(file_system_id_);
  return *this;
}

IsolatedContext::ScopedFSHandle& IsolatedContext::ScopedFSHandle::operator=(
    ScopedFSHandle&& other) {
  if (!file_system_id_.empty())
    IsolatedContext::GetInstance()->RemoveReference(file_system_id_);
  file_system_id_ = std::move(other.file_system_id_);
  // Moving from a string leaves it in a unspecified state, we need to make sure
  // to leave it empty, so explicitly clear it.
  other.file_system_id_.clear();
  return *this;
}

//--------------------------------------------------------------------------

class IsolatedContext::Instance {
 public:
  enum PathType { PLATFORM_PATH, VIRTUAL_PATH };

  // For a single-path isolated file system, which could be registered by
  // IsolatedContext::RegisterFileSystemForPath() or
  // IsolatedContext::RegisterFileSystemForVirtualPath().
  // Most of isolated file system contexts should be of this type.
  Instance(FileSystemType type,
           const std::string& filesystem_id,
           const MountPointInfo& file_info,
           PathType path_type);

  // For a multi-paths isolated file system.  As of writing only file system
  // type which could have multi-paths is Dragged file system, and
  // could be registered by IsolatedContext::RegisterDraggedFileSystem().
  Instance(FileSystemType type, const std::set<MountPointInfo>& files);

  Instance(const Instance&) = delete;
  Instance& operator=(const Instance&) = delete;

  ~Instance();

  FileSystemType type() const { return type_; }
  const std::string& filesystem_id() const { return filesystem_id_; }
  const MountPointInfo& file_info() const { return file_info_; }
  const std::set<MountPointInfo>& files() const { return files_; }
  int ref_counts() const { return ref_counts_; }

  void AddRef() { ++ref_counts_; }
  void RemoveRef() { --ref_counts_; }

  bool ResolvePathForName(const std::string& name, base::FilePath* path) const;

  // Returns true if the instance is a single-path instance.
  bool IsSinglePathInstance() const;

 private:
  const FileSystemType type_;
  const std::string filesystem_id_;

  // For single-path instance.
  const MountPointInfo file_info_;
  const PathType path_type_;

  // For multiple-path instance (e.g. dragged file system).
  const std::set<MountPointInfo> files_;

  // Reference counts. Note that an isolated filesystem is created with ref==0
  // and will get deleted when the ref count reaches <=0.
  int ref_counts_;
};

IsolatedContext::Instance::Instance(FileSystemType type,
                                    const std::string& filesystem_id,
                                    const MountPointInfo& file_info,
                                    Instance::PathType path_type)
    : type_(type),
      filesystem_id_(filesystem_id),
      file_info_(file_info),
      path_type_(path_type),
      ref_counts_(0) {
  DCHECK(IsSinglePathIsolatedFileSystem(type_));
}

IsolatedContext::Instance::Instance(FileSystemType type,
                                    const std::set<MountPointInfo>& files)
    : type_(type), path_type_(PLATFORM_PATH), files_(files), ref_counts_(0) {
  DCHECK(!IsSinglePathIsolatedFileSystem(type_));
}

IsolatedContext::Instance::~Instance() = default;

bool IsolatedContext::Instance::ResolvePathForName(const std::string& name,
                                                   base::FilePath* path) const {
  if (IsSinglePathIsolatedFileSystem(type_)) {
    switch (path_type_) {
      case PLATFORM_PATH:
        *path = file_info_.path;
        break;
      case VIRTUAL_PATH:
        *path = base::FilePath();
        break;
      default:
        NOTREACHED();
    }

    return file_info_.name == name;
  }
  auto found = files_.find(MountPointInfo(name, base::FilePath()));
  if (found == files_.end())
    return false;
  *path = found->path;
  return true;
}

bool IsolatedContext::Instance::IsSinglePathInstance() const {
  return IsSinglePathIsolatedFileSystem(type_);
}

//--------------------------------------------------------------------------

// static
IsolatedContext* IsolatedContext::GetInstance() {
  return g_isolated_context.Pointer();
}

// static
bool IsolatedContext::IsIsolatedType(FileSystemType type) {
  return type == kFileSystemTypeIsolated || type == kFileSystemTypeExternal;
}

std::string IsolatedContext::RegisterDraggedFileSystem(
    const FileInfoSet& files) {
  base::AutoLock locker(lock_);
  std::string filesystem_id = GetNewFileSystemId();
  instance_map_[filesystem_id] =
      std::make_unique<Instance>(kFileSystemTypeDragged, files.fileset());
  return filesystem_id;
}

IsolatedContext::ScopedFSHandle IsolatedContext::RegisterFileSystemForPath(
    FileSystemType type,
    const std::string& filesystem_id,
    const base::FilePath& path_in,
    std::string* register_name) {
  base::FilePath path(path_in.NormalizePathSeparators());
  if (!IsPathValid(path)) {
    return ScopedFSHandle();
  }
  std::string name;
  if (register_name && !register_name->empty()) {
    name = *register_name;
  } else {
    name = base::FilePath(GetRegisterNameForPath(path)).AsUTF8Unsafe();
    if (register_name)
      register_name->assign(name);
  }

  std::string new_id;
  {
    base::AutoLock locker(lock_);
    new_id = GetNewFileSystemId();
    instance_map_[new_id] = std::make_unique<Instance>(
        type, filesystem_id, MountPointInfo(name, path),
        Instance::PLATFORM_PATH);
    path_to_id_map_[path].insert(new_id);
  }
  return ScopedFSHandle(new_id);
}

std::string IsolatedContext::RegisterFileSystemForVirtualPath(
    FileSystemType type,
    const std::string& register_name,
    const base::FilePath& cracked_path_prefix) {
  base::AutoLock locker(lock_);
  base::FilePath path(cracked_path_prefix.NormalizePathSeparators());
  if (path.ReferencesParent())
    return std::string();
  std::string filesystem_id = GetNewFileSystemId();
  instance_map_[filesystem_id] = std::make_unique<Instance>(
      type,
      std::string(),  // filesystem_id
      MountPointInfo(register_name, cracked_path_prefix),
      Instance::VIRTUAL_PATH);
  path_to_id_map_[path].insert(filesystem_id);
  return filesystem_id;
}

bool IsolatedContext::HandlesFileSystemMountType(FileSystemType type) const {
  return type == kFileSystemTypeIsolated;
}

bool IsolatedContext::RevokeFileSystem(const std::string& filesystem_id) {
  base::AutoLock locker(lock_);
  return UnregisterFileSystem(filesystem_id);
}

bool IsolatedContext::GetRegisteredPath(const std::string& filesystem_id,
                                        base::FilePath* path) const {
  DCHECK(path);
  base::AutoLock locker(lock_);
  auto found = instance_map_.find(filesystem_id);
  if (found == instance_map_.end() || !found->second->IsSinglePathInstance())
    return false;
  *path = found->second->file_info().path;
  return true;
}

bool IsolatedContext::CrackVirtualPath(
    const base::FilePath& virtual_path,
    std::string* id_or_name,
    FileSystemType* type,
    std::string* cracked_id,
    base::FilePath* path,
    FileSystemMountOption* mount_option) const {
  DCHECK(id_or_name);
  DCHECK(path);

  // This should not contain any '..' references.
  if (virtual_path.ReferencesParent())
    return false;

  // Set the default mount option.
  *mount_option = FileSystemMountOption();

  // The virtual_path should comprise <id_or_name> and <relative_path> parts.
  std::vector<base::FilePath::StringType> components =
      virtual_path.GetComponents();
  if (components.size() < 1)
    return false;
  auto component_iter = components.begin();
  std::string fsid = base::FilePath(*component_iter++).MaybeAsASCII();
  if (fsid.empty())
    return false;

  base::FilePath cracked_path;
  {
    base::AutoLock locker(lock_);
    auto found_instance = instance_map_.find(fsid);
    if (found_instance == instance_map_.end())
      return false;
    *id_or_name = fsid;
    const Instance* instance = found_instance->second.get();
    if (type)
      *type = instance->type();
    if (cracked_id)
      *cracked_id = instance->filesystem_id();

    if (component_iter == components.end()) {
      // The virtual root case.
      path->clear();
      return true;
    }

    // *component_iter should be a name of the registered path.
    std::string name = base::FilePath(*component_iter++).AsUTF8Unsafe();
    if (!instance->ResolvePathForName(name, &cracked_path))
      return false;
  }

  for (; component_iter != components.end(); ++component_iter)
    cracked_path = cracked_path.Append(*component_iter);
  *path = cracked_path;
  return true;
}

FileSystemURL IsolatedContext::CrackURL(
    const GURL& url,
    const blink::StorageKey& storage_key) const {
  FileSystemURL filesystem_url = FileSystemURL(url, storage_key);
  if (!filesystem_url.is_valid())
    return FileSystemURL();
  return CrackFileSystemURL(filesystem_url);
}

FileSystemURL IsolatedContext::CreateCrackedFileSystemURL(
    const blink::StorageKey& storage_key,
    FileSystemType type,
    const base::FilePath& virtual_path) const {
  return CrackFileSystemURL(FileSystemURL(storage_key, type, virtual_path));
}

void IsolatedContext::RevokeFileSystemByPath(const base::FilePath& path_in) {
  base::AutoLock locker(lock_);
  base::FilePath path(path_in.NormalizePathSeparators());
  auto ids_iter = path_to_id_map_.find(path);
  if (ids_iter == path_to_id_map_.end())
    return;
  for (auto& id : ids_iter->second)
    instance_map_.erase(id);
  path_to_id_map_.erase(ids_iter);
}

void IsolatedContext::AddReference(const std::string& filesystem_id) {
  base::AutoLock locker(lock_);
  DCHECK(base::Contains(instance_map_, filesystem_id));
  instance_map_[filesystem_id]->AddRef();
}

void IsolatedContext::RemoveReference(const std::string& filesystem_id) {
  base::AutoLock locker(lock_);
  // This could get called for non-existent filesystem if it has been
  // already deleted by RevokeFileSystemByPath.
  auto found = instance_map_.find(filesystem_id);
  if (found == instance_map_.end())
    return;
  Instance* instance = found->second.get();
  DCHECK_GT(instance->ref_counts(), 0);
  instance->RemoveRef();
  if (instance->ref_counts() == 0) {
    bool deleted = UnregisterFileSystem(filesystem_id);
    DCHECK(deleted);
  }
}

bool IsolatedContext::GetDraggedFileInfo(
    const std::string& filesystem_id,
    std::vector<MountPointInfo>* files) const {
  DCHECK(files);
  base::AutoLock locker(lock_);
  auto found = instance_map_.find(filesystem_id);
  if (found == instance_map_.end() ||
      found->second->type() != kFileSystemTypeDragged)
    return false;
  files->assign(found->second->files().begin(), found->second->files().end());
  return true;
}

base::FilePath IsolatedContext::CreateVirtualRootPath(
    const std::string& filesystem_id) const {
  return base::FilePath().AppendASCII(filesystem_id);
}

IsolatedContext::IsolatedContext() = default;

IsolatedContext::~IsolatedContext() = default;

FileSystemURL IsolatedContext::CrackFileSystemURL(
    const FileSystemURL& url) const {
  if (!HandlesFileSystemMountType(url.type()))
    return FileSystemURL();

  std::string mount_name;
  std::string cracked_mount_name;
  FileSystemType cracked_type;
  base::FilePath cracked_path;
  FileSystemMountOption cracked_mount_option;
  if (!CrackVirtualPath(url.path(), &mount_name, &cracked_type,
                        &cracked_mount_name, &cracked_path,
                        &cracked_mount_option)) {
    return FileSystemURL();
  }

  return FileSystemURL(
      url.storage_key(), url.mount_type(), url.virtual_path(),
      !url.filesystem_id().empty() ? url.filesystem_id() : mount_name,
      cracked_type, cracked_path,
      cracked_mount_name.empty() ? mount_name : cracked_mount_name,
      cracked_mount_option);
}

bool IsolatedContext::UnregisterFileSystem(const std::string& filesystem_id) {
  lock_.AssertAcquired();
  auto found = instance_map_.find(filesystem_id);
  if (found == instance_map_.end())
    return false;
  Instance* instance = found->second.get();
  if (instance->IsSinglePathInstance()) {
    auto ids_iter = path_to_id_map_.find(instance->file_info().path);
    CHECK(ids_iter != path_to_id_map_.end());
    ids_iter->second.erase(filesystem_id);
    if (ids_iter->second.empty())
      path_to_id_map_.erase(ids_iter);
  }
  instance_map_.erase(found);
  return true;
}

std::string IsolatedContext::GetNewFileSystemId() const {
  // Returns an arbitrary random string which must be unique in the map.
  lock_.AssertAcquired();
  uint8_t random_data[16];
  std::string id;
  do {
    base::RandBytes(random_data);
    id = base::HexEncode(random_data);
  } while (base::Contains(instance_map_, id));
  return id;
}

}  // namespace storage