File
Blob: src/workerd/api/url-standard.h
| 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 | |
| 13 | namespace 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. |
| 19 | namespace url { |
| 20 | |
| 21 | class 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). |
| 26 | class 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 | }; |
| 36 | public: |
| 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 | |
| 133 | private: |
| 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. |
| 159 | class URL: public jsg::Object { |
| 160 | public: |
| 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 | |
| 256 | private: |
| 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 |