Commit 3a2a02a6 authored by Kevin Marshall's avatar Kevin Marshall Committed by Commit Bot

[fuchsia] Implement allow/deny enforcement in URLRequestRewriter.

Implements the UrlRequestAction FIDL API, which allows embedders to
specify requests to block based on the the requests' host and/or scheme.

* Fix issue where wildcards passed in "hosts_filter" are URL-escaped.
* Add "page_type" edge transition detection to TestNavigationListener.
* Report early navigation errors in NavigationControllerImpl.

Bug: 1054475
Change-Id: If4df4889f980c5a239719d09f039eee32efd1c1d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2070103Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarFabrice de Gans-Riberi <fdegans@chromium.org>
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#762033}
parent d3aa798b
......@@ -106,34 +106,51 @@ void TestNavigationListener::OnNavigationStateChanged(
current_state_.set_can_go_back(change.can_go_back());
if (change.has_can_go_forward())
current_state_.set_can_go_forward(change.can_go_forward());
if (change.has_is_main_document_loaded())
if (change.has_page_type())
current_state_.set_page_type(change.page_type());
if (change.has_is_main_document_loaded()) {
current_state_.set_is_main_document_loaded(
change.is_main_document_loaded());
}
if (VLOG_IS_ON(1)) {
std::string state_string;
state_string.reserve(100);
if (current_state_.has_url())
if (current_state_.has_url()) {
state_string.append(
base::StringPrintf(" url=%s ", current_state_.url().c_str()));
}
if (current_state_.has_title())
if (current_state_.has_title()) {
state_string.append(
base::StringPrintf(" title='%s' ", current_state_.title().c_str()));
}
if (current_state_.has_can_go_back())
if (current_state_.has_can_go_back()) {
state_string.append(
base::StringPrintf(" can_go_back=%d ", current_state_.can_go_back()));
}
if (current_state_.has_can_go_forward())
if (current_state_.has_can_go_forward()) {
state_string.append(base::StringPrintf(" can_go_forward=%d ",
current_state_.can_go_forward()));
}
if (current_state_.has_page_type()) {
state_string.append(base::StringPrintf(
" page_type=%s ",
(current_state_.page_type() == fuchsia::web::PageType::NORMAL
? "NORMAL"
: "ERROR")));
}
if (current_state_.has_is_main_document_loaded())
if (current_state_.has_is_main_document_loaded()) {
state_string.append(
base::StringPrintf(" is_main_document_loaded=%d ",
current_state_.is_main_document_loaded()));
}
VLOG(1) << "Navigation state changed: " << state_string;
}
......@@ -175,6 +192,12 @@ bool TestNavigationListener::AllFieldsMatch(
return false;
}
if (expected.has_page_type() &&
(!current_state_.has_page_type() ||
expected.page_type() != current_state_.page_type())) {
return false;
}
if (expected.has_is_main_document_loaded() &&
(!current_state_.has_is_main_document_loaded() ||
expected.is_main_document_loaded() !=
......
......@@ -1710,6 +1710,131 @@ IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest, ExtraHeaders) {
testing::Contains(testing::Key("X-2ExtraHeaders")));
}
// Tests that UrlRequestActions can be set up to deny requests to specific
// hosts.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteDeny) {
fuchsia::web::FramePtr frame = CreateFrame();
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_hosts_filter({"127.0.0.1"});
rule.set_action(fuchsia::web::UrlRequestAction::DENY);
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// 127.0.0.1 should be blocked.
const GURL page_url(embedded_test_server()->GetURL(kPage4Path));
{
fuchsia::web::NavigationState error_state;
error_state.set_page_type(fuchsia::web::PageType::ERROR);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilNavigationStateMatches(error_state);
}
// However, "localhost" is not blocked, so this request should be allowed.
{
GURL::Replacements replacements;
replacements.SetHostStr("localhost");
GURL page_url_localhost = page_url.ReplaceComponents(replacements);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
page_url_localhost.spec()));
navigation_listener_.RunUntilUrlEquals(page_url_localhost);
}
}
// Tests that a UrlRequestAction with no filter criteria will apply to all
// requests.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteDenyAll) {
fuchsia::web::FramePtr frame = CreateFrame();
// No filter criteria are set, so everything is denied.
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_action(fuchsia::web::UrlRequestAction::DENY);
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// 127.0.0.1 should be blocked.
const GURL page_url(embedded_test_server()->GetURL(kPage4Path));
{
fuchsia::web::NavigationState error_state;
error_state.set_page_type(fuchsia::web::PageType::ERROR);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilNavigationStateMatches(error_state);
}
// "localhost" should be blocked.
{
GURL::Replacements replacements;
replacements.SetHostStr("localhost");
GURL page_url_localhost = page_url.ReplaceComponents(replacements);
fuchsia::web::NavigationState error_state;
error_state.set_page_type(fuchsia::web::PageType::ERROR);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilNavigationStateMatches(error_state);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
page_url_localhost.spec()));
navigation_listener_.RunUntilNavigationStateMatches(error_state);
}
}
// Tests that UrlRequestActions can be set up to only allow requests for a
// single host, while denying everything else.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteSelectiveAllow) {
fuchsia::web::FramePtr frame = CreateFrame();
// Allow 127.0.0.1.
fuchsia::web::UrlRequestRewriteRule rule;
rule.set_hosts_filter({"127.0.0.1"});
rule.set_action(fuchsia::web::UrlRequestAction::ALLOW);
std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
rules.push_back(std::move(rule));
// Deny everything else.
rule = {};
rule.set_action(fuchsia::web::UrlRequestAction::DENY);
rules.push_back(std::move(rule));
frame->SetUrlRequestRewriteRules(std::move(rules), []() {});
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// 127.0.0.1 should be allowed.
const GURL page_url(embedded_test_server()->GetURL(kPage4Path));
{
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(), page_url.spec()));
navigation_listener_.RunUntilUrlEquals(page_url);
}
// "localhost" should be blocked.
{
GURL::Replacements replacements;
replacements.SetHostStr("localhost");
GURL page_url_localhost = page_url.ReplaceComponents(replacements);
fuchsia::web::NavigationState error_state;
error_state.set_page_type(fuchsia::web::PageType::ERROR);
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
controller.get(), fuchsia::web::LoadUrlParams(),
page_url_localhost.spec()));
navigation_listener_.RunUntilNavigationStateMatches(error_state);
}
}
// Tests the URLRequestRewrite API properly adds headers on every requests.
IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest,
UrlRequestRewriteAddHeaders) {
......
......@@ -11,6 +11,7 @@
#include "content/public/browser/web_contents.h"
#include "content/public/common/was_activated_option.mojom.h"
#include "fuchsia/base/string_util.h"
#include "net/base/net_errors.h"
#include "net/http/http_util.h"
#include "ui/base/page_transition_types.h"
......@@ -93,6 +94,12 @@ void NavigationControllerImpl::OnNavigationEntryChanged() {
UpdateNavigationStateFromNavigationEntry(
web_contents_->GetController().GetVisibleEntry(), web_contents_,
&new_state);
if (new_state.page_type() != fuchsia::web::PageType::ERROR &&
uncommitted_load_error_) {
// If there was a loading error which prevented the navigation entry from
// being committed, then reflect the error in |new_state|.
new_state.set_page_type(fuchsia::web::PageType::ERROR);
}
DiffNavigationEntries(previous_navigation_state_, new_state,
&pending_navigation_event_);
......@@ -237,6 +244,7 @@ void NavigationControllerImpl::DidFinishLoad(
void NavigationControllerImpl::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
uncommitted_load_error_ = false;
if (!navigation_handle->IsInMainFrame() ||
navigation_handle->IsSameDocument()) {
return;
......@@ -246,6 +254,13 @@ void NavigationControllerImpl::DidStartNavigation(
OnNavigationEntryChanged();
}
void NavigationControllerImpl::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
uncommitted_load_error_ = !navigation_handle->HasCommitted() &&
navigation_handle->GetNetErrorCode() != net::OK;
OnNavigationEntryChanged();
}
void DiffNavigationEntries(const fuchsia::web::NavigationState& old_entry,
const fuchsia::web::NavigationState& new_entry,
fuchsia::web::NavigationState* difference) {
......
......@@ -52,12 +52,12 @@ class NavigationControllerImpl : public fuchsia::web::NavigationController,
void GetVisibleEntry(GetVisibleEntryCallback callback) final;
// content::WebContentsObserver implementation.
void TitleWasSet(content::NavigationEntry*) override;
void DocumentAvailableInMainFrame() override;
void TitleWasSet(content::NavigationEntry*) final;
void DocumentAvailableInMainFrame() final;
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override;
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
const GURL& validated_url) final;
void DidStartNavigation(content::NavigationHandle* navigation_handle) final;
void DidFinishNavigation(content::NavigationHandle* navigation_handle) final;
content::WebContents* const web_contents_;
......@@ -73,6 +73,9 @@ class NavigationControllerImpl : public fuchsia::web::NavigationController,
// True once the main document finishes loading.
bool is_main_document_loaded_ = false;
// True if navigation failed due to an error during page load.
bool uncommitted_load_error_ = false;
base::WeakPtrFactory<NavigationControllerImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl);
......
......@@ -106,12 +106,19 @@ bool ValidateRules(
}
if (rule.has_schemes_filter() && rule.schemes_filter().empty())
return false;
if (!rule.has_rewrites())
if (rule.has_rewrites()) {
if (rule.has_action())
return false;
for (const auto& rewrite : rule.rewrites()) {
if (!ValidateRewrite(rewrite))
return false;
}
} else if (!rule.has_action()) {
// No rewrites, no action = no point!
return false;
}
}
return true;
}
......@@ -159,7 +166,7 @@ zx_status_t UrlRequestRewriteRulesManager::OnRulesUpdated(
base::AutoLock auto_lock(lock_);
cached_rules_ =
base::MakeRefCounted<WebEngineURLLoaderThrottle::UrlRequestRewriteRules>(
mojo::ConvertTo<std::vector<mojom::UrlRequestRewriteRulePtr>>(
mojo::ConvertTo<std::vector<mojom::UrlRequestRulePtr>>(
std::move(rules)));
// Send the updated rules to the receivers.
......
......@@ -50,11 +50,11 @@ TEST_F(UrlRequestRewriteRulesManagerTest, ConvertAddHeader) {
ASSERT_EQ(cached_rules->data.size(), 1u);
ASSERT_FALSE(cached_rules->data[0]->hosts_filter);
ASSERT_FALSE(cached_rules->data[0]->schemes_filter);
ASSERT_EQ(cached_rules->data[0]->rewrites.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->rewrites[0]->is_add_headers());
ASSERT_EQ(cached_rules->data[0]->actions.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->actions[0]->is_add_headers());
const net::HttpRequestHeaders& headers =
cached_rules->data[0]->rewrites[0]->get_add_headers()->headers;
cached_rules->data[0]->actions[0]->get_add_headers()->headers;
ASSERT_EQ(headers.GetHeaderVector().size(), 1u);
std::string value;
......@@ -72,11 +72,11 @@ TEST_F(UrlRequestRewriteRulesManagerTest, ConvertRemoveHeader) {
ASSERT_EQ(cached_rules->data.size(), 1u);
ASSERT_FALSE(cached_rules->data[0]->hosts_filter);
ASSERT_FALSE(cached_rules->data[0]->schemes_filter);
ASSERT_EQ(cached_rules->data[0]->rewrites.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->rewrites[0]->is_remove_header());
ASSERT_EQ(cached_rules->data[0]->actions.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->actions[0]->is_remove_header());
const mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header1 =
cached_rules->data[0]->rewrites[0]->get_remove_header();
cached_rules->data[0]->actions[0]->get_remove_header();
ASSERT_TRUE(remove_header1->query_pattern);
ASSERT_EQ(remove_header1->query_pattern.value().compare("Test"), 0);
ASSERT_EQ(remove_header1->header_name.compare("Header"), 0);
......@@ -89,11 +89,11 @@ TEST_F(UrlRequestRewriteRulesManagerTest, ConvertRemoveHeader) {
ASSERT_EQ(cached_rules->data.size(), 1u);
ASSERT_FALSE(cached_rules->data[0]->hosts_filter);
ASSERT_FALSE(cached_rules->data[0]->schemes_filter);
ASSERT_EQ(cached_rules->data[0]->rewrites.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->rewrites[0]->is_remove_header());
ASSERT_EQ(cached_rules->data[0]->actions.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->actions[0]->is_remove_header());
const mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header2 =
cached_rules->data[0]->rewrites[0]->get_remove_header();
cached_rules->data[0]->actions[0]->get_remove_header();
ASSERT_FALSE(remove_header2->query_pattern);
ASSERT_EQ(remove_header2->header_name.compare("Header"), 0);
}
......@@ -110,13 +110,12 @@ TEST_F(UrlRequestRewriteRulesManagerTest, ConvertSubstituteQueryPattern) {
ASSERT_EQ(cached_rules->data.size(), 1u);
ASSERT_FALSE(cached_rules->data[0]->hosts_filter);
ASSERT_FALSE(cached_rules->data[0]->schemes_filter);
ASSERT_EQ(cached_rules->data[0]->rewrites.size(), 1u);
ASSERT_TRUE(
cached_rules->data[0]->rewrites[0]->is_substitute_query_pattern());
ASSERT_EQ(cached_rules->data[0]->actions.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->actions[0]->is_substitute_query_pattern());
const mojom::UrlRequestRewriteSubstituteQueryPatternPtr&
substitute_query_pattern =
cached_rules->data[0]->rewrites[0]->get_substitute_query_pattern();
cached_rules->data[0]->actions[0]->get_substitute_query_pattern();
ASSERT_EQ(substitute_query_pattern->pattern.compare("Pattern"), 0);
ASSERT_EQ(substitute_query_pattern->substitution.compare("Substitution"), 0);
}
......@@ -132,11 +131,11 @@ TEST_F(UrlRequestRewriteRulesManagerTest, ConvertReplaceUrl) {
ASSERT_EQ(cached_rules->data.size(), 1u);
ASSERT_FALSE(cached_rules->data[0]->hosts_filter);
ASSERT_FALSE(cached_rules->data[0]->schemes_filter);
ASSERT_EQ(cached_rules->data[0]->rewrites.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->rewrites[0]->is_replace_url());
ASSERT_EQ(cached_rules->data[0]->actions.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->actions[0]->is_replace_url());
const mojom::UrlRequestRewriteReplaceUrlPtr& replace_url =
cached_rules->data[0]->rewrites[0]->get_replace_url();
cached_rules->data[0]->actions[0]->get_replace_url();
ASSERT_EQ(replace_url->url_ends_with.compare("/something"), 0);
ASSERT_EQ(replace_url->new_url.spec().compare(url.spec()), 0);
}
......@@ -210,10 +209,10 @@ TEST_F(UrlRequestRewriteRulesManagerTest, RuleRenewal) {
scoped_refptr<WebEngineURLLoaderThrottle::UrlRequestRewriteRules>
cached_rules = url_request_rewrite_rules_manager_->GetCachedRules();
ASSERT_EQ(cached_rules->data.size(), 1u);
ASSERT_EQ(cached_rules->data[0]->rewrites.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->rewrites[0]->is_add_headers());
ASSERT_EQ(cached_rules->data[0]->actions.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->actions[0]->is_add_headers());
ASSERT_TRUE(
cached_rules->data[0]->rewrites[0]->get_add_headers()->headers.HasHeader(
cached_rules->data[0]->actions[0]->get_add_headers()->headers.HasHeader(
"Test1"));
EXPECT_EQ(UpdateRulesFromRewrite(
......@@ -223,10 +222,10 @@ TEST_F(UrlRequestRewriteRulesManagerTest, RuleRenewal) {
// We should have the new rules.
cached_rules = url_request_rewrite_rules_manager_->GetCachedRules();
ASSERT_EQ(cached_rules->data.size(), 1u);
ASSERT_EQ(cached_rules->data[0]->rewrites.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->rewrites[0]->is_add_headers());
ASSERT_EQ(cached_rules->data[0]->actions.size(), 1u);
ASSERT_TRUE(cached_rules->data[0]->actions[0]->is_add_headers());
ASSERT_TRUE(
cached_rules->data[0]->rewrites[0]->get_add_headers()->headers.HasHeader(
cached_rules->data[0]->actions[0]->get_add_headers()->headers.HasHeader(
"Test2"));
}
......
......@@ -79,21 +79,23 @@ void ApplyReplaceUrl(network::ResourceRequest* request,
}
void ApplyRewrite(network::ResourceRequest* request,
const mojom::UrlRequestRewritePtr& rewrite) {
const mojom::UrlRequestActionPtr& rewrite) {
switch (rewrite->which()) {
case mojom::UrlRequestRewrite::Tag::ADD_HEADERS:
case mojom::UrlRequestAction::Tag::ADD_HEADERS:
ApplyAddHeaders(request, rewrite->get_add_headers());
break;
case mojom::UrlRequestRewrite::Tag::REMOVE_HEADER:
case mojom::UrlRequestAction::Tag::REMOVE_HEADER:
ApplyRemoveHeader(request, rewrite->get_remove_header());
break;
case mojom::UrlRequestRewrite::Tag::SUBSTITUTE_QUERY_PATTERN:
case mojom::UrlRequestAction::Tag::SUBSTITUTE_QUERY_PATTERN:
ApplySubstituteQueryPattern(request,
rewrite->get_substitute_query_pattern());
break;
case mojom::UrlRequestRewrite::Tag::REPLACE_URL:
case mojom::UrlRequestAction::Tag::REPLACE_URL:
ApplyReplaceUrl(request, rewrite->get_replace_url());
break;
case mojom::UrlRequestAction::Tag::POLICY:
break;
}
}
......@@ -101,9 +103,6 @@ bool HostMatches(const base::StringPiece& url_host,
const base::StringPiece& rule_host) {
const base::StringPiece kWildcard("*.");
if (base::StartsWith(rule_host, kWildcard, base::CompareCase::SENSITIVE)) {
// |rule_host| starts with a wildcard (e.g. "*.test.xyz"). Check if
// |url_host| ends with ".test.xyz". The "." character is included here to
// prevent accidentally matching "notatest.xyz".
if (base::EndsWith(url_host, rule_host.substr(1),
base::CompareCase::SENSITIVE)) {
return true;
......@@ -117,8 +116,10 @@ bool HostMatches(const base::StringPiece& url_host,
return base::CompareCaseInsensitiveASCII(url_host, rule_host) == 0;
}
void ApplyRule(network::ResourceRequest* request,
const mojom::UrlRequestRewriteRulePtr& rule) {
// Returns true if the host and scheme filters defined in |rule| match
// |request|.
bool RuleFiltersMatchRequest(network::ResourceRequest* request,
const mojom::UrlRequestRulePtr& rule) {
const GURL& url = request->url;
if (rule->hosts_filter) {
......@@ -128,7 +129,7 @@ void ApplyRule(network::ResourceRequest* request,
break;
}
if (!found)
return;
return false;
}
if (rule->schemes_filter) {
......@@ -140,19 +141,46 @@ void ApplyRule(network::ResourceRequest* request,
}
}
if (!found)
return;
return false;
}
for (const auto& rewrite : rule->rewrites) {
return true;
}
// Applies |rule|'s transformations to |request| if it satisfies |rule|'s
// criteria.
void ApplyRule(network::ResourceRequest* request,
const mojom::UrlRequestRulePtr& rule) {
if (!RuleFiltersMatchRequest(request, rule))
return;
for (const auto& rewrite : rule->actions)
ApplyRewrite(request, rewrite);
}
}
void ApplyRules(network::ResourceRequest* request,
const std::vector<mojom::UrlRequestRewriteRulePtr>& rules) {
for (const auto& rule : rules) {
ApplyRule(request, rule);
// Returns true if |request| is either allowed or left unblocked by any rules.
bool IsRequestAllowed(
network::ResourceRequest* request,
const WebEngineURLLoaderThrottle::UrlRequestRewriteRules& rules) {
for (const auto& rule : rules.data) {
if (rule->actions.size() != 1)
continue;
if (rule->actions[0]->which() != mojom::UrlRequestAction::Tag::POLICY)
continue;
if (!RuleFiltersMatchRequest(request, rule))
continue;
switch (rule->actions[0]->get_policy()) {
case mojom::UrlRequestAccessPolicy::kAllow:
return true;
case mojom::UrlRequestAccessPolicy::kDeny:
return false;
}
}
return true;
}
} // namespace
......@@ -173,8 +201,16 @@ void WebEngineURLLoaderThrottle::WillStartRequest(
scoped_refptr<WebEngineURLLoaderThrottle::UrlRequestRewriteRules>
cached_rules = cached_rules_provider_->GetCachedRules();
// |cached_rules| may be empty if no rule was ever sent to WebEngine.
if (cached_rules)
ApplyRules(request, cached_rules->data);
if (cached_rules) {
if (!IsRequestAllowed(request, *cached_rules)) {
delegate_->CancelWithError(net::ERR_ABORTED,
"Resource load blocked by embedder policy.");
return;
}
for (const auto& rule : cached_rules->data)
ApplyRule(request, rule);
}
*defer = false;
}
......
......@@ -18,7 +18,7 @@ class WEB_ENGINE_EXPORT WebEngineURLLoaderThrottle
: public blink::URLLoaderThrottle {
public:
using UrlRequestRewriteRules =
base::RefCountedData<std::vector<mojom::UrlRequestRewriteRulePtr>>;
base::RefCountedData<std::vector<mojom::UrlRequestRulePtr>>;
// An interface to provide rewrite rules to the throttle. Its
// implementation must outlive the WebEngineURLLoaderThrottle.
......
......@@ -6,6 +6,7 @@
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
namespace {
......@@ -51,15 +52,15 @@ TEST_F(WebEngineURLLoaderThrottleTest, WildcardHosts) {
mojom::UrlRequestRewriteAddHeadersPtr add_headers =
mojom::UrlRequestRewriteAddHeaders::New();
add_headers->headers.SetHeader("Header", "Value");
mojom::UrlRequestRewritePtr rewrite =
mojom::UrlRequestRewrite::NewAddHeaders(std::move(add_headers));
std::vector<mojom::UrlRequestRewritePtr> rewrites;
rewrites.push_back(std::move(rewrite));
mojom::UrlRequestRewriteRulePtr rule = mojom::UrlRequestRewriteRule::New();
mojom::UrlRequestActionPtr rewrite =
mojom::UrlRequestAction::NewAddHeaders(std::move(add_headers));
std::vector<mojom::UrlRequestActionPtr> actions;
actions.push_back(std::move(rewrite));
mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
rule->hosts_filter = base::Optional<std::vector<std::string>>({"*.test.net"});
rule->rewrites = std::move(rewrites);
rule->actions = std::move(actions);
std::vector<mojom::UrlRequestRewriteRulePtr> rules;
std::vector<mojom::UrlRequestRulePtr> rules;
rules.push_back(std::move(rule));
TestCachedRulesProvider provider;
......@@ -100,15 +101,15 @@ TEST_F(WebEngineURLLoaderThrottleTest, DataReplacementUrl) {
mojom::UrlRequestRewriteReplaceUrl::New();
replace_url->url_ends_with = ".css";
replace_url->new_url = GURL(kCssDataURI);
mojom::UrlRequestRewritePtr rewrite =
mojom::UrlRequestRewrite::NewReplaceUrl(std::move(replace_url));
std::vector<mojom::UrlRequestRewritePtr> rewrites;
rewrites.push_back(std::move(rewrite));
mojom::UrlRequestRewriteRulePtr rule = mojom::UrlRequestRewriteRule::New();
mojom::UrlRequestActionPtr rewrite =
mojom::UrlRequestAction::NewReplaceUrl(std::move(replace_url));
std::vector<mojom::UrlRequestActionPtr> actions;
actions.push_back(std::move(rewrite));
mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
rule->hosts_filter = base::Optional<std::vector<std::string>>({"*.test.net"});
rule->rewrites = std::move(rewrites);
rule->actions = std::move(actions);
std::vector<mojom::UrlRequestRewriteRulePtr> rules;
std::vector<mojom::UrlRequestRulePtr> rules;
rules.push_back(std::move(rule));
TestCachedRulesProvider provider;
......@@ -124,3 +125,74 @@ TEST_F(WebEngineURLLoaderThrottleTest, DataReplacementUrl) {
throttle.WillStartRequest(&request, &defer);
EXPECT_EQ(request.url, base::StringPiece(kCssDataURI));
}
class TestThrottleDelegate : public blink::URLLoaderThrottle::Delegate {
public:
TestThrottleDelegate() = default;
~TestThrottleDelegate() override = default;
bool canceled() const { return canceled_; }
base::StringPiece cancel_reason() const { return cancel_reason_; }
void Reset() {
canceled_ = false;
cancel_reason_.clear();
}
// URLLoaderThrottle::Delegate implementation.
void CancelWithError(int error_code,
base::StringPiece custom_reason) override {
canceled_ = true;
cancel_reason_ = custom_reason.as_string();
}
void Resume() override {}
private:
bool canceled_ = false;
std::string cancel_reason_;
};
// Tests that resource loads can be allowed or blocked based on the
// UrlRequestAction policy.
TEST_F(WebEngineURLLoaderThrottleTest, AllowAndDeny) {
std::vector<mojom::UrlRequestRulePtr> rules;
{
mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
rule->hosts_filter = base::Optional<std::vector<std::string>>({"test.net"});
rule->actions.push_back(mojom::UrlRequestAction::NewPolicy(
mojom::UrlRequestAccessPolicy::kAllow));
rules.push_back(std::move(rule));
}
{
mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
rule->actions.push_back(mojom::UrlRequestAction::NewPolicy(
mojom::UrlRequestAccessPolicy::kDeny));
rules.push_back(std::move(rule));
}
TestCachedRulesProvider provider;
provider.SetCachedRules(
base::MakeRefCounted<WebEngineURLLoaderThrottle::UrlRequestRewriteRules>(
std::move(rules)));
WebEngineURLLoaderThrottle throttle(&provider);
bool defer = false;
TestThrottleDelegate delegate;
throttle.set_delegate(&delegate);
network::ResourceRequest request1;
request1.url = GURL("http://test.net");
throttle.WillStartRequest(&request1, &defer);
EXPECT_FALSE(delegate.canceled());
delegate.Reset();
network::ResourceRequest request2;
request2.url = GURL("http://blocked.net");
throttle.WillStartRequest(&request2, &defer);
EXPECT_TRUE(delegate.canceled());
EXPECT_EQ(delegate.cancel_reason(),
"Resource load blocked by embedder policy.");
}
......@@ -41,7 +41,7 @@ void UrlRequestRulesReceiver::OnUrlRequestRulesReceiverAssociatedReceiver(
}
void UrlRequestRulesReceiver::OnRulesUpdated(
std::vector<mojom::UrlRequestRewriteRulePtr> rules) {
std::vector<mojom::UrlRequestRulePtr> rules) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AutoLock auto_lock(lock_);
cached_rules_ =
......
......@@ -42,8 +42,7 @@ class UrlRequestRulesReceiver
mojo::PendingAssociatedReceiver<mojom::UrlRequestRulesReceiver> receiver);
// mojom::UrlRequestRulesReceiver implementation.
void OnRulesUpdated(
std::vector<mojom::UrlRequestRewriteRulePtr> rules) override;
void OnRulesUpdated(std::vector<mojom::UrlRequestRulePtr> rules) override;
// WebEngineURLLoaderThrottle::CachedRulesProvider implementation.
scoped_refptr<WebEngineURLLoaderThrottle::UrlRequestRewriteRules>
......
......@@ -13,11 +13,11 @@ import "url/mojom/url.mojom";
// TODO(https://crbug.com/976975): Support URL rewriting for service workers.
interface UrlRequestRulesReceiver {
// Receives a set of rules to apply to URL requests.
OnRulesUpdated(array<UrlRequestRewriteRule> rules);
OnRulesUpdated(array<UrlRequestRule> rules);
};
// A URL request rewrite rule.
struct UrlRequestRewriteRule {
// A URL request modification rule.
struct UrlRequestRule {
// Set of hosts to apply the rewrites to. If empty, the rule will apply to
// every request, independent of host.
array<string>? hosts_filter;
......@@ -27,10 +27,10 @@ struct UrlRequestRewriteRule {
array<string>? schemes_filter;
// URL request rewrites to apply.
array<UrlRequestRewrite> rewrites;
array<UrlRequestAction> actions;
};
union UrlRequestRewrite {
union UrlRequestAction {
// Adds a set of headers to a URL request.
UrlRequestRewriteAddHeaders add_headers;
......@@ -42,6 +42,10 @@ union UrlRequestRewrite {
// Replaces a URL if the original URL ends with a pattern.
UrlRequestRewriteReplaceUrl replace_url;
// Specifies whether the request should be allowed or blocked.
// Cannot be combined with other UrlRequestActions.
UrlRequestAccessPolicy policy;
};
// Adds |headers| to the URL request. If a header is already present in the
......@@ -81,3 +85,8 @@ struct UrlRequestRewriteReplaceUrl {
// The replacement URL.
url.mojom.Url new_url;
};
enum UrlRequestAccessPolicy {
kAllow,
kDeny,
};
......@@ -6,6 +6,8 @@
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "fuchsia/base/string_util.h"
#include "net/base/url_util.h"
namespace {
......@@ -17,26 +19,30 @@ std::string NormalizeHost(base::StringPiece host) {
namespace mojo {
mojom::UrlRequestRewriteAddHeadersPtr
TypeConverter<mojom::UrlRequestRewriteAddHeadersPtr,
fuchsia::web::UrlRequestRewriteAddHeaders>::
Convert(const fuchsia::web::UrlRequestRewriteAddHeaders& input) {
template <>
struct TypeConverter<mojom::UrlRequestRewriteAddHeadersPtr,
fuchsia::web::UrlRequestRewriteAddHeaders> {
static mojom::UrlRequestRewriteAddHeadersPtr Convert(
const fuchsia::web::UrlRequestRewriteAddHeaders& input) {
mojom::UrlRequestRewriteAddHeadersPtr add_headers =
mojom::UrlRequestRewriteAddHeaders::New();
if (input.has_headers()) {
for (const auto& header : input.headers()) {
base::StringPiece header_name = cr_fuchsia::BytesAsString(header.name);
base::StringPiece header_value = cr_fuchsia::BytesAsString(header.value);
base::StringPiece header_value =
cr_fuchsia::BytesAsString(header.value);
add_headers->headers.SetHeader(header_name, header_value);
}
}
return add_headers;
}
}
};
mojom::UrlRequestRewriteRemoveHeaderPtr
TypeConverter<mojom::UrlRequestRewriteRemoveHeaderPtr,
fuchsia::web::UrlRequestRewriteRemoveHeader>::
Convert(const fuchsia::web::UrlRequestRewriteRemoveHeader& input) {
template <>
struct TypeConverter<mojom::UrlRequestRewriteRemoveHeaderPtr,
fuchsia::web::UrlRequestRewriteRemoveHeader> {
static mojom::UrlRequestRewriteRemoveHeaderPtr Convert(
const fuchsia::web::UrlRequestRewriteRemoveHeader& input) {
mojom::UrlRequestRewriteRemoveHeaderPtr remove_header =
mojom::UrlRequestRewriteRemoveHeader::New();
if (input.has_query_pattern())
......@@ -45,12 +51,13 @@ TypeConverter<mojom::UrlRequestRewriteRemoveHeaderPtr,
remove_header->header_name =
cr_fuchsia::BytesAsString(input.header_name()).as_string();
return remove_header;
}
}
};
mojom::UrlRequestRewriteSubstituteQueryPatternPtr
TypeConverter<mojom::UrlRequestRewriteSubstituteQueryPatternPtr,
fuchsia::web::UrlRequestRewriteSubstituteQueryPattern>::
Convert(
template <>
struct TypeConverter<mojom::UrlRequestRewriteSubstituteQueryPatternPtr,
fuchsia::web::UrlRequestRewriteSubstituteQueryPattern> {
static mojom::UrlRequestRewriteSubstituteQueryPatternPtr Convert(
const fuchsia::web::UrlRequestRewriteSubstituteQueryPattern& input) {
mojom::UrlRequestRewriteSubstituteQueryPatternPtr substitute_query_pattern =
mojom::UrlRequestRewriteSubstituteQueryPattern::New();
......@@ -59,12 +66,14 @@ TypeConverter<mojom::UrlRequestRewriteSubstituteQueryPatternPtr,
if (input.has_substitution())
substitute_query_pattern->substitution = input.substitution();
return substitute_query_pattern;
}
}
};
mojom::UrlRequestRewriteReplaceUrlPtr
TypeConverter<mojom::UrlRequestRewriteReplaceUrlPtr,
fuchsia::web::UrlRequestRewriteReplaceUrl>::
Convert(const fuchsia::web::UrlRequestRewriteReplaceUrl& input) {
template <>
struct TypeConverter<mojom::UrlRequestRewriteReplaceUrlPtr,
fuchsia::web::UrlRequestRewriteReplaceUrl> {
static mojom::UrlRequestRewriteReplaceUrlPtr Convert(
const fuchsia::web::UrlRequestRewriteReplaceUrl& input) {
mojom::UrlRequestRewriteReplaceUrlPtr replace_url =
mojom::UrlRequestRewriteReplaceUrl::New();
if (input.has_url_ends_with())
......@@ -72,41 +81,59 @@ TypeConverter<mojom::UrlRequestRewriteReplaceUrlPtr,
if (input.has_new_url())
replace_url->new_url = GURL(input.new_url());
return replace_url;
}
}
};
template <>
struct TypeConverter<mojom::UrlRequestAccessPolicy,
fuchsia::web::UrlRequestAction> {
static mojom::UrlRequestAccessPolicy Convert(
const fuchsia::web::UrlRequestAction& input) {
switch (input) {
case fuchsia::web::UrlRequestAction::ALLOW:
return mojom::UrlRequestAccessPolicy::kAllow;
case fuchsia::web::UrlRequestAction::DENY:
return mojom::UrlRequestAccessPolicy::kDeny;
}
}
};
mojom::UrlRequestRewritePtr
TypeConverter<mojom::UrlRequestRewritePtr, fuchsia::web::UrlRequestRewrite>::
Convert(const fuchsia::web::UrlRequestRewrite& input) {
template <>
struct TypeConverter<mojom::UrlRequestActionPtr,
fuchsia::web::UrlRequestRewrite> {
static mojom::UrlRequestActionPtr Convert(
const fuchsia::web::UrlRequestRewrite& input) {
switch (input.Which()) {
case fuchsia::web::UrlRequestRewrite::Tag::kAddHeaders:
return mojom::UrlRequestRewrite::NewAddHeaders(
return mojom::UrlRequestAction::NewAddHeaders(
mojo::ConvertTo<mojom::UrlRequestRewriteAddHeadersPtr>(
input.add_headers()));
case fuchsia::web::UrlRequestRewrite::Tag::kRemoveHeader:
return mojom::UrlRequestRewrite::NewRemoveHeader(
return mojom::UrlRequestAction::NewRemoveHeader(
mojo::ConvertTo<mojom::UrlRequestRewriteRemoveHeaderPtr>(
input.remove_header()));
case fuchsia::web::UrlRequestRewrite::Tag::kSubstituteQueryPattern:
return mojom::UrlRequestRewrite::NewSubstituteQueryPattern(
return mojom::UrlRequestAction::NewSubstituteQueryPattern(
mojo::ConvertTo<mojom::UrlRequestRewriteSubstituteQueryPatternPtr>(
input.substitute_query_pattern()));
case fuchsia::web::UrlRequestRewrite::Tag::kReplaceUrl:
return mojom::UrlRequestRewrite::NewReplaceUrl(
return mojom::UrlRequestAction::NewReplaceUrl(
mojo::ConvertTo<mojom::UrlRequestRewriteReplaceUrlPtr>(
input.replace_url()));
default:
// This is to prevent build breakage when adding new rewrites to the FIDL
// definition.
// This is to prevent build breakage when adding new rewrites to the
// FIDL definition.
NOTREACHED();
return nullptr;
}
}
}
};
mojom::UrlRequestRewriteRulePtr
TypeConverter<mojom::UrlRequestRewriteRulePtr,
fuchsia::web::UrlRequestRewriteRule>::
mojom::UrlRequestRulePtr
TypeConverter<mojom::UrlRequestRulePtr, fuchsia::web::UrlRequestRewriteRule>::
Convert(const fuchsia::web::UrlRequestRewriteRule& input) {
mojom::UrlRequestRewriteRulePtr rule = mojom::UrlRequestRewriteRule::New();
mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
if (input.has_hosts_filter()) {
// Convert host names in case they contain non-ASCII characters.
const base::StringPiece kWildcard("*.");
......@@ -122,11 +149,19 @@ TypeConverter<mojom::UrlRequestRewriteRulePtr,
}
rule->hosts_filter = std::move(hosts);
}
if (input.has_schemes_filter())
rule->schemes_filter = base::make_optional(input.schemes_filter());
if (input.has_rewrites())
rule->rewrites = mojo::ConvertTo<std::vector<mojom::UrlRequestRewritePtr>>(
if (input.has_rewrites()) {
rule->actions = mojo::ConvertTo<std::vector<mojom::UrlRequestActionPtr>>(
input.rewrites());
} else if (input.has_action()) {
rule->actions = std::vector<mojom::UrlRequestActionPtr>();
rule->actions.push_back(mojom::UrlRequestAction::NewPolicy(
mojo::ConvertTo<mojom::UrlRequestAccessPolicy>(input.action())));
}
return rule;
}
......
......@@ -7,51 +7,15 @@
#include <fuchsia/web/cpp/fidl.h>
#include "fuchsia/base/string_util.h"
#include "fuchsia/engine/url_request_rewrite.mojom.h"
#include "mojo/public/cpp/bindings/type_converter.h"
namespace mojo {
template <>
struct TypeConverter<mojom::UrlRequestRewriteAddHeadersPtr,
fuchsia::web::UrlRequestRewriteAddHeaders> {
static mojom::UrlRequestRewriteAddHeadersPtr Convert(
const fuchsia::web::UrlRequestRewriteAddHeaders& input);
};
template <>
struct TypeConverter<mojom::UrlRequestRewriteRemoveHeaderPtr,
fuchsia::web::UrlRequestRewriteRemoveHeader> {
static mojom::UrlRequestRewriteRemoveHeaderPtr Convert(
const fuchsia::web::UrlRequestRewriteRemoveHeader& input);
};
template <>
struct TypeConverter<mojom::UrlRequestRewriteSubstituteQueryPatternPtr,
fuchsia::web::UrlRequestRewriteSubstituteQueryPattern> {
static mojom::UrlRequestRewriteSubstituteQueryPatternPtr Convert(
const fuchsia::web::UrlRequestRewriteSubstituteQueryPattern& input);
};
template <>
struct TypeConverter<mojom::UrlRequestRewriteReplaceUrlPtr,
fuchsia::web::UrlRequestRewriteReplaceUrl> {
static mojom::UrlRequestRewriteReplaceUrlPtr Convert(
const fuchsia::web::UrlRequestRewriteReplaceUrl& input);
};
template <>
struct TypeConverter<mojom::UrlRequestRewritePtr,
fuchsia::web::UrlRequestRewrite> {
static mojom::UrlRequestRewritePtr Convert(
const fuchsia::web::UrlRequestRewrite& input);
};
template <>
struct TypeConverter<mojom::UrlRequestRewriteRulePtr,
struct TypeConverter<mojom::UrlRequestRulePtr,
fuchsia::web::UrlRequestRewriteRule> {
static mojom::UrlRequestRewriteRulePtr Convert(
static mojom::UrlRequestRulePtr Convert(
const fuchsia::web::UrlRequestRewriteRule& input);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment