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  }