返回 DeepSeek-Reasonix
sysproxy.go
根目录 / internal / sysproxy / sysproxy.go
1 // Package sysproxy resolves the OS-level proxy (Windows system/PAC settings)
2 // for a target URL. ForURL returns nil on platforms without system-proxy
3 // support or when no proxy applies, so callers fall back to direct/env.
4 package sysproxy
5
6 import (
7 "net/netip"
8 "net/url"
9 "slices"
10 "strings"
11 )
12
13 func splitList(s string) []string {
14 return strings.FieldsFunc(s, func(r rune) bool {
15 return r == ';' || r == ' ' || r == '\t' || r == '\n' || r == '\r'
16 })
17 }
18
19 // parseProxyList picks a proxy from a WinHTTP/IE proxy string for scheme. The
20 // string is either "host:port" (all protocols) or "http=h:p;https=h:p" form.
21 func parseProxyList(list, scheme string) *url.URL {
22 var fallback string
23 for _, f := range splitList(list) {
24 if before, after, ok := strings.Cut(f, "="); ok {
25 if strings.EqualFold(before, scheme) {
26 return hostProxyURL(after)
27 }
28 continue
29 }
30 if fallback == "" {
31 fallback = f
32 }
33 }
34 if fallback != "" {
35 return hostProxyURL(fallback)
36 }
37 return nil
38 }
39
40 func hostProxyURL(hostport string) *url.URL {
41 hostport = strings.TrimSpace(hostport)
42 if i := strings.Index(hostport, "://"); i >= 0 {
43 hostport = hostport[i+3:]
44 }
45 if hostport == "" {
46 return nil
47 }
48 return &url.URL{Scheme: "http", Host: hostport}
49 }
50
51 // bypassed reports whether host matches a WinINET proxy-bypass entry. "<local>"
52 // matches dotless (intranet) hosts, and "*" is a wildcard anywhere in an entry
53 // ("127.*", "*.corp.local"). Loopback is bypassed unless "<-loopback>" is listed,
54 // as WinINET does.
55 func bypassed(host, bypass string) bool {
56 host = strings.ToLower(strings.TrimSpace(host))
57 if host == "" {
58 return false
59 }
60 entries := splitList(strings.ToLower(bypass))
61 if isLoopback(host) && !slices.Contains(entries, "<-loopback>") {
62 return true
63 }
64 for _, e := range entries {
65 if e == "<local>" {
66 if !strings.Contains(host, ".") {
67 return true
68 }
69 continue
70 }
71 if wildcardMatch(strings.Trim(e, "[]"), host) {
72 return true
73 }
74 }
75 return false
76 }
77
78 func isLoopback(host string) bool {
79 if host == "localhost" {
80 return true
81 }
82 ip, err := netip.ParseAddr(host)
83 return err == nil && ip.IsLoopback()
84 }
85
86 // wildcardMatch reports whether s matches pattern, where "*" matches any run of
87 // characters and every other character matches itself.
88 func wildcardMatch(pattern, s string) bool {
89 parts := strings.Split(pattern, "*")
90 if len(parts) == 1 {
91 return pattern == s
92 }
93 if !strings.HasPrefix(s, parts[0]) {
94 return false
95 }
96 s = s[len(parts[0]):]
97 last := parts[len(parts)-1]
98 for _, part := range parts[1 : len(parts)-1] {
99 i := strings.Index(s, part)
100 if i < 0 {
101 return false
102 }
103 s = s[i+len(part):]
104 }
105 return len(s) >= len(last) && strings.HasSuffix(s, last)
106 }
107
107 lines GO