fix(caldav): expand RRULE for recurring events
This commit is contained in:
@@ -27,8 +27,20 @@ import "regexp"
|
||||
var tzidParamRe = regexp.MustCompile(`TZID=("?)([^";:\r\n]+)("?)`)
|
||||
|
||||
// tzidLineRe matches a standalone "TZID:<name>" property line, as found
|
||||
// inside a VTIMEZONE component.
|
||||
var tzidLineRe = regexp.MustCompile(`(?m)^TZID:([^\r\n]+)$`)
|
||||
// inside a VTIMEZONE component. The trailing "(\r?)" capture group is
|
||||
// required, not cosmetic: Go's RE2 engine only matches "$" in multi-line
|
||||
// mode immediately before a literal "\n", not before "\r\n" — so without
|
||||
// consuming (and re-emitting) an optional trailing "\r" explicitly, this
|
||||
// regex silently fails to match on any CRLF-terminated feed (which is
|
||||
// every Outlook/Exchange-published ICS feed in practice), leaving the
|
||||
// VTIMEZONE's own TZID line un-normalized even though the "TZID=..."
|
||||
// parameter form (tzidParamRe, used on DTSTART/DTEND/etc.) still matches
|
||||
// fine. The practical effect was a VTIMEZONE whose declared TZID
|
||||
// ("W. Europe Standard Time") never matched the now-normalized
|
||||
// "TZID=Europe/Berlin" parameters referencing it elsewhere in the same
|
||||
// object, so any code trying to find "the VTIMEZONE for Europe/Berlin"
|
||||
// (see internal/icssub.Series.referencedTimezones) would never find one.
|
||||
var tzidLineRe = regexp.MustCompile(`(?m)^TZID:([^\r\n]+)(\r?)$`)
|
||||
|
||||
// NormalizeTimeZones rewrites any recognized Windows timezone identifier
|
||||
// in data to its IANA equivalent, leaving everything else (including
|
||||
@@ -51,7 +63,12 @@ func NormalizeTimeZones(data []byte) []byte {
|
||||
if !ok {
|
||||
return m
|
||||
}
|
||||
return []byte("TZID:" + iana)
|
||||
// sub[2] is the optional trailing "\r" the regex consumed as part
|
||||
// of the match (see the doc comment above) — it must be re-emitted
|
||||
// here, or a CRLF-terminated line loses its "\r" and every
|
||||
// subsequent line in the file ends up misaligned relative to the
|
||||
// original byte offsets a caller might have recorded.
|
||||
return []byte("TZID:" + iana + string(sub[2]))
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -69,3 +69,36 @@ func TestNormalizeTimeZonesLeavesUnknownAndIANAZonesAlone(t *testing.T) {
|
||||
t.Fatalf("expected data to be unchanged, got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNormalizeTimeZonesFixesVTIMEZONECRLFLine reproduces a real bug: the
|
||||
// standalone "TZID:<name>" property line inside a VTIMEZONE component
|
||||
// (as opposed to a "TZID=<name>" parameter on DTSTART/DTEND/etc.) was
|
||||
// never being normalized on real-world feeds, because every
|
||||
// Outlook/Exchange-published ICS uses CRLF line endings and Go's RE2 "$"
|
||||
// anchor in multi-line mode only matches immediately before a literal
|
||||
// "\n" — not before "\r\n". So on a line like
|
||||
// "TZID:W. Europe Standard Time\r\n", the old
|
||||
// `^TZID:([^\r\n]+)$` pattern never matched at all, leaving the
|
||||
// VTIMEZONE's own declared TZID un-normalized while every "TZID=..."
|
||||
// parameter elsewhere in the same object WAS normalized (that regex has
|
||||
// no such issue) — so nothing referencing the VTIMEZONE by name could
|
||||
// ever find it again. See internal/icssub.Series.referencedTimezones,
|
||||
// which depends on exactly this match to embed the right VTIMEZONE in a
|
||||
// synthetic per-series VCALENDAR.
|
||||
func TestNormalizeTimeZonesFixesVTIMEZONECRLFLine(t *testing.T) {
|
||||
const raw = "BEGIN:VCALENDAR\r\n" +
|
||||
"BEGIN:VTIMEZONE\r\n" +
|
||||
"TZID:W. Europe Standard Time\r\n" +
|
||||
"END:VTIMEZONE\r\n" +
|
||||
"END:VCALENDAR\r\n"
|
||||
const want = "BEGIN:VCALENDAR\r\n" +
|
||||
"BEGIN:VTIMEZONE\r\n" +
|
||||
"TZID:Europe/Berlin\r\n" +
|
||||
"END:VTIMEZONE\r\n" +
|
||||
"END:VCALENDAR\r\n"
|
||||
|
||||
got := string(NormalizeTimeZones([]byte(raw)))
|
||||
if got != want {
|
||||
t.Fatalf("CRLF-terminated VTIMEZONE TZID line was not normalized:\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user