| // 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 "storage/browser/fileapi/isolated_context.h" | 
 |  | 
 | #include <stddef.h> | 
 | #include <stdint.h> | 
 |  | 
 | #include "base/logging.h" | 
 | #include "base/macros.h" | 
 | #include "base/rand_util.h" | 
 | #include "base/stl_util.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "storage/browser/fileapi/file_system_url.h" | 
 |  | 
 | namespace storage { | 
 |  | 
 | namespace { | 
 |  | 
 | 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() {} | 
 | IsolatedContext::FileInfoSet::~FileInfoSet() {} | 
 |  | 
 | bool IsolatedContext::FileInfoSet::AddPath( | 
 |     const base::FilePath& path, std::string* registered_name) { | 
 |   // The given path should not contain any '..' and should be absolute. | 
 |   if (path.ReferencesParent() || !path.IsAbsolute()) | 
 |     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) { | 
 |   // The given path should not contain any '..' and should be absolute. | 
 |   if (path.ReferencesParent() || !path.IsAbsolute()) | 
 |     return false; | 
 |   return fileset_.insert( | 
 |       MountPointInfo(name, path.NormalizePathSeparators())).second; | 
 | } | 
 |  | 
 | //-------------------------------------------------------------------------- | 
 |  | 
 | 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(); | 
 |  | 
 |   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_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(Instance); | 
 | }; | 
 |  | 
 | 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() {} | 
 |  | 
 | 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; | 
 |   } | 
 |   std::set<MountPointInfo>::const_iterator 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] = new Instance( | 
 |       kFileSystemTypeDragged, files.fileset()); | 
 |   return filesystem_id; | 
 | } | 
 |  | 
 | std::string 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 (path.ReferencesParent() || !path.IsAbsolute()) | 
 |     return std::string(); | 
 |   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); | 
 |   } | 
 |  | 
 |   base::AutoLock locker(lock_); | 
 |   std::string new_id = GetNewFileSystemId(); | 
 |   instance_map_[new_id] = new Instance(type, filesystem_id, | 
 |                                        MountPointInfo(name, path), | 
 |                                        Instance::PLATFORM_PATH); | 
 |   path_to_id_map_[path].insert(new_id); | 
 |   return 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] = new 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_); | 
 |   IDToInstance::const_iterator 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(&components); | 
 |   if (components.size() < 1) | 
 |     return false; | 
 |   std::vector<base::FilePath::StringType>::iterator 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_); | 
 |     IDToInstance::const_iterator found_instance = instance_map_.find(fsid); | 
 |     if (found_instance == instance_map_.end()) | 
 |       return false; | 
 |     *id_or_name = fsid; | 
 |     const Instance* instance = found_instance->second; | 
 |     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 { | 
 |   FileSystemURL filesystem_url = FileSystemURL(url); | 
 |   if (!filesystem_url.is_valid()) | 
 |     return FileSystemURL(); | 
 |   return CrackFileSystemURL(filesystem_url); | 
 | } | 
 |  | 
 | FileSystemURL IsolatedContext::CreateCrackedFileSystemURL( | 
 |     const GURL& origin, | 
 |     FileSystemType type, | 
 |     const base::FilePath& path) const { | 
 |   return CrackFileSystemURL(FileSystemURL(origin, type, path)); | 
 | } | 
 |  | 
 | void IsolatedContext::RevokeFileSystemByPath(const base::FilePath& path_in) { | 
 |   base::AutoLock locker(lock_); | 
 |   base::FilePath path(path_in.NormalizePathSeparators()); | 
 |   PathToID::iterator ids_iter = path_to_id_map_.find(path); | 
 |   if (ids_iter == path_to_id_map_.end()) | 
 |     return; | 
 |   std::set<std::string>& ids = ids_iter->second; | 
 |   for (std::set<std::string>::iterator iter = ids.begin(); | 
 |        iter != ids.end(); ++iter) { | 
 |     IDToInstance::iterator found = instance_map_.find(*iter); | 
 |     if (found != instance_map_.end()) { | 
 |       delete found->second; | 
 |       instance_map_.erase(found); | 
 |     } | 
 |   } | 
 |   path_to_id_map_.erase(ids_iter); | 
 | } | 
 |  | 
 | void IsolatedContext::AddReference(const std::string& filesystem_id) { | 
 |   base::AutoLock locker(lock_); | 
 |   DCHECK(instance_map_.find(filesystem_id) != instance_map_.end()); | 
 |   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. | 
 |   IDToInstance::iterator found = instance_map_.find(filesystem_id); | 
 |   if (found == instance_map_.end()) | 
 |     return; | 
 |   Instance* instance = found->second; | 
 |   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_); | 
 |   IDToInstance::const_iterator 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() { | 
 | } | 
 |  | 
 | IsolatedContext::~IsolatedContext() { | 
 |   STLDeleteContainerPairSecondPointers(instance_map_.begin(), | 
 |                                        instance_map_.end()); | 
 | } | 
 |  | 
 | 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.origin(), 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(); | 
 |   IDToInstance::iterator found = instance_map_.find(filesystem_id); | 
 |   if (found == instance_map_.end()) | 
 |     return false; | 
 |   Instance* instance = found->second; | 
 |   if (instance->IsSinglePathInstance()) { | 
 |     PathToID::iterator ids_iter = path_to_id_map_.find( | 
 |         instance->file_info().path); | 
 |     DCHECK(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); | 
 |   } | 
 |   delete found->second; | 
 |   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(); | 
 |   uint32_t random_data[4]; | 
 |   std::string id; | 
 |   do { | 
 |     base::RandBytes(random_data, sizeof(random_data)); | 
 |     id = base::HexEncode(random_data, sizeof(random_data)); | 
 |   } while (instance_map_.find(id) != instance_map_.end()); | 
 |   return id; | 
 | } | 
 |  | 
 | }  // namespace storage |