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