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}