golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/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 "golang.org/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 // single label domain is recognized as scheme by url.Parse 73 cfg: httpproxy.Config{ 74 HTTPProxy: "localhost", 75 }, 76 want: "http://localhost", 77 }, { 78 cfg: httpproxy.Config{ 79 HTTPProxy: "https://cache.corp.example.com", 80 }, 81 want: "https://cache.corp.example.com", 82 }, { 83 cfg: httpproxy.Config{ 84 HTTPProxy: "http://127.0.0.1:8080", 85 }, 86 want: "http://127.0.0.1:8080", 87 }, { 88 cfg: httpproxy.Config{ 89 HTTPProxy: "https://127.0.0.1:8080", 90 }, 91 want: "https://127.0.0.1:8080", 92 }, { 93 cfg: httpproxy.Config{ 94 HTTPProxy: "socks5://127.0.0.1", 95 }, 96 want: "socks5://127.0.0.1", 97 }, { 98 // Preserve unknown schemes. 99 cfg: httpproxy.Config{ 100 HTTPProxy: "foo://host", 101 }, 102 want: "foo://host", 103 }, { 104 // Don't use secure for http 105 cfg: httpproxy.Config{ 106 HTTPProxy: "http.proxy.tld", 107 HTTPSProxy: "secure.proxy.tld", 108 }, 109 req: "http://insecure.tld/", 110 want: "http://http.proxy.tld", 111 }, { 112 // Use secure for https. 113 cfg: httpproxy.Config{ 114 HTTPProxy: "http.proxy.tld", 115 HTTPSProxy: "secure.proxy.tld", 116 }, 117 req: "https://secure.tld/", 118 want: "http://secure.proxy.tld", 119 }, { 120 cfg: httpproxy.Config{ 121 HTTPProxy: "http.proxy.tld", 122 HTTPSProxy: "https://secure.proxy.tld", 123 }, 124 req: "https://secure.tld/", 125 want: "https://secure.proxy.tld", 126 }, { 127 cfg: httpproxy.Config{ 128 HTTPProxy: "http.proxy.tld", 129 }, 130 req: "https://secure.tld/", 131 want: "<nil>", 132 }, { 133 cfg: httpproxy.Config{ 134 HTTPProxy: "http.proxy.tld", 135 }, 136 req: "ftp://insecure.tld/", 137 want: "<nil>", 138 }, { 139 // Issue 16405: don't use HTTP_PROXY in a CGI environment, 140 // where HTTP_PROXY can be attacker-controlled. 141 cfg: httpproxy.Config{ 142 HTTPProxy: "http://10.1.2.3:8080", 143 CGI: true, 144 }, 145 want: "<nil>", 146 wanterr: errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy"), 147 }, { 148 // HTTPS proxy is still used even in CGI environment. 149 // (perhaps dubious but it's the historical behaviour). 150 cfg: httpproxy.Config{ 151 HTTPSProxy: "https://secure.proxy.tld", 152 CGI: true, 153 }, 154 req: "https://secure.tld/", 155 want: "https://secure.proxy.tld", 156 }, { 157 want: "<nil>", 158 }, { 159 cfg: httpproxy.Config{ 160 NoProxy: "example.com", 161 HTTPProxy: "proxy", 162 }, 163 req: "http://example.com/", 164 want: "<nil>", 165 }, { 166 cfg: httpproxy.Config{ 167 NoProxy: ".example.com", 168 HTTPProxy: "proxy", 169 }, 170 req: "http://example.com/", 171 want: "http://proxy", 172 }, { 173 cfg: httpproxy.Config{ 174 NoProxy: "ample.com", 175 HTTPProxy: "proxy", 176 }, 177 req: "http://example.com/", 178 want: "http://proxy", 179 }, { 180 cfg: httpproxy.Config{ 181 NoProxy: "example.com", 182 HTTPProxy: "proxy", 183 }, 184 req: "http://foo.example.com/", 185 want: "<nil>", 186 }, { 187 cfg: httpproxy.Config{ 188 NoProxy: ".foo.com", 189 HTTPProxy: "proxy", 190 }, 191 req: "http://example.com/", 192 want: "http://proxy", 193 }, { 194 cfg: httpproxy.Config{ 195 NoProxy: ".示例.com", 196 HTTPProxy: "proxy", 197 }, 198 req: "http://www.示例.com", 199 want: "<nil>", 200 }, { 201 cfg: httpproxy.Config{ 202 NoProxy: "xn--fsq092h.com", 203 HTTPProxy: "proxy", 204 }, 205 req: "http://www.示例.com", 206 want: "<nil>", 207 }, { 208 cfg: httpproxy.Config{ 209 NoProxy: "示例.com", 210 HTTPProxy: "proxy", 211 }, 212 req: "http://www.xn--fsq092h.com", 213 want: "<nil>", 214 }, 215 } 216 217 func testProxyForURL(t *testing.T, tt proxyForURLTest) { 218 setHelper(t) 219 reqURLStr := tt.req 220 if reqURLStr == "" { 221 reqURLStr = "http://example.com" 222 } 223 reqURL, err := url.Parse(reqURLStr) 224 if err != nil { 225 t.Errorf("invalid URL %q", reqURLStr) 226 return 227 } 228 cfg := tt.cfg 229 proxyForURL := cfg.ProxyFunc() 230 url, err := proxyForURL(reqURL) 231 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { 232 t.Errorf("%v: got error = %q, want %q", tt, g, e) 233 return 234 } 235 if got := fmt.Sprintf("%s", url); got != tt.want { 236 t.Errorf("%v: got URL = %q, want %q", tt, url, tt.want) 237 } 238 239 // Check that changing the Config doesn't change the results 240 // of the functuon. 241 cfg = httpproxy.Config{} 242 url, err = proxyForURL(reqURL) 243 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { 244 t.Errorf("(after mutating config) %v: got error = %q, want %q", tt, g, e) 245 return 246 } 247 if got := fmt.Sprintf("%s", url); got != tt.want { 248 t.Errorf("(after mutating config) %v: got URL = %q, want %q", tt, url, tt.want) 249 } 250 } 251 252 func TestProxyForURL(t *testing.T) { 253 for _, tt := range proxyForURLTests { 254 testProxyForURL(t, tt) 255 } 256 } 257 258 func TestFromEnvironment(t *testing.T) { 259 os.Setenv("HTTP_PROXY", "httpproxy") 260 os.Setenv("HTTPS_PROXY", "httpsproxy") 261 os.Setenv("NO_PROXY", "noproxy") 262 os.Setenv("REQUEST_METHOD", "") 263 got := httpproxy.FromEnvironment() 264 want := httpproxy.Config{ 265 HTTPProxy: "httpproxy", 266 HTTPSProxy: "httpsproxy", 267 NoProxy: "noproxy", 268 } 269 if *got != want { 270 t.Errorf("unexpected proxy config, got %#v want %#v", got, want) 271 } 272 } 273 274 func TestFromEnvironmentWithRequestMethod(t *testing.T) { 275 os.Setenv("HTTP_PROXY", "httpproxy") 276 os.Setenv("HTTPS_PROXY", "httpsproxy") 277 os.Setenv("NO_PROXY", "noproxy") 278 os.Setenv("REQUEST_METHOD", "PUT") 279 got := httpproxy.FromEnvironment() 280 want := httpproxy.Config{ 281 HTTPProxy: "httpproxy", 282 HTTPSProxy: "httpsproxy", 283 NoProxy: "noproxy", 284 CGI: true, 285 } 286 if *got != want { 287 t.Errorf("unexpected proxy config, got %#v want %#v", got, want) 288 } 289 } 290 291 func TestFromEnvironmentLowerCase(t *testing.T) { 292 os.Setenv("http_proxy", "httpproxy") 293 os.Setenv("https_proxy", "httpsproxy") 294 os.Setenv("no_proxy", "noproxy") 295 os.Setenv("REQUEST_METHOD", "") 296 got := httpproxy.FromEnvironment() 297 want := httpproxy.Config{ 298 HTTPProxy: "httpproxy", 299 HTTPSProxy: "httpsproxy", 300 NoProxy: "noproxy", 301 } 302 if *got != want { 303 t.Errorf("unexpected proxy config, got %#v want %#v", got, want) 304 } 305 } 306 307 var UseProxyTests = []struct { 308 host string 309 match bool 310 }{ 311 // Never proxy localhost: 312 {"localhost", false}, 313 {"127.0.0.1", false}, 314 {"127.0.0.2", false}, 315 {"[::1]", false}, 316 {"[::2]", true}, // not a loopback address 317 318 {"192.168.1.1", false}, // matches exact IPv4 319 {"192.168.1.2", true}, // ports do not match 320 {"192.168.1.3", false}, // matches exact IPv4:port 321 {"192.168.1.4", true}, // no match 322 {"10.0.0.2", false}, // matches IPv4/CIDR 323 {"[2001:db8::52:0:1]", false}, // matches exact IPv6 324 {"[2001:db8::52:0:2]", true}, // no match 325 {"[2001:db8::52:0:3]", false}, // matches exact [IPv6]:port 326 {"[2002:db8:a::123]", false}, // matches IPv6/CIDR 327 {"[fe80::424b:c8be:1643:a1b6]", true}, // no match 328 329 {"barbaz.net", true}, // does not match as .barbaz.net 330 {"www.barbaz.net", false}, // does match as .barbaz.net 331 {"foobar.com", false}, // does match as foobar.com 332 {"www.foobar.com", false}, // match because NO_PROXY includes "foobar.com" 333 {"foofoobar.com", true}, // not match as a part of foobar.com 334 {"baz.com", true}, // not match as a part of barbaz.com 335 {"localhost.net", true}, // not match as suffix of address 336 {"local.localhost", true}, // not match as prefix as address 337 {"barbarbaz.net", true}, // not match, wrong domain 338 {"wildcard.io", true}, // does not match as *.wildcard.io 339 {"nested.wildcard.io", false}, // match as *.wildcard.io 340 {"awildcard.io", true}, // not a match because of '*' 341 } 342 343 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" 344 345 func TestUseProxy(t *testing.T) { 346 cfg := &httpproxy.Config{ 347 NoProxy: noProxy, 348 } 349 for _, test := range UseProxyTests { 350 if httpproxy.ExportUseProxy(cfg, test.host+":80") != test.match { 351 t.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) 352 } 353 } 354 } 355 356 func TestInvalidNoProxy(t *testing.T) { 357 cfg := &httpproxy.Config{ 358 NoProxy: ":1", 359 } 360 ok := httpproxy.ExportUseProxy(cfg, "example.com:80") // should not panic 361 if !ok { 362 t.Errorf("useProxy unexpected return; got false; want true") 363 } 364 } 365 366 func TestAllNoProxy(t *testing.T) { 367 cfg := &httpproxy.Config{ 368 NoProxy: "*", 369 } 370 for _, test := range UseProxyTests { 371 if httpproxy.ExportUseProxy(cfg, test.host+":80") != false { 372 t.Errorf("useProxy(%v) = true, want false", test.host) 373 } 374 } 375 } 376 377 func BenchmarkProxyForURL(b *testing.B) { 378 cfg := &httpproxy.Config{ 379 HTTPProxy: "http://proxy.example.org", 380 HTTPSProxy: "https://proxy.example.org", 381 NoProxy: noProxy, 382 } 383 for _, test := range UseProxyTests { 384 u, err := url.Parse("https://" + test.host + ":80") 385 if err != nil { 386 b.Fatalf("parsed failed: %s", test.host) 387 } 388 proxyFunc := cfg.ProxyFunc() 389 b.Run(test.host, func(b *testing.B) { 390 for n := 0; n < b.N; n++ { 391 if au, e := proxyFunc(u); e != nil && test.match == (au != nil) { 392 b.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) 393 } 394 } 395 }) 396 } 397 }