1// Copyright (c) 2013 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/external_mount_points.h"
6
7#include "base/files/file_path.h"
8#include "base/lazy_instance.h"
9#include "base/path_service.h"
10#include "base/stl_util.h"
11#include "storage/browser/fileapi/file_system_url.h"
12
13namespace {
14
15// Normalizes file path so it has normalized separators and ends with exactly
16// one separator. Paths have to be normalized this way for use in
17// GetVirtualPath method. Separators cannot be completely stripped, or
18// GetVirtualPath could not working in some edge cases.
19// For example, /a/b/c(1)/d would be erroneously resolved as c/d if the
20// following mount points were registered: "/a/b/c", "/a/b/c(1)". (Note:
21// "/a/b/c" < "/a/b/c(1)" < "/a/b/c/").
22base::FilePath NormalizeFilePath(const base::FilePath& path) {
23  if (path.empty())
24    return path;
25
26  base::FilePath::StringType path_str = path.StripTrailingSeparators().value();
27  if (!base::FilePath::IsSeparator(path_str[path_str.length() - 1]))
28    path_str.append(FILE_PATH_LITERAL("/"));
29
30  return base::FilePath(path_str).NormalizePathSeparators();
31}
32
33bool IsOverlappingMountPathForbidden(storage::FileSystemType type) {
34  return type != storage::kFileSystemTypeNativeMedia &&
35         type != storage::kFileSystemTypeDeviceMedia;
36}
37
38// Wrapper around ref-counted ExternalMountPoints that will be used to lazily
39// create and initialize LazyInstance system ExternalMountPoints.
40class SystemMountPointsLazyWrapper {
41 public:
42  SystemMountPointsLazyWrapper()
43      : system_mount_points_(storage::ExternalMountPoints::CreateRefCounted()) {
44  }
45
46  ~SystemMountPointsLazyWrapper() {}
47
48  storage::ExternalMountPoints* get() { return system_mount_points_.get(); }
49
50 private:
51  scoped_refptr<storage::ExternalMountPoints> system_mount_points_;
52};
53
54base::LazyInstance<SystemMountPointsLazyWrapper>::Leaky
55    g_external_mount_points = LAZY_INSTANCE_INITIALIZER;
56
57}  // namespace
58
59namespace storage {
60
61class ExternalMountPoints::Instance {
62 public:
63  Instance(FileSystemType type,
64           const base::FilePath& path,
65           const FileSystemMountOption& mount_option)
66      : type_(type),
67        path_(path.StripTrailingSeparators()),
68        mount_option_(mount_option) {}
69  ~Instance() {}
70
71  FileSystemType type() const { return type_; }
72  const base::FilePath& path() const { return path_; }
73  const FileSystemMountOption& mount_option() const { return mount_option_; }
74
75 private:
76  const FileSystemType type_;
77  const base::FilePath path_;
78  const FileSystemMountOption mount_option_;
79
80  DISALLOW_COPY_AND_ASSIGN(Instance);
81};
82
83//--------------------------------------------------------------------------
84
85// static
86ExternalMountPoints* ExternalMountPoints::GetSystemInstance() {
87  return g_external_mount_points.Pointer()->get();
88}
89
90// static
91scoped_refptr<ExternalMountPoints> ExternalMountPoints::CreateRefCounted() {
92  return new ExternalMountPoints();
93}
94
95bool ExternalMountPoints::RegisterFileSystem(
96    const std::string& mount_name,
97    FileSystemType type,
98    const FileSystemMountOption& mount_option,
99    const base::FilePath& path_in) {
100  // COPY_SYNC_OPTION_SYNC is only applicable to native local file system.
101  DCHECK(type == kFileSystemTypeNativeLocal ||
102         mount_option.copy_sync_option() != COPY_SYNC_OPTION_SYNC);
103
104  base::AutoLock locker(lock_);
105
106  base::FilePath path = NormalizeFilePath(path_in);
107  if (!ValidateNewMountPoint(mount_name, type, path))
108    return false;
109
110  instance_map_[mount_name] = new Instance(type, path, mount_option);
111  if (!path.empty() && IsOverlappingMountPathForbidden(type))
112    path_to_name_map_.insert(std::make_pair(path, mount_name));
113  return true;
114}
115
116bool ExternalMountPoints::HandlesFileSystemMountType(
117    FileSystemType type) const {
118  return type == kFileSystemTypeExternal ||
119         type == kFileSystemTypeNativeForPlatformApp;
120}
121
122bool ExternalMountPoints::RevokeFileSystem(const std::string& mount_name) {
123  base::AutoLock locker(lock_);
124  NameToInstance::iterator found = instance_map_.find(mount_name);
125  if (found == instance_map_.end())
126    return false;
127  Instance* instance = found->second;
128  if (IsOverlappingMountPathForbidden(instance->type()))
129    path_to_name_map_.erase(NormalizeFilePath(instance->path()));
130  delete found->second;
131  instance_map_.erase(found);
132  return true;
133}
134
135bool ExternalMountPoints::GetRegisteredPath(
136    const std::string& filesystem_id, base::FilePath* path) const {
137  DCHECK(path);
138  base::AutoLock locker(lock_);
139  NameToInstance::const_iterator found = instance_map_.find(filesystem_id);
140  if (found == instance_map_.end())
141    return false;
142  *path = found->second->path();
143  return true;
144}
145
146bool ExternalMountPoints::CrackVirtualPath(
147    const base::FilePath& virtual_path,
148    std::string* mount_name,
149    FileSystemType* type,
150    std::string* cracked_id,
151    base::FilePath* path,
152    FileSystemMountOption* mount_option) const {
153  DCHECK(mount_name);
154  DCHECK(path);
155
156  // The path should not contain any '..' references.
157  if (virtual_path.ReferencesParent())
158    return false;
159
160  // The virtual_path should comprise of <mount_name> and <relative_path> parts.
161  std::vector<base::FilePath::StringType> components;
162  virtual_path.GetComponents(&components);
163  if (components.size() < 1)
164    return false;
165
166  std::vector<base::FilePath::StringType>::iterator component_iter =
167      components.begin();
168  std::string maybe_mount_name =
169      base::FilePath(*component_iter++).MaybeAsASCII();
170  if (maybe_mount_name.empty())
171    return false;
172
173  base::FilePath cracked_path;
174  {
175    base::AutoLock locker(lock_);
176    NameToInstance::const_iterator found_instance =
177        instance_map_.find(maybe_mount_name);
178    if (found_instance == instance_map_.end())
179      return false;
180
181    *mount_name = maybe_mount_name;
182    const Instance* instance = found_instance->second;
183    if (type)
184      *type = instance->type();
185    cracked_path = instance->path();
186    *mount_option = instance->mount_option();
187  }
188
189  for (; component_iter != components.end(); ++component_iter)
190    cracked_path = cracked_path.Append(*component_iter);
191  *path = cracked_path;
192  return true;
193}
194
195FileSystemURL ExternalMountPoints::CrackURL(const GURL& url) const {
196  FileSystemURL filesystem_url = FileSystemURL(url);
197  if (!filesystem_url.is_valid())
198    return FileSystemURL();
199  return CrackFileSystemURL(filesystem_url);
200}
201
202FileSystemURL ExternalMountPoints::CreateCrackedFileSystemURL(
203    const GURL& origin,
204    FileSystemType type,
205    const base::FilePath& path) const {
206  return CrackFileSystemURL(FileSystemURL(origin, type, path));
207}
208
209void ExternalMountPoints::AddMountPointInfosTo(
210    std::vector<MountPointInfo>* mount_points) const {
211  base::AutoLock locker(lock_);
212  DCHECK(mount_points);
213  for (NameToInstance::const_iterator iter = instance_map_.begin();
214       iter != instance_map_.end(); ++iter) {
215    mount_points->push_back(MountPointInfo(iter->first, iter->second->path()));
216  }
217}
218
219bool ExternalMountPoints::GetVirtualPath(const base::FilePath& path_in,
220                                         base::FilePath* virtual_path) const {
221  DCHECK(virtual_path);
222
223  base::AutoLock locker(lock_);
224
225  base::FilePath path = NormalizeFilePath(path_in);
226  std::map<base::FilePath, std::string>::const_reverse_iterator iter(
227      path_to_name_map_.upper_bound(path));
228  if (iter == path_to_name_map_.rend())
229    return false;
230
231  *virtual_path = CreateVirtualRootPath(iter->second);
232  if (iter->first == path)
233    return true;
234  return iter->first.AppendRelativePath(path, virtual_path);
235}
236
237base::FilePath ExternalMountPoints::CreateVirtualRootPath(
238    const std::string& mount_name) const {
239  return base::FilePath().AppendASCII(mount_name);
240}
241
242FileSystemURL ExternalMountPoints::CreateExternalFileSystemURL(
243    const GURL& origin,
244    const std::string& mount_name,
245    const base::FilePath& path) const {
246  return CreateCrackedFileSystemURL(
247      origin,
248      storage::kFileSystemTypeExternal,
249      // Avoid using FilePath::Append as path may be an absolute path.
250      base::FilePath(CreateVirtualRootPath(mount_name).value() +
251                     base::FilePath::kSeparators[0] + path.value()));
252}
253
254void ExternalMountPoints::RevokeAllFileSystems() {
255  NameToInstance instance_map_copy;
256  {
257    base::AutoLock locker(lock_);
258    instance_map_copy = instance_map_;
259    instance_map_.clear();
260    path_to_name_map_.clear();
261  }
262  STLDeleteContainerPairSecondPointers(instance_map_copy.begin(),
263                                       instance_map_copy.end());
264}
265
266ExternalMountPoints::ExternalMountPoints() {}
267
268ExternalMountPoints::~ExternalMountPoints() {
269  STLDeleteContainerPairSecondPointers(instance_map_.begin(),
270                                       instance_map_.end());
271}
272
273FileSystemURL ExternalMountPoints::CrackFileSystemURL(
274    const FileSystemURL& url) const {
275  if (!HandlesFileSystemMountType(url.type()))
276    return FileSystemURL();
277
278  base::FilePath virtual_path = url.path();
279  if (url.type() == kFileSystemTypeNativeForPlatformApp) {
280#if defined(OS_CHROMEOS)
281    // On Chrome OS, find a mount point and virtual path for the external fs.
282    if (!GetVirtualPath(url.path(), &virtual_path))
283      return FileSystemURL();
284#else
285    // On other OS, it is simply a native local path.
286    return FileSystemURL(
287        url.origin(), url.mount_type(), url.virtual_path(),
288        url.mount_filesystem_id(), kFileSystemTypeNativeLocal,
289        url.path(), url.filesystem_id(), url.mount_option());
290#endif
291  }
292
293  std::string mount_name;
294  FileSystemType cracked_type;
295  std::string cracked_id;
296  base::FilePath cracked_path;
297  FileSystemMountOption cracked_mount_option;
298
299  if (!CrackVirtualPath(virtual_path, &mount_name, &cracked_type,
300                        &cracked_id, &cracked_path, &cracked_mount_option)) {
301    return FileSystemURL();
302  }
303
304  return FileSystemURL(
305      url.origin(), url.mount_type(), url.virtual_path(),
306      !url.filesystem_id().empty() ? url.filesystem_id() : mount_name,
307      cracked_type, cracked_path,
308      cracked_id.empty() ? mount_name : cracked_id, cracked_mount_option);
309}
310
311bool ExternalMountPoints::ValidateNewMountPoint(const std::string& mount_name,
312                                                FileSystemType type,
313                                                const base::FilePath& path) {
314  lock_.AssertAcquired();
315
316  // Mount name must not be empty.
317  if (mount_name.empty())
318    return false;
319
320  // Verify there is no registered mount point with the same name.
321  NameToInstance::iterator found = instance_map_.find(mount_name);
322  if (found != instance_map_.end())
323    return false;
324
325  // Allow empty paths.
326  if (path.empty())
327    return true;
328
329  // Verify path is legal.
330  if (path.ReferencesParent() || !path.IsAbsolute())
331    return false;
332
333  if (IsOverlappingMountPathForbidden(type)) {
334    // Check there the new path does not overlap with one of the existing ones.
335    std::map<base::FilePath, std::string>::reverse_iterator potential_parent(
336        path_to_name_map_.upper_bound(path));
337    if (potential_parent != path_to_name_map_.rend()) {
338      if (potential_parent->first == path ||
339          potential_parent->first.IsParent(path)) {
340        return false;
341      }
342    }
343
344    std::map<base::FilePath, std::string>::iterator potential_child =
345        path_to_name_map_.upper_bound(path);
346    if (potential_child != path_to_name_map_.end()) {
347      if (potential_child->first == path ||
348          path.IsParent(potential_child->first)) {
349        return false;
350      }
351    }
352  }
353
354  return true;
355}
356
357}  // namespace storage
358