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