0

Add ServiceWorkerRouterEvaluator.

To evaluate the ServiceWorkerRouterRules, an evaluator has been
implemented.  Since it is used from both the browser process and the
renderer processes for routing decision, it has been implemented in
content/common.

Bug: 1371756
Change-Id: If201d406a69ec635ba4f1ee2d6a0d04e5f4244e6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4570363
Reviewed-by: Minoru Chikamune <chikamune@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Reviewed-by: Shunya Shishido <sisidovski@chromium.org>
Commit-Queue: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Reviewed-by: Jeremy Roman <jbroman@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1152299}
This commit is contained in:
Yoshisato Yanagisawa
2023-06-02 01:41:35 +00:00
committed by Chromium LUCI CQ
parent c319fec4c2
commit 84c89e84a4
6 changed files with 439 additions and 0 deletions

@ -150,6 +150,8 @@ source_set("common") {
"service_worker/race_network_request_url_loader_client.h",
"service_worker/service_worker_resource_loader.cc",
"service_worker/service_worker_resource_loader.h",
"service_worker/service_worker_router_evaluator.cc",
"service_worker/service_worker_router_evaluator.h",
"set_process_title.cc",
"set_process_title.h",
"skia_utils.cc",

@ -0,0 +1,3 @@
include_rules = {
"+third_party/liburlpattern",
}

@ -0,0 +1,76 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/service_worker/service_worker_router_evaluator.h"
#include "third_party/liburlpattern/options.h"
#include "third_party/liburlpattern/pattern.h"
#include "third_party/re2/src/re2/re2.h"
namespace {
std::string ConvertToRegex(const blink::UrlPattern& url_pattern) {
liburlpattern::Options options = {.delimiter_list = "/",
.prefix_list = "/",
.sensitive = true,
.strict = false};
liburlpattern::Pattern pattern(url_pattern.pathname, options, "[^/]+?");
VLOG(3) << "regex string:" << pattern.GenerateRegexString();
return pattern.GenerateRegexString();
}
} // namespace
namespace content {
ServiceWorkerRouterEvaluator::RouterRule::RouterRule()
: url_patterns(RE2::Set(RE2::Options(), RE2::Anchor::UNANCHORED)) {}
ServiceWorkerRouterEvaluator::RouterRule::~RouterRule() = default;
ServiceWorkerRouterEvaluator::ServiceWorkerRouterEvaluator(
blink::ServiceWorkerRouterRules rules)
: rules_(std::move(rules)) {
Compile();
}
ServiceWorkerRouterEvaluator::~ServiceWorkerRouterEvaluator() = default;
void ServiceWorkerRouterEvaluator::Compile() {
for (const auto& r : rules_.rules) {
std::unique_ptr<RouterRule> rule = absl::make_unique<RouterRule>();
for (const auto& condition : r.conditions) {
CHECK_EQ(condition.type,
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern);
if (rule->url_patterns.Add(ConvertToRegex(*condition.url_pattern),
nullptr) == -1) {
// Failed to parse the regex.
return;
}
}
rule->url_pattern_length = r.conditions.size();
if (!rule->url_patterns.Compile()) {
// Failed to compile the regex.
return;
}
rule->sources = r.sources;
compiled_rules_.emplace_back(std::move(rule));
}
is_valid_ = true;
}
std::vector<blink::ServiceWorkerRouterSource>
ServiceWorkerRouterEvaluator::Evaluate(
const network::ResourceRequest& request) const {
for (const auto& rule : compiled_rules_) {
std::vector<int> vec;
if (rule->url_patterns.Match(request.url.path(), &vec) &&
// ensure it matches all included patterns.
vec.size() == rule->url_pattern_length) {
return rule->sources;
}
}
return std::vector<blink::ServiceWorkerRouterSource>();
}
} // namespace content

@ -0,0 +1,48 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_COMMON_SERVICE_WORKER_SERVICE_WORKER_ROUTER_EVALUATOR_H_
#define CONTENT_COMMON_SERVICE_WORKER_SERVICE_WORKER_ROUTER_EVALUATOR_H_
#include <utility>
#include "content/common/content_export.h"
#include "services/network/public/cpp/resource_request.h"
#include "third_party/blink/public/common/service_worker/service_worker_router_rule.h"
#include "third_party/re2/src/re2/set.h"
namespace content {
class CONTENT_EXPORT ServiceWorkerRouterEvaluator {
public:
struct RouterRule {
RouterRule();
~RouterRule();
RE2::Set url_patterns;
size_t url_pattern_length = 0;
std::vector<blink::ServiceWorkerRouterSource> sources;
};
explicit ServiceWorkerRouterEvaluator(blink::ServiceWorkerRouterRules rules);
~ServiceWorkerRouterEvaluator();
bool IsValid() { return is_valid_; }
// Returns an empty list if nothing matched.
std::vector<blink::ServiceWorkerRouterSource> Evaluate(
const network::ResourceRequest& request) const;
const blink::ServiceWorkerRouterRules& rules() const { return rules_; }
private:
void Compile();
const blink::ServiceWorkerRouterRules rules_;
std::vector<std::unique_ptr<RouterRule>> compiled_rules_;
bool is_valid_ = false;
};
} // namespace content
#endif // CONTENT_COMMON_SERVICE_WORKER_SERVICE_WORKER_ROUTER_EVALUATOR_H_

@ -0,0 +1,309 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/service_worker/service_worker_router_evaluator.h"
#include "base/strings/string_piece.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/liburlpattern/parse.h"
#include "third_party/liburlpattern/pattern.h"
namespace content {
namespace {
TEST(ServiceWorkerRouterEvaluator, EmptyRule) {
blink::ServiceWorkerRouterRules rules;
ServiceWorkerRouterEvaluator evaluator(rules);
EXPECT_EQ(0U, evaluator.rules().rules.size());
ASSERT_TRUE(evaluator.IsValid());
network::ResourceRequest request;
request.method = "GET";
request.url = GURL("https://example.com/");
const auto sources = evaluator.Evaluate(request);
EXPECT_TRUE(sources.empty());
}
TEST(ServiceWorkerRouterEvaluator, SimpleMatch) {
blink::ServiceWorkerRouterRules rules;
{
blink::ServiceWorkerRouterRule rule;
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"/test/*",
[](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterSource source;
source.type = blink::ServiceWorkerRouterSource::SourceType::kNetwork;
source.network_source = blink::ServiceWorkerRouterNetworkSource{};
rule.sources.push_back(source);
}
rules.rules.push_back(rule);
}
ASSERT_EQ(1U, rules.rules.size());
ServiceWorkerRouterEvaluator evaluator(rules);
ASSERT_EQ(1U, evaluator.rules().rules.size());
EXPECT_TRUE(evaluator.IsValid());
network::ResourceRequest request;
request.method = "GET";
request.url = GURL("https://example.com/test/page.html");
const auto sources = evaluator.Evaluate(request);
EXPECT_EQ(1U, sources.size());
}
TEST(ServiceWorkerRouterEvaluator, SimpleExactMatch) {
blink::ServiceWorkerRouterRules rules;
{
blink::ServiceWorkerRouterRule rule;
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"/test/page.html",
[](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterSource source;
source.type = blink::ServiceWorkerRouterSource::SourceType::kNetwork;
source.network_source = blink::ServiceWorkerRouterNetworkSource{};
rule.sources.push_back(source);
}
rules.rules.push_back(rule);
}
ASSERT_EQ(1U, rules.rules.size());
ServiceWorkerRouterEvaluator evaluator(rules);
ASSERT_EQ(1U, evaluator.rules().rules.size());
EXPECT_TRUE(evaluator.IsValid());
network::ResourceRequest request;
request.method = "GET";
request.url = GURL("https://example.com/test/page.html");
const auto sources = evaluator.Evaluate(request);
EXPECT_EQ(1U, sources.size());
}
TEST(ServiceWorkerRouterEvaluator, NotMatchingCondition) {
blink::ServiceWorkerRouterRules rules;
{
blink::ServiceWorkerRouterRule rule;
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"/test/*",
[](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterSource source;
source.type = blink::ServiceWorkerRouterSource::SourceType::kNetwork;
source.network_source = blink::ServiceWorkerRouterNetworkSource{};
rule.sources.push_back(source);
}
rules.rules.push_back(rule);
}
ASSERT_EQ(1U, rules.rules.size());
ServiceWorkerRouterEvaluator evaluator(rules);
ASSERT_EQ(1U, evaluator.rules().rules.size());
EXPECT_TRUE(evaluator.IsValid());
network::ResourceRequest request;
request.method = "GET";
request.url = GURL("https://example.com/notmatched/page.html");
const auto sources = evaluator.Evaluate(request);
EXPECT_EQ(0U, sources.size());
}
TEST(ServiceWorkerRouterEvaluator, OneConditionMisMatch) {
blink::ServiceWorkerRouterRules rules;
{
blink::ServiceWorkerRouterRule rule;
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"/test/*",
[](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"/notmatch/*",
[](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterSource source;
source.type = blink::ServiceWorkerRouterSource::SourceType::kNetwork;
source.network_source = blink::ServiceWorkerRouterNetworkSource{};
rule.sources.push_back(source);
}
rules.rules.push_back(rule);
}
ASSERT_EQ(1U, rules.rules.size());
ServiceWorkerRouterEvaluator evaluator(rules);
ASSERT_EQ(1U, evaluator.rules().rules.size());
EXPECT_TRUE(evaluator.IsValid());
network::ResourceRequest request;
request.method = "GET";
request.url = GURL("https://example.com/test/page.html");
const auto sources = evaluator.Evaluate(request);
EXPECT_EQ(0U, sources.size());
}
TEST(ServiceWorkerRouterEvaluator, AllConditionMatch) {
blink::ServiceWorkerRouterRules rules;
{
blink::ServiceWorkerRouterRule rule;
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"/test/*",
[](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"*.html", [](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterSource source;
source.type = blink::ServiceWorkerRouterSource::SourceType::kNetwork;
source.network_source = blink::ServiceWorkerRouterNetworkSource{};
rule.sources.push_back(source);
}
rules.rules.push_back(rule);
}
ASSERT_EQ(1U, rules.rules.size());
ServiceWorkerRouterEvaluator evaluator(rules);
ASSERT_EQ(1U, evaluator.rules().rules.size());
EXPECT_TRUE(evaluator.IsValid());
network::ResourceRequest request;
request.method = "GET";
request.url = GURL("https://example.com/test/page.html");
const auto sources = evaluator.Evaluate(request);
EXPECT_EQ(1U, sources.size());
}
TEST(ServiceWorkerRouterEvaluator, ChooseMatchedRoute) {
blink::ServiceWorkerRouterRules rules;
{
blink::ServiceWorkerRouterRule rule;
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"*.html", [](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterSource source;
source.type = blink::ServiceWorkerRouterSource::SourceType::kNetwork;
source.network_source = blink::ServiceWorkerRouterNetworkSource{};
rule.sources.push_back(source);
rule.sources.push_back(source);
}
rules.rules.push_back(rule);
}
{
blink::ServiceWorkerRouterRule rule;
{
blink::ServiceWorkerRouterCondition condition;
condition.type =
blink::ServiceWorkerRouterCondition::ConditionType::kUrlPattern;
blink::UrlPattern url_pattern;
auto parse_result = liburlpattern::Parse(
"*.css", [](base::StringPiece input) { return std::string(input); });
ASSERT_TRUE(parse_result.ok());
url_pattern.pathname = parse_result.value().PartList();
condition.url_pattern = url_pattern;
rule.conditions.push_back(condition);
}
{
blink::ServiceWorkerRouterSource source;
source.type = blink::ServiceWorkerRouterSource::SourceType::kNetwork;
source.network_source = blink::ServiceWorkerRouterNetworkSource{};
rule.sources.push_back(source);
rule.sources.push_back(source);
rule.sources.push_back(source);
rule.sources.push_back(source);
}
rules.rules.push_back(rule);
}
ASSERT_EQ(2U, rules.rules.size());
ServiceWorkerRouterEvaluator evaluator(rules);
ASSERT_EQ(2U, evaluator.rules().rules.size());
EXPECT_TRUE(evaluator.IsValid());
network::ResourceRequest request;
request.method = "GET";
request.url = GURL("https://example.com/top/test.css");
const auto sources = evaluator.Evaluate(request);
EXPECT_EQ(4U, sources.size());
}
} // namespace
} // namespace content

@ -2690,6 +2690,7 @@ test("content_unittests") {
"../common/input/gesture_event_stream_validator_unittest.cc",
"../common/input/touch_event_stream_validator_unittest.cc",
"../common/pseudonymization_salt_unittest.cc",
"../common/service_worker/service_worker_router_evaluator_unittest.cc",
"../common/url_utils_unittest.cc",
"../common/user_agent_unittest.cc",
"../common/webid/identity_url_loader_throttle_unittest.cc",