cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2021.10.5

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

connection/header_test.go

717lines · modecode

1package connection
2
3import (
4 "fmt"
5 "math/rand"
6 "net/http"
7 "net/url"
8 "reflect"
9 "regexp"
10 "sort"
11 "strings"
12 "testing"
13 "testing/quick"
14
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17
18 "github.com/cloudflare/cloudflared/h2mux"
19)
20
21type ByName []h2mux.Header
22
23func (a ByName) Len() int { return len(a) }
24func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
25func (a ByName) Less(i, j int) bool {
26 if a[i].Name == a[j].Name {
27 return a[i].Value < a[j].Value
28 }
29
30 return a[i].Name < a[j].Name
31}
32
33func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) {
34 request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
35 assert.NoError(t, err)
36
37 mockHeaders := http.Header{
38 "Mock header 1": {"Mock value 1"},
39 "Mock header 2": {"Mock value 2"},
40 }
41
42 headersConversionErr := H2RequestHeadersToH1Request(createSerializedHeaders(RequestUserHeaders, mockHeaders), request)
43
44 assert.True(t, reflect.DeepEqual(mockHeaders, request.Header))
45 assert.NoError(t, headersConversionErr)
46}
47
48func createSerializedHeaders(headersField string, headers http.Header) []h2mux.Header {
49 return []h2mux.Header{{
50 Name: headersField,
51 Value: SerializeHeaders(headers),
52 }}
53}
54
55func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) {
56 request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
57 assert.NoError(t, err)
58
59 emptyHeaders := make(http.Header)
60 headersConversionErr := H2RequestHeadersToH1Request(
61 []h2mux.Header{{
62 Name: RequestUserHeaders,
63 Value: SerializeHeaders(emptyHeaders),
64 }},
65 request,
66 )
67
68 assert.True(t, reflect.DeepEqual(emptyHeaders, request.Header))
69 assert.NoError(t, headersConversionErr)
70}
71
72func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) {
73 request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
74 assert.NoError(t, err)
75
76 mockRequestHeaders := []h2mux.Header{
77 {Name: ":path", Value: "//bad_path/"},
78 {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
79 }
80
81 headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
82
83 assert.Equal(t, http.Header{
84 "Mock header": []string{"Mock value"},
85 }, request.Header)
86
87 assert.Equal(t, "http://example.com//bad_path/", request.URL.String())
88
89 assert.NoError(t, headersConversionErr)
90}
91
92func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) {
93 request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
94 assert.NoError(t, err)
95
96 mockRequestHeaders := []h2mux.Header{
97 {Name: ":path", Value: "/?query=mock%20value"},
98 {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
99 }
100
101 headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
102
103 assert.Equal(t, http.Header{
104 "Mock header": []string{"Mock value"},
105 }, request.Header)
106
107 assert.Equal(t, "http://example.com/?query=mock%20value", request.URL.String())
108
109 assert.NoError(t, headersConversionErr)
110}
111
112func TestH2RequestHeadersToH1Request_HostPathWithURLEncoding(t *testing.T) {
113 request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
114 assert.NoError(t, err)
115
116 mockRequestHeaders := []h2mux.Header{
117 {Name: ":path", Value: "/mock%20path"},
118 {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
119 }
120
121 headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
122
123 assert.Equal(t, http.Header{
124 "Mock header": []string{"Mock value"},
125 }, request.Header)
126
127 assert.Equal(t, "http://example.com/mock%20path", request.URL.String())
128
129 assert.NoError(t, headersConversionErr)
130}
131
132func TestH2RequestHeadersToH1Request_WeirdURLs(t *testing.T) {
133 type testCase struct {
134 path string
135 want string
136 }
137 testCases := []testCase{
138 {
139 path: "",
140 want: "",
141 },
142 {
143 path: "/",
144 want: "/",
145 },
146 {
147 path: "//",
148 want: "//",
149 },
150 {
151 path: "/test",
152 want: "/test",
153 },
154 {
155 path: "//test",
156 want: "//test",
157 },
158 {
159 // https://github.com/cloudflare/cloudflared/issues/81
160 path: "//test/",
161 want: "//test/",
162 },
163 {
164 path: "/%2Ftest",
165 want: "/%2Ftest",
166 },
167 {
168 path: "//%20test",
169 want: "//%20test",
170 },
171 {
172 // https://github.com/cloudflare/cloudflared/issues/124
173 path: "/test?get=somthing%20a",
174 want: "/test?get=somthing%20a",
175 },
176 {
177 path: "/%20",
178 want: "/%20",
179 },
180 {
181 // stdlib's EscapedPath() will always percent-encode ' '
182 path: "/ ",
183 want: "/%20",
184 },
185 {
186 path: "/ a ",
187 want: "/%20a%20",
188 },
189 {
190 path: "/a%20b",
191 want: "/a%20b",
192 },
193 {
194 path: "/foo/bar;param?query#frag",
195 want: "/foo/bar;param?query#frag",
196 },
197 {
198 // stdlib's EscapedPath() will always percent-encode non-ASCII chars
199 path: "/a␠b",
200 want: "/a%E2%90%A0b",
201 },
202 {
203 path: "/a-umlaut-ä",
204 want: "/a-umlaut-%C3%A4",
205 },
206 {
207 path: "/a-umlaut-%C3%A4",
208 want: "/a-umlaut-%C3%A4",
209 },
210 {
211 path: "/a-umlaut-%c3%a4",
212 want: "/a-umlaut-%c3%a4",
213 },
214 {
215 // here the second '#' is treated as part of the fragment
216 path: "/a#b#c",
217 want: "/a#b%23c",
218 },
219 {
220 path: "/a#b␠c",
221 want: "/a#b%E2%90%A0c",
222 },
223 {
224 path: "/a#b%20c",
225 want: "/a#b%20c",
226 },
227 {
228 path: "/a#b c",
229 want: "/a#b%20c",
230 },
231 {
232 // stdlib's EscapedPath() will always percent-encode '\'
233 path: "/\\",
234 want: "/%5C",
235 },
236 {
237 path: "/a\\",
238 want: "/a%5C",
239 },
240 {
241 path: "/a,b.c.",
242 want: "/a,b.c.",
243 },
244 {
245 path: "/.",
246 want: "/.",
247 },
248 {
249 // stdlib's EscapedPath() will always percent-encode '`'
250 path: "/a`",
251 want: "/a%60",
252 },
253 {
254 path: "/a[0]",
255 want: "/a[0]",
256 },
257 {
258 path: "/?a[0]=5 &b[]=",
259 want: "/?a[0]=5 &b[]=",
260 },
261 {
262 path: "/?a=%22b%20%22",
263 want: "/?a=%22b%20%22",
264 },
265 }
266
267 for index, testCase := range testCases {
268 requestURL := "https://example.com"
269
270 request, err := http.NewRequest(http.MethodGet, requestURL, nil)
271 assert.NoError(t, err)
272
273 mockRequestHeaders := []h2mux.Header{
274 {Name: ":path", Value: testCase.path},
275 {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
276 }
277
278 headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
279 assert.NoError(t, headersConversionErr)
280
281 assert.Equal(t,
282 http.Header{
283 "Mock header": []string{"Mock value"},
284 },
285 request.Header)
286
287 assert.Equal(t,
288 "https://example.com"+testCase.want,
289 request.URL.String(),
290 "Failed URL index: %v %#v", index, testCase)
291 }
292}
293
294func TestH2RequestHeadersToH1Request_QuickCheck(t *testing.T) {
295 config := &quick.Config{
296 Values: func(args []reflect.Value, rand *rand.Rand) {
297 args[0] = reflect.ValueOf(randomHTTP2Path(t, rand))
298 },
299 }
300
301 type testOrigin struct {
302 url string
303
304 expectedScheme string
305 expectedBasePath string
306 }
307 testOrigins := []testOrigin{
308 {
309 url: "http://origin.hostname.example.com:8080",
310 expectedScheme: "http",
311 expectedBasePath: "http://origin.hostname.example.com:8080",
312 },
313 {
314 url: "http://origin.hostname.example.com:8080/",
315 expectedScheme: "http",
316 expectedBasePath: "http://origin.hostname.example.com:8080",
317 },
318 {
319 url: "http://origin.hostname.example.com:8080/api",
320 expectedScheme: "http",
321 expectedBasePath: "http://origin.hostname.example.com:8080/api",
322 },
323 {
324 url: "http://origin.hostname.example.com:8080/api/",
325 expectedScheme: "http",
326 expectedBasePath: "http://origin.hostname.example.com:8080/api",
327 },
328 {
329 url: "https://origin.hostname.example.com:8080/api",
330 expectedScheme: "https",
331 expectedBasePath: "https://origin.hostname.example.com:8080/api",
332 },
333 }
334
335 // use multiple schemes to demonstrate that the URL is based on the
336 // origin's scheme, not the :scheme header
337 for _, testScheme := range []string{"http", "https"} {
338 for _, testOrigin := range testOrigins {
339 assertion := func(testPath string) bool {
340 const expectedMethod = "POST"
341 const expectedHostname = "request.hostname.example.com"
342
343 h2 := []h2mux.Header{
344 {Name: ":method", Value: expectedMethod},
345 {Name: ":scheme", Value: testScheme},
346 {Name: ":authority", Value: expectedHostname},
347 {Name: ":path", Value: testPath},
348 {Name: RequestUserHeaders, Value: ""},
349 }
350 h1, err := http.NewRequest("GET", testOrigin.url, nil)
351 require.NoError(t, err)
352
353 err = H2RequestHeadersToH1Request(h2, h1)
354 return assert.NoError(t, err) &&
355 assert.Equal(t, expectedMethod, h1.Method) &&
356 assert.Equal(t, expectedHostname, h1.Host) &&
357 assert.Equal(t, testOrigin.expectedScheme, h1.URL.Scheme) &&
358 assert.Equal(t, testOrigin.expectedBasePath+testPath, h1.URL.String())
359 }
360 err := quick.Check(assertion, config)
361 assert.NoError(t, err)
362 }
363 }
364}
365
366func randomASCIIPrintableChar(rand *rand.Rand) int {
367 // smallest printable ASCII char is 32, largest is 126
368 const startPrintable = 32
369 const endPrintable = 127
370 return startPrintable + rand.Intn(endPrintable-startPrintable)
371}
372
373// randomASCIIText generates an ASCII string, some of whose characters may be
374// percent-encoded. Its "logical length" (ignoring percent-encoding) is
375// between 1 and `maxLength`.
376func randomASCIIText(rand *rand.Rand, minLength int, maxLength int) string {
377 length := minLength + rand.Intn(maxLength)
378 var result strings.Builder
379 for i := 0; i < length; i++ {
380 c := randomASCIIPrintableChar(rand)
381
382 // 1/4 chance of using percent encoding when not necessary
383 if c == '%' || rand.Intn(4) == 0 {
384 result.WriteString(fmt.Sprintf("%%%02X", c))
385 } else {
386 result.WriteByte(byte(c))
387 }
388 }
389 return result.String()
390}
391
392// Calls `randomASCIIText` and ensures the result is a valid URL path,
393// i.e. one that can pass unchanged through url.URL.String()
394func randomHTTP1Path(t *testing.T, rand *rand.Rand, minLength int, maxLength int) string {
395 text := randomASCIIText(rand, minLength, maxLength)
396 re, err := regexp.Compile("[^/;,]*")
397 require.NoError(t, err)
398 return "/" + re.ReplaceAllStringFunc(text, url.PathEscape)
399}
400
401// Calls `randomASCIIText` and ensures the result is a valid URL query,
402// i.e. one that can pass unchanged through url.URL.String()
403func randomHTTP1Query(rand *rand.Rand, minLength int, maxLength int) string {
404 text := randomASCIIText(rand, minLength, maxLength)
405 return "?" + strings.ReplaceAll(text, "#", "%23")
406}
407
408// Calls `randomASCIIText` and ensures the result is a valid URL fragment,
409// i.e. one that can pass unchanged through url.URL.String()
410func randomHTTP1Fragment(t *testing.T, rand *rand.Rand, minLength int, maxLength int) string {
411 text := randomASCIIText(rand, minLength, maxLength)
412 u, err := url.Parse("#" + text)
413 require.NoError(t, err)
414 return u.String()
415}
416
417// Assemble a random :path pseudoheader that is legal by Go stdlib standards
418// (i.e. all characters will satisfy "net/url".shouldEscape for their respective locations)
419func randomHTTP2Path(t *testing.T, rand *rand.Rand) string {
420 result := randomHTTP1Path(t, rand, 1, 64)
421 if rand.Intn(2) == 1 {
422 result += randomHTTP1Query(rand, 1, 32)
423 }
424 if rand.Intn(2) == 1 {
425 result += randomHTTP1Fragment(t, rand, 1, 16)
426 }
427 return result
428}
429
430func stdlibHeaderToH2muxHeader(headers http.Header) (h2muxHeaders []h2mux.Header) {
431 for name, values := range headers {
432 for _, value := range values {
433 h2muxHeaders = append(h2muxHeaders, h2mux.Header{Name: name, Value: value})
434 }
435 }
436
437 return h2muxHeaders
438}
439
440func TestSerializeHeaders(t *testing.T) {
441 request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
442 assert.NoError(t, err)
443
444 mockHeaders := http.Header{
445 "Mock-Header-One": {"Mock header one value", "three"},
446 "Mock-Header-Two-Long": {"Mock header two value\nlong"},
447 ":;": {":;", ";:"},
448 ":": {":"},
449 ";": {";"},
450 ";;": {";;"},
451 "Empty values": {"", ""},
452 "": {"Empty key"},
453 "control\tcharacter\b\n": {"value\n\b\t"},
454 ";\v:": {":\v;"},
455 }
456
457 for header, values := range mockHeaders {
458 for _, value := range values {
459 // Note that Golang's http library is opinionated;
460 // at this point every header name will be title-cased in order to comply with the HTTP RFC
461 // This means our proxy is not completely transparent when it comes to proxying headers
462 request.Header.Add(header, value)
463 }
464 }
465
466 serializedHeaders := SerializeHeaders(request.Header)
467
468 // Sanity check: the headers serialized to something that's not an empty string
469 assert.NotEqual(t, "", serializedHeaders)
470
471 // Deserialize back, and ensure we get the same set of headers
472 deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
473 assert.NoError(t, err)
474
475 assert.Equal(t, 13, len(deserializedHeaders))
476 h2muxExpectedHeaders := stdlibHeaderToH2muxHeader(mockHeaders)
477
478 sort.Sort(ByName(deserializedHeaders))
479 sort.Sort(ByName(h2muxExpectedHeaders))
480
481 assert.True(
482 t,
483 reflect.DeepEqual(h2muxExpectedHeaders, deserializedHeaders),
484 fmt.Sprintf("got = %#v, want = %#v\n", deserializedHeaders, h2muxExpectedHeaders),
485 )
486}
487
488func TestSerializeNoHeaders(t *testing.T) {
489 request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
490 assert.NoError(t, err)
491
492 serializedHeaders := SerializeHeaders(request.Header)
493 deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
494 assert.NoError(t, err)
495 assert.Equal(t, 0, len(deserializedHeaders))
496}
497
498func TestDeserializeMalformed(t *testing.T) {
499 var err error
500
501 malformedData := []string{
502 "malformed data",
503 "bW9jawo=", // "mock"
504 "bW9jawo=:ZGF0YQo=:bW9jawo=", // "mock:data:mock"
505 "::",
506 }
507
508 for _, malformedValue := range malformedData {
509 _, err = DeserializeHeaders(malformedValue)
510 assert.Error(t, err)
511 }
512}
513
514func TestParseRequestHeaders(t *testing.T) {
515 mockUserHeadersToSerialize := http.Header{
516 "Mock-Header-One": {"1", "1.5"},
517 "Mock-Header-Two": {"2"},
518 "Mock-Header-Three": {"3"},
519 }
520
521 mockHeaders := []h2mux.Header{
522 {Name: "One", Value: "1"}, // will be dropped
523 {Name: "Cf-Two", Value: "cf-value-1"},
524 {Name: "Cf-Two", Value: "cf-value-2"},
525 {Name: RequestUserHeaders, Value: SerializeHeaders(mockUserHeadersToSerialize)},
526 }
527
528 expectedHeaders := []h2mux.Header{
529 {Name: "Cf-Two", Value: "cf-value-1"},
530 {Name: "Cf-Two", Value: "cf-value-2"},
531 {Name: "Mock-Header-One", Value: "1"},
532 {Name: "Mock-Header-One", Value: "1.5"},
533 {Name: "Mock-Header-Two", Value: "2"},
534 {Name: "Mock-Header-Three", Value: "3"},
535 }
536 h1 := &http.Request{
537 Header: make(http.Header),
538 }
539 err := H2RequestHeadersToH1Request(mockHeaders, h1)
540 assert.NoError(t, err)
541 assert.ElementsMatch(t, expectedHeaders, stdlibHeaderToH2muxHeader(h1.Header))
542}
543
544func TestIsControlRequestHeader(t *testing.T) {
545 controlRequestHeaders := []string{
546 // Anything that begins with cf-
547 "cf-sample-header",
548
549 // Any http2 pseudoheader
550 ":sample-pseudo-header",
551
552 // content-length is a special case, it has to be there
553 // for some requests to work (per the HTTP2 spec)
554 "content-length",
555
556 // Websocket request headers
557 "connection",
558 "upgrade",
559 }
560
561 for _, header := range controlRequestHeaders {
562 assert.True(t, IsControlRequestHeader(header))
563 }
564}
565
566func TestIsControlResponseHeader(t *testing.T) {
567 controlResponseHeaders := []string{
568 // Anything that begins with cf-int- or cf-cloudflared-
569 "cf-int-sample-header",
570 "cf-cloudflared-sample-header",
571
572 // Any http2 pseudoheader
573 ":sample-pseudo-header",
574
575 // content-length is a special case, it has to be there
576 // for some requests to work (per the HTTP2 spec)
577 "content-length",
578 }
579
580 for _, header := range controlResponseHeaders {
581 assert.True(t, IsControlResponseHeader(header))
582 }
583}
584
585func TestIsNotControlRequestHeader(t *testing.T) {
586 notControlRequestHeaders := []string{
587 "mock-header",
588 "another-sample-header",
589 }
590
591 for _, header := range notControlRequestHeaders {
592 assert.False(t, IsControlRequestHeader(header))
593 }
594}
595
596func TestIsNotControlResponseHeader(t *testing.T) {
597 notControlResponseHeaders := []string{
598 "mock-header",
599 "another-sample-header",
600 "upgrade",
601 "connection",
602 "cf-whatever", // On the response path, we only want to filter cf-int- and cf-cloudflared-
603 }
604
605 for _, header := range notControlResponseHeaders {
606 assert.False(t, IsControlResponseHeader(header))
607 }
608}
609
610func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
611 mockHeaders := http.Header{
612 "User-header-one": {""},
613 "User-header-two": {"1", "2"},
614 "cf-header": {"cf-value"},
615 "cf-int-header": {"cf-int-value"},
616 "cf-cloudflared-header": {"cf-cloudflared-value"},
617 "Content-Length": {"123"},
618 }
619 mockResponse := http.Response{
620 StatusCode: 200,
621 Header: mockHeaders,
622 }
623
624 headers := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
625
626 serializedHeadersIndex := -1
627 for i, header := range headers {
628 if header.Name == ResponseUserHeaders {
629 serializedHeadersIndex = i
630 break
631 }
632 }
633 assert.NotEqual(t, -1, serializedHeadersIndex)
634 actualControlHeaders := append(
635 headers[:serializedHeadersIndex],
636 headers[serializedHeadersIndex+1:]...,
637 )
638 expectedControlHeaders := []h2mux.Header{
639 {Name: ":status", Value: "200"},
640 {Name: "content-length", Value: "123"},
641 }
642
643 assert.ElementsMatch(t, expectedControlHeaders, actualControlHeaders)
644
645 actualUserHeaders, err := DeserializeHeaders(headers[serializedHeadersIndex].Value)
646 expectedUserHeaders := []h2mux.Header{
647 {Name: "User-header-one", Value: ""},
648 {Name: "User-header-two", Value: "1"},
649 {Name: "User-header-two", Value: "2"},
650 {Name: "cf-header", Value: "cf-value"},
651 }
652 assert.NoError(t, err)
653 assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)
654}
655
656// The purpose of this test is to check that our code and the http.Header
657// implementation don't throw validation errors about header size
658func TestHeaderSize(t *testing.T) {
659 largeValue := randSeq(5 * 1024 * 1024) // 5Mb
660 largeHeaders := http.Header{
661 "User-header": {largeValue},
662 }
663 mockResponse := http.Response{
664 StatusCode: 200,
665 Header: largeHeaders,
666 }
667
668 serializedHeaders := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
669 request, err := http.NewRequest(http.MethodGet, "https://example.com/", nil)
670 assert.NoError(t, err)
671 for _, header := range serializedHeaders {
672 request.Header.Set(header.Name, header.Value)
673 }
674
675 for _, header := range serializedHeaders {
676 if header.Name != ResponseUserHeaders {
677 continue
678 }
679
680 deserializedHeaders, err := DeserializeHeaders(header.Value)
681 assert.NoError(t, err)
682 assert.Equal(t, largeValue, deserializedHeaders[0].Value)
683 }
684}
685
686func randSeq(n int) string {
687 randomizer := rand.New(rand.NewSource(17))
688 var letters = []rune(":;,+/=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
689 b := make([]rune, n)
690 for i := range b {
691 b[i] = letters[randomizer.Intn(len(letters))]
692 }
693 return string(b)
694}
695
696func BenchmarkH1ResponseToH2ResponseHeaders(b *testing.B) {
697 ser := "eC1mb3J3YXJkZWQtcHJvdG8:aHR0cHM;dXBncmFkZS1pbnNlY3VyZS1yZXF1ZXN0cw:MQ;YWNjZXB0LWxhbmd1YWdl:ZW4tVVMsZW47cT0wLjkscnU7cT0wLjg;YWNjZXB0LWVuY29kaW5n:Z3ppcA;eC1mb3J3YXJkZWQtZm9y:MTczLjI0NS42MC42;dXNlci1hZ2VudA:TW96aWxsYS81LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTBfMTRfNikgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzg0LjAuNDE0Ny44OSBTYWZhcmkvNTM3LjM2;c2VjLWZldGNoLW1vZGU:bmF2aWdhdGU;Y2RuLWxvb3A:Y2xvdWRmbGFyZQ;c2VjLWZldGNoLWRlc3Q:ZG9jdW1lbnQ;c2VjLWZldGNoLXVzZXI:PzE;c2VjLWZldGNoLXNpdGU:bm9uZQ;Y29va2ll:X19jZmR1aWQ9ZGNkOWZjOGNjNWMxMzE0NTMyYTFkMjhlZDEyOWRhOTYwMTU2OTk1MTYzNDsgX19jZl9ibT1mYzY2MzMzYzAzZmM0MWFiZTZmOWEyYzI2ZDUwOTA0YzIxYzZhMTQ2LTE1OTU2MjIzNDEtMTgwMC1BZTVzS2pIU2NiWGVFM05mMUhrTlNQMG1tMHBLc2pQWkloVnM1Z2g1SkNHQkFhS1UxVDB2b003alBGN3FjMHVSR2NjZGcrWHdhL1EzbTJhQzdDVU4xZ2M9;YWNjZXB0:dGV4dC9odG1sLGFwcGxpY2F0aW9uL3hodG1sK3htbCxhcHBsaWNhdGlvbi94bWw7cT0wLjksaW1hZ2Uvd2VicCxpbWFnZS9hcG5nLCovKjtxPTAuOCxhcHBsaWNhdGlvbi9zaWduZWQtZXhjaGFuZ2U7dj1iMztxPTAuOQ"
698 h2, _ := DeserializeHeaders(ser)
699 h1 := make(http.Header)
700 for _, header := range h2 {
701 h1.Add(header.Name, header.Value)
702 }
703 h1.Add("Content-Length", "200")
704 h1.Add("Cf-Something", "Else")
705 h1.Add("Upgrade", "websocket")
706
707 h1resp := &http.Response{
708 StatusCode: 200,
709 Header: h1,
710 }
711
712 b.ReportAllocs()
713 b.ResetTimer()
714 for i := 0; i < b.N; i++ {
715 _ = H1ResponseToH2ResponseHeaders(h1resp.StatusCode, h1resp.Header)
716 }
717}
718