hostess/hostess/hostname.go

102 lines
3.1 KiB
Go
Raw Normal View History

2020-02-27 04:14:07 +00:00
package hostess
import (
"fmt"
"net"
"regexp"
"strings"
)
var ipv4Pattern = regexp.MustCompile(`^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`)
var ipv6Pattern = regexp.MustCompile(`^[(a-fA-F0-9){1-4}:]+$`)
// LooksLikeIPv4 returns true if the IP looks like it's IPv4. This does not
// validate whether the string is a valid IP address.
func LooksLikeIPv4(ip string) bool {
return ipv4Pattern.MatchString(ip)
}
// LooksLikeIPv6 returns true if the IP looks like it's IPv6. This does not
// validate whether the string is a valid IP address.
func LooksLikeIPv6(ip string) bool {
if !strings.Contains(ip, ":") {
return false
}
return ipv6Pattern.MatchString(ip)
}
// Hostname represents a hosts file entry, including a Domain, IP, whether the
// Hostname is enabled (uncommented in the hosts file), and whether the IP is
// in the IPv6 format. You should always create these with NewHostname(). Note:
// when using Hostnames in the context of a Hostlist, you should not change the
// Hostname fields except through the Hostlist's aggregate methods. Doing so
// can cause unexpected behavior. Instead, use Hostlist's Add, Remove, Enable,
// and Disable methods.
type Hostname struct {
Domain string `json:"domain"`
IP net.IP `json:"ip"`
Enabled bool `json:"enabled"`
IPv6 bool `json:"-"`
}
// NewHostname creates a new Hostname struct and automatically sets the IPv6
// field based on the IP you pass in.
func NewHostname(domain, ip string, enabled bool) (*Hostname, error) {
if !LooksLikeIPv4(ip) && !LooksLikeIPv6(ip) {
return nil, fmt.Errorf("Unable to parse IP address %q", ip)
}
IP := net.ParseIP(ip)
return &Hostname{domain, IP, enabled, LooksLikeIPv6(ip)}, nil
}
// MustHostname calls NewHostname but panics if there is an error parsing it.
func MustHostname(domain, ip string, enabled bool) *Hostname {
hostname, err := NewHostname(domain, ip, enabled)
if err != nil {
panic(err)
}
return hostname
}
// Equal compares two Hostnames. Note that only the Domain and IP fields are
// compared because Enabled is transient state, and IPv6 should be set
// automatically based on IP.
func (h *Hostname) Equal(n *Hostname) bool {
return h.Domain == n.Domain && h.IP.Equal(n.IP)
}
// EqualIP compares an IP against this Hostname.
func (h *Hostname) EqualIP(ip net.IP) bool {
return h.IP.Equal(ip)
}
// IsValid does a spot-check on the domain and IP to make sure they aren't blank
func (h *Hostname) IsValid() bool {
return h.Domain != "" && h.IP != nil
}
// Format outputs the Hostname as you'd see it in a hosts file, with a comment
// if it is disabled. E.g.
// # 127.0.0.1 blah.example.com
func (h *Hostname) Format() string {
r := fmt.Sprintf("%s %s", h.IP.String(), h.Domain)
if !h.Enabled {
r = "# " + r
}
return r
}
// FormatEnabled displays Hostname.Enabled as (On) or (Off)
func (h *Hostname) FormatEnabled() string {
if h.Enabled {
return "(On)"
}
return "(Off)"
}
// FormatHuman outputs the Hostname in a more human-readable format:
// blah.example.com -> 127.0.0.1 (Off)
func (h *Hostname) FormatHuman() string {
return fmt.Sprintf("%s -> %s %s", h.Domain, h.IP, h.FormatEnabled())
}