Skip to content
File

Blob: src/workerd/api/headers.h

cpp229 lines
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 
9namespace workerd::api {
10 
11class Headers final: public jsg::Object {
12private:
13 template <typename T>
14 struct IteratorState {
15 kj::Array<T> copy;
16 decltype(copy.begin()) cursor = copy.begin();
17 };
18 
19public:
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 
172private:
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