From c4df5bcab802ca502a364af5610218f006e6b101 Mon Sep 17 00:00:00 2001 From: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Date: Wed, 20 May 2026 16:38:13 +0530 Subject: [PATCH] fix: validate URL schemes and single-label hosts Signed-off-by: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> --- pkg/utils/helper.go | 17 ++++++++++++----- pkg/utils/helper_test.go | 10 ++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pkg/utils/helper.go b/pkg/utils/helper.go index 9255129c7..e309c1600 100644 --- a/pkg/utils/helper.go +++ b/pkg/utils/helper.go @@ -177,16 +177,23 @@ func ValidateRegistryName(rn string) bool { return re.MatchString(rn) } -// ValidateURL checks if the URL has valid format, non-empty host, and host is a valid IP or domain. -// Domain regex: labels must start/end with alphanumeric, can contain hyphens, max 63 chars, TLD min 2 letters. -func ValidateURL(rawURL string) error { - var domainNameRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`) +var ( + domainNameRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`) + hostLabelRegex = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`) +) +// ValidateURL checks if the URL has valid format, an HTTP(S) scheme, and a valid host. +// Hostnames may be IP addresses, localhost, FQDNs, or single RFC 1123 labels for internal networks. +func ValidateURL(rawURL string) error { parsedURL, err := url.ParseRequestURI(rawURL) if err != nil { return fmt.Errorf("invalid URL format: %v", err) } + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { + return fmt.Errorf("URL scheme must be http or https") + } + host := parsedURL.Hostname() if host == "" { return fmt.Errorf("URL must contain a valid host") @@ -196,7 +203,7 @@ func ValidateURL(rawURL string) error { return nil } - if host == "localhost" { + if host == "localhost" || hostLabelRegex.MatchString(host) { return nil } diff --git a/pkg/utils/helper_test.go b/pkg/utils/helper_test.go index 33c3aba4d..62063b6d5 100644 --- a/pkg/utils/helper_test.go +++ b/pkg/utils/helper_test.go @@ -147,13 +147,20 @@ func TestValidateURL(t *testing.T) { {"valid localhost https", "https://localhost", false}, {"valid localhost http with port", "http://localhost:8080", false}, {"valid localhost https with port", "https://localhost:8443", false}, + {"valid single-label host", "http://harbor", false}, + {"valid single-label host with hyphen", "http://harbor-core", false}, + {"valid single-label host with port", "https://registry:8443", false}, // invalid URLs {"empty string", "", true}, {"no host", "https://", true}, {"bare path", "/just/a/path", true}, + {"unsupported ftp scheme", "ftp://example.com/webhook", true}, + {"unsupported file scheme", "file://example.com/webhook", true}, {"invalid domain double dot", "https://invalid..domain", true}, {"domain starting with hyphen", "https://-invalid.com", true}, + {"single-label host starting with hyphen", "https://-harbor", true}, + {"single-label host ending with hyphen", "https://harbor-", true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -165,6 +172,9 @@ func TestValidateURL(t *testing.T) { } }) } + + err := utils.ValidateURL("ftp://example.com/webhook") + assert.EqualError(t, err, "URL scheme must be http or https") } func TestPrintFormat(t *testing.T) {