blob: a93e660c561515d1f8546b5e716f3fc6ba2111bb [file] [log] [blame]
// Copyright 2019 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 "fuchsia/engine/browser/url_request_rewrite_rules_manager.h"
#include "base/strings/strcat.h"
#include "fuchsia/base/string_util.h"
#include "fuchsia/engine/url_request_rewrite_type_converters.h"
#include "net/http/http_util.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
namespace {
using RoutingIdRewriterMap =
std::unordered_map<int, UrlRequestRewriteRulesManager*>;
RoutingIdRewriterMap& GetRewriterMap() {
static base::NoDestructor<RoutingIdRewriterMap> rewriter_map;
return *rewriter_map;
}
bool IsValidUrlHost(base::StringPiece host) {
return GURL(base::StrCat({url::kHttpScheme, "://", host})).is_valid();
}
bool ValidateAddHeaders(
const fuchsia::web::UrlRequestRewriteAddHeaders& add_headers) {
if (!add_headers.has_headers())
return false;
for (const auto& header : add_headers.headers()) {
base::StringPiece header_name = cr_fuchsia::BytesAsString(header.name);
base::StringPiece header_value = cr_fuchsia::BytesAsString(header.value);
if (!net::HttpUtil::IsValidHeaderName(header_name) ||
!net::HttpUtil::IsValidHeaderValue(header_value))
return false;
}
return true;
}
bool ValidateRemoveHeader(
const fuchsia::web::UrlRequestRewriteRemoveHeader& remove_header) {
if (!remove_header.has_header_name())
return false;
if (!net::HttpUtil::IsValidHeaderName(
cr_fuchsia::BytesAsString(remove_header.header_name())))
return false;
return true;
}
bool ValidateSubstituteQueryPattern(
const fuchsia::web::UrlRequestRewriteSubstituteQueryPattern&
substitute_query_pattern) {
if (!substitute_query_pattern.has_pattern() ||
!substitute_query_pattern.has_substitution())
return false;
return true;
}
bool ValidateReplaceUrl(
const fuchsia::web::UrlRequestRewriteReplaceUrl& replace_url) {
if (!replace_url.has_url_ends_with() || !replace_url.has_new_url())
return false;
if (!GURL("http://site.com/" + replace_url.url_ends_with()).is_valid())
return false;
if (!GURL(replace_url.new_url()).is_valid())
return false;
return true;
}
bool ValidateRewrite(const fuchsia::web::UrlRequestRewrite& rewrite) {
switch (rewrite.Which()) {
case fuchsia::web::UrlRequestRewrite::Tag::kAddHeaders:
return ValidateAddHeaders(rewrite.add_headers());
case fuchsia::web::UrlRequestRewrite::Tag::kRemoveHeader:
return ValidateRemoveHeader(rewrite.remove_header());
case fuchsia::web::UrlRequestRewrite::Tag::kSubstituteQueryPattern:
return ValidateSubstituteQueryPattern(rewrite.substitute_query_pattern());
case fuchsia::web::UrlRequestRewrite::Tag::kReplaceUrl:
return ValidateReplaceUrl(rewrite.replace_url());
default:
// This is to prevent build breakage when adding new rewrites to the FIDL
// definition. This can also happen if the client sends an empty rewrite,
// which is invalid.
return false;
}
}
bool ValidateRules(
const std::vector<fuchsia::web::UrlRequestRewriteRule>& rules) {
for (const auto& rule : rules) {
if (rule.has_hosts_filter()) {
if (rule.hosts_filter().empty())
return false;
const base::StringPiece kWildcard("*.");
for (const base::StringPiece host : rule.hosts_filter()) {
if (base::StartsWith(host, kWildcard, base::CompareCase::SENSITIVE)) {
if (!IsValidUrlHost(host.substr(2)))
return false;
} else {
if (!IsValidUrlHost(host))
return false;
}
}
}
if (rule.has_schemes_filter() && rule.schemes_filter().empty())
return false;
if (!rule.has_rewrites())
return false;
for (const auto& rewrite : rule.rewrites()) {
if (!ValidateRewrite(rewrite))
return false;
}
}
return true;
}
} // namespace
UrlRequestRewriteRulesManager::ActiveFrame::ActiveFrame(
content::RenderFrameHost* rfh,
mojo::AssociatedRemote<mojom::UrlRequestRulesReceiver> ar)
: render_frame_host(rfh), associated_remote(std::move(ar)) {}
UrlRequestRewriteRulesManager::ActiveFrame::ActiveFrame(
UrlRequestRewriteRulesManager::ActiveFrame&&) = default;
UrlRequestRewriteRulesManager::ActiveFrame::~ActiveFrame() = default;
// static
UrlRequestRewriteRulesManager*
UrlRequestRewriteRulesManager::ForFrameTreeNodeId(int frame_tree_node_id) {
auto iter = GetRewriterMap().find(frame_tree_node_id);
if (iter == GetRewriterMap().end())
return nullptr;
return iter->second;
}
// static
std::unique_ptr<UrlRequestRewriteRulesManager>
UrlRequestRewriteRulesManager::CreateForTesting() {
return base::WrapUnique(new UrlRequestRewriteRulesManager());
}
UrlRequestRewriteRulesManager::UrlRequestRewriteRulesManager(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {}
UrlRequestRewriteRulesManager::~UrlRequestRewriteRulesManager() = default;
zx_status_t UrlRequestRewriteRulesManager::OnRulesUpdated(
std::vector<fuchsia::web::UrlRequestRewriteRule> rules,
fuchsia::web::Frame::SetUrlRequestRewriteRulesCallback callback) {
if (!ValidateRules(rules)) {
base::AutoLock auto_lock(lock_);
cached_rules_ = nullptr;
return ZX_ERR_INVALID_ARGS;
}
base::AutoLock auto_lock(lock_);
cached_rules_ =
base::MakeRefCounted<WebEngineURLLoaderThrottle::UrlRequestRewriteRules>(
mojo::ConvertTo<std::vector<mojom::UrlRequestRewriteRulePtr>>(
std::move(rules)));
// Send the updated rules to the receivers.
for (const auto& receiver_pair : active_frames_) {
receiver_pair.second.associated_remote->OnRulesUpdated(
mojo::Clone(cached_rules_->data));
}
// TODO(crbug.com/976975): Only call the callback when there are pending
// throttles.
std::move(callback)();
return ZX_OK;
}
scoped_refptr<WebEngineURLLoaderThrottle::UrlRequestRewriteRules>
UrlRequestRewriteRulesManager::GetCachedRules() {
base::AutoLock auto_lock(lock_);
return cached_rules_;
}
UrlRequestRewriteRulesManager::UrlRequestRewriteRulesManager() {}
void UrlRequestRewriteRulesManager::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
int frame_tree_node_id = render_frame_host->GetFrameTreeNodeId();
if (active_frames_.find(frame_tree_node_id) != active_frames_.end()) {
// This happens on cross-process navigations. It is not necessary to refresh
// the global map in this case as RenderFrameDeleted will not have been
// called for this RenderFrameHost.
size_t deleted = active_frames_.erase(frame_tree_node_id);
DCHECK(deleted == 1);
} else {
// Register this instance of UrlRequestRewriteRulesManager as the URL
// request rewriter handler for this RenderFrameHost ID.
auto iter =
GetRewriterMap().emplace(std::make_pair(frame_tree_node_id, this));
DCHECK(iter.second);
}
// Register the frame rules receiver.
mojo::AssociatedRemote<mojom::UrlRequestRulesReceiver> rules_receiver;
render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
&rules_receiver);
ActiveFrame active_frame(render_frame_host, std::move(rules_receiver));
auto iter =
active_frames_.emplace(frame_tree_node_id, std::move(active_frame));
DCHECK(iter.second);
base::AutoLock auto_lock(lock_);
if (cached_rules_) {
// Send an initial set of rules.
iter.first->second.associated_remote->OnRulesUpdated(
mojo::Clone(cached_rules_->data));
}
}
void UrlRequestRewriteRulesManager::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
int frame_tree_node_id = render_frame_host->GetFrameTreeNodeId();
auto iter = active_frames_.find(frame_tree_node_id);
DCHECK(iter != active_frames_.end());
// On cross-process navigations, the new RenderFrameHost is created before
// the old one is deleted. When that happens, the map has already been
// updated, so it is safe to return here.
if (iter->second.render_frame_host != render_frame_host)
return;
active_frames_.erase(iter);
size_t deleted = GetRewriterMap().erase(frame_tree_node_id);
DCHECK(deleted == 1);
}