| // 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" |
| |
| "internal/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: "<nil>", |
| 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: "<nil>", |
| }, { |
| cfg: httpproxy.Config{ |
| NoProxy: "example.com", |
| HTTPProxy: "proxy", |
| }, |
| req: "http://example.com/", |
| want: "<nil>", |
| }, { |
| 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: "<nil>", |
| }, { |
| 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) |
| } |
| } |
| }) |
| } |
| } |