返回 DeepSeek-Reasonix
system_windows.go
根目录 / internal / sysproxy / system_windows.go
1 //go:build windows
2
3 package sysproxy
4
5 import (
6 "log/slog"
7 "net/url"
8 "strings"
9 "unsafe"
10
11 "golang.org/x/sys/windows"
12 )
13
14 var (
15 winhttp = windows.NewLazySystemDLL("winhttp.dll")
16 procGetIEProxyConfig = winhttp.NewProc("WinHttpGetIEProxyConfigForCurrentUser")
17 procOpen = winhttp.NewProc("WinHttpOpen")
18 procGetProxyForURL = winhttp.NewProc("WinHttpGetProxyForUrl")
19 procCloseHandle = winhttp.NewProc("WinHttpCloseHandle")
20
21 kernel32 = windows.NewLazySystemDLL("kernel32.dll")
22 procGlobalFree = kernel32.NewProc("GlobalFree")
23 )
24
25 type ieProxyConfig struct {
26 fAutoDetect int32
27 lpszAutoConfigURL *uint16
28 lpszProxy *uint16
29 lpszProxyBypass *uint16
30 }
31
32 type autoproxyOptions struct {
33 dwFlags uint32
34 dwAutoDetectFlags uint32
35 lpszAutoConfigURL *uint16
36 lpvReserved uintptr
37 dwReserved uint32
38 fAutoLogonIfChallenged int32
39 }
40
41 type proxyInfo struct {
42 dwAccessType uint32
43 lpszProxy *uint16
44 lpszProxyBypass *uint16
45 }
46
47 const (
48 fAutoDetect = 0x00000001
49 fConfigURL = 0x00000002
50 detectTypeDHCP = 0x00000001
51 detectTypeDNSA = 0x00000002
52 accessTypeNoProxy = 1
53 )
54
55 // ForURL resolves the Windows system proxy (static IE proxy, PAC autoconfig URL,
56 // or WPAD auto-detect) for target. Returns nil when the system is set to direct
57 // or no proxy applies; callers then fall back to env/direct.
58 func ForURL(target *url.URL) (*url.URL, error) {
59 if target == nil {
60 return nil, nil
61 }
62 var ie ieProxyConfig
63 if r, _, callErr := procGetIEProxyConfig.Call(uintptr(unsafe.Pointer(&ie))); r == 0 {
64 // A service account or an RDP session with no per-user IE config fails
65 // here, which is indistinguishable from "system is set to direct"
66 // unless the errno is recorded (#4798).
67 slog.Debug("sysproxy: WinHttpGetIEProxyConfigForCurrentUser failed",
68 "err", callErr, "host", target.Hostname())
69 return nil, nil
70 }
71 defer globalFree(ie.lpszProxy)
72 defer globalFree(ie.lpszProxyBypass)
73 defer globalFree(ie.lpszAutoConfigURL)
74
75 scheme := strings.ToLower(target.Scheme)
76 if scheme == "" {
77 scheme = "http"
78 }
79
80 if ie.lpszProxy != nil {
81 bypass := ptrToString(ie.lpszProxyBypass)
82 if !bypassed(target.Hostname(), bypass) {
83 if u := parseProxyList(ptrToString(ie.lpszProxy), scheme); u != nil {
84 return u, nil
85 }
86 }
87 }
88 if ie.fAutoDetect != 0 || ie.lpszAutoConfigURL != nil {
89 if u := pacProxy(target, ie.lpszAutoConfigURL, scheme); u != nil {
90 return u, nil
91 }
92 }
93 slog.Debug("sysproxy: no system proxy applies; using a direct connection",
94 "host", target.Hostname(),
95 "static_proxy", ie.lpszProxy != nil,
96 "auto_detect", ie.fAutoDetect != 0,
97 "pac_url", ie.lpszAutoConfigURL != nil)
98 return nil, nil
99 }
100
101 func pacProxy(target *url.URL, autoConfigURL *uint16, scheme string) *url.URL {
102 session, _, _ := procOpen.Call(0, accessTypeNoProxy, 0, 0, 0)
103 if session == 0 {
104 return nil
105 }
106 defer func() { _, _, _ = procCloseHandle.Call(session) }()
107
108 opts := autoproxyOptions{fAutoLogonIfChallenged: 1}
109 if autoConfigURL != nil {
110 opts.dwFlags = fConfigURL
111 opts.lpszAutoConfigURL = autoConfigURL
112 } else {
113 opts.dwFlags = fAutoDetect
114 opts.dwAutoDetectFlags = detectTypeDHCP | detectTypeDNSA
115 }
116
117 urlPtr, err := windows.UTF16PtrFromString(target.String())
118 if err != nil {
119 return nil
120 }
121 var info proxyInfo
122 r, _, _ := procGetProxyForURL.Call(session, uintptr(unsafe.Pointer(urlPtr)), uintptr(unsafe.Pointer(&opts)), uintptr(unsafe.Pointer(&info)))
123 if r == 0 {
124 return nil
125 }
126 defer globalFree(info.lpszProxy)
127 defer globalFree(info.lpszProxyBypass)
128 if info.lpszProxy == nil {
129 return nil
130 }
131 return parseProxyList(ptrToString(info.lpszProxy), scheme)
132 }
133
134 func ptrToString(p *uint16) string {
135 if p == nil {
136 return ""
137 }
138 return windows.UTF16PtrToString(p)
139 }
140
141 func globalFree(p *uint16) {
142 if p != nil {
143 _, _, _ = procGlobalFree.Call(uintptr(unsafe.Pointer(p)))
144 }
145 }
146
146 lines GO