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/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 "storage/browser/fileapi/file_system_url.h"
16
17namespace storage {
18
19namespace {
20
21base::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
43bool 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
50static base::LazyInstance<IsolatedContext>::Leaky g_isolated_context =
51    LAZY_INSTANCE_INITIALIZER;
52
53}  // namespace
54
55IsolatedContext::FileInfoSet::FileInfoSet() {}
56IsolatedContext::FileInfoSet::~FileInfoSet() {}
57
58bool 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
87bool 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
98class 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
153IsolatedContext::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
165IsolatedContext::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
174IsolatedContext::Instance::~Instance() {}
175
176bool 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
200bool IsolatedContext::Instance::IsSinglePathInstance() const {
201  return IsSinglePathIsolatedFileSystem(type_);
202}
203
204//--------------------------------------------------------------------------
205
206// static
207IsolatedContext* IsolatedContext::GetInstance() {
208  return g_isolated_context.Pointer();
209}
210
211// static
212bool IsolatedContext::IsIsolatedType(FileSystemType type) {
213  return type == kFileSystemTypeIsolated || type == kFileSystemTypeExternal;
214}
215
216std::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
225std::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
251std::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
269bool IsolatedContext::HandlesFileSystemMountType(FileSystemType type) const {
270  return type == kFileSystemTypeIsolated;
271}
272
273bool IsolatedContext::RevokeFileSystem(const std::string& filesystem_id) {
274  base::AutoLock locker(lock_);
275  return UnregisterFileSystem(filesystem_id);
276}
277
278bool 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
289bool 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
348FileSystemURL 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
355FileSystemURL IsolatedContext::CreateCrackedFileSystemURL(
356    const GURL& origin,
357    FileSystemType type,
358    const base::FilePath& path) const {
359  return CrackFileSystemURL(FileSystemURL(origin, type, path));
360}
361
362void 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
380void 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
386void 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
402bool 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
416base::FilePath IsolatedContext::CreateVirtualRootPath(
417    const std::string& filesystem_id) const {
418  return base::FilePath().AppendASCII(filesystem_id);
419}
420
421IsolatedContext::IsolatedContext() {
422}
423
424IsolatedContext::~IsolatedContext() {
425  STLDeleteContainerPairSecondPointers(instance_map_.begin(),
426                                       instance_map_.end());
427}
428
429FileSystemURL 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
453bool 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
472std::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 storage
485