aboutsummaryrefslogtreecommitdiff
path: root/internal/calfeed/ical_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-27 21:27:10 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-27 21:27:10 +0200
commit2901c73fde7ff19e8cdea00f933dc628d11c6ec6 (patch)
tree5ea265b5f27c0dc05f888cb84ae345e1751307e7 /internal/calfeed/ical_test.go
parent45e0b7652552867f7f99ffe22c968641510d5143 (diff)
downloadlectio-2901c73fde7ff19e8cdea00f933dc628d11c6ec6.tar.gz
lectio-2901c73fde7ff19e8cdea00f933dc628d11c6ec6.zip
feat(calfeed): RFC-5545 iCal renderer with injection-safe escaping
Diffstat (limited to 'internal/calfeed/ical_test.go')
-rw-r--r--internal/calfeed/ical_test.go52
1 files changed, 52 insertions, 0 deletions
diff --git a/internal/calfeed/ical_test.go b/internal/calfeed/ical_test.go
new file mode 100644
index 0000000..fb7ee9a
--- /dev/null
+++ b/internal/calfeed/ical_test.go
@@ -0,0 +1,52 @@
+package calfeed
+
+import (
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestICalEscapeInjection(t *testing.T) {
+ // A malicious custom-calendar name must not be able to inject lines/props.
+ got := icalEscape("Evil\r\nBEGIN:VEVENT\nSUMMARY:hijack; a,b\\c")
+ if strings.ContainsAny(got, "\r\n") {
+ t.Fatalf("unescaped newline survived: %q", got)
+ }
+ for _, sub := range []string{`\n`, `\;`, `\,`, `\\`} {
+ if !strings.Contains(got, sub) {
+ t.Fatalf("missing escape %q in %q", sub, got)
+ }
+ }
+}
+
+func TestICalStructure(t *testing.T) {
+ days := []DayView{{
+ Date: "2026-01-06", Season: "time-after-epiphany", Week: 1, Colour: "white",
+ Observed: CelView{Name: "The Epiphany of the Lord", Rank: "class-1"},
+ Readings: []ReadingView{{Part: "gospel", Citation: "Matt 2:1-12"}},
+ }}
+ out := string(ICal("old", days, time.Date(2026, 7, 27, 12, 0, 0, 0, time.UTC)))
+ for _, want := range []string{
+ "BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//lectio//calendar//EN",
+ "BEGIN:VEVENT", "UID:2026-01-06-old@lectio", "DTSTART;VALUE=DATE:20260106",
+ "DTEND;VALUE=DATE:20260107", "SUMMARY:The Epiphany of the Lord",
+ "CATEGORIES:WHITE", "DTSTAMP:20260727T120000Z", "END:VEVENT", "END:VCALENDAR",
+ } {
+ if !strings.Contains(out, want) {
+ t.Fatalf("missing %q in:\n%s", want, out)
+ }
+ }
+ // injection attempt via day count: exactly one VEVENT
+ if strings.Count(out, "BEGIN:VEVENT") != 1 {
+ t.Fatalf("expected 1 VEVENT")
+ }
+}
+
+func TestFoldLine(t *testing.T) {
+ long := "SUMMARY:" + strings.Repeat("x", 200)
+ for _, line := range strings.Split(foldLine(long), "\r\n") {
+ if len(line) > 75 {
+ t.Fatalf("line exceeds 75 octets: %d", len(line))
+ }
+ }
+}