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  }