File
Blob: src/workerd/api/urlpattern-standard.c++
| 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 | |
| 9 | namespace workerd::api::urlpattern { |
| 10 | std::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 | |
| 30 | bool 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 | |
| 39 | std::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 | |
| 64 | ada::url_pattern_options URLPattern::URLPatternOptions::toAdaType() const { |
| 65 | return {.ignore_case = ignoreCase.orDefault(false)}; |
| 66 | } |
| 67 | |
| 68 | ada::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 | |
| 82 | URLPattern::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 | |
| 103 | URLPattern::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 | |
| 122 | bool URLPattern::getHasRegExpGroups() const { |
| 123 | return inner.has_regexp_groups(); |
| 124 | } |
| 125 | |
| 126 | URLPattern::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 | } |
| 157 | URL_PATTERN_COMPONENTS(DEFINE_GETTER) |
| 158 | #undef DEFINE_GETTER |
| 159 | |
| 160 | jsg::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 | |
| 205 | bool 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 | |
| 234 | kj::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 |