Raw
 1package web
 2
 3import (
 4	"fmt"
 5	"net/url"
 6	"strings"
 7
 8	"tug/internal/platform/config"
 9)
10
11type origins map[string]bool
12
13func newOrigins(srv config.Server, extra []string) (origins, error) {
14	o := origins{}
15	for _, d := range []string{srv.DomainIDP, srv.DomainAccount, srv.DomainAdmin} {
16		if d != "" {
17			o["https://"+d] = true
18		}
19	}
20	for _, e := range extra {
21		v, err := originOf(e)
22		if err != nil {
23			return nil, err
24		}
25		o[v] = true
26	}
27	return o, nil
28}
29
30func originOf(e string) (string, error) {
31	u, err := url.Parse(e)
32	if err != nil || (u.Scheme != "https" && u.Scheme != "http") || u.Host == "" || u.User != nil || u.Path != "" || u.RawQuery != "" || u.Fragment != "" {
33		return "", fmt.Errorf("navigation origin %q is not scheme://host[:port]", e)
34	}
35	return u.Scheme + "://" + u.Host, nil
36}
37
38func unsafeNavigation(v string) bool {
39	return strings.IndexFunc(v, func(c rune) bool { return c <= 0x20 || c == 0x7f || c == '\\' }) >= 0
40}
41
42func relativeSameOrigin(v string) bool {
43	return strings.HasPrefix(v, "/") && !strings.HasPrefix(v, "//") && !unsafeNavigation(v)
44}
45
46func (o origins) allows(v string) error {
47	if unsafeNavigation(v) {
48		return fmt.Errorf("navigation to %q contains a space, a control character or a backslash", v)
49	}
50	if relativeSameOrigin(v) || strings.HasPrefix(v, "#") {
51		return nil
52	}
53	u, err := url.Parse(v)
54	if err != nil {
55		return fmt.Errorf("navigation to %q does not parse", v)
56	}
57	if u.Opaque != "" || u.User != nil || u.Host == "" || !o[u.Scheme+"://"+u.Host] {
58		return fmt.Errorf("navigation to %q is not an allowed destination", v)
59	}
60	return nil
61}