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