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, "]") }