Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion internal/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func (a *App) handleIcal(ctx *gin.Context) {
return
}

response := []byte(cleaned.Serialize())
response := []byte(normalizeCRLF(cleaned.Serialize()))
ctx.Header("Content-Type", "text/calendar")
ctx.Header("Content-Length", fmt.Sprintf("%d", len(response)))

Expand All @@ -235,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) {
Expand Down
27 changes: 27 additions & 0 deletions internal/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
Loading