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