|
| 1 | +// Copyright 2018 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 http |
| 6 | + |
| 7 | +import ( |
| 8 | + "errors" |
| 9 | + "fmt" |
| 10 | + "golang_org/x/net/idna" |
| 11 | + "net" |
| 12 | + "net/url" |
| 13 | + "os" |
| 14 | + "strings" |
| 15 | + "sync" |
| 16 | + "unicode/utf8" |
| 17 | +) |
| 18 | + |
| 19 | +// ProxyFromEnvironment returns the URL of the proxy to use for a |
| 20 | +// given request, as indicated by the environment variables |
| 21 | +// HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions |
| 22 | +// thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https |
| 23 | +// requests. |
| 24 | +// |
| 25 | +// The environment values may be either a complete URL or a |
| 26 | +// "host[:port]", in which case the "http" scheme is assumed. |
| 27 | +// An error is returned if the value is a different form. |
| 28 | +// |
| 29 | +// A nil URL and nil error are returned if no proxy is defined in the |
| 30 | +// environment, or a proxy should not be used for the given request, |
| 31 | +// as defined by NO_PROXY. |
| 32 | +// |
| 33 | +// As a special case, if req.URL.Host is "localhost" (with or without |
| 34 | +// a port number), then a nil URL and nil error will be returned. |
| 35 | +func ProxyFromEnvironment(req *Request) (*url.URL, error) { |
| 36 | + var proxy string |
| 37 | + if req.URL.Scheme == "https" { |
| 38 | + proxy = httpsProxyEnv.Get() |
| 39 | + } |
| 40 | + if proxy == "" { |
| 41 | + proxy = httpProxyEnv.Get() |
| 42 | + if proxy != "" && os.Getenv("REQUEST_METHOD") != "" { |
| 43 | + return nil, errors.New("net/http: refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy") |
| 44 | + } |
| 45 | + } |
| 46 | + if proxy == "" { |
| 47 | + return nil, nil |
| 48 | + } |
| 49 | + if !useProxy(canonicalAddr(req.URL)) { |
| 50 | + return nil, nil |
| 51 | + } |
| 52 | + proxyURL, err := url.Parse(proxy) |
| 53 | + if err != nil || |
| 54 | + (proxyURL.Scheme != "http" && |
| 55 | + proxyURL.Scheme != "https" && |
| 56 | + proxyURL.Scheme != "socks5") { |
| 57 | + // proxy was bogus. Try prepending "http://" to it and |
| 58 | + // see if that parses correctly. If not, we fall |
| 59 | + // through and complain about the original one. |
| 60 | + if proxyURL, err := url.Parse("http://" + proxy); err == nil { |
| 61 | + return proxyURL, nil |
| 62 | + } |
| 63 | + |
| 64 | + } |
| 65 | + if err != nil { |
| 66 | + return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) |
| 67 | + } |
| 68 | + return proxyURL, nil |
| 69 | +} |
| 70 | + |
| 71 | +// ProxyURL returns a proxy function (for use in a Transport) |
| 72 | +// that always returns the same URL. |
| 73 | +func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error) { |
| 74 | + return func(*Request) (*url.URL, error) { |
| 75 | + return fixedURL, nil |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +func isASCII(s string) bool { |
| 80 | + for i := 0; i < len(s); i++ { |
| 81 | + if s[i] >= utf8.RuneSelf { |
| 82 | + return false |
| 83 | + } |
| 84 | + } |
| 85 | + return true |
| 86 | +} |
| 87 | + |
| 88 | +func idnaASCII(v string) (string, error) { |
| 89 | + // TODO: Consider removing this check after verifying performance is okay. |
| 90 | + // Right now punycode verification, length checks, context checks, and the |
| 91 | + // permissible character tests are all omitted. It also prevents the ToASCII |
| 92 | + // call from salvaging an invalid IDN, when possible. As a result it may be |
| 93 | + // possible to have two IDNs that appear identical to the user where the |
| 94 | + // ASCII-only version causes an error downstream whereas the non-ASCII |
| 95 | + // version does not. |
| 96 | + // Note that for correct ASCII IDNs ToASCII will only do considerably more |
| 97 | + // work, but it will not cause an allocation. |
| 98 | + if isASCII(v) { |
| 99 | + return v, nil |
| 100 | + } |
| 101 | + return idna.Lookup.ToASCII(v) |
| 102 | +} |
| 103 | + |
| 104 | +// canonicalAddr returns url.Host but always with a ":port" suffix |
| 105 | +func canonicalAddr(url *url.URL) string { |
| 106 | + addr := url.Hostname() |
| 107 | + if v, err := idnaASCII(addr); err == nil { |
| 108 | + addr = v |
| 109 | + } |
| 110 | + port := url.Port() |
| 111 | + if port == "" { |
| 112 | + port = portMap[url.Scheme] |
| 113 | + } |
| 114 | + return net.JoinHostPort(addr, port) |
| 115 | +} |
| 116 | + |
| 117 | +// envOnce looks up an environment variable (optionally by multiple |
| 118 | +// names) once. It mitigates expensive lookups on some platforms |
| 119 | +// (e.g. Windows). |
| 120 | +type envOnce struct { |
| 121 | + names []string |
| 122 | + once sync.Once |
| 123 | + val string |
| 124 | +} |
| 125 | + |
| 126 | +func (e *envOnce) Get() string { |
| 127 | + e.once.Do(e.init) |
| 128 | + return e.val |
| 129 | +} |
| 130 | + |
| 131 | +func (e *envOnce) init() { |
| 132 | + for _, n := range e.names { |
| 133 | + e.val = os.Getenv(n) |
| 134 | + if e.val != "" { |
| 135 | + return |
| 136 | + } |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +// reset is used by tests |
| 141 | +func (e *envOnce) reset() { |
| 142 | + e.once = sync.Once{} |
| 143 | + e.val = "" |
| 144 | +} |
| 145 | + |
| 146 | +var ( |
| 147 | + httpProxyEnv = &envOnce{ |
| 148 | + names: []string{"HTTP_PROXY", "http_proxy"}, |
| 149 | + } |
| 150 | + httpsProxyEnv = &envOnce{ |
| 151 | + names: []string{"HTTPS_PROXY", "https_proxy"}, |
| 152 | + } |
| 153 | + noProxyEnv = &envOnce{ |
| 154 | + names: []string{"NO_PROXY", "no_proxy"}, |
| 155 | + } |
| 156 | +) |
| 157 | + |
| 158 | +// Given a string of the form "host", "host:port", or "[ipv6::address]:port", |
| 159 | +// return true if the string includes a port. |
| 160 | +func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } |
| 161 | + |
| 162 | +// useProxy reports whether requests to addr should use a proxy, |
| 163 | +// according to the NO_PROXY or no_proxy environment variable. |
| 164 | +// addr is always a canonicalAddr with a host and port. |
| 165 | +func useProxy(addr string) bool { |
| 166 | + if len(addr) == 0 { |
| 167 | + return true |
| 168 | + } |
| 169 | + host, _, err := net.SplitHostPort(addr) |
| 170 | + if err != nil { |
| 171 | + return false |
| 172 | + } |
| 173 | + if host == "localhost" { |
| 174 | + return false |
| 175 | + } |
| 176 | + if ip := net.ParseIP(host); ip != nil { |
| 177 | + if ip.IsLoopback() { |
| 178 | + return false |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + noProxy := noProxyEnv.Get() |
| 183 | + if noProxy == "*" { |
| 184 | + return false |
| 185 | + } |
| 186 | + |
| 187 | + addr = strings.ToLower(strings.TrimSpace(addr)) |
| 188 | + if hasPort(addr) { |
| 189 | + addr = addr[:strings.LastIndex(addr, ":")] |
| 190 | + } |
| 191 | + |
| 192 | + for _, p := range strings.Split(noProxy, ",") { |
| 193 | + p = strings.ToLower(strings.TrimSpace(p)) |
| 194 | + if len(p) == 0 { |
| 195 | + continue |
| 196 | + } |
| 197 | + if hasPort(p) { |
| 198 | + p = p[:strings.LastIndex(p, ":")] |
| 199 | + } |
| 200 | + if addr == p { |
| 201 | + return false |
| 202 | + } |
| 203 | + if len(p) == 0 { |
| 204 | + // There is no host part, likely the entry is malformed; ignore. |
| 205 | + continue |
| 206 | + } |
| 207 | + if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) { |
| 208 | + // no_proxy ".foo.com" matches "bar.foo.com" or "foo.com" |
| 209 | + return false |
| 210 | + } |
| 211 | + if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' { |
| 212 | + // no_proxy "foo.com" matches "bar.foo.com" |
| 213 | + return false |
| 214 | + } |
| 215 | + } |
| 216 | + return true |
| 217 | +} |
0 commit comments