Skip to content

Commit 3a99bde

Browse files
authored
Implement URL normalization for proxy server URL comparison. (#301)
1 parent a0fad3b commit 3a99bde

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

cli/internal/tygerproxy/tygerproxy.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,29 @@ func CheckProxyAlreadyRunning(options *ProxyOptions) (*ProxyServiceMetadata, err
189189
return existingProxy, ErrProxyNotRunning
190190
}
191191

192-
if existingProxy.ServerUrl != options.ServerUrl {
192+
if !urlsEquivalent(existingProxy.ServerUrl, options.ServerUrl) {
193193
return existingProxy, ErrProxyAlreadyRunningWrongTarget
194194
}
195195

196196
return existingProxy, nil
197197
}
198198

199+
func urlsEquivalent(a, b string) bool {
200+
return normalizeUrl(a) == normalizeUrl(b)
201+
}
202+
203+
func normalizeUrl(raw string) string {
204+
u, err := url.Parse(raw)
205+
if err != nil {
206+
return raw
207+
}
208+
u.Scheme = strings.ToLower(u.Scheme)
209+
u.Host = strings.ToLower(u.Host)
210+
u.Path = strings.TrimRight(u.Path, "/")
211+
u.RawQuery = u.Query().Encode()
212+
return u.String()
213+
}
214+
199215
func GetExistingProxyMetadata(options *ProxyOptions) *ProxyServiceMetadata {
200216
// note: not using retryablehttp here because we are hitting localhost
201217
// and we want to fail quickly
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package tygerproxy
5+
6+
import "testing"
7+
8+
func TestUrlsEquivalent(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
a string
12+
b string
13+
want bool
14+
}{
15+
{"identical", "http://myserver:8080", "http://myserver:8080", true},
16+
{"scheme casing", "http://myserver:8080", "HTTP://myserver:8080", true},
17+
{"host casing", "http://MyServer:8080", "http://myserver:8080", true},
18+
{"mixed case scheme and host", "HTTPS://MyServer.Example.COM/api", "https://myserver.example.com/api", true},
19+
{"escaped vs unescaped brackets in query",
20+
"ssh://user@myhost/opt/tyger/api.sock?option[StrictHostKeyChecking]=no&option[UserKnownHostsFile]=NUL",
21+
"ssh://user@myhost/opt/tyger/api.sock?option%5BStrictHostKeyChecking%5D=no&option%5BUserKnownHostsFile%5D=NUL",
22+
true},
23+
{"escaped vs unescaped brackets reversed",
24+
"ssh://user@myhost/opt/tyger/api.sock?option%5BStrictHostKeyChecking%5D=no&option%5BUserKnownHostsFile%5D=NUL",
25+
"ssh://user@myhost/opt/tyger/api.sock?option[StrictHostKeyChecking]=no&option[UserKnownHostsFile]=NUL",
26+
true},
27+
{"different servers", "http://server-a:8080", "http://server-b:8080", false},
28+
{"different ports", "http://myserver:8080", "http://myserver:9090", false},
29+
{"different schemes", "http://myserver:8080", "https://myserver:8080", false},
30+
{"different query values",
31+
"ssh://user@myhost/opt/tyger/api.sock?option[StrictHostKeyChecking]=no",
32+
"ssh://user@myhost/opt/tyger/api.sock?option[StrictHostKeyChecking]=yes",
33+
false},
34+
{"different paths", "http://myserver:8080/a", "http://myserver:8080/b", false},
35+
{"trailing slash", "http://myserver:8080/api/", "http://myserver:8080/api", true},
36+
}
37+
38+
for _, tt := range tests {
39+
t.Run(tt.name, func(t *testing.T) {
40+
got := urlsEquivalent(tt.a, tt.b)
41+
if got != tt.want {
42+
t.Errorf("UrlsEquivalent(%q, %q) = %v, want %v", tt.a, tt.b, got, tt.want)
43+
}
44+
})
45+
}
46+
}

0 commit comments

Comments
 (0)