From 859cb5a4fe297470e6bbe2428009bd9d494c4318 Mon Sep 17 00:00:00 2001 From: Deepak Ganesh Date: Sat, 1 Aug 2026 22:20:57 +0530 Subject: [PATCH] fix: reject invalid dotted-decimal in hostname_rfc1123 validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per RFC 1123 §2.1, a valid hostname can never have the dotted-decimal form #.#.#.# since the highest-level component label must be alphabetic. The hostname_rfc1123 validator accepted strings like '277.168.0.1' because the regex allows all-digit labels. Now, when the input consists entirely of digits and dots (dotted-decimal form), it is validated as an IPv4 address via net.ParseIP and rejected if invalid. Fixes #1561 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- baked_in.go | 29 ++++++++++++++++++++++++++++- validator_test.go | 12 ++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/baked_in.go b/baked_in.go index cea97829..29ac1fb3 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2911,7 +2911,34 @@ func isHostnameRFC952(fl FieldLevel) bool { } func isHostnameRFC1123(fl FieldLevel) bool { - return hostnameRegexRFC1123().MatchString(fl.Field().String()) + val := fl.Field().String() + if !hostnameRegexRFC1123().MatchString(val) { + return false + } + // RFC 1123 §2.1: "a valid host name can never have the dotted-decimal + // form #.#.#.#, since at least the highest-level component label will + // be alphabetic." If the value has exactly four dot-separated all-numeric + // parts (i.e. it looks like an IPv4 address), accept it only when + // net.ParseIP confirms it is a valid IPv4 address. + if looksLikeIPv4(val) { + return net.ParseIP(val) != nil + } + return true +} + +// looksLikeIPv4 reports whether s has exactly four dot-separated parts that +// are each composed entirely of ASCII digits (e.g. "192.168.0.1", "277.168.0.1"). +func looksLikeIPv4(s string) bool { + parts := 1 + for i := 0; i < len(s); i++ { + c := s[i] + if c == '.' { + parts++ + } else if c < '0' || c > '9' { + return false + } + } + return parts == 4 } func isFQDN(fl FieldLevel) bool { diff --git a/validator_test.go b/validator_test.go index 2f2c1c7a..2062f50f 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11078,6 +11078,18 @@ func TestHostnameRFC1123Validation(t *testing.T) { {"example.", false}, {"test_example", false}, {"192.168.0.1", true}, + {"277.168.0.1", false}, + {"999.999.999.999", false}, + {"0.0.0.0", true}, + {"255.255.255.255", true}, + {"1.2.3.256", false}, + {"3com.com", true}, + {"1and1.com", true}, + {"7-eleven.com", true}, + {"1234", true}, + {"12345", true}, + {"1.2.3", true}, + {"1.2.3.4.5", true}, {"email@example.com", false}, {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, {"2001:cdba:0:0:0:0:3257:9652", false},