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