Skip to content

Commit 68e8e13

Browse files
author
Dean Karn
authored
Merge pull request #563 from terala/master
Add hostname_port validator feature
2 parents fa149de + 1f676c7 commit 68e8e13

File tree

4 files changed

+66
-8
lines changed

4 files changed

+66
-8
lines changed

baked_in.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ var (
166166
"html_encoded": isHTMLEncoded,
167167
"url_encoded": isURLEncoded,
168168
"dir": isDir,
169+
"hostname_port": isHostnamePort,
169170
"lowercase": isLowercase,
170171
"uppercase": isUppercase,
171172
}
@@ -2010,6 +2011,25 @@ func isDir(fl FieldLevel) bool {
20102011
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
20112012
}
20122013

2014+
// isHostnamePort validates a <dns>:<port> combination for fields typically used for socket address.
2015+
func isHostnamePort(fl FieldLevel) bool {
2016+
val := fl.Field().String()
2017+
host, port, err := net.SplitHostPort(val)
2018+
if err != nil {
2019+
return false
2020+
}
2021+
// Port must be a iny <= 65535.
2022+
if portNum, err := strconv.ParseInt(port, 10, 32); err != nil || portNum > 65535 || portNum < 1 {
2023+
return false
2024+
}
2025+
2026+
// If host is specified, it should match a DNS name
2027+
if host != "" {
2028+
return hostnameRegexRFC1123.MatchString(host)
2029+
}
2030+
return true
2031+
}
2032+
20132033
// isLowercase is the validation function for validating if the current field's value is a lowercase string.
20142034
func isLowercase(fl FieldLevel) bool {
20152035
field := fl.Field()

doc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,13 @@ This is done using os.Stat, which is a platform independent function.
10491049
10501050
Usage: dir
10511051
1052+
HostPort
1053+
1054+
This validates that a string value contains a valid DNS hostname and port that
1055+
can be used to valiate fields typically passed to sockets and connections.
1056+
1057+
Usage: hostname_port
1058+
10521059
Alias Validators and Tags
10531060
10541061
NOTE: When returning an error, the tag returned in "FieldError" will be

regexes.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ const (
3636
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
3737
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
3838
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])$`
39-
hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952
40-
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
41-
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
42-
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
43-
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
39+
hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952
40+
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
41+
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
42+
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
43+
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
4444
ethAddressRegexString = `^0x[0-9a-fA-F]{40}$`
4545
ethAddressUpperRegexString = `^0x[0-9A-F]{40}$`
4646
ethAddressLowerRegexString = `^0x[0-9a-f]{40}$`

validator_test.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7954,15 +7954,15 @@ func TestHostnameRFC1123Validation(t *testing.T) {
79547954

79557955
if test.expected {
79567956
if !IsEqual(errs, nil) {
7957-
t.Fatalf("Index: %d hostname failed Error: %v", i, errs)
7957+
t.Fatalf("Hostname: %v failed Error: %v", test, errs)
79587958
}
79597959
} else {
79607960
if IsEqual(errs, nil) {
7961-
t.Fatalf("Index: %d hostname failed Error: %v", i, errs)
7961+
t.Fatalf("Hostname: %v failed Error: %v", test, errs)
79627962
} else {
79637963
val := getError(errs, "", "")
79647964
if val.Tag() != "hostname_rfc1123" {
7965-
t.Fatalf("Index: %d hostname failed Error: %v", i, errs)
7965+
t.Fatalf("Hostname: %v failed Error: %v", i, errs)
79667966
}
79677967
}
79687968
}
@@ -9004,6 +9004,37 @@ func TestGetTag(t *testing.T) {
90049004
Equal(t, tag, "mytag")
90059005
}
90069006

9007+
func Test_hostnameport_validator(t *testing.T) {
9008+
9009+
type Host struct {
9010+
Addr string `validate:"hostname_port"`
9011+
}
9012+
9013+
type testInput struct {
9014+
data string
9015+
expected bool
9016+
}
9017+
testData := []testInput{
9018+
{"bad..domain.name:234", false},
9019+
{"extra.dot.com.", false},
9020+
{"localhost:1234", true},
9021+
{"192.168.1.1:1234", true},
9022+
{":1234", true},
9023+
{"domain.com:1334", true},
9024+
{"this.domain.com:234", true},
9025+
{"domain:75000", false},
9026+
{"missing.port", false},
9027+
}
9028+
for _, td := range testData {
9029+
h := Host{Addr: td.data}
9030+
v := New()
9031+
err := v.Struct(h)
9032+
if td.expected != (err == nil) {
9033+
t.Fatalf("Test failed for data: %v Error: %v", td.data, err)
9034+
}
9035+
}
9036+
}
9037+
90079038
func TestLowercaseValidation(t *testing.T) {
90089039
tests := []struct {
90099040
param string

0 commit comments

Comments
 (0)