1
2
3
4
5
6
7
8
9
10
11 package httpproxy
12
13 import (
14 "errors"
15 "fmt"
16 "net"
17 "net/url"
18 "os"
19 "strings"
20 "unicode/utf8"
21
22 "golang.org/x/net/idna"
23 )
24
25
26
27 type Config struct {
28
29
30
31 HTTPProxy string
32
33
34
35
36 HTTPSProxy string
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 NoProxy string
52
53
54
55
56
57
58
59 CGI bool
60 }
61
62
63 type config struct {
64
65 Config
66
67
68 httpsProxy *url.URL
69
70
71 httpProxy *url.URL
72
73
74
75 ipMatchers []matcher
76
77
78
79 domainMatchers []matcher
80 }
81
82
83
84
85
86
87
88
89 func FromEnvironment() *Config {
90 return &Config{
91 HTTPProxy: getEnvAny("HTTP_PROXY", "http_proxy"),
92 HTTPSProxy: getEnvAny("HTTPS_PROXY", "https_proxy"),
93 NoProxy: getEnvAny("NO_PROXY", "no_proxy"),
94 CGI: os.Getenv("REQUEST_METHOD") != "",
95 }
96 }
97
98 func getEnvAny(names ...string) string {
99 for _, n := range names {
100 if val := os.Getenv(n); val != "" {
101 return val
102 }
103 }
104 return ""
105 }
106
107
108
109
110
111
112
113
114
115
116
117 func (cfg *Config) ProxyFunc() func(reqURL *url.URL) (*url.URL, error) {
118
119 cfg1 := &config{
120 Config: *cfg,
121 }
122 cfg1.init()
123 return cfg1.proxyForURL
124 }
125
126 func (cfg *config) proxyForURL(reqURL *url.URL) (*url.URL, error) {
127 var proxy *url.URL
128 if reqURL.Scheme == "https" {
129 proxy = cfg.httpsProxy
130 } else if reqURL.Scheme == "http" {
131 proxy = cfg.httpProxy
132 if proxy != nil && cfg.CGI {
133 return nil, errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")
134 }
135 }
136 if proxy == nil {
137 return nil, nil
138 }
139 if !cfg.useProxy(canonicalAddr(reqURL)) {
140 return nil, nil
141 }
142
143 return proxy, nil
144 }
145
146 func parseProxy(proxy string) (*url.URL, error) {
147 if proxy == "" {
148 return nil, nil
149 }
150
151 proxyURL, err := url.Parse(proxy)
152 if err != nil || proxyURL.Scheme == "" || proxyURL.Host == "" {
153
154
155
156 if proxyURL, err := url.Parse("http://" + proxy); err == nil {
157 return proxyURL, nil
158 }
159 }
160 if err != nil {
161 return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
162 }
163 return proxyURL, nil
164 }
165
166
167
168
169 func (cfg *config) useProxy(addr string) bool {
170 if len(addr) == 0 {
171 return true
172 }
173 host, port, err := net.SplitHostPort(addr)
174 if err != nil {
175 return false
176 }
177 if host == "localhost" {
178 return false
179 }
180 ip := net.ParseIP(host)
181 if ip != nil {
182 if ip.IsLoopback() {
183 return false
184 }
185 }
186
187 addr = strings.ToLower(strings.TrimSpace(host))
188
189 if ip != nil {
190 for _, m := range cfg.ipMatchers {
191 if m.match(addr, port, ip) {
192 return false
193 }
194 }
195 }
196 for _, m := range cfg.domainMatchers {
197 if m.match(addr, port, ip) {
198 return false
199 }
200 }
201 return true
202 }
203
204 func (c *config) init() {
205 if parsed, err := parseProxy(c.HTTPProxy); err == nil {
206 c.httpProxy = parsed
207 }
208 if parsed, err := parseProxy(c.HTTPSProxy); err == nil {
209 c.httpsProxy = parsed
210 }
211
212 for _, p := range strings.Split(c.NoProxy, ",") {
213 p = strings.ToLower(strings.TrimSpace(p))
214 if len(p) == 0 {
215 continue
216 }
217
218 if p == "*" {
219 c.ipMatchers = []matcher{allMatch{}}
220 c.domainMatchers = []matcher{allMatch{}}
221 return
222 }
223
224
225 if _, pnet, err := net.ParseCIDR(p); err == nil {
226 c.ipMatchers = append(c.ipMatchers, cidrMatch{cidr: pnet})
227 continue
228 }
229
230
231 phost, pport, err := net.SplitHostPort(p)
232 if err == nil {
233 if len(phost) == 0 {
234
235 continue
236 }
237 if phost[0] == '[' && phost[len(phost)-1] == ']' {
238 phost = phost[1 : len(phost)-1]
239 }
240 } else {
241 phost = p
242 }
243
244 if pip := net.ParseIP(phost); pip != nil {
245 c.ipMatchers = append(c.ipMatchers, ipMatch{ip: pip, port: pport})
246 continue
247 }
248
249 if len(phost) == 0 {
250
251 continue
252 }
253
254
255
256
257
258 if strings.HasPrefix(phost, "*.") {
259 phost = phost[1:]
260 }
261 matchHost := false
262 if phost[0] != '.' {
263 matchHost = true
264 phost = "." + phost
265 }
266 if v, err := idnaASCII(phost); err == nil {
267 phost = v
268 }
269 c.domainMatchers = append(c.domainMatchers, domainMatch{host: phost, port: pport, matchHost: matchHost})
270 }
271 }
272
273 var portMap = map[string]string{
274 "http": "80",
275 "https": "443",
276 "socks5": "1080",
277 }
278
279
280 func canonicalAddr(url *url.URL) string {
281 addr := url.Hostname()
282 if v, err := idnaASCII(addr); err == nil {
283 addr = v
284 }
285 port := url.Port()
286 if port == "" {
287 port = portMap[url.Scheme]
288 }
289 return net.JoinHostPort(addr, port)
290 }
291
292
293
294 func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
295
296 func idnaASCII(v string) (string, error) {
297
298
299
300
301
302
303
304
305
306 if isASCII(v) {
307 return v, nil
308 }
309 return idna.Lookup.ToASCII(v)
310 }
311
312 func isASCII(s string) bool {
313 for i := 0; i < len(s); i++ {
314 if s[i] >= utf8.RuneSelf {
315 return false
316 }
317 }
318 return true
319 }
320
321
322 type matcher interface {
323
324
325 match(host, port string, ip net.IP) bool
326 }
327
328
329 type allMatch struct{}
330
331 func (a allMatch) match(host, port string, ip net.IP) bool {
332 return true
333 }
334
335 type cidrMatch struct {
336 cidr *net.IPNet
337 }
338
339 func (m cidrMatch) match(host, port string, ip net.IP) bool {
340 return m.cidr.Contains(ip)
341 }
342
343 type ipMatch struct {
344 ip net.IP
345 port string
346 }
347
348 func (m ipMatch) match(host, port string, ip net.IP) bool {
349 if m.ip.Equal(ip) {
350 return m.port == "" || m.port == port
351 }
352 return false
353 }
354
355 type domainMatch struct {
356 host string
357 port string
358
359 matchHost bool
360 }
361
362 func (m domainMatch) match(host, port string, ip net.IP) bool {
363 if strings.HasSuffix(host, m.host) || (m.matchHost && host == m.host[1:]) {
364 return m.port == "" || m.port == port
365 }
366 return false
367 }
368
View as plain text