diff --git a/baked_in.go b/baked_in.go index b6fbaafad..8ec856d18 100644 --- a/baked_in.go +++ b/baked_in.go @@ -186,6 +186,7 @@ var ( "ipv4": isIPv4, "ipv6": isIPv6, "ip": isIP, + "ipv4_port": isIPv4Port, "cidrv4": isCIDRv4, "cidrv6": isCIDRv6, "cidr": isCIDR, @@ -2704,6 +2705,28 @@ func isHostnamePort(fl FieldLevel) bool { return true } +// isIPv4Port validates a : combination for fields typically used for socket address. +func isIPv4Port(fl FieldLevel) bool { + val := fl.Field().String() + host, port, err := net.SplitHostPort(val) + if err != nil { + return false + } + // Validate port range (1-65535). + if portNum, err := strconv.ParseUInt(port, 10, 32,); err != nil || portNum < 1 || portNum > 65535 { + return false + } + + // If no host is specified, return true (valid) + if host == "" { + return true + } + + // Parse and validate IPv4 address + ip := net.ParseIP(host) + return ip != nil && ip.To4() != nil +} + // isLowercase is the validation function for validating if the current field's value is a lowercase string. func isLowercase(fl FieldLevel) bool { field := fl.Field() diff --git a/validator_test.go b/validator_test.go index 0f96b7775..d4401d808 100644 --- a/validator_test.go +++ b/validator_test.go @@ -12377,6 +12377,38 @@ func Test_hostnameport_validator(t *testing.T) { } } +func Test_ipv4_port_validator(t *testing.T) { + type IPv4Port struct { + BindAddr string `validate:"ipv4_port"` + } + + type testInput struct { + data string + expected bool + } + testData := []testInput{ + {"192.168.1.1:1234", true}, + {":1234", true}, + {"localhost:1234", false}, + {"aaa.bbb.ccc.ddd:234", false}, + {":alpha", false}, + {"1.2.3.4", false}, + {"2001:db8::1:0.0.0.0:234", false}, + {"2001:db8::1:0.0.0.0", false}, + {"2001:db8::1:0.0.0.0:", false}, + {"2001:db8::1:0.0.0.0:123456", false}, + {"2001:db8::1:0.0.0.0:123456:", false}, + } + for _, td := range testData { + h := IPv4Port{BindAddr: td.data} + v := New() + err := v.Struct(h) + if td.expected != (err == nil) { + t.Fatalf("Test failed for data: %v Error: %v", td.data, err) + } + } +} + func TestLowercaseValidation(t *testing.T) { tests := []struct { param string