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