blob: 8dc45c920688d6bba552cbd7d077dcb65c45fcb7 [file] [log] [blame]
// Copyright 2013 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 "extensions/browser/quota_service.h"
#include "base/threading/thread_task_runner_handle.h"
#include "extensions/browser/extension_function.h"
#include "extensions/common/error_utils.h"
namespace {
// If the browser stays open long enough, we reset state once a day.
// Whatever this value is, it should be an order of magnitude longer than
// the longest interval in any of the QuotaLimitHeuristics in use.
constexpr int kPurgeIntervalInDays = 1;
constexpr char kOverQuotaError[] = "This request exceeds the * quota.";
bool g_purge_disabled_for_testing = false;
} // namespace
namespace extensions {
QuotaService::QuotaService() {
if (!g_purge_disabled_for_testing && base::ThreadTaskRunnerHandle::IsSet()) {
purge_timer_.Start(FROM_HERE,
base::TimeDelta::FromDays(kPurgeIntervalInDays),
this,
&QuotaService::Purge);
}
}
QuotaService::~QuotaService() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
purge_timer_.Stop();
Purge();
}
std::string QuotaService::Assess(const std::string& extension_id,
ExtensionFunction* function,
const base::ListValue* args,
const base::TimeTicks& event_time) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (function->ShouldSkipQuotaLimiting())
return std::string();
// Lookup function list for extension.
FunctionHeuristicsMap& functions = function_heuristics_[extension_id];
// Lookup heuristics for function, create if necessary.
QuotaLimitHeuristics& heuristics = functions[function->name()];
if (heuristics.empty())
function->GetQuotaLimitHeuristics(&heuristics);
if (heuristics.empty())
return std::string(); // No heuristic implies no limit.
QuotaLimitHeuristic* failed_heuristic = NULL;
for (const auto& heuristic : heuristics) {
// Apply heuristic to each item (bucket).
if (!heuristic->ApplyToArgs(args, event_time)) {
failed_heuristic = heuristic.get();
break;
}
}
if (!failed_heuristic)
return std::string();
std::string error = failed_heuristic->GetError();
DCHECK_GT(error.length(), 0u);
return error;
}
QuotaService::ScopedDisablePurgeForTesting::ScopedDisablePurgeForTesting() {
DCHECK(!g_purge_disabled_for_testing);
g_purge_disabled_for_testing = true;
}
QuotaService::ScopedDisablePurgeForTesting::~ScopedDisablePurgeForTesting() {
DCHECK(g_purge_disabled_for_testing);
g_purge_disabled_for_testing = false;
}
void QuotaService::Purge() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
for (auto it = function_heuristics_.begin(); it != function_heuristics_.end();
function_heuristics_.erase(it++)) {
it->second.clear();
}
}
void QuotaLimitHeuristic::Bucket::Reset(const Config& config,
const base::TimeTicks& start) {
num_tokens_ = config.refill_token_count;
expiration_ = start + config.refill_interval;
}
void QuotaLimitHeuristic::SingletonBucketMapper::GetBucketsForArgs(
const base::ListValue* args,
BucketList* buckets) {
buckets->push_back(&bucket_);
}
QuotaLimitHeuristic::QuotaLimitHeuristic(const Config& config,
BucketMapper* map,
const std::string& name)
: config_(config), bucket_mapper_(map), name_(name) {}
QuotaLimitHeuristic::~QuotaLimitHeuristic() {}
bool QuotaLimitHeuristic::ApplyToArgs(const base::ListValue* args,
const base::TimeTicks& event_time) {
BucketList buckets;
bucket_mapper_->GetBucketsForArgs(args, &buckets);
for (auto i = buckets.begin(); i != buckets.end(); ++i) {
if ((*i)->expiration().is_null()) // A brand new bucket.
(*i)->Reset(config_, event_time);
if (!Apply(*i, event_time))
return false; // It only takes one to spoil it for everyone.
}
return true;
}
std::string QuotaLimitHeuristic::GetError() const {
return extensions::ErrorUtils::FormatErrorMessage(kOverQuotaError, name_);
}
bool QuotaService::TimedLimit::Apply(Bucket* bucket,
const base::TimeTicks& event_time) {
if (event_time > bucket->expiration())
bucket->Reset(config(), event_time);
return bucket->DeductToken();
}
} // namespace extensions