github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/net/http/httpproxy/proxy.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 provides support for HTTP proxy determination 6 // based on environment variables, as provided by net/http's 7 // ProxyFromEnvironment function. 8 // 9 // The API is not subject to the Go 1 compatibility promise and may change at 10 // any time. 11 package httpproxy 12 13 import ( 14 "errors" 15 "fmt" 16 "net" 17 "net/url" 18 "os" 19 "strings" 20 "unicode/utf8" 21 22 "github.com/hxx258456/ccgo/net/idna" 23 ) 24 25 // Config holds configuration for HTTP proxy settings. See 26 // FromEnvironment for details. 27 type Config struct { 28 // HTTPProxy represents the value of the HTTP_PROXY or 29 // http_proxy environment variable. It will be used as the proxy 30 // URL for HTTP requests unless overridden by NoProxy. 31 HTTPProxy string 32 33 // HTTPSProxy represents the HTTPS_PROXY or https_proxy 34 // environment variable. It will be used as the proxy URL for 35 // HTTPS requests unless overridden by NoProxy. 36 HTTPSProxy string 37 38 // NoProxy represents the NO_PROXY or no_proxy environment 39 // variable. It specifies a string that contains comma-separated values 40 // specifying hosts that should be excluded from proxying. Each value is 41 // represented by an IP address prefix (1.2.3.4), an IP address prefix in 42 // CIDR notation (1.2.3.4/8), a domain name, or a special DNS label (*). 43 // An IP address prefix and domain name can also include a literal port 44 // number (1.2.3.4:80). 45 // A domain name matches that name and all subdomains. A domain name with 46 // a leading "." matches subdomains only. For example "foo.com" matches 47 // "foo.com" and "bar.foo.com"; ".y.com" matches "x.y.com" but not "y.com". 48 // A single asterisk (*) indicates that no proxying should be done. 49 // A best effort is made to parse the string and errors are 50 // ignored. 51 NoProxy string 52 53 // CGI holds whether the current process is running 54 // as a CGI handler (FromEnvironment infers this from the 55 // presence of a REQUEST_METHOD environment variable). 56 // When this is set, ProxyForURL will return an error 57 // when HTTPProxy applies, because a client could be 58 // setting HTTP_PROXY maliciously. See https://golang.org/s/cgihttpproxy. 59 CGI bool 60 } 61 62 // config holds the parsed configuration for HTTP proxy settings. 63 type config struct { 64 // Config represents the original configuration as defined above. 65 Config 66 67 // httpsProxy is the parsed URL of the HTTPSProxy if defined. 68 httpsProxy *url.URL 69 70 // httpProxy is the parsed URL of the HTTPProxy if defined. 71 httpProxy *url.URL 72 73 // ipMatchers represent all values in the NoProxy that are IP address 74 // prefixes or an IP address in CIDR notation. 75 ipMatchers []matcher 76 77 // domainMatchers represent all values in the NoProxy that are a domain 78 // name or hostname & domain name 79 domainMatchers []matcher 80 } 81 82 // FromEnvironment returns a Config instance populated from the 83 // environment variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the 84 // lowercase versions thereof). HTTPS_PROXY takes precedence over 85 // HTTP_PROXY for https requests. 86 // 87 // The environment values may be either a complete URL or a 88 // "host[:port]", in which case the "http" scheme is assumed. An error 89 // is returned if the value is a different form. 90 func FromEnvironment() *Config { 91 return &Config{ 92 HTTPProxy: getEnvAny("HTTP_PROXY", "http_proxy"), 93 HTTPSProxy: getEnvAny("HTTPS_PROXY", "https_proxy"), 94 NoProxy: getEnvAny("NO_PROXY", "no_proxy"), 95 CGI: os.Getenv("REQUEST_METHOD") != "", 96 } 97 } 98 99 func getEnvAny(names ...string) string { 100 for _, n := range names { 101 if val := os.Getenv(n); val != "" { 102 return val 103 } 104 } 105 return "" 106 } 107 108 // ProxyFunc returns a function that determines the proxy URL to use for 109 // a given request URL. Changing the contents of cfg will not affect 110 // proxy functions created earlier. 111 // 112 // A nil URL and nil error are returned if no proxy is defined in the 113 // environment, or a proxy should not be used for the given request, as 114 // defined by NO_PROXY. 115 // 116 // As a special case, if req.URL.Host is "localhost" or a loopback address 117 // (with or without a port number), then a nil URL and nil error will be returned. 118 func (cfg *Config) ProxyFunc() func(reqURL *url.URL) (*url.URL, error) { 119 // Preprocess the Config settings for more efficient evaluation. 120 cfg1 := &config{ 121 Config: *cfg, 122 } 123 cfg1.init() 124 return cfg1.proxyForURL 125 } 126 127 func (cfg *config) proxyForURL(reqURL *url.URL) (*url.URL, error) { 128 var proxy *url.URL 129 if reqURL.Scheme == "https" { 130 proxy = cfg.httpsProxy 131 } else if reqURL.Scheme == "http" { 132 proxy = cfg.httpProxy 133 if proxy != nil && cfg.CGI { 134 return nil, errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy") 135 } 136 } 137 if proxy == nil { 138 return nil, nil 139 } 140 if !cfg.useProxy(canonicalAddr(reqURL)) { 141 return nil, nil 142 } 143 144 return proxy, nil 145 } 146 147 func parseProxy(proxy string) (*url.URL, error) { 148 if proxy == "" { 149 return nil, nil 150 } 151 152 proxyURL, err := url.Parse(proxy) 153 if err != nil || 154 (proxyURL.Scheme != "http" && 155 proxyURL.Scheme != "https" && 156 proxyURL.Scheme != "socks5") { 157 // proxy was bogus. Try prepending "http://" to it and 158 // see if that parses correctly. If not, we fall 159 // through and complain about the original one. 160 if proxyURL, err := url.Parse("http://" + proxy); err == nil { 161 return proxyURL, nil 162 } 163 } 164 if err != nil { 165 return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) 166 } 167 return proxyURL, nil 168 } 169 170 // useProxy reports whether requests to addr should use a proxy, 171 // according to the NO_PROXY or no_proxy environment variable. 172 // addr is always a canonicalAddr with a host and port. 173 func (cfg *config) useProxy(addr string) bool { 174 if len(addr) == 0 { 175 return true 176 } 177 host, port, err := net.SplitHostPort(addr) 178 if err != nil { 179 return false 180 } 181 if host == "localhost" { 182 return false 183 } 184 ip := net.ParseIP(host) 185 if ip != nil { 186 if ip.IsLoopback() { 187 return false 188 } 189 } 190 191 addr = strings.ToLower(strings.TrimSpace(host)) 192 193 if ip != nil { 194 for _, m := range cfg.ipMatchers { 195 if m.match(addr, port, ip) { 196 return false 197 } 198 } 199 } 200 for _, m := range cfg.domainMatchers { 201 if m.match(addr, port, ip) { 202 return false 203 } 204 } 205 return true 206 } 207 208 func (c *config) init() { 209 if parsed, err := parseProxy(c.HTTPProxy); err == nil { 210 c.httpProxy = parsed 211 } 212 if parsed, err := parseProxy(c.HTTPSProxy); err == nil { 213 c.httpsProxy = parsed 214 } 215 216 for _, p := range strings.Split(c.NoProxy, ",") { 217 p = strings.ToLower(strings.TrimSpace(p)) 218 if len(p) == 0 { 219 continue 220 } 221 222 if p == "*" { 223 c.ipMatchers = []matcher{allMatch{}} 224 c.domainMatchers = []matcher{allMatch{}} 225 return 226 } 227 228 // IPv4/CIDR, IPv6/CIDR 229 if _, pnet, err := net.ParseCIDR(p); err == nil { 230 c.ipMatchers = append(c.ipMatchers, cidrMatch{cidr: pnet}) 231 continue 232 } 233 234 // IPv4:port, [IPv6]:port 235 phost, pport, err := net.SplitHostPort(p) 236 if err == nil { 237 if len(phost) == 0 { 238 // There is no host part, likely the entry is malformed; ignore. 239 continue 240 } 241 if phost[0] == '[' && phost[len(phost)-1] == ']' { 242 phost = phost[1 : len(phost)-1] 243 } 244 } else { 245 phost = p 246 } 247 // IPv4, IPv6 248 if pip := net.ParseIP(phost); pip != nil { 249 c.ipMatchers = append(c.ipMatchers, ipMatch{ip: pip, port: pport}) 250 continue 251 } 252 253 if len(phost) == 0 { 254 // There is no host part, likely the entry is malformed; ignore. 255 continue 256 } 257 258 // domain.com or domain.com:80 259 // foo.com matches bar.foo.com 260 // .domain.com or .domain.com:port 261 // *.domain.com or *.domain.com:port 262 if strings.HasPrefix(phost, "*.") { 263 phost = phost[1:] 264 } 265 matchHost := false 266 if phost[0] != '.' { 267 matchHost = true 268 phost = "." + phost 269 } 270 c.domainMatchers = append(c.domainMatchers, domainMatch{host: phost, port: pport, matchHost: matchHost}) 271 } 272 } 273 274 var portMap = map[string]string{ 275 "http": "80", 276 "https": "443", 277 "socks5": "1080", 278 } 279 280 // canonicalAddr returns url.Host but always with a ":port" suffix 281 func canonicalAddr(url *url.URL) string { 282 addr := url.Hostname() 283 if v, err := idnaASCII(addr); err == nil { 284 addr = v 285 } 286 port := url.Port() 287 if port == "" { 288 port = portMap[url.Scheme] 289 } 290 return net.JoinHostPort(addr, port) 291 } 292 293 // Given a string of the form "host", "host:port", or "[ipv6::address]:port", 294 // return true if the string includes a port. 295 func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } 296 297 func idnaASCII(v string) (string, error) { 298 // TODO: Consider removing this check after verifying performance is okay. 299 // Right now punycode verification, length checks, context checks, and the 300 // permissible character tests are all omitted. It also prevents the ToASCII 301 // call from salvaging an invalid IDN, when possible. As a result it may be 302 // possible to have two IDNs that appear identical to the user where the 303 // ASCII-only version causes an error downstream whereas the non-ASCII 304 // version does not. 305 // Note that for correct ASCII IDNs ToASCII will only do considerably more 306 // work, but it will not cause an allocation. 307 if isASCII(v) { 308 return v, nil 309 } 310 return idna.Lookup.ToASCII(v) 311 } 312 313 func isASCII(s string) bool { 314 for i := 0; i < len(s); i++ { 315 if s[i] >= utf8.RuneSelf { 316 return false 317 } 318 } 319 return true 320 } 321 322 // matcher represents the matching rule for a given value in the NO_PROXY list 323 type matcher interface { 324 // match returns true if the host and optional port or ip and optional port 325 // are allowed 326 match(host, port string, ip net.IP) bool 327 } 328 329 // allMatch matches on all possible inputs 330 type allMatch struct{} 331 332 func (a allMatch) match(host, port string, ip net.IP) bool { 333 return true 334 } 335 336 type cidrMatch struct { 337 cidr *net.IPNet 338 } 339 340 func (m cidrMatch) match(host, port string, ip net.IP) bool { 341 return m.cidr.Contains(ip) 342 } 343 344 type ipMatch struct { 345 ip net.IP 346 port string 347 } 348 349 func (m ipMatch) match(host, port string, ip net.IP) bool { 350 if m.ip.Equal(ip) { 351 return m.port == "" || m.port == port 352 } 353 return false 354 } 355 356 type domainMatch struct { 357 host string 358 port string 359 360 matchHost bool 361 } 362 363 func (m domainMatch) match(host, port string, ip net.IP) bool { 364 if strings.HasSuffix(host, m.host) || (m.matchHost && host == m.host[1:]) { 365 return m.port == "" || m.port == port 366 } 367 return false 368 }