CompileHostnamePattern compiles a hostname pattern into a regular expression. A hostname pattern is a string that may contain a single wildcard character at the beginning. The wildcard character matches any number of hostname-safe characters excluding periods. The pattern is case-insensitive. The s
(pattern string)
| 241 | // trailing periods and whitespace. The first submatch will be the wildcard |
| 242 | // match. |
| 243 | func CompileHostnamePattern(pattern string) (*regexp.Regexp, error) { |
| 244 | pattern = strings.ToLower(pattern) |
| 245 | if strings.Contains(pattern, "http:") || strings.Contains(pattern, "https:") { |
| 246 | return nil, xerrors.Errorf("hostname pattern must not contain a scheme: %q", pattern) |
| 247 | } |
| 248 | |
| 249 | if strings.HasPrefix(pattern, ".") || strings.HasSuffix(pattern, ".") { |
| 250 | return nil, xerrors.Errorf("hostname pattern must not start or end with a period: %q", pattern) |
| 251 | } |
| 252 | if strings.Count(pattern, ".") < 1 { |
| 253 | return nil, xerrors.Errorf("hostname pattern must contain at least two labels/segments: %q", pattern) |
| 254 | } |
| 255 | if strings.Count(pattern, "*") != 1 { |
| 256 | return nil, xerrors.Errorf("hostname pattern must contain exactly one asterisk: %q", pattern) |
| 257 | } |
| 258 | if !strings.HasPrefix(pattern, "*") { |
| 259 | return nil, xerrors.Errorf("hostname pattern must only contain an asterisk at the beginning: %q", pattern) |
| 260 | } |
| 261 | |
| 262 | // If there is a hostname:port, we only care about the hostname. For hostname |
| 263 | // pattern reasons, we do not actually care what port the client is requesting. |
| 264 | // Any port provided here is used for generating urls for the ui, not for |
| 265 | // validation. |
| 266 | hostname, _, err := net.SplitHostPort(pattern) |
| 267 | if err == nil { |
| 268 | pattern = hostname |
| 269 | } |
| 270 | |
| 271 | for i, label := range strings.Split(pattern, ".") { |
| 272 | if i == 0 { |
| 273 | // We have to allow the asterisk to be a valid hostname label, so |
| 274 | // we strip the asterisk (which is only on the first one). |
| 275 | label = strings.TrimPrefix(label, "*") |
| 276 | // Put an "a" at the start to stand in for the asterisk in the regex |
| 277 | // test below. This makes `*.coder.com` become `a.coder.com` and |
| 278 | // `*--prod.coder.com` become `a--prod.coder.com`. |
| 279 | label = "a" + label |
| 280 | } |
| 281 | if !validHostnameLabelRegex.MatchString(label) { |
| 282 | return nil, xerrors.Errorf("hostname pattern contains invalid label %q: %q", label, pattern) |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | // Replace periods with escaped periods. |
| 287 | regexPattern := strings.ReplaceAll(pattern, ".", "\\.") |
| 288 | |
| 289 | // Capture wildcard match. |
| 290 | regexPattern = strings.Replace(regexPattern, "*", "([^.]+)", 1) |
| 291 | |
| 292 | // Allow trailing period. |
| 293 | regexPattern += "\\.?" |
| 294 | |
| 295 | // Allow optional port number. |
| 296 | regexPattern += "(:\\d+)?" |
| 297 | |
| 298 | // Allow leading and trailing whitespace. |
| 299 | regexPattern = `^\s*` + regexPattern + `\s*$` |
| 300 |