mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
v0.3.8
This commit is contained in:
@@ -0,0 +1,470 @@
|
||||
package netproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cursor/internal/logger"
|
||||
|
||||
"golang.org/x/net/http/httpproxy"
|
||||
)
|
||||
|
||||
const (
|
||||
proxyCacheTTL = time.Second
|
||||
alwaysNoProxyList = "localhost,127.0.0.1,::1"
|
||||
)
|
||||
|
||||
var (
|
||||
installOnce sync.Once
|
||||
initialDefaultTransport = cloneDefaultTransport()
|
||||
defaultResolver = &proxyResolver{}
|
||||
proxyTransports sync.Map
|
||||
)
|
||||
|
||||
// InstallDefaultTransport makes clients with a nil Transport use the same
|
||||
// proxy resolution as clients created through this package.
|
||||
func InstallDefaultTransport() {
|
||||
installOnce.Do(func() {
|
||||
http.DefaultTransport = NewTransport(nil)
|
||||
defaultResolver.logCurrentSnapshot("default transport installed")
|
||||
})
|
||||
}
|
||||
|
||||
// NewHTTPClient creates an HTTP client that follows environment and OS proxy
|
||||
// settings while preserving the caller's timeout choice.
|
||||
func NewHTTPClient(timeout time.Duration) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: NewTransport(nil),
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTransport clones the given transport and installs proxy resolution on it.
|
||||
// When base is nil, it clones Go's original default transport.
|
||||
func NewTransport(base *http.Transport) *http.Transport {
|
||||
transport := initialDefaultTransport.Clone()
|
||||
if base != nil {
|
||||
transport = base.Clone()
|
||||
}
|
||||
transport.Proxy = ProxyForRequest
|
||||
proxyTransports.Store(transport, struct{}{})
|
||||
return transport
|
||||
}
|
||||
|
||||
// ProxyForRequest resolves the proxy URL for a single request.
|
||||
func ProxyForRequest(req *http.Request) (*url.URL, error) {
|
||||
if req == nil || req.URL == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return defaultResolver.proxyForURL(req.URL)
|
||||
}
|
||||
|
||||
// CurrentStatus returns the latest proxy resolver snapshot without exposing
|
||||
// proxy credentials.
|
||||
func CurrentStatus() Status {
|
||||
return statusFromSnapshot(defaultResolver.currentSnapshot())
|
||||
}
|
||||
|
||||
type proxyResolver struct {
|
||||
mu sync.Mutex
|
||||
snapshot proxySnapshot
|
||||
}
|
||||
|
||||
type proxySnapshot struct {
|
||||
expiresAt time.Time
|
||||
source string
|
||||
active bool
|
||||
description string
|
||||
key string
|
||||
httpProxy string
|
||||
httpsProxy string
|
||||
proxyFunc func(*url.URL) (*url.URL, error)
|
||||
systemBypass []string
|
||||
excludeSimple bool
|
||||
pacIgnored bool
|
||||
loadErrMessage string
|
||||
}
|
||||
|
||||
// Status is a sanitized summary of the proxy resolver's current decision.
|
||||
type Status struct {
|
||||
Source string `json:"source"`
|
||||
Active bool `json:"active"`
|
||||
UsingSystemProxy bool `json:"usingSystemProxy"`
|
||||
UsingEnvProxy bool `json:"usingEnvProxy"`
|
||||
HTTPProxy string `json:"httpProxy"`
|
||||
HTTPSProxy string `json:"httpsProxy"`
|
||||
Description string `json:"description"`
|
||||
PACIgnored bool `json:"pacIgnored"`
|
||||
LoadError string `json:"loadError"`
|
||||
}
|
||||
|
||||
type systemProxyConfig struct {
|
||||
Source string
|
||||
HTTPProxy string
|
||||
HTTPSProxy string
|
||||
SOCKSProxy string
|
||||
Bypass []string
|
||||
ExcludeSimple bool
|
||||
PACEnabled bool
|
||||
PACURL string
|
||||
LoadErrMessage string
|
||||
}
|
||||
|
||||
func (resolver *proxyResolver) proxyForURL(reqURL *url.URL) (*url.URL, error) {
|
||||
snapshot := resolver.currentSnapshot()
|
||||
if snapshot.proxyFunc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if shouldBypassSystemProxy(reqURL, snapshot.systemBypass, snapshot.excludeSimple) {
|
||||
return nil, nil
|
||||
}
|
||||
return snapshot.proxyFunc(reqURL)
|
||||
}
|
||||
|
||||
func (resolver *proxyResolver) currentSnapshot() proxySnapshot {
|
||||
now := time.Now()
|
||||
resolver.mu.Lock()
|
||||
defer resolver.mu.Unlock()
|
||||
|
||||
if !resolver.snapshot.expiresAt.IsZero() && now.Before(resolver.snapshot.expiresAt) {
|
||||
return resolver.snapshot
|
||||
}
|
||||
next := buildProxySnapshot(now)
|
||||
if next.key != resolver.snapshot.key {
|
||||
logProxySnapshot(next)
|
||||
closeIdleProxyConnections()
|
||||
}
|
||||
resolver.snapshot = next
|
||||
return next
|
||||
}
|
||||
|
||||
func (resolver *proxyResolver) logCurrentSnapshot(prefix string) {
|
||||
resolver.mu.Lock()
|
||||
next := buildProxySnapshot(time.Now())
|
||||
if prefix != "" {
|
||||
next.description = strings.TrimSpace(prefix + "; " + next.description)
|
||||
}
|
||||
logProxySnapshot(next)
|
||||
resolver.snapshot = next
|
||||
closeIdleProxyConnections()
|
||||
resolver.mu.Unlock()
|
||||
}
|
||||
|
||||
func buildProxySnapshot(now time.Time) proxySnapshot {
|
||||
if cfg, ok := envProxyConfig(); ok {
|
||||
return snapshotFromConfig(now, "env", cfg, nil, false, false, "")
|
||||
}
|
||||
|
||||
systemConfig := loadSystemProxyConfig()
|
||||
httpProxy := systemConfig.HTTPProxy
|
||||
httpsProxy := systemConfig.HTTPSProxy
|
||||
if systemConfig.SOCKSProxy != "" {
|
||||
if httpProxy == "" {
|
||||
httpProxy = systemConfig.SOCKSProxy
|
||||
}
|
||||
if httpsProxy == "" {
|
||||
httpsProxy = systemConfig.SOCKSProxy
|
||||
}
|
||||
}
|
||||
if httpProxy == "" && httpsProxy == "" {
|
||||
return proxySnapshot{
|
||||
expiresAt: now.Add(proxyCacheTTL),
|
||||
source: directSource(systemConfig),
|
||||
description: directDescription(systemConfig),
|
||||
key: directKey(systemConfig),
|
||||
pacIgnored: systemConfig.PACEnabled,
|
||||
loadErrMessage: systemConfig.LoadErrMessage,
|
||||
}
|
||||
}
|
||||
|
||||
cfg := httpproxy.Config{
|
||||
HTTPProxy: httpProxy,
|
||||
HTTPSProxy: httpsProxy,
|
||||
NoProxy: joinNoProxy(alwaysNoProxyList, envNoProxy(), strings.Join(systemConfig.Bypass, ",")),
|
||||
}
|
||||
return snapshotFromConfig(now, "system", cfg, systemConfig.Bypass, systemConfig.ExcludeSimple, systemConfig.PACEnabled, systemConfig.LoadErrMessage)
|
||||
}
|
||||
|
||||
func snapshotFromConfig(now time.Time, source string, cfg httpproxy.Config, bypass []string, excludeSimple bool, pacIgnored bool, loadErr string) proxySnapshot {
|
||||
proxyFunc := cfg.ProxyFunc()
|
||||
httpDesc := sanitizeProxyValue(cfg.HTTPProxy)
|
||||
httpsDesc := sanitizeProxyValue(cfg.HTTPSProxy)
|
||||
active := httpDesc != "" || httpsDesc != ""
|
||||
description := fmt.Sprintf("source=%s http=%s https=%s", source, displayProxyDesc(httpDesc), displayProxyDesc(httpsDesc))
|
||||
if cfg.NoProxy != "" {
|
||||
description += " no_proxy=configured"
|
||||
}
|
||||
if excludeSimple {
|
||||
description += " exclude_simple=true"
|
||||
}
|
||||
if pacIgnored {
|
||||
description += " pac=ignored"
|
||||
}
|
||||
if loadErr != "" {
|
||||
description += " load_error=" + loadErr
|
||||
}
|
||||
return proxySnapshot{
|
||||
expiresAt: now.Add(proxyCacheTTL),
|
||||
source: source,
|
||||
active: active,
|
||||
description: description,
|
||||
key: strings.Join([]string{source, httpDesc, httpsDesc, cfg.NoProxy, fmt.Sprint(excludeSimple), fmt.Sprint(pacIgnored), loadErr}, "|"),
|
||||
httpProxy: httpDesc,
|
||||
httpsProxy: httpsDesc,
|
||||
proxyFunc: proxyFunc,
|
||||
systemBypass: cleanBypassRules(bypass),
|
||||
excludeSimple: excludeSimple,
|
||||
pacIgnored: pacIgnored,
|
||||
loadErrMessage: loadErr,
|
||||
}
|
||||
}
|
||||
|
||||
func envProxyConfig() (httpproxy.Config, bool) {
|
||||
allProxy := firstEnv("ALL_PROXY", "all_proxy")
|
||||
httpProxy := firstEnv("HTTP_PROXY", "http_proxy")
|
||||
httpsProxy := firstEnv("HTTPS_PROXY", "https_proxy")
|
||||
if httpProxy == "" {
|
||||
httpProxy = allProxy
|
||||
}
|
||||
if httpsProxy == "" {
|
||||
httpsProxy = allProxy
|
||||
}
|
||||
hasProxy := httpProxy != "" || httpsProxy != "" || allProxy != ""
|
||||
return httpproxy.Config{
|
||||
HTTPProxy: httpProxy,
|
||||
HTTPSProxy: httpsProxy,
|
||||
NoProxy: joinNoProxy(alwaysNoProxyList, envNoProxy()),
|
||||
CGI: os.Getenv("REQUEST_METHOD") != "",
|
||||
}, hasProxy
|
||||
}
|
||||
|
||||
func envNoProxy() string {
|
||||
return firstEnv("NO_PROXY", "no_proxy")
|
||||
}
|
||||
|
||||
func firstEnv(names ...string) string {
|
||||
for _, name := range names {
|
||||
if value := strings.TrimSpace(os.Getenv(name)); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func directSource(cfg systemProxyConfig) string {
|
||||
if cfg.PACEnabled {
|
||||
return "system-pac-ignored"
|
||||
}
|
||||
if cfg.LoadErrMessage != "" {
|
||||
return "direct"
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
||||
func directDescription(cfg systemProxyConfig) string {
|
||||
parts := []string{"source=direct"}
|
||||
if cfg.PACEnabled {
|
||||
parts = append(parts, "pac=ignored")
|
||||
}
|
||||
if cfg.LoadErrMessage != "" {
|
||||
parts = append(parts, "load_error="+cfg.LoadErrMessage)
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func directKey(cfg systemProxyConfig) string {
|
||||
return strings.Join([]string{"direct", fmt.Sprint(cfg.PACEnabled), cfg.PACURL, cfg.LoadErrMessage}, "|")
|
||||
}
|
||||
|
||||
func logProxySnapshot(snapshot proxySnapshot) {
|
||||
if snapshot.description == "" {
|
||||
snapshot.description = "source=direct"
|
||||
}
|
||||
logger.Infof("net proxy: %s", snapshot.description)
|
||||
}
|
||||
|
||||
func statusFromSnapshot(snapshot proxySnapshot) Status {
|
||||
source := strings.TrimSpace(snapshot.source)
|
||||
if source == "" || source == "none" || source == "system-pac-ignored" {
|
||||
source = "direct"
|
||||
}
|
||||
return Status{
|
||||
Source: source,
|
||||
Active: snapshot.active,
|
||||
UsingSystemProxy: snapshot.active && snapshot.source == "system",
|
||||
UsingEnvProxy: snapshot.active && snapshot.source == "env",
|
||||
HTTPProxy: snapshot.httpProxy,
|
||||
HTTPSProxy: snapshot.httpsProxy,
|
||||
Description: snapshot.description,
|
||||
PACIgnored: snapshot.pacIgnored,
|
||||
LoadError: snapshot.loadErrMessage,
|
||||
}
|
||||
}
|
||||
|
||||
func closeIdleProxyConnections() {
|
||||
proxyTransports.Range(func(key any, _ any) bool {
|
||||
if transport, ok := key.(*http.Transport); ok && transport != nil {
|
||||
transport.CloseIdleConnections()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func cloneDefaultTransport() *http.Transport {
|
||||
if transport, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
clone := transport.Clone()
|
||||
clone.Proxy = nil
|
||||
return clone
|
||||
}
|
||||
return &http.Transport{
|
||||
DialContext: (&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func joinNoProxy(parts ...string) string {
|
||||
items := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
for _, value := range strings.FieldsFunc(part, func(r rune) bool {
|
||||
return r == ',' || r == ';'
|
||||
}) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || strings.EqualFold(value, "<local>") {
|
||||
continue
|
||||
}
|
||||
items = append(items, value)
|
||||
}
|
||||
}
|
||||
return strings.Join(items, ",")
|
||||
}
|
||||
|
||||
func cleanBypassRules(rules []string) []string {
|
||||
cleaned := make([]string, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
for _, value := range strings.FieldsFunc(rule, func(r rune) bool {
|
||||
return r == ',' || r == ';'
|
||||
}) {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
cleaned = append(cleaned, value)
|
||||
}
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func shouldBypassSystemProxy(reqURL *url.URL, rules []string, excludeSimple bool) bool {
|
||||
if reqURL == nil {
|
||||
return false
|
||||
}
|
||||
host := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(reqURL.Hostname()), "."))
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
if excludeSimple && isSimpleHostname(host) {
|
||||
return true
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if ruleMatchesHost(rule, host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSimpleHostname(host string) bool {
|
||||
if host == "" || strings.Contains(host, ".") {
|
||||
return false
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return false
|
||||
}
|
||||
return !strings.Contains(host, ":")
|
||||
}
|
||||
|
||||
func ruleMatchesHost(rule string, host string) bool {
|
||||
rule = strings.TrimSpace(strings.TrimSuffix(rule, "."))
|
||||
if rule == "" {
|
||||
return false
|
||||
}
|
||||
if rule == "*" {
|
||||
return true
|
||||
}
|
||||
if strings.EqualFold(rule, "<local>") {
|
||||
return isSimpleHostname(host)
|
||||
}
|
||||
if strings.Contains(rule, "://") {
|
||||
if parsed, err := url.Parse(rule); err == nil {
|
||||
rule = strings.TrimSpace(parsed.Hostname())
|
||||
}
|
||||
}
|
||||
if strings.Contains(rule, ":") {
|
||||
if ruleHost, _, err := net.SplitHostPort(rule); err == nil {
|
||||
rule = ruleHost
|
||||
}
|
||||
}
|
||||
rule = strings.ToLower(strings.TrimSuffix(rule, "."))
|
||||
if strings.ContainsAny(rule, "*?") {
|
||||
matched, err := path.Match(rule, host)
|
||||
return err == nil && matched
|
||||
}
|
||||
if strings.HasPrefix(rule, ".") {
|
||||
return strings.HasSuffix(host, rule)
|
||||
}
|
||||
if strings.HasPrefix(rule, "*.") {
|
||||
return strings.HasSuffix(host, strings.TrimPrefix(rule, "*"))
|
||||
}
|
||||
return host == rule || strings.HasSuffix(host, "."+rule)
|
||||
}
|
||||
|
||||
func sanitizeProxyValue(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
parsed, err := url.Parse(raw)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
parsed, err = url.Parse("http://" + raw)
|
||||
}
|
||||
if err != nil || parsed == nil || parsed.Host == "" {
|
||||
return "invalid"
|
||||
}
|
||||
scheme := strings.TrimSpace(parsed.Scheme)
|
||||
if scheme == "" {
|
||||
scheme = "http"
|
||||
}
|
||||
return scheme + "://" + parsed.Host
|
||||
}
|
||||
|
||||
func displayProxyDesc(value string) string {
|
||||
if value == "" {
|
||||
return "none"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeProxyAddress(defaultScheme string, raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(raw, "://") {
|
||||
return raw
|
||||
}
|
||||
if defaultScheme == "" {
|
||||
defaultScheme = "http"
|
||||
}
|
||||
return defaultScheme + "://" + raw
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//go:build darwin
|
||||
|
||||
package netproxy
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func loadSystemProxyConfig() systemProxyConfig {
|
||||
output, err := exec.Command("scutil", "--proxy").Output()
|
||||
if err != nil {
|
||||
return systemProxyConfig{
|
||||
Source: "macos",
|
||||
LoadErrMessage: sanitizeLoadError(err),
|
||||
}
|
||||
}
|
||||
cfg := parseDarwinProxyOutput(string(output))
|
||||
cfg.Source = "macos"
|
||||
return cfg
|
||||
}
|
||||
|
||||
func parseDarwinProxyOutput(output string) systemProxyConfig {
|
||||
var cfg systemProxyConfig
|
||||
values := map[string]string{}
|
||||
inExceptions := false
|
||||
|
||||
for _, rawLine := range strings.Split(output, "\n") {
|
||||
line := strings.TrimSpace(rawLine)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if line == "}" {
|
||||
inExceptions = false
|
||||
continue
|
||||
}
|
||||
key, value, ok := strings.Cut(line, " : ")
|
||||
if !ok {
|
||||
key, value, ok = strings.Cut(line, ":")
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
value = strings.TrimSpace(value)
|
||||
if inExceptions {
|
||||
if value != "" && value != "<array> {" {
|
||||
cfg.Bypass = append(cfg.Bypass, value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if key == "ExceptionsList" {
|
||||
inExceptions = strings.Contains(value, "<array>")
|
||||
continue
|
||||
}
|
||||
values[key] = value
|
||||
}
|
||||
|
||||
if boolValue(values["HTTPEnable"]) {
|
||||
cfg.HTTPProxy = hostPortProxy("http", values["HTTPProxy"], values["HTTPPort"])
|
||||
}
|
||||
if boolValue(values["HTTPSEnable"]) {
|
||||
cfg.HTTPSProxy = hostPortProxy("http", values["HTTPSProxy"], values["HTTPSPort"])
|
||||
}
|
||||
if boolValue(values["SOCKSEnable"]) {
|
||||
cfg.SOCKSProxy = hostPortProxy("socks5", values["SOCKSProxy"], values["SOCKSPort"])
|
||||
}
|
||||
cfg.ExcludeSimple = boolValue(values["ExcludeSimpleHostnames"])
|
||||
cfg.PACEnabled = boolValue(values["ProxyAutoConfigEnable"]) || boolValue(values["ProxyAutoDiscoveryEnable"])
|
||||
cfg.PACURL = strings.TrimSpace(values["ProxyAutoConfigURLString"])
|
||||
return cfg
|
||||
}
|
||||
|
||||
func boolValue(value string) bool {
|
||||
value = strings.TrimSpace(strings.ToLower(value))
|
||||
return value == "1" || value == "true" || value == "yes"
|
||||
}
|
||||
|
||||
func hostPortProxy(scheme string, host string, portValue string) string {
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
port := strings.TrimSpace(portValue)
|
||||
if port != "" {
|
||||
if _, err := strconv.Atoi(port); err == nil {
|
||||
host = host + ":" + port
|
||||
}
|
||||
}
|
||||
return normalizeProxyAddress(scheme, host)
|
||||
}
|
||||
|
||||
func sanitizeLoadError(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
text := strings.TrimSpace(err.Error())
|
||||
if text == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return strings.ReplaceAll(text, " ", "_")
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build !darwin && !windows
|
||||
|
||||
package netproxy
|
||||
|
||||
func loadSystemProxyConfig() systemProxyConfig {
|
||||
return systemProxyConfig{Source: "env-only"}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//go:build windows
|
||||
|
||||
package netproxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const internetSettingsRegistryPath = `Software\Microsoft\Windows\CurrentVersion\Internet Settings`
|
||||
|
||||
func loadSystemProxyConfig() systemProxyConfig {
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, internetSettingsRegistryPath, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return systemProxyConfig{
|
||||
Source: "windows",
|
||||
LoadErrMessage: sanitizeLoadError(err),
|
||||
}
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
var cfg systemProxyConfig
|
||||
cfg.Source = "windows"
|
||||
if value, _, err := key.GetStringValue("AutoConfigURL"); err == nil && strings.TrimSpace(value) != "" {
|
||||
cfg.PACEnabled = true
|
||||
cfg.PACURL = strings.TrimSpace(value)
|
||||
}
|
||||
if value, _, err := key.GetIntegerValue("AutoDetect"); err == nil && value != 0 {
|
||||
cfg.PACEnabled = true
|
||||
}
|
||||
if value, _, err := key.GetStringValue("ProxyOverride"); err == nil {
|
||||
cfg.Bypass, cfg.ExcludeSimple = parseWindowsProxyOverride(value)
|
||||
}
|
||||
enabled, _, err := key.GetIntegerValue("ProxyEnable")
|
||||
if err != nil || enabled == 0 {
|
||||
return cfg
|
||||
}
|
||||
proxyServer, _, err := key.GetStringValue("ProxyServer")
|
||||
if err != nil {
|
||||
cfg.LoadErrMessage = sanitizeLoadError(err)
|
||||
return cfg
|
||||
}
|
||||
cfg.HTTPProxy, cfg.HTTPSProxy, cfg.SOCKSProxy = parseWindowsProxyServer(proxyServer)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func parseWindowsProxyServer(raw string) (string, string, string) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", "", ""
|
||||
}
|
||||
if !strings.Contains(raw, "=") {
|
||||
proxy := normalizeProxyAddress("http", raw)
|
||||
return proxy, proxy, ""
|
||||
}
|
||||
|
||||
var httpProxy, httpsProxy, socksProxy string
|
||||
for _, part := range strings.Split(raw, ";") {
|
||||
key, value, ok := strings.Cut(part, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
key = strings.ToLower(strings.TrimSpace(key))
|
||||
value = strings.TrimSpace(value)
|
||||
switch key {
|
||||
case "http":
|
||||
httpProxy = normalizeProxyAddress("http", value)
|
||||
case "https":
|
||||
httpsProxy = normalizeProxyAddress("http", value)
|
||||
case "socks", "socks5":
|
||||
socksProxy = normalizeProxyAddress("socks5", value)
|
||||
}
|
||||
}
|
||||
return httpProxy, httpsProxy, socksProxy
|
||||
}
|
||||
|
||||
func parseWindowsProxyOverride(raw string) ([]string, bool) {
|
||||
var (
|
||||
rules []string
|
||||
excludeSimple bool
|
||||
)
|
||||
for _, part := range strings.Split(raw, ";") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(part, "<local>") {
|
||||
excludeSimple = true
|
||||
continue
|
||||
}
|
||||
rules = append(rules, part)
|
||||
}
|
||||
return rules, excludeSimple
|
||||
}
|
||||
|
||||
func sanitizeLoadError(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
text := strings.TrimSpace(err.Error())
|
||||
if text == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return strings.ReplaceAll(text, " ", "_")
|
||||
}
|
||||
Reference in New Issue
Block a user