diff --git a/.github/workflows/windows-proxy.yml b/.github/workflows/windows-proxy.yml index 9ccb30c8..0874c7e3 100644 --- a/.github/workflows/windows-proxy.yml +++ b/.github/workflows/windows-proxy.yml @@ -42,6 +42,8 @@ jobs: - name: Build probe helper shell: pwsh run: | + # Out-File does not create parent directories. + New-Item -ItemType Directory -Force -Path probe, probe/fetch, probe/bin | Out-Null @' package main @@ -49,7 +51,6 @@ jobs: "fmt" "net/http" "os" - "time" "github.com/topcheer/ggcode/internal/util" ) @@ -74,9 +75,10 @@ jobs: import ( "fmt" + "io" "net/http" "os" - "io" + "time" "github.com/topcheer/ggcode/internal/util" ) @@ -141,16 +143,26 @@ jobs: New-Item -ItemType Directory -Force -Path probe/www | Out-Null $pac | Out-File -Encoding ascii probe/www/proxy.pac + # Serve the PAC script on 127.0.0.1:8099 -- AutoConfigURL points + # here and fetchPACScript GETs it, so something must listen. + $py = if (Get-Command python -ErrorAction SilentlyContinue) { 'python' } else { 'py' } + $server = Start-Process -FilePath $py -ArgumentList '-m','http.server','8099','--directory','probe/www' -PassThru -WindowStyle Hidden $reg = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" Set-ItemProperty $reg AutoConfigURL "http://127.0.0.1:8099/proxy.pac" try { + $up = $false + foreach ($i in 1..20) { + try { Invoke-WebRequest -NoProxy -UseBasicParsing http://127.0.0.1:8099/proxy.pac -TimeoutSec 2 | Out-Null; $up = $true; break } catch { Start-Sleep -Milliseconds 500 } + } + if (-not $up) { Write-Error "PAC server on :8099 did not come up"; exit 1 } $out = & probe/bin/probe.exe if ($out -notmatch '^PROXY http://127\.0\.0\.1:8888') { Write-Error "expected PAC-directed proxy, got: $out"; exit 1 } Write-Host "layer3 OK: $out" } finally { Remove-ItemProperty $reg AutoConfigURL + if ($server -and -not $server.HasExited) { Stop-Process -Id $server.Id -Force } } - name: Table tests (all platforms) shell: pwsh - run: go test -tags goolm -count=1 -run "PAC|SmartProxy" ./internal/util/ + run: go test -tags goolm -count=1 -run "PAC|SmartProxy|Directive" ./internal/util/ diff --git a/internal/util/sysproxy_windows.go b/internal/util/sysproxy_windows.go index 599f205e..5f9b62ff 100644 --- a/internal/util/sysproxy_windows.go +++ b/internal/util/sysproxy_windows.go @@ -15,6 +15,7 @@ import ( "github.com/mattn/go-ieproxy" "github.com/saucelabs/forwarder/pac" + "golang.org/x/net/http/httpproxy" ) // Windows system-proxy + PAC support (#761). @@ -69,6 +70,35 @@ type pacState struct { resultCache sync.Map } +// staticSystemProxy resolves the registry static proxy (ProxyEnable / +// ProxyServer / ProxyOverride) for a single request, mirroring +// ieproxy's staticProxy but never consulting the auto-config entry. +// Returns (nil, nil) when the static proxy is disabled. Note: an +// autoDetect flag alone (WPAD, no AutoConfigURL) must NOT suppress the +// static proxy -- github runners boot with fAutoDetect=true, and the +// guard used to turn layer 2 into DIRECT there. +func staticSystemProxy(req *http.Request) (*url.URL, error) { + conf := ieproxy.GetConf() + if !conf.Static.Active || req.URL == nil { + return nil, nil + } + cfg := httpproxy.Config{ + HTTPSProxy: protocolFallback(conf.Static.Protocols, "https"), + HTTPProxy: protocolFallback(conf.Static.Protocols, "http"), + NoProxy: conf.Static.NoProxy, + } + return cfg.ProxyFunc()(req.URL) +} + +// protocolFallback returns the per-protocol static proxy, falling back to +// the protocol-less default entry ("http=...;https=..." vs "host:port"). +func protocolFallback(m map[string]string, proto string) string { + if v, ok := m[proto]; ok { + return v + } + return m[""] +} + var thePAC pacState func buildWindowsProxyFunc() func(*http.Request) (*url.URL, error) { @@ -78,10 +108,13 @@ func buildWindowsProxyFunc() func(*http.Request) (*url.URL, error) { return u, err } - // 2. Static system proxy. ieproxy.GetProxyFunc reads the registry - // (ProxyEnable/ProxyServer/ProxyOverride) and handles per-protocol - // formats; it returns (nil, nil) for direct when disabled. - if u, err := ieproxy.GetProxyFunc()(req); err == nil && u != nil { + // 2. Static system proxy. Read the registry conf ourselves and + // build an httpproxy.Config only from the static entry. We must NOT + // use ieproxy.GetProxyFunc() here: when AutoConfig is active it + // resolves PAC itself via WinHTTP and returns scheme-less URLs + // (&url.URL{Host: ...}), which both short-circuits our own PAC + // engine below (dead code) and mis-handles SOCKS directives. + if u, err := staticSystemProxy(req); err == nil && u != nil { return u, nil }