File
Blob: src/workerd/api/headers.h
| 1 | #pragma once |
| 2 | |
| 3 | #include <workerd/jsg/jsg.h> |
| 4 | #include <workerd/jsg/memory.h> |
| 5 | #include <workerd/io/compatibility-date.capnp.h> |
| 6 | #include <workerd/io/worker-interface.capnp.h> |
| 7 | #include <kj/compat/http.h> |
| 8 | |
| 9 | namespace workerd::api { |
| 10 | |
| 11 | class Headers final: public jsg::Object { |
| 12 | private: |
| 13 | template <typename T> |
| 14 | struct IteratorState { |
| 15 | kj::Array<T> copy; |
| 16 | decltype(copy.begin()) cursor = copy.begin(); |
| 17 | }; |
| 18 | |
| 19 | public: |
| 20 | static constexpr kj::uint MAX_COMMON_HEADER_ID = |
| 21 | static_cast<kj::uint>(capnp::CommonHeaderName::WWW_AUTHENTICATE); |
| 22 | |
| 23 | enum class Guard { |
| 24 | // WARNING: This type is serialized, do not change the numeric values. |
| 25 | IMMUTABLE = 0, |
| 26 | REQUEST = 1, |
| 27 | // REQUEST_NO_CORS, // CORS not relevant on server side |
| 28 | RESPONSE = 2, |
| 29 | NONE = 3 |
| 30 | }; |
| 31 | |
| 32 | struct DisplayedHeader { |
| 33 | kj::String key; // lower-cased name |
| 34 | kj::String value; // comma-concatenation of all values seen |
| 35 | }; |
| 36 | |
| 37 | Headers(): guard(Guard::NONE) {} |
| 38 | explicit Headers(jsg::Lock& js, jsg::Dict<kj::String, kj::String> dict); |
| 39 | explicit Headers(jsg::Lock& js, const Headers& other); |
| 40 | explicit Headers(jsg::Lock& js, const kj::HttpHeaders& other, Guard guard); |
| 41 | KJ_DISALLOW_COPY_AND_MOVE(Headers); |
| 42 | |
| 43 | // Make a copy of this Headers object, and preserve the guard. |
| 44 | jsg::Ref<Headers> clone(jsg::Lock& js) const; |
| 45 | |
| 46 | // Fill in the given HttpHeaders with these headers. Note that strings are inserted by |
| 47 | // reference, so the output must be consumed immediately. |
| 48 | void shallowCopyTo(kj::HttpHeaders& out); |
| 49 | |
| 50 | // Returns headers with lower-case name and comma-concatenated duplicates. |
| 51 | kj::Array<DisplayedHeader> getDisplayedHeaders(jsg::Lock& js); |
| 52 | |
| 53 | using StringPair = jsg::Sequence<kj::String>; |
| 54 | using StringPairs = jsg::Sequence<StringPair>; |
| 55 | |
| 56 | // Per the fetch specification, it is possible to initialize a Headers object |
| 57 | // from any other object that has a Symbol.iterator implementation. Those are |
| 58 | // handled in this Initializer definition using the StringPairs definition |
| 59 | // that aliases jsg::Sequence<jsg::Sequence<kj::String>>. Technically, |
| 60 | // the Headers object itself falls under that definition as well. However, treating |
| 61 | // a Headers object as a jsg::Sequence<jsg::Sequence<T>> is nowhere near as |
| 62 | // performant and has the side effect of forcing all header names to be lower-cased |
| 63 | // rather than case-preserved. Instead of following the spec exactly here, we |
| 64 | // choose to special case creating a Header object from another Header object. |
| 65 | // This is an intentional departure from the spec. |
| 66 | using Initializer = kj::OneOf<jsg::Ref<Headers>, |
| 67 | StringPairs, |
| 68 | jsg::Dict<kj::String, kj::String>>; |
| 69 | |
| 70 | static jsg::Ref<Headers> constructor(jsg::Lock& js, jsg::Optional<Initializer> init); |
| 71 | kj::Maybe<kj::String> get(jsg::Lock& js, kj::String name); |
| 72 | |
| 73 | // getAll is a legacy non-standard extension API that we introduced before |
| 74 | // getSetCookie() was defined. We continue to support it for backwards |
| 75 | // compatibility but users really ought to be using getSetCookie() now. |
| 76 | kj::Array<kj::StringPtr> getAll(kj::String name); |
| 77 | |
| 78 | // The Set-Cookie header is special in that it is the only HTTP header that |
| 79 | // is not permitted to be combined into a single instance. |
| 80 | kj::Array<kj::StringPtr> getSetCookie(); |
| 81 | |
| 82 | bool has(kj::String name); |
| 83 | |
| 84 | void set(jsg::Lock& js, kj::String name, kj::String value); |
| 85 | void append(jsg::Lock& js, kj::String name, kj::String value); |
| 86 | void delete_(kj::String name); |
| 87 | |
| 88 | // The *Unguarded variations of set/append are used for internal use when we want to |
| 89 | // bypass certain checks, such as the guard check. These are not intended for public use and should be used with caution. |
| 90 | kj::Maybe<kj::String> getPtr(jsg::Lock& js, kj::StringPtr name); |
| 91 | void setUnguarded(jsg::Lock& js, kj::String name, kj::String value); |
| 92 | void appendUnguarded(jsg::Lock& js, kj::String name, kj::String value); |
| 93 | |
| 94 | // The *Common variations of get/has/set/delete are used for internal use when we want to access |
| 95 | // common headers by their common enum ID. These are not intended for public use and should be |
| 96 | // used with caution. These also avoid guard checks. |
| 97 | kj::Maybe<kj::String> getCommon(jsg::Lock& js, capnp::CommonHeaderName idx); |
| 98 | bool hasCommon(capnp::CommonHeaderName idx); |
| 99 | void setCommon(capnp::CommonHeaderName idx, kj::String value); |
| 100 | void deleteCommon(capnp::CommonHeaderName idx); |
| 101 | |
| 102 | void forEach(jsg::Lock& js, |
| 103 | jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<Headers>)>, |
| 104 | jsg::Optional<jsg::Value>); |
| 105 | |
| 106 | bool inspectImmutable(); |
| 107 | |
| 108 | JSG_ITERATOR(EntryIterator, entries, |
| 109 | kj::Array<kj::String>, |
| 110 | IteratorState<DisplayedHeader>, |
| 111 | entryIteratorNext) |
| 112 | JSG_ITERATOR(KeyIterator, keys, |
| 113 | kj::String, |
| 114 | IteratorState<kj::String>, |
| 115 | keyOrValueIteratorNext) |
| 116 | JSG_ITERATOR(ValueIterator, values, |
| 117 | kj::String, |
| 118 | IteratorState<kj::String>, |
| 119 | keyOrValueIteratorNext) |
| 120 | |
| 121 | // JavaScript API. |
| 122 | |
| 123 | JSG_RESOURCE_TYPE(Headers, CompatibilityFlags::Reader flags) { |
| 124 | JSG_METHOD(get); |
| 125 | JSG_METHOD(getAll); |
| 126 | if (flags.getHttpHeadersGetSetCookie()) { |
| 127 | JSG_METHOD(getSetCookie); |
| 128 | } |
| 129 | JSG_METHOD(has); |
| 130 | JSG_METHOD(set); |
| 131 | JSG_METHOD(append); |
| 132 | JSG_METHOD_NAMED(delete, delete_); |
| 133 | JSG_METHOD(forEach); |
| 134 | JSG_METHOD(entries); |
| 135 | JSG_METHOD(keys); |
| 136 | JSG_METHOD(values); |
| 137 | |
| 138 | JSG_INSPECT_PROPERTY(immutable, inspectImmutable); |
| 139 | |
| 140 | JSG_ITERABLE(entries); |
| 141 | |
| 142 | JSG_TS_DEFINE(type HeadersInit = Headers | Iterable<Iterable<string>> | Record<string, string>); |
| 143 | // All type aliases get inlined when exporting RTTI, but this type alias is included by |
| 144 | // the official TypeScript types, so users might be depending on it. |
| 145 | |
| 146 | JSG_TS_OVERRIDE({ |
| 147 | constructor(init?: HeadersInit); |
| 148 | |
| 149 | entries(): IterableIterator<[key: string, value: string]>; |
| 150 | [Symbol.iterator](): IterableIterator<[key: string, value: string]>; |
| 151 | |
| 152 | forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: Headers) => void, thisArg?: This): void; |
| 153 | }); |
| 154 | } |
| 155 | |
| 156 | void serialize(jsg::Lock& js, jsg::Serializer& serializer); |
| 157 | static jsg::Ref<Headers> deserialize( |
| 158 | jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer); |
| 159 | |
| 160 | JSG_SERIALIZABLE(rpc::SerializationTag::HEADERS); |
| 161 | |
| 162 | void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; |
| 163 | |
| 164 | // A header is identified by either a common header ID or an uncommon header name. |
| 165 | // The header key name is always identifed in lower-case form, while the original |
| 166 | // casing is preserved in the actual Header struct to support case-preserving display. |
| 167 | // TODO(perf): We can likely optimize this further by interning uncommon header names |
| 168 | // so that we avoid repeated allocations of the same uncommon header name. Unless |
| 169 | // it proves to be a performance problem, however, we can leave that for future work. |
| 170 | using HeaderKey = kj::OneOf<uint, kj::String>; |
| 171 | |
| 172 | private: |
| 173 | struct Header final { |
| 174 | // The name is only set when the casing of the name differs from the lower-cased key. |
| 175 | kj::Maybe<kj::String> name; |
| 176 | kj::Vector<kj::String> values; |
| 177 | Header() = default; |
| 178 | explicit Header(kj::Maybe<kj::String> name): name(kj::mv(name)) { |
| 179 | values.reserve(1); |
| 180 | } |
| 181 | |
| 182 | kj::Own<Header> clone() const { |
| 183 | Header header; |
| 184 | header.name = name.map([](const kj::String& s) { return kj::str(s); }); |
| 185 | header.values = KJ_MAP(v, values) { return kj::str(v); }; |
| 186 | return kj::heap(kj::mv(header)); |
| 187 | } |
| 188 | |
| 189 | JSG_MEMORY_INFO(Header) { |
| 190 | tracker.trackField("name", name); |
| 191 | for (const auto& value : values) { |
| 192 | tracker.trackField("value", value); |
| 193 | } |
| 194 | } |
| 195 | }; |
| 196 | |
| 197 | // This wastes one slot, but it is a fixed array for fast access. |
| 198 | kj::FixedArray<kj::Maybe<kj::Own<Header>>, MAX_COMMON_HEADER_ID + 1> commonHeaders; |
| 199 | |
| 200 | // The key is always lower-case. |
| 201 | kj::HashMap<kj::String, kj::Own<Header>> uncommonHeaders; |
| 202 | |
| 203 | Guard guard; |
| 204 | |
| 205 | kj::Maybe<Header&> tryGetHeader(const HeaderKey& key); |
| 206 | |
| 207 | void checkGuard() { |
| 208 | JSG_REQUIRE(guard == Guard::NONE, TypeError, "Can't modify immutable headers."); |
| 209 | } |
| 210 | |
| 211 | static kj::Maybe<kj::Array<kj::String>> entryIteratorNext(jsg::Lock& js, auto& state) { |
| 212 | if (state.cursor == state.copy.end()) { |
| 213 | return kj::none; |
| 214 | } |
| 215 | auto& ret = *state.cursor++; |
| 216 | return kj::arr(kj::mv(ret.key), kj::mv(ret.value)); |
| 217 | } |
| 218 | |
| 219 | static kj::Maybe<kj::String> keyOrValueIteratorNext(jsg::Lock& js, auto& state) { |
| 220 | if (state.cursor == state.copy.end()) { |
| 221 | return kj::none; |
| 222 | } |
| 223 | auto& ret = *state.cursor++; |
| 224 | return kj::mv(ret); |
| 225 | } |
| 226 | }; |
| 227 | |
| 228 | } // namespace workerd::api |