Trust corebin cache without Stat; batch networksetup; ActiveProxyURI instead of Config.Clone. Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
3.5 KiB
Go
153 lines
3.5 KiB
Go
//go:build darwin
|
||
|
||
package sysproxy
|
||
|
||
import (
|
||
"bufio"
|
||
"bytes"
|
||
"fmt"
|
||
"net"
|
||
"os/exec"
|
||
"strings"
|
||
"sync"
|
||
)
|
||
|
||
type darwinController struct {
|
||
mu sync.Mutex
|
||
enabled bool
|
||
host string
|
||
httpPort string
|
||
socksPort string
|
||
services []string
|
||
}
|
||
|
||
func newPlatform() Controller { return &darwinController{} }
|
||
|
||
func (c *darwinController) Enable(httpHostPort string) error {
|
||
c.mu.Lock()
|
||
defer c.mu.Unlock()
|
||
|
||
host, port, err := net.SplitHostPort(httpHostPort)
|
||
if err != nil {
|
||
return fmt.Errorf("sysproxy: bad address %q: %w", httpHostPort, err)
|
||
}
|
||
if host == "" || host == "0.0.0.0" {
|
||
host = "127.0.0.1"
|
||
}
|
||
socks := "1080"
|
||
if port == "1081" {
|
||
socks = "1080"
|
||
}
|
||
|
||
services, err := listNetworkServices()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if len(services) == 0 {
|
||
return fmt.Errorf("sysproxy: no network services")
|
||
}
|
||
|
||
// One bash process for all networksetup calls (was 6 execs × N services).
|
||
// setwebproxy / setsecurewebproxy / setsocksfirewallproxy also turn the proxy on.
|
||
var b strings.Builder
|
||
b.WriteString("set -e\n")
|
||
for _, svc := range services {
|
||
q := shellQuote(svc)
|
||
fmt.Fprintf(&b, "networksetup -setwebproxy %s %s %s\n", q, shellQuote(host), shellQuote(port))
|
||
fmt.Fprintf(&b, "networksetup -setsecurewebproxy %s %s %s\n", q, shellQuote(host), shellQuote(port))
|
||
fmt.Fprintf(&b, "networksetup -setsocksfirewallproxy %s %s %s\n", q, shellQuote(host), shellQuote(socks))
|
||
}
|
||
if err := runBash(b.String()); err != nil {
|
||
return err
|
||
}
|
||
|
||
c.enabled = true
|
||
c.host = host
|
||
c.httpPort = port
|
||
c.socksPort = socks
|
||
c.services = append([]string(nil), services...)
|
||
return nil
|
||
}
|
||
|
||
func (c *darwinController) Disable() error {
|
||
c.mu.Lock()
|
||
defer c.mu.Unlock()
|
||
if !c.enabled {
|
||
return nil
|
||
}
|
||
return c.disableServices(c.services)
|
||
}
|
||
|
||
func (c *darwinController) ForceDisable() error {
|
||
c.mu.Lock()
|
||
defer c.mu.Unlock()
|
||
svcs := c.services
|
||
return c.disableServices(svcs)
|
||
}
|
||
|
||
func (c *darwinController) disableServices(services []string) error {
|
||
if len(services) == 0 {
|
||
var err error
|
||
services, err = listNetworkServices()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
if len(services) == 0 {
|
||
c.enabled = false
|
||
c.services = nil
|
||
return nil
|
||
}
|
||
var b strings.Builder
|
||
b.WriteString("set +e\n") // best-effort: turn off what we can
|
||
for _, svc := range services {
|
||
q := shellQuote(svc)
|
||
fmt.Fprintf(&b, "networksetup -setwebproxystate %s off\n", q)
|
||
fmt.Fprintf(&b, "networksetup -setsecurewebproxystate %s off\n", q)
|
||
fmt.Fprintf(&b, "networksetup -setsocksfirewallproxystate %s off\n", q)
|
||
}
|
||
err := runBash(b.String())
|
||
c.enabled = false
|
||
c.services = nil
|
||
return err
|
||
}
|
||
|
||
func (c *darwinController) Enabled() bool {
|
||
c.mu.Lock()
|
||
defer c.mu.Unlock()
|
||
return c.enabled
|
||
}
|
||
|
||
func listNetworkServices() ([]string, error) {
|
||
out, err := exec.Command("networksetup", "-listallnetworkservices").Output()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("networksetup: %w", err)
|
||
}
|
||
var services []string
|
||
sc := bufio.NewScanner(bytes.NewReader(out))
|
||
for sc.Scan() {
|
||
line := strings.TrimSpace(sc.Text())
|
||
if line == "" || strings.HasPrefix(line, "An asterisk") {
|
||
continue
|
||
}
|
||
if strings.HasPrefix(line, "*") {
|
||
continue // disabled service
|
||
}
|
||
services = append(services, line)
|
||
}
|
||
return services, sc.Err()
|
||
}
|
||
|
||
func runBash(script string) error {
|
||
cmd := exec.Command("/bin/bash", "-c", script)
|
||
out, err := cmd.CombinedOutput()
|
||
if err != nil {
|
||
return fmt.Errorf("networksetup batch: %w (%s)", err, strings.TrimSpace(string(out)))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func shellQuote(s string) string {
|
||
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
||
}
|