1// Copyright (c) 2015-2021 MinIO, Inc. 2// 3// This file is part of MinIO Object Storage stack 4// 5// This program is free software: you can redistribute it and/or modify 6// it under the terms of the GNU Affero General Public License as published by 7// the Free Software Foundation, either version 3 of the License, or 8// (at your option) any later version. 9// 10// This program is distributed in the hope that it will be useful 11// but WITHOUT ANY WARRANTY; without even the implied warranty of 12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13// GNU Affero General Public License for more details. 14// 15// You should have received a copy of the GNU Affero General Public License 16// along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18package net 19 20import ( 21 "encoding/json" 22 "errors" 23 "net" 24 "regexp" 25 "strings" 26) 27 28var hostLabelRegexp = regexp.MustCompile("^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$") 29 30// Host - holds network host IP/name and its port. 31type Host struct { 32 Name string 33 Port Port 34 IsPortSet bool 35} 36 37// IsEmpty - returns whether Host is empty or not 38func (host Host) IsEmpty() bool { 39 return host.Name == "" 40} 41 42// String - returns string representation of Host. 43func (host Host) String() string { 44 if !host.IsPortSet { 45 return host.Name 46 } 47 48 return net.JoinHostPort(host.Name, host.Port.String()) 49} 50 51// Equal - checks whether given host is equal or not. 52func (host Host) Equal(compHost Host) bool { 53 return host.String() == compHost.String() 54} 55 56// MarshalJSON - converts Host into JSON data 57func (host Host) MarshalJSON() ([]byte, error) { 58 return json.Marshal(host.String()) 59} 60 61// UnmarshalJSON - parses data into Host. 62func (host *Host) UnmarshalJSON(data []byte) (err error) { 63 var s string 64 if err = json.Unmarshal(data, &s); err != nil { 65 return err 66 } 67 68 // Allow empty string 69 if s == "" { 70 *host = Host{} 71 return nil 72 } 73 74 var h *Host 75 if h, err = ParseHost(s); err != nil { 76 return err 77 } 78 79 *host = *h 80 return nil 81} 82 83// ParseHost - parses string into Host 84func ParseHost(s string) (*Host, error) { 85 if s == "" { 86 return nil, errors.New("invalid argument") 87 } 88 isValidHost := func(host string) bool { 89 if host == "" { 90 return true 91 } 92 93 if ip := net.ParseIP(host); ip != nil { 94 return true 95 } 96 97 // host is not a valid IPv4 or IPv6 address 98 // host may be a hostname 99 // refer https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names 100 // why checks are done like below 101 if len(host) < 1 || len(host) > 253 { 102 return false 103 } 104 105 for _, label := range strings.Split(host, ".") { 106 if len(label) < 1 || len(label) > 63 { 107 return false 108 } 109 110 if !hostLabelRegexp.MatchString(label) { 111 return false 112 } 113 } 114 115 return true 116 } 117 118 var port Port 119 var isPortSet bool 120 host, portStr, err := net.SplitHostPort(s) 121 if err != nil { 122 if !strings.Contains(err.Error(), "missing port in address") { 123 return nil, err 124 } 125 host = s 126 } else { 127 if port, err = ParsePort(portStr); err != nil { 128 return nil, err 129 } 130 131 isPortSet = true 132 } 133 134 if host != "" { 135 host, err = trimIPv6(host) 136 if err != nil { 137 return nil, err 138 } 139 } 140 141 // IPv6 requires a link-local address on every network interface. 142 // `%interface` should be preserved. 143 trimmedHost := host 144 145 if i := strings.LastIndex(trimmedHost, "%"); i > -1 { 146 // `%interface` can be skipped for validity check though. 147 trimmedHost = trimmedHost[:i] 148 } 149 150 if !isValidHost(trimmedHost) { 151 return nil, errors.New("invalid hostname") 152 } 153 154 return &Host{ 155 Name: host, 156 Port: port, 157 IsPortSet: isPortSet, 158 }, nil 159} 160 161// IPv6 can be embedded with square brackets. 162func trimIPv6(host string) (string, error) { 163 // `missing ']' in host` error is already handled in `SplitHostPort` 164 if host[len(host)-1] == ']' { 165 if host[0] != '[' { 166 return "", errors.New("missing '[' in host") 167 } 168 return host[1:][:len(host)-2], nil 169 } 170 return host, nil 171} 172