github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/utils/proxy/proxyconfig.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package proxy
     5  
     6  import (
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/juju/errors"
    14  	proxyutils "github.com/juju/proxy"
    15  )
    16  
    17  // ProxyConfig stores the proxy settings that should be used for web
    18  // requests made from this process.
    19  type ProxyConfig struct {
    20  	mu          sync.Mutex
    21  	http, https *url.URL
    22  	noProxy     string
    23  }
    24  
    25  // Set updates the stored settings to the new ones passed in.
    26  func (pc *ProxyConfig) Set(newSettings proxyutils.Settings) error {
    27  	pc.mu.Lock()
    28  	defer pc.mu.Unlock()
    29  	httpUrl, err := tolerantParse(newSettings.Http)
    30  	if err != nil {
    31  		return errors.Annotate(err, "http proxy")
    32  	}
    33  	httpsUrl, err := tolerantParse(newSettings.Https)
    34  	if err != nil {
    35  		return errors.Annotate(err, "https proxy")
    36  	}
    37  	pc.http = httpUrl
    38  	pc.https = httpsUrl
    39  	pc.noProxy = newSettings.FullNoProxy()
    40  	return nil
    41  }
    42  
    43  // GetProxy returns the URL of the proxy to use for a given request as
    44  // indicated by the proxy settings. It behaves the same as the
    45  // net/http.ProxyFromEnvironment function, except that it uses the
    46  // stored settings rather than pulling the configuration from
    47  // environment variables. (The implementation is copied from
    48  // net/http.ProxyFromEnvironment.)
    49  func (pc *ProxyConfig) GetProxy(req *http.Request) (*url.URL, error) {
    50  	pc.mu.Lock()
    51  	defer pc.mu.Unlock()
    52  
    53  	var proxy *url.URL
    54  	if req.URL.Scheme == "https" {
    55  		proxy = pc.https
    56  	}
    57  	if proxy == nil {
    58  		proxy = pc.http
    59  	}
    60  	if proxy == nil {
    61  		return nil, nil
    62  	}
    63  	if !pc.useProxy(canonicalAddr(req.URL)) {
    64  		return nil, nil
    65  	}
    66  	return proxy, nil
    67  }
    68  
    69  // useProxy reports whether requests to addr should use a proxy,
    70  // according to the NoProxy value of the proxy setting.
    71  // addr is always a canonicalAddr with a host and port.
    72  // (Implementation copied from net/http.useProxy.)
    73  func (pc *ProxyConfig) useProxy(addr string) bool {
    74  	if len(addr) == 0 {
    75  		return true
    76  	}
    77  	host, _, err := net.SplitHostPort(addr)
    78  	if err != nil {
    79  		return false
    80  	}
    81  	if host == "localhost" {
    82  		return false
    83  	}
    84  	ip := net.ParseIP(host)
    85  	if ip != nil && ip.IsLoopback() {
    86  		return false
    87  	}
    88  
    89  	if pc.noProxy == "*" {
    90  		return false
    91  	}
    92  
    93  	addr = strings.ToLower(strings.TrimSpace(addr))
    94  	if hasPort(addr) {
    95  		addr = addr[:strings.LastIndex(addr, ":")]
    96  	}
    97  
    98  	for _, p := range strings.Split(pc.noProxy, ",") {
    99  		p = strings.ToLower(strings.TrimSpace(p))
   100  		if len(p) == 0 {
   101  			continue
   102  		}
   103  		if hasPort(p) {
   104  			p = p[:strings.LastIndex(p, ":")]
   105  		}
   106  		if addr == p {
   107  			return false
   108  		}
   109  		if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
   110  			// no_proxy ".foo.com" matches "bar.foo.com" or "foo.com"
   111  			return false
   112  		}
   113  		if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
   114  			// no_proxy "foo.com" matches "bar.foo.com"
   115  			return false
   116  		}
   117  		if _, net, err := net.ParseCIDR(p); ip != nil && err == nil && net.Contains(ip) {
   118  			return false
   119  		}
   120  	}
   121  	return true
   122  }
   123  
   124  // InstallInDefaultTransport sets the proxy resolution used by the
   125  // default HTTP transport to use the proxy details stored in this
   126  // ProxyConfig. Requests made without an explicit transport will
   127  // respect these proxy settings.
   128  func (pc *ProxyConfig) InstallInDefaultTransport() error {
   129  	transport, ok := http.DefaultTransport.(*http.Transport)
   130  	if !ok {
   131  		return errors.Errorf("http.DefaultTransport was %T instead of *http.Transport", http.DefaultTransport)
   132  	}
   133  	transport.Proxy = pc.GetProxy
   134  	return nil
   135  }
   136  
   137  var DefaultConfig = ProxyConfig{}
   138  
   139  func tolerantParse(value string) (*url.URL, error) {
   140  	if value == "" {
   141  		return nil, nil
   142  	}
   143  	proxyURL, err := url.Parse(value)
   144  	if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
   145  		// proxy was bogus. Try prepending "http://" to it and
   146  		// see if that parses correctly. If not, we fall
   147  		// through and complain about the original one.
   148  		if proxyURL, err := url.Parse("http://" + value); err == nil {
   149  			return proxyURL, nil
   150  		}
   151  	}
   152  	if err != nil {
   153  		return nil, errors.Errorf("invalid proxy address %q: %v", value, err)
   154  	}
   155  	return proxyURL, nil
   156  }
   157  
   158  // Internal utilities copied from net/http/transport.go
   159  var portMap = map[string]string{
   160  	"http":  "80",
   161  	"https": "443",
   162  }
   163  
   164  // canonicalAddr returns url.Host but always with a ":port" suffix
   165  func canonicalAddr(url *url.URL) string {
   166  	addr := url.Host
   167  	if !hasPort(addr) {
   168  		if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
   169  			addr = addr[1 : len(addr)-1]
   170  		}
   171  		return net.JoinHostPort(addr, portMap[url.Scheme])
   172  	}
   173  	return addr
   174  }
   175  
   176  // Given a string of the form "host", "host:port", or "[ipv6::address]:port",
   177  // return true if the string includes a port.
   178  func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }