blob: 3c877810ce63fec80a3bfca3ddbc290415a45530 [file] [log] [blame]
// 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 "third_party/blink/renderer/modules/url_pattern/url_pattern.h"
#include "base/i18n/uchar.h"
#include "base/strings/string_util.h"
#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h"
#include "third_party/blink/renderer/bindings/modules/v8/usv_string_or_url_pattern_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_url_pattern_component_result.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_url_pattern_result.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/liburlpattern/parse.h"
#include "third_party/liburlpattern/pattern.h"
namespace blink {
// A struct representing all the information needed to match a particular
// component of a URL.
class URLPattern::Component final
: public GarbageCollected<URLPattern::Component> {
public:
bool Match(StringView input, Vector<String>* group_list) const {
return regexp->Match(input, /*start_from=*/0, /*match_length=*/nullptr,
group_list) == 0;
}
void Trace(Visitor* visitor) const { visitor->Trace(regexp); }
// The pattern compiled down to a js regular expression.
Member<ScriptRegexp> regexp;
// The names to be applied to the regular expression capture groups. Note,
// liburlpattern regular expressions do not use named capture groups directly.
Vector<String> name_list;
Component(ScriptRegexp* r, Vector<String> n)
: regexp(r), name_list(std::move(n)) {}
};
namespace {
// The liburlpattern::Options to use for most component patterns. We
// default to strict mode and case sensitivity. In addition, most
// components have no concept of a delimiter or prefix character.
const liburlpattern::Options& DefaultOptions() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(liburlpattern::Options, options,
({.delimiter_list = "",
.prefix_list = "",
.sensitive = true,
.strict = true}));
return options;
}
// The liburlpattern::Options to use for hostname patterns. This uses a
// "." delimiter controlling how far a named group like ":bar" will match
// by default. Note, hostnames are case insensitive but we require case
// sensitivity here. This assumes that the hostname values have already
// been normalized to lower case as in URL().
const liburlpattern::Options& HostnameOptions() {
DEFINE_STATIC_LOCAL(liburlpattern::Options, options,
({.delimiter_list = ".",
.prefix_list = "",
.sensitive = true,
.strict = true}));
return options;
}
// The liburlpattern::Options to use for pathname patterns. This uses a
// "/" delimiter controlling how far a named group like ":bar" will match
// by default. It also configures "/" to be treated as an automatic
// prefix before groups.
const liburlpattern::Options& PathnameOptions() {
DEFINE_STATIC_LOCAL(liburlpattern::Options, options,
({.delimiter_list = "/",
.prefix_list = "/",
.sensitive = true,
.strict = true}));
return options;
}
// An enum indicating whether the associated component values be operated
// on are for patterns or URLs. Validation and canonicalization will
// do different things depending on the type.
enum class ValueType {
kPattern,
kURL,
};
// Utility function to determine if a pathname is absolute or not. For
// kURL values this mainly consists of a check for a leading slash. For
// patterns we do some additional checking for escaped or grouped slashes.
bool IsAbsolutePathname(const String& pathname, ValueType type) {
if (pathname.IsEmpty())
return false;
if (pathname[0] == '/')
return true;
if (type == ValueType::kURL)
return false;
if (pathname.length() < 2)
return false;
// Patterns treat escaped slashes and slashes within an explicit grouping as
// valid leading slashes. For example, "\/foo" or "{/foo}". Patterns do
// not consider slashes within a custom regexp group as valid for the leading
// pathname slash for now. To support that we would need to be able to
// detect things like ":name_123(/foo)" as a valid leading group in a pattern,
// but that is considered too complex for now.
if ((pathname[0] == '\\' || pathname[0] == '{') && pathname[1] == '/') {
return true;
}
return false;
}
// Utility function to validate that a pattern value contains only ASCII.
void ValidatePatternEncoding(const String& pattern,
StringView label,
ExceptionState& exception_state) {
if (pattern.ContainsOnlyASCIIOrEmpty())
return;
// TODO: Consider if we should canonicalize patterns instead. See:
// https://github.com/WICG/urlpattern/issues/33
exception_state.ThrowTypeError("Illegal character in " + label +
" pattern '" + pattern +
"'. Patterns must be URL encoded ASCII.");
}
String StringFromCanonOutput(const url::CanonOutput& output,
url::Component component) {
return String::FromUTF8(output.data() + component.begin, component.len);
}
// Utility function to canonicalize a protocol string. Throws an exception
// if the input is invalid. The canonicalization and/or validation will
// differ depending on whether |type| is kURL or kPattern.
String CanonicalizeProtocol(const String& input,
ValueType type,
ExceptionState& exception_state) {
if (type == ValueType::kPattern) {
ValidatePatternEncoding(input, "protocol", exception_state);
return input;
}
bool result = false;
url::RawCanonOutputT<char> canon_output;
url::Component component;
if (input.Is8Bit()) {
StringUTF8Adaptor utf8(input);
result = url::CanonicalizeScheme(
utf8.data(), url::Component(0, utf8.size()), &canon_output, &component);
} else {
result = url::CanonicalizeScheme(
base::i18n::ToChar16Ptr(input.Characters16()),
url::Component(0, input.length()), &canon_output, &component);
}
if (!result) {
exception_state.ThrowTypeError("Invalid protocol '" + input + "'.");
return String();
}
return StringFromCanonOutput(canon_output, component);
}
// Utility function to canonicalize username and/or password strings. Throws
// an exception if either is invalid. The canonicalization and/or validation
// will differ depending on whether |type| is kURL or kPattern. On success
// |username_out| and |password_out| will contain the canonical values.
void CanonicalizeUsernameAndPassword(const String& username,
const String& password,
ValueType type,
String& username_out,
String& password_out,
ExceptionState& exception_state) {
if (type == ValueType::kPattern) {
ValidatePatternEncoding(username, "username", exception_state);
if (exception_state.HadException())
return;
ValidatePatternEncoding(password, "password", exception_state);
if (exception_state.HadException())
return;
username_out = username;
password_out = password;
return;
}
bool result = false;
url::RawCanonOutputT<char> canon_output;
url::Component username_component;
url::Component password_component;
if (username && password && username.Is8Bit() && password.Is8Bit()) {
StringUTF8Adaptor username_utf8(username);
StringUTF8Adaptor password_utf8(password);
result = url::CanonicalizeUserInfo(
username_utf8.data(), url::Component(0, username_utf8.size()),
password_utf8.data(), url::Component(0, password_utf8.size()),
&canon_output, &username_component, &password_component);
} else {
String username16(username);
String password16(password);
username16.Ensure16Bit();
password16.Ensure16Bit();
result = url::CanonicalizeUserInfo(
base::i18n::ToChar16Ptr(username16.Characters16()),
url::Component(0, username16.length()),
base::i18n::ToChar16Ptr(password16.Characters16()),
url::Component(0, password16.length()), &canon_output,
&username_component, &password_component);
}
if (!result) {
exception_state.ThrowTypeError("Invalid username '" + username +
"' and/or password '" + password + "'.");
return;
}
if (username_component.len != -1)
username_out = StringFromCanonOutput(canon_output, username_component);
if (password_component.len != -1)
password_out = StringFromCanonOutput(canon_output, password_component);
}
// Utility function to canonicalize a hostname string. Throws an exception
// if the input is invalid. The canonicalization and/or validation will
// differ depending on whether |type| is kURL or kPattern.
String CanonicalizeHostname(const String& input,
ValueType type,
ExceptionState& exception_state) {
if (type == ValueType::kPattern) {
ValidatePatternEncoding(input, "hostname", exception_state);
return input;
}
bool success = false;
String result = SecurityOrigin::CanonicalizeHost(input, &success);
if (!success) {
exception_state.ThrowTypeError("Invalid hostname '" + input + "'.");
return String();
}
return result;
}
// Utility function to canonicalize a port string. Throws an exception
// if the input is invalid. The canonicalization and/or validation will
// differ depending on whether |type| is kURL or kPattern. The |protocol|
// must be provided in order to handle default ports correctly.
String CanonicalizePort(const String& input,
ValueType type,
const String& protocol,
ExceptionState& exception_state) {
if (type == ValueType::kPattern) {
ValidatePatternEncoding(input, "port", exception_state);
return input;
}
int default_port = url::PORT_UNSPECIFIED;
if (!input.IsEmpty()) {
StringUTF8Adaptor protocol_utf8(protocol);
default_port =
url::DefaultPortForScheme(protocol_utf8.data(), protocol_utf8.size());
}
// Since ports only consist of digits there should be no encoding needed.
// Therefore we directly use the UTF8 encoding version of CanonicalizePort().
StringUTF8Adaptor utf8(input);
url::RawCanonOutputT<char> canon_output;
url::Component component;
if (!url::CanonicalizePort(utf8.data(), url::Component(0, utf8.size()),
default_port, &canon_output, &component)) {
exception_state.ThrowTypeError("Invalid port '" + input + "'.");
return String();
}
return component.len == -1 ? g_empty_string
: StringFromCanonOutput(canon_output, component);
}
// Utility function to canonicalize a pathname string. Throws an exception
// if the input is invalid. The canonicalization and/or validation will
// differ depending on whether |type| is kURL or kPattern.
String CanonicalizePathname(const String& input,
ValueType type,
ExceptionState& exception_state) {
if (type == ValueType::kPattern) {
ValidatePatternEncoding(input, "pathname", exception_state);
return input;
}
if (!IsAbsolutePathname(input, type)) {
exception_state.ThrowTypeError("Cannot resolve absolute pathname for '" +
input + "'.");
return String();
}
bool result = false;
url::RawCanonOutputT<char> canon_output;
url::Component component;
if (input.Is8Bit()) {
StringUTF8Adaptor utf8(input);
result = url::CanonicalizePath(utf8.data(), url::Component(0, utf8.size()),
&canon_output, &component);
} else {
result = url::CanonicalizePath(
base::i18n::ToChar16Ptr(input.Characters16()),
url::Component(0, input.length()), &canon_output, &component);
}
if (!result) {
exception_state.ThrowTypeError("Invalid pathname '" + input + "'.");
return String();
}
return StringFromCanonOutput(canon_output, component);
}
// Utility function to canonicalize a search string. Throws an exception
// if the input is invalid. The canonicalization and/or validation will
// differ depending on whether |type| is kURL or kPattern.
String CanonicalizeSearch(const String& input,
ValueType type,
ExceptionState& exception_state) {
if (type == ValueType::kPattern) {
ValidatePatternEncoding(input, "search", exception_state);
return input;
}
url::RawCanonOutputT<char> canon_output;
url::Component component;
if (input.Is8Bit()) {
StringUTF8Adaptor utf8(input);
url::CanonicalizeQuery(utf8.data(), url::Component(0, utf8.size()),
/*converter=*/nullptr, &canon_output, &component);
} else {
url::CanonicalizeQuery(base::i18n::ToChar16Ptr(input.Characters16()),
url::Component(0, input.length()),
/*converter=*/nullptr, &canon_output, &component);
}
return StringFromCanonOutput(canon_output, component);
}
// Utility function to canonicalize a hash string. Throws an exception
// if the input is invalid. The canonicalization and/or validation will
// differ depending on whether |type| is kURL or kPattern.
String CanonicalizeHash(const String& input,
ValueType type,
ExceptionState& exception_state) {
if (type == ValueType::kPattern) {
ValidatePatternEncoding(input, "hash", exception_state);
return input;
}
url::RawCanonOutputT<char> canon_output;
url::Component component;
if (input.Is8Bit()) {
StringUTF8Adaptor utf8(input);
url::CanonicalizeRef(utf8.data(), url::Component(0, utf8.size()),
&canon_output, &component);
} else {
url::CanonicalizeRef(base::i18n::ToChar16Ptr(input.Characters16()),
url::Component(0, input.length()), &canon_output,
&component);
}
return StringFromCanonOutput(canon_output, component);
}
// A utility method that takes a URLPatternInit, splits it apart, and applies
// the individual component values in the given set of strings. The strings
// are only applied if a value is present in the init structure.
void ApplyInit(const URLPatternInit* init,
ValueType type,
String& protocol,
String& username,
String& password,
String& hostname,
String& port,
String& pathname,
String& search,
String& hash,
ExceptionState& exception_state) {
// If there is a baseURL we need to apply its component values first. The
// rest of the URLPatternInit structure will then later override these
// values. Note, the baseURL will always set either an empty string or
// longer value for each considered component. We do not allow null strings
// to persist for these components past this phase since they should no
// longer be treated as wildcards.
KURL base_url;
if (init->hasBaseURL()) {
base_url = KURL(init->baseURL());
if (!base_url.IsValid() || base_url.IsEmpty()) {
exception_state.ThrowTypeError("Invalid baseURL '" + init->baseURL() +
"'.");
return;
}
protocol = base_url.Protocol() ? base_url.Protocol() : g_empty_string;
username = base_url.User() ? base_url.User() : g_empty_string;
password = base_url.Pass() ? base_url.Pass() : g_empty_string;
hostname = base_url.Host() ? base_url.Host() : g_empty_string;
port =
base_url.Port() > 0 ? String::Number(base_url.Port()) : g_empty_string;
pathname = base_url.GetPath() ? base_url.GetPath() : g_empty_string;
// Do no propagate search or hash from the base URL. This matches the
// behavior when resolving a relative URL against a base URL.
}
// Apply the URLPatternInit component values on top of the default and
// baseURL values.
if (init->hasProtocol()) {
protocol = CanonicalizeProtocol(init->protocol(), type, exception_state);
if (exception_state.HadException())
return;
}
if (init->hasUsername() || init->hasPassword()) {
CanonicalizeUsernameAndPassword(init->username(), init->password(), type,
username, password, exception_state);
if (exception_state.HadException())
return;
}
if (init->hasHostname()) {
hostname = CanonicalizeHostname(init->hostname(), type, exception_state);
if (exception_state.HadException())
return;
}
if (init->hasPort()) {
port = CanonicalizePort(init->port(), type, protocol, exception_state);
if (exception_state.HadException())
return;
}
if (init->hasPathname()) {
pathname = init->pathname();
if (base_url.IsValid() && base_url.IsHierarchical() &&
!IsAbsolutePathname(pathname, type)) {
// Find the last slash in the baseURL pathname. Since the URL is
// hierarchical it should have a slash to be valid, but we are cautious
// and check. If there is no slash then we cannot use resolve the
// relative pathname and just treat the init pathname as an absolute
// value.
auto slash_index = base_url.GetPath().ReverseFind("/");
if (slash_index != kNotFound) {
// Extract the baseURL path up to and including the first slash. Append
// the relative init pathname to it.
pathname = base_url.GetPath().Substring(0, slash_index + 1) + pathname;
}
}
pathname = CanonicalizePathname(pathname, type, exception_state);
if (exception_state.HadException())
return;
}
if (init->hasSearch()) {
search = CanonicalizeSearch(init->search(), type, exception_state);
if (exception_state.HadException())
return;
}
if (init->hasHash()) {
hash = CanonicalizeHash(init->hash(), type, exception_state);
if (exception_state.HadException())
return;
}
}
} // namespace
URLPattern* URLPattern::Create(const URLPatternInit* init,
ExceptionState& exception_state) {
// Each component defaults to a wildcard matching any input. We use
// the null string as a shorthand for the default.
String protocol;
String username;
String password;
String hostname;
String port;
String pathname;
String search;
String hash;
// Apply the input URLPatternInit on top of the default values.
ApplyInit(init, ValueType::kPattern, protocol, username, password, hostname,
port, pathname, search, hash, exception_state);
if (exception_state.HadException())
return nullptr;
// Compile each component pattern into a Component structure that can
// be used for matching. Components that match any input may have a
// nullptr Component struct pointer.
auto* protocol_component =
CompilePattern(protocol, "protocol", DefaultOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
auto* username_component =
CompilePattern(username, "username", DefaultOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
auto* password_component =
CompilePattern(password, "password", DefaultOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
auto* hostname_component =
CompilePattern(hostname, "hostname", HostnameOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
auto* port_component =
CompilePattern(port, "port", DefaultOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
auto* pathname_component =
CompilePattern(pathname, "pathname", PathnameOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
auto* search_component =
CompilePattern(search, "search", DefaultOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
auto* hash_component =
CompilePattern(hash, "hash", DefaultOptions(), exception_state);
if (exception_state.HadException())
return nullptr;
return MakeGarbageCollected<URLPattern>(
protocol_component, username_component, password_component,
hostname_component, port_component, pathname_component, search_component,
hash_component, base::PassKey<URLPattern>());
}
URLPattern::URLPattern(Component* protocol,
Component* username,
Component* password,
Component* hostname,
Component* port,
Component* pathname,
Component* search,
Component* hash,
base::PassKey<URLPattern> key)
: protocol_(protocol),
username_(username),
password_(password),
hostname_(hostname),
port_(port),
pathname_(pathname),
search_(search),
hash_(hash) {}
bool URLPattern::test(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state) const {
return Match(input, /*result=*/nullptr, exception_state);
}
URLPatternResult* URLPattern::exec(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state) const {
URLPatternResult* result = URLPatternResult::Create();
if (!Match(input, result, exception_state))
return nullptr;
return result;
}
void URLPattern::Trace(Visitor* visitor) const {
visitor->Trace(protocol_);
visitor->Trace(username_);
visitor->Trace(password_);
visitor->Trace(hostname_);
visitor->Trace(port_);
visitor->Trace(pathname_);
visitor->Trace(search_);
visitor->Trace(hash_);
ScriptWrappable::Trace(visitor);
}
// static
URLPattern::Component* URLPattern::CompilePattern(
const String& pattern,
StringView component,
const liburlpattern::Options& options,
ExceptionState& exception_state) {
// If the pattern is null then optimize by not compiling a pattern. Instead,
// a nullptr Component is interpreted as matching any input value.
if (pattern.IsNull())
return nullptr;
// Parse the pattern.
StringUTF8Adaptor utf8(pattern);
auto parse_result = liburlpattern::Parse(
absl::string_view(utf8.data(), utf8.size()), options);
if (!parse_result.ok()) {
exception_state.ThrowTypeError("Invalid " + component + " pattern '" +
pattern + "'.");
return nullptr;
}
// Extract a regular expression string from the parsed pattern.
std::vector<std::string> name_list;
std::string regexp_string =
parse_result.value().GenerateRegexString(&name_list);
// Compile the regular expression to verify it is valid.
auto case_sensitive = options.sensitive ? WTF::kTextCaseSensitive
: WTF::kTextCaseASCIIInsensitive;
DCHECK(base::IsStringASCII(regexp_string));
ScriptRegexp* regexp = MakeGarbageCollected<ScriptRegexp>(
String(regexp_string.data(), regexp_string.size()), case_sensitive);
if (!regexp->IsValid()) {
// TODO: Figure out which embedded regex expression caused the failure
// by compiling each pattern kRegex part individually.
exception_state.ThrowTypeError("Invalid " + component + " pattern '" +
pattern + "'.");
return nullptr;
}
Vector<String> wtf_name_list;
wtf_name_list.ReserveInitialCapacity(
static_cast<wtf_size_t>(name_list.size()));
for (const auto& name : name_list) {
DCHECK(base::IsStringASCII(name));
wtf_name_list.push_back(String(name.data(), name.size()));
}
return MakeGarbageCollected<URLPattern::Component>(std::move(regexp),
std::move(wtf_name_list));
}
bool URLPattern::Match(const USVStringOrURLPatternInit& input,
URLPatternResult* result,
ExceptionState& exception_state) const {
// By default each URL component value starts with an empty string. The
// given input is then layered on top of these defaults.
String protocol(g_empty_string);
String username(g_empty_string);
String password(g_empty_string);
String hostname(g_empty_string);
String port(g_empty_string);
String pathname(g_empty_string);
String search(g_empty_string);
String hash(g_empty_string);
if (input.IsURLPatternInit()) {
// Layer the URLPatternInit values on top of the default empty strings.
ApplyInit(input.GetAsURLPatternInit(), ValueType::kURL, protocol, username,
password, hostname, port, pathname, search, hash,
exception_state);
if (exception_state.HadException()) {
// Treat exceptions simply as a failure to match.
exception_state.ClearException();
return false;
}
} else {
DCHECK(input.IsUSVString());
// The compile the input string as a fully resolved URL.
KURL url(input.GetAsUSVString());
if (!url.IsValid() || url.IsEmpty()) {
// Treat as failure to match, but don't throw an exception.
return false;
}
// TODO: Support relative URLs here by taking a string in a second argument.
// Apply the parsed URL components on top of our defaults.
if (url.Protocol())
protocol = url.Protocol();
if (url.User())
username = url.User();
if (url.Pass())
password = url.Pass();
if (url.Host())
hostname = url.Host();
if (url.Port() > 0)
port = String::Number(url.Port());
if (url.GetPath())
pathname = url.GetPath();
if (url.Query())
search = url.Query();
if (url.FragmentIdentifier())
hash = url.FragmentIdentifier();
}
Vector<String> protocol_group_list;
Vector<String> username_group_list;
Vector<String> password_group_list;
Vector<String> hostname_group_list;
Vector<String> port_group_list;
Vector<String> pathname_group_list;
Vector<String> search_group_list;
Vector<String> hash_group_list;
// If we are not generating a full result then we don't need to populate
// group lists.
auto* protocol_group_list_ref = result ? &protocol_group_list : nullptr;
auto* username_group_list_ref = result ? &username_group_list : nullptr;
auto* password_group_list_ref = result ? &password_group_list : nullptr;
auto* hostname_group_list_ref = result ? &hostname_group_list : nullptr;
auto* port_group_list_ref = result ? &port_group_list : nullptr;
auto* pathname_group_list_ref = result ? &pathname_group_list : nullptr;
auto* search_group_list_ref = result ? &search_group_list : nullptr;
auto* hash_group_list_ref = result ? &hash_group_list : nullptr;
// Each component of the pattern must match the corresponding component of
// the input. If a pattern Component is nullptr, then it matches any
// input and we can avoid running a real regular expression match.
bool matched =
(!protocol_ || protocol_->Match(protocol, protocol_group_list_ref)) &&
(!username_ || username_->Match(username, username_group_list_ref)) &&
(!password_ || password_->Match(password, password_group_list_ref)) &&
(!hostname_ || hostname_->Match(hostname, hostname_group_list_ref)) &&
(!port_ || port_->Match(port, port_group_list_ref)) &&
(!pathname_ || pathname_->Match(pathname, pathname_group_list_ref)) &&
(!search_ || search_->Match(search, search_group_list_ref)) &&
(!hash_ || hash_->Match(hash, hash_group_list_ref));
if (!matched || !result)
return matched;
// TODO: The result.input contains the data before canonicalization, but the
// component results will contain inputs after canonicalization. Is
// this what we want? See: https://github.com/WICG/urlpattern/issues/34
result->setInput(input);
result->setProtocol(
MakeComponentResult(protocol_, protocol, protocol_group_list));
result->setUsername(
MakeComponentResult(username_, username, username_group_list));
result->setPassword(
MakeComponentResult(password_, password, password_group_list));
result->setHostname(
MakeComponentResult(hostname_, hostname, hostname_group_list));
result->setPort(MakeComponentResult(port_, port, port_group_list));
result->setPathname(
MakeComponentResult(pathname_, pathname, pathname_group_list));
result->setSearch(MakeComponentResult(search_, search, search_group_list));
result->setHash(MakeComponentResult(hash_, hash, hash_group_list));
return true;
}
// static
URLPatternComponentResult* URLPattern::MakeComponentResult(
Component* component,
const String& input,
const Vector<String>& group_list) {
Vector<std::pair<String, String>> groups;
if (!component) {
// When there is not Component we must act as if there was a default
// wildcard pattern with a group. The group includes the entire input.
groups.emplace_back("0", input);
} else {
DCHECK_EQ(component->name_list.size(), group_list.size());
for (wtf_size_t i = 0; i < group_list.size(); ++i) {
groups.emplace_back(component->name_list[i], group_list[i]);
}
}
auto* result = URLPatternComponentResult::Create();
result->setInput(input);
result->setGroups(groups);
return result;
}
} // namespace blink