package web import ( "fmt" "net/url" "strings" "tug/internal/platform/config" ) type origins map[string]bool func newOrigins(srv config.Server, extra []string) (origins, error) { o := origins{} for _, d := range []string{srv.DomainIDP, srv.DomainAccount, srv.DomainAdmin} { if d != "" { o["https://"+d] = true } } for _, e := range extra { v, err := originOf(e) if err != nil { return nil, err } o[v] = true } return o, nil } func originOf(e string) (string, error) { u, err := url.Parse(e) if err != nil || (u.Scheme != "https" && u.Scheme != "http") || u.Host == "" || u.User != nil || u.Path != "" || u.RawQuery != "" || u.Fragment != "" { return "", fmt.Errorf("navigation origin %q is not scheme://host[:port]", e) } return u.Scheme + "://" + u.Host, nil } func unsafeNavigation(v string) bool { return strings.IndexFunc(v, func(c rune) bool { return c <= 0x20 || c == 0x7f || c == '\\' }) >= 0 } func relativeSameOrigin(v string) bool { return strings.HasPrefix(v, "/") && !strings.HasPrefix(v, "//") && !unsafeNavigation(v) } func (o origins) allows(v string) error { if unsafeNavigation(v) { return fmt.Errorf("navigation to %q contains a space, a control character or a backslash", v) } if relativeSameOrigin(v) || strings.HasPrefix(v, "#") { return nil } u, err := url.Parse(v) if err != nil { return fmt.Errorf("navigation to %q does not parse", v) } if u.Opaque != "" || u.User != nil || u.Host == "" || !o[u.Scheme+"://"+u.Host] { return fmt.Errorf("navigation to %q is not an allowed destination", v) } return nil }