-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdomain.go
117 lines (101 loc) · 2.76 KB
/
domain.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Froxy - HTTP over SSH proxy
//
// Copyright (C) 2019 and up by Alexander Pevzner ([email protected])
// See LICENSE for license terms and conditions
//
// Domain name validator
package main
import (
"errors"
"fmt"
"strings"
)
//
// Validate domain name
//
// This function validates international domain name, entered by user.
// It may either accept domain as is, return an error or suggest a
// replacement (for example, URL is replaced with naked hostname)
//
// This function attempts to be as tolerant as possible to user input.
// In particular, it accepts URLs and domains with port. If function
// accepts user input, the returned replacement will be a pure host
// name, converted to lower case
//
func DomainValidate(domain string) (string, error) {
// Check for URL
domain = domainCheckURL(domain)
// Strip port, if any
if i := strings.IndexByte(domain, ':'); i >= 0 {
domain = domain[:i]
}
// Decode IDN
domain = IDNEncode(domain)
// Check total length
switch {
case len(domain) < 1:
return "", errors.New("Domain name is empty")
case len(domain) > 253:
return "", errors.New("Domain name exceeds 253 bytes")
}
// Check for invalid characters
for _, c := range domain {
switch {
// Underscore characters are not allowed in domain names, but some
// browsers allow them, so we do the same
case '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' ||
c == '-' || c == '_' || c == '.':
default:
return "", fmt.Errorf("Domain name contains character '%c'", c)
}
}
// Split into labels and check each of them
labels := strings.Split(domain, ".")
for _, label := range labels {
// Labels cannot start or end with hyphens, but we are permissive
// here in case somebody may register such a domain and
// browsers are tolerant
switch {
case len(label) < 1:
return "", errors.New("Domain name label is empty")
case len(label) > 63:
return "", errors.New("Domain name label exceeds 63 bytes")
}
}
return IDNDecode(domain), nil
}
//
// If domain looks like URL, fetch and return the hostname
// Otherwise, return the domain verbatim
//
func domainCheckURL(domain string) string {
// URL starts with scheme://host/...,
// where scheme must be [a-zA-Z][a-zA-Z0-9+-.]*
for i := 0; i < len(domain); i++ {
c := domain[i]
switch {
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.':
if i == 0 {
return domain
}
case c == ':':
if i == 0 {
return domain
}
if !strings.HasPrefix(domain[i:], "://") {
return domain
}
// Strip schema
host := domain[i+3:]
// Strip path, if any
if j := strings.IndexByte(host, '/'); j >= 0 {
host = host[:j]
}
return host
default:
return domain
}
}
return domain
}