You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
7.0 KiB

  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 and HTTPS requests unless overridden by
  27. // HTTPSProxy or NoProxy.
  28. HTTPProxy string
  29. // HTTPSProxy represents the HTTPS_PROXY or https_proxy
  30. // environment variable. It will be used as the proxy URL for
  31. // HTTPS requests unless overridden by NoProxy.
  32. HTTPSProxy string
  33. // NoProxy represents the NO_PROXY or no_proxy environment
  34. // variable. It specifies URLs that should be excluded from
  35. // proxying as a comma-separated list of domain names or a
  36. // single asterisk (*) to indicate that no proxying should be
  37. // done. A domain name matches that name and all subdomains. A
  38. // domain name with a leading "." matches subdomains only. For
  39. // example "foo.com" matches "foo.com" and "bar.foo.com";
  40. // ".y.com" matches "x.y.com" but not "y.com".
  41. NoProxy string
  42. // CGI holds whether the current process is running
  43. // as a CGI handler (FromEnvironment infers this from the
  44. // presence of a REQUEST_METHOD environment variable).
  45. // When this is set, ProxyForURL will return an error
  46. // when HTTPProxy applies, because a client could be
  47. // setting HTTP_PROXY maliciously. See https://golang.org/s/cgihttpproxy.
  48. CGI bool
  49. }
  50. // FromEnvironment returns a Config instance populated from the
  51. // environment variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the
  52. // lowercase versions thereof). HTTPS_PROXY takes precedence over
  53. // HTTP_PROXY for https requests.
  54. //
  55. // The environment values may be either a complete URL or a
  56. // "host[:port]", in which case the "http" scheme is assumed. An error
  57. // is returned if the value is a different form.
  58. func FromEnvironment() *Config {
  59. return &Config{
  60. HTTPProxy: getEnvAny("HTTP_PROXY", "http_proxy"),
  61. HTTPSProxy: getEnvAny("HTTPS_PROXY", "https_proxy"),
  62. NoProxy: getEnvAny("NO_PROXY", "no_proxy"),
  63. CGI: os.Getenv("REQUEST_METHOD") != "",
  64. }
  65. }
  66. func getEnvAny(names ...string) string {
  67. for _, n := range names {
  68. if val := os.Getenv(n); val != "" {
  69. return val
  70. }
  71. }
  72. return ""
  73. }
  74. // ProxyFunc returns a function that determines the proxy URL to use for
  75. // a given request URL. Changing the contents of cfg will not affect
  76. // proxy functions created earlier.
  77. //
  78. // A nil URL and nil error are returned if no proxy is defined in the
  79. // environment, or a proxy should not be used for the given request, as
  80. // defined by NO_PROXY.
  81. //
  82. // As a special case, if req.URL.Host is "localhost" (with or without a
  83. // port number), then a nil URL and nil error will be returned.
  84. func (cfg *Config) ProxyFunc() func(reqURL *url.URL) (*url.URL, error) {
  85. // Prevent Config changes from affecting the function calculation.
  86. // TODO Preprocess proxy settings for more efficient evaluation.
  87. cfg1 := *cfg
  88. return cfg1.proxyForURL
  89. }
  90. func (cfg *Config) proxyForURL(reqURL *url.URL) (*url.URL, error) {
  91. var proxy string
  92. if reqURL.Scheme == "https" {
  93. proxy = cfg.HTTPSProxy
  94. }
  95. if proxy == "" {
  96. proxy = cfg.HTTPProxy
  97. if proxy != "" && cfg.CGI {
  98. return nil, errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")
  99. }
  100. }
  101. if proxy == "" {
  102. return nil, nil
  103. }
  104. if !cfg.useProxy(canonicalAddr(reqURL)) {
  105. return nil, nil
  106. }
  107. proxyURL, err := url.Parse(proxy)
  108. if err != nil ||
  109. (proxyURL.Scheme != "http" &&
  110. proxyURL.Scheme != "https" &&
  111. proxyURL.Scheme != "socks5") {
  112. // proxy was bogus. Try prepending "http://" to it and
  113. // see if that parses correctly. If not, we fall
  114. // through and complain about the original one.
  115. if proxyURL, err := url.Parse("http://" + proxy); err == nil {
  116. return proxyURL, nil
  117. }
  118. }
  119. if err != nil {
  120. return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
  121. }
  122. return proxyURL, nil
  123. }
  124. // useProxy reports whether requests to addr should use a proxy,
  125. // according to the NO_PROXY or no_proxy environment variable.
  126. // addr is always a canonicalAddr with a host and port.
  127. func (cfg *Config) useProxy(addr string) bool {
  128. if len(addr) == 0 {
  129. return true
  130. }
  131. host, _, err := net.SplitHostPort(addr)
  132. if err != nil {
  133. return false
  134. }
  135. if host == "localhost" {
  136. return false
  137. }
  138. if ip := net.ParseIP(host); ip != nil {
  139. if ip.IsLoopback() {
  140. return false
  141. }
  142. }
  143. noProxy := cfg.NoProxy
  144. if noProxy == "*" {
  145. return false
  146. }
  147. addr = strings.ToLower(strings.TrimSpace(addr))
  148. if hasPort(addr) {
  149. addr = addr[:strings.LastIndex(addr, ":")]
  150. }
  151. for _, p := range strings.Split(noProxy, ",") {
  152. p = strings.ToLower(strings.TrimSpace(p))
  153. if len(p) == 0 {
  154. continue
  155. }
  156. if hasPort(p) {
  157. p = p[:strings.LastIndex(p, ":")]
  158. }
  159. if addr == p {
  160. return false
  161. }
  162. if len(p) == 0 {
  163. // There is no host part, likely the entry is malformed; ignore.
  164. continue
  165. }
  166. if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
  167. // no_proxy ".foo.com" matches "bar.foo.com" or "foo.com"
  168. return false
  169. }
  170. if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
  171. // no_proxy "foo.com" matches "bar.foo.com"
  172. return false
  173. }
  174. }
  175. return true
  176. }
  177. var portMap = map[string]string{
  178. "http": "80",
  179. "https": "443",
  180. "socks5": "1080",
  181. }
  182. // canonicalAddr returns url.Host but always with a ":port" suffix
  183. func canonicalAddr(url *url.URL) string {
  184. addr := url.Hostname()
  185. if v, err := idnaASCII(addr); err == nil {
  186. addr = v
  187. }
  188. port := url.Port()
  189. if port == "" {
  190. port = portMap[url.Scheme]
  191. }
  192. return net.JoinHostPort(addr, port)
  193. }
  194. // Given a string of the form "host", "host:port", or "[ipv6::address]:port",
  195. // return true if the string includes a port.
  196. func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
  197. func idnaASCII(v string) (string, error) {
  198. // TODO: Consider removing this check after verifying performance is okay.
  199. // Right now punycode verification, length checks, context checks, and the
  200. // permissible character tests are all omitted. It also prevents the ToASCII
  201. // call from salvaging an invalid IDN, when possible. As a result it may be
  202. // possible to have two IDNs that appear identical to the user where the
  203. // ASCII-only version causes an error downstream whereas the non-ASCII
  204. // version does not.
  205. // Note that for correct ASCII IDNs ToASCII will only do considerably more
  206. // work, but it will not cause an allocation.
  207. if isASCII(v) {
  208. return v, nil
  209. }
  210. return idna.Lookup.ToASCII(v)
  211. }
  212. func isASCII(s string) bool {
  213. for i := 0; i < len(s); i++ {
  214. if s[i] >= utf8.RuneSelf {
  215. return false
  216. }
  217. }
  218. return true
  219. }