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