From dd0d82ad0f56590d1396ad4f12c6379c996adec8 Mon Sep 17 00:00:00 2001 From: "sentry[bot]" <39604003+sentry[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:06:59 +0000 Subject: [PATCH 1/2] Normalize iCal line endings to CRLF --- internal/app.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/app.go b/internal/app.go index d5faaa4..3512f15 100644 --- a/internal/app.go +++ b/internal/app.go @@ -226,7 +226,12 @@ func (a *App) handleIcal(ctx *gin.Context) { return } - response := []byte(cleaned.Serialize()) + // Normalize line endings to CRLF as required by RFC 5545. + // First replace any existing CRLF with LF to avoid doubling the \r, + // then replace all LF with CRLF. + serialized := strings.ReplaceAll(cleaned.Serialize(), "\r\n", "\n") + serialized = strings.ReplaceAll(serialized, "\n", "\r\n") + response := []byte(serialized) ctx.Header("Content-Type", "text/calendar") ctx.Header("Content-Length", fmt.Sprintf("%d", len(response))) From 821bbf11ad1bdf182e354316f6f7b32ce21e8b98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:17:21 +0000 Subject: [PATCH 2/2] Add test for CRLF normalization and extract helper function Agent-Logs-Url: https://github.com/TUM-Dev/CalendarProxy/sessions/6214dd01-8799-45e9-a021-70d21545a01a Co-authored-by: kordianbruck <298860+kordianbruck@users.noreply.github.com> --- internal/app.go | 15 +++++++++------ internal/app_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/internal/app.go b/internal/app.go index 3512f15..740daa1 100644 --- a/internal/app.go +++ b/internal/app.go @@ -226,12 +226,7 @@ func (a *App) handleIcal(ctx *gin.Context) { return } - // Normalize line endings to CRLF as required by RFC 5545. - // First replace any existing CRLF with LF to avoid doubling the \r, - // then replace all LF with CRLF. - serialized := strings.ReplaceAll(cleaned.Serialize(), "\r\n", "\n") - serialized = strings.ReplaceAll(serialized, "\n", "\r\n") - response := []byte(serialized) + response := []byte(normalizeCRLF(cleaned.Serialize())) ctx.Header("Content-Type", "text/calendar") ctx.Header("Content-Length", fmt.Sprintf("%d", len(response))) @@ -240,6 +235,14 @@ func (a *App) handleIcal(ctx *gin.Context) { } } +// normalizeCRLF normalizes line endings in s to CRLF (\r\n) as required by RFC 5545. +// It first replaces any existing CRLF with LF to avoid doubling the \r, +// then replaces all LF with CRLF. +func normalizeCRLF(s string) string { + s = strings.ReplaceAll(s, "\r\n", "\n") + return strings.ReplaceAll(s, "\n", "\r\n") +} + // handleGetCourses returns a list of all courses that are currently offered on campus. // This is used to populate the dropdown in the landing page for hiding courses. func (a *App) handleGetCourses(ctx *gin.Context) { diff --git a/internal/app_test.go b/internal/app_test.go index 638d85a..8837eeb 100644 --- a/internal/app_test.go +++ b/internal/app_test.go @@ -171,6 +171,33 @@ func TestLocationReplacement(t *testing.T) { } } +func TestNormalizeCRLF(t *testing.T) { + testData, app := getTestData(t, "duplication.ics") + calendar, err := app.getCleanedCalendar([]byte(testData), map[string]bool{}) + if err != nil { + t.Fatal(err) + } + + serialized := normalizeCRLF(calendar.Serialize()) + + // RFC 5545 requires CRLF line endings; there must be no bare \n. + if !strings.Contains(serialized, "\r\n") { + t.Error("serialized calendar should contain CRLF line endings") + } + for i, ch := range serialized { + if ch == '\n' && (i == 0 || serialized[i-1] != '\r') { + t.Error("serialized calendar contains a bare LF (\\n) without a preceding CR (\\r)") + break + } + } + + // Content-Length must equal byte length of the normalized output. + response := []byte(serialized) + if len(response) == 0 { + t.Error("serialized calendar response must not be empty") + } +} + func TestCourseFiltering(t *testing.T) { testData, app := getTestData(t, "coursefiltering.ics")