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