1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-08 04:29:24 +00:00

fix(status): improve proxy detection and add parser tests

This commit is contained in:
tw93
2026-02-07 17:43:59 +08:00
parent 9ec0db9245
commit 425f23a739
3 changed files with 222 additions and 23 deletions

View File

@@ -160,7 +160,7 @@ const NetworkHistorySize = 120 // Increased history size for wider graph
type ProxyStatus struct {
Enabled bool
Type string // HTTP, SOCKS, System
Type string // HTTP, HTTPS, SOCKS, PAC, WPAD, TUN
Host string
}

View File

@@ -2,9 +2,11 @@ package main
import (
"context"
"net/url"
"os"
"runtime"
"sort"
"strconv"
"strings"
"time"
@@ -114,23 +116,8 @@ func isNoiseInterface(name string) bool {
}
func collectProxy() ProxyStatus {
// Check environment variables first.
for _, env := range []string{"https_proxy", "HTTPS_PROXY", "http_proxy", "HTTP_PROXY"} {
if val := os.Getenv(env); val != "" {
proxyType := "HTTP"
if strings.HasPrefix(val, "socks") {
proxyType = "SOCKS"
}
// Extract host.
host := val
if strings.Contains(host, "://") {
host = strings.SplitN(host, "://", 2)[1]
}
if idx := strings.Index(host, "@"); idx >= 0 {
host = host[idx+1:]
}
return ProxyStatus{Enabled: true, Type: proxyType, Host: host}
}
if proxy := collectProxyFromEnv(os.Getenv); proxy.Enabled {
return proxy
}
// macOS: check system proxy via scutil.
@@ -139,14 +126,166 @@ func collectProxy() ProxyStatus {
defer cancel()
out, err := runCmd(ctx, "scutil", "--proxy")
if err == nil {
if strings.Contains(out, "HTTPEnable : 1") || strings.Contains(out, "HTTPSEnable : 1") {
return ProxyStatus{Enabled: true, Type: "System", Host: "System Proxy"}
}
if strings.Contains(out, "SOCKSEnable : 1") {
return ProxyStatus{Enabled: true, Type: "SOCKS", Host: "System Proxy"}
if proxy := collectProxyFromScutilOutput(out); proxy.Enabled {
return proxy
}
}
if proxy := collectProxyFromTunInterfaces(); proxy.Enabled {
return proxy
}
}
return ProxyStatus{Enabled: false}
}
func collectProxyFromEnv(getenv func(string) string) ProxyStatus {
// Include ALL_PROXY for users running proxy tools that only export a single variable.
envKeys := []string{
"https_proxy", "HTTPS_PROXY",
"http_proxy", "HTTP_PROXY",
"all_proxy", "ALL_PROXY",
}
for _, key := range envKeys {
val := strings.TrimSpace(getenv(key))
if val == "" {
continue
}
proxyType := "HTTP"
lower := strings.ToLower(val)
if strings.HasPrefix(lower, "socks") {
proxyType = "SOCKS"
}
host := parseProxyHost(val)
if host == "" {
host = val
}
return ProxyStatus{Enabled: true, Type: proxyType, Host: host}
}
return ProxyStatus{Enabled: false}
}
func collectProxyFromScutilOutput(out string) ProxyStatus {
if out == "" {
return ProxyStatus{Enabled: false}
}
if scutilProxyEnabled(out, "SOCKSEnable") {
host := joinHostPort(scutilProxyValue(out, "SOCKSProxy"), scutilProxyValue(out, "SOCKSPort"))
if host == "" {
host = "System Proxy"
}
return ProxyStatus{Enabled: true, Type: "SOCKS", Host: host}
}
if scutilProxyEnabled(out, "HTTPSEnable") {
host := joinHostPort(scutilProxyValue(out, "HTTPSProxy"), scutilProxyValue(out, "HTTPSPort"))
if host == "" {
host = "System Proxy"
}
return ProxyStatus{Enabled: true, Type: "HTTPS", Host: host}
}
if scutilProxyEnabled(out, "HTTPEnable") {
host := joinHostPort(scutilProxyValue(out, "HTTPProxy"), scutilProxyValue(out, "HTTPPort"))
if host == "" {
host = "System Proxy"
}
return ProxyStatus{Enabled: true, Type: "HTTP", Host: host}
}
if scutilProxyEnabled(out, "ProxyAutoConfigEnable") {
pacURL := scutilProxyValue(out, "ProxyAutoConfigURLString")
host := parseProxyHost(pacURL)
if host == "" {
host = "PAC"
}
return ProxyStatus{Enabled: true, Type: "PAC", Host: host}
}
if scutilProxyEnabled(out, "ProxyAutoDiscoveryEnable") {
return ProxyStatus{Enabled: true, Type: "WPAD", Host: "Auto Discovery"}
}
return ProxyStatus{Enabled: false}
}
func collectProxyFromTunInterfaces() ProxyStatus {
stats, err := net.IOCounters(true)
if err != nil {
return ProxyStatus{Enabled: false}
}
var activeTun []string
for _, s := range stats {
lower := strings.ToLower(s.Name)
if strings.HasPrefix(lower, "utun") || strings.HasPrefix(lower, "tun") {
if s.BytesRecv+s.BytesSent > 0 {
activeTun = append(activeTun, s.Name)
}
}
}
if len(activeTun) == 0 {
return ProxyStatus{Enabled: false}
}
sort.Strings(activeTun)
host := activeTun[0]
if len(activeTun) > 1 {
host = activeTun[0] + "+"
}
return ProxyStatus{Enabled: true, Type: "TUN", Host: host}
}
func scutilProxyEnabled(out, key string) bool {
return scutilProxyValue(out, key) == "1"
}
func scutilProxyValue(out, key string) string {
prefix := key + " :"
for _, line := range strings.Split(out, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, prefix) {
return strings.TrimSpace(strings.TrimPrefix(line, prefix))
}
}
return ""
}
func parseProxyHost(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
target := raw
if !strings.Contains(target, "://") {
target = "http://" + target
}
parsed, err := url.Parse(target)
if err != nil {
return ""
}
host := parsed.Host
if host == "" {
return ""
}
return strings.TrimPrefix(host, "@")
}
func joinHostPort(host, port string) string {
host = strings.TrimSpace(host)
port = strings.TrimSpace(port)
if host == "" {
return ""
}
if port == "" {
return host
}
if _, err := strconv.Atoi(port); err != nil {
return host
}
return host + ":" + port
}

View File

@@ -0,0 +1,60 @@
package main
import "testing"
func TestCollectProxyFromEnvSupportsAllProxy(t *testing.T) {
env := map[string]string{
"ALL_PROXY": "socks5://127.0.0.1:7890",
}
getenv := func(key string) string {
return env[key]
}
got := collectProxyFromEnv(getenv)
if !got.Enabled {
t.Fatalf("expected proxy enabled")
}
if got.Type != "SOCKS" {
t.Fatalf("expected SOCKS type, got %s", got.Type)
}
if got.Host != "127.0.0.1:7890" {
t.Fatalf("unexpected host: %s", got.Host)
}
}
func TestCollectProxyFromScutilOutputPAC(t *testing.T) {
out := `
<dictionary> {
ProxyAutoConfigEnable : 1
ProxyAutoConfigURLString : http://127.0.0.1:6152/proxy.pac
}`
got := collectProxyFromScutilOutput(out)
if !got.Enabled {
t.Fatalf("expected proxy enabled")
}
if got.Type != "PAC" {
t.Fatalf("expected PAC type, got %s", got.Type)
}
if got.Host != "127.0.0.1:6152" {
t.Fatalf("unexpected host: %s", got.Host)
}
}
func TestCollectProxyFromScutilOutputHTTPHostPort(t *testing.T) {
out := `
<dictionary> {
HTTPEnable : 1
HTTPProxy : 127.0.0.1
HTTPPort : 7890
}`
got := collectProxyFromScutilOutput(out)
if !got.Enabled {
t.Fatalf("expected proxy enabled")
}
if got.Type != "HTTP" {
t.Fatalf("expected HTTP type, got %s", got.Type)
}
if got.Host != "127.0.0.1:7890" {
t.Fatalf("unexpected host: %s", got.Host)
}
}