Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 

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