@@ -98,6 +98,87 @@ void requireValidHeaderValue(kj::StringPtr value) {
9898 JSG_REQUIRE (c != ' \0 ' && c != ' \r ' && c != ' \n ' , TypeError, " Invalid header value." );
9999 }
100100}
101+
102+ // If any more headers are added to the CommonHeaderName enum later, we should be careful about
103+ // introducing them into serialization. We need to roll out a change that recognizes the new IDs
104+ // before rolling out a change that sends them. MAX_COMMON_HEADER_ID is the max value we're willing
105+ // to send.
106+ static constexpr size_t MAX_COMMON_HEADER_ID =
107+ static_cast <size_t >(capnp::CommonHeaderName::WWW_AUTHENTICATE);
108+
109+ // Constexpr array of lowercase common header names (must match CommonHeaderName enum order
110+ // and must be kept in sync with the ordinal values defined in http-over-capnp.capnp). Since
111+ // it is extremely unlikely that those will change often, we hardcode them here for runtime
112+ // efficiency.
113+ static constexpr const char * COMMON_HEADER_NAMES[] = {
114+ nullptr , // 0: invalid
115+ " accept-charset" , // 1
116+ " accept-encoding" , // 2
117+ " accept-language" , // 3
118+ " accept-ranges" , // 4
119+ " accept" , // 5
120+ " access-control-allow-origin" , // 6
121+ " age" , // 7
122+ " allow" , // 8
123+ " authorization" , // 9
124+ " cache-control" , // 10
125+ " content-disposition" , // 11
126+ " content-encoding" , // 12
127+ " content-language" , // 13
128+ " content-length" , // 14
129+ " content-location" , // 15
130+ " content-range" , // 16
131+ " content-type" , // 17
132+ " cookie" , // 18
133+ " date" , // 19
134+ " etag" , // 20
135+ " expect" , // 21
136+ " expires" , // 22
137+ " from" , // 23
138+ " host" , // 24
139+ " if-match" , // 25
140+ " if-modified-since" , // 26
141+ " if-none-match" , // 27
142+ " if-range" , // 28
143+ " if-unmodified-since" , // 29
144+ " last-modified" , // 30
145+ " link" , // 31
146+ " location" , // 32
147+ " max-forwards" , // 33
148+ " proxy-authenticate" , // 34
149+ " proxy-authorization" , // 35
150+ " range" , // 36
151+ " referer" , // 37
152+ " refresh" , // 38
153+ " retry-after" , // 39
154+ " server" , // 40
155+ " set-cookie" , // 41
156+ " strict-transport-security" , // 42
157+ " transfer-encoding" , // 43
158+ " user-agent" , // 44
159+ " vary" , // 45
160+ " via" , // 46
161+ " www-authenticate" , // 47
162+ };
163+
164+ kj::String getCommonHeaderName (uint id) {
165+ KJ_ASSERT (id > 0 && id <= MAX_COMMON_HEADER_ID, " Invalid common header ID" );
166+ auto name = COMMON_HEADER_NAMES[id];
167+ KJ_DASSERT (name != nullptr );
168+ return kj::str (name);
169+ }
170+
171+ kj::Maybe<uint> getCommonHeaderId (kj::StringPtr name) {
172+ // It really shouldn't be possible for a name to be empty but just in case...
173+ if (name.size () == 0 ) return kj::none;
174+ for (uint i = 1 ; i <= MAX_COMMON_HEADER_ID; ++i) {
175+ KJ_DASSERT (COMMON_HEADER_NAMES[i] != nullptr );
176+ if (name == COMMON_HEADER_NAMES[i]) return i;
177+ }
178+ return kj::none;
179+ }
180+
181+ static_assert (std::size(COMMON_HEADER_NAMES) == (MAX_COMMON_HEADER_ID + 1 ));
101182} // namespace
102183
103184Headers::Headers (jsg::Lock& js, jsg::Dict<jsg::ByteString, jsg::ByteString> dict)
@@ -416,78 +497,6 @@ bool Headers::inspectImmutable() {
416497// capitalization). So, it's certainly not worth it to try to keep the original capitalization
417498// across serialization.
418499
419- // If any more headers are added to the CommonHeaderName enum later, we should be careful about
420- // introducing them into serialization. We need to roll out a change that recognizes the new IDs
421- // before rolling out a change that sends them. MAX_COMMON_HEADER_ID is the max value we're willing
422- // to send.
423- static constexpr uint MAX_COMMON_HEADER_ID =
424- static_cast <uint>(capnp::CommonHeaderName::WWW_AUTHENTICATE);
425-
426- // ID for the `$commonText` annotation declared in http-over-capnp.capnp.
427- // TODO(cleanup): Cap'n Proto should really codegen constants for annotation IDs so we don't have
428- // to copy them.
429- static constexpr uint64_t COMMON_TEXT_ANNOTATION_ID = 0x857745131db6fc83 ;
430-
431- static kj::Array<kj::StringPtr> makeCommonHeaderList () {
432- auto enums = capnp::Schema::from<capnp::CommonHeaderName>().getEnumerants ();
433- auto builder = kj::heapArrayBuilder<kj::StringPtr>(enums.size ());
434- bool first = true ;
435- for (auto e: enums) {
436- if (first) {
437- // Value zero is invalid, skip it.
438- static_assert (static_cast <uint>(capnp::CommonHeaderName::INVALID) == 0 );
439-
440- // Add `nullptr` to the array so that our array indexes aren't off-by-one from the enum
441- // values. We could in theory skip this and use +1 and -1 in a bunch of places but that seems
442- // error-prone.
443- builder.add (nullptr );
444-
445- first = false ;
446- continue ;
447- }
448-
449- kj::Maybe<kj::StringPtr> name;
450-
451- // Look for $commonText annotation.
452- for (auto ann: e.getProto ().getAnnotations ()) {
453- if (ann.getId () == COMMON_TEXT_ANNOTATION_ID) {
454- name = ann.getValue ().getText ();
455- break ;
456- }
457- }
458-
459- builder.add (KJ_ASSERT_NONNULL (name));
460- }
461-
462- return builder.finish ();
463- }
464-
465- static kj::ArrayPtr<const kj::StringPtr> getCommonHeaderList () {
466- static const kj::Array<kj::StringPtr> LIST = makeCommonHeaderList ();
467- return LIST;
468- }
469-
470- static kj::HashMap<kj::String, uint> makeCommonHeaderMap () {
471- kj::HashMap<kj::String, uint> result;
472- auto list = getCommonHeaderList ();
473- KJ_ASSERT (MAX_COMMON_HEADER_ID < list.size ());
474- for (auto i: kj::range (1 , MAX_COMMON_HEADER_ID + 1 )) {
475- auto key = kj::str (list[i]);
476- for (auto & c: key) {
477- if (' A' <= c && c <= ' Z' ) {
478- c = c - ' A' + ' a' ;
479- }
480- }
481- result.insert (kj::mv (key), i);
482- }
483- return result;
484- }
485-
486- static const kj::HashMap<kj::String, uint>& getCommonHeaderMap () {
487- static const kj::HashMap<kj::String, uint> MAP = makeCommonHeaderMap ();
488- return MAP;
489- }
490-
491500void Headers::serialize (jsg::Lock& js, jsg::Serializer& serializer) {
492501 // We serialize as a series of key-value pairs. Each value is a length-delimited string. Each key
493502 // is a common header ID, or the value zero to indicate an uncommon header, which is then
@@ -503,10 +512,9 @@ void Headers::serialize(jsg::Lock& js, jsg::Serializer& serializer) {
503512 serializer.writeRawUint32 (count);
504513
505514 // Now write key/values.
506- auto & commonHeaders = getCommonHeaderMap ();
507515 for (auto & entry: headers) {
508516 auto & header = entry.second ;
509- auto commonId = commonHeaders. find (header.key );
517+ auto commonId = getCommonHeaderId (header.key );
510518 for (auto & value: header.values ) {
511519 KJ_IF_SOME (c, commonId) {
512520 serializer.writeRawUint32 (c);
@@ -527,15 +535,14 @@ jsg::Ref<Headers> Headers::deserialize(
527535
528536 uint count = deserializer.readRawUint32 ();
529537
530- auto commonHeaders = getCommonHeaderList ();
531538 for (auto i KJ_UNUSED: kj::zeroTo (count)) {
532539 uint commonId = deserializer.readRawUint32 ();
533540 kj::String name;
534541 if (commonId == 0 ) {
535542 name = deserializer.readLengthDelimitedString ();
536543 } else {
537- KJ_ASSERT (commonId < commonHeaders. size () );
538- name = kj::str (commonHeaders[ commonId] );
544+ KJ_ASSERT (commonId <= MAX_COMMON_HEADER_ID );
545+ name = getCommonHeaderName ( commonId);
539546 }
540547
541548 auto value = deserializer.readLengthDelimitedString ();
0 commit comments