| // 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 |