diff --git a/README.md b/README.md index da700e8..06177c5 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ This package currently provides the following checkers: - [ascii](doc/checkers/ascii.md) checks if the given string consists of only ASCII characters. - [cidr](doc/checkers/cidr.md) checker checks if the value is a valid CIDR notation IP address and prefix length. - [digits](doc/checkers/digits.md) checks if the given string consists of only digit characters. +- [fqdn](doc/checkers/fqdn.md) checks if the given string is a fully qualified domain name. - [ip](doc/checkers/ip.md) checks if the given value is an IP address. - [ipv4](doc/checkers/ipv4.md) checks if the given value is an IPv4 address. - [ipv6](doc/checkers/ipv6.md) checks if the given value is an IPv6 address. diff --git a/checker.go b/checker.go index c77c249..2cedfcd 100644 --- a/checker.go +++ b/checker.go @@ -35,6 +35,7 @@ var makers = map[string]MakeFunc{ CheckerAscii: makeAscii, CheckerCidr: makeCidr, CheckerDigits: makeDigits, + CheckerFqdn: makeFqdn, CheckerIp: makeIp, CheckerIpV4: makeIpV4, CheckerIpV6: makeIpV6, diff --git a/doc/checkers/fqdn.md b/doc/checkers/fqdn.md new file mode 100644 index 0000000..4b1ece4 --- /dev/null +++ b/doc/checkers/fqdn.md @@ -0,0 +1,28 @@ +# FQDN Checker + +The Full Qualified Domain Name (FQDN) is the complete domain name for a computer or host on the internet. The ```fqdn``` checker checks if the given string consists of a FQDN. If the string is not a valid FQDN, the checker will return the ```NOT_FQDN``` result. Here is an example: + +```golang +type Request struct { + Domain string `checkers:"fqdn"` +} + +request := &Request{ + Domain: "zdo.com", +} + +_, valid := Check(request) +if !valid { + // Send the mistakes back to the user +} +``` + +In your custom checkers, you can call the ```fqdn``` checker function ```IsFqdn``` to validate the user input. Here is an example: + +```golang +result := IsFqdn("zdo.com") + +if result != ResultValid { + // Send the mistakes back to the user +} +``` diff --git a/fqdn.go b/fqdn.go new file mode 100644 index 0000000..6617c22 --- /dev/null +++ b/fqdn.go @@ -0,0 +1,71 @@ +package checker + +import ( + "reflect" + "regexp" + "strings" +) + +// CheckerFqdn is the name of the checker. +const CheckerFqdn = "fqdn" + +// ResultNotFqdn indicates that the given string contains non-ASCII characters. +const ResultNotFqdn = "NOT_FQDN" + +// Valid characters excluding full-width characters. +var fqdnValidChars = regexp.MustCompile("^[a-z0-9\u00a1-\uff00\uff06-\uffff-]+$") + +// IsFqdn checks if the given string is a fully qualified domain name. +func IsFqdn(domain string) Result { + parts := strings.Split(domain, ".") + + // Require TLD + if len(parts) < 2 { + return ResultNotFqdn + } + + tld := parts[len(parts)-1] + + // Should be all numeric TLD + if IsDigits(tld) == ResultValid { + return ResultNotFqdn + } + + // Short TLD + if len(tld) < 2 { + return ResultNotFqdn + } + + for _, part := range parts { + // Cannot be more than 63 characters + if len(part) > 63 { + return ResultNotFqdn + } + + // Check for valid characters + if !fqdnValidChars.MatchString(part) { + return ResultNotFqdn + } + + // Should not start or end with a hyphen (-) character. + if part[0] == '-' || part[len(part)-1] == '-' { + return ResultNotFqdn + } + } + + return ResultValid +} + +// makeFqdn makes a checker function for the ascii checker. +func makeFqdn(_ string) CheckFunc { + return checkFqdn +} + +// checkFqdn checks if the given string is a fully qualified domain name. +func checkFqdn(value, _ reflect.Value) Result { + if value.Kind() != reflect.String { + panic("string expected") + } + + return IsFqdn(value.String()) +} diff --git a/fqdn_test.go b/fqdn_test.go new file mode 100644 index 0000000..2724ad6 --- /dev/null +++ b/fqdn_test.go @@ -0,0 +1,84 @@ +package checker + +import "testing" + +func TestCheckFdqnWithoutTld(t *testing.T) { + if IsFqdn("abcd") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnShortTld(t *testing.T) { + if IsFqdn("abcd.c") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnNumericTld(t *testing.T) { + if IsFqdn("abcd.1234") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnLong(t *testing.T) { + if IsFqdn("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.com") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnInvalidCharacters(t *testing.T) { + if IsFqdn("ab_cd.com") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnStaringWithHyphen(t *testing.T) { + if IsFqdn("-abcd.com") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnStaringEndingWithHyphen(t *testing.T) { + if IsFqdn("abcd-.com") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnStartingWithDot(t *testing.T) { + if IsFqdn(".abcd.com") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFdqnEndingWithDot(t *testing.T) { + if IsFqdn("abcd.com.") != ResultNotFqdn { + t.Fail() + } +} + +func TestCheckFqdnNonString(t *testing.T) { + defer FailIfNoPanic(t) + + type Request struct { + Domain int `checkers:"fqdn"` + } + + request := &Request{} + + Check(request) +} + +func TestCheckFqdnValid(t *testing.T) { + type Request struct { + Domain string `checkers:"fqdn"` + } + + request := &Request{ + Domain: "zdo.com", + } + + _, valid := Check(request) + if !valid { + t.Fail() + } +}