summaryrefslogtreecommitdiff
path: root/chromium/components/js_injection/common
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/js_injection/common')
-rw-r--r--chromium/components/js_injection/common/BUILD.gn71
-rw-r--r--chromium/components/js_injection/common/OWNERS4
-rw-r--r--chromium/components/js_injection/common/interfaces.mojom65
-rw-r--r--chromium/components/js_injection/common/origin_matcher.cc125
-rw-r--r--chromium/components/js_injection/common/origin_matcher.h75
-rw-r--r--chromium/components/js_injection/common/origin_matcher.mojom22
-rw-r--r--chromium/components/js_injection/common/origin_matcher_internal.cc125
-rw-r--r--chromium/components/js_injection/common/origin_matcher_internal.h89
-rw-r--r--chromium/components/js_injection/common/origin_matcher_mojom_traits.cc85
-rw-r--r--chromium/components/js_injection/common/origin_matcher_mojom_traits.h45
-rw-r--r--chromium/components/js_injection/common/origin_matcher_unittest.cc404
11 files changed, 1110 insertions, 0 deletions
diff --git a/chromium/components/js_injection/common/BUILD.gn b/chromium/components/js_injection/common/BUILD.gn
new file mode 100644
index 00000000000..4c444214440
--- /dev/null
+++ b/chromium/components/js_injection/common/BUILD.gn
@@ -0,0 +1,71 @@
+# Copyright 2020 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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("common_mojom") {
+ sources = [
+ "interfaces.mojom",
+ "origin_matcher.mojom",
+ ]
+
+ public_deps = [
+ "//mojo/public/mojom/base",
+ "//third_party/blink/public/mojom:mojom_core",
+ ]
+
+ cpp_typemaps = [
+ {
+ types = [
+ {
+ mojom = "js_injection.mojom.OriginMatcher"
+ cpp = "::js_injection::OriginMatcher"
+ },
+ {
+ mojom = "js_injection.mojom.OriginMatcherRule"
+ cpp = "::std::unique_ptr<::js_injection::OriginMatcherRule>"
+ move_only = true
+ },
+ ]
+ traits_headers = [
+ "origin_matcher_mojom_traits.h",
+ "origin_matcher.h",
+ ]
+ traits_sources = [ "origin_matcher_mojom_traits.cc" ]
+ traits_public_deps = [ ":common" ]
+ },
+ ]
+ overridden_deps = [ "//third_party/blink/public/mojom:mojom_core" ]
+ component_deps = [ "//third_party/blink/public/common" ]
+}
+
+source_set("common") {
+ public = [ "origin_matcher.h" ]
+ sources = [
+ "origin_matcher.cc",
+ "origin_matcher_internal.cc",
+ "origin_matcher_internal.h",
+ ]
+ deps = [
+ "//base",
+ "//net",
+ "//url",
+ ]
+
+ # origin_matcher_internal is needed by mojom traits and tests.
+ friend = [ ":*" ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "origin_matcher_unittest.cc" ]
+ deps = [
+ ":common",
+ ":common_mojom",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//url",
+ ]
+}
diff --git a/chromium/components/js_injection/common/OWNERS b/chromium/components/js_injection/common/OWNERS
new file mode 100644
index 00000000000..b36774dad1d
--- /dev/null
+++ b/chromium/components/js_injection/common/OWNERS
@@ -0,0 +1,4 @@
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chromium/components/js_injection/common/interfaces.mojom b/chromium/components/js_injection/common/interfaces.mojom
new file mode 100644
index 00000000000..0e5b1047f91
--- /dev/null
+++ b/chromium/components/js_injection/common/interfaces.mojom
@@ -0,0 +1,65 @@
+// 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.
+
+module js_injection.mojom;
+
+import "components/js_injection/common/origin_matcher.mojom";
+import "mojo/public/mojom/base/string16.mojom";
+import "third_party/blink/public/mojom/messaging/message_port_descriptor.mojom";
+
+// JsObject struct represents a JavaScript object we will inject in the main
+// JavaScript world of a frame. |js_object_name| will be used as the name
+// of the JavaScript object. We will inject the object if the frame's origin
+// matches |origin_matcher|. |js_to_browser_messaging| will be used for that
+// JavaScript object to send message back to browser side.
+struct JsObject {
+ mojo_base.mojom.String16 js_object_name;
+ pending_associated_remote<JsToBrowserMessaging> js_to_browser_messaging;
+ js_injection.mojom.OriginMatcher origin_matcher;
+};
+
+// DocumentStartJavaScript struct contains the JavaScript snippet |script| and
+// the corresponding |origin_matcher|. We will run the script if the frame's
+// origin matches any rules in the |origin_matcher|.
+struct DocumentStartJavaScript {
+ int32 script_id;
+ mojo_base.mojom.String16 script;
+ js_injection.mojom.OriginMatcher origin_matcher;
+};
+
+// For JavaScript postMessage() API, implemented by browser.
+interface JsToBrowserMessaging {
+ // Called from renderer, browser receives |message| and possible |ports|,
+ // The |message| is an opaque type and the contents are defined by the client
+ // of this API.
+ PostMessage(mojo_base.mojom.String16 message,
+ array<blink.mojom.MessagePortDescriptor> ports);
+
+ // When there is a new BrowserToJsMessaging created in renderer, we need to
+ // send/ it to browser, so browser could send message back to Js.
+ SetBrowserToJsMessaging(
+ pending_associated_remote<BrowserToJsMessaging> browser_to_js_messaging);
+};
+
+// For the browser to reply back to injected JavaScript object. Implemented by
+// the renderer.
+interface BrowserToJsMessaging {
+ // Called from browser, to send message to page.
+ OnPostMessage(mojo_base.mojom.String16 message);
+};
+
+// For browser to configure renderer, implemented by renderer.
+interface JsCommunication {
+ // Called from browser, to tell renderer that if we need to inject
+ // JavaScript objects to the frame based on the |js_objects| array.
+ SetJsObjects(array<js_injection.mojom.JsObject> js_objects);
+
+ // Called from browser, to add a script for a frame to run at document start
+ // stage. The script will run only if the frame's origin matches any of the
+ // allowed_origin_rules.
+ AddDocumentStartScript(js_injection.mojom.DocumentStartJavaScript script);
+
+ // Called from browser, to remove the script by the given script_id.
+ RemoveDocumentStartScript(int32 script_id);
+};
diff --git a/chromium/components/js_injection/common/origin_matcher.cc b/chromium/components/js_injection/common/origin_matcher.cc
new file mode 100644
index 00000000000..bdfc9fa3eaf
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher.cc
@@ -0,0 +1,125 @@
+// Copyright 2020 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 "components/js_injection/common/origin_matcher.h"
+
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/parse_number.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+#include "url/url_util.h"
+
+namespace js_injection {
+
+namespace {
+
+inline int GetDefaultPortForSchemeIfNoPortInfo(const std::string& scheme,
+ int port) {
+ // The input has explicit port information, so don't modify it.
+ if (port != -1)
+ return port;
+
+ // Hard code the port for http and https.
+ if (scheme == url::kHttpScheme)
+ return 80;
+ if (scheme == url::kHttpsScheme)
+ return 443;
+
+ return port;
+}
+
+} // namespace
+
+OriginMatcher::OriginMatcher(const OriginMatcher& rhs) {
+ *this = rhs;
+}
+
+OriginMatcher& OriginMatcher::operator=(const OriginMatcher& rhs) {
+ rules_.clear();
+ for (const auto& rule : rhs.Serialize())
+ AddRuleFromString(rule);
+ return *this;
+}
+
+void OriginMatcher::SetRules(RuleList rules) {
+ rules_.swap(rules);
+}
+
+bool OriginMatcher::AddRuleFromString(const std::string& raw_untrimmed) {
+ std::string raw;
+ base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL, &raw);
+
+ if (raw == "*") {
+ rules_.push_back(std::make_unique<MatchAllOriginsRule>());
+ return true;
+ }
+
+ // Extract scheme-restriction.
+ std::string::size_type scheme_pos = raw.find("://");
+ if (scheme_pos == std::string::npos)
+ return false;
+
+ const std::string scheme = raw.substr(0, scheme_pos);
+ if (!SubdomainMatchingRule::IsValidScheme(scheme))
+ return false;
+
+ std::string host_and_port = raw.substr(scheme_pos + 3);
+ if (host_and_port.empty()) {
+ if (!SubdomainMatchingRule::IsValidSchemeAndHost(scheme, std::string()))
+ return false;
+ rules_.push_back(
+ std::make_unique<SubdomainMatchingRule>(scheme, std::string(), -1));
+ return true;
+ }
+
+ std::string host;
+ int port;
+ if (!net::ParseHostAndPort(host_and_port, &host, &port) ||
+ !SubdomainMatchingRule::IsValidSchemeAndHost(scheme, host)) {
+ return false;
+ }
+
+ // Check if we have an <ip-address>[:port] input and try to canonicalize the
+ // IP literal.
+ net::IPAddress ip_address;
+ if (ip_address.AssignFromIPLiteral(host)) {
+ port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
+ host = ip_address.ToString();
+ if (ip_address.IsIPv6())
+ host = '[' + host + ']';
+ rules_.push_back(
+ std::make_unique<SubdomainMatchingRule>(scheme, host, port));
+ return true;
+ }
+
+ port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
+ rules_.push_back(std::make_unique<SubdomainMatchingRule>(scheme, host, port));
+ return true;
+}
+
+bool OriginMatcher::Matches(const url::Origin& origin) const {
+ GURL origin_url = origin.GetURL();
+ // Since we only do kInclude vs kNoMatch, the order doesn't actually matter.
+ for (auto it = rules_.rbegin(); it != rules_.rend(); ++it) {
+ net::SchemeHostPortMatcherResult result = (*it)->Evaluate(origin_url);
+ if (result == net::SchemeHostPortMatcherResult::kInclude)
+ return true;
+ }
+ return false;
+}
+
+std::vector<std::string> OriginMatcher::Serialize() const {
+ std::vector<std::string> result;
+ result.reserve(rules_.size());
+ for (const auto& rule : rules_) {
+ result.push_back(rule->ToString());
+ }
+ return result;
+}
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/common/origin_matcher.h b/chromium/components/js_injection/common/origin_matcher.h
new file mode 100644
index 00000000000..08f176042b6
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher.h
@@ -0,0 +1,75 @@
+// Copyright 2020 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.
+
+#ifndef COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_H_
+#define COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace url {
+class Origin;
+} // namespace url
+
+namespace js_injection {
+
+class OriginMatcherRule;
+
+// An url origin matcher allows wildcard subdomain matching. It supports two
+// types of rules.
+//
+// (1) "*"
+// A single * (without quote) will match any origin.
+//
+// (2) SCHEME "://" [ HOSTNAME_PATTERN ][":" PORT]
+//
+// SCHEME is required. When matching custom schemes, HOSTNAME_PATTERN and PORT
+// shouldn't present. When SCHEME is "http" or "https", HOSTNAME_PATTERN is
+// required.
+//
+// HOSTNAME_PATTERN allows wildcard '*' to match subdomains, such as
+// "*.example.com". Rules such as "x.*.y.com", "*foobar.com" are not allowed.
+// Note that "*.example.com" won't match "example.com", so need another rule
+// "example.com" to match it. If the HOSTNAME_PATTERN is an IP literal, it
+// will be used for exact matching.
+//
+// PORT is optional for "http" and "https" schemes, when it is not present, for
+// "http" and "https" schemes, it will match default port number (80 and 443
+// correspondingly).
+class OriginMatcher {
+ public:
+ using RuleList = std::vector<std::unique_ptr<OriginMatcherRule>>;
+
+ OriginMatcher() = default;
+ // Allow copy and assign.
+ OriginMatcher(const OriginMatcher& rhs);
+ OriginMatcher(OriginMatcher&&) = default;
+ OriginMatcher& operator=(const OriginMatcher& rhs);
+ OriginMatcher& operator=(OriginMatcher&&) = default;
+
+ ~OriginMatcher() = default;
+
+ void SetRules(RuleList rules);
+
+ // Adds a rule given by the string |raw|. Returns true if the rule was
+ // successfully added.
+ bool AddRuleFromString(const std::string& raw);
+
+ // Returns true if the |origin| matches any rule in this matcher.
+ bool Matches(const url::Origin& origin) const;
+
+ // Returns the current list of rules.
+ const RuleList& rules() const { return rules_; }
+
+ // Returns string representation of this origin matcher.
+ std::vector<std::string> Serialize() const;
+
+ private:
+ RuleList rules_;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_H_
diff --git a/chromium/components/js_injection/common/origin_matcher.mojom b/chromium/components/js_injection/common/origin_matcher.mojom
new file mode 100644
index 00000000000..1461812adb0
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher.mojom
@@ -0,0 +1,22 @@
+// Copyright 2020 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.
+
+module js_injection.mojom;
+
+// Values correspond to that of SubdomainMatchingRule.
+struct SubdomainMatchingRule {
+ string scheme;
+ // An empty host matches any host.
+ string optional_host;
+ int32 optional_port;
+};
+
+struct OriginMatcherRule {
+ // If this is not set, the rule matches any url.
+ SubdomainMatchingRule? subdomain_matching_rule;
+};
+
+struct OriginMatcher {
+ array<OriginMatcherRule> rules;
+};
diff --git a/chromium/components/js_injection/common/origin_matcher_internal.cc b/chromium/components/js_injection/common/origin_matcher_internal.cc
new file mode 100644
index 00000000000..0630b5f9e72
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_internal.cc
@@ -0,0 +1,125 @@
+// Copyright 2020 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 "components/js_injection/common/origin_matcher_internal.h"
+
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/scheme_host_port_matcher_rule.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace js_injection {
+namespace {
+
+// Returns false if |host| has too many wildcards.
+inline bool HostWildcardSanityCheck(const std::string& host) {
+ size_t wildcard_count = std::count(host.begin(), host.end(), '*');
+ if (wildcard_count == 0)
+ return true;
+
+ // We only allow one wildcard.
+ if (wildcard_count > 1)
+ return false;
+
+ // Start with "*." for subdomain matching.
+ if (base::StartsWith(host, "*.", base::CompareCase::SENSITIVE))
+ return true;
+
+ return false;
+}
+
+} // namespace
+
+OriginMatcherRule::OriginMatcherRule(OriginMatcherRuleType type)
+ : type_(type) {}
+
+OriginMatcherRule::~OriginMatcherRule() = default;
+
+MatchAllOriginsRule::MatchAllOriginsRule()
+ : OriginMatcherRule(OriginMatcherRuleType::kAny) {}
+
+MatchAllOriginsRule::~MatchAllOriginsRule() = default;
+
+net::SchemeHostPortMatcherResult MatchAllOriginsRule::Evaluate(
+ const GURL& url) const {
+ return net::SchemeHostPortMatcherResult::kInclude;
+}
+
+std::string MatchAllOriginsRule::ToString() const {
+ return "*";
+}
+
+SubdomainMatchingRule::SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port)
+ : OriginMatcherRule(OriginMatcherRuleType::kSubdomain),
+ scheme_(base::ToLowerASCII(scheme)),
+ optional_host_(base::ToLowerASCII(optional_host)),
+ optional_port_(optional_port) {
+ DCHECK(IsValidScheme(scheme));
+ DCHECK(IsValidSchemeAndHost(scheme_, optional_host_));
+}
+
+SubdomainMatchingRule::~SubdomainMatchingRule() = default;
+
+// static
+bool SubdomainMatchingRule::IsValidScheme(const std::string& scheme) {
+ // Wild cards are not allowed in the scheme.
+ return !scheme.empty() && scheme.find('*') == std::string::npos;
+}
+
+// static
+bool SubdomainMatchingRule::CanSchemeHaveHost(const std::string& scheme) {
+ return scheme == url::kHttpScheme || scheme == url::kHttpsScheme;
+}
+
+// static
+bool SubdomainMatchingRule::IsValidSchemeAndHost(const std::string& scheme,
+ const std::string& host) {
+ if (host.empty()) {
+ if (CanSchemeHaveHost(scheme))
+ return false;
+ return true;
+ }
+ if (!CanSchemeHaveHost(scheme))
+ return false;
+
+ // |scheme| is either https or http.
+
+ // URL like rule is invalid.
+ if (host.find('/') != std::string::npos)
+ return false;
+
+ return HostWildcardSanityCheck(host);
+}
+
+net::SchemeHostPortMatcherResult SubdomainMatchingRule::Evaluate(
+ const GURL& url) const {
+ if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) {
+ // Didn't match port expectation.
+ return net::SchemeHostPortMatcherResult::kNoMatch;
+ }
+
+ if (url.scheme() != scheme_) {
+ // Didn't match scheme expectation.
+ return net::SchemeHostPortMatcherResult::kNoMatch;
+ }
+
+ return base::MatchPattern(url.host(), optional_host_)
+ ? net::SchemeHostPortMatcherResult::kInclude
+ : net::SchemeHostPortMatcherResult::kNoMatch;
+}
+
+std::string SubdomainMatchingRule::ToString() const {
+ std::string str;
+ base::StringAppendF(&str, "%s://%s", scheme_.c_str(), optional_host_.c_str());
+ if (optional_port_ != -1)
+ base::StringAppendF(&str, ":%d", optional_port_);
+ return str;
+}
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/common/origin_matcher_internal.h b/chromium/components/js_injection/common/origin_matcher_internal.h
new file mode 100644
index 00000000000..b9661bfa7e9
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_internal.h
@@ -0,0 +1,89 @@
+// Copyright 2020 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.
+
+#ifndef COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_INTERNAL_H_
+#define COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_INTERNAL_H_
+
+#include <string>
+
+#include "net/base/scheme_host_port_matcher.h"
+
+// NOTE: this file is an implementation detail and only used by code in
+// js_injection.
+
+namespace js_injection {
+
+enum class OriginMatcherRuleType { kAny, kSubdomain };
+
+// Common superclass that includes the type of matcher.
+class OriginMatcherRule : public net::SchemeHostPortMatcherRule {
+ public:
+ explicit OriginMatcherRule(OriginMatcherRuleType type);
+ ~OriginMatcherRule() override;
+
+ OriginMatcherRuleType type() const { return type_; }
+
+ private:
+ const OriginMatcherRuleType type_;
+};
+
+// Matches *all* urls.
+class MatchAllOriginsRule : public OriginMatcherRule {
+ public:
+ MatchAllOriginsRule();
+ MatchAllOriginsRule(const MatchAllOriginsRule&) = delete;
+ MatchAllOriginsRule& operator=(const MatchAllOriginsRule&) = delete;
+ ~MatchAllOriginsRule() override;
+
+ // OriginMatcherRule:
+ net::SchemeHostPortMatcherResult Evaluate(const GURL& url) const override;
+ std::string ToString() const override;
+};
+
+// Matches against a specific scheme, optional host (potentially with
+// wild-cards) and an optional port.
+class SubdomainMatchingRule : public OriginMatcherRule {
+ public:
+ SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port);
+ // This constructor is implemented only in tests, it does no checking of
+ // args.
+ SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port,
+ bool for_test);
+ SubdomainMatchingRule(const SubdomainMatchingRule&) = delete;
+ SubdomainMatchingRule& operator=(const SubdomainMatchingRule&) = delete;
+ ~SubdomainMatchingRule() override;
+
+ // Returns true if |scheme| is a valid scheme identifier.
+ static bool IsValidScheme(const std::string& scheme);
+
+ // Returns true if the |scheme| is allowed to have a host and port part.
+ static bool CanSchemeHaveHost(const std::string& scheme);
+
+ // Returns true if |scheme| and |host| are valid.
+ static bool IsValidSchemeAndHost(const std::string& scheme,
+ const std::string& host);
+
+ const std::string& scheme() const { return scheme_; }
+ const std::string& optional_host() const { return optional_host_; }
+ int optional_port() const { return optional_port_; }
+
+ // OriginMatcherRule:
+ net::SchemeHostPortMatcherResult Evaluate(const GURL& url) const override;
+ std::string ToString() const override;
+
+ private:
+ const std::string scheme_;
+ // Empty string means no host provided.
+ const std::string optional_host_;
+ // -1 means no port provided.
+ const int optional_port_;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_INTERNAL_H_
diff --git a/chromium/components/js_injection/common/origin_matcher_mojom_traits.cc b/chromium/components/js_injection/common/origin_matcher_mojom_traits.cc
new file mode 100644
index 00000000000..3bb7bd838a0
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_mojom_traits.cc
@@ -0,0 +1,85 @@
+// Copyright 2020 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 "components/js_injection/common/origin_matcher_mojom_traits.h"
+
+#include "base/strings/pattern.h"
+#include "base/strings/stringprintf.h"
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/parse_number.h"
+#include "net/base/scheme_host_port_matcher_rule.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+#include "url/url_util.h"
+
+namespace mojo {
+
+using js_injection::SubdomainMatchingRule;
+using js_injection::mojom::OriginMatcherRuleDataView;
+
+// static
+js_injection::mojom::SubdomainMatchingRulePtr
+StructTraits<OriginMatcherRuleDataView, OriginMatcherRuleUniquePtr>::
+ subdomain_matching_rule(const OriginMatcherRuleUniquePtr& rule) {
+ if (rule->type() == js_injection::OriginMatcherRuleType::kAny)
+ return nullptr;
+
+ DCHECK_EQ(js_injection::OriginMatcherRuleType::kSubdomain, rule->type());
+ const SubdomainMatchingRule* matching_rule =
+ static_cast<SubdomainMatchingRule*>(rule.get());
+ js_injection::mojom::SubdomainMatchingRulePtr matching_rule_ptr(
+ js_injection::mojom::SubdomainMatchingRule::New());
+
+ matching_rule_ptr->scheme = matching_rule->scheme();
+ matching_rule_ptr->optional_host = matching_rule->optional_host();
+ matching_rule_ptr->optional_port = matching_rule->optional_port();
+ return matching_rule_ptr;
+}
+
+// static
+bool StructTraits<OriginMatcherRuleDataView, OriginMatcherRuleUniquePtr>::Read(
+ OriginMatcherRuleDataView r,
+ OriginMatcherRuleUniquePtr* out) {
+ DCHECK(!out->get());
+
+ js_injection::mojom::SubdomainMatchingRuleDataView
+ subdomain_matching_rule_data_view;
+ r.GetSubdomainMatchingRuleDataView(&subdomain_matching_rule_data_view);
+ if (subdomain_matching_rule_data_view.is_null()) {
+ *out = std::make_unique<js_injection::MatchAllOriginsRule>();
+ return true;
+ }
+
+ js_injection::mojom::SubdomainMatchingRulePtr subdomain_matching_rule;
+ if (!r.ReadSubdomainMatchingRule(&subdomain_matching_rule))
+ return false;
+ if (!SubdomainMatchingRule::IsValidScheme(subdomain_matching_rule->scheme) ||
+ !SubdomainMatchingRule::IsValidSchemeAndHost(
+ subdomain_matching_rule->scheme,
+ subdomain_matching_rule->optional_host)) {
+ return false;
+ }
+ *out = std::make_unique<SubdomainMatchingRule>(
+ subdomain_matching_rule->scheme, subdomain_matching_rule->optional_host,
+ subdomain_matching_rule->optional_port);
+ return true;
+}
+
+// static
+bool StructTraits<js_injection::mojom::OriginMatcherDataView,
+ js_injection::OriginMatcher>::
+ Read(js_injection::mojom::OriginMatcherDataView data,
+ js_injection::OriginMatcher* out) {
+ std::vector<OriginMatcherRuleUniquePtr> rules;
+ if (!data.ReadRules(&rules))
+ return false;
+ out->SetRules(std::move(rules));
+ return true;
+}
+
+} // namespace mojo
diff --git a/chromium/components/js_injection/common/origin_matcher_mojom_traits.h b/chromium/components/js_injection/common/origin_matcher_mojom_traits.h
new file mode 100644
index 00000000000..873d2cb94e2
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_mojom_traits.h
@@ -0,0 +1,45 @@
+// Copyright 2020 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.
+
+#ifndef COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_MOJOM_TRAITS_H_
+#define COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_MOJOM_TRAITS_H_
+
+#include <string>
+#include <vector>
+
+#include "components/js_injection/common/origin_matcher.h"
+#include "components/js_injection/common/origin_matcher.mojom.h"
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+
+namespace mojo {
+
+using OriginMatcherRuleUniquePtr =
+ std::unique_ptr<js_injection::OriginMatcherRule>;
+
+template <>
+struct StructTraits<js_injection::mojom::OriginMatcherRuleDataView,
+ OriginMatcherRuleUniquePtr> {
+ static js_injection::mojom::SubdomainMatchingRulePtr subdomain_matching_rule(
+ const OriginMatcherRuleUniquePtr& rule);
+ static bool Read(js_injection::mojom::OriginMatcherRuleDataView r,
+ OriginMatcherRuleUniquePtr* out);
+};
+
+template <>
+struct StructTraits<js_injection::mojom::OriginMatcherDataView,
+ js_injection::OriginMatcher> {
+ public:
+ static const std::vector<OriginMatcherRuleUniquePtr>& rules(
+ const js_injection::OriginMatcher& r) {
+ return r.rules();
+ }
+
+ static bool Read(js_injection::mojom::OriginMatcherDataView data,
+ js_injection::OriginMatcher* out);
+};
+
+} // namespace mojo
+
+#endif // COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_MOJOM_TRAITS_H_
diff --git a/chromium/components/js_injection/common/origin_matcher_unittest.cc b/chromium/components/js_injection/common/origin_matcher_unittest.cc
new file mode 100644
index 00000000000..ab7564ef210
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_unittest.cc
@@ -0,0 +1,404 @@
+// Copyright 2020 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 "components/js_injection/common/origin_matcher.h"
+
+#include "components/js_injection/common/origin_matcher.mojom.h"
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_util.h"
+
+namespace js_injection {
+
+SubdomainMatchingRule::SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port,
+ bool for_test)
+ : OriginMatcherRule(OriginMatcherRuleType::kSubdomain),
+ scheme_(scheme),
+ optional_host_(optional_host),
+ optional_port_(optional_port) {}
+
+class OriginMatcherTest : public testing::Test {
+ public:
+ void SetUp() override {
+ scheme_registry_ = std::make_unique<url::ScopedSchemeRegistryForTests>();
+ url::EnableNonStandardSchemesForAndroidWebView();
+ }
+
+ static url::Origin CreateOriginFromString(const std::string& url) {
+ return url::Origin::Create(GURL(url));
+ }
+
+ private:
+ std::unique_ptr<url::ScopedSchemeRegistryForTests> scheme_registry_;
+};
+
+TEST_F(OriginMatcherTest, InvalidInputs) {
+ OriginMatcher matcher;
+ // Empty string is invalid.
+ EXPECT_FALSE(matcher.AddRuleFromString(""));
+ // Scheme doesn't present.
+ EXPECT_FALSE(matcher.AddRuleFromString("example.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("://example.com"));
+ // Scheme doesn't do wildcard matching.
+ EXPECT_FALSE(matcher.AddRuleFromString("*://example.com"));
+ // URL like rule is invalid.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://www.example.com/index.html"));
+ EXPECT_FALSE(matcher.AddRuleFromString("http://192.168.0.1/*"));
+ // Only accept hostname pattern starts with "*." if there is a "*" inside.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://*foobar.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://x.*.y.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://*example.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://e*xample.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("http://*"));
+ // Invalid port.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:**"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:-1"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:+443"));
+ // Empty hostname pattern for http/https.
+ EXPECT_FALSE(matcher.AddRuleFromString("http://"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://:80"));
+ // No IP block support.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://192.168.0.0/16"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://fefe:13::abc/33"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://:1"));
+ // Invalid IP address.
+ EXPECT_FALSE(matcher.AddRuleFromString("http://[a:b:*]"));
+ EXPECT_FALSE(matcher.AddRuleFromString("http://[a:b:*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://fefe:13::*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://fefe:13:*/33"));
+ // Custom scheme with host and/or port are invalid. This is because in
+ // WebView, all the URI with the same custom scheme belong to one origin.
+ EXPECT_FALSE(matcher.AddRuleFromString("x-mail://hostname:80"));
+ EXPECT_FALSE(matcher.AddRuleFromString("x-mail://hostname"));
+ EXPECT_FALSE(matcher.AddRuleFromString("x-mail://*"));
+ // file scheme with "host"
+ EXPECT_FALSE(matcher.AddRuleFromString("file://host"));
+ EXPECT_FALSE(matcher.AddRuleFromString("file://*"));
+}
+
+TEST_F(OriginMatcherTest, ExactMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://www.example.com:99"));
+ EXPECT_EQ("https://www.example.com:99", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, SchemeDefaultPortHttp) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("http://www.example.com"));
+ EXPECT_EQ("http://www.example.com:80", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:80")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://music.example.com:80")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://music.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:80")));
+}
+
+TEST_F(OriginMatcherTest, SchemeDefaultPortHttps) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://www.example.com"));
+ EXPECT_EQ("https://www.example.com:443", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:443")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:443")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, SubdomainMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://*.example.com"));
+ EXPECT_EQ("https://*.example.com:443", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:443")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:443")));
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("https://music.video.radio.example.com")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("ftp://www.example.com")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("https://example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, SubdomainMatching2) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("http://*.www.example.com"));
+ EXPECT_EQ("http://*.www.example.com:80", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://abc.www.example.com:80")));
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("http://music.video.www.example.com")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("ftp://www.example.com")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("https://example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, PunyCode) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("http://*.xn--fsqu00a.com"));
+
+ // Chinese domain example.com
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("http://www.例子.com")));
+}
+
+TEST_F(OriginMatcherTest, IPv4AddressMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://192.168.0.1"));
+ EXPECT_EQ("https://192.168.0.1:443", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("https://192.168.0.1")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://192.168.0.1:443")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://192.168.0.1:99")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("http://192.168.0.1")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("http://192.168.0.2")));
+}
+
+TEST_F(OriginMatcherTest, IPv6AddressMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://[3ffe:2a00:100:7031:0:0::1]"));
+ // Note that the IPv6 address is canonicalized.
+ EXPECT_EQ("https://[3ffe:2a00:100:7031::1]:443",
+ matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("https://[3ffe:2a00:100:7031::1]")));
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("https://[3ffe:2a00:100:7031::1]:443")));
+
+ EXPECT_FALSE(matcher.Matches(
+ CreateOriginFromString("http://[3ffe:2a00:100:7031::1]")));
+ EXPECT_FALSE(matcher.Matches(
+ CreateOriginFromString("http://[3ffe:2a00:100:7031::1]:443")));
+ EXPECT_FALSE(matcher.Matches(
+ CreateOriginFromString("https://[3ffe:2a00:100:7031::1]:8080")));
+}
+
+TEST_F(OriginMatcherTest, WildcardMatchesEveryOrigin) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("*"));
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://foo.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:8080")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("http://192.168.0.1")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://192.168.0.1:8080")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("https://[a:b:c:d::]")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://[a:b:c:d::]:8080")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("ftp://example.com")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("about:blank")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString(
+ "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("file:///usr/local/a.txt")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString(
+ "blob:http://127.0.0.1:8080/0530b9d1-c1c2-40ff-9f9c-c57336646baa")));
+}
+
+TEST_F(OriginMatcherTest, FileOrigin) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("file://"));
+
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("file:///sdcard")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("file:///android_assets")));
+}
+
+TEST_F(OriginMatcherTest, CustomSchemeOrigin) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("x-mail://"));
+
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("x-mail://hostname")));
+}
+
+namespace {
+
+void CompareMatcherRules(const OriginMatcherRule& r1,
+ const OriginMatcherRule& r2) {
+ ASSERT_EQ(r1.type(), r2.type());
+ if (r1.type() == js_injection::OriginMatcherRuleType::kAny)
+ return;
+ const SubdomainMatchingRule& s1 =
+ static_cast<const SubdomainMatchingRule&>(r1);
+ const SubdomainMatchingRule& s2 =
+ static_cast<const SubdomainMatchingRule&>(r2);
+ EXPECT_EQ(s1.scheme(), s2.scheme());
+ EXPECT_EQ(s1.optional_host(), s2.optional_host());
+ EXPECT_EQ(s1.optional_port(), s2.optional_port());
+}
+
+void CompareMatchers(const OriginMatcher& m1, const OriginMatcher& m2) {
+ ASSERT_EQ(m1.rules().size(), m2.rules().size());
+ for (size_t i = 0; i < m1.rules().size(); ++i) {
+ ASSERT_NO_FATAL_FAILURE(
+ CompareMatcherRules(*(m1.rules()[i].get()), *(m2.rules())[i].get()));
+ }
+}
+
+} // namespace
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeMatchAll) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ ASSERT_TRUE(matcher.AddRuleFromString("*"));
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+ ASSERT_NO_FATAL_FAILURE(CompareMatchers(matcher, deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeSubdomainMatcher) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ ASSERT_TRUE(matcher.AddRuleFromString("https://*.example.com"));
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+ ASSERT_NO_FATAL_FAILURE(CompareMatchers(matcher, deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeInvalidSubdomain) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The subdomain is not allowed to have a '/'.
+ rules.push_back(std::make_unique<SubdomainMatchingRule>(
+ "http", "bogus/host", 100, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeInvalidScheme) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The scheme can not be empty.
+ rules.push_back(std::make_unique<SubdomainMatchingRule>(std::string(),
+ "host", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeTooManyWildcards) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // Only one wildcard is allowed.
+ rules.push_back(
+ std::make_unique<SubdomainMatchingRule>("http", "**", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeInvalidWildcard) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The wild card must be at the front.
+ rules.push_back(
+ std::make_unique<SubdomainMatchingRule>("http", "ab*", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeValidWildcard) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The wild card must be at the front.
+ rules.push_back(
+ std::make_unique<SubdomainMatchingRule>("http", "*.ab", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+ ASSERT_NO_FATAL_FAILURE(CompareMatchers(matcher, deserialized));
+}
+
+} // namespace js_injection