github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/net/http_test.go (about) 1 //go:build go1.8 2 // +build go1.8 3 4 /* 5 Copyright 2016 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package net 21 22 import ( 23 "crypto/tls" 24 "fmt" 25 "io" 26 "net" 27 "net/http" 28 "net/url" 29 "os" 30 "reflect" 31 "strings" 32 "testing" 33 34 "github.com/stretchr/testify/assert" 35 netutils "k8s.io/utils/net" 36 ) 37 38 func TestGetClientIP(t *testing.T) { 39 ipString := "10.0.0.1" 40 ip := netutils.ParseIPSloppy(ipString) 41 invalidIPString := "invalidIPString" 42 testCases := []struct { 43 Request http.Request 44 ExpectedIP net.IP 45 }{ 46 { 47 Request: http.Request{}, 48 }, 49 { 50 Request: http.Request{ 51 Header: map[string][]string{ 52 "X-Real-Ip": {ipString}, 53 }, 54 }, 55 ExpectedIP: ip, 56 }, 57 { 58 Request: http.Request{ 59 Header: map[string][]string{ 60 "X-Real-Ip": {invalidIPString}, 61 }, 62 }, 63 }, 64 { 65 Request: http.Request{ 66 Header: map[string][]string{ 67 "X-Forwarded-For": {ipString}, 68 }, 69 }, 70 ExpectedIP: ip, 71 }, 72 { 73 Request: http.Request{ 74 Header: map[string][]string{ 75 "X-Forwarded-For": {invalidIPString}, 76 }, 77 }, 78 }, 79 { 80 Request: http.Request{ 81 Header: map[string][]string{ 82 "X-Forwarded-For": {invalidIPString + "," + ipString}, 83 }, 84 }, 85 ExpectedIP: ip, 86 }, 87 { 88 Request: http.Request{ 89 // RemoteAddr is in the form host:port 90 RemoteAddr: ipString + ":1234", 91 }, 92 ExpectedIP: ip, 93 }, 94 { 95 Request: http.Request{ 96 RemoteAddr: invalidIPString, 97 }, 98 }, 99 { 100 Request: http.Request{ 101 Header: map[string][]string{ 102 "X-Forwarded-For": {invalidIPString}, 103 }, 104 // RemoteAddr is in the form host:port 105 RemoteAddr: ipString, 106 }, 107 ExpectedIP: ip, 108 }, 109 } 110 111 for i, test := range testCases { 112 if a, e := GetClientIP(&test.Request), test.ExpectedIP; reflect.DeepEqual(e, a) != true { 113 t.Fatalf("test case %d failed. expected: %v, actual: %v", i, e, a) 114 } 115 } 116 } 117 118 func TestAppendForwardedForHeader(t *testing.T) { 119 testCases := []struct { 120 addr, forwarded, expected string 121 }{ 122 {"1.2.3.4:8000", "", "1.2.3.4"}, 123 {"1.2.3.4:8000", "8.8.8.8", "8.8.8.8, 1.2.3.4"}, 124 {"1.2.3.4:8000", "8.8.8.8, 1.2.3.4", "8.8.8.8, 1.2.3.4, 1.2.3.4"}, 125 {"1.2.3.4:8000", "foo,bar", "foo,bar, 1.2.3.4"}, 126 } 127 for i, test := range testCases { 128 req := &http.Request{ 129 RemoteAddr: test.addr, 130 Header: make(http.Header), 131 } 132 if test.forwarded != "" { 133 req.Header.Set("X-Forwarded-For", test.forwarded) 134 } 135 136 AppendForwardedForHeader(req) 137 actual := req.Header.Get("X-Forwarded-For") 138 if actual != test.expected { 139 t.Errorf("[%d] Expected %q, Got %q", i, test.expected, actual) 140 } 141 } 142 } 143 144 func TestProxierWithNoProxyCIDR(t *testing.T) { 145 testCases := []struct { 146 name string 147 noProxy string 148 url string 149 150 expectedDelegated bool 151 }{ 152 { 153 name: "no env", 154 url: "https://192.168.143.1/api", 155 expectedDelegated: true, 156 }, 157 { 158 name: "no cidr", 159 noProxy: "192.168.63.1", 160 url: "https://192.168.143.1/api", 161 expectedDelegated: true, 162 }, 163 { 164 name: "hostname", 165 noProxy: "192.168.63.0/24,192.168.143.0/24", 166 url: "https://my-hostname/api", 167 expectedDelegated: true, 168 }, 169 { 170 name: "match second cidr", 171 noProxy: "192.168.63.0/24,192.168.143.0/24", 172 url: "https://192.168.143.1/api", 173 expectedDelegated: false, 174 }, 175 { 176 name: "match second cidr with host:port", 177 noProxy: "192.168.63.0/24,192.168.143.0/24", 178 url: "https://192.168.143.1:8443/api", 179 expectedDelegated: false, 180 }, 181 { 182 name: "IPv6 cidr", 183 noProxy: "2001:db8::/48", 184 url: "https://[2001:db8::1]/api", 185 expectedDelegated: false, 186 }, 187 { 188 name: "IPv6+port cidr", 189 noProxy: "2001:db8::/48", 190 url: "https://[2001:db8::1]:8443/api", 191 expectedDelegated: false, 192 }, 193 { 194 name: "IPv6, not matching cidr", 195 noProxy: "2001:db8::/48", 196 url: "https://[2001:db8:1::1]/api", 197 expectedDelegated: true, 198 }, 199 { 200 name: "IPv6+port, not matching cidr", 201 noProxy: "2001:db8::/48", 202 url: "https://[2001:db8:1::1]:8443/api", 203 expectedDelegated: true, 204 }, 205 } 206 207 for _, test := range testCases { 208 os.Setenv("NO_PROXY", test.noProxy) 209 actualDelegated := false 210 proxyFunc := NewProxierWithNoProxyCIDR(func(req *http.Request) (*url.URL, error) { 211 actualDelegated = true 212 return nil, nil 213 }) 214 215 req, err := http.NewRequest("GET", test.url, nil) 216 if err != nil { 217 t.Errorf("%s: unexpected err: %v", test.name, err) 218 continue 219 } 220 if _, err := proxyFunc(req); err != nil { 221 t.Errorf("%s: unexpected err: %v", test.name, err) 222 continue 223 } 224 225 if test.expectedDelegated != actualDelegated { 226 t.Errorf("%s: expected %v, got %v", test.name, test.expectedDelegated, actualDelegated) 227 continue 228 } 229 } 230 } 231 232 type fakeTLSClientConfigHolder struct { 233 called bool 234 } 235 236 func (f *fakeTLSClientConfigHolder) TLSClientConfig() *tls.Config { 237 f.called = true 238 return nil 239 } 240 func (f *fakeTLSClientConfigHolder) RoundTrip(*http.Request) (*http.Response, error) { 241 return nil, nil 242 } 243 244 func TestTLSClientConfigHolder(t *testing.T) { 245 rt := &fakeTLSClientConfigHolder{} 246 TLSClientConfig(rt) 247 248 if !rt.called { 249 t.Errorf("didn't find tls config") 250 } 251 } 252 253 func TestJoinPreservingTrailingSlash(t *testing.T) { 254 tests := []struct { 255 a string 256 b string 257 want string 258 }{ 259 // All empty 260 {"", "", ""}, 261 262 // Empty a 263 {"", "/", "/"}, 264 {"", "foo", "foo"}, 265 {"", "/foo", "/foo"}, 266 {"", "/foo/", "/foo/"}, 267 268 // Empty b 269 {"/", "", "/"}, 270 {"foo", "", "foo"}, 271 {"/foo", "", "/foo"}, 272 {"/foo/", "", "/foo/"}, 273 274 // Both populated 275 {"/", "/", "/"}, 276 {"foo", "foo", "foo/foo"}, 277 {"/foo", "/foo", "/foo/foo"}, 278 {"/foo/", "/foo/", "/foo/foo/"}, 279 } 280 for _, tt := range tests { 281 name := fmt.Sprintf("%q+%q=%q", tt.a, tt.b, tt.want) 282 t.Run(name, func(t *testing.T) { 283 if got := JoinPreservingTrailingSlash(tt.a, tt.b); got != tt.want { 284 t.Errorf("JoinPreservingTrailingSlash() = %v, want %v", got, tt.want) 285 } 286 }) 287 } 288 } 289 290 func TestAllowsHTTP2(t *testing.T) { 291 testcases := []struct { 292 Name string 293 Transport *http.Transport 294 ExpectAllows bool 295 }{ 296 { 297 Name: "empty", 298 Transport: &http.Transport{}, 299 ExpectAllows: true, 300 }, 301 { 302 Name: "empty tlsconfig", 303 Transport: &http.Transport{TLSClientConfig: &tls.Config{}}, 304 ExpectAllows: true, 305 }, 306 { 307 Name: "zero-length NextProtos", 308 Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{}}}, 309 ExpectAllows: true, 310 }, 311 { 312 Name: "includes h2 in NextProtos after", 313 Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2"}}}, 314 ExpectAllows: true, 315 }, 316 { 317 Name: "includes h2 in NextProtos before", 318 Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"h2", "http/1.1"}}}, 319 ExpectAllows: true, 320 }, 321 { 322 Name: "includes h2 in NextProtos between", 323 Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2", "h3"}}}, 324 ExpectAllows: true, 325 }, 326 { 327 Name: "excludes h2 in NextProtos", 328 Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1"}}}, 329 ExpectAllows: false, 330 }, 331 } 332 333 for _, tc := range testcases { 334 t.Run(tc.Name, func(t *testing.T) { 335 allows := allowsHTTP2(tc.Transport) 336 if allows != tc.ExpectAllows { 337 t.Errorf("expected %v, got %v", tc.ExpectAllows, allows) 338 } 339 }) 340 } 341 } 342 343 func TestSourceIPs(t *testing.T) { 344 tests := []struct { 345 name string 346 realIP string 347 forwardedFor string 348 remoteAddr string 349 expected []string 350 }{{ 351 name: "no headers, missing remoteAddr", 352 expected: []string{}, 353 }, { 354 name: "no headers, just remoteAddr host:port", 355 remoteAddr: "1.2.3.4:555", 356 expected: []string{"1.2.3.4"}, 357 }, { 358 name: "no headers, just remoteAddr host", 359 remoteAddr: "1.2.3.4", 360 expected: []string{"1.2.3.4"}, 361 }, { 362 name: "empty forwarded-for chain", 363 forwardedFor: " ", 364 remoteAddr: "1.2.3.4", 365 expected: []string{"1.2.3.4"}, 366 }, { 367 name: "invalid forwarded-for chain", 368 forwardedFor: "garbage garbage values!", 369 remoteAddr: "1.2.3.4", 370 expected: []string{"1.2.3.4"}, 371 }, { 372 name: "partially invalid forwarded-for chain", 373 forwardedFor: "garbage garbage values!,4.5.6.7", 374 remoteAddr: "1.2.3.4", 375 expected: []string{"4.5.6.7", "1.2.3.4"}, 376 }, { 377 name: "valid forwarded-for chain", 378 forwardedFor: "120.120.120.126,2.2.2.2,4.5.6.7", 379 remoteAddr: "1.2.3.4", 380 expected: []string{"120.120.120.126", "2.2.2.2", "4.5.6.7", "1.2.3.4"}, 381 }, { 382 name: "valid forwarded-for chain with redundant remoteAddr", 383 forwardedFor: "2.2.2.2,1.2.3.4", 384 remoteAddr: "1.2.3.4", 385 expected: []string{"2.2.2.2", "1.2.3.4"}, 386 }, { 387 name: "invalid Real-Ip", 388 realIP: "garbage, just garbage!", 389 remoteAddr: "1.2.3.4", 390 expected: []string{"1.2.3.4"}, 391 }, { 392 name: "invalid Real-Ip with forwarded-for", 393 realIP: "garbage, just garbage!", 394 forwardedFor: "2.2.2.2", 395 remoteAddr: "1.2.3.4", 396 expected: []string{"2.2.2.2", "1.2.3.4"}, 397 }, { 398 name: "valid Real-Ip", 399 realIP: "2.2.2.2", 400 remoteAddr: "1.2.3.4", 401 expected: []string{"2.2.2.2", "1.2.3.4"}, 402 }, { 403 name: "redundant Real-Ip", 404 realIP: "1.2.3.4", 405 remoteAddr: "1.2.3.4", 406 expected: []string{"1.2.3.4"}, 407 }, { 408 name: "valid Real-Ip with forwarded-for", 409 realIP: "2.2.2.2", 410 forwardedFor: "120.120.120.126,4.5.6.7", 411 remoteAddr: "1.2.3.4", 412 expected: []string{"120.120.120.126", "4.5.6.7", "2.2.2.2", "1.2.3.4"}, 413 }, { 414 name: "redundant Real-Ip with forwarded-for", 415 realIP: "2.2.2.2", 416 forwardedFor: "120.120.120.126,2.2.2.2,4.5.6.7", 417 remoteAddr: "1.2.3.4", 418 expected: []string{"120.120.120.126", "2.2.2.2", "4.5.6.7", "1.2.3.4"}, 419 }, { 420 name: "full redundancy", 421 realIP: "1.2.3.4", 422 forwardedFor: "1.2.3.4", 423 remoteAddr: "1.2.3.4", 424 expected: []string{"1.2.3.4"}, 425 }, { 426 name: "full ipv6", 427 realIP: "abcd:ef01:2345:6789:abcd:ef01:2345:6789", 428 forwardedFor: "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111,0:1111:2222:3333:4444:5555:6666:7777", 429 remoteAddr: "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa", 430 expected: []string{ 431 "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111", 432 "0:1111:2222:3333:4444:5555:6666:7777", 433 "abcd:ef01:2345:6789:abcd:ef01:2345:6789", 434 "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa", 435 }, 436 }, { 437 name: "mixed ipv4 ipv6", 438 forwardedFor: "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111,1.2.3.4", 439 remoteAddr: "0:0:0:0:0:ffff:102:304", // ipv6 equivalent to 1.2.3.4 440 expected: []string{ 441 "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111", 442 "1.2.3.4", 443 }, 444 }} 445 446 for _, test := range tests { 447 t.Run(test.name, func(t *testing.T) { 448 req, _ := http.NewRequest("GET", "https://cluster.k8s.io/apis/foobars/v1/foo/bar", nil) 449 req.RemoteAddr = test.remoteAddr 450 if test.forwardedFor != "" { 451 req.Header.Set("X-Forwarded-For", test.forwardedFor) 452 } 453 if test.realIP != "" { 454 req.Header.Set("X-Real-Ip", test.realIP) 455 } 456 457 actualIPs := SourceIPs(req) 458 actual := make([]string, len(actualIPs)) 459 for i, ip := range actualIPs { 460 actual[i] = ip.String() 461 } 462 463 assert.Equal(t, test.expected, actual) 464 }) 465 } 466 } 467 468 func TestParseWarningHeader(t *testing.T) { 469 tests := []struct { 470 name string 471 472 header string 473 474 wantResult WarningHeader 475 wantRemainder string 476 wantErr string 477 }{ 478 // invalid cases 479 { 480 name: "empty", 481 header: ``, 482 wantErr: "fewer than 3 segments", 483 }, 484 { 485 name: "bad code", 486 header: `A B`, 487 wantErr: "fewer than 3 segments", 488 }, 489 { 490 name: "short code", 491 header: `1 - "text"`, 492 wantErr: "not 3 digits", 493 }, 494 { 495 name: "bad code", 496 header: `A - "text"`, 497 wantErr: "not 3 digits", 498 }, 499 { 500 name: "invalid date quoting", 501 header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT `, 502 wantErr: "unterminated date segment", 503 }, 504 { 505 name: "invalid post-date", 506 header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" other`, 507 wantErr: "unexpected token after warn-date", 508 }, 509 { 510 name: "agent control character", 511 header: " 299 agent\u0000name \"text\"", 512 wantErr: "invalid agent", 513 }, 514 { 515 name: "agent non-utf8 character", 516 header: " 299 agent\xc5name \"text\"", 517 wantErr: "invalid agent", 518 }, 519 { 520 name: "text control character", 521 header: " 299 - \"text\u0000\"content", 522 wantErr: "invalid text", 523 }, 524 { 525 name: "text non-utf8 character", 526 header: " 299 - \"text\xc5\"content", 527 wantErr: "invalid text", 528 }, 529 530 // valid cases 531 { 532 name: "ok", 533 header: `299 - "text"`, 534 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text`}, 535 }, 536 { 537 name: "ok", 538 header: `299 - "text\"\\\a\b\c"`, 539 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, 540 }, 541 // big code 542 { 543 name: "big code", 544 header: `321 - "text"`, 545 wantResult: WarningHeader{Code: 321, Agent: "-", Text: "text"}, 546 }, 547 // RFC 2047 decoding 548 { 549 name: "ok, rfc 2047, iso-8859-1, q", 550 header: `299 - "=?iso-8859-1?q?this=20is=20some=20text?="`, 551 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `this is some text`}, 552 }, 553 { 554 name: "ok, rfc 2047, utf-8, b", 555 header: `299 - "=?UTF-8?B?VGhpcyBpcyBhIGhvcnNleTog8J+Qjg==?= And =?UTF-8?B?VGhpcyBpcyBhIGhvcnNleTog8J+Qjg==?="`, 556 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `This is a horsey: 🐎 And This is a horsey: 🐎`}, 557 }, 558 { 559 name: "ok, rfc 2047, utf-8, q", 560 header: `299 - "=?UTF-8?Q?This is a \"horsey\": =F0=9F=90=8E?="`, 561 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `This is a "horsey": 🐎`}, 562 }, 563 { 564 name: "ok, rfc 2047, unknown charset", 565 header: `299 - "=?UTF-9?Q?This is a horsey: =F0=9F=90=8E?="`, 566 wantResult: WarningHeader{Code: 299, Agent: "-", Text: `=?UTF-9?Q?This is a horsey: =F0=9F=90=8E?=`}, 567 }, 568 { 569 name: "ok with spaces", 570 header: ` 299 - "text\"\\\a\b\c" `, 571 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, 572 }, 573 { 574 name: "ok with date", 575 header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" `, 576 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, 577 }, 578 { 579 name: "ok with date and comma", 580 header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" , `, 581 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, 582 }, 583 { 584 name: "ok with comma", 585 header: ` 299 - "text\"\\\a\b\c" , `, 586 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, 587 }, 588 { 589 name: "ok with date and comma and remainder", 590 header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" , remainder `, 591 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, 592 wantRemainder: "remainder", 593 }, 594 { 595 name: "ok with comma and remainder", 596 header: ` 299 - "text\"\\\a\b\c" ,remainder text,second remainder`, 597 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, 598 wantRemainder: "remainder text,second remainder", 599 }, 600 { 601 name: "ok with utf-8 content directly in warn-text", 602 header: ` 299 - "Test of Iñtërnâtiônàlizætiøn,💝🐹🌇⛔" `, 603 wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `Test of Iñtërnâtiônàlizætiøn,💝🐹🌇⛔`}, 604 }, 605 } 606 for _, tt := range tests { 607 t.Run(tt.name, func(t *testing.T) { 608 gotResult, gotRemainder, err := ParseWarningHeader(tt.header) 609 switch { 610 case err == nil && len(tt.wantErr) > 0: 611 t.Errorf("ParseWarningHeader() no error, expected error %q", tt.wantErr) 612 return 613 case err != nil && len(tt.wantErr) == 0: 614 t.Errorf("ParseWarningHeader() error %q, expected no error", err) 615 return 616 case err != nil && len(tt.wantErr) > 0 && !strings.Contains(err.Error(), tt.wantErr): 617 t.Errorf("ParseWarningHeader() error %q, expected error %q", err, tt.wantErr) 618 return 619 } 620 if err != nil { 621 return 622 } 623 if !reflect.DeepEqual(gotResult, tt.wantResult) { 624 t.Errorf("ParseWarningHeader() gotResult = %#v, want %#v", gotResult, tt.wantResult) 625 } 626 if gotRemainder != tt.wantRemainder { 627 t.Errorf("ParseWarningHeader() gotRemainder = %v, want %v", gotRemainder, tt.wantRemainder) 628 } 629 }) 630 } 631 } 632 633 func TestNewWarningHeader(t *testing.T) { 634 tests := []struct { 635 name string 636 637 code int 638 agent string 639 text string 640 641 want string 642 wantErr string 643 }{ 644 // invalid cases 645 { 646 name: "code too low", 647 code: -1, 648 agent: `-`, 649 text: `example warning`, 650 wantErr: "between 0 and 999", 651 }, 652 { 653 name: "code too high", 654 code: 1000, 655 agent: `-`, 656 text: `example warning`, 657 wantErr: "between 0 and 999", 658 }, 659 { 660 name: "agent with space", 661 code: 299, 662 agent: `test agent`, 663 text: `example warning`, 664 wantErr: `agent must be valid`, 665 }, 666 { 667 name: "agent with newline", 668 code: 299, 669 agent: "test\nagent", 670 text: `example warning`, 671 wantErr: `agent must be valid`, 672 }, 673 { 674 name: "agent with backslash", 675 code: 299, 676 agent: `test\agent`, 677 text: `example warning`, 678 wantErr: `agent must be valid`, 679 }, 680 { 681 name: "agent with quote", 682 code: 299, 683 agent: `test"agent"`, 684 text: `example warning`, 685 wantErr: `agent must be valid`, 686 }, 687 { 688 name: "agent with control character", 689 code: 299, 690 agent: "test\u0000agent", 691 text: `example warning`, 692 wantErr: `agent must be valid`, 693 }, 694 { 695 name: "agent with non-UTF8", 696 code: 299, 697 agent: "test\xc5agent", 698 text: `example warning`, 699 wantErr: `agent must be valid`, 700 }, 701 { 702 name: "text with newline", 703 code: 299, 704 agent: `-`, 705 text: "Test of new\nline", 706 wantErr: "text must be valid", 707 }, 708 { 709 name: "text with control character", 710 code: 299, 711 agent: `-`, 712 text: "Test of control\u0000character", 713 wantErr: "text must be valid", 714 }, 715 { 716 name: "text with non-UTF8", 717 code: 299, 718 agent: `-`, 719 text: "Test of control\xc5character", 720 wantErr: "text must be valid", 721 }, 722 723 { 724 name: "valid empty text", 725 code: 299, 726 agent: `-`, 727 text: ``, 728 want: `299 - ""`, 729 }, 730 { 731 name: "valid empty agent", 732 code: 299, 733 agent: ``, 734 text: `example warning`, 735 want: `299 - "example warning"`, 736 }, 737 { 738 name: "valid low code", 739 code: 1, 740 agent: `-`, 741 text: `example warning`, 742 want: `001 - "example warning"`, 743 }, 744 { 745 name: "valid high code", 746 code: 999, 747 agent: `-`, 748 text: `example warning`, 749 want: `999 - "example warning"`, 750 }, 751 { 752 name: "valid utf-8", 753 code: 299, 754 agent: `-`, 755 text: `Test of "Iñtërnâtiônàlizætiøn,💝🐹🌇⛔"`, 756 want: `299 - "Test of \"Iñtërnâtiônàlizætiøn,💝🐹🌇⛔\""`, 757 }, 758 } 759 760 for _, tt := range tests { 761 t.Run(tt.name, func(t *testing.T) { 762 got, err := NewWarningHeader(tt.code, tt.agent, tt.text) 763 764 switch { 765 case err == nil && len(tt.wantErr) > 0: 766 t.Fatalf("ParseWarningHeader() no error, expected error %q", tt.wantErr) 767 case err != nil && len(tt.wantErr) == 0: 768 t.Fatalf("ParseWarningHeader() error %q, expected no error", err) 769 case err != nil && len(tt.wantErr) > 0 && !strings.Contains(err.Error(), tt.wantErr): 770 t.Fatalf("ParseWarningHeader() error %q, expected error %q", err, tt.wantErr) 771 } 772 if err != nil { 773 return 774 } 775 776 if got != tt.want { 777 t.Fatalf("NewWarningHeader() = %v, want %v", got, tt.want) 778 } 779 780 roundTrip, remaining, err := ParseWarningHeader(got) 781 if err != nil { 782 t.Fatalf("error roundtripping: %v", err) 783 } 784 if len(remaining) > 0 { 785 t.Fatalf("unexpected remainder roundtripping: %s", remaining) 786 } 787 agent := tt.agent 788 if len(agent) == 0 { 789 agent = "-" 790 } 791 expect := WarningHeader{Code: tt.code, Agent: agent, Text: tt.text} 792 if roundTrip != expect { 793 t.Fatalf("after round trip, want:\n%#v\ngot\n%#v", expect, roundTrip) 794 } 795 }) 796 } 797 } 798 799 func TestParseWarningHeaders(t *testing.T) { 800 tests := []struct { 801 name string 802 803 headers []string 804 805 want []WarningHeader 806 wantErrs []string 807 }{ 808 { 809 name: "empty", 810 headers: []string{}, 811 want: nil, 812 wantErrs: []string{}, 813 }, 814 { 815 name: "multi-header with error", 816 headers: []string{ 817 `299 - "warning 1.1",299 - "warning 1.2"`, 818 `299 - "warning 2", 299 - "warning unquoted`, 819 ` 299 - "warning 3.1" , 299 - "warning 3.2" `, 820 }, 821 want: []WarningHeader{ 822 {Code: 299, Agent: "-", Text: "warning 1.1"}, 823 {Code: 299, Agent: "-", Text: "warning 1.2"}, 824 {Code: 299, Agent: "-", Text: "warning 2"}, 825 {Code: 299, Agent: "-", Text: "warning 3.1"}, 826 {Code: 299, Agent: "-", Text: "warning 3.2"}, 827 }, 828 wantErrs: []string{"invalid warning header: invalid quoted string: missing closing quote"}, 829 }, 830 } 831 for _, tt := range tests { 832 t.Run(tt.name, func(t *testing.T) { 833 got, gotErrs := ParseWarningHeaders(tt.headers) 834 835 switch { 836 case len(gotErrs) != len(tt.wantErrs): 837 t.Fatalf("ParseWarningHeader() got %v, expected %v", gotErrs, tt.wantErrs) 838 case len(gotErrs) == len(tt.wantErrs) && len(gotErrs) > 0: 839 gotErrStrings := []string{} 840 for _, err := range gotErrs { 841 gotErrStrings = append(gotErrStrings, err.Error()) 842 } 843 if !reflect.DeepEqual(gotErrStrings, tt.wantErrs) { 844 t.Fatalf("ParseWarningHeader() got %v, expected %v", gotErrs, tt.wantErrs) 845 } 846 } 847 if len(gotErrs) > 0 { 848 return 849 } 850 851 if !reflect.DeepEqual(got, tt.want) { 852 t.Errorf("ParseWarningHeaders() got %#v, want %#v", got, tt.want) 853 } 854 }) 855 } 856 } 857 858 func TestIsProbableEOF(t *testing.T) { 859 tests := []struct { 860 name string 861 err error 862 expected bool 863 }{ 864 { 865 name: "with no error", 866 expected: false, 867 }, 868 { 869 name: "with EOF error", 870 err: io.EOF, 871 expected: true, 872 }, 873 { 874 name: "with unexpected EOF error", 875 err: io.ErrUnexpectedEOF, 876 expected: true, 877 }, 878 { 879 name: "with broken connection error", 880 err: fmt.Errorf("http: can't write HTTP request on broken connection"), 881 expected: true, 882 }, 883 { 884 name: "with server sent GOAWAY error", 885 err: fmt.Errorf("error foo - http2: server sent GOAWAY and closed the connection - error bar"), 886 expected: true, 887 }, 888 { 889 name: "with connection reset by peer error", 890 err: fmt.Errorf("error foo - connection reset by peer - error bar"), 891 expected: true, 892 }, 893 { 894 name: "with use of closed network connection error", 895 err: fmt.Errorf("error foo - Use of closed network connection - error bar"), 896 expected: true, 897 }, 898 { 899 name: "with url error", 900 err: &url.Error{ 901 Err: io.ErrUnexpectedEOF, 902 }, 903 expected: true, 904 }, 905 { 906 name: "with unrecognized error", 907 err: fmt.Errorf("error foo"), 908 expected: false, 909 }, 910 } 911 912 for _, test := range tests { 913 t.Run(test.name, func(t *testing.T) { 914 actual := IsProbableEOF(test.err) 915 assert.Equal(t, test.expected, actual) 916 }) 917 } 918 } 919 920 func setEnv(key, value string) func() { 921 originalValue := os.Getenv(key) 922 os.Setenv(key, value) 923 return func() { 924 os.Setenv(key, originalValue) 925 } 926 } 927 928 func TestReadIdleTimeoutSeconds(t *testing.T) { 929 reset := setEnv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", "60") 930 if e, a := 60, readIdleTimeoutSeconds(); e != a { 931 t.Errorf("expected %d, got %d", e, a) 932 } 933 reset() 934 935 reset = setEnv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", "illegal value") 936 if e, a := 30, readIdleTimeoutSeconds(); e != a { 937 t.Errorf("expected %d, got %d", e, a) 938 } 939 reset() 940 } 941 942 func TestPingTimeoutSeconds(t *testing.T) { 943 reset := setEnv("HTTP2_PING_TIMEOUT_SECONDS", "60") 944 if e, a := 60, pingTimeoutSeconds(); e != a { 945 t.Errorf("expected %d, got %d", e, a) 946 } 947 reset() 948 949 reset = setEnv("HTTP2_PING_TIMEOUT_SECONDS", "illegal value") 950 if e, a := 15, pingTimeoutSeconds(); e != a { 951 t.Errorf("expected %d, got %d", e, a) 952 } 953 reset() 954 } 955 956 func Benchmark_ParseQuotedString(b *testing.B) { 957 str := `"The quick brown" fox jumps over the lazy dog` 958 b.ReportAllocs() 959 b.ResetTimer() 960 for i := 0; i < b.N; i++ { 961 quoted, remainder, err := parseQuotedString(str) 962 if err != nil { 963 b.Errorf("Unexpected error %s", err) 964 } 965 if quoted != "The quick brown" { 966 b.Errorf("Unexpected quoted string %s", quoted) 967 } 968 if remainder != "fox jumps over the lazy dog" { 969 b.Errorf("Unexpected remainder string %s", quoted) 970 } 971 } 972 }