cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2021.7.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

connection/header_test.go

677lines · modeblame

8ca0d86cIgor Postelnik5 years ago1package connection
6ea9b5c3Nick Vollmar6 years ago2
3import (
4"fmt"
5"math/rand"
6"net/http"
7"net/url"
8"reflect"
9"regexp"
6488843aAreg Harutyunyan6 years ago10"sort"
6ea9b5c3Nick Vollmar6 years ago11"strings"
12"testing"
13"testing/quick"
14
15"github.com/stretchr/testify/assert"
16"github.com/stretchr/testify/require"
8ca0d86cIgor Postelnik5 years ago17
18"github.com/cloudflare/cloudflared/h2mux"
6ea9b5c3Nick Vollmar6 years ago19)
20
8ca0d86cIgor Postelnik5 years ago21type ByName []h2mux.Header
6488843aAreg Harutyunyan6 years ago22
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 {
26if a[i].Name == a[j].Name {
27return a[i].Value < a[j].Value
28}
29
30return a[i].Name < a[j].Name
31}
32
6ea9b5c3Nick Vollmar6 years ago33func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) {
34request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
35assert.NoError(t, err)
36
6488843aAreg Harutyunyan6 years ago37mockHeaders := http.Header{
38"Mock header 1": {"Mock value 1"},
39"Mock header 2": {"Mock value 2"},
40}
6ea9b5c3Nick Vollmar6 years ago41
8ca0d86cIgor Postelnik5 years ago42headersConversionErr := H2RequestHeadersToH1Request(createSerializedHeaders(RequestUserHeaders, mockHeaders), request)
6ea9b5c3Nick Vollmar6 years ago43
6488843aAreg Harutyunyan6 years ago44assert.True(t, reflect.DeepEqual(mockHeaders, request.Header))
6ea9b5c3Nick Vollmar6 years ago45assert.NoError(t, headersConversionErr)
46}
47
8ca0d86cIgor Postelnik5 years ago48func createSerializedHeaders(headersField string, headers http.Header) []h2mux.Header {
49return []h2mux.Header{{
50Name: headersField,
51Value: SerializeHeaders(headers),
44e3be2cIgor Postelnik6 years ago52}}
53}
54
6ea9b5c3Nick Vollmar6 years ago55func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) {
56request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
57assert.NoError(t, err)
58
8ca0d86cIgor Postelnik5 years ago59emptyHeaders := make(http.Header)
6488843aAreg Harutyunyan6 years ago60headersConversionErr := H2RequestHeadersToH1Request(
8ca0d86cIgor Postelnik5 years ago61[]h2mux.Header{{
62Name: RequestUserHeaders,
63Value: SerializeHeaders(emptyHeaders),
6488843aAreg Harutyunyan6 years ago64}},
6ea9b5c3Nick Vollmar6 years ago65request,
66)
67
8ca0d86cIgor Postelnik5 years ago68assert.True(t, reflect.DeepEqual(emptyHeaders, request.Header))
6ea9b5c3Nick Vollmar6 years ago69assert.NoError(t, headersConversionErr)
70}
71
72func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) {
73request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
74assert.NoError(t, err)
75
8ca0d86cIgor Postelnik5 years ago76mockRequestHeaders := []h2mux.Header{
6488843aAreg Harutyunyan6 years ago77{Name: ":path", Value: "//bad_path/"},
8ca0d86cIgor Postelnik5 years ago78{Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
6488843aAreg Harutyunyan6 years ago79}
80
81headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
6ea9b5c3Nick Vollmar6 years ago82
83assert.Equal(t, http.Header{
84"Mock header": []string{"Mock value"},
85}, request.Header)
86
87assert.Equal(t, "http://example.com//bad_path/", request.URL.String())
88
89assert.NoError(t, headersConversionErr)
90}
91
92func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) {
93request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
94assert.NoError(t, err)
95
8ca0d86cIgor Postelnik5 years ago96mockRequestHeaders := []h2mux.Header{
6488843aAreg Harutyunyan6 years ago97{Name: ":path", Value: "/?query=mock%20value"},
8ca0d86cIgor Postelnik5 years ago98{Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
6488843aAreg Harutyunyan6 years ago99}
100
101headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
6ea9b5c3Nick Vollmar6 years ago102
103assert.Equal(t, http.Header{
104"Mock header": []string{"Mock value"},
105}, request.Header)
106
107assert.Equal(t, "http://example.com/?query=mock%20value", request.URL.String())
108
109assert.NoError(t, headersConversionErr)
110}
111
112func TestH2RequestHeadersToH1Request_HostPathWithURLEncoding(t *testing.T) {
113request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
114assert.NoError(t, err)
115
8ca0d86cIgor Postelnik5 years ago116mockRequestHeaders := []h2mux.Header{
6488843aAreg Harutyunyan6 years ago117{Name: ":path", Value: "/mock%20path"},
8ca0d86cIgor Postelnik5 years ago118{Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
6488843aAreg Harutyunyan6 years ago119}
120
121headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
6ea9b5c3Nick Vollmar6 years ago122
123assert.Equal(t, http.Header{
124"Mock header": []string{"Mock value"},
125}, request.Header)
126
127assert.Equal(t, "http://example.com/mock%20path", request.URL.String())
128
129assert.NoError(t, headersConversionErr)
130}
131
132func TestH2RequestHeadersToH1Request_WeirdURLs(t *testing.T) {
133type testCase struct {
134path string
135want string
136}
137testCases := []testCase{
138{
139path: "",
140want: "",
141},
142{
143path: "/",
144want: "/",
145},
146{
147path: "//",
148want: "//",
149},
150{
151path: "/test",
152want: "/test",
153},
154{
155path: "//test",
156want: "//test",
157},
158{
159// https://github.com/cloudflare/cloudflared/issues/81
160path: "//test/",
161want: "//test/",
162},
163{
164path: "/%2Ftest",
165want: "/%2Ftest",
166},
167{
168path: "//%20test",
169want: "//%20test",
170},
171{
172// https://github.com/cloudflare/cloudflared/issues/124
173path: "/test?get=somthing%20a",
174want: "/test?get=somthing%20a",
175},
176{
177path: "/%20",
178want: "/%20",
179},
180{
181// stdlib's EscapedPath() will always percent-encode ' '
182path: "/ ",
183want: "/%20",
184},
185{
186path: "/ a ",
187want: "/%20a%20",
188},
189{
190path: "/a%20b",
191want: "/a%20b",
192},
193{
194path: "/foo/bar;param?query#frag",
195want: "/foo/bar;param?query#frag",
196},
197{
198// stdlib's EscapedPath() will always percent-encode non-ASCII chars
199path: "/a␠b",
200want: "/a%E2%90%A0b",
201},
202{
203path: "/a-umlaut-ä",
204want: "/a-umlaut-%C3%A4",
205},
206{
207path: "/a-umlaut-%C3%A4",
208want: "/a-umlaut-%C3%A4",
209},
210{
211path: "/a-umlaut-%c3%a4",
212want: "/a-umlaut-%c3%a4",
213},
214{
215// here the second '#' is treated as part of the fragment
216path: "/a#b#c",
217want: "/a#b%23c",
218},
219{
220path: "/a#b␠c",
221want: "/a#b%E2%90%A0c",
222},
223{
224path: "/a#b%20c",
225want: "/a#b%20c",
226},
227{
228path: "/a#b c",
229want: "/a#b%20c",
230},
231{
232// stdlib's EscapedPath() will always percent-encode '\'
233path: "/\\",
234want: "/%5C",
235},
236{
237path: "/a\\",
238want: "/a%5C",
239},
240{
241path: "/a,b.c.",
242want: "/a,b.c.",
243},
244{
245path: "/.",
246want: "/.",
247},
248{
249// stdlib's EscapedPath() will always percent-encode '`'
250path: "/a`",
251want: "/a%60",
252},
253{
254path: "/a[0]",
255want: "/a[0]",
256},
257{
258path: "/?a[0]=5 &b[]=",
259want: "/?a[0]=5 &b[]=",
260},
261{
262path: "/?a=%22b%20%22",
263want: "/?a=%22b%20%22",
264},
265}
266
267for index, testCase := range testCases {
268requestURL := "https://example.com"
269
270request, err := http.NewRequest(http.MethodGet, requestURL, nil)
271assert.NoError(t, err)
6488843aAreg Harutyunyan6 years ago272
8ca0d86cIgor Postelnik5 years ago273mockRequestHeaders := []h2mux.Header{
6488843aAreg Harutyunyan6 years ago274{Name: ":path", Value: testCase.path},
8ca0d86cIgor Postelnik5 years ago275{Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
6488843aAreg Harutyunyan6 years ago276}
277
278headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
6ea9b5c3Nick Vollmar6 years ago279assert.NoError(t, headersConversionErr)
280
281assert.Equal(t,
282http.Header{
283"Mock header": []string{"Mock value"},
284},
285request.Header)
286
287assert.Equal(t,
288"https://example.com"+testCase.want,
289request.URL.String(),
290"Failed URL index: %v %#v", index, testCase)
291}
292}
293
294func TestH2RequestHeadersToH1Request_QuickCheck(t *testing.T) {
295config := &quick.Config{
296Values: func(args []reflect.Value, rand *rand.Rand) {
297args[0] = reflect.ValueOf(randomHTTP2Path(t, rand))
298},
299}
300
301type testOrigin struct {
302url string
303
304expectedScheme string
305expectedBasePath string
306}
307testOrigins := []testOrigin{
308{
309url: "http://origin.hostname.example.com:8080",
310expectedScheme: "http",
311expectedBasePath: "http://origin.hostname.example.com:8080",
312},
313{
314url: "http://origin.hostname.example.com:8080/",
315expectedScheme: "http",
316expectedBasePath: "http://origin.hostname.example.com:8080",
317},
318{
319url: "http://origin.hostname.example.com:8080/api",
320expectedScheme: "http",
321expectedBasePath: "http://origin.hostname.example.com:8080/api",
322},
323{
324url: "http://origin.hostname.example.com:8080/api/",
325expectedScheme: "http",
326expectedBasePath: "http://origin.hostname.example.com:8080/api",
327},
328{
329url: "https://origin.hostname.example.com:8080/api",
330expectedScheme: "https",
331expectedBasePath: "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
337for _, testScheme := range []string{"http", "https"} {
338for _, testOrigin := range testOrigins {
339assertion := func(testPath string) bool {
340const expectedMethod = "POST"
341const expectedHostname = "request.hostname.example.com"
342
8ca0d86cIgor Postelnik5 years ago343h2 := []h2mux.Header{
d6c2c4eeAreg Harutyunyan6 years ago344{Name: ":method", Value: expectedMethod},
345{Name: ":scheme", Value: testScheme},
346{Name: ":authority", Value: expectedHostname},
347{Name: ":path", Value: testPath},
8ca0d86cIgor Postelnik5 years ago348{Name: RequestUserHeaders, Value: ""},
6ea9b5c3Nick Vollmar6 years ago349}
350h1, err := http.NewRequest("GET", testOrigin.url, nil)
351require.NoError(t, err)
352
6488843aAreg Harutyunyan6 years ago353err = H2RequestHeadersToH1Request(h2, h1)
6ea9b5c3Nick Vollmar6 years ago354return assert.NoError(t, err) &&
355assert.Equal(t, expectedMethod, h1.Method) &&
356assert.Equal(t, expectedHostname, h1.Host) &&
357assert.Equal(t, testOrigin.expectedScheme, h1.URL.Scheme) &&
358assert.Equal(t, testOrigin.expectedBasePath+testPath, h1.URL.String())
359}
360err := quick.Check(assertion, config)
361assert.NoError(t, err)
362}
363}
364}
365
366func randomASCIIPrintableChar(rand *rand.Rand) int {
367// smallest printable ASCII char is 32, largest is 126
368const startPrintable = 32
369const endPrintable = 127
370return 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 {
377length := minLength + rand.Intn(maxLength)
1a968891Adam Chalmers5 years ago378var result strings.Builder
6ea9b5c3Nick Vollmar6 years ago379for i := 0; i < length; i++ {
380c := randomASCIIPrintableChar(rand)
381
382// 1/4 chance of using percent encoding when not necessary
383if c == '%' || rand.Intn(4) == 0 {
1a968891Adam Chalmers5 years ago384result.WriteString(fmt.Sprintf("%%%02X", c))
6ea9b5c3Nick Vollmar6 years ago385} else {
1a968891Adam Chalmers5 years ago386result.WriteByte(byte(c))
6ea9b5c3Nick Vollmar6 years ago387}
388}
1a968891Adam Chalmers5 years ago389return result.String()
6ea9b5c3Nick Vollmar6 years ago390}
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 {
395text := randomASCIIText(rand, minLength, maxLength)
d6c2c4eeAreg Harutyunyan6 years ago396re, err := regexp.Compile("[^/;,]*")
6ea9b5c3Nick Vollmar6 years ago397require.NoError(t, err)
d6c2c4eeAreg Harutyunyan6 years ago398return "/" + re.ReplaceAllStringFunc(text, url.PathEscape)
6ea9b5c3Nick Vollmar6 years ago399}
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()
d6c2c4eeAreg Harutyunyan6 years ago403func randomHTTP1Query(rand *rand.Rand, minLength int, maxLength int) string {
6ea9b5c3Nick Vollmar6 years ago404text := randomASCIIText(rand, minLength, maxLength)
405return "?" + 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 {
411text := randomASCIIText(rand, minLength, maxLength)
d6c2c4eeAreg Harutyunyan6 years ago412u, err := url.Parse("#" + text)
6ea9b5c3Nick Vollmar6 years ago413require.NoError(t, err)
d6c2c4eeAreg Harutyunyan6 years ago414return u.String()
6ea9b5c3Nick Vollmar6 years ago415}
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 {
420result := randomHTTP1Path(t, rand, 1, 64)
421if rand.Intn(2) == 1 {
d6c2c4eeAreg Harutyunyan6 years ago422result += randomHTTP1Query(rand, 1, 32)
6ea9b5c3Nick Vollmar6 years ago423}
424if rand.Intn(2) == 1 {
425result += randomHTTP1Fragment(t, rand, 1, 16)
426}
427return result
428}
d6c2c4eeAreg Harutyunyan6 years ago429
8ca0d86cIgor Postelnik5 years ago430func stdlibHeaderToH2muxHeader(headers http.Header) (h2muxHeaders []h2mux.Header) {
6488843aAreg Harutyunyan6 years ago431for name, values := range headers {
432for _, value := range values {
8ca0d86cIgor Postelnik5 years ago433h2muxHeaders = append(h2muxHeaders, h2mux.Header{Name: name, Value: value})
6488843aAreg Harutyunyan6 years ago434}
435}
436
437return h2muxHeaders
438}
439
d6c2c4eeAreg Harutyunyan6 years ago440func TestSerializeHeaders(t *testing.T) {
441request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
442assert.NoError(t, err)
443
6488843aAreg Harutyunyan6 years ago444mockHeaders := http.Header{
d6c2c4eeAreg Harutyunyan6 years ago445"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
457for header, values := range mockHeaders {
458for _, 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
462request.Header.Add(header, value)
463}
464}
465
6488843aAreg Harutyunyan6 years ago466serializedHeaders := SerializeHeaders(request.Header)
d6c2c4eeAreg Harutyunyan6 years ago467
468// Sanity check: the headers serialized to something that's not an empty string
469assert.NotEqual(t, "", serializedHeaders)
470
471// Deserialize back, and ensure we get the same set of headers
472deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
473assert.NoError(t, err)
474
6488843aAreg Harutyunyan6 years ago475assert.Equal(t, 13, len(deserializedHeaders))
476h2muxExpectedHeaders := stdlibHeaderToH2muxHeader(mockHeaders)
477
478sort.Sort(ByName(deserializedHeaders))
479sort.Sort(ByName(h2muxExpectedHeaders))
480
481assert.True(
482t,
483reflect.DeepEqual(h2muxExpectedHeaders, deserializedHeaders),
484fmt.Sprintf("got = %#v, want = %#v\n", deserializedHeaders, h2muxExpectedHeaders),
485)
d6c2c4eeAreg Harutyunyan6 years ago486}
487
488func TestSerializeNoHeaders(t *testing.T) {
489request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
490assert.NoError(t, err)
491
6488843aAreg Harutyunyan6 years ago492serializedHeaders := SerializeHeaders(request.Header)
d6c2c4eeAreg Harutyunyan6 years ago493deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
494assert.NoError(t, err)
495assert.Equal(t, 0, len(deserializedHeaders))
496}
497
498func TestDeserializeMalformed(t *testing.T) {
499var err error
500
501malformedData := []string{
502"malformed data",
503"bW9jawo=", // "mock"
504"bW9jawo=:ZGF0YQo=:bW9jawo=", // "mock:data:mock"
505"::",
506}
507
508for _, malformedValue := range malformedData {
6488843aAreg Harutyunyan6 years ago509_, err = DeserializeHeaders(malformedValue)
d6c2c4eeAreg Harutyunyan6 years ago510assert.Error(t, err)
511}
512}
6488843aAreg Harutyunyan6 years ago513
514func TestParseHeaders(t *testing.T) {
515mockUserHeadersToSerialize := http.Header{
516"Mock-Header-One": {"1", "1.5"},
517"Mock-Header-Two": {"2"},
518"Mock-Header-Three": {"3"},
519}
520
8ca0d86cIgor Postelnik5 years ago521mockHeaders := []h2mux.Header{
44e3be2cIgor Postelnik6 years ago522{Name: "One", Value: "1"}, // will be dropped
6488843aAreg Harutyunyan6 years ago523{Name: "Cf-Two", Value: "cf-value-1"},
524{Name: "Cf-Two", Value: "cf-value-2"},
8ca0d86cIgor Postelnik5 years ago525{Name: RequestUserHeaders, Value: SerializeHeaders(mockUserHeadersToSerialize)},
6488843aAreg Harutyunyan6 years ago526}
527
8ca0d86cIgor Postelnik5 years ago528expectedHeaders := []h2mux.Header{
44e3be2cIgor Postelnik6 years ago529{Name: "Cf-Two", Value: "cf-value-1"},
530{Name: "Cf-Two", Value: "cf-value-2"},
6488843aAreg Harutyunyan6 years ago531{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}
44e3be2cIgor Postelnik6 years ago536h1 := &http.Request{
537Header: make(http.Header),
6488843aAreg Harutyunyan6 years ago538}
44e3be2cIgor Postelnik6 years ago539err := H2RequestHeadersToH1Request(mockHeaders, h1)
540assert.NoError(t, err)
541assert.ElementsMatch(t, expectedHeaders, stdlibHeaderToH2muxHeader(h1.Header))
6488843aAreg Harutyunyan6 years ago542}
543
544func TestIsControlHeader(t *testing.T) {
545controlHeaders := []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
557for _, header := range controlHeaders {
558assert.True(t, IsControlHeader(header))
559}
560}
561
562func TestIsNotControlHeader(t *testing.T) {
563notControlHeaders := []string{
44e3be2cIgor Postelnik6 years ago564"mock-header",
565"another-sample-header",
6488843aAreg Harutyunyan6 years ago566}
567
568for _, header := range notControlHeaders {
569assert.False(t, IsControlHeader(header))
570}
571}
572
573func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
574mockHeaders := http.Header{
575"User-header-one": {""},
576"User-header-two": {"1", "2"},
577"cf-header": {"cf-value"},
578"Content-Length": {"123"},
579}
580mockResponse := http.Response{
581StatusCode: 200,
582Header: mockHeaders,
583}
584
e2262085cthuang5 years ago585headers := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
6488843aAreg Harutyunyan6 years ago586
587serializedHeadersIndex := -1
588for i, header := range headers {
8ca0d86cIgor Postelnik5 years ago589if header.Name == ResponseUserHeaders {
6488843aAreg Harutyunyan6 years ago590serializedHeadersIndex = i
591break
592}
593}
594assert.NotEqual(t, -1, serializedHeadersIndex)
595actualControlHeaders := append(
596headers[:serializedHeadersIndex],
597headers[serializedHeadersIndex+1:]...,
598)
8ca0d86cIgor Postelnik5 years ago599expectedControlHeaders := []h2mux.Header{
6488843aAreg Harutyunyan6 years ago600{Name: ":status", Value: "200"},
601{Name: "content-length", Value: "123"},
602}
603
604assert.ElementsMatch(t, expectedControlHeaders, actualControlHeaders)
605
606actualUserHeaders, err := DeserializeHeaders(headers[serializedHeadersIndex].Value)
8ca0d86cIgor Postelnik5 years ago607expectedUserHeaders := []h2mux.Header{
6488843aAreg Harutyunyan6 years ago608{Name: "User-header-one", Value: ""},
609{Name: "User-header-two", Value: "1"},
610{Name: "User-header-two", Value: "2"},
611}
612assert.NoError(t, err)
613assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)
614}
a14aa032Areg Harutyunyan6 years ago615
616// The purpose of this test is to check that our code and the http.Header
617// implementation don't throw validation errors about header size
618func TestHeaderSize(t *testing.T) {
619largeValue := randSeq(5 * 1024 * 1024) // 5Mb
620largeHeaders := http.Header{
621"User-header": {largeValue},
622}
623mockResponse := http.Response{
624StatusCode: 200,
625Header: largeHeaders,
626}
627
e2262085cthuang5 years ago628serializedHeaders := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
a14aa032Areg Harutyunyan6 years ago629request, err := http.NewRequest(http.MethodGet, "https://example.com/", nil)
630assert.NoError(t, err)
631for _, header := range serializedHeaders {
632request.Header.Set(header.Name, header.Value)
633}
634
635for _, header := range serializedHeaders {
8ca0d86cIgor Postelnik5 years ago636if header.Name != ResponseUserHeaders {
a14aa032Areg Harutyunyan6 years ago637continue
638}
639
640deserializedHeaders, err := DeserializeHeaders(header.Value)
641assert.NoError(t, err)
642assert.Equal(t, largeValue, deserializedHeaders[0].Value)
643}
644}
645
646func randSeq(n int) string {
647randomizer := rand.New(rand.NewSource(17))
648var letters = []rune(":;,+/=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
649b := make([]rune, n)
650for i := range b {
651b[i] = letters[randomizer.Intn(len(letters))]
652}
653return string(b)
654}
61d54611Igor Postelnik6 years ago655
656func BenchmarkH1ResponseToH2ResponseHeaders(b *testing.B) {
657ser := "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"
658h2, _ := DeserializeHeaders(ser)
659h1 := make(http.Header)
660for _, header := range h2 {
661h1.Add(header.Name, header.Value)
662}
663h1.Add("Content-Length", "200")
664h1.Add("Cf-Something", "Else")
665h1.Add("Upgrade", "websocket")
666
667h1resp := &http.Response{
668StatusCode: 200,
1a968891Adam Chalmers5 years ago669Header: h1,
61d54611Igor Postelnik6 years ago670}
671
672b.ReportAllocs()
673b.ResetTimer()
674for i := 0; i < b.N; i++ {
e2262085cthuang5 years ago675_ = H1ResponseToH2ResponseHeaders(h1resp.StatusCode, h1resp.Header)
61d54611Igor Postelnik6 years ago676}
677}