proxy.go 10 KB

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