135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
//go:build windows
|
|
|
|
package sysproxy
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
var (
|
|
winhttp = windows.NewLazySystemDLL("winhttp.dll")
|
|
procGetIEProxyConfig = winhttp.NewProc("WinHttpGetIEProxyConfigForCurrentUser")
|
|
procOpen = winhttp.NewProc("WinHttpOpen")
|
|
procGetProxyForURL = winhttp.NewProc("WinHttpGetProxyForUrl")
|
|
procCloseHandle = winhttp.NewProc("WinHttpCloseHandle")
|
|
|
|
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
procGlobalFree = kernel32.NewProc("GlobalFree")
|
|
)
|
|
|
|
type ieProxyConfig struct {
|
|
fAutoDetect int32
|
|
lpszAutoConfigURL *uint16
|
|
lpszProxy *uint16
|
|
lpszProxyBypass *uint16
|
|
}
|
|
|
|
type autoproxyOptions struct {
|
|
dwFlags uint32
|
|
dwAutoDetectFlags uint32
|
|
lpszAutoConfigURL *uint16
|
|
lpvReserved uintptr
|
|
dwReserved uint32
|
|
fAutoLogonIfChallenged int32
|
|
}
|
|
|
|
type proxyInfo struct {
|
|
dwAccessType uint32
|
|
lpszProxy *uint16
|
|
lpszProxyBypass *uint16
|
|
}
|
|
|
|
const (
|
|
fAutoDetect = 0x00000001
|
|
fConfigURL = 0x00000002
|
|
detectTypeDHCP = 0x00000001
|
|
detectTypeDNSA = 0x00000002
|
|
accessTypeNoProxy = 1
|
|
)
|
|
|
|
// ForURL resolves the Windows system proxy (static IE proxy, PAC autoconfig URL,
|
|
// or WPAD auto-detect) for target. Returns nil when the system is set to direct
|
|
// or no proxy applies; callers then fall back to env/direct.
|
|
func ForURL(target *url.URL) (*url.URL, error) {
|
|
if target == nil {
|
|
return nil, nil
|
|
}
|
|
var ie ieProxyConfig
|
|
if r, _, _ := procGetIEProxyConfig.Call(uintptr(unsafe.Pointer(&ie))); r == 0 {
|
|
return nil, nil
|
|
}
|
|
defer globalFree(ie.lpszProxy)
|
|
defer globalFree(ie.lpszProxyBypass)
|
|
defer globalFree(ie.lpszAutoConfigURL)
|
|
|
|
scheme := strings.ToLower(target.Scheme)
|
|
if scheme == "" {
|
|
scheme = "http"
|
|
}
|
|
|
|
if ie.lpszProxy != nil {
|
|
bypass := ptrToString(ie.lpszProxyBypass)
|
|
if !bypassed(target.Hostname(), bypass) {
|
|
if u := parseProxyList(ptrToString(ie.lpszProxy), scheme); u != nil {
|
|
return u, nil
|
|
}
|
|
}
|
|
}
|
|
if ie.fAutoDetect != 0 || ie.lpszAutoConfigURL != nil {
|
|
if u := pacProxy(target, ie.lpszAutoConfigURL, scheme); u != nil {
|
|
return u, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func pacProxy(target *url.URL, autoConfigURL *uint16, scheme string) *url.URL {
|
|
session, _, _ := procOpen.Call(0, accessTypeNoProxy, 0, 0, 0)
|
|
if session == 0 {
|
|
return nil
|
|
}
|
|
defer func() { _, _, _ = procCloseHandle.Call(session) }()
|
|
|
|
opts := autoproxyOptions{fAutoLogonIfChallenged: 1}
|
|
if autoConfigURL != nil {
|
|
opts.dwFlags = fConfigURL
|
|
opts.lpszAutoConfigURL = autoConfigURL
|
|
} else {
|
|
opts.dwFlags = fAutoDetect
|
|
opts.dwAutoDetectFlags = detectTypeDHCP | detectTypeDNSA
|
|
}
|
|
|
|
urlPtr, err := windows.UTF16PtrFromString(target.String())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var info proxyInfo
|
|
r, _, _ := procGetProxyForURL.Call(session, uintptr(unsafe.Pointer(urlPtr)), uintptr(unsafe.Pointer(&opts)), uintptr(unsafe.Pointer(&info)))
|
|
if r == 0 {
|
|
return nil
|
|
}
|
|
defer globalFree(info.lpszProxy)
|
|
defer globalFree(info.lpszProxyBypass)
|
|
if info.lpszProxy == nil {
|
|
return nil
|
|
}
|
|
return parseProxyList(ptrToString(info.lpszProxy), scheme)
|
|
}
|
|
|
|
func ptrToString(p *uint16) string {
|
|
if p == nil {
|
|
return ""
|
|
}
|
|
return windows.UTF16PtrToString(p)
|
|
}
|
|
|
|
func globalFree(p *uint16) {
|
|
if p != nil {
|
|
_, _, _ = procGlobalFree.Call(uintptr(unsafe.Pointer(p)))
|
|
}
|
|
}
|