text: // Copyright (c) 2013 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 "storage/browser/fileapi/external_mount_points.h"
  
  #include "base/files/file_path.h"
  #include "base/lazy_instance.h"
  #include "base/stl_util.h"
  #include "storage/browser/fileapi/file_system_url.h"
  
  namespace {
  
  // Normalizes file path so it has normalized separators and ends with exactly
  // one separator. Paths have to be normalized this way for use in
  // GetVirtualPath method. Separators cannot be completely stripped, or
  // GetVirtualPath could not working in some edge cases.
  // For example, /a/b/c(1)/d would be erroneously resolved as c/d if the
  // following mount points were registered: "/a/b/c", "/a/b/c(1)". (Note:
  // "/a/b/c" < "/a/b/c(1)" < "/a/b/c/").
  base::FilePath NormalizeFilePath(const base::FilePath& path) {
    if (path.empty())
      return path;
  
    base::FilePath::StringType path_str = path.StripTrailingSeparators().value();
    if (!base::FilePath::IsSeparator(path_str[path_str.length() - 1]))
      path_str.append(FILE_PATH_LITERAL("/"));
  
    return base::FilePath(path_str).NormalizePathSeparators();
  }
  
  bool IsOverlappingMountPathForbidden(storage::FileSystemType type) {
    return type != storage::kFileSystemTypeNativeMedia &&
           type != storage::kFileSystemTypeDeviceMedia;
  }
  
  // Wrapper around ref-counted ExternalMountPoints that will be used to lazily
  // create and initialize LazyInstance system ExternalMountPoints.
  class SystemMountPointsLazyWrapper {
   public:
    SystemMountPointsLazyWrapper()
        : system_mount_points_(storage::ExternalMountPoints::CreateRefCounted()) {
    }
  
    ~SystemMountPointsLazyWrapper() {}
  
    storage::ExternalMountPoints* get() { return system_mount_points_.get(); }
  
   private:
    scoped_refptr<storage::ExternalMountPoints> system_mount_points_;
  };
  
  base::LazyInstance<SystemMountPointsLazyWrapper>::Leaky
      g_external_mount_points = LAZY_INSTANCE_INITIALIZER;
  
  }  // namespace
  
  namespace storage {
  
  class ExternalMountPoints::Instance {
   public:
    Instance(FileSystemType type,
             const base::FilePath& path,
             const FileSystemMountOption& mount_option)
        : type_(type),
          path_(path.StripTrailingSeparators()),
          mount_option_(mount_option) {}
    ~Instance() {}
  
    FileSystemType type() const { return type_; }
    const base::FilePath& path() const { return path_; }
    const FileSystemMountOption& mount_option() const { return mount_option_; }
  
   private:
    const FileSystemType type_;
    const base::FilePath path_;
    const FileSystemMountOption mount_option_;
  
    DISALLOW_COPY_AND_ASSIGN(Instance);
  };
  
  //--------------------------------------------------------------------------
  
  // static
  ExternalMountPoints* ExternalMountPoints::GetSystemInstance() {
    return g_external_mount_points.Pointer()->get();
  }
  
  // static
  scoped_refptr<ExternalMountPoints> ExternalMountPoints::CreateRefCounted() {
    return new ExternalMountPoints();
  }
  
  bool ExternalMountPoints::RegisterFileSystem(
      const std::string& mount_name,
      FileSystemType type,
      const FileSystemMountOption& mount_option,
      const base::FilePath& path_in) {
    base::AutoLock locker(lock_);
  
    base::FilePath path = NormalizeFilePath(path_in);
    if (!ValidateNewMountPoint(mount_name, type, path))
      return false;
  
    instance_map_[mount_name] = new Instance(type, path, mount_option);
    if (!path.empty() && IsOverlappingMountPathForbidden(type))
      path_to_name_map_.insert(std::make_pair(path, mount_name));
    return true;
  }
  
  bool ExternalMountPoints::HandlesFileSystemMountType(
      FileSystemType type) const {
    return type == kFileSystemTypeExternal ||
           type == kFileSystemTypeNativeForPlatformApp;
  }
  
  bool ExternalMountPoints::RevokeFileSystem(const std::string& mount_name) {
    base::AutoLock locker(lock_);
    NameToInstance::iterator found = instance_map_.find(mount_name);
    if (found == instance_map_.end())
      return false;
    Instance* instance = found->second;
    if (IsOverlappingMountPathForbidden(instance->type()))
      path_to_name_map_.erase(NormalizeFilePath(instance->path()));
    delete found->second;
    instance_map_.erase(found);
    return true;
  }
  
  bool ExternalMountPoints::GetRegisteredPath(
      const std::string& filesystem_id, base::FilePath* path) const {
    DCHECK(path);
    base::AutoLock locker(lock_);
    NameToInstance::const_iterator found = instance_map_.find(filesystem_id);
    if (found == instance_map_.end())
      return false;
    *path = found->second->path();
    return true;
  }
  
  bool ExternalMountPoints::CrackVirtualPath(
      const base::FilePath& virtual_path,
      std::string* mount_name,
      FileSystemType* type,
      std::string* cracked_id,
      base::FilePath* path,
      FileSystemMountOption* mount_option) const {
    DCHECK(mount_name);
    DCHECK(path);
  
    // The path should not contain any '..' references.
    if (virtual_path.ReferencesParent())
      return false;
  
    // The virtual_path should comprise of <mount_name> and <relative_path> parts.
    std::vector<base::FilePath::StringType> components;
    virtual_path.GetComponents(&components);
    if (components.size() < 1)
      return false;
  
    std::vector<base::FilePath::StringType>::iterator component_iter =
        components.begin();
    std::string maybe_mount_name =
        base::FilePath(*component_iter++).AsUTF8Unsafe();
  
    base::FilePath cracked_path;
    {
      base::AutoLock locker(lock_);
      NameToInstance::const_iterator found_instance =
          instance_map_.find(maybe_mount_name);
      if (found_instance == instance_map_.end())
        return false;
  
      *mount_name = maybe_mount_name;
      const Instance* instance = found_instance->second;
      if (type)
        *type = instance->type();
      cracked_path = instance->path();
      *mount_option = instance->mount_option();
    }
  
    for (; component_iter != components.end(); ++component_iter)
      cracked_path = cracked_path.Append(*component_iter);
    *path = cracked_path;
    return true;
  }
  
  FileSystemURL ExternalMountPoints::CrackURL(const GURL& url) const {
    FileSystemURL filesystem_url = FileSystemURL(url);
    if (!filesystem_url.is_valid())
      return FileSystemURL();
    return CrackFileSystemURL(filesystem_url);
  }
  
  FileSystemURL ExternalMountPoints::CreateCrackedFileSystemURL(
      const GURL& origin,
      FileSystemType type,
      const base::FilePath& path) const {
    return CrackFileSystemURL(FileSystemURL(origin, type, path));
  }
  
  void ExternalMountPoints::AddMountPointInfosTo(
      std::vector<MountPointInfo>* mount_points) const {
    base::AutoLock locker(lock_);
    DCHECK(mount_points);
    for (NameToInstance::const_iterator iter = instance_map_.begin();
         iter != instance_map_.end(); ++iter) {
      mount_points->push_back(MountPointInfo(iter->first, iter->second->path()));
    }
  }
  
  bool ExternalMountPoints::GetVirtualPath(const base::FilePath& path_in,
                                           base::FilePath* virtual_path) const {
    DCHECK(virtual_path);
  
    base::AutoLock locker(lock_);
  
    base::FilePath path = NormalizeFilePath(path_in);
    std::map<base::FilePath, std::string>::const_reverse_iterator iter(
        path_to_name_map_.upper_bound(path));
    if (iter == path_to_name_map_.rend())
      return false;
  
    *virtual_path = CreateVirtualRootPath(iter->second);
    if (iter->first == path)
      return true;
    return iter->first.AppendRelativePath(path, virtual_path);
  }
  
  base::FilePath ExternalMountPoints::CreateVirtualRootPath(
      const std::string& mount_name) const {
    return base::FilePath().Append(base::FilePath::FromUTF8Unsafe(mount_name));
  }
  
  FileSystemURL ExternalMountPoints::CreateExternalFileSystemURL(
      const GURL& origin,
      const std::string& mount_name,
      const base::FilePath& path) const {
    return CreateCrackedFileSystemURL(
        origin,
        storage::kFileSystemTypeExternal,
        // Avoid using FilePath::Append as path may be an absolute path.
        base::FilePath(CreateVirtualRootPath(mount_name).value() +
                       base::FilePath::kSeparators[0] + path.value()));
  }
  
  void ExternalMountPoints::RevokeAllFileSystems() {
    NameToInstance instance_map_copy;
    {
      base::AutoLock locker(lock_);
      instance_map_copy = instance_map_;
      instance_map_.clear();
      path_to_name_map_.clear();
    }
    STLDeleteContainerPairSecondPointers(instance_map_copy.begin(),
                                         instance_map_copy.end());
  }
  
  ExternalMountPoints::ExternalMountPoints() {}
  
  ExternalMountPoints::~ExternalMountPoints() {
    STLDeleteContainerPairSecondPointers(instance_map_.begin(),
                                         instance_map_.end());
  }
  
  FileSystemURL ExternalMountPoints::CrackFileSystemURL(
      const FileSystemURL& url) const {
    if (!HandlesFileSystemMountType(url.type()))
      return FileSystemURL();
  
    base::FilePath virtual_path = url.path();
    if (url.type() == kFileSystemTypeNativeForPlatformApp) {
  #if defined(OS_CHROMEOS)
      // On Chrome OS, find a mount point and virtual path for the external fs.
      if (!GetVirtualPath(url.path(), &virtual_path))
        return FileSystemURL();
  #else
      // On other OS, it is simply a native local path.
      return FileSystemURL(
          url.origin(), url.mount_type(), url.virtual_path(),
          url.mount_filesystem_id(), kFileSystemTypeNativeLocal,
          url.path(), url.filesystem_id(), url.mount_option());
  #endif
    }
  
    std::string mount_name;
    FileSystemType cracked_type;
    std::string cracked_id;
    base::FilePath cracked_path;
    FileSystemMountOption cracked_mount_option;
  
    if (!CrackVirtualPath(virtual_path, &mount_name, &cracked_type,
                          &cracked_id, &cracked_path, &cracked_mount_option)) {
      return FileSystemURL();
    }
  
    return FileSystemURL(
        url.origin(), url.mount_type(), url.virtual_path(),
        !url.filesystem_id().empty() ? url.filesystem_id() : mount_name,
        cracked_type, cracked_path,
        cracked_id.empty() ? mount_name : cracked_id, cracked_mount_option);
  }
  
  bool ExternalMountPoints::ValidateNewMountPoint(const std::string& mount_name,
                                                  FileSystemType type,
                                                  const base::FilePath& path) {
    lock_.AssertAcquired();
  
    // Mount name must not be empty.
    if (mount_name.empty())
      return false;
  
    // Verify there is no registered mount point with the same name.
    NameToInstance::iterator found = instance_map_.find(mount_name);
    if (found != instance_map_.end())
      return false;
  
    // Allow empty paths.
    if (path.empty())
      return true;
  
    // Verify path is legal.
    if (path.ReferencesParent() || !path.IsAbsolute())
      return false;
  
    if (IsOverlappingMountPathForbidden(type)) {
      // Check there the new path does not overlap with one of the existing ones.
      std::map<base::FilePath, std::string>::reverse_iterator potential_parent(
          path_to_name_map_.upper_bound(path));
      if (potential_parent != path_to_name_map_.rend()) {
        if (potential_parent->first == path ||
            potential_parent->first.IsParent(path)) {
          return false;
        }
      }
  
      std::map<base::FilePath, std::string>::iterator potential_child =
          path_to_name_map_.upper_bound(path);
      if (potential_child != path_to_name_map_.end()) {
        if (potential_child->first == path ||
            path.IsParent(potential_child->first)) {
          return false;
        }
      }
    }
  
    return true;
  }
  
  }  // namespace storage
  