storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/net/host.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package net 18 19 import ( 20 "encoding/json" 21 "errors" 22 "net" 23 "regexp" 24 "strings" 25 ) 26 27 var hostLabelRegexp = regexp.MustCompile("^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$") 28 29 // Host - holds network host IP/name and its port. 30 type Host struct { 31 Name string 32 Port Port 33 IsPortSet bool 34 } 35 36 // IsEmpty - returns whether Host is empty or not 37 func (host Host) IsEmpty() bool { 38 return host.Name == "" 39 } 40 41 // String - returns string representation of Host. 42 func (host Host) String() string { 43 if !host.IsPortSet { 44 return host.Name 45 } 46 47 return net.JoinHostPort(host.Name, host.Port.String()) 48 } 49 50 // Equal - checks whether given host is equal or not. 51 func (host Host) Equal(compHost Host) bool { 52 return host.String() == compHost.String() 53 } 54 55 // MarshalJSON - converts Host into JSON data 56 func (host Host) MarshalJSON() ([]byte, error) { 57 return json.Marshal(host.String()) 58 } 59 60 // UnmarshalJSON - parses data into Host. 61 func (host *Host) UnmarshalJSON(data []byte) (err error) { 62 var s string 63 if err = json.Unmarshal(data, &s); err != nil { 64 return err 65 } 66 67 // Allow empty string 68 if s == "" { 69 *host = Host{} 70 return nil 71 } 72 73 var h *Host 74 if h, err = ParseHost(s); err != nil { 75 return err 76 } 77 78 *host = *h 79 return nil 80 } 81 82 // ParseHost - parses string into Host 83 func ParseHost(s string) (*Host, error) { 84 if s == "" { 85 return nil, errors.New("invalid argument") 86 } 87 isValidHost := func(host string) bool { 88 if host == "" { 89 return true 90 } 91 92 if ip := net.ParseIP(host); ip != nil { 93 return true 94 } 95 96 // host is not a valid IPv4 or IPv6 address 97 // host may be a hostname 98 // refer https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names 99 // why checks are done like below 100 if len(host) < 1 || len(host) > 253 { 101 return false 102 } 103 104 for _, label := range strings.Split(host, ".") { 105 if len(label) < 1 || len(label) > 63 { 106 return false 107 } 108 109 if !hostLabelRegexp.MatchString(label) { 110 return false 111 } 112 } 113 114 return true 115 } 116 117 var port Port 118 var isPortSet bool 119 host, portStr, err := net.SplitHostPort(s) 120 if err != nil { 121 if !strings.Contains(err.Error(), "missing port in address") { 122 return nil, err 123 } 124 host = s 125 } else { 126 if port, err = ParsePort(portStr); err != nil { 127 return nil, err 128 } 129 130 isPortSet = true 131 } 132 133 if host != "" { 134 host, err = trimIPv6(host) 135 if err != nil { 136 return nil, err 137 } 138 } 139 140 // IPv6 requires a link-local address on every network interface. 141 // `%interface` should be preserved. 142 trimmedHost := host 143 144 if i := strings.LastIndex(trimmedHost, "%"); i > -1 { 145 // `%interface` can be skipped for validity check though. 146 trimmedHost = trimmedHost[:i] 147 } 148 149 if !isValidHost(trimmedHost) { 150 return nil, errors.New("invalid hostname") 151 } 152 153 return &Host{ 154 Name: host, 155 Port: port, 156 IsPortSet: isPortSet, 157 }, nil 158 } 159 160 // IPv6 can be embedded with square brackets. 161 func trimIPv6(host string) (string, error) { 162 // `missing ']' in host` error is already handled in `SplitHostPort` 163 if host[len(host)-1] == ']' { 164 if host[0] != '[' { 165 return "", errors.New("missing '[' in host") 166 } 167 return host[1:][:len(host)-2], nil 168 } 169 return host, nil 170 }