Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions internal/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,21 @@ func checkPort(portStr string) error {
return nil
}

// fullStopReplacer maps the Unicode characters IDNA treats as label separators
// (UTS #46) onto ASCII dots, so a domain pasted with an ideographic full stop
// is read as the domain the user meant instead of as one extra label (#281).
var fullStopReplacer = strings.NewReplacer("。", ".", ".", ".", "。", ".")

// normalizeFullStops rewrites every IDNA label separator in s to ".".
func normalizeFullStops(s string) string { return fullStopReplacer.Replace(s) }

// NormalizeDomain turns a target as users type or paste it into the canonical
// form every entry point scans: trimmed, lowercase, no trailing dot, IDNs in
// punycode. It also strips a URL scheme and path, a :port and a leading "*.",
// returning a one-line note for each so the caller can say what it did (#54).
// The result is checked with Domain.
func NormalizeDomain(input string) (domain string, notes []string, err error) {
s := strings.TrimSpace(input)
s := normalizeFullStops(strings.TrimSpace(input))
if strings.Contains(s, "@") {
return "", nil, fmt.Errorf("%q looks like an email address; pass just the domain", input)
}
Expand All @@ -114,7 +122,7 @@ func NormalizeDomain(input string) (domain string, notes []string, err error) {
s = s[2:]
notes = append(notes, fmt.Sprintf("ignoring the leading *. wildcard; scanning %s", s))
}
s = strings.ToLower(strings.TrimSuffix(s, "."))
s = strings.ToLower(strings.TrimRight(s, "."))
if !isASCII(s) {
ascii, ierr := toASCII(s)
if ierr != nil {
Expand All @@ -137,6 +145,9 @@ func Domain(domain string) error {
if len(domain) == 0 {
return fmt.Errorf("domain cannot be empty")
}
if strings.HasSuffix(domain, ".") {
return fmt.Errorf("domain cannot end with a dot: %s", domain)
}
if len(domain) > 253 {
return fmt.Errorf("domain exceeds maximum length of 253 characters")
}
Expand Down
6 changes: 6 additions & 0 deletions internal/validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func TestDomain(t *testing.T) {
"example-.com",
".example.com",
"example..com",
"example.com.",
strings.Repeat("a", 254) + ".com",
strings.Repeat("a", 64) + ".com",
}
Expand Down Expand Up @@ -106,6 +107,11 @@ func TestNormalizeDomain(t *testing.T) {
{in: "*.example.com", want: "example.com", notes: 1},
{in: "bücher.de", want: "xn--bcher-kva.de", notes: 1},
{in: "_dmarc.example.com", want: "_dmarc.example.com"},
{in: "example.com..", want: "example.com"},
{in: "example.com。", want: "example.com"},
{in: "bücher.example。", want: "xn--bcher-kva.example", notes: 1},
{in: "bücher.example.。", want: "xn--bcher-kva.example", notes: 1},
{in: "example.com", want: "example.com"},
{in: "[email protected]", wantErr: "email address"},
{in: "https:///nohost", wantErr: "no host"},
{in: "localhost", wantErr: "invalid domain format"},
Expand Down
Loading