Skip to content
File

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

10.2 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 "url-standard.h"
6 
7#include "blob.h"
8#include "util.h"
9 
10#include <workerd/io/features.h>
11 
12#include <unicode/uchar.h>
13#include <unicode/uidna.h>
14#include <unicode/ustring.h>
15#include <unicode/utf8.h>
16 
17#include <kj/array.h>
18 
19#include <algorithm>
20#include <cmath>
21#include <cstring>
22#include <map>
23#include <numeric>
24 
25#if _WIN32
26#include <ws2tcpip.h>
27#undef RELATIVE
28#else
29#include <arpa/inet.h>
30#endif
31 
32namespace workerd::api::url {
33 
34namespace {
35jsg::Url parseImpl(kj::StringPtr url, kj::Maybe<kj::StringPtr> maybeBase) {
36 return JSG_REQUIRE_NONNULL(jsg::Url::tryParse(url, maybeBase), TypeError, "Invalid URL string.");
37}
38} // namespace
39 
40jsg::Ref<URL> URL::constructor(
41 jsg::Lock& js, jsg::USVString url, jsg::Optional<jsg::USVString> base) {
42 return js.alloc<URL>(kj::mv(url), base.map([](jsg::USVString& base) { return base.asPtr(); }));
43}
44 
45URL::URL(kj::StringPtr url, kj::Maybe<kj::StringPtr> base): inner(parseImpl(url, base)) {}
46 
47URL::~URL() noexcept(false) {
48 KJ_IF_SOME(searchParams, maybeSearchParams) {
49 searchParams->maybeUrl = kj::none;
50 }
51}
52 
53bool URL::canParse(jsg::USVString url, jsg::Optional<jsg::USVString> maybeBase) {
54 return jsg::Url::canParse(url, maybeBase.map([](jsg::USVString& str) { return str.asPtr(); }));
55}
56 
57jsg::JsString URL::createObjectURL(
58 jsg::Lock& js, kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>> object) {
59 // TODO(soon): Implement this
60 JSG_FAIL_REQUIRE(Error, "URL.createObjectURL() is not implemented"_kj);
61}
62 
63void URL::revokeObjectURL(jsg::Lock& js, jsg::USVString object_url) {
64 // TODO(soon): Implement this
65 JSG_FAIL_REQUIRE(Error, "URL.revokeObjectURL() is not implemented"_kj);
66}
67 
68jsg::Ref<URLSearchParams> URL::getSearchParams(jsg::Lock& js) {
69 KJ_IF_SOME(searchParams, maybeSearchParams) {
70 return searchParams.addRef();
71 }
72 return maybeSearchParams.emplace(js.alloc<URLSearchParams>(inner.getSearch(), *this)).addRef();
73}
74 
75kj::Array<const char> URL::getOrigin() {
76 return inner.getOrigin();
77}
78 
79kj::ArrayPtr<const char> URL::getHref() {
80 return inner.getHref();
81}
82 
83void URL::setHref(jsg::Lock& js, jsg::USVString value) {
84 // Href setter is the only place in URL parser that is allowed to throw except the constructor.
85 if (!inner.setHref(value)) {
86 auto context = jsg::TypeErrorContext::setterArgument(typeid(URL), "href");
87 jsg::throwTypeError(js.v8Isolate, context, "valid URL string");
88 }
89 KJ_IF_SOME(searchParams, maybeSearchParams) {
90 searchParams->reset();
91 }
92}
93 
94kj::ArrayPtr<const char> URL::getProtocol() {
95 return inner.getProtocol();
96}
97 
98void URL::setProtocol(jsg::USVString value) {
99 inner.setProtocol(value);
100}
101 
102kj::ArrayPtr<const char> URL::getUsername() {
103 return inner.getUsername();
104}
105 
106void URL::setUsername(jsg::USVString value) {
107 inner.setUsername(value);
108}
109 
110kj::ArrayPtr<const char> URL::getPassword() {
111 return inner.getPassword();
112}
113 
114void URL::setPassword(jsg::USVString value) {
115 inner.setPassword(value);
116}
117 
118kj::ArrayPtr<const char> URL::getHost() {
119 return inner.getHost();
120}
121 
122void URL::setHost(jsg::USVString value) {
123 inner.setHost(value);
124}
125 
126kj::ArrayPtr<const char> URL::getHostname() {
127 return inner.getHostname();
128}
129 
130void URL::setHostname(jsg::USVString value) {
131 inner.setHostname(value);
132}
133 
134kj::ArrayPtr<const char> URL::getPort() {
135 return inner.getPort();
136}
137 
138void URL::setPort(jsg::USVString value) {
139 inner.setPort(kj::Maybe(value.asPtr()));
140}
141 
142kj::ArrayPtr<const char> URL::getPathname() {
143 return inner.getPathname();
144}
145 
146void URL::setPathname(jsg::USVString value) {
147 inner.setPathname(value);
148}
149 
150kj::ArrayPtr<const char> URL::getSearch() {
151 return inner.getSearch();
152}
153 
154void URL::setSearch(jsg::USVString value) {
155 inner.setSearch(kj::Maybe(value.asPtr()));
156 KJ_IF_SOME(searchParams, maybeSearchParams) {
157 searchParams->reset();
158 }
159}
160 
161kj::ArrayPtr<const char> URL::getHash() {
162 return inner.getHash();
163}
164 
165void URL::setHash(jsg::USVString hash) {
166 inner.setHash(kj::Maybe(hash.asPtr()));
167}
168 
169void URL::visitForGc(jsg::GcVisitor& visitor) {
170 visitor.visit(maybeSearchParams);
171}
172 
173// ======================================================================================
174namespace {
175jsg::UrlSearchParams initSearchParams(const URLSearchParams::Initializer& init) {
176 KJ_SWITCH_ONEOF(init) {
177 KJ_CASE_ONEOF(pairs, URLSearchParams::StringPairs) {
178 jsg::UrlSearchParams params;
179 for (auto& item: pairs) {
180 JSG_REQUIRE(item.size() == 2, TypeError, "Invalid URL search params sequence.");
181 params.append(item[0], item[1]);
182 }
183 return kj::mv(params);
184 }
185 KJ_CASE_ONEOF(dict, jsg::Dict<jsg::USVString, jsg::USVString>) {
186 kj::HashMap<kj::StringPtr, kj::StringPtr> paramsMap;
187 for (auto& item: dict.fields) {
188 paramsMap.upsert(item.name, item.value);
189 }
190 
191 jsg::UrlSearchParams params;
192 for (auto& item: paramsMap) {
193 params.append(item.key, item.value);
194 }
195 return kj::mv(params);
196 }
197 KJ_CASE_ONEOF(str, jsg::USVString) {
198 return JSG_REQUIRE_NONNULL(
199 jsg::UrlSearchParams::tryParse(str), TypeError, "Invalid URL search params string.");
200 }
201 }
202 KJ_UNREACHABLE;
203}
204 
205jsg::UrlSearchParams initFromSearch(kj::Maybe<kj::ArrayPtr<const char>>& maybeQuery) {
206 KJ_IF_SOME(query, maybeQuery) {
207 return JSG_REQUIRE_NONNULL(
208 jsg::UrlSearchParams::tryParse(query), TypeError, "Invalid URL search params string.");
209 }
210 return jsg::UrlSearchParams();
211}
212} // namespace
213 
214jsg::Ref<URLSearchParams> URLSearchParams::constructor(
215 jsg::Lock& js, jsg::Optional<Initializer> init) {
216 return js.alloc<URLSearchParams>(kj::mv(init).orDefault(jsg::USVString()));
217}
218 
219URLSearchParams::URLSearchParams(Initializer initializer): inner(initSearchParams(initializer)) {}
220 
221URLSearchParams::URLSearchParams(kj::Maybe<kj::ArrayPtr<const char>> maybeQuery, URL& url)
222 : inner(initFromSearch(maybeQuery)),
223 maybeUrl(url) {}
224 
225uint URLSearchParams::getSize() {
226 return inner.size();
227}
228 
229void URLSearchParams::update() {
230 KJ_IF_SOME(url, maybeUrl) {
231 auto serialized = toString();
232 url.inner.setSearch(kj::Maybe(serialized.asPtr()));
233 }
234}
235 
236void URLSearchParams::reset() {
237 KJ_IF_SOME(url, maybeUrl) {
238 auto search = kj::Maybe(url.inner.getSearch());
239 inner.reset(search);
240 }
241}
242 
243void URLSearchParams::append(jsg::USVString name, jsg::USVString value) {
244 inner.append(name, value);
245 update();
246}
247 
248void URLSearchParams::delete_(
249 jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value) {
250 KJ_ON_SCOPE_SUCCESS(update());
251 if (FeatureFlags::get(js).getUrlSearchParamsDeleteHasValueArg()) {
252 // The whatwg url spec was updated to add a second optional argument to delete()
253 // and has(). While it was determined that it likely didn't break browser users,
254 // the change could break at least a couple existing deployed workers so we have
255 // to gate support behind a compat flag.
256 KJ_IF_SOME(v, value) {
257 inner.delete_(name, kj::Maybe(v.asPtr()));
258 return;
259 }
260 }
261 inner.delete_(name);
262}
263 
264kj::Maybe<kj::ArrayPtr<const char>> URLSearchParams::get(jsg::USVString name) {
265 return inner.get(name);
266}
267 
268kj::Array<kj::ArrayPtr<const char>> URLSearchParams::getAll(jsg::USVString name) {
269 return inner.getAll(name);
270}
271 
272bool URLSearchParams::has(jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value) {
273 if (FeatureFlags::get(js).getUrlSearchParamsDeleteHasValueArg()) {
274 // The whatwg url spec was updated to add a second optional argument to delete()
275 // and has(). While it was determined that it likely didn't break browser users,
276 // the change could break at least a couple existing deployed workers so we have
277 // to gate support behind a compat flag.
278 KJ_IF_SOME(v, value) {
279 return inner.has(name, kj::Maybe(v.asPtr()));
280 }
281 }
282 return inner.has(name);
283}
284 
285void URLSearchParams::set(jsg::USVString name, jsg::USVString value) {
286 inner.set(name, value);
287 update();
288}
289 
290void URLSearchParams::sort() {
291 inner.sort();
292 update();
293}
294 
295jsg::Ref<URLSearchParams::EntryIterator> URLSearchParams::entries(jsg::Lock& js) {
296 return js.alloc<URLSearchParams::EntryIterator>(
297 IteratorState<jsg::UrlSearchParams::EntryIterator>(JSG_THIS, inner.getEntries()));
298}
299 
300jsg::Ref<URLSearchParams::KeyIterator> URLSearchParams::keys(jsg::Lock& js) {
301 return js.alloc<URLSearchParams::KeyIterator>(
302 IteratorState<jsg::UrlSearchParams::KeyIterator>(JSG_THIS, inner.getKeys()));
303}
304 
305jsg::Ref<URLSearchParams::ValueIterator> URLSearchParams::values(jsg::Lock& js) {
306 return alloc<URLSearchParams::ValueIterator>(
307 IteratorState<jsg::UrlSearchParams::ValueIterator>(JSG_THIS, inner.getValues()));
308}
309 
310kj::Maybe<kj::Array<kj::ArrayPtr<const char>>> URLSearchParams::entryIteratorNext(
311 jsg::Lock& js, URLSearchParams::IteratorState<jsg::UrlSearchParams::EntryIterator>& state) {
312 return state.inner.next().map([](const jsg::UrlSearchParams::EntryIterator::Entry& entry) {
313 return kj::arr(entry.key, entry.value);
314 });
315}
316 
317kj::Maybe<kj::ArrayPtr<const char>> URLSearchParams::keyIteratorNext(
318 jsg::Lock& js, URLSearchParams::IteratorState<jsg::UrlSearchParams::KeyIterator>& state) {
319 return state.inner.next();
320}
321 
322kj::Maybe<kj::ArrayPtr<const char>> URLSearchParams::valueIteratorNext(
323 jsg::Lock& js, URLSearchParams::IteratorState<jsg::UrlSearchParams::ValueIterator>& state) {
324 return state.inner.next();
325}
326 
327void URLSearchParams::forEach(jsg::Lock& js,
328 jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)> callback,
329 jsg::Optional<jsg::JsValue> thisArg) {
330 auto receiver = js.undefined();
331 KJ_IF_SOME(arg, thisArg) {
332 if (!arg.isNullOrUndefined()) {
333 receiver = arg;
334 }
335 }
336 callback.setReceiver(js.v8Ref<v8::Value>(receiver));
337 
338 auto entries = inner.getEntries();
339 while (entries.hasNext()) {
340 auto next = KJ_ASSERT_NONNULL(entries.next());
341 kj::String value(
342 const_cast<char*>(next.value.begin()), next.value.size(), kj::NullArrayDisposer::instance);
343 kj::String key(
344 const_cast<char*>(next.key.begin()), next.key.size(), kj::NullArrayDisposer::instance);
345 callback(js, value, key, JSG_THIS);
346 }
347}
348 
349kj::String URLSearchParams::toString() {
350 return kj::str(inner);
351}
352 
353} // namespace workerd::api::url