arblant
firefox-vpn-client / core / internal / desktop / sysproxy_windows_test.go
sysproxy_windows_test.go290 lines · 9.9 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
//go:build windows

package desktop

import (
	"fmt"
	"strings"
	"testing"
	"time"

	"golang.org/x/sys/windows/registry"

	"github.com/armanlife/firefox-vpn-client/core/internal/config"
	"github.com/armanlife/firefox-vpn-client/core/internal/service"
)

// useTestProxyKeys points the system proxy code at temporary keys under
// HKCU\Software, so the user's real proxy setting is never touched.
func useTestProxyKeys(t *testing.T) registry.Key {
	t.Helper()
	base := fmt.Sprintf(`Software\FirefoxVPNTest-%d`, time.Now().UnixNano())
	oldSettings, oldBackup, oldAnnounce := internetSettingsKey, backupKey, announceProxyChange
	internetSettingsKey, backupKey = base+`\Internet Settings`, base+`\PreviousProxy`
	announceProxyChange = func() {}
	k, _, err := registry.CreateKey(registry.CURRENT_USER, internetSettingsKey, registry.ALL_ACCESS)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		k.Close()
		_ = registry.DeleteKey(registry.CURRENT_USER, internetSettingsKey)
		_ = registry.DeleteKey(registry.CURRENT_USER, backupKey)
		_ = registry.DeleteKey(registry.CURRENT_USER, base)
		internetSettingsKey, backupKey, announceProxyChange = oldSettings, oldBackup, oldAnnounce
	})
	return k
}

func setting(t *testing.T) proxySetting {
	t.Helper()
	k, err := registry.OpenKey(registry.CURRENT_USER, internetSettingsKey, registry.QUERY_VALUE)
	if err != nil {
		t.Fatal(err)
	}
	defer k.Close()
	return readProxySetting(k)
}

func TestSystemProxySetAndRestore(t *testing.T) {
	k := useTestProxyKeys(t)
	corp := proxySetting{enabled: true, server: "corp.example:8080", override: "*.corp.example", script: "http://corp.example/proxy.pac"}
	if err := writeProxySetting(k, corp); err != nil {
		t.Fatal(err)
	}

	// Set replaces the user's proxy and setup script, which Windows would
	// use first.
	if err := setSystemProxy("127.0.0.1:10810"); err != nil {
		t.Fatal(err)
	}
	if p := setting(t); !p.enabled || p.server != "127.0.0.1:10810" || !strings.Contains(p.override, "<local>") || p.script != "" {
		t.Fatalf("after set: %+v", p)
	}
	if err := setSystemProxy("127.0.0.1:10810"); err != nil { // again: keeps the first backup
		t.Fatal(err)
	}
	if err := restoreSystemProxy("127.0.0.1:10810"); err != nil {
		t.Fatal(err)
	}
	if p := setting(t); p != corp {
		t.Fatalf("the previous setting must come back: %+v", p)
	}

	// A proxy the user chose meanwhile stays.
	if err := setSystemProxy("127.0.0.1:10810"); err != nil {
		t.Fatal(err)
	}
	_ = k.SetStringValue("ProxyServer", "other.example:3128")
	if err := restoreSystemProxy("127.0.0.1:10810"); err != nil {
		t.Fatal(err)
	}
	if p := setting(t); p.server != "other.example:3128" {
		t.Fatalf("the user's own proxy was replaced: %+v", p)
	}

	// Clear turns any system proxy off, setup script included.
	_ = k.SetStringValue("AutoConfigURL", "http://corp.example/proxy.pac")
	if err := clearSystemProxy(); err != nil {
		t.Fatal(err)
	}
	if p := setting(t); p.enabled || p.script != "" {
		t.Fatalf("after clear: %+v", p)
	}
}

func trayStatus(phase, mode string) *service.TrayStatus {
	st := &service.TrayStatus{SystemProxy: mode}
	st.State.Phase = phase
	st.Proxy = &service.TrayProxy{Listen: "0.0.0.0:10810"}
	return st
}

// Set is carried out only while the proxy runs, and undone when it stops
// or the service is gone; clear acts once.
func TestSystemProxyModes(t *testing.T) {
	k := useTestProxyKeys(t)
	user := proxySetting{enabled: true, server: "user.example:3128"}
	if err := writeProxySetting(k, user); err != nil {
		t.Fatal(err)
	}
	var sync systemProxySync

	// While the proxy starts, set changes nothing yet.
	sync.update(trayStatus("starting", config.SystemProxySet))
	if p := setting(t); p != user || sync.active() {
		t.Fatalf("set before the proxy runs: %+v", p)
	}
	// The tray refreshes every few seconds; the setting must stay.
	for i := 0; i < 3; i++ {
		sync.update(trayStatus("running", config.SystemProxySet))
		if p := setting(t); p.server != "127.0.0.1:10810" || !p.enabled || !sync.active() {
			t.Fatalf("set while running, refresh %d: %+v", i+1, p)
		}
	}
	sync.update(trayStatus("error", config.SystemProxySet))
	if p := setting(t); p != user || sync.active() {
		t.Fatalf("the proxy stopped: the previous setting comes back: %+v", p)
	}
	sync.update(trayStatus("running", config.SystemProxySet))
	sync.update(nil)
	if p := setting(t); p != user || sync.active() {
		t.Fatalf("the service is gone: restored: %+v", p)
	}
	sync.update(trayStatus("running", config.SystemProxySet))
	sync.clear()
	if p := setting(t); p != user || sync.active() {
		t.Fatalf("the host exits: restored: %+v", p)
	}

	// Switching from set to clear restores this program's setting, then
	// turns the system proxy off.
	sync.update(trayStatus("running", config.SystemProxySet))
	sync.update(trayStatus("running", config.SystemProxyClear))
	if p := setting(t); p.enabled || p.server != "user.example:3128" || sync.active() {
		t.Fatalf("clear turns the system proxy off: %+v", p)
	}
	// Clear acts once: a proxy set by hand afterwards stays.
	_ = writeProxySetting(k, user)
	for i := 0; i < 3; i++ {
		sync.update(trayStatus("running", config.SystemProxyClear))
	}
	if p := setting(t); p != user {
		t.Fatalf("clear must not fight a later setting: %+v", p)
	}

	// A setting left behind by an earlier copy is restored when not wanted.
	_ = setSystemProxy("127.0.0.1:10810")
	var fresh systemProxySync
	fresh.update(trayStatus("starting", config.SystemProxySet))
	if p := setting(t); p != user {
		t.Fatalf("a leftover setting must be restored: %+v", p)
	}
	// A host that starts in clear keeps another program's proxy (v2rayN's,
	// say), even across refreshes.
	var clearing systemProxySync
	for i := 0; i < 3; i++ {
		clearing.update(trayStatus("running", config.SystemProxyClear))
	}
	if p := setting(t); p != user {
		t.Fatalf("a host starting in clear must keep another program's proxy: %+v", p)
	}
	// It still undoes a setting this program left behind.
	_ = setSystemProxy("127.0.0.1:10810")
	var leftover systemProxySync
	leftover.update(trayStatus("starting", config.SystemProxyClear))
	if p := setting(t); p != user {
		t.Fatalf("a leftover of this program is restored in clear too: %+v", p)
	}
}

// Another program (v2rayN, Clash) replacing this program's setting is
// reported, never taken back by itself; the user's request takes it back,
// and the other program's proxy becomes the one restored afterwards.
func TestSystemProxyTakenByAnotherProgram(t *testing.T) {
	k := useTestProxyKeys(t)
	var reports []*service.SystemProxyTaken
	sync := systemProxySync{report: func(taken *service.SystemProxyTaken) { reports = append(reports, taken) }}
	running := trayStatus("running", config.SystemProxySet)
	sync.update(running)
	if len(reports) != 0 {
		t.Fatalf("this program's own setting reported as taken: %v", reports)
	}

	other := proxySetting{enabled: true, server: "127.0.0.1:7890", override: "<local>"}
	_ = writeProxySetting(k, other)
	for i := 0; i < 3; i++ {
		sync.checked = time.Time{} // as if takenCheck had passed
		sync.update(running)
	}
	if p := setting(t); p != other {
		t.Fatalf("the other program's setting was taken back by itself: %+v", p)
	}
	if len(reports) != 1 || reports[0] == nil || reports[0].By != "127.0.0.1:7890" {
		t.Fatalf("reports %v; want one naming the other proxy", reports)
	}

	// Turned off by the other program: reported with no address.
	_ = writeProxySetting(k, proxySetting{})
	sync.checked = time.Time{}
	sync.update(running)
	if len(reports) != 2 || reports[1] == nil || reports[1].By != "" {
		t.Fatalf("reports %v; want the proxy turned off", reports)
	}

	// The user asks to set it again.
	_ = writeProxySetting(k, other)
	again := trayStatus("running", config.SystemProxySet)
	again.SystemProxyRetake = 1
	sync.update(again)
	if p := setting(t); p.server != "127.0.0.1:10810" || !p.enabled {
		t.Fatalf("set again: %+v", p)
	}
	if last := reports[len(reports)-1]; last != nil {
		t.Fatalf("taken back, yet reported as taken: %v", last)
	}
	sync.clear()
	if p := setting(t); p != other {
		t.Fatalf("the other program's setting comes back when this one stops: %+v", p)
	}
}

// While auto-connect has not seen the connection work, set waits; it
// applies once the attempt ends.
func TestSystemProxyWaitsForAutoConnect(t *testing.T) {
	k := useTestProxyKeys(t)
	user := proxySetting{enabled: true, server: "user.example:3128"}
	if err := writeProxySetting(k, user); err != nil {
		t.Fatal(err)
	}
	var sync systemProxySync
	waiting := trayStatus("running", config.SystemProxySet)
	waiting.AutoConnecting = true
	sync.update(waiting)
	if p := setting(t); p != user || sync.active() {
		t.Fatalf("set while auto-connect tests: %+v", p)
	}
	sync.update(trayStatus("running", config.SystemProxySet))
	if p := setting(t); p.server != "127.0.0.1:10810" || !p.enabled || !sync.active() {
		t.Fatalf("set after a passed test: %+v", p)
	}
	sync.clear()
}

// Two copies apply the same setting (a status screen running the proxy, a
// tray that controls it): the tray hands the setting over, so closing it
// or a failed poll leaves the host's setting, which the host still
// restores.
func TestSystemProxyHandOver(t *testing.T) {
	k := useTestProxyKeys(t)
	user := proxySetting{enabled: true, server: "user.example:3128"}
	if err := writeProxySetting(k, user); err != nil {
		t.Fatal(err)
	}
	var host, tray systemProxySync
	running := trayStatus("running", config.SystemProxySet)
	host.update(running)
	tray.update(running) // before it saw system_proxy_by_host
	running.SystemProxyByHost = true
	tray.handOver(running)
	tray.update(nil)
	tray.clear()
	if p := setting(t); p.server != "127.0.0.1:10810" || !p.enabled || !host.active() || tray.active() {
		t.Fatalf("the tray undid the host's setting: %+v", p)
	}
	host.clear()
	if p := setting(t); p != user {
		t.Fatalf("the host restores the previous setting: %+v", p)
	}

	// A setting of the tray's for another address goes.
	if err := setSystemProxy("127.0.0.1:20810"); err != nil {
		t.Fatal(err)
	}
	var old systemProxySync
	old.applied = "127.0.0.1:20810"
	old.handOver(running)
	if p := setting(t); p != user {
		t.Fatalf("the tray's setting for another address stays: %+v", p)
	}
}