h2mux_header_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. package connection
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "net/http"
  6. "net/url"
  7. "reflect"
  8. "regexp"
  9. "strings"
  10. "testing"
  11. "testing/quick"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. "github.com/cloudflare/cloudflared/h2mux"
  15. )
  16. type ByName []h2mux.Header
  17. func (a ByName) Len() int { return len(a) }
  18. func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  19. func (a ByName) Less(i, j int) bool {
  20. if a[i].Name == a[j].Name {
  21. return a[i].Value < a[j].Value
  22. }
  23. return a[i].Name < a[j].Name
  24. }
  25. func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) {
  26. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  27. assert.NoError(t, err)
  28. mockHeaders := http.Header{
  29. "Mock header 1": {"Mock value 1"},
  30. "Mock header 2": {"Mock value 2"},
  31. }
  32. headersConversionErr := H2RequestHeadersToH1Request(createSerializedHeaders(RequestUserHeaders, mockHeaders), request)
  33. assert.True(t, reflect.DeepEqual(mockHeaders, request.Header))
  34. assert.NoError(t, headersConversionErr)
  35. }
  36. func createSerializedHeaders(headersField string, headers http.Header) []h2mux.Header {
  37. return []h2mux.Header{{
  38. Name: headersField,
  39. Value: SerializeHeaders(headers),
  40. }}
  41. }
  42. func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) {
  43. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  44. assert.NoError(t, err)
  45. emptyHeaders := make(http.Header)
  46. headersConversionErr := H2RequestHeadersToH1Request(
  47. []h2mux.Header{{
  48. Name: RequestUserHeaders,
  49. Value: SerializeHeaders(emptyHeaders),
  50. }},
  51. request,
  52. )
  53. assert.True(t, reflect.DeepEqual(emptyHeaders, request.Header))
  54. assert.NoError(t, headersConversionErr)
  55. }
  56. func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) {
  57. request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
  58. assert.NoError(t, err)
  59. mockRequestHeaders := []h2mux.Header{
  60. {Name: ":path", Value: "//bad_path/"},
  61. {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  62. }
  63. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  64. assert.Equal(t, http.Header{
  65. "Mock header": []string{"Mock value"},
  66. }, request.Header)
  67. assert.Equal(t, "http://example.com//bad_path/", request.URL.String())
  68. assert.NoError(t, headersConversionErr)
  69. }
  70. func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) {
  71. request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
  72. assert.NoError(t, err)
  73. mockRequestHeaders := []h2mux.Header{
  74. {Name: ":path", Value: "/?query=mock%20value"},
  75. {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  76. }
  77. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  78. assert.Equal(t, http.Header{
  79. "Mock header": []string{"Mock value"},
  80. }, request.Header)
  81. assert.Equal(t, "http://example.com/?query=mock%20value", request.URL.String())
  82. assert.NoError(t, headersConversionErr)
  83. }
  84. func TestH2RequestHeadersToH1Request_HostPathWithURLEncoding(t *testing.T) {
  85. request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
  86. assert.NoError(t, err)
  87. mockRequestHeaders := []h2mux.Header{
  88. {Name: ":path", Value: "/mock%20path"},
  89. {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  90. }
  91. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  92. assert.Equal(t, http.Header{
  93. "Mock header": []string{"Mock value"},
  94. }, request.Header)
  95. assert.Equal(t, "http://example.com/mock%20path", request.URL.String())
  96. assert.NoError(t, headersConversionErr)
  97. }
  98. func TestH2RequestHeadersToH1Request_WeirdURLs(t *testing.T) {
  99. type testCase struct {
  100. path string
  101. want string
  102. }
  103. testCases := []testCase{
  104. {
  105. path: "",
  106. want: "",
  107. },
  108. {
  109. path: "/",
  110. want: "/",
  111. },
  112. {
  113. path: "//",
  114. want: "//",
  115. },
  116. {
  117. path: "/test",
  118. want: "/test",
  119. },
  120. {
  121. path: "//test",
  122. want: "//test",
  123. },
  124. {
  125. // https://github.com/cloudflare/cloudflared/issues/81
  126. path: "//test/",
  127. want: "//test/",
  128. },
  129. {
  130. path: "/%2Ftest",
  131. want: "/%2Ftest",
  132. },
  133. {
  134. path: "//%20test",
  135. want: "//%20test",
  136. },
  137. {
  138. // https://github.com/cloudflare/cloudflared/issues/124
  139. path: "/test?get=somthing%20a",
  140. want: "/test?get=somthing%20a",
  141. },
  142. {
  143. path: "/%20",
  144. want: "/%20",
  145. },
  146. {
  147. // stdlib's EscapedPath() will always percent-encode ' '
  148. path: "/ ",
  149. want: "/%20",
  150. },
  151. {
  152. path: "/ a ",
  153. want: "/%20a%20",
  154. },
  155. {
  156. path: "/a%20b",
  157. want: "/a%20b",
  158. },
  159. {
  160. path: "/foo/bar;param?query#frag",
  161. want: "/foo/bar;param?query#frag",
  162. },
  163. {
  164. // stdlib's EscapedPath() will always percent-encode non-ASCII chars
  165. path: "/a␠b",
  166. want: "/a%E2%90%A0b",
  167. },
  168. {
  169. path: "/a-umlaut-ä",
  170. want: "/a-umlaut-%C3%A4",
  171. },
  172. {
  173. path: "/a-umlaut-%C3%A4",
  174. want: "/a-umlaut-%C3%A4",
  175. },
  176. {
  177. path: "/a-umlaut-%c3%a4",
  178. want: "/a-umlaut-%c3%a4",
  179. },
  180. {
  181. // here the second '#' is treated as part of the fragment
  182. path: "/a#b#c",
  183. want: "/a#b%23c",
  184. },
  185. {
  186. path: "/a#b␠c",
  187. want: "/a#b%E2%90%A0c",
  188. },
  189. {
  190. path: "/a#b%20c",
  191. want: "/a#b%20c",
  192. },
  193. {
  194. path: "/a#b c",
  195. want: "/a#b%20c",
  196. },
  197. {
  198. // stdlib's EscapedPath() will always percent-encode '\'
  199. path: "/\\",
  200. want: "/%5C",
  201. },
  202. {
  203. path: "/a\\",
  204. want: "/a%5C",
  205. },
  206. {
  207. path: "/a,b.c.",
  208. want: "/a,b.c.",
  209. },
  210. {
  211. path: "/.",
  212. want: "/.",
  213. },
  214. {
  215. // stdlib's EscapedPath() will always percent-encode '`'
  216. path: "/a`",
  217. want: "/a%60",
  218. },
  219. {
  220. path: "/a[0]",
  221. want: "/a[0]",
  222. },
  223. {
  224. path: "/?a[0]=5 &b[]=",
  225. want: "/?a[0]=5 &b[]=",
  226. },
  227. {
  228. path: "/?a=%22b%20%22",
  229. want: "/?a=%22b%20%22",
  230. },
  231. }
  232. for index, testCase := range testCases {
  233. requestURL := "https://example.com"
  234. request, err := http.NewRequest(http.MethodGet, requestURL, nil)
  235. assert.NoError(t, err)
  236. mockRequestHeaders := []h2mux.Header{
  237. {Name: ":path", Value: testCase.path},
  238. {Name: RequestUserHeaders, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
  239. }
  240. headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
  241. assert.NoError(t, headersConversionErr)
  242. assert.Equal(t,
  243. http.Header{
  244. "Mock header": []string{"Mock value"},
  245. },
  246. request.Header)
  247. assert.Equal(t,
  248. "https://example.com"+testCase.want,
  249. request.URL.String(),
  250. "Failed URL index: %v %#v", index, testCase)
  251. }
  252. }
  253. func TestH2RequestHeadersToH1Request_QuickCheck(t *testing.T) {
  254. config := &quick.Config{
  255. Values: func(args []reflect.Value, rand *rand.Rand) {
  256. args[0] = reflect.ValueOf(randomHTTP2Path(t, rand))
  257. },
  258. }
  259. type testOrigin struct {
  260. url string
  261. expectedScheme string
  262. expectedBasePath string
  263. }
  264. testOrigins := []testOrigin{
  265. {
  266. url: "http://origin.hostname.example.com:8080",
  267. expectedScheme: "http",
  268. expectedBasePath: "http://origin.hostname.example.com:8080",
  269. },
  270. {
  271. url: "http://origin.hostname.example.com:8080/",
  272. expectedScheme: "http",
  273. expectedBasePath: "http://origin.hostname.example.com:8080",
  274. },
  275. {
  276. url: "http://origin.hostname.example.com:8080/api",
  277. expectedScheme: "http",
  278. expectedBasePath: "http://origin.hostname.example.com:8080/api",
  279. },
  280. {
  281. url: "http://origin.hostname.example.com:8080/api/",
  282. expectedScheme: "http",
  283. expectedBasePath: "http://origin.hostname.example.com:8080/api",
  284. },
  285. {
  286. url: "https://origin.hostname.example.com:8080/api",
  287. expectedScheme: "https",
  288. expectedBasePath: "https://origin.hostname.example.com:8080/api",
  289. },
  290. }
  291. // use multiple schemes to demonstrate that the URL is based on the
  292. // origin's scheme, not the :scheme header
  293. for _, testScheme := range []string{"http", "https"} {
  294. for _, testOrigin := range testOrigins {
  295. assertion := func(testPath string) bool {
  296. const expectedMethod = "POST"
  297. const expectedHostname = "request.hostname.example.com"
  298. h2 := []h2mux.Header{
  299. {Name: ":method", Value: expectedMethod},
  300. {Name: ":scheme", Value: testScheme},
  301. {Name: ":authority", Value: expectedHostname},
  302. {Name: ":path", Value: testPath},
  303. {Name: RequestUserHeaders, Value: ""},
  304. }
  305. h1, err := http.NewRequest("GET", testOrigin.url, nil)
  306. require.NoError(t, err)
  307. err = H2RequestHeadersToH1Request(h2, h1)
  308. return assert.NoError(t, err) &&
  309. assert.Equal(t, expectedMethod, h1.Method) &&
  310. assert.Equal(t, expectedHostname, h1.Host) &&
  311. assert.Equal(t, testOrigin.expectedScheme, h1.URL.Scheme) &&
  312. assert.Equal(t, testOrigin.expectedBasePath+testPath, h1.URL.String())
  313. }
  314. err := quick.Check(assertion, config)
  315. assert.NoError(t, err)
  316. }
  317. }
  318. }
  319. func randomASCIIPrintableChar(rand *rand.Rand) int {
  320. // smallest printable ASCII char is 32, largest is 126
  321. const startPrintable = 32
  322. const endPrintable = 127
  323. return startPrintable + rand.Intn(endPrintable-startPrintable)
  324. }
  325. // randomASCIIText generates an ASCII string, some of whose characters may be
  326. // percent-encoded. Its "logical length" (ignoring percent-encoding) is
  327. // between 1 and `maxLength`.
  328. func randomASCIIText(rand *rand.Rand, minLength int, maxLength int) string {
  329. length := minLength + rand.Intn(maxLength)
  330. var result strings.Builder
  331. for i := 0; i < length; i++ {
  332. c := randomASCIIPrintableChar(rand)
  333. // 1/4 chance of using percent encoding when not necessary
  334. if c == '%' || rand.Intn(4) == 0 {
  335. result.WriteString(fmt.Sprintf("%%%02X", c))
  336. } else {
  337. result.WriteByte(byte(c))
  338. }
  339. }
  340. return result.String()
  341. }
  342. // Calls `randomASCIIText` and ensures the result is a valid URL path,
  343. // i.e. one that can pass unchanged through url.URL.String()
  344. func randomHTTP1Path(t *testing.T, rand *rand.Rand, minLength int, maxLength int) string {
  345. text := randomASCIIText(rand, minLength, maxLength)
  346. re, err := regexp.Compile("[^/;,]*")
  347. require.NoError(t, err)
  348. return "/" + re.ReplaceAllStringFunc(text, url.PathEscape)
  349. }
  350. // Calls `randomASCIIText` and ensures the result is a valid URL query,
  351. // i.e. one that can pass unchanged through url.URL.String()
  352. func randomHTTP1Query(rand *rand.Rand, minLength int, maxLength int) string {
  353. text := randomASCIIText(rand, minLength, maxLength)
  354. return "?" + strings.ReplaceAll(text, "#", "%23")
  355. }
  356. // Calls `randomASCIIText` and ensures the result is a valid URL fragment,
  357. // i.e. one that can pass unchanged through url.URL.String()
  358. func randomHTTP1Fragment(t *testing.T, rand *rand.Rand, minLength int, maxLength int) string {
  359. text := randomASCIIText(rand, minLength, maxLength)
  360. u, err := url.Parse("#" + text)
  361. require.NoError(t, err)
  362. return u.String()
  363. }
  364. // Assemble a random :path pseudoheader that is legal by Go stdlib standards
  365. // (i.e. all characters will satisfy "net/url".shouldEscape for their respective locations)
  366. func randomHTTP2Path(t *testing.T, rand *rand.Rand) string {
  367. result := randomHTTP1Path(t, rand, 1, 64)
  368. if rand.Intn(2) == 1 {
  369. result += randomHTTP1Query(rand, 1, 32)
  370. }
  371. if rand.Intn(2) == 1 {
  372. result += randomHTTP1Fragment(t, rand, 1, 16)
  373. }
  374. return result
  375. }
  376. func stdlibHeaderToH2muxHeader(headers http.Header) (h2muxHeaders []h2mux.Header) {
  377. for name, values := range headers {
  378. for _, value := range values {
  379. h2muxHeaders = append(h2muxHeaders, h2mux.Header{Name: name, Value: value})
  380. }
  381. }
  382. return h2muxHeaders
  383. }
  384. func TestParseRequestHeaders(t *testing.T) {
  385. mockUserHeadersToSerialize := http.Header{
  386. "Mock-Header-One": {"1", "1.5"},
  387. "Mock-Header-Two": {"2"},
  388. "Mock-Header-Three": {"3"},
  389. }
  390. mockHeaders := []h2mux.Header{
  391. {Name: "One", Value: "1"}, // will be dropped
  392. {Name: "Cf-Two", Value: "cf-value-1"},
  393. {Name: "Cf-Two", Value: "cf-value-2"},
  394. {Name: RequestUserHeaders, Value: SerializeHeaders(mockUserHeadersToSerialize)},
  395. }
  396. expectedHeaders := []h2mux.Header{
  397. {Name: "Cf-Two", Value: "cf-value-1"},
  398. {Name: "Cf-Two", Value: "cf-value-2"},
  399. {Name: "Mock-Header-One", Value: "1"},
  400. {Name: "Mock-Header-One", Value: "1.5"},
  401. {Name: "Mock-Header-Two", Value: "2"},
  402. {Name: "Mock-Header-Three", Value: "3"},
  403. }
  404. h1 := &http.Request{
  405. Header: make(http.Header),
  406. }
  407. err := H2RequestHeadersToH1Request(mockHeaders, h1)
  408. assert.NoError(t, err)
  409. assert.ElementsMatch(t, expectedHeaders, stdlibHeaderToH2muxHeader(h1.Header))
  410. }
  411. func TestIsH2muxControlRequestHeader(t *testing.T) {
  412. controlRequestHeaders := []string{
  413. // Anything that begins with cf-
  414. "cf-sample-header",
  415. // Any http2 pseudoheader
  416. ":sample-pseudo-header",
  417. // content-length is a special case, it has to be there
  418. // for some requests to work (per the HTTP2 spec)
  419. "content-length",
  420. // Websocket request headers
  421. "connection",
  422. "upgrade",
  423. }
  424. for _, header := range controlRequestHeaders {
  425. assert.True(t, IsH2muxControlRequestHeader(header))
  426. }
  427. }
  428. func TestIsH2muxControlResponseHeader(t *testing.T) {
  429. controlResponseHeaders := []string{
  430. // Anything that begins with cf-int- or cf-cloudflared-
  431. "cf-int-sample-header",
  432. "cf-cloudflared-sample-header",
  433. // Any http2 pseudoheader
  434. ":sample-pseudo-header",
  435. // content-length is a special case, it has to be there
  436. // for some requests to work (per the HTTP2 spec)
  437. "content-length",
  438. }
  439. for _, header := range controlResponseHeaders {
  440. assert.True(t, IsH2muxControlResponseHeader(header))
  441. }
  442. }
  443. func TestIsNotH2muxControlRequestHeader(t *testing.T) {
  444. notControlRequestHeaders := []string{
  445. "mock-header",
  446. "another-sample-header",
  447. }
  448. for _, header := range notControlRequestHeaders {
  449. assert.False(t, IsH2muxControlRequestHeader(header))
  450. }
  451. }
  452. func TestIsNotH2muxControlResponseHeader(t *testing.T) {
  453. notControlResponseHeaders := []string{
  454. "mock-header",
  455. "another-sample-header",
  456. "upgrade",
  457. "connection",
  458. "cf-whatever", // On the response path, we only want to filter cf-int- and cf-cloudflared-
  459. }
  460. for _, header := range notControlResponseHeaders {
  461. assert.False(t, IsH2muxControlResponseHeader(header))
  462. }
  463. }
  464. func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
  465. mockHeaders := http.Header{
  466. "User-header-one": {""},
  467. "User-header-two": {"1", "2"},
  468. "cf-header": {"cf-value"},
  469. "cf-int-header": {"cf-int-value"},
  470. "cf-cloudflared-header": {"cf-cloudflared-value"},
  471. "Content-Length": {"123"},
  472. }
  473. mockResponse := http.Response{
  474. StatusCode: 200,
  475. Header: mockHeaders,
  476. }
  477. headers := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
  478. serializedHeadersIndex := -1
  479. for i, header := range headers {
  480. if header.Name == ResponseUserHeaders {
  481. serializedHeadersIndex = i
  482. break
  483. }
  484. }
  485. assert.NotEqual(t, -1, serializedHeadersIndex)
  486. actualControlHeaders := append(
  487. headers[:serializedHeadersIndex],
  488. headers[serializedHeadersIndex+1:]...,
  489. )
  490. expectedControlHeaders := []h2mux.Header{
  491. {Name: ":status", Value: "200"},
  492. {Name: "content-length", Value: "123"},
  493. }
  494. assert.ElementsMatch(t, expectedControlHeaders, actualControlHeaders)
  495. actualUserHeaders, err := DeserializeHeaders(headers[serializedHeadersIndex].Value)
  496. expectedUserHeaders := []h2mux.Header{
  497. {Name: "User-header-one", Value: ""},
  498. {Name: "User-header-two", Value: "1"},
  499. {Name: "User-header-two", Value: "2"},
  500. {Name: "cf-header", Value: "cf-value"},
  501. }
  502. assert.NoError(t, err)
  503. assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)
  504. }
  505. // The purpose of this test is to check that our code and the http.Header
  506. // implementation don't throw validation errors about header size
  507. func TestHeaderSize(t *testing.T) {
  508. largeValue := randSeq(5 * 1024 * 1024) // 5Mb
  509. largeHeaders := http.Header{
  510. "User-header": {largeValue},
  511. }
  512. mockResponse := http.Response{
  513. StatusCode: 200,
  514. Header: largeHeaders,
  515. }
  516. serializedHeaders := H1ResponseToH2ResponseHeaders(mockResponse.StatusCode, mockResponse.Header)
  517. request, err := http.NewRequest(http.MethodGet, "https://example.com/", nil)
  518. assert.NoError(t, err)
  519. for _, header := range serializedHeaders {
  520. request.Header.Set(header.Name, header.Value)
  521. }
  522. for _, header := range serializedHeaders {
  523. if header.Name != ResponseUserHeaders {
  524. continue
  525. }
  526. deserializedHeaders, err := DeserializeHeaders(header.Value)
  527. assert.NoError(t, err)
  528. assert.Equal(t, largeValue, deserializedHeaders[0].Value)
  529. }
  530. }
  531. func randSeq(n int) string {
  532. randomizer := rand.New(rand.NewSource(17))
  533. var letters = []rune(":;,+/=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
  534. b := make([]rune, n)
  535. for i := range b {
  536. b[i] = letters[randomizer.Intn(len(letters))]
  537. }
  538. return string(b)
  539. }
  540. func BenchmarkH1ResponseToH2ResponseHeaders(b *testing.B) {
  541. 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"
  542. h2, _ := DeserializeHeaders(ser)
  543. h1 := make(http.Header)
  544. for _, header := range h2 {
  545. h1.Add(header.Name, header.Value)
  546. }
  547. h1.Add("Content-Length", "200")
  548. h1.Add("Cf-Something", "Else")
  549. h1.Add("Upgrade", "websocket")
  550. h1resp := &http.Response{
  551. StatusCode: 200,
  552. Header: h1,
  553. }
  554. b.ReportAllocs()
  555. b.ResetTimer()
  556. for i := 0; i < b.N; i++ {
  557. _ = H1ResponseToH2ResponseHeaders(h1resp.StatusCode, h1resp.Header)
  558. }
  559. }