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