Blame vendor/golang.org/x/net/http/httpproxy/proxy.go

Packit 63bb0d
// Copyright 2017 The Go Authors. All rights reserved.
Packit 63bb0d
// Use of this source code is governed by a BSD-style
Packit 63bb0d
// license that can be found in the LICENSE file.
Packit 63bb0d
Packit 63bb0d
// Package httpproxy provides support for HTTP proxy determination
Packit 63bb0d
// based on environment variables, as provided by net/http's
Packit 63bb0d
// ProxyFromEnvironment function.
Packit 63bb0d
//
Packit 63bb0d
// The API is not subject to the Go 1 compatibility promise and may change at
Packit 63bb0d
// any time.
Packit 63bb0d
package httpproxy
Packit 63bb0d
Packit 63bb0d
import (
Packit 63bb0d
	"errors"
Packit 63bb0d
	"fmt"
Packit 63bb0d
	"net"
Packit 63bb0d
	"net/url"
Packit 63bb0d
	"os"
Packit 63bb0d
	"strings"
Packit 63bb0d
	"unicode/utf8"
Packit 63bb0d
Packit 63bb0d
	"golang.org/x/net/idna"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// Config holds configuration for HTTP proxy settings. See
Packit 63bb0d
// FromEnvironment for details.
Packit 63bb0d
type Config struct {
Packit 63bb0d
	// HTTPProxy represents the value of the HTTP_PROXY or
Packit 63bb0d
	// http_proxy environment variable. It will be used as the proxy
Packit 63bb0d
	// URL for HTTP requests and HTTPS requests unless overridden by
Packit 63bb0d
	// HTTPSProxy or NoProxy.
Packit 63bb0d
	HTTPProxy string
Packit 63bb0d
Packit 63bb0d
	// HTTPSProxy represents the HTTPS_PROXY or https_proxy
Packit 63bb0d
	// environment variable. It will be used as the proxy URL for
Packit 63bb0d
	// HTTPS requests unless overridden by NoProxy.
Packit 63bb0d
	HTTPSProxy string
Packit 63bb0d
Packit 63bb0d
	// NoProxy represents the NO_PROXY or no_proxy environment
Packit 63bb0d
	// variable. It specifies a string that contains comma-separated values
Packit 63bb0d
	// specifying hosts that should be excluded from proxying. Each value is
Packit 63bb0d
	// represented by an IP address prefix (1.2.3.4), an IP address prefix in
Packit 63bb0d
	// CIDR notation (1.2.3.4/8), a domain name, or a special DNS label (*).
Packit 63bb0d
	// An IP address prefix and domain name can also include a literal port
Packit 63bb0d
	// number (1.2.3.4:80).
Packit 63bb0d
	// A domain name matches that name and all subdomains. A domain name with
Packit 63bb0d
	// a leading "." matches subdomains only. For example "foo.com" matches
Packit 63bb0d
	// "foo.com" and "bar.foo.com"; ".y.com" matches "x.y.com" but not "y.com".
Packit 63bb0d
	// A single asterisk (*) indicates that no proxying should be done.
Packit 63bb0d
	// A best effort is made to parse the string and errors are
Packit 63bb0d
	// ignored.
Packit 63bb0d
	NoProxy string
Packit 63bb0d
Packit 63bb0d
	// CGI holds whether the current process is running
Packit 63bb0d
	// as a CGI handler (FromEnvironment infers this from the
Packit 63bb0d
	// presence of a REQUEST_METHOD environment variable).
Packit 63bb0d
	// When this is set, ProxyForURL will return an error
Packit 63bb0d
	// when HTTPProxy applies, because a client could be
Packit 63bb0d
	// setting HTTP_PROXY maliciously. See https://golang.org/s/cgihttpproxy.
Packit 63bb0d
	CGI bool
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// config holds the parsed configuration for HTTP proxy settings.
Packit 63bb0d
type config struct {
Packit 63bb0d
	// Config represents the original configuration as defined above.
Packit 63bb0d
	Config
Packit 63bb0d
Packit 63bb0d
	// httpsProxy is the parsed URL of the HTTPSProxy if defined.
Packit 63bb0d
	httpsProxy *url.URL
Packit 63bb0d
Packit 63bb0d
	// httpProxy is the parsed URL of the HTTPProxy if defined.
Packit 63bb0d
	httpProxy *url.URL
Packit 63bb0d
Packit 63bb0d
	// ipMatchers represent all values in the NoProxy that are IP address
Packit 63bb0d
	// prefixes or an IP address in CIDR notation.
Packit 63bb0d
	ipMatchers []matcher
Packit 63bb0d
Packit 63bb0d
	// domainMatchers represent all values in the NoProxy that are a domain
Packit 63bb0d
	// name or hostname & domain name
Packit 63bb0d
	domainMatchers []matcher
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// FromEnvironment returns a Config instance populated from the
Packit 63bb0d
// environment variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the
Packit 63bb0d
// lowercase versions thereof). HTTPS_PROXY takes precedence over
Packit 63bb0d
// HTTP_PROXY for https requests.
Packit 63bb0d
//
Packit 63bb0d
// The environment values may be either a complete URL or a
Packit 63bb0d
// "host[:port]", in which case the "http" scheme is assumed. An error
Packit 63bb0d
// is returned if the value is a different form.
Packit 63bb0d
func FromEnvironment() *Config {
Packit 63bb0d
	return &Config{
Packit 63bb0d
		HTTPProxy:  getEnvAny("HTTP_PROXY", "http_proxy"),
Packit 63bb0d
		HTTPSProxy: getEnvAny("HTTPS_PROXY", "https_proxy"),
Packit 63bb0d
		NoProxy:    getEnvAny("NO_PROXY", "no_proxy"),
Packit 63bb0d
		CGI:        os.Getenv("REQUEST_METHOD") != "",
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func getEnvAny(names ...string) string {
Packit 63bb0d
	for _, n := range names {
Packit 63bb0d
		if val := os.Getenv(n); val != "" {
Packit 63bb0d
			return val
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return ""
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// ProxyFunc returns a function that determines the proxy URL to use for
Packit 63bb0d
// a given request URL. Changing the contents of cfg will not affect
Packit 63bb0d
// proxy functions created earlier.
Packit 63bb0d
//
Packit 63bb0d
// A nil URL and nil error are returned if no proxy is defined in the
Packit 63bb0d
// environment, or a proxy should not be used for the given request, as
Packit 63bb0d
// defined by NO_PROXY.
Packit 63bb0d
//
Packit 63bb0d
// As a special case, if req.URL.Host is "localhost" (with or without a
Packit 63bb0d
// port number), then a nil URL and nil error will be returned.
Packit 63bb0d
func (cfg *Config) ProxyFunc() func(reqURL *url.URL) (*url.URL, error) {
Packit 63bb0d
	// Preprocess the Config settings for more efficient evaluation.
Packit 63bb0d
	cfg1 := &config{
Packit 63bb0d
		Config: *cfg,
Packit 63bb0d
	}
Packit 63bb0d
	cfg1.init()
Packit 63bb0d
	return cfg1.proxyForURL
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (cfg *config) proxyForURL(reqURL *url.URL) (*url.URL, error) {
Packit 63bb0d
	var proxy *url.URL
Packit 63bb0d
	if reqURL.Scheme == "https" {
Packit 63bb0d
		proxy = cfg.httpsProxy
Packit 63bb0d
	}
Packit 63bb0d
	if proxy == nil {
Packit 63bb0d
		proxy = cfg.httpProxy
Packit 63bb0d
		if proxy != nil && cfg.CGI {
Packit 63bb0d
			return nil, errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	if proxy == nil {
Packit 63bb0d
		return nil, nil
Packit 63bb0d
	}
Packit 63bb0d
	if !cfg.useProxy(canonicalAddr(reqURL)) {
Packit 63bb0d
		return nil, nil
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return proxy, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func parseProxy(proxy string) (*url.URL, error) {
Packit 63bb0d
	if proxy == "" {
Packit 63bb0d
		return nil, nil
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	proxyURL, err := url.Parse(proxy)
Packit 63bb0d
	if err != nil ||
Packit 63bb0d
		(proxyURL.Scheme != "http" &&
Packit 63bb0d
			proxyURL.Scheme != "https" &&
Packit 63bb0d
			proxyURL.Scheme != "socks5") {
Packit 63bb0d
		// proxy was bogus. Try prepending "http://" to it and
Packit 63bb0d
		// see if that parses correctly. If not, we fall
Packit 63bb0d
		// through and complain about the original one.
Packit 63bb0d
		if proxyURL, err := url.Parse("http://" + proxy); err == nil {
Packit 63bb0d
			return proxyURL, nil
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
Packit 63bb0d
	}
Packit 63bb0d
	return proxyURL, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// useProxy reports whether requests to addr should use a proxy,
Packit 63bb0d
// according to the NO_PROXY or no_proxy environment variable.
Packit 63bb0d
// addr is always a canonicalAddr with a host and port.
Packit 63bb0d
func (cfg *config) useProxy(addr string) bool {
Packit 63bb0d
	if len(addr) == 0 {
Packit 63bb0d
		return true
Packit 63bb0d
	}
Packit 63bb0d
	host, port, err := net.SplitHostPort(addr)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return false
Packit 63bb0d
	}
Packit 63bb0d
	if host == "localhost" {
Packit 63bb0d
		return false
Packit 63bb0d
	}
Packit 63bb0d
	ip := net.ParseIP(host)
Packit 63bb0d
	if ip != nil {
Packit 63bb0d
		if ip.IsLoopback() {
Packit 63bb0d
			return false
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	addr = strings.ToLower(strings.TrimSpace(host))
Packit 63bb0d
Packit 63bb0d
	if ip != nil {
Packit 63bb0d
		for _, m := range cfg.ipMatchers {
Packit 63bb0d
			if m.match(addr, port, ip) {
Packit 63bb0d
				return false
Packit 63bb0d
			}
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	for _, m := range cfg.domainMatchers {
Packit 63bb0d
		if m.match(addr, port, ip) {
Packit 63bb0d
			return false
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return true
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (c *config) init() {
Packit 63bb0d
	if parsed, err := parseProxy(c.HTTPProxy); err == nil {
Packit 63bb0d
		c.httpProxy = parsed
Packit 63bb0d
	}
Packit 63bb0d
	if parsed, err := parseProxy(c.HTTPSProxy); err == nil {
Packit 63bb0d
		c.httpsProxy = parsed
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	for _, p := range strings.Split(c.NoProxy, ",") {
Packit 63bb0d
		p = strings.ToLower(strings.TrimSpace(p))
Packit 63bb0d
		if len(p) == 0 {
Packit 63bb0d
			continue
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		if p == "*" {
Packit 63bb0d
			c.ipMatchers = []matcher{allMatch{}}
Packit 63bb0d
			c.domainMatchers = []matcher{allMatch{}}
Packit 63bb0d
			return
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		// IPv4/CIDR, IPv6/CIDR
Packit 63bb0d
		if _, pnet, err := net.ParseCIDR(p); err == nil {
Packit 63bb0d
			c.ipMatchers = append(c.ipMatchers, cidrMatch{cidr: pnet})
Packit 63bb0d
			continue
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		// IPv4:port, [IPv6]:port
Packit 63bb0d
		phost, pport, err := net.SplitHostPort(p)
Packit 63bb0d
		if err == nil {
Packit 63bb0d
			if len(phost) == 0 {
Packit 63bb0d
				// There is no host part, likely the entry is malformed; ignore.
Packit 63bb0d
				continue
Packit 63bb0d
			}
Packit 63bb0d
			if phost[0] == '[' && phost[len(phost)-1] == ']' {
Packit 63bb0d
				phost = phost[1 : len(phost)-1]
Packit 63bb0d
			}
Packit 63bb0d
		} else {
Packit 63bb0d
			phost = p
Packit 63bb0d
		}
Packit 63bb0d
		// IPv4, IPv6
Packit 63bb0d
		if pip := net.ParseIP(phost); pip != nil {
Packit 63bb0d
			c.ipMatchers = append(c.ipMatchers, ipMatch{ip: pip, port: pport})
Packit 63bb0d
			continue
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		if len(phost) == 0 {
Packit 63bb0d
			// There is no host part, likely the entry is malformed; ignore.
Packit 63bb0d
			continue
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		// domain.com or domain.com:80
Packit 63bb0d
		// foo.com matches bar.foo.com
Packit 63bb0d
		// .domain.com or .domain.com:port
Packit 63bb0d
		// *.domain.com or *.domain.com:port
Packit 63bb0d
		if strings.HasPrefix(phost, "*.") {
Packit 63bb0d
			phost = phost[1:]
Packit 63bb0d
		}
Packit 63bb0d
		matchHost := false
Packit 63bb0d
		if phost[0] != '.' {
Packit 63bb0d
			matchHost = true
Packit 63bb0d
			phost = "." + phost
Packit 63bb0d
		}
Packit 63bb0d
		c.domainMatchers = append(c.domainMatchers, domainMatch{host: phost, port: pport, matchHost: matchHost})
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
var portMap = map[string]string{
Packit 63bb0d
	"http":   "80",
Packit 63bb0d
	"https":  "443",
Packit 63bb0d
	"socks5": "1080",
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// canonicalAddr returns url.Host but always with a ":port" suffix
Packit 63bb0d
func canonicalAddr(url *url.URL) string {
Packit 63bb0d
	addr := url.Hostname()
Packit 63bb0d
	if v, err := idnaASCII(addr); err == nil {
Packit 63bb0d
		addr = v
Packit 63bb0d
	}
Packit 63bb0d
	port := url.Port()
Packit 63bb0d
	if port == "" {
Packit 63bb0d
		port = portMap[url.Scheme]
Packit 63bb0d
	}
Packit 63bb0d
	return net.JoinHostPort(addr, port)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
Packit 63bb0d
// return true if the string includes a port.
Packit 63bb0d
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
Packit 63bb0d
Packit 63bb0d
func idnaASCII(v string) (string, error) {
Packit 63bb0d
	// TODO: Consider removing this check after verifying performance is okay.
Packit 63bb0d
	// Right now punycode verification, length checks, context checks, and the
Packit 63bb0d
	// permissible character tests are all omitted. It also prevents the ToASCII
Packit 63bb0d
	// call from salvaging an invalid IDN, when possible. As a result it may be
Packit 63bb0d
	// possible to have two IDNs that appear identical to the user where the
Packit 63bb0d
	// ASCII-only version causes an error downstream whereas the non-ASCII
Packit 63bb0d
	// version does not.
Packit 63bb0d
	// Note that for correct ASCII IDNs ToASCII will only do considerably more
Packit 63bb0d
	// work, but it will not cause an allocation.
Packit 63bb0d
	if isASCII(v) {
Packit 63bb0d
		return v, nil
Packit 63bb0d
	}
Packit 63bb0d
	return idna.Lookup.ToASCII(v)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func isASCII(s string) bool {
Packit 63bb0d
	for i := 0; i < len(s); i++ {
Packit 63bb0d
		if s[i] >= utf8.RuneSelf {
Packit 63bb0d
			return false
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return true
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// matcher represents the matching rule for a given value in the NO_PROXY list
Packit 63bb0d
type matcher interface {
Packit 63bb0d
	// match returns true if the host and optional port or ip and optional port
Packit 63bb0d
	// are allowed
Packit 63bb0d
	match(host, port string, ip net.IP) bool
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// allMatch matches on all possible inputs
Packit 63bb0d
type allMatch struct{}
Packit 63bb0d
Packit 63bb0d
func (a allMatch) match(host, port string, ip net.IP) bool {
Packit 63bb0d
	return true
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type cidrMatch struct {
Packit 63bb0d
	cidr *net.IPNet
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (m cidrMatch) match(host, port string, ip net.IP) bool {
Packit 63bb0d
	return m.cidr.Contains(ip)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type ipMatch struct {
Packit 63bb0d
	ip   net.IP
Packit 63bb0d
	port string
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (m ipMatch) match(host, port string, ip net.IP) bool {
Packit 63bb0d
	if m.ip.Equal(ip) {
Packit 63bb0d
		return m.port == "" || m.port == port
Packit 63bb0d
	}
Packit 63bb0d
	return false
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type domainMatch struct {
Packit 63bb0d
	host string
Packit 63bb0d
	port string
Packit 63bb0d
Packit 63bb0d
	matchHost bool
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (m domainMatch) match(host, port string, ip net.IP) bool {
Packit 63bb0d
	if strings.HasSuffix(host, m.host) || (m.matchHost && host == m.host[1:]) {
Packit 63bb0d
		return m.port == "" || m.port == port
Packit 63bb0d
	}
Packit 63bb0d
	return false
Packit 63bb0d
}