// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package httpproxy_test import ( "bytes" "errors" "fmt" "net/url" "os" "strings" "testing" "golang.org/x/net/http/httpproxy" ) // setHelper calls t.Helper() for Go 1.9+ (see go19_test.go) and does nothing otherwise. var setHelper = func(t *testing.T) {} type proxyForURLTest struct { cfg httpproxy.Config req string // URL to fetch; blank means "http://example.com" want string wanterr error } func (t proxyForURLTest) String() string { var buf bytes.Buffer space := func() { if buf.Len() > 0 { buf.WriteByte(' ') } } if t.cfg.HTTPProxy != "" { fmt.Fprintf(&buf, "http_proxy=%q", t.cfg.HTTPProxy) } if t.cfg.HTTPSProxy != "" { space() fmt.Fprintf(&buf, "https_proxy=%q", t.cfg.HTTPSProxy) } if t.cfg.NoProxy != "" { space() fmt.Fprintf(&buf, "no_proxy=%q", t.cfg.NoProxy) } req := "http://example.com" if t.req != "" { req = t.req } space() fmt.Fprintf(&buf, "req=%q", req) return strings.TrimSpace(buf.String()) } var proxyForURLTests = []proxyForURLTest{{ cfg: httpproxy.Config{ HTTPProxy: "127.0.0.1:8080", }, want: "http://127.0.0.1:8080", }, { cfg: httpproxy.Config{ HTTPProxy: "cache.corp.example.com:1234", }, want: "http://cache.corp.example.com:1234", }, { cfg: httpproxy.Config{ HTTPProxy: "cache.corp.example.com", }, want: "http://cache.corp.example.com", }, { cfg: httpproxy.Config{ HTTPProxy: "https://cache.corp.example.com", }, want: "https://cache.corp.example.com", }, { cfg: httpproxy.Config{ HTTPProxy: "http://127.0.0.1:8080", }, want: "http://127.0.0.1:8080", }, { cfg: httpproxy.Config{ HTTPProxy: "https://127.0.0.1:8080", }, want: "https://127.0.0.1:8080", }, { cfg: httpproxy.Config{ HTTPProxy: "socks5://127.0.0.1", }, want: "socks5://127.0.0.1", }, { // Don't use secure for http cfg: httpproxy.Config{ HTTPProxy: "http.proxy.tld", HTTPSProxy: "secure.proxy.tld", }, req: "http://insecure.tld/", want: "http://http.proxy.tld", }, { // Use secure for https. cfg: httpproxy.Config{ HTTPProxy: "http.proxy.tld", HTTPSProxy: "secure.proxy.tld", }, req: "https://secure.tld/", want: "http://secure.proxy.tld", }, { cfg: httpproxy.Config{ HTTPProxy: "http.proxy.tld", HTTPSProxy: "https://secure.proxy.tld", }, req: "https://secure.tld/", want: "https://secure.proxy.tld", }, { // Issue 16405: don't use HTTP_PROXY in a CGI environment, // where HTTP_PROXY can be attacker-controlled. cfg: httpproxy.Config{ HTTPProxy: "http://10.1.2.3:8080", CGI: true, }, want: "", wanterr: errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy"), }, { // HTTPS proxy is still used even in CGI environment. // (perhaps dubious but it's the historical behaviour). cfg: httpproxy.Config{ HTTPSProxy: "https://secure.proxy.tld", CGI: true, }, req: "https://secure.tld/", want: "https://secure.proxy.tld", }, { want: "", }, { cfg: httpproxy.Config{ NoProxy: "example.com", HTTPProxy: "proxy", }, req: "http://example.com/", want: "", }, { cfg: httpproxy.Config{ NoProxy: ".example.com", HTTPProxy: "proxy", }, req: "http://example.com/", want: "http://proxy", }, { cfg: httpproxy.Config{ NoProxy: "ample.com", HTTPProxy: "proxy", }, req: "http://example.com/", want: "http://proxy", }, { cfg: httpproxy.Config{ NoProxy: "example.com", HTTPProxy: "proxy", }, req: "http://foo.example.com/", want: "", }, { cfg: httpproxy.Config{ NoProxy: ".foo.com", HTTPProxy: "proxy", }, req: "http://example.com/", want: "http://proxy", }} func testProxyForURL(t *testing.T, tt proxyForURLTest) { setHelper(t) reqURLStr := tt.req if reqURLStr == "" { reqURLStr = "http://example.com" } reqURL, err := url.Parse(reqURLStr) if err != nil { t.Errorf("invalid URL %q", reqURLStr) return } cfg := tt.cfg proxyForURL := cfg.ProxyFunc() url, err := proxyForURL(reqURL) if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { t.Errorf("%v: got error = %q, want %q", tt, g, e) return } if got := fmt.Sprintf("%s", url); got != tt.want { t.Errorf("%v: got URL = %q, want %q", tt, url, tt.want) } // Check that changing the Config doesn't change the results // of the functuon. cfg = httpproxy.Config{} url, err = proxyForURL(reqURL) if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { t.Errorf("(after mutating config) %v: got error = %q, want %q", tt, g, e) return } if got := fmt.Sprintf("%s", url); got != tt.want { t.Errorf("(after mutating config) %v: got URL = %q, want %q", tt, url, tt.want) } } func TestProxyForURL(t *testing.T) { for _, tt := range proxyForURLTests { testProxyForURL(t, tt) } } func TestFromEnvironment(t *testing.T) { os.Setenv("HTTP_PROXY", "httpproxy") os.Setenv("HTTPS_PROXY", "httpsproxy") os.Setenv("NO_PROXY", "noproxy") os.Setenv("REQUEST_METHOD", "") got := httpproxy.FromEnvironment() want := httpproxy.Config{ HTTPProxy: "httpproxy", HTTPSProxy: "httpsproxy", NoProxy: "noproxy", } if *got != want { t.Errorf("unexpected proxy config, got %#v want %#v", got, want) } } func TestFromEnvironmentWithRequestMethod(t *testing.T) { os.Setenv("HTTP_PROXY", "httpproxy") os.Setenv("HTTPS_PROXY", "httpsproxy") os.Setenv("NO_PROXY", "noproxy") os.Setenv("REQUEST_METHOD", "PUT") got := httpproxy.FromEnvironment() want := httpproxy.Config{ HTTPProxy: "httpproxy", HTTPSProxy: "httpsproxy", NoProxy: "noproxy", CGI: true, } if *got != want { t.Errorf("unexpected proxy config, got %#v want %#v", got, want) } } func TestFromEnvironmentLowerCase(t *testing.T) { os.Setenv("http_proxy", "httpproxy") os.Setenv("https_proxy", "httpsproxy") os.Setenv("no_proxy", "noproxy") os.Setenv("REQUEST_METHOD", "") got := httpproxy.FromEnvironment() want := httpproxy.Config{ HTTPProxy: "httpproxy", HTTPSProxy: "httpsproxy", NoProxy: "noproxy", } if *got != want { t.Errorf("unexpected proxy config, got %#v want %#v", got, want) } } var UseProxyTests = []struct { host string match bool }{ // Never proxy localhost: {"localhost", false}, {"127.0.0.1", false}, {"127.0.0.2", false}, {"[::1]", false}, {"[::2]", true}, // not a loopback address {"192.168.1.1", false}, // matches exact IPv4 {"192.168.1.2", true}, // ports do not match {"192.168.1.3", false}, // matches exact IPv4:port {"192.168.1.4", true}, // no match {"10.0.0.2", false}, // matches IPv4/CIDR {"[2001:db8::52:0:1]", false}, // matches exact IPv6 {"[2001:db8::52:0:2]", true}, // no match {"[2001:db8::52:0:3]", false}, // matches exact [IPv6]:port {"[2002:db8:a::123]", false}, // matches IPv6/CIDR {"[fe80::424b:c8be:1643:a1b6]", true}, // no match {"barbaz.net", true}, // does not match as .barbaz.net {"www.barbaz.net", false}, // does match as .barbaz.net {"foobar.com", false}, // does match as foobar.com {"www.foobar.com", false}, // match because NO_PROXY includes "foobar.com" {"foofoobar.com", true}, // not match as a part of foobar.com {"baz.com", true}, // not match as a part of barbaz.com {"localhost.net", true}, // not match as suffix of address {"local.localhost", true}, // not match as prefix as address {"barbarbaz.net", true}, // not match, wrong domain {"wildcard.io", true}, // does not match as *.wildcard.io {"nested.wildcard.io", false}, // match as *.wildcard.io {"awildcard.io", true}, // not a match because of '*' } 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" func TestUseProxy(t *testing.T) { cfg := &httpproxy.Config{ NoProxy: noProxy, } for _, test := range UseProxyTests { if httpproxy.ExportUseProxy(cfg, test.host+":80") != test.match { t.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) } } } func TestInvalidNoProxy(t *testing.T) { cfg := &httpproxy.Config{ NoProxy: ":1", } ok := httpproxy.ExportUseProxy(cfg, "example.com:80") // should not panic if !ok { t.Errorf("useProxy unexpected return; got false; want true") } } func TestAllNoProxy(t *testing.T) { cfg := &httpproxy.Config{ NoProxy: "*", } for _, test := range UseProxyTests { if httpproxy.ExportUseProxy(cfg, test.host+":80") != false { t.Errorf("useProxy(%v) = true, want false", test.host) } } } func BenchmarkProxyForURL(b *testing.B) { cfg := &httpproxy.Config{ HTTPProxy: "http://proxy.example.org", HTTPSProxy: "https://proxy.example.org", NoProxy: noProxy, } for _, test := range UseProxyTests { u, err := url.Parse("https://" + test.host + ":80") if err != nil { b.Fatalf("parsed failed: %s", test.host) } proxyFunc := cfg.ProxyFunc() b.Run(test.host, func(b *testing.B) { for n := 0; n < b.N; n++ { if au, e := proxyFunc(u); e != nil && test.match == (au != nil) { b.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) } } }) } }