Skip to content
File

Blob: src/workerd/api/url-standard.h

cpp278 lines
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#pragma once
6 
7#include <kj/one-of.h>
8#include <workerd/api/blob.h>
9#include <workerd/jsg/jsg.h>
10#include <workerd/jsg/url.h>
11#include <workerd/io/compatibility-date.capnp.h>
12 
13namespace workerd::api {
14// The original URL implementation based on kj::Url is not compliant with the
15// WHATWG URL standard, but we can't get rid of it. This is an alternate
16// implementation that is based on the spec. It can be enabled using a
17// configuration flag. We put it in its own namespace to keep its classes
18// from conflicting with the old implementation.
19namespace url {
20 
21class URL;
22 
23// The URLSearchParams object is a wrapper for application/x-www-form-urlencoded
24// data. It can be used by itself or with URL (every URL object has a searchParams
25// attribute that is kept in sync).
26class URLSearchParams: public jsg::Object {
27 template <typename T>
28 struct IteratorState {
29 jsg::Ref<URLSearchParams> self;
30 T inner;
31 IteratorState(jsg::Ref<URLSearchParams> self, T t) : self(kj::mv(self)), inner(kj::mv(t)) {}
32 void visitForGc(jsg::GcVisitor& visitor) {
33 visitor.visit(self);
34 }
35 };
36public:
37 using StringPair = jsg::Sequence<jsg::USVString>;
38 using StringPairs = jsg::Sequence<StringPair>;
39 
40 using Initializer = kj::OneOf<StringPairs,
41 jsg::Dict<jsg::USVString, jsg::USVString>,
42 jsg::USVString>;
43 
44 // Constructor called by the static constructor method.
45 URLSearchParams(Initializer init);
46 
47 // Constructor called by the URL class when created.
48 URLSearchParams(kj::Maybe<kj::ArrayPtr<const char>> maybeQuery, URL& url);
49 
50 static jsg::Ref<URLSearchParams> constructor(jsg::Lock& js, jsg::Optional<Initializer> init);
51 
52 void append(jsg::USVString name, jsg::USVString value);
53 void delete_(jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value);
54 kj::Maybe<kj::ArrayPtr<const char>> get(jsg::USVString name);
55 kj::Array<kj::ArrayPtr<const char>> getAll(jsg::USVString name);
56 bool has(jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value);
57 void set(jsg::USVString name, jsg::USVString value);
58 void sort();
59 
60 JSG_ITERATOR(EntryIterator, entries,
61 kj::Array<kj::ArrayPtr<const char>>,
62 IteratorState<jsg::UrlSearchParams::EntryIterator>,
63 entryIteratorNext)
64 JSG_ITERATOR(KeyIterator, keys,
65 kj::ArrayPtr<const char>,
66 IteratorState<jsg::UrlSearchParams::KeyIterator>,
67 keyIteratorNext)
68 JSG_ITERATOR(ValueIterator, values,
69 kj::ArrayPtr<const char>,
70 IteratorState<jsg::UrlSearchParams::ValueIterator>,
71 valueIteratorNext)
72 
73 void forEach(jsg::Lock&,
74 jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)>,
75 jsg::Optional<jsg::JsValue>);
76 
77 kj::String toString();
78 
79 uint getSize();
80 
81 JSG_RESOURCE_TYPE(URLSearchParams, CompatibilityFlags::Reader flags) {
82 JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);
83 JSG_METHOD(append);
84 JSG_METHOD_NAMED(delete, delete_);
85 JSG_METHOD(get);
86 JSG_METHOD(getAll);
87 JSG_METHOD(has);
88 JSG_METHOD(set);
89 JSG_METHOD(sort);
90 JSG_METHOD(entries);
91 JSG_METHOD(keys);
92 JSG_METHOD(values);
93 JSG_METHOD(forEach);
94 JSG_METHOD(toString);
95 JSG_ITERABLE(entries);
96 
97 if (!flags.getSpecCompliantUrl()) {
98 // This is a hack. The spec-compliant URLSearchParams type is used in the Body constructor,
99 // see https://github.com/cloudflare/workerd/blob/v1.20241127.0/src/workerd/api/http.h#L255
100 // This means that when the TypeScript generation scripts are visiting root types for
101 // inclusion, we'll always visit the spec-compliant type even if we have the "url-standard"
102 // flag disabled. Rather than updating those usages based on which flags are enabled, we just
103 // delete the spec compliant declaration in an override if "url-standard" is disabled.
104 // We do the same for the non-spec-compliant URLSearchParams
105 // (https://github.com/cloudflare/workerd/blob/v1.20241127.0/src/workerd/api/url.h#L219).
106 JSG_TS_OVERRIDE(type URLSearchParams = never);
107 } else if (flags.getUrlSearchParamsDeleteHasValueArg()) {
108 JSG_TS_OVERRIDE(URLSearchParams {
109 entries(): IterableIterator<[key: string, value: string]>;
110 [Symbol.iterator](): IterableIterator<[key: string, value: string]>;
111 
112 forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void;
113 });
114 } else {
115 JSG_TS_OVERRIDE(URLSearchParams {
116 delete(name: string): void;
117 has(name: string): boolean;
118 
119 entries(): IterableIterator<[key: string, value: string]>;
120 [Symbol.iterator](): IterableIterator<[key: string, value: string]>;
121 
122 forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void;
123 });
124 }
125 // Rename from urlURLSearchParams
126 }
127 
128 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
129 tracker.trackField("inner", inner);
130 tracker.trackField("url", maybeUrl);
131 }
132 
133private:
134 jsg::UrlSearchParams inner;
135 kj::Maybe<URL&> maybeUrl;
136 
137 // Updates the associated URL (if any) with the serialized contents of this URLSearchParam
138 void update();
139 
140 // Updates the contents of this URLSearchParam with the current contents of the associated
141 // URLs search component.
142 void reset();
143 
144 static kj::Maybe<kj::Array<kj::ArrayPtr<const char>>> entryIteratorNext(
145 jsg::Lock& js,
146 IteratorState<jsg::UrlSearchParams::EntryIterator>& state);
147 static kj::Maybe<kj::ArrayPtr<const char>> keyIteratorNext(
148 jsg::Lock& js,
149 IteratorState<jsg::UrlSearchParams::KeyIterator>& state);
150 static kj::Maybe<kj::ArrayPtr<const char>> valueIteratorNext(
151 jsg::Lock& js,
152 IteratorState<jsg::UrlSearchParams::ValueIterator>& state);
153 
154 friend class URL;
155};
156 
157// The humble URL object, in all its spec-compliant glory.
158// The majority of the implementation is covered by jsg::Url.
159class URL: public jsg::Object {
160public:
161 URL(kj::StringPtr url, kj::Maybe<kj::StringPtr> base = kj::none);
162 ~URL() noexcept(false) override;
163 
164 static jsg::Ref<URL> constructor(jsg::Lock& js, jsg::USVString url, jsg::Optional<jsg::USVString> base);
165 
166 static kj::Maybe<jsg::Ref<URL>> parse(jsg::Lock& js, jsg::USVString url, jsg::Optional<jsg::USVString> base) {
167 // Method should not throw if the parse fails
168 return js.tryCatch([&]() -> kj::Maybe<jsg::Ref<URL>> {
169 return constructor(js, kj::mv(url), kj::mv(base));
170 }, [](auto) -> kj::Maybe<jsg::Ref<URL>> { return kj::none; });
171 }
172 
173 kj::ArrayPtr<const char> getHref();
174 void setHref(jsg::Lock& js, jsg::USVString value);
175 
176 kj::Array<const char> getOrigin();
177 
178 kj::ArrayPtr<const char> getProtocol();
179 void setProtocol(jsg::USVString value);
180 
181 kj::ArrayPtr<const char> getUsername();
182 void setUsername(jsg::USVString value);
183 
184 kj::ArrayPtr<const char> getPassword();
185 void setPassword(jsg::USVString value);
186 
187 kj::ArrayPtr<const char> getHost();
188 void setHost(jsg::USVString value);
189 
190 kj::ArrayPtr<const char> getHostname();
191 void setHostname(jsg::USVString value);
192 
193 kj::ArrayPtr<const char> getPort();
194 void setPort(jsg::USVString value);
195 
196 kj::ArrayPtr<const char> getPathname();
197 void setPathname(jsg::USVString value);
198 
199 kj::ArrayPtr<const char> getSearch();
200 void setSearch(jsg::USVString value);
201 
202 kj::ArrayPtr<const char> getHash();
203 void setHash(jsg::USVString value);
204 
205 jsg::Ref<URLSearchParams> getSearchParams(jsg::Lock& js);
206 
207 // Standard utility that returns true if the given input can be
208 // successfully parsed as a URL. This is useful for validating
209 // URL inputs without incurring the additional cost of constructing
210 // and throwing an error. For example:
211 //
212 // const urls = [
213 // 'https://example.org/good',
214 // 'not a url'
215 // ].filter((test) => URL.canParse(test));
216 //
217 static bool canParse(jsg::USVString url, jsg::Optional<jsg::USVString> base = kj::none);
218 static jsg::JsString createObjectURL(jsg::Lock& js, kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>> object);
219 static void revokeObjectURL(jsg::Lock& js, jsg::USVString object_url);
220 
221 JSG_RESOURCE_TYPE(URL) {
222 JSG_READONLY_PROTOTYPE_PROPERTY(origin, getOrigin);
223 JSG_PROTOTYPE_PROPERTY(href, getHref, setHref);
224 JSG_PROTOTYPE_PROPERTY(protocol, getProtocol, setProtocol);
225 JSG_PROTOTYPE_PROPERTY(username, getUsername, setUsername);
226 JSG_PROTOTYPE_PROPERTY(password, getPassword, setPassword);
227 JSG_PROTOTYPE_PROPERTY(host, getHost, setHost);
228 JSG_PROTOTYPE_PROPERTY(hostname, getHostname, setHostname);
229 JSG_PROTOTYPE_PROPERTY(port, getPort, setPort);
230 JSG_PROTOTYPE_PROPERTY(pathname, getPathname, setPathname);
231 JSG_PROTOTYPE_PROPERTY(search, getSearch, setSearch);
232 JSG_PROTOTYPE_PROPERTY(hash, getHash, setHash);
233 JSG_READONLY_PROTOTYPE_PROPERTY(searchParams, getSearchParams);
234 JSG_METHOD_NAMED(toJSON, getHref);
235 JSG_METHOD_NAMED(toString, getHref);
236 JSG_STATIC_METHOD(canParse);
237 JSG_STATIC_METHOD(parse);
238 JSG_STATIC_METHOD(createObjectURL);
239 JSG_STATIC_METHOD(revokeObjectURL);
240 
241 JSG_TS_OVERRIDE(URL {
242 constructor(url: string | URL, base?: string | URL);
243 });
244 // Rename from urlURL, and allow URLs which get coerced to strings in either
245 // constructor parameter
246 }
247 
248 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
249 tracker.trackField("inner", inner);
250 tracker.trackField("searchParams", maybeSearchParams);
251 }
252 
253 operator const jsg::Url&() const { return inner; }
254 operator jsg::Url() { return inner.clone(); }
255 
256private:
257 jsg::Url inner;
258 kj::Maybe<jsg::Ref<URLSearchParams>> maybeSearchParams;
259 
260 void visitForGc(jsg::GcVisitor& visitor);
261 
262 friend class URLSearchParams;
263};
264 
265#define EW_URL_STANDARD_ISOLATE_TYPES \
266 api::url::URL, \
267 api::url::URLSearchParams, \
268 api::url::URLSearchParams::EntryIterator, \
269 api::url::URLSearchParams::EntryIterator::Next, \
270 api::url::URLSearchParams::KeyIterator, \
271 api::url::URLSearchParams::KeyIterator::Next, \
272 api::url::URLSearchParams::ValueIterator, \
273 api::url::URLSearchParams::ValueIterator::Next
274 
275} // namespace url
276} // namespace api
277