From e022a958d923ef3247d15e89addbbd2360341ba3 Mon Sep 17 00:00:00 2001 From: Renzo Toma Date: Mon, 17 Jun 2024 11:27:00 +0200 Subject: [PATCH] feat: add validator for IPv4 bind address / host:port supporting older Golang versions by using net.ParseIP, not netip.ParseAddr --- baked_in.go | 24 ++++++++++++++++++++++++ validator_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/baked_in.go b/baked_in.go index b6fbaafad..da4933829 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,29 @@ 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() + ip, port, err := net.SplitHostPort(val) + if err != nil { + return false + } + // Port must be a iny <= 65535. + if portNum, err := strconv.ParseInt( + port, 10, 32, + ); err != nil || portNum > 65535 || portNum < 1 { + return false + } + + // If IP address is specified, it should match a valid IPv4 address + if ip != "" { + // we need to support older Golang versions, so we can not use netip.ParseAddr + parsedIp := net.ParseIP(ip) + return parsedIp != nil && parsedIp.To4() != nil + } + return true +} + // 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