Home | History | Annotate | Download | only in fileapi
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "webkit/browser/fileapi/isolated_context.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/files/file_path.h"
      9 #include "base/logging.h"
     10 #include "base/rand_util.h"
     11 #include "base/stl_util.h"
     12 #include "base/strings/string_number_conversions.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "webkit/browser/fileapi/file_system_url.h"
     16 
     17 namespace fileapi {
     18 
     19 namespace {
     20 
     21 base::FilePath::StringType GetRegisterNameForPath(const base::FilePath& path) {
     22   // If it's not a root path simply return a base name.
     23   if (path.DirName() != path)
     24     return path.BaseName().value();
     25 
     26 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
     27   base::FilePath::StringType name;
     28   for (size_t i = 0;
     29        i < path.value().size() && !base::FilePath::IsSeparator(path.value()[i]);
     30        ++i) {
     31     if (path.value()[i] == L':') {
     32       name.append(L"_drive");
     33       break;
     34     }
     35     name.append(1, path.value()[i]);
     36   }
     37   return name;
     38 #else
     39   return FILE_PATH_LITERAL("<root>");
     40 #endif
     41 }
     42 
     43 bool IsSinglePathIsolatedFileSystem(FileSystemType type) {
     44   DCHECK_NE(kFileSystemTypeUnknown, type);
     45   // As of writing dragged file system is the only filesystem which could have
     46   // multiple top-level paths.
     47   return type != kFileSystemTypeDragged;
     48 }
     49 
     50 static base::LazyInstance<IsolatedContext>::Leaky g_isolated_context =
     51     LAZY_INSTANCE_INITIALIZER;
     52 
     53 }  // namespace
     54 
     55 IsolatedContext::FileInfoSet::FileInfoSet() {}
     56 IsolatedContext::FileInfoSet::~FileInfoSet() {}
     57 
     58 bool IsolatedContext::FileInfoSet::AddPath(
     59     const base::FilePath& path, std::string* registered_name) {
     60   // The given path should not contain any '..' and should be absolute.
     61   if (path.ReferencesParent() || !path.IsAbsolute())
     62     return false;
     63   base::FilePath::StringType name = GetRegisterNameForPath(path);
     64   std::string utf8name = base::FilePath(name).AsUTF8Unsafe();
     65   base::FilePath normalized_path = path.NormalizePathSeparators();
     66   bool inserted =
     67       fileset_.insert(MountPointInfo(utf8name, normalized_path)).second;
     68   if (!inserted) {
     69     int suffix = 1;
     70     std::string basepart =
     71         base::FilePath(name).RemoveExtension().AsUTF8Unsafe();
     72     std::string ext =
     73         base::FilePath(base::FilePath(name).Extension()).AsUTF8Unsafe();
     74     while (!inserted) {
     75       utf8name = base::StringPrintf("%s (%d)", basepart.c_str(), suffix++);
     76       if (!ext.empty())
     77         utf8name.append(ext);
     78       inserted =
     79           fileset_.insert(MountPointInfo(utf8name, normalized_path)).second;
     80     }
     81   }
     82   if (registered_name)
     83     *registered_name = utf8name;
     84   return true;
     85 }
     86 
     87 bool IsolatedContext::FileInfoSet::AddPathWithName(
     88     const base::FilePath& path, const std::string& name) {
     89   // The given path should not contain any '..' and should be absolute.
     90   if (path.ReferencesParent() || !path.IsAbsolute())
     91     return false;
     92   return fileset_.insert(
     93       MountPointInfo(name, path.NormalizePathSeparators())).second;
     94 }
     95 
     96 //--------------------------------------------------------------------------
     97 
     98 class IsolatedContext::Instance {
     99  public:
    100   enum PathType {
    101     PLATFORM_PATH,
    102     VIRTUAL_PATH
    103   };
    104 
    105   // For a single-path isolated file system, which could be registered by
    106   // IsolatedContext::RegisterFileSystemForPath() or
    107   // IsolatedContext::RegisterFileSystemForVirtualPath().
    108   // Most of isolated file system contexts should be of this type.
    109   Instance(FileSystemType type,
    110            const std::string& filesystem_id,
    111            const MountPointInfo& file_info,
    112            PathType path_type);
    113 
    114   // For a multi-paths isolated file system.  As of writing only file system
    115   // type which could have multi-paths is Dragged file system, and
    116   // could be registered by IsolatedContext::RegisterDraggedFileSystem().
    117   Instance(FileSystemType type, const std::set<MountPointInfo>& files);
    118 
    119   ~Instance();
    120 
    121   FileSystemType type() const { return type_; }
    122   const std::string& filesystem_id() const { return filesystem_id_; }
    123   const MountPointInfo& file_info() const { return file_info_; }
    124   const std::set<MountPointInfo>& files() const { return files_; }
    125   int ref_counts() const { return ref_counts_; }
    126 
    127   void AddRef() { ++ref_counts_; }
    128   void RemoveRef() { --ref_counts_; }
    129 
    130   bool ResolvePathForName(const std::string& name, base::FilePath* path) const;
    131 
    132   // Returns true if the instance is a single-path instance.
    133   bool IsSinglePathInstance() const;
    134 
    135  private:
    136   const FileSystemType type_;
    137   const std::string filesystem_id_;
    138 
    139   // For single-path instance.
    140   const MountPointInfo file_info_;
    141   const PathType path_type_;
    142 
    143   // For multiple-path instance (e.g. dragged file system).
    144   const std::set<MountPointInfo> files_;
    145 
    146   // Reference counts. Note that an isolated filesystem is created with ref==0
    147   // and will get deleted when the ref count reaches <=0.
    148   int ref_counts_;
    149 
    150   DISALLOW_COPY_AND_ASSIGN(Instance);
    151 };
    152 
    153 IsolatedContext::Instance::Instance(FileSystemType type,
    154                                     const std::string& filesystem_id,
    155                                     const MountPointInfo& file_info,
    156                                     Instance::PathType path_type)
    157     : type_(type),
    158       filesystem_id_(filesystem_id),
    159       file_info_(file_info),
    160       path_type_(path_type),
    161       ref_counts_(0) {
    162   DCHECK(IsSinglePathIsolatedFileSystem(type_));
    163 }
    164 
    165 IsolatedContext::Instance::Instance(FileSystemType type,
    166                                     const std::set<MountPointInfo>& files)
    167     : type_(type),
    168       path_type_(PLATFORM_PATH),
    169       files_(files),
    170       ref_counts_(0) {
    171   DCHECK(!IsSinglePathIsolatedFileSystem(type_));
    172 }
    173 
    174 IsolatedContext::Instance::~Instance() {}
    175 
    176 bool IsolatedContext::Instance::ResolvePathForName(const std::string& name,
    177                                                    base::FilePath* path) const {
    178   if (IsSinglePathIsolatedFileSystem(type_)) {
    179     switch (path_type_) {
    180       case PLATFORM_PATH:
    181         *path = file_info_.path;
    182         break;
    183       case VIRTUAL_PATH:
    184         *path = base::FilePath();
    185         break;
    186       default:
    187         NOTREACHED();
    188     }
    189 
    190     return file_info_.name == name;
    191   }
    192   std::set<MountPointInfo>::const_iterator found = files_.find(
    193       MountPointInfo(name, base::FilePath()));
    194   if (found == files_.end())
    195     return false;
    196   *path = found->path;
    197   return true;
    198 }
    199 
    200 bool IsolatedContext::Instance::IsSinglePathInstance() const {
    201   return IsSinglePathIsolatedFileSystem(type_);
    202 }
    203 
    204 //--------------------------------------------------------------------------
    205 
    206 // static
    207 IsolatedContext* IsolatedContext::GetInstance() {
    208   return g_isolated_context.Pointer();
    209 }
    210 
    211 // static
    212 bool IsolatedContext::IsIsolatedType(FileSystemType type) {
    213   return type == kFileSystemTypeIsolated || type == kFileSystemTypeExternal;
    214 }
    215 
    216 std::string IsolatedContext::RegisterDraggedFileSystem(
    217     const FileInfoSet& files) {
    218   base::AutoLock locker(lock_);
    219   std::string filesystem_id = GetNewFileSystemId();
    220   instance_map_[filesystem_id] = new Instance(
    221       kFileSystemTypeDragged, files.fileset());
    222   return filesystem_id;
    223 }
    224 
    225 std::string IsolatedContext::RegisterFileSystemForPath(
    226     FileSystemType type,
    227     const std::string& filesystem_id,
    228     const base::FilePath& path_in,
    229     std::string* register_name) {
    230   base::FilePath path(path_in.NormalizePathSeparators());
    231   if (path.ReferencesParent() || !path.IsAbsolute())
    232     return std::string();
    233   std::string name;
    234   if (register_name && !register_name->empty()) {
    235     name = *register_name;
    236   } else {
    237     name = base::FilePath(GetRegisterNameForPath(path)).AsUTF8Unsafe();
    238     if (register_name)
    239       register_name->assign(name);
    240   }
    241 
    242   base::AutoLock locker(lock_);
    243   std::string new_id = GetNewFileSystemId();
    244   instance_map_[new_id] = new Instance(type, filesystem_id,
    245                                        MountPointInfo(name, path),
    246                                        Instance::PLATFORM_PATH);
    247   path_to_id_map_[path].insert(new_id);
    248   return new_id;
    249 }
    250 
    251 std::string IsolatedContext::RegisterFileSystemForVirtualPath(
    252     FileSystemType type,
    253     const std::string& register_name,
    254     const base::FilePath& cracked_path_prefix) {
    255   base::AutoLock locker(lock_);
    256   base::FilePath path(cracked_path_prefix.NormalizePathSeparators());
    257   if (path.ReferencesParent())
    258     return std::string();
    259   std::string filesystem_id = GetNewFileSystemId();
    260   instance_map_[filesystem_id] = new Instance(
    261       type,
    262       std::string(),  // filesystem_id
    263       MountPointInfo(register_name, cracked_path_prefix),
    264       Instance::VIRTUAL_PATH);
    265   path_to_id_map_[path].insert(filesystem_id);
    266   return filesystem_id;
    267 }
    268 
    269 bool IsolatedContext::HandlesFileSystemMountType(FileSystemType type) const {
    270   return type == kFileSystemTypeIsolated;
    271 }
    272 
    273 bool IsolatedContext::RevokeFileSystem(const std::string& filesystem_id) {
    274   base::AutoLock locker(lock_);
    275   return UnregisterFileSystem(filesystem_id);
    276 }
    277 
    278 bool IsolatedContext::GetRegisteredPath(
    279     const std::string& filesystem_id, base::FilePath* path) const {
    280   DCHECK(path);
    281   base::AutoLock locker(lock_);
    282   IDToInstance::const_iterator found = instance_map_.find(filesystem_id);
    283   if (found == instance_map_.end() || !found->second->IsSinglePathInstance())
    284     return false;
    285   *path = found->second->file_info().path;
    286   return true;
    287 }
    288 
    289 bool IsolatedContext::CrackVirtualPath(
    290     const base::FilePath& virtual_path,
    291     std::string* id_or_name,
    292     FileSystemType* type,
    293     std::string* cracked_id,
    294     base::FilePath* path,
    295     FileSystemMountOption* mount_option) const {
    296   DCHECK(id_or_name);
    297   DCHECK(path);
    298 
    299   // This should not contain any '..' references.
    300   if (virtual_path.ReferencesParent())
    301     return false;
    302 
    303   // Set the default mount option.
    304   *mount_option = FileSystemMountOption();
    305 
    306   // The virtual_path should comprise <id_or_name> and <relative_path> parts.
    307   std::vector<base::FilePath::StringType> components;
    308   virtual_path.GetComponents(&components);
    309   if (components.size() < 1)
    310     return false;
    311   std::vector<base::FilePath::StringType>::iterator component_iter =
    312       components.begin();
    313   std::string fsid = base::FilePath(*component_iter++).MaybeAsASCII();
    314   if (fsid.empty())
    315     return false;
    316 
    317   base::FilePath cracked_path;
    318   {
    319     base::AutoLock locker(lock_);
    320     IDToInstance::const_iterator found_instance = instance_map_.find(fsid);
    321     if (found_instance == instance_map_.end())
    322       return false;
    323     *id_or_name = fsid;
    324     const Instance* instance = found_instance->second;
    325     if (type)
    326       *type = instance->type();
    327     if (cracked_id)
    328       *cracked_id = instance->filesystem_id();
    329 
    330     if (component_iter == components.end()) {
    331       // The virtual root case.
    332       path->clear();
    333       return true;
    334     }
    335 
    336     // *component_iter should be a name of the registered path.
    337     std::string name = base::FilePath(*component_iter++).AsUTF8Unsafe();
    338     if (!instance->ResolvePathForName(name, &cracked_path))
    339       return false;
    340   }
    341 
    342   for (; component_iter != components.end(); ++component_iter)
    343     cracked_path = cracked_path.Append(*component_iter);
    344   *path = cracked_path;
    345   return true;
    346 }
    347 
    348 FileSystemURL IsolatedContext::CrackURL(const GURL& url) const {
    349   FileSystemURL filesystem_url = FileSystemURL(url);
    350   if (!filesystem_url.is_valid())
    351     return FileSystemURL();
    352   return CrackFileSystemURL(filesystem_url);
    353 }
    354 
    355 FileSystemURL IsolatedContext::CreateCrackedFileSystemURL(
    356     const GURL& origin,
    357     FileSystemType type,
    358     const base::FilePath& path) const {
    359   return CrackFileSystemURL(FileSystemURL(origin, type, path));
    360 }
    361 
    362 void IsolatedContext::RevokeFileSystemByPath(const base::FilePath& path_in) {
    363   base::AutoLock locker(lock_);
    364   base::FilePath path(path_in.NormalizePathSeparators());
    365   PathToID::iterator ids_iter = path_to_id_map_.find(path);
    366   if (ids_iter == path_to_id_map_.end())
    367     return;
    368   std::set<std::string>& ids = ids_iter->second;
    369   for (std::set<std::string>::iterator iter = ids.begin();
    370        iter != ids.end(); ++iter) {
    371     IDToInstance::iterator found = instance_map_.find(*iter);
    372     if (found != instance_map_.end()) {
    373       delete found->second;
    374       instance_map_.erase(found);
    375     }
    376   }
    377   path_to_id_map_.erase(ids_iter);
    378 }
    379 
    380 void IsolatedContext::AddReference(const std::string& filesystem_id) {
    381   base::AutoLock locker(lock_);
    382   DCHECK(instance_map_.find(filesystem_id) != instance_map_.end());
    383   instance_map_[filesystem_id]->AddRef();
    384 }
    385 
    386 void IsolatedContext::RemoveReference(const std::string& filesystem_id) {
    387   base::AutoLock locker(lock_);
    388   // This could get called for non-existent filesystem if it has been
    389   // already deleted by RevokeFileSystemByPath.
    390   IDToInstance::iterator found = instance_map_.find(filesystem_id);
    391   if (found == instance_map_.end())
    392     return;
    393   Instance* instance = found->second;
    394   DCHECK_GT(instance->ref_counts(), 0);
    395   instance->RemoveRef();
    396   if (instance->ref_counts() == 0) {
    397     bool deleted = UnregisterFileSystem(filesystem_id);
    398     DCHECK(deleted);
    399   }
    400 }
    401 
    402 bool IsolatedContext::GetDraggedFileInfo(
    403     const std::string& filesystem_id,
    404     std::vector<MountPointInfo>* files) const {
    405   DCHECK(files);
    406   base::AutoLock locker(lock_);
    407   IDToInstance::const_iterator found = instance_map_.find(filesystem_id);
    408   if (found == instance_map_.end() ||
    409       found->second->type() != kFileSystemTypeDragged)
    410     return false;
    411   files->assign(found->second->files().begin(),
    412                 found->second->files().end());
    413   return true;
    414 }
    415 
    416 base::FilePath IsolatedContext::CreateVirtualRootPath(
    417     const std::string& filesystem_id) const {
    418   return base::FilePath().AppendASCII(filesystem_id);
    419 }
    420 
    421 IsolatedContext::IsolatedContext() {
    422 }
    423 
    424 IsolatedContext::~IsolatedContext() {
    425   STLDeleteContainerPairSecondPointers(instance_map_.begin(),
    426                                        instance_map_.end());
    427 }
    428 
    429 FileSystemURL IsolatedContext::CrackFileSystemURL(
    430     const FileSystemURL& url) const {
    431   if (!HandlesFileSystemMountType(url.type()))
    432     return FileSystemURL();
    433 
    434   std::string mount_name;
    435   std::string cracked_mount_name;
    436   FileSystemType cracked_type;
    437   base::FilePath cracked_path;
    438   FileSystemMountOption cracked_mount_option;
    439   if (!CrackVirtualPath(url.path(), &mount_name, &cracked_type,
    440                         &cracked_mount_name, &cracked_path,
    441                         &cracked_mount_option)) {
    442     return FileSystemURL();
    443   }
    444 
    445   return FileSystemURL(
    446       url.origin(), url.mount_type(), url.virtual_path(),
    447       !url.filesystem_id().empty() ? url.filesystem_id() : mount_name,
    448       cracked_type, cracked_path,
    449       cracked_mount_name.empty() ? mount_name : cracked_mount_name,
    450       cracked_mount_option);
    451 }
    452 
    453 bool IsolatedContext::UnregisterFileSystem(const std::string& filesystem_id) {
    454   lock_.AssertAcquired();
    455   IDToInstance::iterator found = instance_map_.find(filesystem_id);
    456   if (found == instance_map_.end())
    457     return false;
    458   Instance* instance = found->second;
    459   if (instance->IsSinglePathInstance()) {
    460     PathToID::iterator ids_iter = path_to_id_map_.find(
    461         instance->file_info().path);
    462     DCHECK(ids_iter != path_to_id_map_.end());
    463     ids_iter->second.erase(filesystem_id);
    464     if (ids_iter->second.empty())
    465       path_to_id_map_.erase(ids_iter);
    466   }
    467   delete found->second;
    468   instance_map_.erase(found);
    469   return true;
    470 }
    471 
    472 std::string IsolatedContext::GetNewFileSystemId() const {
    473   // Returns an arbitrary random string which must be unique in the map.
    474   lock_.AssertAcquired();
    475   uint32 random_data[4];
    476   std::string id;
    477   do {
    478     base::RandBytes(random_data, sizeof(random_data));
    479     id = base::HexEncode(random_data, sizeof(random_data));
    480   } while (instance_map_.find(id) != instance_map_.end());
    481   return id;
    482 }
    483 
    484 }  // namespace fileapi
    485