oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1package calendar
2
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "strings"
7)
8
9// CommonZones is the settings page's zone list.
10//
11// A short list, not the whole IANA database. Six hundred names in a
12// select is a control nobody can use, and the seam for the rest is a
13// text field beside it — somebody in Pacific/Chatham types it and the
14// server validates it against the real database, so the list being
15// short costs them one field rather than the feature.
16var CommonZones = []string{
17 "UTC",
18 "Europe/Dublin",
19 "Europe/London",
20 "Europe/Lisbon",
21 "Europe/Madrid",
22 "Europe/Paris",
23 "Europe/Berlin",
24 "Europe/Amsterdam",
25 "Europe/Brussels",
26 "Europe/Zurich",
27 "Europe/Rome",
28 "Europe/Stockholm",
29 "Europe/Helsinki",
30 "Europe/Athens",
31 "Europe/Warsaw",
32 "Europe/Kyiv",
33 "Europe/Istanbul",
34 "Europe/Moscow",
35 "America/St_Johns",
36 "America/Halifax",
37 "America/New_York",
38 "America/Toronto",
39 "America/Chicago",
40 "America/Mexico_City",
41 "America/Denver",
42 "America/Phoenix",
43 "America/Los_Angeles",
44 "America/Vancouver",
45 "America/Anchorage",
46 "America/Sao_Paulo",
47 "America/Argentina/Buenos_Aires",
48 "America/Bogota",
49 "America/Lima",
50 "America/Santiago",
51 "Africa/Cairo",
52 "Africa/Lagos",
53 "Africa/Nairobi",
54 "Africa/Johannesburg",
55 "Asia/Jerusalem",
56 "Asia/Dubai",
57 "Asia/Karachi",
58 "Asia/Kolkata",
59 "Asia/Kathmandu",
60 "Asia/Dhaka",
61 "Asia/Bangkok",
62 "Asia/Jakarta",
63 "Asia/Singapore",
64 "Asia/Hong_Kong",
65 "Asia/Shanghai",
66 "Asia/Manila",
67 "Asia/Tokyo",
68 "Asia/Seoul",
69 "Australia/Perth",
70 "Australia/Adelaide",
71 "Australia/Brisbane",
72 "Australia/Sydney",
73 "Pacific/Auckland",
74 "Pacific/Honolulu",
75}
76
77// commonZoneSet is CommonZones as a lookup, built once.
78var commonZoneSet = func() map[string]bool {
79 m := make(map[string]bool, len(CommonZones))
80 for _, z := range CommonZones {
81 m[z] = true
82 }
83 return m
84}()
85
86// IsCommonZone reports whether name is on the CommonZones list.
87//
88// It is the bound on a zone a STRANGER may name: the public booking page
89// accepts ?zone= only from this list (plus the host's own zone), so an
90// unauthenticated request can never reach time.LoadLocation with a
91// string of its choosing.
92func IsCommonZone(name string) bool { return commonZoneSet[name] }
93
94// etagFor is the feed's cache validator: a hash of exactly the bytes
95// being served, so it changes when and only when the file does.
96//
97// Not the calendar's updated_at, which would miss an occurrence rolling
98// out of the window without anything being edited, and not a random
99// value, which would defeat the 304 entirely.
100func etagFor(body string) string {
101 sum := sha256.Sum256([]byte(body))
102 return `"` + hex.EncodeToString(sum[:16]) + `"`
103}
104
105// safeFilename reduces a calendar's name to something a Content-
106// Disposition header and a filesystem will both accept.
107//
108// The name is content — it came out of a sealed column — so it must not
109// be able to inject a header. Anything outside a conservative set
110// becomes a dash rather than being escaped, because an escaped filename
111// that round-trips through three operating systems is not worth the
112// surface.
113func safeFilename(name string) string {
114 var b strings.Builder
115 for _, r := range name {
116 switch {
117 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
118 b.WriteRune(r)
119 case r == ' ' || r == '-' || r == '_':
120 b.WriteRune('-')
121 }
122 }
123 out := strings.Trim(b.String(), "-")
124 if out == "" {
125 return "calendar"
126 }
127 if len(out) > 60 {
128 out = out[:60]
129 }
130 return out
131}
132