even more general url validation · Entire
even more general url validation
5c60d93→main·
Soph·2mo ago·2 files·+51 added/-3 removed
Sessions
8feaf84a600fView transcript
?\
Same-repo check bypassed by trailing slash differenceCodex·GPT-5.4·3 steps
Changes
2
internal/validation
Mvalidation.go+44/-2
Mvalidation_test.go+7/-1
1 unmodified line
2
3
4
5
6
7
8
9
21 unmodified lines
31
32
33
32
33
34
35
36
37
38
3 unmodified lines
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
1 unmodified line
import (
"fmt"
"net"
"net/url"
"strings"
"github.com/go-git/go-git/v6/plumbing"
)
21 unmodified lines
// point at the same repository. Empty URLs are ignored so the caller can
// surface a more specific "missing URL" error.
func ValidateEndpoints(sourceURL, targetURL string) error {
src := strings.TrimSpace(sourceURL)
dst := strings.TrimSpace(targetURL)
src := normalizeEndpointURL(sourceURL)
dst := normalizeEndpointURL(targetURL)
if src == "" || dst == "" {
return nil
}
3 unmodified lines
return nil
}
func normalizeEndpointURL(raw string) string {
trimmed := strings.TrimRight(strings.TrimSpace(raw), "/")
if trimmed == "" {
return ""
}
parsed, err := url.Parse(trimmed)
if err != nil || parsed.Host == "" {
return trimmed
}
if parsed.Scheme != "http" && parsed.Scheme != "https" {
return trimmed
}
scheme := strings.ToLower(parsed.Scheme)
host := strings.ToLower(parsed.Hostname())
port := parsed.Port()
if port != "" && !isDefaultPort(scheme, port) {
host = net.JoinHostPort(host, port)
}
path := strings.TrimRight(parsed.EscapedPath(), "/")
if path == "" {
path = "/"
}
normalized := scheme + "://" + host + path
if parsed.RawQuery != "" {
normalized += "?" + parsed.RawQuery
}
if parsed.Fragment != "" {
normalized += "#" + parsed.Fragment
}
return normalized
}
func isDefaultPort(scheme, port string) bool {
return (scheme == "http" && port == "80") || (scheme == "https" && port == "443")
}
// NormalizeProtocolMode validates the configured protocol mode and applies the
// default auto mode when the user did not specify one.
func NormalizeProtocolMode(mode string) (string, error) {
Minternal/validation/validation.go+44/-2
84 unmodified lines
85
86
87
88
89
90
91
92
93
94
95
91
96
97
98
99
100
84 unmodified lines
{name: "different URLs", source: "https://src.example/r.git", target: "https://dst.example/r.git"},
{name: "same URL", source: "https://example.com/r.git", target: "https://example.com/r.git", wantErr: true},
{name: "same URL with surrounding whitespace", source: " https://example.com/r.git", target: "https://example.com/r.git\t", wantErr: true},
{name: "same URL with userinfo", source: "https://user@example.com/r.git", target: "https://example.com/r.git", wantErr: true},
{name: "same URL with default https port", source: "https://example.com:443/r.git", target: "https://example.com/r.git", wantErr: true},
{name: "same URL with default http port", source: "http://example.com:80/r.git", target: "http://example.com/r.git", wantErr: true},
{name: "same URL with mixed host case", source: "https://EXAMPLE.com/r.git", target: "https://example.com/r.git", wantErr: true},
{name: "different non-default port", source: "https://example.com:8443/r.git", target: "https://example.com/r.git"},
{name: "empty source defers to other checks", source: "", target: "https://example.com/r.git"},
{name: "empty target defers to other checks", source: "https://example.com/r.git", target: ""},
{name: "both empty defers to other checks", source: "", target: ""},
{name: "differ only by trailing slash", source: "https://example.com/r.git", target: "https://example.com/r.git/"},
{name: "differ only by trailing slash", source: "https://example.com/r.git", target: "https://example.com/r.git/", wantErr: true},
{name: "differ only by repeated trailing slashes", source: "https://example.com/r.git//", target: "https://example.com/r.git/", wantErr: true},
}
for _, tt := range tests {