mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-11 23:53:19 -10:00
bypassed() matched only exact entries and a leading "*". The bypass list Clash, v2rayN and xray write by default is `<local>;localhost;127.*;10.*; ...;192.168.*`, so every trailing-wildcard entry missed. It also lacked WinINET's implicit rule that loopback goes direct unless `<-loopback>` is listed. In the default auto mode, on Windows with such a system proxy, requests to local and LAN model endpoints went through the proxy: Ollama or LM Studio on 127.0.0.1, vLLM on 192.168.x. That added a hop, failed outright when the proxy routed to a remote exit, and made a cancelled stream keep the local model generating. Cancel closed only the client-to-proxy hop, and the proxy held its upstream connection open. This is why TestStreamCancelDoesNotReconnect hung on a developer machine with a system proxy. CI's Windows runners have no system proxy, and the package is not in the PR smoke set. "*" is now a wildcard anywhere in an entry. Only "*" is special, and brackets are trimmed from IPv6 entries. localhost, 127/8 and ::1 bypass unless `<-loopback>` is present. TestBypassed gains the trailing-wildcard, near-miss (192.169.* vs 192.168.*, 110.* vs 10.*), mid-pattern and loopback cases. It fails on 8 of them without the fix.
107 lines
2.6 KiB
Go
107 lines
2.6 KiB
Go
// Package sysproxy resolves the OS-level proxy (Windows system/PAC settings)
|
|
// for a target URL. ForURL returns nil on platforms without system-proxy
|
|
// support or when no proxy applies, so callers fall back to direct/env.
|
|
package sysproxy
|
|
|
|
import (
|
|
"net/netip"
|
|
"net/url"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
func splitList(s string) []string {
|
|
return strings.FieldsFunc(s, func(r rune) bool {
|
|
return r == ';' || r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
|
})
|
|
}
|
|
|
|
// parseProxyList picks a proxy from a WinHTTP/IE proxy string for scheme. The
|
|
// string is either "host:port" (all protocols) or "http=h:p;https=h:p" form.
|
|
func parseProxyList(list, scheme string) *url.URL {
|
|
var fallback string
|
|
for _, f := range splitList(list) {
|
|
if before, after, ok := strings.Cut(f, "="); ok {
|
|
if strings.EqualFold(before, scheme) {
|
|
return hostProxyURL(after)
|
|
}
|
|
continue
|
|
}
|
|
if fallback == "" {
|
|
fallback = f
|
|
}
|
|
}
|
|
if fallback != "" {
|
|
return hostProxyURL(fallback)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hostProxyURL(hostport string) *url.URL {
|
|
hostport = strings.TrimSpace(hostport)
|
|
if i := strings.Index(hostport, "://"); i >= 0 {
|
|
hostport = hostport[i+3:]
|
|
}
|
|
if hostport == "" {
|
|
return nil
|
|
}
|
|
return &url.URL{Scheme: "http", Host: hostport}
|
|
}
|
|
|
|
// bypassed reports whether host matches a WinINET proxy-bypass entry. "<local>"
|
|
// matches dotless (intranet) hosts, and "*" is a wildcard anywhere in an entry
|
|
// ("127.*", "*.corp.local"). Loopback is bypassed unless "<-loopback>" is listed,
|
|
// as WinINET does.
|
|
func bypassed(host, bypass string) bool {
|
|
host = strings.ToLower(strings.TrimSpace(host))
|
|
if host == "" {
|
|
return false
|
|
}
|
|
entries := splitList(strings.ToLower(bypass))
|
|
if isLoopback(host) && !slices.Contains(entries, "<-loopback>") {
|
|
return true
|
|
}
|
|
for _, e := range entries {
|
|
if e == "<local>" {
|
|
if !strings.Contains(host, ".") {
|
|
return true
|
|
}
|
|
continue
|
|
}
|
|
if wildcardMatch(strings.Trim(e, "[]"), host) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isLoopback(host string) bool {
|
|
if host == "localhost" {
|
|
return true
|
|
}
|
|
ip, err := netip.ParseAddr(host)
|
|
return err == nil && ip.IsLoopback()
|
|
}
|
|
|
|
// wildcardMatch reports whether s matches pattern, where "*" matches any run of
|
|
// characters and every other character matches itself.
|
|
func wildcardMatch(pattern, s string) bool {
|
|
parts := strings.Split(pattern, "*")
|
|
if len(parts) == 1 {
|
|
return pattern == s
|
|
}
|
|
if !strings.HasPrefix(s, parts[0]) {
|
|
return false
|
|
}
|
|
s = s[len(parts[0]):]
|
|
last := parts[len(parts)-1]
|
|
for _, part := range parts[1 : len(parts)-1] {
|
|
i := strings.Index(s, part)
|
|
if i < 0 {
|
|
return false
|
|
}
|
|
s = s[i+len(part):]
|
|
}
|
|
return len(s) >= len(last) && strings.HasSuffix(s, last)
|
|
}
|