Skip to content

Commit

Permalink
feat: add GetDomain() and CheckDomain()
Browse files Browse the repository at this point in the history
  • Loading branch information
fufuok committed Aug 20, 2024
1 parent 7d81d28 commit 3f79dbb
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 0 deletions.
78 changes: 78 additions & 0 deletions domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package utils

import (
"errors"
"fmt"
"strings"
"unicode/utf8"
)

// GetDomain 检查并返回清除前后空白的域名
func GetDomain(name string) string {
name = strings.TrimSpace(name)
if CheckDomain(name) != nil {
return ""
}
return name
}

// CheckDomain returns an error if the host name is not valid.
// See https://tools.ietf.org/html/rfc1034#section-3.5 and
// https://tools.ietf.org/html/rfc1123#section-2.
// Ref: chmike/domain
func CheckDomain(name string) error {
if len(name) == 0 {
return errors.New("domain name is empty")
}
if name[len(name)-1] == '.' {
if len(name) > 254 {
return fmt.Errorf("domain name length is %d, can't exceed 254 with a trailing dot", len(name))
}
name = name[:len(name)-1] // drop valid trailing dot
if len(name) == 0 {
return errors.New("domain name is a single dot")
}
} else if len(name) > 253 {
return fmt.Errorf("domain name length is %d, can't exceed 253 without a trailing dot", len(name))
}
var l int
for i := 0; i < len(name); i++ {
b := name[i]
if b == '.' {
// check domain labels validity
switch {
case i == l:
return fmt.Errorf("domain has an empty label at offset %d", l)
case i-l > 63:
return fmt.Errorf("domain byte length of label '%s' is %d, can't exceed 63", name[l:i], i-l)
case name[l] == '-':
return fmt.Errorf("domain label '%s' at offset %d begins with a hyphen", name[l:i], l)
case name[i-1] == '-':
return fmt.Errorf("domain label '%s' at offset %d ends with a hyphen", name[l:i], l)
}
l = i + 1
continue
}
// test label character validity, note: tests are ordered by decreasing validity frequency
if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b >= 'A' && b <= 'Z') {
// show the printable unicode character starting at byte offset i
c, _ := utf8.DecodeRuneInString(name[i:])
if c == utf8.RuneError {
return fmt.Errorf("domain has invalid rune at offset %d", i)
}
return fmt.Errorf("domain has invalid character '%c' at offset %d", c, i)
}
}
// check top level domain validity
switch {
case len(name)-l > 63:
return fmt.Errorf("domain's top level domain '%s' has byte length %d, can't exceed 63", name[l:], len(name)-l)
case name[l] == '-':
return fmt.Errorf("domain's top level domain '%s' at offset %d begin with a hyphen", name[l:], l)
case name[len(name)-1] == '-':
return fmt.Errorf("domain's top level domain '%s' at offset %d ends with a hyphen", name[l:], l)
case name[l] >= '0' && name[l] <= '9':
return fmt.Errorf("domain's top level domain '%s' at offset %d begins with a digit", name[l:], l)
}
return nil
}
94 changes: 94 additions & 0 deletions domain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package utils

import (
"strings"
"testing"

"github.com/fufuok/utils/assert"
)

var longName = strings.Repeat(strings.Repeat("a", 63)+".", 4)

func TestDomainRegexp(t *testing.T) {
f63 := strings.Repeat("f", 63)
cases := []struct {
in, want string
}{
{"f", "f"},
{"f.cn", "f.cn"},
{"7.cn", "7.cn"},
{"f-7.com.cn", "f-7.com.cn"},
{"f--f.cn", "f--f.cn"},
{"-f.cn", ""},
{"f-.cn", ""},
{"f_.cn", ""},
{"f_f.cn", ""},
{"f.f", "f.f"},
{"f.77", ""},
{"f." + f63, "f." + f63},
{"f." + f63 + ".cn", "f." + f63 + ".cn"},
{"f.f" + f63, ""},
{"f.f" + f63 + ".cn", ""},
{"_.f.cn", ""},
{"*.f.cn", ""},
{"xn--fiq06l2rdsvs.xn--vuq861b.xn--fiqs8s", "xn--fiq06l2rdsvs.xn--vuq861b.xn--fiqs8s"},
}
for _, c := range cases {
assert.Equal(t, c.want, GetDomain(c.in), c.in)
}
}

// Ref: chmike/domain
func TestCheckDomain(t *testing.T) {
tests := []struct {
n string
e string
}{
// 0
{n: "", e: "domain name is empty"},
{n: "example.com"},
{n: "EXAMPLE.com"},
{n: "foo-bar.com"},
{n: "www1.foo-bar.com"},
// 5
{n: "192.168.1.1.example.com"},
{n: strings.Repeat("a", 70) + ".com", e: "domain byte length of label 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' is 70, can't exceed 63"},
{n: "example.com" + strings.Repeat("a", 70), e: "domain's top level domain 'comaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' has byte length 73, can't exceed 63"},
{n: "?", e: "domain has invalid character '?' at offset 0"},
{n: "\t", e: "domain has invalid character '\t' at offset 0"},
// 10
{n: "exàmple.com", e: "domain has invalid character 'à' at offset 2"},
{n: "www.\xbd\xb2.com", e: "domain has invalid rune at offset 4"},
{n: "-example.com", e: "domain label '-example' at offset 0 begins with a hyphen"},
{n: "example-.com", e: "domain label 'example-' at offset 0 ends with a hyphen"},
{n: "example.-com", e: "domain's top level domain '-com' at offset 8 begin with a hyphen"},
// 15
{n: "example.com-", e: "domain's top level domain 'com-' at offset 8 ends with a hyphen"},
{n: "example.1com", e: "domain's top level domain '1com' at offset 8 begins with a digit"},
{n: ".example.com", e: "domain has an empty label at offset 0"},
{n: "example..com", e: "domain has an empty label at offset 8"},
{n: "example.com."},
// 20
{n: longName, e: "domain name length is 256, can't exceed 254 with a trailing dot"},
{n: longName[:253]},
{n: longName[:253] + "."},
{n: longName[:254], e: "domain name length is 254, can't exceed 253 without a trailing dot"},
{n: longName[:255] + ".", e: "domain name length is 256, can't exceed 254 with a trailing dot"},
// 25
{n: ".", e: "domain name is a single dot"},
}
for i, test := range tests {
err := CheckDomain(test.n)
if (err == nil) != (test.e == "") {
if err != nil {
t.Errorf("%2d unexpected error: %q", i, err)
} else {
t.Errorf("%2d unexpected nil error", i)
}
continue
}
if err != nil && err.Error() != test.e {
t.Errorf("%2d expect error %q, got %q", i, test.e, err)
}
}
}

0 comments on commit 3f79dbb

Please sign in to comment.