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 ||
153 (proxyURL.Scheme != "http" &&
154 proxyURL.Scheme != "https" &&
155 proxyURL.Scheme != "socks5") {
156
157
158
159 if proxyURL, err := url.Parse("http://" + proxy); err == nil {
160 return proxyURL, nil
161 }
162 }
163 if err != nil {
164 return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
165 }
166 return proxyURL, nil
167 }
168
169
170
171
172 func (cfg *config) useProxy(addr string) bool {
173 if len(addr) == 0 {
174 return true
175 }
176 host, port, err := net.SplitHostPort(addr)
177 if err != nil {
178 return false
179 }
180 if host == "localhost" {
181 return false
182 }
183 ip := net.ParseIP(host)
184 if ip != nil {
185 if ip.IsLoopback() {
186 return false
187 }
188 }
189
190 addr = strings.ToLower(strings.TrimSpace(host))
191
192 if ip != nil {
193 for _, m := range cfg.ipMatchers {
194 if m.match(addr, port, ip) {
195 return false
196 }
197 }
198 }
199 for _, m := range cfg.domainMatchers {
200 if m.match(addr, port, ip) {
201 return false
202 }
203 }
204 return true
205 }
206
207 func (c *config) init() {
208 if parsed, err := parseProxy(c.HTTPProxy); err == nil {
209 c.httpProxy = parsed
210 }
211 if parsed, err := parseProxy(c.HTTPSProxy); err == nil {
212 c.httpsProxy = parsed
213 }
214
215 for _, p := range strings.Split(c.NoProxy, ",") {
216 p = strings.ToLower(strings.TrimSpace(p))
217 if len(p) == 0 {
218 continue
219 }
220
221 if p == "*" {
222 c.ipMatchers = []matcher{allMatch{}}
223 c.domainMatchers = []matcher{allMatch{}}
224 return
225 }
226
227
228 if _, pnet, err := net.ParseCIDR(p); err == nil {
229 c.ipMatchers = append(c.ipMatchers, cidrMatch{cidr: pnet})
230 continue
231 }
232
233
234 phost, pport, err := net.SplitHostPort(p)
235 if err == nil {
236 if len(phost) == 0 {
237
238 continue
239 }
240 if phost[0] == '[' && phost[len(phost)-1] == ']' {
241 phost = phost[1 : len(phost)-1]
242 }
243 } else {
244 phost = p
245 }
246
247 if pip := net.ParseIP(phost); pip != nil {
248 c.ipMatchers = append(c.ipMatchers, ipMatch{ip: pip, port: pport})
249 continue
250 }
251
252 if len(phost) == 0 {
253
254 continue
255 }
256
257
258
259
260
261 if strings.HasPrefix(phost, "*.") {
262 phost = phost[1:]
263 }
264 matchHost := false
265 if phost[0] != '.' {
266 matchHost = true
267 phost = "." + phost
268 }
269 if v, err := idnaASCII(phost); err == nil {
270 phost = v
271 }
272 c.domainMatchers = append(c.domainMatchers, domainMatch{host: phost, port: pport, matchHost: matchHost})
273 }
274 }
275
276 var portMap = map[string]string{
277 "http": "80",
278 "https": "443",
279 "socks5": "1080",
280 }
281
282
283 func canonicalAddr(url *url.URL) string {
284 addr := url.Hostname()
285 if v, err := idnaASCII(addr); err == nil {
286 addr = v
287 }
288 port := url.Port()
289 if port == "" {
290 port = portMap[url.Scheme]
291 }
292 return net.JoinHostPort(addr, port)
293 }
294
295
296
297 func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
298
299 func idnaASCII(v string) (string, error) {
300
301
302
303
304
305
306
307
308
309 if isASCII(v) {
310 return v, nil
311 }
312 return idna.Lookup.ToASCII(v)
313 }
314
315 func isASCII(s string) bool {
316 for i := 0; i < len(s); i++ {
317 if s[i] >= utf8.RuneSelf {
318 return false
319 }
320 }
321 return true
322 }
323
324
325 type matcher interface {
326
327
328 match(host, port string, ip net.IP) bool
329 }
330
331
332 type allMatch struct{}
333
334 func (a allMatch) match(host, port string, ip net.IP) bool {
335 return true
336 }
337
338 type cidrMatch struct {
339 cidr *net.IPNet
340 }
341
342 func (m cidrMatch) match(host, port string, ip net.IP) bool {
343 return m.cidr.Contains(ip)
344 }
345
346 type ipMatch struct {
347 ip net.IP
348 port string
349 }
350
351 func (m ipMatch) match(host, port string, ip net.IP) bool {
352 if m.ip.Equal(ip) {
353 return m.port == "" || m.port == port
354 }
355 return false
356 }
357
358 type domainMatch struct {
359 host string
360 port string
361
362 matchHost bool
363 }
364
365 func (m domainMatch) match(host, port string, ip net.IP) bool {
366 if strings.HasSuffix(host, m.host) || (m.matchHost && host == m.host[1:]) {
367 return m.port == "" || m.port == port
368 }
369 return false
370 }
371
View as plain text