Skip to content
File

Blob: src/workerd/api/urlpattern-standard.c++

9.4 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "urlpattern-standard.h"
6 
7#include "ada.h"
8 
9namespace workerd::api::urlpattern {
10std::optional<URLPattern::URLPatternRegexEngine::regex_type> URLPattern::URLPatternRegexEngine::
11 create_instance(std::string_view pattern, bool ignore_case) {
12 auto& js = jsg::Lock::current();
13 jsg::Lock::RegExpFlags flags = jsg::Lock::RegExpFlags::kUNICODE_SETS;
14 if (ignore_case) {
15 flags = static_cast<jsg::Lock::RegExpFlags>(
16 flags | static_cast<int>(jsg::Lock::RegExpFlags::kIGNORE_CASE));
17 }
18 
19 // std::string_view is not guaranteed to be null-terminated, but kj::StringPtr requires it.
20 // We need to create a null-terminated copy.
21 auto str = kj::str(kj::arrayPtr(pattern.data(), pattern.size()));
22 JSG_TRY(js) {
23 return jsg::JsRef(js, js.regexp(str, flags));
24 }
25 JSG_CATCH(_) {
26 return std::nullopt;
27 }
28}
29 
30bool URLPattern::URLPatternRegexEngine::regex_match(
31 std::string_view input, const regex_type& pattern) {
32 auto& js = jsg::Lock::current();
33 // std::string_view is not guaranteed to be null-terminated, but kj::StringPtr requires it.
34 // We need to create a null-terminated copy.
35 auto str = kj::str(kj::arrayPtr(input.data(), input.size()));
36 return pattern.getHandle(js).match(js, str);
37}
38 
39std::optional<std::vector<std::optional<std::string>>> URLPattern::URLPatternRegexEngine::
40 regex_search(std::string_view input, const regex_type& pattern) {
41 auto& js = jsg::Lock::current();
42 // std::string_view is not guaranteed to be null-terminated, but kj::StringPtr requires it.
43 // We need to create a null-terminated copy.
44 auto str = kj::str(kj::arrayPtr(input.data(), input.size()));
45 KJ_IF_SOME(matches, pattern.getHandle(js)(js, str)) {
46 std::vector<std::optional<std::string>> results(matches.size() - 1);
47 // The first value is always the input of the exec() command. Therefore
48 // we should avoid it while constructing the returning vector.
49 for (size_t i = 1; i < matches.size(); i++) {
50 auto value = matches.get(js, i);
51 if (value.isUndefined()) {
52 results[i - 1] = std::nullopt;
53 } else {
54 KJ_DASSERT(value.isString());
55 auto str = value.toString(js);
56 results[i - 1] = std::string(str.cStr(), str.size());
57 }
58 }
59 return kj::mv(results);
60 }
61 return std::nullopt;
62}
63 
64ada::url_pattern_options URLPattern::URLPatternOptions::toAdaType() const {
65 return {.ignore_case = ignoreCase.orDefault(false)};
66}
67 
68ada::url_pattern_init URLPattern::URLPatternInit::toAdaType() const {
69 ada::url_pattern_init init{};
70#define V(_, name) \
71 KJ_IF_SOME(v, name) { \
72 init.name = std::string(v.cStr(), v.size()); \
73 }
74 URL_PATTERN_COMPONENTS(V)
75#undef V
76 KJ_IF_SOME(b, baseURL) {
77 init.base_url = std::string(b.cStr(), b.size());
78 }
79 return init;
80}
81 
82URLPattern::URLPatternInit URLPattern::createURLPatternInit(
83 jsg::Lock& js, const ada::url_pattern_init& other) {
84 // By converting to USVString we are asserting that these values are always valid UTF-8 data,
85 // with no unpaired surrogates, etc. This is true because these values are derived from user
86 // input that was already a USVString, and ada-url will not introduce invalid UTF-8 data when
87 // it processes input.
88 
89 URLPatternInit result{};
90#define V(_, name) \
91 if (auto v = other.name) { \
92 result.name = jsg::USVString(kj::str(kj::ArrayPtr(v->c_str(), v->size()))); \
93 }
94 URL_PATTERN_COMPONENTS(V)
95#undef V
96 
97 if (auto v = other.base_url) {
98 result.baseURL = jsg::USVString(kj::str(kj::ArrayPtr(v->c_str(), v->size())));
99 }
100 return result;
101}
102 
103URLPattern::URLPatternComponentResult URLPattern::createURLPatternComponentResult(
104 jsg::Lock& js, const ada::url_pattern_component_result& other) {
105 auto result = URLPatternComponentResult{
106 .input = jsg::JsRef(js, js.str(kj::ArrayPtr(other.input.c_str(), other.input.size()))),
107 .groups = jsg::JsRef(js, js.obj()),
108 };
109 
110 for (auto& [key, value]: other.groups) {
111 auto k = js.str(kj::ArrayPtr(key.c_str(), key.size()));
112 
113 if (value) {
114 result.groups.getHandle(js).set(js, k, js.str(kj::ArrayPtr(value->c_str(), value->size())));
115 } else {
116 result.groups.getHandle(js).set(js, k, js.undefined());
117 }
118 }
119 return result;
120}
121 
122bool URLPattern::getHasRegExpGroups() const {
123 return inner.has_regexp_groups();
124}
125 
126URLPattern::URLPatternResult URLPattern::createURLPatternResult(
127 jsg::Lock& js, const ada::url_pattern_result& other) {
128 URLPatternResult result{
129#define V(_, name) .name = URLPattern::createURLPatternComponentResult(js, other.name),
130 URL_PATTERN_COMPONENTS(V)
131#undef V
132 };
133 
134 auto vecInputs =
135 kj::heapArray<kj::OneOf<jsg::JsRef<jsg::JsString>, URLPatternInit>>(other.inputs.size());
136 size_t i = 0;
137 for (const auto& input: other.inputs) {
138 if (std::holds_alternative<std::string_view>(input)) {
139 auto raw = std::get<std::string_view>(input);
140 vecInputs[i] = jsg::JsRef(js, js.str(kj::ArrayPtr(raw.data(), raw.size())));
141 } else {
142 KJ_DASSERT(std::holds_alternative<ada::url_pattern_init>(input));
143 auto obj = std::get<ada::url_pattern_init>(input);
144 vecInputs[i] = createURLPatternInit(js, obj);
145 }
146 i++;
147 }
148 result.inputs = mv(vecInputs);
149 return result;
150}
151 
152#define DEFINE_GETTER(uppercase, lowercase) \
153 kj::StringPtr URLPattern::get##uppercase() const { \
154 auto value = inner.get_##lowercase(); \
155 return kj::StringPtr(value.data(), value.size()); \
156 }
157URL_PATTERN_COMPONENTS(DEFINE_GETTER)
158#undef DEFINE_GETTER
159 
160jsg::Ref<URLPattern> URLPattern::constructor(jsg::Lock& js,
161 jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> maybeInput,
162 jsg::Optional<kj::OneOf<jsg::USVString, URLPatternOptions>> maybeBase,
163 jsg::Optional<URLPatternOptions> maybeOptions) {
164 ada::url_pattern_input input;
165 std::optional<std::string_view> base{};
166 std::optional<ada::url_pattern_options> options{};
167 
168 KJ_IF_SOME(mi, maybeInput) {
169 KJ_SWITCH_ONEOF(mi) {
170 KJ_CASE_ONEOF(str, jsg::USVString) {
171 input = std::string_view(str.begin(), str.size());
172 }
173 KJ_CASE_ONEOF(init, URLPatternInit) {
174 input = init.toAdaType();
175 }
176 }
177 } else {
178 input = ada::url_pattern_init{};
179 }
180 
181 KJ_IF_SOME(b, maybeBase) {
182 KJ_SWITCH_ONEOF(b) {
183 KJ_CASE_ONEOF(str, jsg::USVString) {
184 base = std::string_view(str.begin(), str.size());
185 }
186 KJ_CASE_ONEOF(o, URLPatternOptions) {
187 options = o.toAdaType();
188 }
189 }
190 }
191 
192 if (!options.has_value()) {
193 KJ_IF_SOME(o, maybeOptions) {
194 options = o.toAdaType();
195 }
196 }
197 
198 std::string_view* base_opt = base ? &base.value() : nullptr;
199 ada::url_pattern_options* options_opt = options ? &options.value() : nullptr;
200 auto result = ada::parse_url_pattern<URLPatternRegexEngine>(kj::mv(input), base_opt, options_opt);
201 JSG_REQUIRE(result.has_value(), TypeError, "Failed to construct URLPattern"_kj);
202 return js.alloc<URLPattern>(std::move(*result));
203}
204 
205bool URLPattern::test(jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> maybeInput,
206 jsg::Optional<jsg::USVString> maybeBase) {
207 ada::result<bool> result;
208 std::optional<std::string_view> base{};
209 
210 KJ_IF_SOME(b, maybeBase) {
211 base = std::string_view(b.begin(), b.size());
212 }
213 
214 std::string_view* base_ptr = base ? &base.value() : nullptr;
215 
216 KJ_IF_SOME(mi, maybeInput) {
217 KJ_SWITCH_ONEOF(mi) {
218 KJ_CASE_ONEOF(str, jsg::USVString) {
219 result = inner.test(std::string_view(str.begin(), str.size()), base_ptr);
220 }
221 KJ_CASE_ONEOF(pi, URLPattern::URLPatternInit) {
222 result = inner.test(pi.toAdaType(), base_ptr);
223 }
224 }
225 } else {
226 result = inner.test(ada::url_pattern_init{}, base_ptr);
227 }
228 
229 JSG_REQUIRE(result.has_value(), TypeError, "Failed to test URLPattern");
230 
231 return *result;
232}
233 
234kj::Maybe<URLPattern::URLPatternResult> URLPattern::exec(jsg::Lock& js,
235 jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> maybeInput,
236 jsg::Optional<jsg::USVString> maybeBase) {
237 ada::result<std::optional<ada::url_pattern_result>> result;
238 std::optional<std::string_view> base_url{};
239 
240 KJ_IF_SOME(b, maybeBase) {
241 base_url = std::string_view(b.cStr(), b.size());
242 }
243 
244 KJ_IF_SOME(mi, maybeInput) {
245 KJ_SWITCH_ONEOF(mi) {
246 KJ_CASE_ONEOF(str, jsg::USVString) {
247 result = inner.exec(
248 std::string_view(str.begin(), str.size()), base_url ? &base_url.value() : nullptr);
249 }
250 KJ_CASE_ONEOF(pi, URLPattern::URLPatternInit) {
251 result = inner.exec(pi.toAdaType(), base_url ? &base_url.value() : nullptr);
252 }
253 }
254 } else {
255 result = inner.exec(ada::url_pattern_init{}, base_url ? &base_url.value() : nullptr);
256 }
257 
258 // If result does not exist, we should throw.
259 JSG_REQUIRE(result.has_value(), TypeError, "Failed to exec URLPattern"_kj);
260 
261 if (result->has_value()) {
262 return createURLPatternResult(js, **result);
263 }
264 
265 // Return null
266 return kj::none;
267}
268} // namespace workerd::api::urlpattern