blob: f03661ebab5d26cfd86efe331e5206a2b28fe416 [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 "extensions/renderer/extension_throttle_manager.h"
#include <utility>
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "extensions/common/constants.h"
#include "extensions/renderer/extension_url_loader_throttle.h"
#include "net/base/url_util.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_request.h"
namespace extensions {
const unsigned int ExtensionThrottleManager::kMaximumNumberOfEntries = 1500;
const unsigned int ExtensionThrottleManager::kRequestsBetweenCollecting = 200;
ExtensionThrottleManager::ExtensionThrottleManager()
: requests_since_last_gc_(0),
ignore_user_gesture_load_flag_for_tests_(false) {
url_id_replacements_.ClearPassword();
url_id_replacements_.ClearUsername();
url_id_replacements_.ClearQuery();
url_id_replacements_.ClearRef();
}
ExtensionThrottleManager::~ExtensionThrottleManager() {
base::AutoLock auto_lock(lock_);
// Delete all entries.
url_entries_.clear();
}
std::unique_ptr<content::URLLoaderThrottle>
ExtensionThrottleManager::MaybeCreateURLLoaderThrottle(
const blink::WebURLRequest& request) {
if (!request.SiteForCookies().ProtocolIs(extensions::kExtensionScheme))
return nullptr;
return std::make_unique<ExtensionURLLoaderThrottle>(this);
}
ExtensionThrottleEntry* ExtensionThrottleManager::RegisterRequestUrl(
const GURL& url) {
// Internal function, no locking.
// Normalize the url.
std::string url_id = GetIdFromUrl(url);
// Periodically garbage collect old entries.
GarbageCollectEntriesIfNecessary();
// Find the entry in the map or create a new null entry.
std::unique_ptr<ExtensionThrottleEntry>& entry = url_entries_[url_id];
// If the entry exists but could be garbage collected at this point, we
// start with a fresh entry so that we possibly back off a bit less
// aggressively (i.e. this resets the error count when the entry's URL
// hasn't been requested in long enough).
if (entry && entry->IsEntryOutdated())
entry.reset();
// Create the entry if needed.
if (!entry) {
if (backoff_policy_for_tests_) {
entry.reset(
new ExtensionThrottleEntry(url_id, backoff_policy_for_tests_.get(),
ignore_user_gesture_load_flag_for_tests_));
} else {
entry.reset(new ExtensionThrottleEntry(
url_id, ignore_user_gesture_load_flag_for_tests_));
}
// We only disable back-off throttling on an entry that we have
// just constructed. This is to allow unit tests to explicitly override
// the entry for localhost URLs.
if (net::IsLocalhost(url)) {
// TODO(joi): Once sliding window is separate from back-off throttling,
// we can simply return a dummy implementation of
// ExtensionThrottleEntry here that never blocks anything.
entry->DisableBackoffThrottling();
}
}
return entry.get();
}
bool ExtensionThrottleManager::ShouldRejectRequest(const GURL& request_url,
int request_load_flags) {
base::AutoLock auto_lock(lock_);
return RegisterRequestUrl(request_url)
->ShouldRejectRequest(request_load_flags);
}
bool ExtensionThrottleManager::ShouldRejectRedirect(
const GURL& request_url,
int request_load_flags,
const net::RedirectInfo& redirect_info) {
{
// An entry GC when requests are outstanding can purge entries so check
// before use.
base::AutoLock auto_lock(lock_);
auto it = url_entries_.find(GetIdFromUrl(request_url));
if (it != url_entries_.end())
it->second->UpdateWithResponse(redirect_info.status_code);
}
return ShouldRejectRequest(redirect_info.new_url, request_load_flags);
}
void ExtensionThrottleManager::WillProcessResponse(
const GURL& response_url,
const network::ResourceResponseHead& response_head) {
if (response_head.network_accessed) {
// An entry GC when requests are outstanding can purge entries so check
// before use.
base::AutoLock auto_lock(lock_);
auto it = url_entries_.find(GetIdFromUrl(response_url));
if (it != url_entries_.end())
it->second->UpdateWithResponse(response_head.headers->response_code());
}
}
void ExtensionThrottleManager::SetBackoffPolicyForTests(
std::unique_ptr<net::BackoffEntry::Policy> policy) {
base::AutoLock auto_lock(lock_);
backoff_policy_for_tests_ = std::move(policy);
}
void ExtensionThrottleManager::OverrideEntryForTests(
const GURL& url,
std::unique_ptr<ExtensionThrottleEntry> entry) {
base::AutoLock auto_lock(lock_);
// Normalize the url.
std::string url_id = GetIdFromUrl(url);
// Periodically garbage collect old entries.
GarbageCollectEntriesIfNecessary();
url_entries_[url_id] = std::move(entry);
}
void ExtensionThrottleManager::EraseEntryForTests(const GURL& url) {
base::AutoLock auto_lock(lock_);
// Normalize the url.
std::string url_id = GetIdFromUrl(url);
url_entries_.erase(url_id);
}
void ExtensionThrottleManager::SetIgnoreUserGestureLoadFlagForTests(
bool ignore_user_gesture_load_flag_for_tests) {
base::AutoLock auto_lock(lock_);
ignore_user_gesture_load_flag_for_tests_ = true;
}
void ExtensionThrottleManager::SetOnline(bool is_online) {
// When we switch from online to offline or change IP addresses, we
// clear all back-off history. This is a precaution in case the change in
// online state now lets us communicate without error with servers that
// we were previously getting 500 or 503 responses from (perhaps the
// responses are from a badly-written proxy that should have returned a
// 502 or 504 because it's upstream connection was down or it had no route
// to the server).
// Remove all entries. Any entries that in-flight requests have a reference
// to will live until those requests end, and these entries may be
// inconsistent with new entries for the same URLs, but since what we
// want is a clean slate for the new connection type, this is OK.
base::AutoLock auto_lock(lock_);
url_entries_.clear();
requests_since_last_gc_ = 0;
}
std::string ExtensionThrottleManager::GetIdFromUrl(const GURL& url) const {
if (!url.is_valid())
return url.possibly_invalid_spec();
GURL id = url.ReplaceComponents(url_id_replacements_);
return base::ToLowerASCII(id.spec());
}
void ExtensionThrottleManager::GarbageCollectEntriesIfNecessary() {
requests_since_last_gc_++;
if (requests_since_last_gc_ < kRequestsBetweenCollecting)
return;
requests_since_last_gc_ = 0;
GarbageCollectEntries();
}
void ExtensionThrottleManager::GarbageCollectEntries() {
base::EraseIf(url_entries_, [](const auto& entry) {
return entry.second->IsEntryOutdated();
});
// In case something broke we want to make sure not to grow indefinitely.
while (url_entries_.size() > kMaximumNumberOfEntries) {
url_entries_.erase(url_entries_.begin());
}
}
} // namespace extensions