github.com/icodeface/tls@v0.0.0-20230910023335-34df9250cd12/internal/x/net/http/httpproxy/proxy_test.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package httpproxy_test 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "net/url" 12 "os" 13 "strings" 14 "testing" 15 16 "github.com/icodeface/tls/internal/x/net/http/httpproxy" 17 ) 18 19 // setHelper calls t.Helper() for Go 1.9+ (see go19_test.go) and does nothing otherwise. 20 var setHelper = func(t *testing.T) {} 21 22 type proxyForURLTest struct { 23 cfg httpproxy.Config 24 req string // URL to fetch; blank means "http://example.com" 25 want string 26 wanterr error 27 } 28 29 func (t proxyForURLTest) String() string { 30 var buf bytes.Buffer 31 space := func() { 32 if buf.Len() > 0 { 33 buf.WriteByte(' ') 34 } 35 } 36 if t.cfg.HTTPProxy != "" { 37 fmt.Fprintf(&buf, "http_proxy=%q", t.cfg.HTTPProxy) 38 } 39 if t.cfg.HTTPSProxy != "" { 40 space() 41 fmt.Fprintf(&buf, "https_proxy=%q", t.cfg.HTTPSProxy) 42 } 43 if t.cfg.NoProxy != "" { 44 space() 45 fmt.Fprintf(&buf, "no_proxy=%q", t.cfg.NoProxy) 46 } 47 req := "http://example.com" 48 if t.req != "" { 49 req = t.req 50 } 51 space() 52 fmt.Fprintf(&buf, "req=%q", req) 53 return strings.TrimSpace(buf.String()) 54 } 55 56 var proxyForURLTests = []proxyForURLTest{{ 57 cfg: httpproxy.Config{ 58 HTTPProxy: "127.0.0.1:8080", 59 }, 60 want: "http://127.0.0.1:8080", 61 }, { 62 cfg: httpproxy.Config{ 63 HTTPProxy: "cache.corp.example.com:1234", 64 }, 65 want: "http://cache.corp.example.com:1234", 66 }, { 67 cfg: httpproxy.Config{ 68 HTTPProxy: "cache.corp.example.com", 69 }, 70 want: "http://cache.corp.example.com", 71 }, { 72 cfg: httpproxy.Config{ 73 HTTPProxy: "https://cache.corp.example.com", 74 }, 75 want: "https://cache.corp.example.com", 76 }, { 77 cfg: httpproxy.Config{ 78 HTTPProxy: "http://127.0.0.1:8080", 79 }, 80 want: "http://127.0.0.1:8080", 81 }, { 82 cfg: httpproxy.Config{ 83 HTTPProxy: "https://127.0.0.1:8080", 84 }, 85 want: "https://127.0.0.1:8080", 86 }, { 87 cfg: httpproxy.Config{ 88 HTTPProxy: "socks5://127.0.0.1", 89 }, 90 want: "socks5://127.0.0.1", 91 }, { 92 // Don't use secure for http 93 cfg: httpproxy.Config{ 94 HTTPProxy: "http.proxy.tld", 95 HTTPSProxy: "secure.proxy.tld", 96 }, 97 req: "http://insecure.tld/", 98 want: "http://http.proxy.tld", 99 }, { 100 // Use secure for https. 101 cfg: httpproxy.Config{ 102 HTTPProxy: "http.proxy.tld", 103 HTTPSProxy: "secure.proxy.tld", 104 }, 105 req: "https://secure.tld/", 106 want: "http://secure.proxy.tld", 107 }, { 108 cfg: httpproxy.Config{ 109 HTTPProxy: "http.proxy.tld", 110 HTTPSProxy: "https://secure.proxy.tld", 111 }, 112 req: "https://secure.tld/", 113 want: "https://secure.proxy.tld", 114 }, { 115 // Issue 16405: don't use HTTP_PROXY in a CGI environment, 116 // where HTTP_PROXY can be attacker-controlled. 117 cfg: httpproxy.Config{ 118 HTTPProxy: "http://10.1.2.3:8080", 119 CGI: true, 120 }, 121 want: "<nil>", 122 wanterr: errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy"), 123 }, { 124 // HTTPS proxy is still used even in CGI environment. 125 // (perhaps dubious but it's the historical behaviour). 126 cfg: httpproxy.Config{ 127 HTTPSProxy: "https://secure.proxy.tld", 128 CGI: true, 129 }, 130 req: "https://secure.tld/", 131 want: "https://secure.proxy.tld", 132 }, { 133 want: "<nil>", 134 }, { 135 cfg: httpproxy.Config{ 136 NoProxy: "example.com", 137 HTTPProxy: "proxy", 138 }, 139 req: "http://example.com/", 140 want: "<nil>", 141 }, { 142 cfg: httpproxy.Config{ 143 NoProxy: ".example.com", 144 HTTPProxy: "proxy", 145 }, 146 req: "http://example.com/", 147 want: "http://proxy", 148 }, { 149 cfg: httpproxy.Config{ 150 NoProxy: "ample.com", 151 HTTPProxy: "proxy", 152 }, 153 req: "http://example.com/", 154 want: "http://proxy", 155 }, { 156 cfg: httpproxy.Config{ 157 NoProxy: "example.com", 158 HTTPProxy: "proxy", 159 }, 160 req: "http://foo.example.com/", 161 want: "<nil>", 162 }, { 163 cfg: httpproxy.Config{ 164 NoProxy: ".foo.com", 165 HTTPProxy: "proxy", 166 }, 167 req: "http://example.com/", 168 want: "http://proxy", 169 }} 170 171 func testProxyForURL(t *testing.T, tt proxyForURLTest) { 172 setHelper(t) 173 reqURLStr := tt.req 174 if reqURLStr == "" { 175 reqURLStr = "http://example.com" 176 } 177 reqURL, err := url.Parse(reqURLStr) 178 if err != nil { 179 t.Errorf("invalid URL %q", reqURLStr) 180 return 181 } 182 cfg := tt.cfg 183 proxyForURL := cfg.ProxyFunc() 184 url, err := proxyForURL(reqURL) 185 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { 186 t.Errorf("%v: got error = %q, want %q", tt, g, e) 187 return 188 } 189 if got := fmt.Sprintf("%s", url); got != tt.want { 190 t.Errorf("%v: got URL = %q, want %q", tt, url, tt.want) 191 } 192 193 // Check that changing the Config doesn't change the results 194 // of the functuon. 195 cfg = httpproxy.Config{} 196 url, err = proxyForURL(reqURL) 197 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { 198 t.Errorf("(after mutating config) %v: got error = %q, want %q", tt, g, e) 199 return 200 } 201 if got := fmt.Sprintf("%s", url); got != tt.want { 202 t.Errorf("(after mutating config) %v: got URL = %q, want %q", tt, url, tt.want) 203 } 204 } 205 206 func TestProxyForURL(t *testing.T) { 207 for _, tt := range proxyForURLTests { 208 testProxyForURL(t, tt) 209 } 210 } 211 212 func TestFromEnvironment(t *testing.T) { 213 os.Setenv("HTTP_PROXY", "httpproxy") 214 os.Setenv("HTTPS_PROXY", "httpsproxy") 215 os.Setenv("NO_PROXY", "noproxy") 216 os.Setenv("REQUEST_METHOD", "") 217 got := httpproxy.FromEnvironment() 218 want := httpproxy.Config{ 219 HTTPProxy: "httpproxy", 220 HTTPSProxy: "httpsproxy", 221 NoProxy: "noproxy", 222 } 223 if *got != want { 224 t.Errorf("unexpected proxy config, got %#v want %#v", got, want) 225 } 226 } 227 228 func TestFromEnvironmentWithRequestMethod(t *testing.T) { 229 os.Setenv("HTTP_PROXY", "httpproxy") 230 os.Setenv("HTTPS_PROXY", "httpsproxy") 231 os.Setenv("NO_PROXY", "noproxy") 232 os.Setenv("REQUEST_METHOD", "PUT") 233 got := httpproxy.FromEnvironment() 234 want := httpproxy.Config{ 235 HTTPProxy: "httpproxy", 236 HTTPSProxy: "httpsproxy", 237 NoProxy: "noproxy", 238 CGI: true, 239 } 240 if *got != want { 241 t.Errorf("unexpected proxy config, got %#v want %#v", got, want) 242 } 243 } 244 245 func TestFromEnvironmentLowerCase(t *testing.T) { 246 os.Setenv("http_proxy", "httpproxy") 247 os.Setenv("https_proxy", "httpsproxy") 248 os.Setenv("no_proxy", "noproxy") 249 os.Setenv("REQUEST_METHOD", "") 250 got := httpproxy.FromEnvironment() 251 want := httpproxy.Config{ 252 HTTPProxy: "httpproxy", 253 HTTPSProxy: "httpsproxy", 254 NoProxy: "noproxy", 255 } 256 if *got != want { 257 t.Errorf("unexpected proxy config, got %#v want %#v", got, want) 258 } 259 } 260 261 var UseProxyTests = []struct { 262 host string 263 match bool 264 }{ 265 // Never proxy localhost: 266 {"localhost", false}, 267 {"127.0.0.1", false}, 268 {"127.0.0.2", false}, 269 {"[::1]", false}, 270 {"[::2]", true}, // not a loopback address 271 272 {"192.168.1.1", false}, // matches exact IPv4 273 {"192.168.1.2", true}, // ports do not match 274 {"192.168.1.3", false}, // matches exact IPv4:port 275 {"192.168.1.4", true}, // no match 276 {"10.0.0.2", false}, // matches IPv4/CIDR 277 {"[2001:db8::52:0:1]", false}, // matches exact IPv6 278 {"[2001:db8::52:0:2]", true}, // no match 279 {"[2001:db8::52:0:3]", false}, // matches exact [IPv6]:port 280 {"[2002:db8:a::123]", false}, // matches IPv6/CIDR 281 {"[fe80::424b:c8be:1643:a1b6]", true}, // no match 282 283 {"barbaz.net", true}, // does not match as .barbaz.net 284 {"www.barbaz.net", false}, // does match as .barbaz.net 285 {"foobar.com", false}, // does match as foobar.com 286 {"www.foobar.com", false}, // match because NO_PROXY includes "foobar.com" 287 {"foofoobar.com", true}, // not match as a part of foobar.com 288 {"baz.com", true}, // not match as a part of barbaz.com 289 {"localhost.net", true}, // not match as suffix of address 290 {"local.localhost", true}, // not match as prefix as address 291 {"barbarbaz.net", true}, // not match, wrong domain 292 {"wildcard.io", true}, // does not match as *.wildcard.io 293 {"nested.wildcard.io", false}, // match as *.wildcard.io 294 {"awildcard.io", true}, // not a match because of '*' 295 } 296 297 var noProxy = "foobar.com, .barbaz.net, *.wildcard.io, 192.168.1.1, 192.168.1.2:81, 192.168.1.3:80, 10.0.0.0/30, 2001:db8::52:0:1, [2001:db8::52:0:2]:443, [2001:db8::52:0:3]:80, 2002:db8:a::45/64" 298 299 func TestUseProxy(t *testing.T) { 300 cfg := &httpproxy.Config{ 301 NoProxy: noProxy, 302 } 303 for _, test := range UseProxyTests { 304 if httpproxy.ExportUseProxy(cfg, test.host+":80") != test.match { 305 t.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) 306 } 307 } 308 } 309 310 func TestInvalidNoProxy(t *testing.T) { 311 cfg := &httpproxy.Config{ 312 NoProxy: ":1", 313 } 314 ok := httpproxy.ExportUseProxy(cfg, "example.com:80") // should not panic 315 if !ok { 316 t.Errorf("useProxy unexpected return; got false; want true") 317 } 318 } 319 320 func TestAllNoProxy(t *testing.T) { 321 cfg := &httpproxy.Config{ 322 NoProxy: "*", 323 } 324 for _, test := range UseProxyTests { 325 if httpproxy.ExportUseProxy(cfg, test.host+":80") != false { 326 t.Errorf("useProxy(%v) = true, want false", test.host) 327 } 328 } 329 } 330 331 func BenchmarkProxyForURL(b *testing.B) { 332 cfg := &httpproxy.Config{ 333 HTTPProxy: "http://proxy.example.org", 334 HTTPSProxy: "https://proxy.example.org", 335 NoProxy: noProxy, 336 } 337 for _, test := range UseProxyTests { 338 u, err := url.Parse("https://" + test.host + ":80") 339 if err != nil { 340 b.Fatalf("parsed failed: %s", test.host) 341 } 342 proxyFunc := cfg.ProxyFunc() 343 b.Run(test.host, func(b *testing.B) { 344 for n := 0; n < b.N; n++ { 345 if au, e := proxyFunc(u); e != nil && test.match == (au != nil) { 346 b.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) 347 } 348 } 349 }) 350 } 351 }