Skip to content
File

Blob: src/workerd/api/url.h

cpp283 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 <workerd/jsg/jsg.h>
8#include <workerd/io/compatibility-date.capnp.h>
9#include <kj/refcount.h>
10#include <kj/compat/url.h>
11 
12namespace workerd::api {
13 
14class URLSearchParams;
15 
16// Implements the URL interface as prescribed by: https://url.spec.whatwg.org/#api
17// This is the legacy, non-standard implementation.
18class URL: public jsg::Object {
19public:
20 static jsg::Ref<URL> constructor(jsg::Lock& js, kj::String url, jsg::Optional<kj::String> base);
21 
22 // Href is the only setter that throws. All others ignore errors, leaving their values
23 // unchanged.
24 kj::String getHref();
25 
26 // Href is the only setter that throws. All others ignore errors, leaving their values
27 // unchanged.
28 void setHref(jsg::Lock& js, kj::String value);
29 
30 kj::String getOrigin();
31 
32 kj::String getProtocol();
33 void setProtocol(kj::String value);
34 
35 kj::String getUsername();
36 void setUsername(kj::String value);
37 
38 kj::String getPassword();
39 void setPassword(kj::String value);
40 
41 kj::String getHost();
42 void setHost(kj::String value);
43 
44 kj::String getHostname();
45 void setHostname(kj::String value);
46 
47 kj::String getPort();
48 void setPort(kj::String value);
49 
50 kj::String getPathname();
51 void setPathname(kj::String value);
52 
53 kj::String getSearch();
54 void setSearch(kj::String value);
55 
56 jsg::Ref<URLSearchParams> getSearchParams(jsg::Lock& js);
57 
58 kj::String getHash();
59 void setHash(kj::String value);
60 
61 kj::String toString();
62 kj::String toJSON();
63 
64 JSG_RESOURCE_TYPE(URL, CompatibilityFlags::Reader flags) {
65 // Previously, we were setting all properties as instance properties,
66 // which broke the ability to subclass the URL object. With the
67 // compatibility flag set, we instead attach the properties to the
68 // prototype.
69 if (flags.getJsgPropertyOnPrototypeTemplate()) {
70 JSG_PROTOTYPE_PROPERTY(href, getHref, setHref);
71 JSG_READONLY_PROTOTYPE_PROPERTY(origin, getOrigin);
72 JSG_PROTOTYPE_PROPERTY(protocol, getProtocol, setProtocol);
73 JSG_PROTOTYPE_PROPERTY(username, getUsername, setUsername);
74 JSG_PROTOTYPE_PROPERTY(password, getPassword, setPassword);
75 JSG_PROTOTYPE_PROPERTY(host, getHost, setHost);
76 JSG_PROTOTYPE_PROPERTY(hostname, getHostname, setHostname);
77 JSG_PROTOTYPE_PROPERTY(port, getPort, setPort);
78 JSG_PROTOTYPE_PROPERTY(pathname, getPathname, setPathname);
79 JSG_PROTOTYPE_PROPERTY(search, getSearch, setSearch);
80 JSG_READONLY_PROTOTYPE_PROPERTY(searchParams, getSearchParams);
81 JSG_PROTOTYPE_PROPERTY(hash, getHash, setHash);
82 } else {
83 JSG_INSTANCE_PROPERTY(href, getHref, setHref);
84 JSG_READONLY_INSTANCE_PROPERTY(origin, getOrigin);
85 JSG_INSTANCE_PROPERTY(protocol, getProtocol, setProtocol);
86 JSG_INSTANCE_PROPERTY(username, getUsername, setUsername);
87 JSG_INSTANCE_PROPERTY(password, getPassword, setPassword);
88 JSG_INSTANCE_PROPERTY(host, getHost, setHost);
89 JSG_INSTANCE_PROPERTY(hostname, getHostname, setHostname);
90 JSG_INSTANCE_PROPERTY(port, getPort, setPort);
91 JSG_INSTANCE_PROPERTY(pathname, getPathname, setPathname);
92 JSG_INSTANCE_PROPERTY(search, getSearch, setSearch);
93 JSG_READONLY_INSTANCE_PROPERTY(searchParams, getSearchParams);
94 JSG_INSTANCE_PROPERTY(hash, getHash, setHash);
95 }
96 
97 JSG_METHOD(toString);
98 JSG_METHOD(toJSON);
99 
100 JSG_TS_OVERRIDE({
101 constructor(url: string | URL, base?: string | URL);
102 });
103 // Allow URLs which get coerced to strings in either constructor parameter
104 }
105 
106 // Treat as private -- needs to be public for js.alloc<T>()...
107 explicit URL(kj::Url&& u);
108 
109 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
110 size_t size = 0;
111 size += url->scheme.size();
112 KJ_IF_SOME(ui, url->userInfo) {
113 size += ui.username.size();
114 KJ_IF_SOME(pwd, ui.password) {
115 size += pwd.size();
116 }
117 }
118 size += url->host.size();
119 for (const auto& p: url->path) {
120 size += p.size();
121 }
122 for (const auto& param : url->query) {
123 size += param.name.size();
124 size += param.value.size();
125 }
126 KJ_IF_SOME(frag, url->fragment) {
127 size += frag.size();
128 }
129 tracker.trackFieldWithSize("inner", size);
130 tracker.trackField("searchParams", searchParams);
131 }
132 
133private:
134 friend class URLSearchParams;
135 
136 struct RefcountedUrl: kj::Refcounted, kj::Url {
137 template <typename... Args>
138 RefcountedUrl(Args&&... args): kj::Url(kj::fwd<Args>(args)...) {}
139 };
140 
141 kj::Own<RefcountedUrl> url;
142 kj::Maybe<jsg::Ref<URLSearchParams>> searchParams;
143 
144 void visitForGc(jsg::GcVisitor& visitor) {
145 visitor.visit(searchParams);
146 }
147};
148 
149class URLSearchParams: public jsg::Object {
150 // TODO(cleanup): Combine implementation with FormData?
151private:
152 struct IteratorState {
153 jsg::Ref<URLSearchParams> parent;
154 uint index = 0;
155 
156 void visitForGc(jsg::GcVisitor& visitor) {
157 visitor.visit(parent);
158 }
159 };
160 
161public:
162 explicit URLSearchParams(kj::Own<URL::RefcountedUrl> url);
163 
164 using Initializer = kj::OneOf<jsg::Ref<URLSearchParams>, kj::String, jsg::Dict<kj::String>,
165 kj::Array<kj::Array<kj::String>>>;
166 
167 static jsg::Ref<URLSearchParams> constructor(jsg::Lock& js, jsg::Optional<Initializer> init);
168 
169 void append(kj::String name, kj::String value);
170 void delete_(kj::String name);
171 kj::Maybe<kj::String> get(kj::String name);
172 kj::Array<kj::String> getAll(kj::String name);
173 bool has(kj::String name);
174 void set(kj::String name, kj::String value);
175 
176 void sort();
177 
178 JSG_ITERATOR(EntryIterator, entries,
179 kj::Array<kj::String>,
180 IteratorState,
181 entryIteratorNext)
182 JSG_ITERATOR(KeyIterator, keys,
183 kj::StringPtr,
184 IteratorState,
185 keyIteratorNext)
186 JSG_ITERATOR(ValueIterator, values,
187 kj::StringPtr,
188 IteratorState,
189 valueIteratorNext)
190 
191 void forEach(jsg::Lock&,
192 jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)>,
193 jsg::Optional<jsg::Value>);
194 
195 kj::String toString();
196 
197 inline uint getSize() { return url->query.size(); }
198 
199 JSG_RESOURCE_TYPE(URLSearchParams, CompatibilityFlags::Reader flags) {
200 JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);
201 JSG_METHOD(append);
202 JSG_METHOD_NAMED(delete, delete_);
203 JSG_METHOD(get);
204 JSG_METHOD(getAll);
205 JSG_METHOD(has);
206 JSG_METHOD(set);
207 
208 JSG_METHOD(sort);
209 
210 JSG_METHOD(entries);
211 JSG_METHOD(keys);
212 JSG_METHOD(values);
213 JSG_METHOD(forEach);
214 
215 JSG_ITERABLE(entries);
216 
217 JSG_METHOD(toString);
218 
219 if (flags.getSpecCompliantUrl()) {
220 // This is a hack. The non-spec-compliant URLSearchParams type is used in
221 // the Response and Request constructors. This means that when the
222 // TypeScript generation scripts are visiting root types for inclusion,
223 // we'll always visit the non-spec-compliant type even if we have the
224 // "url-standard" flag enabled. Rather than updating those usages based
225 // on which flags are enabled, we just delete the non-spec complaint
226 // declaration in an override if "url-standard" is enabled.
227 JSG_TS_OVERRIDE(type URLSearchParams = never);
228 } else {
229 JSG_TS_OVERRIDE({
230 constructor(init?: URLSearchParams | string | Record<string, string> | [key: string, value: string][]);
231 
232 entries(): IterableIterator<[key: string, value: string]>;
233 [Symbol.iterator](): IterableIterator<[key: string, value: string]>;
234 
235 forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void;
236 });
237 }
238 }
239 
240 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
241 tracker.trackFieldWithSize("url", url->toString().size());
242 }
243 
244private:
245 kj::Own<URL::RefcountedUrl> url;
246 
247 static kj::Maybe<kj::Array<kj::String>> entryIteratorNext(jsg::Lock& js, IteratorState& state) {
248 if (state.index >= state.parent->url->query.size()) {
249 return kj::none;
250 }
251 auto& [key, value] = state.parent->url->query[state.index++];
252 return kj::arr(kj::str(key), kj::str(value));
253 }
254 
255 static kj::Maybe<kj::StringPtr> keyIteratorNext(jsg::Lock& js, IteratorState& state) {
256 if (state.index >= state.parent->url->query.size()) {
257 return kj::none;
258 }
259 auto& [key, value] = state.parent->url->query[state.index++];
260 return key.asPtr();
261 }
262 
263 static kj::Maybe<kj::StringPtr> valueIteratorNext(jsg::Lock& js, IteratorState& state) {
264 if (state.index >= state.parent->url->query.size()) {
265 return kj::none;
266 }
267 auto& [key, value] = state.parent->url->query[state.index++];
268 return value.asPtr();
269 }
270};
271 
272#define EW_URL_ISOLATE_TYPES \
273 api::URL, \
274 api::URLSearchParams, \
275 api::URLSearchParams::EntryIterator, \
276 api::URLSearchParams::EntryIterator::Next, \
277 api::URLSearchParams::KeyIterator, \
278 api::URLSearchParams::KeyIterator::Next, \
279 api::URLSearchParams::ValueIterator, \
280 api::URLSearchParams::ValueIterator::Next
281 
282} // namespace workerd::api