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