sitelink.gno
2.92 Kb · 121 lines
1package kourtv3
2import (
3 "chain"
4 "strconv"
5 "strings"
6)
7const maxSiteDomainLen = 100
8var siteDomain string
9func siteDomainFault(d string) string {
10 if len(d) < 4 || len(d) > maxSiteDomainLen {
11 return "a site domain is 4..100 characters"
12 }
13 if !strings.Contains(d, ".") {
14 return "a site domain needs a dot — it is a hostname, not a word"
15 }
16 if d[0] == '.' || d[0] == '-' || d[len(d)-1] == '.' || d[len(d)-1] == '-' {
17 return "a site domain may not begin or end with a dot or a hyphen"
18 }
19 for i := 0; i < len(d); i++ {
20 c := d[i]
21 ok := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-'
22 if !ok {
23 return "a site domain is lowercase letters, digits, dots and " +
24 "hyphens only — no scheme, no path, no port"
25 }
26 }
27 if strings.Contains(d, "..") {
28 return "a site domain may not contain an empty label"
29 }
30 return ""
31}
32func mustSiteDomain(d string) {
33 if fault := siteDomainFault(d); fault != "" {
34 panic("kourtv3: " + fault)
35 }
36}
37func SetSiteDomain(cur realm, domain string) {
38 if !cur.IsCurrent() {
39 panic(errStaleRealm)
40 }
41 d := ensureGlobalDAO()
42 if cur.Previous().Address() != d.admin {
43 panic("kourtv3: only the global DAO admin sets the site domain")
44 }
45 mustSiteDomain(domain)
46 siteDomain = domain
47 chain.Emit(globalActEvent,
48 "court", "*",
49 "claim", "0",
50 "act", "set-site-domain:"+domain,
51 "by", cur.Previous().Address().String(),
52 "height", eventHeight(),
53 )
54}
55func ClearSiteDomain(cur realm) {
56 if !cur.IsCurrent() {
57 panic(errStaleRealm)
58 }
59 d := ensureGlobalDAO()
60 if cur.Previous().Address() != d.admin {
61 panic("kourtv3: only the global DAO admin clears the site domain")
62 }
63 if siteDomain == "" {
64 panic("kourtv3: no site domain is set")
65 }
66 siteDomain = ""
67 chain.Emit(globalActEvent,
68 "court", "*",
69 "claim", "0",
70 "act", "clear-site-domain",
71 "by", cur.Previous().Address().String(),
72 "height", eventHeight(),
73 )
74}
75func SiteDomain() string { return siteDomain }
76func sitePath(path string) string {
77 parts := strings.SplitN(path, "/", 4)
78 if path == "" || parts[0] == "" {
79 return "#/"
80 }
81 if len(parts) == 1 {
82 if parts[0] == helpPath {
83 return "#/"
84 }
85 if parts[0] == adminParamsPath {
86 return "#/params"
87 }
88 return "#/c/" + parts[0]
89 }
90 switch parts[1] {
91 case "me":
92 return "#/me"
93 case "mod":
94 return "#/c/" + parts[0]
95 }
96 if _, err := strconv.ParseUint(parts[1], 10, 64); err != nil {
97 return "#/c/" + parts[0]
98 }
99 return "#/c/" + parts[0] + "/" + parts[1]
100}
101func withSiteBanner(body, path string) string {
102 bar := siteBanner(path)
103 if bar == "" {
104 return body
105 }
106 if !strings.HasPrefix(body, "# ") {
107 return bar + body
108 }
109 i := strings.Index(body, "\n")
110 if i < 0 {
111 return body + "\n\n" + bar
112 }
113 return body[:i+1] + "\n" + bar + strings.TrimLeft(body[i+1:], "\n")
114}
115func siteBanner(path string) string {
116 if siteDomain == "" || siteDomainFault(siteDomain) != "" {
117 return ""
118 }
119 return "[Read this on " + siteDomain + " →](https://" + siteDomain + "/" +
120 sitePath(path) + ")\n\n"
121}