1
2
3
4
5
6
7
8
9
10
11 package httpproxy
12
13 import (
14 "errors"
15 "fmt"
16 "net"
17 "net/netip"
18 "net/url"
19 "os"
20 "strings"
21 "unicode/utf8"
22
23 "golang.org/x/net/idna"
24 )
25
26
27
28 type Config struct {
29
30
31
32 HTTPProxy string
33
34
35
36
37 HTTPSProxy string
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 NoProxy string
53
54
55
56
57
58
59
60 CGI bool
61 }
62
63
64 type config struct {
65
66 Config
67
68
69 httpsProxy *url.URL
70
71
72 httpProxy *url.URL
73
74
75
76 ipMatchers []matcher
77
78
79
80 domainMatchers []matcher
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 || proxyURL.Scheme == "" || proxyURL.Host == "" {
154
155
156
157 if proxyURL, err := url.Parse("http://" + proxy); err == nil {
158 return proxyURL, nil
159 }
160 }
161 if err != nil {
162 return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
163 }
164 return proxyURL, nil
165 }
166
167
168
169
170 func (cfg *config) useProxy(addr string) bool {
171 if len(addr) == 0 {
172 return true
173 }
174 host, port, err := net.SplitHostPort(addr)
175 if err != nil {
176 return false
177 }
178 if host == "localhost" {
179 return false
180 }
181 nip, err := netip.ParseAddr(host)
182 var ip net.IP
183 if err == nil {
184 ip = net.IP(nip.AsSlice())
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 ip != nil {
367 return false
368 }
369 if strings.HasSuffix(host, m.host) || (m.matchHost && host == m.host[1:]) {
370 return m.port == "" || m.port == port
371 }
372 return false
373 }
374
View as plain text