File
Blob: src/workerd/api/url.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 <workerd/jsg/jsg.h> |
| 8 | #include <workerd/io/compatibility-date.capnp.h> |
| 9 | #include <kj/refcount.h> |
| 10 | #include <kj/compat/url.h> |
| 11 | |
| 12 | namespace workerd::api { |
| 13 | |
| 14 | class URLSearchParams; |
| 15 | |
| 16 | // Implements the URL interface as prescribed by: https://url.spec.whatwg.org/#api |
| 17 | // This is the legacy, non-standard implementation. |
| 18 | class URL: public jsg::Object { |
| 19 | public: |
| 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 | |
| 133 | private: |
| 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 | |
| 149 | class URLSearchParams: public jsg::Object { |
| 150 | // TODO(cleanup): Combine implementation with FormData? |
| 151 | private: |
| 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 | |
| 161 | public: |
| 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 | |
| 244 | private: |
| 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 |