1+ import {
2+ Headers ,
3+ RawHeaders
4+ } from "../types" ;
5+
6+ /*
7+
8+ These utils support conversion between the various header representations that we deal
9+ with. Those are:
10+
11+ - Flat arrays of [key, value, key, value, key, ...]. This is the raw header format
12+ generally used by Node.js's APIs throughout.
13+ - Raw header tuple arrays like [[key, value], [key, value]]. This is our own raw header
14+ format, aiming to be fairly easy to use and to preserve header order, header dupes &
15+ header casing throughout.
16+ - Formatted header objects of { key: value, key: value }. These are returned as the most
17+ convenient and consistent header format: keys are lowercased, and values are either
18+ strings or arrays of strings (for duplicate headers). This is returned by Node's APIs,
19+ but with some unclear normalization rules, so in practice we build raw headers and
20+ reconstruct this ourselves everyhere, by lowercasing & building arrays of values.
21+
22+ */
23+
24+ export const findRawHeader = ( rawHeaders : RawHeaders , targetKey : string ) =>
25+ rawHeaders . find ( ( [ key ] ) => key . toLowerCase ( ) === targetKey ) ;
26+
27+ const findRawHeaders = ( rawHeaders : RawHeaders , targetKey : string ) =>
28+ rawHeaders . filter ( ( [ key ] ) => key . toLowerCase ( ) === targetKey ) ;
29+
30+ /**
31+ * Return node's _very_ raw headers ([k, v, k, v, ...]) into our slightly more convenient
32+ * pairwise tuples [[k, v], [k, v], ...] RawHeaders structure.
33+ */
34+ export function pairFlatRawHeaders ( flatRawHeaders : string [ ] ) : RawHeaders {
35+ const result : RawHeaders = [ ] ;
36+ for ( let i = 0 ; i < flatRawHeaders . length ; i += 2 /* Move two at a time */ ) {
37+ result [ i / 2 ] = [ flatRawHeaders [ i ] , flatRawHeaders [ i + 1 ] ] ;
38+ }
39+ return result ;
40+ }
41+
42+ export function flattenPairedRawHeaders ( rawHeaders : RawHeaders ) : string [ ] {
43+ return rawHeaders . flat ( ) ;
44+ }
45+
46+ /**
47+ * Take a raw headers, and turn them into headers, but without some of Node's concessions
48+ * to ease of use, i.e. keeping multiple values as arrays.
49+ */
50+ export function rawHeadersToObject ( rawHeaders : RawHeaders ) : Headers {
51+ return rawHeaders . reduce < Headers > ( ( headers , [ key , value ] ) => {
52+ key = key . toLowerCase ( ) ;
53+
54+ const existingValue = headers [ key ] ;
55+
56+ if ( Array . isArray ( existingValue ) ) {
57+ existingValue . push ( value ) ;
58+ } else if ( existingValue ) {
59+ headers [ key ] = [ existingValue , value ] ;
60+ } else {
61+ headers [ key ] = value ;
62+ }
63+
64+ return headers ;
65+ } , { } ) ;
66+ }
67+
68+ export function objectHeadersToRaw ( headers : Headers ) : RawHeaders {
69+ const rawHeaders : RawHeaders = [ ] ;
70+
71+ for ( let key in headers ) {
72+ const value = headers [ key ] ;
73+
74+ if ( value === undefined ) continue ; // Drop undefined header values
75+
76+ if ( Array . isArray ( value ) ) {
77+ value . forEach ( ( v ) => rawHeaders . push ( [ key , v ] ) ) ;
78+ } else {
79+ rawHeaders . push ( [ key , value ] ) ;
80+ }
81+ }
82+
83+ return rawHeaders ;
84+ }
85+
86+ export function objectHeadersToFlat ( headers : Headers ) : string [ ] {
87+ const flatHeaders : string [ ] = [ ] ;
88+
89+ for ( let key in headers ) {
90+ const value = headers [ key ] ;
91+
92+ if ( value === undefined ) continue ; // Drop undefined header values
93+
94+ if ( Array . isArray ( value ) ) {
95+ value . forEach ( ( v ) => {
96+ flatHeaders . push ( key ) ;
97+ flatHeaders . push ( v ) ;
98+ } ) ;
99+ } else {
100+ flatHeaders . push ( key ) ;
101+ flatHeaders . push ( value ) ;
102+ }
103+ }
104+
105+ return flatHeaders ;
106+ }
107+
108+ // See https://httptoolkit.tech/blog/translating-http-2-into-http-1/ for details on the
109+ // transformations required between H2 & H1 when proxying.
110+ export function h2HeadersToH1 ( h2Headers : RawHeaders ) : RawHeaders {
111+ let h1Headers = h2Headers . filter ( ( [ key ] ) => key [ 0 ] !== ':' ) ;
112+
113+ if ( ! findRawHeader ( h1Headers , 'host' ) && findRawHeader ( h2Headers , ':authority' ) ) {
114+ h1Headers . unshift ( [ 'Host' , findRawHeader ( h2Headers , ':authority' ) ! [ 1 ] ] ) ;
115+ }
116+
117+ // In HTTP/1 you MUST only send one cookie header - in HTTP/2 sending multiple is fine,
118+ // so we have to concatenate them:
119+ const cookieHeaders = findRawHeaders ( h1Headers , 'cookie' )
120+ if ( cookieHeaders . length > 1 ) {
121+ h1Headers = h1Headers . filter ( ( [ key ] ) => key . toLowerCase ( ) !== 'cookie' ) ;
122+ h1Headers . push ( [ 'Cookie' , cookieHeaders . join ( '; ' ) ] ) ;
123+ }
124+
125+ return h1Headers ;
126+ }
127+
128+ // Take from http2/util.js in Node itself
129+ const HTTP2_ILLEGAL_HEADERS = [
130+ 'connection' ,
131+ 'upgrade' ,
132+ 'host' ,
133+ 'http2-settings' ,
134+ 'keep-alive' ,
135+ 'proxy-connection' ,
136+ 'transfer-encoding'
137+ ] ;
138+
139+ export function h1HeadersToH2 ( headers : RawHeaders ) : RawHeaders {
140+ return headers . filter ( ( [ key ] ) =>
141+ ! HTTP2_ILLEGAL_HEADERS . includes ( key . toLowerCase ( ) )
142+ ) ;
143+ }
0 commit comments