hostname.go (2261B)
1 package main 2 3 import ( 4 "fmt" 5 "slices" 6 "strings" 7 ) 8 9 // TODO: Add support for more than just alternation in patterns. We should at 10 // least support leading wildcards, but maybe we should support full regex. 11 // XXX: We currently don't length check hostnames or the labels within. 12 13 type label string 14 15 func parseLabel(labelStr string) (label, error) { 16 if len(labelStr) == 0 { 17 return "", fmt.Errorf("empty label") 18 } 19 20 buf := make([]byte, 0, len(labelStr)) 21 for _, r := range labelStr { 22 switch { 23 case 'A' <= r && r <= 'Z': 24 r += 'a' - 'A' 25 case 'a' <= r && r <= 'z': 26 // Ok 27 case '0' <= r && r <= '9': 28 // Ok 29 case r == '-': 30 // Ok, as long as test after loop passes 31 default: 32 return "", fmt.Errorf("illegal rune in label: %q", r) 33 } 34 35 buf = append(buf, byte(r)) 36 } 37 38 if buf[0] == '-' || buf[len(buf) - 1] == '-' { 39 return "", fmt.Errorf("hyphen at start or end of label") 40 } 41 42 return label(buf), nil 43 } 44 45 type hostname []label 46 47 func (hostname hostname) String() string { 48 // Ughhhh, Go can't convert between hostname and []string, 49 // so we can't use strings.Join. 50 51 var sb strings.Builder 52 for i, label := range hostname { 53 if i > 0 { 54 sb.WriteByte('.') 55 } 56 sb.WriteString(string(label)) 57 } 58 59 return sb.String() 60 } 61 62 func (hostname0 hostname) equal(hostname1 hostname) bool { 63 return slices.Equal(hostname0, hostname1) 64 } 65 66 func parseHostname(hostnameStr string) (hostname, error) { 67 labelStrs := strings.Split(hostnameStr, ".") 68 if len(labelStrs) < 2 { 69 return nil, fmt.Errorf("illegal hostname: expected 2 or more labels") 70 } 71 72 labels := make([]label, 0, len(labelStrs)) 73 for _, labelStr := range labelStrs { 74 label, err := parseLabel(labelStr) 75 if err != nil { 76 return nil, fmt.Errorf("illegal hostname: %w", err) 77 } 78 labels = append(labels, label) 79 } 80 81 return hostname(labels), nil 82 } 83 84 type pattern []hostname 85 86 func (pattern pattern) matches(hostname hostname) bool { 87 return slices.ContainsFunc(pattern, hostname.equal) 88 } 89 90 func parsePattern(hostnameStrs []string) (pattern, error) { 91 pattern := make(pattern, 0, len(hostnameStrs)) 92 for _, hostnameStr := range hostnameStrs { 93 hostname, err := parseHostname(hostnameStr) 94 if err != nil { 95 return nil, err 96 } 97 pattern = append(pattern, hostname) 98 } 99 100 return pattern, nil 101 }