blob: e9973be13a4e4d1ede203600f3ea852a1c1cac0f [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/plugin_private_storage_helper.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ppapi/shared_impl/ppapi_constants.h"
#include "storage/browser/fileapi/async_file_util.h"
#include "storage/browser/fileapi/async_file_util_adapter.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/isolated_context.h"
#include "storage/browser/fileapi/obfuscated_file_util.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "storage/common/fileapi/file_system_util.h"
namespace content {
namespace {
std::string StringTypeToString(const base::FilePath::StringType& value) {
#if defined(OS_POSIX)
return value;
#elif defined(OS_WIN)
return base::WideToUTF8(value);
#endif
}
// Helper for checking the plugin private data for a specified origin and
// plugin for the existence of any file that matches the time range specified.
// All of the operations in this class are done on the IO thread.
//
// This class keeps track of outstanding async requests it generates, and does
// not call |callback_| until they all respond (and thus don't need to worry
// about lifetime of |this| in the async requests). If the data for the origin
// needs to be deleted, it needs to be done on the file task runner, so we
// want to ensure that there are no pending requests that may prevent the
// date from being deleted.
class PluginPrivateDataByOriginChecker {
public:
PluginPrivateDataByOriginChecker(
storage::FileSystemContext* filesystem_context,
const GURL& origin,
const std::string& plugin_name,
const base::Time begin,
const base::Time end,
const base::Callback<void(bool, const GURL&)>& callback)
: filesystem_context_(filesystem_context),
origin_(origin),
plugin_name_(plugin_name),
begin_(begin),
end_(end),
callback_(callback) {
// Create the filesystem ID.
fsid_ = storage::IsolatedContext::GetInstance()
->RegisterFileSystemForVirtualPath(
storage::kFileSystemTypePluginPrivate,
ppapi::kPluginPrivateRootName, base::FilePath());
}
~PluginPrivateDataByOriginChecker() {}
// Checks the files contained in the plugin private filesystem for |origin_|
// and |plugin_name_| for any file whose last modified time is between
// |begin_| and |end_|. |callback_| is called when all actions are complete
// with true and the origin if any such file is found, false and empty GURL
// otherwise.
void CheckFilesOnIOThread();
private:
void OnFileSystemOpened(base::File::Error result);
void OnDirectoryRead(const std::string& root,
base::File::Error result,
storage::AsyncFileUtil::EntryList file_list,
bool has_more);
void OnFileInfo(const std::string& file_name,
base::File::Error result,
const base::File::Info& file_info);
// Keeps track of the pending work. When |task_count_| goes to 0 then
// |callback_| is called and this helper object is destroyed.
void IncrementTaskCount();
void DecrementTaskCount();
// Not owned by this object. Caller is responsible for keeping the
// FileSystemContext alive until |callback_| is called.
storage::FileSystemContext* filesystem_context_;
const GURL origin_;
const std::string plugin_name_;
const base::Time begin_;
const base::Time end_;
const base::Callback<void(bool, const GURL&)> callback_;
std::string fsid_;
int task_count_ = 0;
// Keep track if the data for this origin needs to be deleted due to
// any file found that has last modified time between |begin_| and |end_|.
bool delete_this_origin_data_ = false;
// Keep track if any files exist for this origin.
bool files_found_ = false;
};
void PluginPrivateDataByOriginChecker::CheckFilesOnIOThread() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(storage::ValidateIsolatedFileSystemId(fsid_));
IncrementTaskCount();
filesystem_context_->OpenPluginPrivateFileSystem(
origin_, storage::kFileSystemTypePluginPrivate, fsid_, plugin_name_,
storage::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
base::BindOnce(&PluginPrivateDataByOriginChecker::OnFileSystemOpened,
base::Unretained(this)));
}
void PluginPrivateDataByOriginChecker::OnFileSystemOpened(
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(3) << "Opened filesystem for " << origin_ << ":" << plugin_name_
<< ", result: " << result;
// If we can't open the directory, we can't delete files so simply return.
if (result != base::File::FILE_OK) {
DecrementTaskCount();
return;
}
storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil(
storage::kFileSystemTypePluginPrivate);
std::string root = storage::GetIsolatedFileSystemRootURIString(
origin_, fsid_, ppapi::kPluginPrivateRootName);
std::unique_ptr<storage::FileSystemOperationContext> operation_context =
std::make_unique<storage::FileSystemOperationContext>(
filesystem_context_);
file_util->ReadDirectory(
std::move(operation_context), filesystem_context_->CrackURL(GURL(root)),
base::BindRepeating(&PluginPrivateDataByOriginChecker::OnDirectoryRead,
base::Unretained(this), root));
}
void PluginPrivateDataByOriginChecker::OnDirectoryRead(
const std::string& root,
base::File::Error result,
storage::AsyncFileUtil::EntryList file_list,
bool has_more) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(3) << __func__ << " result: " << result
<< ", #files: " << file_list.size();
// Quit if there is an error.
if (result != base::File::FILE_OK) {
DLOG(ERROR) << "Unable to read directory for " << origin_ << ":"
<< plugin_name_;
DecrementTaskCount();
return;
}
// If there are files found, keep track of it.
if (!file_list.empty())
files_found_ = true;
// No error, process the files returned. No need to do this if we have
// already decided to delete all the data for this origin.
if (!delete_this_origin_data_) {
storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil(
storage::kFileSystemTypePluginPrivate);
for (const auto& file : file_list) {
DVLOG(3) << __func__ << " file: " << file.name.value();
// Nested directories not implemented.
DCHECK_NE(file.type, filesystem::mojom::FsFileType::DIRECTORY);
std::unique_ptr<storage::FileSystemOperationContext> operation_context =
std::make_unique<storage::FileSystemOperationContext>(
filesystem_context_);
storage::FileSystemURL file_url = filesystem_context_->CrackURL(
GURL(root + StringTypeToString(file.name.value())));
IncrementTaskCount();
file_util->GetFileInfo(
std::move(operation_context), file_url,
storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
base::BindOnce(&PluginPrivateDataByOriginChecker::OnFileInfo,
base::Unretained(this),
StringTypeToString(file.name.value())));
}
}
// If there are more files in this directory, wait for the next call.
if (has_more)
return;
DecrementTaskCount();
}
void PluginPrivateDataByOriginChecker::OnFileInfo(
const std::string& file_name,
base::File::Error result,
const base::File::Info& file_info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result == base::File::FILE_OK) {
DVLOG(3) << __func__ << " name: " << file_name
<< ", size: " << file_info.size
<< ", modified: " << file_info.last_modified;
if (file_info.last_modified >= begin_ && file_info.last_modified <= end_)
delete_this_origin_data_ = true;
}
DecrementTaskCount();
}
void PluginPrivateDataByOriginChecker::IncrementTaskCount() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
++task_count_;
}
void PluginPrivateDataByOriginChecker::DecrementTaskCount() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_GT(task_count_, 0);
--task_count_;
if (task_count_)
return;
// If no files exist for this origin, then we can safely delete it.
if (!files_found_)
delete_this_origin_data_ = true;
// If there are no more tasks in progress, then run |callback_| on the
// proper thread.
filesystem_context_->default_file_task_runner()->PostTask(
FROM_HERE, base::BindOnce(callback_, delete_this_origin_data_, origin_));
delete this;
}
// Helper for deleting the plugin private data.
// All of the operations in this class are done on the file task runner.
class PluginPrivateDataDeletionHelper {
public:
PluginPrivateDataDeletionHelper(
scoped_refptr<storage::FileSystemContext> filesystem_context,
const base::Time begin,
const base::Time end,
const base::Closure& callback)
: filesystem_context_(std::move(filesystem_context)),
begin_(begin),
end_(end),
callback_(callback) {}
~PluginPrivateDataDeletionHelper() {}
void CheckOriginsOnFileTaskRunner(const std::set<GURL>& origins);
private:
// Keeps track of the pending work. When |task_count_| goes to 0 then
// |callback_| is called and this helper object is destroyed.
void IncrementTaskCount();
void DecrementTaskCount(bool delete_data_for_origin, const GURL& origin);
// Keep a reference to FileSystemContext until we are done with it.
scoped_refptr<storage::FileSystemContext> filesystem_context_;
const base::Time begin_;
const base::Time end_;
const base::Closure callback_;
int task_count_ = 0;
};
void PluginPrivateDataDeletionHelper::CheckOriginsOnFileTaskRunner(
const std::set<GURL>& origins) {
DCHECK(filesystem_context_->default_file_task_runner()
->RunsTasksInCurrentSequence());
IncrementTaskCount();
base::Callback<void(bool, const GURL&)> decrement_callback =
base::Bind(&PluginPrivateDataDeletionHelper::DecrementTaskCount,
base::Unretained(this));
storage::AsyncFileUtil* async_file_util =
filesystem_context_->GetAsyncFileUtil(
storage::kFileSystemTypePluginPrivate);
storage::ObfuscatedFileUtil* obfuscated_file_util =
static_cast<storage::ObfuscatedFileUtil*>(
static_cast<storage::AsyncFileUtilAdapter*>(async_file_util)
->sync_file_util());
for (const auto& origin : origins) {
// Determine the available plugin private filesystem directories
// for this origin.
base::File::Error error;
base::FilePath path = obfuscated_file_util->GetDirectoryForOriginAndType(
origin, "", false, &error);
if (error != base::File::FILE_OK) {
DLOG(ERROR) << "Unable to read directory for " << origin;
continue;
}
// Currently the plugin private filesystem is only used by Encrypted
// Media Content Decryption Modules (CDM), which used to be hosted as pepper
// plugins. Each CDM gets a directory based on the CdmInfo::file_system_id,
// e.g. application/x-ppapi-widevine-cdm (same as previous plugin mimetypes
// to avoid data migration). See https://crbug.com/479923 for the history.
// Enumerate through the set of directories so that data from any CDM used
// by this origin is deleted.
base::FileEnumerator file_enumerator(path, false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath plugin_path = file_enumerator.Next();
!plugin_path.empty(); plugin_path = file_enumerator.Next()) {
IncrementTaskCount();
PluginPrivateDataByOriginChecker* helper =
new PluginPrivateDataByOriginChecker(
filesystem_context_.get(), origin.GetOrigin(),
plugin_path.BaseName().MaybeAsASCII(), begin_, end_,
decrement_callback);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(
&PluginPrivateDataByOriginChecker::CheckFilesOnIOThread,
base::Unretained(helper)));
// |helper| will delete itself when it is done.
}
}
// Cancels out the call to IncrementTaskCount() at the start of this method.
// If there are no origins specified then this will cause this helper to
// be destroyed.
DecrementTaskCount(false, GURL());
}
void PluginPrivateDataDeletionHelper::IncrementTaskCount() {
DCHECK(filesystem_context_->default_file_task_runner()
->RunsTasksInCurrentSequence());
++task_count_;
}
void PluginPrivateDataDeletionHelper::DecrementTaskCount(
bool delete_data_for_origin,
const GURL& origin) {
DCHECK(filesystem_context_->default_file_task_runner()
->RunsTasksInCurrentSequence());
// Since the PluginPrivateDataByOriginChecker runs on the IO thread,
// delete all the data for |origin| if needed.
if (delete_data_for_origin) {
DCHECK(!origin.is_empty());
DVLOG(3) << "Deleting plugin data for " << origin;
storage::FileSystemBackend* backend =
filesystem_context_->GetFileSystemBackend(
storage::kFileSystemTypePluginPrivate);
storage::FileSystemQuotaUtil* quota_util = backend->GetQuotaUtil();
base::File::Error result = quota_util->DeleteOriginDataOnFileTaskRunner(
filesystem_context_.get(), nullptr, origin,
storage::kFileSystemTypePluginPrivate);
ALLOW_UNUSED_LOCAL(result);
DLOG_IF(ERROR, result != base::File::FILE_OK)
<< "Unable to delete the plugin data for " << origin;
}
DCHECK_GT(task_count_, 0);
--task_count_;
if (task_count_)
return;
// If there are no more tasks in progress, run |callback_| and then
// this helper can be deleted.
callback_.Run();
delete this;
}
} // namespace
void ClearPluginPrivateDataOnFileTaskRunner(
scoped_refptr<storage::FileSystemContext> filesystem_context,
const GURL& storage_origin,
const StoragePartition::OriginMatcherFunction& origin_matcher,
const scoped_refptr<storage::SpecialStoragePolicy>& special_storage_policy,
const base::Time begin,
const base::Time end,
const base::Closure& callback) {
DCHECK(filesystem_context->default_file_task_runner()
->RunsTasksInCurrentSequence());
DVLOG(3) << "Clearing plugin data for origin: " << storage_origin;
storage::FileSystemBackend* backend =
filesystem_context->GetFileSystemBackend(
storage::kFileSystemTypePluginPrivate);
storage::FileSystemQuotaUtil* quota_util = backend->GetQuotaUtil();
// Determine the set of origins used.
std::set<GURL> origins;
quota_util->GetOriginsForTypeOnFileTaskRunner(
storage::kFileSystemTypePluginPrivate, &origins);
if (origins.empty()) {
// No origins, so nothing to do.
callback.Run();
return;
}
// If a specific origin is provided, then check that it is in the list
// returned and remove all the other origins.
if (!storage_origin.is_empty()) {
DCHECK(origin_matcher.is_null()) << "Only 1 of |storage_origin| and "
"|origin_matcher| should be specified.";
if (!base::ContainsKey(origins, storage_origin)) {
// Nothing matches, so nothing to do.
callback.Run();
return;
}
// List should only contain the one value that matches.
origins.clear();
origins.insert(storage_origin);
}
// If a filter is provided, determine which origins match.
if (!origin_matcher.is_null()) {
DCHECK(storage_origin.is_empty())
<< "Only 1 of |storage_origin| and |origin_matcher| should be "
"specified.";
std::set<GURL> origins_to_check;
origins_to_check.swap(origins);
for (const auto& origin : origins_to_check) {
if (origin_matcher.Run(url::Origin::Create(origin),
special_storage_policy.get()))
origins.insert(origin);
}
// If no origins matched, there is nothing to do.
if (origins.empty()) {
callback.Run();
return;
}
}
PluginPrivateDataDeletionHelper* helper = new PluginPrivateDataDeletionHelper(
std::move(filesystem_context), begin, end, callback);
helper->CheckOriginsOnFileTaskRunner(origins);
// |helper| will delete itself when all origins have been checked.
}
} // namespace content