Skip to content
File

Blob: src/workerd/jsg/url.h

cpp400 lines
1#pragma once
2#include <workerd/jsg/memory.h>
3 
4#include <kj/common.h>
5#include <kj/one-of.h>
6#include <kj/string.h>
7 
8namespace workerd::jsg {
9 
10// A WHATWG-compliant URL implementation provided by ada-url.
11class Url final {
12 public:
13 // Keep in sync with ada::scheme:type
14 enum class SchemeType {
15 HTTP = 0,
16 NOT_SPECIAL = 1,
17 HTTPS = 2,
18 WS = 3,
19 FTP = 4,
20 WSS = 5,
21 FILE = 6
22 };
23 
24 // Keep in sync with ada::url_host_type
25 enum class HostType {
26 DEFAULT = 0,
27 IPV4 = 1,
28 IPV6 = 2,
29 };
30 
31 Url(decltype(nullptr)) {}
32 Url(Url&& other) = default;
33 KJ_DISALLOW_COPY(Url);
34 
35 Url& operator=(Url&& other) = default;
36 bool operator==(const Url& other) const KJ_WARN_UNUSED_RESULT;
37 
38 enum class EquivalenceOption {
39 DEFAULT = 0,
40 // When set, the fragment/hash portion of the URL will be ignored when comparing or
41 // cloning URLs.
42 IGNORE_FRAGMENTS = 1 << 0,
43 // When set, the search portion of the URL will be ignored when comparing or cloning URLs.
44 IGNORE_SEARCH = 1 << 1,
45 // When set, the pathname portion of the URL will be normalized by percent-decoding
46 // then re-encoding the pathname. This is useful when comparing URLs that may have
47 // different, but equivalent percent-encoded paths. e.g. %66oo and foo are equivalent.
48 NORMALIZE_PATH = 1 << 2,
49 };
50 
51 bool equal(const Url& other,
52 EquivalenceOption option = EquivalenceOption::DEFAULT) const KJ_WARN_UNUSED_RESULT;
53 
54 // Returns true if the given input can be successfully parsed as a URL. This is generally
55 // more performant than using tryParse and checking for a kj::none result if all you want
56 // to do is verify that the input is parseable. If you actually want to parse and use the
57 // result, use tryParse instead.
58 static bool canParse(kj::ArrayPtr<const char> input,
59 kj::Maybe<kj::ArrayPtr<const char>> base = kj::none) KJ_WARN_UNUSED_RESULT;
60 static bool canParse(
61 kj::StringPtr input, kj::Maybe<kj::StringPtr> base = kj::none) KJ_WARN_UNUSED_RESULT;
62 
63 static kj::Maybe<Url> tryParse(kj::ArrayPtr<const char> input,
64 kj::Maybe<kj::ArrayPtr<const char>> base = kj::none) KJ_WARN_UNUSED_RESULT;
65 static kj::Maybe<Url> tryParse(
66 kj::StringPtr input, kj::Maybe<kj::StringPtr> base = kj::none) KJ_WARN_UNUSED_RESULT;
67 
68 kj::Array<const char> getOrigin() const KJ_WARN_UNUSED_RESULT;
69 kj::ArrayPtr<const char> getProtocol() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
70 kj::ArrayPtr<const char> getHref() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
71 kj::ArrayPtr<const char> getPathname() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
72 kj::ArrayPtr<const char> getUsername() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
73 kj::ArrayPtr<const char> getPassword() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
74 kj::ArrayPtr<const char> getPort() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
75 kj::ArrayPtr<const char> getHash() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
76 kj::ArrayPtr<const char> getHost() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
77 kj::ArrayPtr<const char> getHostname() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
78 kj::ArrayPtr<const char> getSearch() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
79 
80 bool setHref(kj::ArrayPtr<const char> value);
81 bool setHost(kj::ArrayPtr<const char> value);
82 bool setHostname(kj::ArrayPtr<const char> value);
83 bool setProtocol(kj::ArrayPtr<const char> value);
84 bool setUsername(kj::ArrayPtr<const char> value);
85 bool setPassword(kj::ArrayPtr<const char> value);
86 bool setPort(kj::Maybe<kj::ArrayPtr<const char>> value);
87 bool setPathname(kj::ArrayPtr<const char> value);
88 void setSearch(kj::Maybe<kj::ArrayPtr<const char>> value);
89 void setHash(kj::Maybe<kj::ArrayPtr<const char>> value);
90 
91 kj::uint hashCode() const;
92 
93 kj::Maybe<Url> resolve(kj::ArrayPtr<const char> input) KJ_WARN_UNUSED_RESULT;
94 
95 // Copies this Url. If the option is set of EquivalenceOption::IGNORE_FRAGMENTS, the
96 // copied Url will clear any fragment/hash that exists.
97 Url clone(EquivalenceOption option = EquivalenceOption::DEFAULT) const KJ_WARN_UNUSED_RESULT;
98 
99 // Resolve the input relative to this URL
100 kj::Maybe<Url> tryResolve(kj::ArrayPtr<const char> input) const KJ_WARN_UNUSED_RESULT;
101 
102 enum class RelativeOption {
103 DEFAULT,
104 // If the URL ends with a trailing slash, remove it before determining the basename.
105 STRIP_TAILING_SLASHES,
106 };
107 struct Relative;
108 // Given this URL, returns a struct that is a basename and a base Url pair
109 // such that base.tryResolve(basename) is equivalent to this URL. Query
110 // parameters and fragments are not preserved.
111 Relative getRelative(RelativeOption option = RelativeOption::DEFAULT) const;
112 
113 // Returns the parent URL of this URL, which is the URL with the last path component
114 // removed and the trailing slash removed if it exists. For instance, if the URL is
115 // "https://example.com/foo/bar/baz", the parent URL will be "https://example.com/foo/bar".
116 // If the URL has no parent (e.g. "https://example.com/") kj::none is returned.
117 kj::Maybe<jsg::Url> getParent() const KJ_WARN_UNUSED_RESULT;
118 
119 HostType getHostType() const;
120 SchemeType getSchemeType() const;
121 
122 // Convert an ASCII hostname to Unicode.
123 static kj::Array<const char> idnToUnicode(kj::ArrayPtr<const char> value) KJ_WARN_UNUSED_RESULT;
124 
125 // Convert a Unicode hostname to ASCII.
126 static kj::Array<const char> idnToAscii(kj::ArrayPtr<const char> value) KJ_WARN_UNUSED_RESULT;
127 
128 static bool isSpecialScheme(kj::StringPtr protocol);
129 static bool isSpecialSchemeDefaultPort(kj::StringPtr protocol, kj::StringPtr port);
130 
131 JSG_MEMORY_INFO(Url) {
132 tracker.trackFieldWithSize("inner",
133 getProtocol().size() + getUsername().size() + getPassword().size() + getHost().size() +
134 getPathname().size() + getHash().size() + getSearch().size());
135 }
136 
137 static kj::Array<kj::byte> percentDecode(kj::ArrayPtr<const kj::byte> input);
138 
139 private:
140 Url(kj::Own<void> inner);
141 kj::Own<void> inner;
142};
143 
144struct Url::Relative {
145 Url base;
146 kj::String name;
147};
148 
149constexpr Url::EquivalenceOption operator|(Url::EquivalenceOption a, Url::EquivalenceOption b) {
150 return static_cast<Url::EquivalenceOption>(static_cast<int>(a) | static_cast<int>(b));
151}
152constexpr Url::EquivalenceOption operator&(Url::EquivalenceOption a, Url::EquivalenceOption b) {
153 return static_cast<Url::EquivalenceOption>(static_cast<int>(a) & static_cast<int>(b));
154}
155 
156class UrlSearchParams final {
157 public:
158 class KeyIterator final {
159 public:
160 bool hasNext() const;
161 kj::Maybe<kj::ArrayPtr<const char>> next() const;
162 
163 private:
164 KeyIterator(kj::Own<void> inner);
165 kj::Own<void> inner;
166 friend class UrlSearchParams;
167 };
168 class ValueIterator final {
169 public:
170 bool hasNext() const;
171 kj::Maybe<kj::ArrayPtr<const char>> next() const;
172 
173 private:
174 ValueIterator(kj::Own<void> inner);
175 kj::Own<void> inner;
176 friend class UrlSearchParams;
177 };
178 class EntryIterator final {
179 public:
180 struct Entry {
181 kj::ArrayPtr<const char> key;
182 kj::ArrayPtr<const char> value;
183 };
184 bool hasNext() const;
185 kj::Maybe<Entry> next() const;
186 
187 private:
188 EntryIterator(kj::Own<void> inner);
189 kj::Own<void> inner;
190 friend class UrlSearchParams;
191 };
192 
193 UrlSearchParams();
194 UrlSearchParams(UrlSearchParams&& other) = default;
195 KJ_DISALLOW_COPY(UrlSearchParams);
196 
197 UrlSearchParams& operator=(UrlSearchParams&& other) = default;
198 bool operator==(const UrlSearchParams& other) const KJ_WARN_UNUSED_RESULT;
199 
200 static kj::Maybe<UrlSearchParams> tryParse(kj::ArrayPtr<const char> input) KJ_WARN_UNUSED_RESULT;
201 
202 size_t size() const;
203 void append(kj::ArrayPtr<const char> key, kj::ArrayPtr<const char> value);
204 void set(kj::ArrayPtr<const char> key, kj::ArrayPtr<const char> value);
205 void delete_(
206 kj::ArrayPtr<const char> key, kj::Maybe<kj::ArrayPtr<const char>> maybeValue = kj::none);
207 bool has(kj::ArrayPtr<const char> key,
208 kj::Maybe<kj::ArrayPtr<const char>> maybeValue = kj::none) const KJ_WARN_UNUSED_RESULT;
209 kj::Maybe<kj::ArrayPtr<const char>> get(
210 kj::ArrayPtr<const char> key) const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
211 kj::Array<kj::ArrayPtr<const char>> getAll(
212 kj::ArrayPtr<const char> key) const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
213 void sort();
214 KeyIterator getKeys() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
215 ValueIterator getValues() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
216 EntryIterator getEntries() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;
217 
218 kj::Array<const char> toStr() const KJ_WARN_UNUSED_RESULT;
219 
220 JSG_MEMORY_INFO(Url) {
221 tracker.trackField("inner", toStr());
222 }
223 
224 void reset(kj::Maybe<kj::ArrayPtr<const char>> input);
225 
226 private:
227 UrlSearchParams(kj::Own<void> inner);
228 kj::Own<void> inner;
229};
230 
231inline kj::String KJ_STRINGIFY(const Url& url) {
232 return kj::str(url.getHref());
233}
234 
235inline kj::String KJ_STRINGIFY(const UrlSearchParams& searchParams) {
236 return kj::str(searchParams.toStr());
237}
238 
239// ======================================================================================
240 
241// Encapsulates a parsed URLPattern.
242// @see https://wicg.github.io/urlpattern
243class UrlPattern final {
244 public:
245 // If the value is T, the operation is successful.
246 // If the value is kj::String, that's an Error message.
247 template <typename T>
248 using Result = kj::OneOf<T, kj::String>;
249 
250 // An individual, compiled component of a URLPattern.
251 class Component final {
252 public:
253 Component(kj::String pattern, kj::String regex, kj::Array<kj::String> names);
254 
255 Component(Component&&) = default;
256 Component& operator=(Component&&) = default;
257 KJ_DISALLOW_COPY(Component);
258 
259 inline kj::StringPtr getPattern() const KJ_LIFETIMEBOUND {
260 return pattern;
261 }
262 inline kj::StringPtr getRegex() const KJ_LIFETIMEBOUND {
263 return regex;
264 }
265 inline kj::ArrayPtr<const kj::String> getNames() const KJ_LIFETIMEBOUND {
266 return names.asPtr();
267 }
268 
269 JSG_MEMORY_INFO(Component) {
270 tracker.trackField("pattern", pattern);
271 tracker.trackField("regex", regex);
272 for (const auto& name: names) {
273 tracker.trackField("name", name);
274 }
275 }
276 
277 private:
278 // The normalized pattern for this component.
279 kj::String pattern = nullptr;
280 
281 // The generated JavaScript regular expression for this component.
282 kj::String regex = nullptr;
283 
284 // The list of sub-component names extracted for this component.
285 kj::Array<kj::String> names = nullptr;
286 };
287 
288 // A structure providing matching patterns for individual components of a URL.
289 struct Init {
290 kj::Maybe<kj::String> protocol;
291 kj::Maybe<kj::String> username;
292 kj::Maybe<kj::String> password;
293 kj::Maybe<kj::String> hostname;
294 kj::Maybe<kj::String> port;
295 kj::Maybe<kj::String> pathname;
296 kj::Maybe<kj::String> search;
297 kj::Maybe<kj::String> hash;
298 kj::Maybe<kj::String> baseUrl;
299 };
300 
301 struct ProcessInitOptions {
302 enum class Mode {
303 PATTERN,
304 URL,
305 };
306 Mode mode = Mode::PATTERN;
307 kj::Maybe<kj::StringPtr> protocol = kj::none;
308 kj::Maybe<kj::StringPtr> username = kj::none;
309 kj::Maybe<kj::StringPtr> password = kj::none;
310 kj::Maybe<kj::StringPtr> hostname = kj::none;
311 kj::Maybe<kj::StringPtr> port = kj::none;
312 kj::Maybe<kj::StringPtr> pathname = kj::none;
313 kj::Maybe<kj::StringPtr> search = kj::none;
314 kj::Maybe<kj::StringPtr> hash = kj::none;
315 };
316 
317 // Processes the given init according to the specified mode and options.
318 // If a kj::String is returned, then processing failed and the string
319 // is the description to include in the error message (if any).
320 static Result<Init> processInit(
321 Init init, kj::Maybe<ProcessInitOptions> options = kj::none) KJ_WARN_UNUSED_RESULT;
322 
323 struct CompileOptions {
324 // The base URL to use. Only used in the compile(kj::StringPtr, ...) variant.
325 kj::Maybe<kj::StringPtr> baseUrl;
326 bool ignoreCase = false;
327 };
328 
329 static Result<UrlPattern> tryCompile(
330 kj::StringPtr, kj::Maybe<CompileOptions> = kj::none) KJ_WARN_UNUSED_RESULT;
331 static Result<UrlPattern> tryCompile(
332 Init init, kj::Maybe<CompileOptions> = kj::none) KJ_WARN_UNUSED_RESULT;
333 
334 UrlPattern(UrlPattern&&) = default;
335 UrlPattern& operator=(UrlPattern&&) = default;
336 KJ_DISALLOW_COPY(UrlPattern);
337 
338 inline const Component& getProtocol() const KJ_LIFETIMEBOUND {
339 return protocol;
340 }
341 inline const Component& getUsername() const KJ_LIFETIMEBOUND {
342 return username;
343 }
344 inline const Component& getPassword() const KJ_LIFETIMEBOUND {
345 return password;
346 }
347 inline const Component& getHostname() const KJ_LIFETIMEBOUND {
348 return hostname;
349 }
350 inline const Component& getPort() const KJ_LIFETIMEBOUND {
351 return port;
352 }
353 inline const Component& getPathname() const KJ_LIFETIMEBOUND {
354 return pathname;
355 }
356 inline const Component& getSearch() const KJ_LIFETIMEBOUND {
357 return search;
358 }
359 inline const Component& getHash() const KJ_LIFETIMEBOUND {
360 return hash;
361 }
362 
363 // If ignoreCase is true, the JavaScript regular expression created for each pattern
364 // must use the `vi` flag. Otherwise, they must use the `v` flag.
365 inline bool getIgnoreCase() const {
366 return ignoreCase;
367 }
368 
369 JSG_MEMORY_INFO(UrlPattern) {
370 tracker.trackField("protocol", protocol);
371 tracker.trackField("username", username);
372 tracker.trackField("password", password);
373 tracker.trackField("hostname", hostname);
374 tracker.trackField("port", port);
375 tracker.trackField("pathname", pathname);
376 tracker.trackField("search", search);
377 tracker.trackField("hash", hash);
378 }
379 
380 private:
381 UrlPattern(kj::Array<Component> components, bool ignoreCase);
382 
383 Component protocol;
384 Component username;
385 Component password;
386 Component hostname;
387 Component port;
388 Component pathname;
389 Component search;
390 Component hash;
391 bool ignoreCase;
392 
393 static Result<UrlPattern> tryCompileInit(UrlPattern::Init init, const CompileOptions& options);
394};
395} // namespace workerd::jsg
396 
397// Append _url to a string literal to create a parsed URL. An assert will be triggered
398// if the value cannot be parsed successfully.
399const workerd::jsg::Url operator""_url(const char* str, size_t size) KJ_WARN_UNUSED_RESULT;