1 // Copyright 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 "webkit/browser/fileapi/sandbox_context.h" 6 7 #include "base/command_line.h" 8 #include "base/file_util.h" 9 #include "base/metrics/histogram.h" 10 #include "base/stl_util.h" 11 #include "base/task_runner_util.h" 12 #include "net/base/net_util.h" 13 #include "webkit/browser/fileapi/async_file_util_adapter.h" 14 #include "webkit/browser/fileapi/file_system_context.h" 15 #include "webkit/browser/fileapi/file_system_operation_context.h" 16 #include "webkit/browser/fileapi/file_system_url.h" 17 #include "webkit/browser/fileapi/file_system_usage_cache.h" 18 #include "webkit/browser/fileapi/obfuscated_file_util.h" 19 #include "webkit/browser/fileapi/sandbox_file_system_backend.h" 20 #include "webkit/browser/fileapi/sandbox_quota_observer.h" 21 #include "webkit/browser/quota/quota_manager.h" 22 #include "webkit/common/fileapi/file_system_util.h" 23 24 namespace fileapi { 25 26 namespace { 27 28 const char kOpenFileSystemLabel[] = "FileSystem.OpenFileSystem"; 29 const char kOpenFileSystemDetailLabel[] = "FileSystem.OpenFileSystemDetail"; 30 const char kOpenFileSystemDetailNonThrottledLabel[] = 31 "FileSystem.OpenFileSystemDetailNonthrottled"; 32 int64 kMinimumStatsCollectionIntervalHours = 1; 33 34 enum FileSystemError { 35 kOK = 0, 36 kIncognito, 37 kInvalidSchemeError, 38 kCreateDirectoryError, 39 kNotFound, 40 kUnknownError, 41 kFileSystemErrorMax, 42 }; 43 44 // Restricted names. 45 // http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions 46 const base::FilePath::CharType* const kRestrictedNames[] = { 47 FILE_PATH_LITERAL("."), FILE_PATH_LITERAL(".."), 48 }; 49 50 // Restricted chars. 51 const base::FilePath::CharType kRestrictedChars[] = { 52 FILE_PATH_LITERAL('/'), FILE_PATH_LITERAL('\\'), 53 }; 54 55 class ObfuscatedOriginEnumerator 56 : public SandboxContext::OriginEnumerator { 57 public: 58 explicit ObfuscatedOriginEnumerator(ObfuscatedFileUtil* file_util) { 59 enum_.reset(file_util->CreateOriginEnumerator()); 60 } 61 virtual ~ObfuscatedOriginEnumerator() {} 62 63 virtual GURL Next() OVERRIDE { 64 return enum_->Next(); 65 } 66 67 virtual bool HasFileSystemType(fileapi::FileSystemType type) const OVERRIDE { 68 return enum_->HasFileSystemType(type); 69 } 70 71 private: 72 scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enum_; 73 }; 74 75 void OpenFileSystemOnFileThread( 76 ObfuscatedFileUtil* file_util, 77 const GURL& origin_url, 78 FileSystemType type, 79 OpenFileSystemMode mode, 80 base::PlatformFileError* error_ptr) { 81 DCHECK(error_ptr); 82 const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT); 83 file_util->GetDirectoryForOriginAndType(origin_url, type, create, error_ptr); 84 if (*error_ptr != base::PLATFORM_FILE_OK) { 85 UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel, 86 kCreateDirectoryError, 87 kFileSystemErrorMax); 88 } else { 89 UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel, kOK, kFileSystemErrorMax); 90 } 91 // The reference of file_util will be derefed on the FILE thread 92 // when the storage of this callback gets deleted regardless of whether 93 // this method is called or not. 94 } 95 96 void DidOpenFileSystem( 97 base::WeakPtr<SandboxContext> sandbox_context, 98 const base::Callback<void(base::PlatformFileError error)>& callback, 99 base::PlatformFileError* error) { 100 if (sandbox_context.get()) 101 sandbox_context.get()->CollectOpenFileSystemMetrics(*error); 102 callback.Run(*error); 103 } 104 105 } // namespace 106 107 const base::FilePath::CharType 108 SandboxContext::kFileSystemDirectory[] = FILE_PATH_LITERAL("File System"); 109 110 SandboxContext::SandboxContext( 111 quota::QuotaManagerProxy* quota_manager_proxy, 112 base::SequencedTaskRunner* file_task_runner, 113 const base::FilePath& profile_path, 114 quota::SpecialStoragePolicy* special_storage_policy, 115 const FileSystemOptions& file_system_options) 116 : file_task_runner_(file_task_runner), 117 sandbox_file_util_(new AsyncFileUtilAdapter( 118 new ObfuscatedFileUtil( 119 special_storage_policy, 120 profile_path.Append(kFileSystemDirectory), 121 file_task_runner))), 122 file_system_usage_cache_(new FileSystemUsageCache(file_task_runner)), 123 quota_observer_(new SandboxQuotaObserver( 124 quota_manager_proxy, 125 file_task_runner, 126 sync_file_util(), 127 usage_cache())), 128 special_storage_policy_(special_storage_policy), 129 file_system_options_(file_system_options), 130 weak_factory_(this) { 131 } 132 133 SandboxContext::~SandboxContext() { 134 if (!file_task_runner_->RunsTasksOnCurrentThread()) { 135 AsyncFileUtilAdapter* sandbox_file_util = sandbox_file_util_.release(); 136 SandboxQuotaObserver* quota_observer = quota_observer_.release(); 137 FileSystemUsageCache* file_system_usage_cache = 138 file_system_usage_cache_.release(); 139 if (!file_task_runner_->DeleteSoon(FROM_HERE, sandbox_file_util)) 140 delete sandbox_file_util; 141 if (!file_task_runner_->DeleteSoon(FROM_HERE, quota_observer)) 142 delete quota_observer; 143 if (!file_task_runner_->DeleteSoon(FROM_HERE, file_system_usage_cache)) 144 delete file_system_usage_cache; 145 } 146 } 147 148 bool SandboxContext::IsAccessValid(const FileSystemURL& url) const { 149 if (!IsAllowedScheme(url.origin())) 150 return false; 151 152 if (url.path().ReferencesParent()) 153 return false; 154 155 // Return earlier if the path is '/', because VirtualPath::BaseName() 156 // returns '/' for '/' and we fail the "basename != '/'" check below. 157 // (We exclude '.' because it's disallowed by spec.) 158 if (VirtualPath::IsRootPath(url.path()) && 159 url.path() != base::FilePath(base::FilePath::kCurrentDirectory)) 160 return true; 161 162 // Restricted names specified in 163 // http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions 164 base::FilePath filename = VirtualPath::BaseName(url.path()); 165 // See if the name is allowed to create. 166 for (size_t i = 0; i < arraysize(kRestrictedNames); ++i) { 167 if (filename.value() == kRestrictedNames[i]) 168 return false; 169 } 170 for (size_t i = 0; i < arraysize(kRestrictedChars); ++i) { 171 if (filename.value().find(kRestrictedChars[i]) != 172 base::FilePath::StringType::npos) 173 return false; 174 } 175 176 return true; 177 } 178 179 bool SandboxContext::IsAllowedScheme(const GURL& url) const { 180 // Basically we only accept http or https. We allow file:// URLs 181 // only if --allow-file-access-from-files flag is given. 182 if (url.SchemeIs("http") || url.SchemeIs("https")) 183 return true; 184 if (url.SchemeIsFileSystem()) 185 return url.inner_url() && IsAllowedScheme(*url.inner_url()); 186 187 for (size_t i = 0; 188 i < file_system_options_.additional_allowed_schemes().size(); 189 ++i) { 190 if (url.SchemeIs( 191 file_system_options_.additional_allowed_schemes()[i].c_str())) 192 return true; 193 } 194 return false; 195 } 196 197 SandboxContext::OriginEnumerator* SandboxContext::CreateOriginEnumerator() { 198 return new ObfuscatedOriginEnumerator(sync_file_util()); 199 } 200 201 base::FilePath SandboxContext::GetBaseDirectoryForOriginAndType( 202 const GURL& origin_url, fileapi::FileSystemType type, bool create) { 203 base::PlatformFileError error = base::PLATFORM_FILE_OK; 204 base::FilePath path = sync_file_util()->GetDirectoryForOriginAndType( 205 origin_url, type, create, &error); 206 if (error != base::PLATFORM_FILE_OK) 207 return base::FilePath(); 208 return path; 209 } 210 211 void SandboxContext::OpenFileSystem( 212 const GURL& origin_url, 213 fileapi::FileSystemType type, 214 OpenFileSystemMode mode, 215 const OpenFileSystemCallback& callback, 216 const GURL& root_url) { 217 if (!IsAllowedScheme(origin_url)) { 218 callback.Run(GURL(), std::string(), base::PLATFORM_FILE_ERROR_SECURITY); 219 return; 220 } 221 222 std::string name = GetFileSystemName(origin_url, type); 223 224 base::PlatformFileError* error_ptr = new base::PlatformFileError; 225 file_task_runner_->PostTaskAndReply( 226 FROM_HERE, 227 base::Bind(&OpenFileSystemOnFileThread, 228 sync_file_util(), origin_url, type, mode, 229 base::Unretained(error_ptr)), 230 base::Bind(&DidOpenFileSystem, 231 weak_factory_.GetWeakPtr(), 232 base::Bind(callback, root_url, name), 233 base::Owned(error_ptr))); 234 } 235 236 base::PlatformFileError SandboxContext::DeleteOriginDataOnFileThread( 237 FileSystemContext* file_system_context, 238 quota::QuotaManagerProxy* proxy, 239 const GURL& origin_url, 240 fileapi::FileSystemType type) { 241 int64 usage = GetOriginUsageOnFileThread( 242 file_system_context, origin_url, type); 243 usage_cache()->CloseCacheFiles(); 244 bool result = sync_file_util()->DeleteDirectoryForOriginAndType( 245 origin_url, type); 246 if (result && proxy) { 247 proxy->NotifyStorageModified( 248 quota::QuotaClient::kFileSystem, 249 origin_url, 250 FileSystemTypeToQuotaStorageType(type), 251 -usage); 252 } 253 254 if (result) 255 return base::PLATFORM_FILE_OK; 256 return base::PLATFORM_FILE_ERROR_FAILED; 257 } 258 259 void SandboxContext::GetOriginsForTypeOnFileThread( 260 fileapi::FileSystemType type, std::set<GURL>* origins) { 261 DCHECK(origins); 262 scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator()); 263 GURL origin; 264 while (!(origin = enumerator->Next()).is_empty()) { 265 if (enumerator->HasFileSystemType(type)) 266 origins->insert(origin); 267 } 268 } 269 270 void SandboxContext::GetOriginsForHostOnFileThread( 271 fileapi::FileSystemType type, const std::string& host, 272 std::set<GURL>* origins) { 273 DCHECK(origins); 274 scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator()); 275 GURL origin; 276 while (!(origin = enumerator->Next()).is_empty()) { 277 if (host == net::GetHostOrSpecFromURL(origin) && 278 enumerator->HasFileSystemType(type)) 279 origins->insert(origin); 280 } 281 } 282 283 int64 SandboxContext::GetOriginUsageOnFileThread( 284 FileSystemContext* file_system_context, 285 const GURL& origin_url, 286 fileapi::FileSystemType type) { 287 // Don't use usage cache and return recalculated usage for sticky invalidated 288 // origins. 289 if (ContainsKey(sticky_dirty_origins_, std::make_pair(origin_url, type))) 290 return RecalculateUsage(file_system_context, origin_url, type); 291 292 base::FilePath base_path = 293 GetBaseDirectoryForOriginAndType(origin_url, type, false); 294 if (base_path.empty() || !base::DirectoryExists(base_path)) 295 return 0; 296 base::FilePath usage_file_path = 297 base_path.Append(FileSystemUsageCache::kUsageFileName); 298 299 bool is_valid = usage_cache()->IsValid(usage_file_path); 300 uint32 dirty_status = 0; 301 bool dirty_status_available = 302 usage_cache()->GetDirty(usage_file_path, &dirty_status); 303 bool visited = !visited_origins_.insert(origin_url).second; 304 if (is_valid && (dirty_status == 0 || (dirty_status_available && visited))) { 305 // The usage cache is clean (dirty == 0) or the origin is already 306 // initialized and running. Read the cache file to get the usage. 307 int64 usage = 0; 308 return usage_cache()->GetUsage(usage_file_path, &usage) ? usage : -1; 309 } 310 // The usage cache has not been initialized or the cache is dirty. 311 // Get the directory size now and update the cache. 312 usage_cache()->Delete(usage_file_path); 313 314 int64 usage = RecalculateUsage(file_system_context, origin_url, type); 315 316 // This clears the dirty flag too. 317 usage_cache()->UpdateUsage(usage_file_path, usage); 318 return usage; 319 } 320 321 void SandboxContext::InvalidateUsageCache( 322 const GURL& origin, 323 fileapi::FileSystemType type) { 324 base::PlatformFileError error = base::PLATFORM_FILE_OK; 325 base::FilePath usage_file_path = GetUsageCachePathForOriginAndType( 326 sync_file_util(), origin, type, &error); 327 if (error != base::PLATFORM_FILE_OK) 328 return; 329 usage_cache()->IncrementDirty(usage_file_path); 330 } 331 332 void SandboxContext::StickyInvalidateUsageCache( 333 const GURL& origin, 334 fileapi::FileSystemType type) { 335 sticky_dirty_origins_.insert(std::make_pair(origin, type)); 336 quota_observer()->SetUsageCacheEnabled(origin, type, false); 337 InvalidateUsageCache(origin, type); 338 } 339 340 base::FilePath SandboxContext::GetUsageCachePathForOriginAndType( 341 const GURL& origin_url, 342 FileSystemType type) { 343 base::PlatformFileError error; 344 base::FilePath path = GetUsageCachePathForOriginAndType( 345 sync_file_util(), origin_url, type, &error); 346 if (error != base::PLATFORM_FILE_OK) 347 return base::FilePath(); 348 return path; 349 } 350 351 // static 352 base::FilePath SandboxContext::GetUsageCachePathForOriginAndType( 353 ObfuscatedFileUtil* sandbox_file_util, 354 const GURL& origin_url, 355 fileapi::FileSystemType type, 356 base::PlatformFileError* error_out) { 357 DCHECK(error_out); 358 *error_out = base::PLATFORM_FILE_OK; 359 base::FilePath base_path = sandbox_file_util->GetDirectoryForOriginAndType( 360 origin_url, type, false /* create */, error_out); 361 if (*error_out != base::PLATFORM_FILE_OK) 362 return base::FilePath(); 363 return base_path.Append(FileSystemUsageCache::kUsageFileName); 364 } 365 366 int64 SandboxContext::RecalculateUsage(FileSystemContext* context, 367 const GURL& origin, 368 FileSystemType type) { 369 FileSystemOperationContext operation_context(context); 370 FileSystemURL url = context->CreateCrackedFileSystemURL( 371 origin, type, base::FilePath()); 372 scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator( 373 sync_file_util()->CreateFileEnumerator(&operation_context, url, true)); 374 375 base::FilePath file_path_each; 376 int64 usage = 0; 377 378 while (!(file_path_each = enumerator->Next()).empty()) { 379 usage += enumerator->Size(); 380 usage += ObfuscatedFileUtil::ComputeFilePathCost(file_path_each); 381 } 382 383 return usage; 384 } 385 386 void SandboxContext::CollectOpenFileSystemMetrics( 387 base::PlatformFileError error_code) { 388 base::Time now = base::Time::Now(); 389 bool throttled = now < next_release_time_for_open_filesystem_stat_; 390 if (!throttled) { 391 next_release_time_for_open_filesystem_stat_ = 392 now + base::TimeDelta::FromHours(kMinimumStatsCollectionIntervalHours); 393 } 394 395 #define REPORT(report_value) \ 396 UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailLabel, \ 397 (report_value), \ 398 kFileSystemErrorMax); \ 399 if (!throttled) { \ 400 UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailNonThrottledLabel, \ 401 (report_value), \ 402 kFileSystemErrorMax); \ 403 } 404 405 switch (error_code) { 406 case base::PLATFORM_FILE_OK: 407 REPORT(kOK); 408 break; 409 case base::PLATFORM_FILE_ERROR_INVALID_URL: 410 REPORT(kInvalidSchemeError); 411 break; 412 case base::PLATFORM_FILE_ERROR_NOT_FOUND: 413 REPORT(kNotFound); 414 break; 415 case base::PLATFORM_FILE_ERROR_FAILED: 416 default: 417 REPORT(kUnknownError); 418 break; 419 } 420 #undef REPORT 421 } 422 423 ObfuscatedFileUtil* SandboxContext::sync_file_util() { 424 return static_cast<ObfuscatedFileUtil*>(file_util()->sync_file_util()); 425 } 426 427 } // namespace fileapi 428