blob: 1c64aa56a4471d3d8d9bcca24a77a6c506f142c1 [file] [log] [blame]
// Copyright (c) 2012 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/browsing_data/browsing_data_remover_impl.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "content/browser/browsing_data/storage_partition_http_cache_data_remover.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/browsing_data_remover_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/net_errors.h"
#include "net/cookies/cookie_store.h"
#include "net/http/http_network_session.h"
#include "net/http/http_transaction_factory.h"
#include "net/http/transport_security_state.h"
#include "net/ssl/channel_id_service.h"
#include "net/ssl/channel_id_store.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "ppapi/features/features.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "url/origin.h"
using base::UserMetricsAction;
namespace content {
namespace {
// Returns whether |origin| matches |origin_type_mask| given the special
// storage |policy|; and if |predicate| is not null, then also whether
// it matches |predicate|. If |origin_type_mask| contains embedder-specific
// datatypes, |embedder_matcher| must not be null; the decision for those
// datatypes will be delegated to it.
bool DoesOriginMatchMaskAndURLs(
int origin_type_mask,
const base::Callback<bool(const GURL&)>& predicate,
const BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher&
embedder_matcher,
const GURL& origin,
storage::SpecialStoragePolicy* policy) {
if (!predicate.is_null() && !predicate.Run(origin))
return false;
const std::vector<std::string>& schemes = url::GetWebStorageSchemes();
bool is_web_scheme =
(std::find(schemes.begin(), schemes.end(), origin.GetOrigin().scheme()) !=
schemes.end());
// If a websafe origin is unprotected, it matches iff UNPROTECTED_WEB.
if ((!policy || !policy->IsStorageProtected(origin.GetOrigin())) &&
is_web_scheme &&
(origin_type_mask & BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB)) {
return true;
}
origin_type_mask &= ~BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB;
// Hosted applications (protected and websafe origins) iff PROTECTED_WEB.
if (policy && policy->IsStorageProtected(origin.GetOrigin()) &&
is_web_scheme &&
(origin_type_mask & BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB)) {
return true;
}
origin_type_mask &= ~BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
DCHECK(embedder_matcher || !origin_type_mask)
<< "The mask contains embedder-defined origin types, but there is no "
<< "embedder delegate matcher to process them.";
if (!embedder_matcher.is_null())
return embedder_matcher.Run(origin_type_mask, origin, policy);
return false;
}
void ClearHttpAuthCacheOnIOThread(
scoped_refptr<net::URLRequestContextGetter> context_getter,
base::Time delete_begin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
net::HttpNetworkSession* http_session = context_getter->GetURLRequestContext()
->http_transaction_factory()
->GetSession();
DCHECK(http_session);
http_session->http_auth_cache()->ClearEntriesAddedWithin(base::Time::Now() -
delete_begin);
http_session->CloseAllConnections();
}
void OnClearedChannelIDsOnIOThread(net::URLRequestContextGetter* rq_context,
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Need to close open SSL connections which may be using the channel ids we
// are deleting.
// TODO(mattm): http://crbug.com/166069 Make the server bound cert
// service/store have observers that can notify relevant things directly.
rq_context->GetURLRequestContext()
->ssl_config_service()
->NotifySSLConfigChange();
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, std::move(callback));
}
void ClearChannelIDsOnIOThread(
const base::Callback<bool(const std::string&)>& domain_predicate,
base::Time delete_begin,
base::Time delete_end,
scoped_refptr<net::URLRequestContextGetter> request_context,
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
net::ChannelIDService* channel_id_service =
request_context->GetURLRequestContext()->channel_id_service();
channel_id_service->GetChannelIDStore()->DeleteForDomainsCreatedBetween(
domain_predicate, delete_begin, delete_end,
base::Bind(&OnClearedChannelIDsOnIOThread,
base::RetainedRef(std::move(request_context)),
base::Passed(std::move(callback))));
}
} // namespace
BrowsingDataRemoverImpl::BrowsingDataRemoverImpl(
BrowserContext* browser_context)
: browser_context_(browser_context),
remove_mask_(-1),
origin_type_mask_(-1),
is_removing_(false),
storage_partition_for_testing_(nullptr),
weak_ptr_factory_(this) {
DCHECK(browser_context_);
}
BrowsingDataRemoverImpl::~BrowsingDataRemoverImpl() {
if (!task_queue_.empty()) {
VLOG(1) << "BrowsingDataRemoverImpl shuts down with " << task_queue_.size()
<< " pending tasks";
}
UMA_HISTOGRAM_EXACT_LINEAR("History.ClearBrowsingData.TaskQueueAtShutdown",
task_queue_.size(), 10);
// If we are still removing data, notify observers that their task has been
// (albeit unsucessfuly) processed, so they can unregister themselves.
// TODO(bauerb): If it becomes a problem that browsing data might not actually
// be fully cleared when an observer is notified, add a success flag.
while (!task_queue_.empty()) {
if (observer_list_.HasObserver(task_queue_.front().observer))
task_queue_.front().observer->OnBrowsingDataRemoverDone();
task_queue_.pop();
}
}
void BrowsingDataRemoverImpl::SetRemoving(bool is_removing) {
DCHECK_NE(is_removing_, is_removing);
is_removing_ = is_removing;
}
void BrowsingDataRemoverImpl::SetEmbedderDelegate(
BrowsingDataRemoverDelegate* embedder_delegate) {
embedder_delegate_ = embedder_delegate;
}
bool BrowsingDataRemoverImpl::DoesOriginMatchMask(
int origin_type_mask,
const GURL& origin,
storage::SpecialStoragePolicy* policy) const {
BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher embedder_matcher;
if (embedder_delegate_)
embedder_matcher = embedder_delegate_->GetOriginTypeMatcher();
return DoesOriginMatchMaskAndURLs(origin_type_mask,
base::Callback<bool(const GURL&)>(),
embedder_matcher, origin, policy);
}
void BrowsingDataRemoverImpl::Remove(const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int origin_type_mask) {
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder>(), nullptr);
}
void BrowsingDataRemoverImpl::RemoveAndReply(const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int origin_type_mask,
Observer* observer) {
DCHECK(observer);
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder>(), observer);
}
void BrowsingDataRemoverImpl::RemoveWithFilter(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
DCHECK(filter_builder);
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), nullptr);
}
void BrowsingDataRemoverImpl::RemoveWithFilterAndReply(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder,
Observer* observer) {
DCHECK(filter_builder);
DCHECK(observer);
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), observer);
}
void BrowsingDataRemoverImpl::RemoveInternal(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder,
Observer* observer) {
DCHECK(!observer || observer_list_.HasObserver(observer))
<< "Every observer must register itself (by calling AddObserver()) "
<< "before observing a removal task.";
// Remove() and RemoveAndReply() pass a null pointer to indicate no filter.
// No filter is equivalent to one that |IsEmptyBlacklist()|.
if (!filter_builder) {
filter_builder =
BrowsingDataFilterBuilder::Create(BrowsingDataFilterBuilder::BLACKLIST);
DCHECK(filter_builder->IsEmptyBlacklist());
}
task_queue_.emplace(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), observer);
// If this is the only scheduled task, execute it immediately. Otherwise,
// it will be automatically executed when all tasks scheduled before it
// finish.
if (task_queue_.size() == 1) {
SetRemoving(true);
RunNextTask();
}
}
void BrowsingDataRemoverImpl::RunNextTask() {
DCHECK(!task_queue_.empty());
const RemovalTask& removal_task = task_queue_.front();
RemoveImpl(removal_task.delete_begin, removal_task.delete_end,
removal_task.remove_mask, *removal_task.filter_builder,
removal_task.origin_type_mask);
}
void BrowsingDataRemoverImpl::RemoveImpl(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
const BrowsingDataFilterBuilder& filter_builder,
int origin_type_mask) {
// =============== README before adding more storage backends ===============
//
// If you're adding a data storage backend that is included among
// RemoveDataMask::FILTERABLE_DATATYPES, you must do one of the following:
// 1. Support one of the filters generated by |filter_builder|.
// 2. Add a comment explaining why is it acceptable in your case to delete all
// data without filtering URLs / origins / domains.
// 3. Do not support partial deletion, i.e. only delete your data if
// |filter_builder.IsEmptyBlacklist()|. Add a comment explaining why this
// is acceptable.
base::ScopedClosureRunner synchronous_clear_operations(
CreatePendingTaskCompletionClosure());
// crbug.com/140910: Many places were calling this with base::Time() as
// delete_end, even though they should've used base::Time::Max().
DCHECK_NE(base::Time(), delete_end);
delete_begin_ = delete_begin;
delete_end_ = delete_end;
remove_mask_ = remove_mask;
origin_type_mask_ = origin_type_mask;
// Record the combined deletion of cookies and cache.
CookieOrCacheDeletionChoice choice = NEITHER_COOKIES_NOR_CACHE;
if (remove_mask & DATA_TYPE_COOKIES &&
origin_type_mask_ & ORIGIN_TYPE_UNPROTECTED_WEB) {
choice =
remove_mask & DATA_TYPE_CACHE ? BOTH_COOKIES_AND_CACHE : ONLY_COOKIES;
} else if (remove_mask & DATA_TYPE_CACHE) {
choice = ONLY_CACHE;
}
UMA_HISTOGRAM_ENUMERATION(
"History.ClearBrowsingData.UserDeletedCookieOrCache", choice,
MAX_CHOICE_VALUE);
//////////////////////////////////////////////////////////////////////////////
// INITIALIZATION
base::Callback<bool(const GURL& url)> filter =
filter_builder.BuildGeneralFilter();
//////////////////////////////////////////////////////////////////////////////
// DATA_TYPE_DOWNLOADS
if ((remove_mask & DATA_TYPE_DOWNLOADS) &&
(!embedder_delegate_ || embedder_delegate_->MayRemoveDownloadHistory())) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Downloads"));
DownloadManager* download_manager =
BrowserContext::GetDownloadManager(browser_context_);
download_manager->RemoveDownloadsByURLAndTime(filter, delete_begin_,
delete_end_);
}
//////////////////////////////////////////////////////////////////////////////
// DATA_TYPE_CHANNEL_IDS
// Channel IDs are not separated for protected and unprotected web
// origins. We check the origin_type_mask_ to prevent unintended deletion.
if (remove_mask & DATA_TYPE_CHANNEL_IDS &&
!(remove_mask & DATA_TYPE_AVOID_CLOSING_CONNECTIONS) &&
origin_type_mask_ & ORIGIN_TYPE_UNPROTECTED_WEB) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_ChannelIDs"));
// Since we are running on the UI thread don't call GetURLRequestContext().
scoped_refptr<net::URLRequestContextGetter> request_context =
BrowserContext::GetDefaultStoragePartition(browser_context_)
->GetURLRequestContext();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&ClearChannelIDsOnIOThread,
filter_builder.BuildChannelIDFilter(), delete_begin_,
delete_end_, std::move(request_context),
CreatePendingTaskCompletionClosure()));
}
//////////////////////////////////////////////////////////////////////////////
// STORAGE PARTITION DATA
uint32_t storage_partition_remove_mask = 0;
// We ignore the DATA_TYPE_COOKIES request if UNPROTECTED_WEB is not set,
// so that callers who request DATA_TYPE_SITE_DATA with another origin type
// don't accidentally remove the cookies that are associated with the
// UNPROTECTED_WEB origin. This is necessary because cookies are not separated
// between UNPROTECTED_WEB and other origin types.
if (remove_mask & DATA_TYPE_COOKIES &&
origin_type_mask_ & ORIGIN_TYPE_UNPROTECTED_WEB) {
storage_partition_remove_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
}
if (remove_mask & DATA_TYPE_LOCAL_STORAGE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE;
}
if (remove_mask & DATA_TYPE_INDEXED_DB) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
}
if (remove_mask & DATA_TYPE_WEB_SQL) {
storage_partition_remove_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL;
}
if (remove_mask & DATA_TYPE_APP_CACHE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_APPCACHE;
}
if (remove_mask & DATA_TYPE_SERVICE_WORKERS) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS;
}
if (remove_mask & DATA_TYPE_CACHE_STORAGE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE;
}
if (remove_mask & DATA_TYPE_FILE_SYSTEMS) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
}
// Content Decryption Modules used by Encrypted Media store licenses in a
// private filesystem. These are different than content licenses used by
// Flash (which are deleted father down in this method).
if (remove_mask & DATA_TYPE_MEDIA_LICENSES) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_PLUGIN_PRIVATE_DATA;
}
StoragePartition* storage_partition;
if (storage_partition_for_testing_) {
storage_partition = storage_partition_for_testing_;
} else {
storage_partition =
BrowserContext::GetDefaultStoragePartition(browser_context_);
}
if (storage_partition_remove_mask) {
uint32_t quota_storage_remove_mask =
~StoragePartition::QUOTA_MANAGED_STORAGE_MASK_PERSISTENT;
if (delete_begin_ == base::Time() ||
((origin_type_mask_ & ~ORIGIN_TYPE_UNPROTECTED_WEB) != 0)) {
// If we're deleting since the beginning of time, or we're removing
// protected origins, then remove persistent quota data.
quota_storage_remove_mask |=
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_PERSISTENT;
}
// If cookies are supposed to be conditionally deleted from the storage
// partition, create a cookie matcher function.
StoragePartition::CookieMatcherFunction cookie_matcher;
if (!filter_builder.IsEmptyBlacklist() &&
(storage_partition_remove_mask &
StoragePartition::REMOVE_DATA_MASK_COOKIES)) {
cookie_matcher = filter_builder.BuildCookieFilter();
}
BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher embedder_matcher;
if (embedder_delegate_)
embedder_matcher = embedder_delegate_->GetOriginTypeMatcher();
storage_partition->ClearData(
storage_partition_remove_mask, quota_storage_remove_mask,
base::BindRepeating(&DoesOriginMatchMaskAndURLs, origin_type_mask_,
filter, embedder_matcher),
cookie_matcher, delete_begin_, delete_end_,
CreatePendingTaskCompletionClosure());
}
//////////////////////////////////////////////////////////////////////////////
// CACHE
if (remove_mask & DATA_TYPE_CACHE) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Cache"));
// TODO(msramek): Clear the cache of all renderers.
storage_partition->ClearHttpAndMediaCaches(
delete_begin, delete_end,
filter_builder.IsEmptyBlacklist() ? base::Callback<bool(const GURL&)>()
: filter,
CreatePendingTaskCompletionClosure());
// When clearing cache, wipe accumulated network related data
// (TransportSecurityState and HttpServerPropertiesManager data).
storage_partition->GetNetworkContext()->ClearNetworkingHistorySince(
delete_begin, CreatePendingTaskCompletionClosure());
// Tell the shader disk cache to clear.
base::RecordAction(UserMetricsAction("ClearBrowsingData_ShaderCache"));
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
}
//////////////////////////////////////////////////////////////////////////////
// Auth cache.
if ((remove_mask & DATA_TYPE_COOKIES) &&
!(remove_mask & DATA_TYPE_AVOID_CLOSING_CONNECTIONS)) {
scoped_refptr<net::URLRequestContextGetter> request_context =
BrowserContext::GetDefaultStoragePartition(browser_context_)
->GetURLRequestContext();
BrowserThread::PostTaskAndReply(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&ClearHttpAuthCacheOnIOThread,
std::move(request_context), delete_begin_),
CreatePendingTaskCompletionClosure());
}
//////////////////////////////////////////////////////////////////////////////
// Embedder data.
if (embedder_delegate_) {
embedder_delegate_->RemoveEmbedderData(
delete_begin_, delete_end_, remove_mask, filter_builder,
origin_type_mask, CreatePendingTaskCompletionClosure());
}
}
void BrowsingDataRemoverImpl::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void BrowsingDataRemoverImpl::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void BrowsingDataRemoverImpl::SetWouldCompleteCallbackForTesting(
const base::Callback<void(const base::Closure& continue_to_completion)>&
callback) {
would_complete_callback_ = callback;
}
void BrowsingDataRemoverImpl::OverrideStoragePartitionForTesting(
StoragePartition* storage_partition) {
storage_partition_for_testing_ = storage_partition;
}
const base::Time& BrowsingDataRemoverImpl::GetLastUsedBeginTime() {
return delete_begin_;
}
const base::Time& BrowsingDataRemoverImpl::GetLastUsedEndTime() {
return delete_end_;
}
int BrowsingDataRemoverImpl::GetLastUsedRemovalMask() {
return remove_mask_;
}
int BrowsingDataRemoverImpl::GetLastUsedOriginTypeMask() {
return origin_type_mask_;
}
BrowsingDataRemoverImpl::RemovalTask::RemovalTask(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder,
Observer* observer)
: delete_begin(delete_begin),
delete_end(delete_end),
remove_mask(remove_mask),
origin_type_mask(origin_type_mask),
filter_builder(std::move(filter_builder)),
observer(observer) {}
BrowsingDataRemoverImpl::RemovalTask::RemovalTask(
RemovalTask&& other) noexcept = default;
BrowsingDataRemoverImpl::RemovalTask::~RemovalTask() {}
void BrowsingDataRemoverImpl::Notify() {
// Some tests call |RemoveImpl| directly, without using the task scheduler.
// TODO(msramek): Improve those tests so we don't have to do this. Tests
// relying on |RemoveImpl| do so because they need to pass in
// BrowsingDataFilterBuilder while still keeping ownership of it. Making
// BrowsingDataFilterBuilder copyable would solve this.
if (!is_removing_) {
DCHECK(task_queue_.empty());
return;
}
// Inform the observer of the current task unless it has unregistered
// itself in the meantime.
DCHECK(!task_queue_.empty());
if (task_queue_.front().observer != nullptr &&
observer_list_.HasObserver(task_queue_.front().observer)) {
task_queue_.front().observer->OnBrowsingDataRemoverDone();
}
task_queue_.pop();
if (task_queue_.empty()) {
// All removal tasks have finished. Inform the observers that we're idle.
SetRemoving(false);
return;
}
// Yield to the UI thread before executing the next removal task.
// TODO(msramek): Consider also adding a backoff if too many tasks
// are scheduled.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&BrowsingDataRemoverImpl::RunNextTask, GetWeakPtr()));
}
void BrowsingDataRemoverImpl::OnTaskComplete() {
// TODO(brettw) http://crbug.com/305259: This should also observe session
// clearing (what about other things such as passwords, etc.?) and wait for
// them to complete before continuing.
DCHECK_GT(num_pending_tasks_, 0);
num_pending_tasks_--;
if (num_pending_tasks_ > 0)
return;
if (!would_complete_callback_.is_null()) {
would_complete_callback_.Run(
base::Bind(&BrowsingDataRemoverImpl::Notify, GetWeakPtr()));
return;
}
Notify();
}
base::OnceClosure
BrowsingDataRemoverImpl::CreatePendingTaskCompletionClosure() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
num_pending_tasks_++;
return base::BindOnce(&BrowsingDataRemoverImpl::OnTaskComplete, GetWeakPtr());
}
base::WeakPtr<BrowsingDataRemoverImpl> BrowsingDataRemoverImpl::GetWeakPtr() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::WeakPtr<BrowsingDataRemoverImpl> weak_ptr =
weak_ptr_factory_.GetWeakPtr();
// Immediately bind the weak pointer to the UI thread. This makes it easier
// to discover potential misuse on the IO thread.
weak_ptr.get();
return weak_ptr;
}
} // namespace content