From 893747e5ee93f123ea885d25bcc0c9247081d7ca Mon Sep 17 00:00:00 2001 From: Ravi Terala Date: Fri, 3 Jan 2020 11:30:08 -0800 Subject: [PATCH 1/2] Add hostname_port validator feature --- baked_in.go | 20 ++++++++++++++++++++ regexes.go | 10 +++++----- validator_test.go | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/baked_in.go b/baked_in.go index 9eff0e4..3245202 100644 --- a/baked_in.go +++ b/baked_in.go @@ -166,6 +166,7 @@ var ( "html_encoded": isHTMLEncoded, "url_encoded": isURLEncoded, "dir": isDir, + "hostname_port": isHostnamePort, } ) @@ -2007,3 +2008,22 @@ func isDir(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %T", field.Interface())) } + +// isHostnamePort validates a : combination for fields typically used for socket address. +func isHostnamePort(fl FieldLevel) bool { + val := fl.Field().String() + host, 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 host is specified, it should match a DNS name + if host != "" { + return hostnameRegexRFC1123.MatchString(host) + } + return true +} diff --git a/regexes.go b/regexes.go index 63fdc1d..b8bf253 100644 --- a/regexes.go +++ b/regexes.go @@ -36,11 +36,11 @@ const ( latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$` - hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 - hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 - btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address - btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 - btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 + hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 + btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address + btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` diff --git a/validator_test.go b/validator_test.go index 2c8b96b..0abdcab 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7954,15 +7954,15 @@ func TestHostnameRFC1123Validation(t *testing.T) { if test.expected { if !IsEqual(errs, nil) { - t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + t.Fatalf("Hostname: %v failed Error: %v", test, errs) } } else { if IsEqual(errs, nil) { - t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + t.Fatalf("Hostname: %v failed Error: %v", test, errs) } else { val := getError(errs, "", "") if val.Tag() != "hostname_rfc1123" { - t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + t.Fatalf("Hostname: %v failed Error: %v", i, errs) } } } @@ -9003,3 +9003,34 @@ func TestGetTag(t *testing.T) { Equal(t, errs, nil) Equal(t, tag, "mytag") } + +func Test_hostnameport_validator(t *testing.T) { + + type Host struct { + Addr string `validate:"hostname_port"` + } + + type testInput struct { + data string + expected bool + } + testData := []testInput{ + {"bad..domain.name:234", false}, + {"extra.dot.com.", false}, + {"localhost:1234", true}, + {"192.168.1.1:1234", true}, + {":1234", true}, + {"domain.com:1334", true}, + {"this.domain.com:234", true}, + {"domain:75000", false}, + {"missing.port", false}, + } + for _, td := range testData { + h := Host{Addr: 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) + } + } +} From a1ac82a8653a8e36245594d0599a9412f9574ae3 Mon Sep 17 00:00:00 2001 From: Ravi Terala Date: Fri, 17 Jan 2020 09:52:57 -0800 Subject: [PATCH 2/2] Add documentation --- doc.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc.go b/doc.go index 799882c..4c58d95 100644 --- a/doc.go +++ b/doc.go @@ -1037,6 +1037,13 @@ This is done using os.Stat, which is a platform independent function. Usage: dir +HostPort + +This validates that a string value contains a valid DNS hostname and port that +can be used to valiate fields typically passed to sockets and connections. + + Usage: hostname_port + Alias Validators and Tags NOTE: When returning an error, the tag returned in "FieldError" will be