mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: allow suffix after wildcard in wildcard access URL (#4524)
This commit is contained in:
+80
-13
@@ -17,21 +17,10 @@ var (
|
||||
// {PORT/APP_NAME}--{AGENT_NAME}--{WORKSPACE_NAME}--{USERNAME}
|
||||
`^(?P<AppName>%[1]s)--(?P<AgentName>%[1]s)--(?P<WorkspaceName>%[1]s)--(?P<Username>%[1]s)$`,
|
||||
nameRegex))
|
||||
|
||||
validHostnameLabelRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
|
||||
)
|
||||
|
||||
// SplitSubdomain splits a subdomain from the rest of the hostname. E.g.:
|
||||
// - "foo.bar.com" becomes "foo", "bar.com"
|
||||
// - "foo.bar.baz.com" becomes "foo", "bar.baz.com"
|
||||
// - "foo" becomes "foo", ""
|
||||
func SplitSubdomain(hostname string) (subdomain string, rest string) {
|
||||
toks := strings.SplitN(hostname, ".", 2)
|
||||
if len(toks) < 2 {
|
||||
return toks[0], ""
|
||||
}
|
||||
|
||||
return toks[0], toks[1]
|
||||
}
|
||||
|
||||
// ApplicationURL is a parsed application URL hostname.
|
||||
type ApplicationURL struct {
|
||||
// Only one of AppName or Port will be set.
|
||||
@@ -111,3 +100,81 @@ func HostnamesMatch(a, b string) bool {
|
||||
|
||||
return strings.EqualFold(aHost, bHost)
|
||||
}
|
||||
|
||||
// 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 supplied pattern:
|
||||
// - must not start or end with a period
|
||||
// - must contain exactly one asterisk at the beginning
|
||||
// - must not contain any other wildcard characters
|
||||
// - must not contain any other characters that are not hostname-safe (including
|
||||
// whitespace)
|
||||
// - must contain at least two hostname labels/segments (i.e. "foo" or "*" are
|
||||
// not valid patterns, but "foo.bar" and "*.bar" are).
|
||||
//
|
||||
// The returned regular expression will match an entire hostname with optional
|
||||
// trailing periods and whitespace. The first submatch will be the wildcard
|
||||
// match.
|
||||
func CompileHostnamePattern(pattern string) (*regexp.Regexp, error) {
|
||||
pattern = strings.ToLower(pattern)
|
||||
if strings.Contains(pattern, "http:") || strings.Contains(pattern, "https:") {
|
||||
return nil, xerrors.Errorf("hostname pattern must not contain a scheme: %q", pattern)
|
||||
}
|
||||
if strings.Contains(pattern, ":") {
|
||||
return nil, xerrors.Errorf("hostname pattern must not contain a port: %q", pattern)
|
||||
}
|
||||
if strings.HasPrefix(pattern, ".") || strings.HasSuffix(pattern, ".") {
|
||||
return nil, xerrors.Errorf("hostname pattern must not start or end with a period: %q", pattern)
|
||||
}
|
||||
if strings.Count(pattern, ".") < 1 {
|
||||
return nil, xerrors.Errorf("hostname pattern must contain at least two labels/segments: %q", pattern)
|
||||
}
|
||||
if strings.Count(pattern, "*") != 1 {
|
||||
return nil, xerrors.Errorf("hostname pattern must contain exactly one asterisk: %q", pattern)
|
||||
}
|
||||
if !strings.HasPrefix(pattern, "*") {
|
||||
return nil, xerrors.Errorf("hostname pattern must only contain an asterisk at the beginning: %q", pattern)
|
||||
}
|
||||
for i, label := range strings.Split(pattern, ".") {
|
||||
if i == 0 {
|
||||
// We have to allow the asterisk to be a valid hostname label.
|
||||
label = strings.TrimPrefix(label, "*")
|
||||
label = "a" + label
|
||||
}
|
||||
if !validHostnameLabelRegex.MatchString(label) {
|
||||
return nil, xerrors.Errorf("hostname pattern contains invalid label %q: %q", label, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace periods with escaped periods.
|
||||
regexPattern := strings.ReplaceAll(pattern, ".", "\\.")
|
||||
|
||||
// Capture wildcard match.
|
||||
regexPattern = strings.Replace(regexPattern, "*", "([^.]+)", 1)
|
||||
|
||||
// Allow trailing period.
|
||||
regexPattern = regexPattern + "\\.?"
|
||||
|
||||
// Allow optional port number.
|
||||
regexPattern += "(:\\d+)?"
|
||||
|
||||
// Allow leading and trailing whitespace.
|
||||
regexPattern = `^\s*` + regexPattern + `\s*$`
|
||||
|
||||
return regexp.Compile(regexPattern)
|
||||
}
|
||||
|
||||
// ExecuteHostnamePattern executes a pattern generated by CompileHostnamePattern
|
||||
// and returns the wildcard match. If the pattern does not match the hostname,
|
||||
// returns false.
|
||||
func ExecuteHostnamePattern(pattern *regexp.Regexp, hostname string) (string, bool) {
|
||||
matches := pattern.FindStringSubmatch(hostname)
|
||||
if len(matches) < 2 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return matches[1], true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user