6.0.0-git
2018-12-16

[#13866] CalDAV PUT parses multiple VTIMEZONE data incorrectly, even its own ones
Summary CalDAV PUT parses multiple VTIMEZONE data incorrectly, even its own ones
Queue Synchronization
Queue Version FRAMEWORK_5_2
Type Bug
State Assigned
Priority 1. Low
Owners jan (at) horde (dot) org
Requester skhorde (at) smail (dot) inf (dot) fh-bonn-rhein-sieg (dot) de
Created 2015-02-17 (1398 days ago)
Due
Updated 2016-03-14 (1007 days ago)
Assigned 2015-03-13 (1374 days ago)
Resolved
Milestone
Patch No

History
2016-03-14 12:29:07 skhorde (at) smail (dot) inf (dot) fh-bonn-rhein-sieg (dot) de Comment #6 Reply to this comment
This issue applies to

Horde_Icalendar              2.1.3   stable

still. The attachment contains what I PUT to the server and what I GET back.
2015-04-15 16:30:17 Joerg (dot) Pulz (at) frm2 (dot) tum (dot) de Comment #5
New Attachment: patch-framework_Icalendar_lib_Horde_Icalendar_Vtimezone.php Download
Reply to this comment
Forget about the last patch, i was plain wrong.

The gmmktime() call has to use $year of the VEVENT for RRULEs without 
UNTIL parameter to get the correct dates for the CET/CEST switch in 
the year of the event and has to use $result['end'] for all RRULEs 
with UNTIL parameter to calculate the correct switch dates for this 
rules.

I reworked the patch to honor this.
Tested with em Client and Thunderbird/Lightning and seem to be okay.

Would be nice to see this committed as we soon start our internal 
scheduling for the beginning of 2016.

Kind regards
Joerg
2015-04-14 20:01:28 Joerg (dot) Pulz (at) frm2 (dot) tum (dot) de Comment #4
New Attachment: framework_Icalendar_lib_Horde_Icalendar_Vtimezone.php Download
Reply to this comment
I stumbled over the exact same problem last month (mid of march 2013).
Today i found some time to track this down.

Problem is the gmmktime() call in 
framework/Icalendar/lib/Horde/Icalendar/Vtimezone.php (lines 154, 155).
Attached is a patch to fix this.
gmmktime is called with parameter $year which holds the year the event 
actually occurs, instead $result['end'] should be used which holds the 
year value of the RRULE UNTIL field.

Some background information:
Hint: Everything is related to timezone Europe/Berlin. It may may 
affect other timezones too, but did no tests.

The problem is only visible every year for events between the 1st of 
january and the real date for the switch to summertime (CEST), eg. 
29th march 2015.
The CalDAV client has to do the following, which most clients ("em 
Client" in my case) do:
GET url
modify event
PUT url
GET url
While handling the PUT request, Horde parses all timezone entries of 
the submitted isc file (framework/Icalendar/lib/Horde/Icalendar.php 
line 1249).
For all entries without RRULE everything is fine and correctly parsed 
and converted to timestamps, but RRULEs are completely wrong.
Here the example:
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19170416T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYMONTHDAY=15,16,17,18,19,20,21;BYDAY=1MO;UNTIL
  =19180421T000000Z
TZNAME:CEST
END:DAYLIGHT
This results in the follwoing gmmktime() call:
gmmktime(3, 0, 0, 4, 1, 2015) = 1427857200 = "Wed Apr  1 05:00:00 CEST 2015"

After further processing the value gets even worse to "Wed Apr  6 
05:00:00 CEST 2015" but thats not relevant for demonstarting what 
happens.

As the DTSTART is 19170416T020000 (something around -1663448400 as 
timestamp)you get an $change_times array like this:

$change_times[0]['time']=-1663448400
$change_times[0]['from']=3600
$change_times[0]['to']=7200
$change_times[1]['time']=1427857200
$change_times[1]['from']=3600
$change_times[1]['to']=7200

The array is actually much larger but only those entries are relevant 
as all other following entries are wrongly not used, see the following.

Now there is the comparison in 
framework/Icalendar/lib/Horde/Icalendar.php line 1282:

             if (($t >= $change_times[0]['time']) &&
                 ($t < $change_times[0 + 1]['time'])) {
                 return $change_times[0]['to'];

Assume an event on "Thu Mar 26 08:00:00 CET 2015" which gmmktime'ed by 
Horde itself "1427356800".
1427356800 >= -1663448400 && 1427356800 < 1427857200 -> return 7200

So you actually get an offest of 7200 instead of 3600 and the entry is 
stored one hour off in the database and returned one hour off back to 
the client during the next CalDAV GET request and also shown one hour 
off in the Horde WebUI.

You can reproduce this for every year, as long as timezone 
"Europe/Berlin" is used and the event is sometimes between 1st of 
january and the real day of the switch from CET to CEST in this year.

I hope this is enough information to illustrate the root of the problem.

The attached patch is tested to work correctly.
A more complete and probably better solution would be to check the 
actual year of the event against the year for every timezone rule and 
only handle those rules that could affect the event and save some CPU 
cycles not processing rules from decades in the past ever and ever 
again, but that's another story.

Kind regards
Joerg
2015-03-13 18:14:33 Jan Schneider Assigned to Jan Schneider
State ⇒ Assigned
 
2015-02-23 14:18:00 skhorde (at) smail (dot) inf (dot) fh-bonn-rhein-sieg (dot) de Comment #3
New Attachment: caldav_vtimezone_bug13866.patch.bz2 Download
Reply to this comment
With RDATE entries are returned that exist in the requested $year and 
in $year-1.

With RRULE only one $result is returned, the entry of $year. So, if 
the caller wants to know the timezone for the 2nd Feb 2015, the 
timestamps of daylight and standard time of _2015_ are returned, which 
neither does apply! Daylight is in March, standard in Oct.

Attached patch returns the entry for $year and $year-1 for RRULE, too. 
Which is not 100% correct either, but mimmicks the idea of RDATE and 
will cover the situation I faced right now.
2015-02-18 13:45:39 skhorde (at) smail (dot) inf (dot) fh-bonn-rhein-sieg (dot) de Comment #2 Reply to this comment
If I remove these two VTimezone entries from the request below, it works.
It does not work to delete some other pair, it does not work to delete 
just one of them.

BEGIN:DAYLIGHT
TZNAME:CEMT
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
DTSTART:19450524T020000
RDATE;VALUE=DATE-TIME:19450524T020000
RDATE;VALUE=DATE-TIME:19470511T030000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
DTSTART:19450924T030000
RDATE;VALUE=DATE-TIME:19450924T030000
RDATE;VALUE=DATE-TIME:19470629T030000
END:DAYLIGHT

=======================

BEGIN:VCALENDAR
PRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN
VERSION:2.0
X-KDE-ICAL-IMPLEMENTATION-VERSION:1.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19160430T230000
RDATE;VALUE=DATE-TIME:19160430T230000
RDATE;VALUE=DATE-TIME:19170416T020000
RDATE;VALUE=DATE-TIME:19180415T020000
RDATE;VALUE=DATE-TIME:19400401T020000
RDATE;VALUE=DATE-TIME:19430329T020000
RDATE;VALUE=DATE-TIME:19440403T020000
RDATE;VALUE=DATE-TIME:19450402T020000
RDATE;VALUE=DATE-TIME:19460414T020000
RDATE;VALUE=DATE-TIME:19470406T030000
RDATE;VALUE=DATE-TIME:19480418T020000
RDATE;VALUE=DATE-TIME:19490410T020000
RDATE;VALUE=DATE-TIME:19800406T020000
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19800928T030000
RRULE:FREQ=YEARLY;COUNT=16;BYDAY=-1SU;BYMONTH=9
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19161001T010000
RDATE;VALUE=DATE-TIME:19161001T010000
RDATE;VALUE=DATE-TIME:19170917T030000
RDATE;VALUE=DATE-TIME:19180916T030000
RDATE;VALUE=DATE-TIME:19421102T030000
RDATE;VALUE=DATE-TIME:19431004T030000
RDATE;VALUE=DATE-TIME:19441002T030000
RDATE;VALUE=DATE-TIME:19451118T030000
RDATE;VALUE=DATE-TIME:19461007T030000
RDATE;VALUE=DATE-TIME:19471005T030000
RDATE;VALUE=DATE-TIME:19481003T030000
RDATE;VALUE=DATE-TIME:19491002T030000
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEMT
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
DTSTART:19450524T020000
RDATE;VALUE=DATE-TIME:19450524T020000
RDATE;VALUE=DATE-TIME:19470511T030000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
DTSTART:19450924T030000
RDATE;VALUE=DATE-TIME:19450924T030000
RDATE;VALUE=DATE-TIME:19470629T030000
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20150213T133358Z
CREATED:20150213T133358Z
UID:a2b4e780-195e-4b8c-bd71-0c674794b5ab
LAST-MODIFIED:20150218T141422Z
SUMMARY:korg 14:00 $t
DTSTART;TZID=Europe/Berlin:20150217T140000
DTEND;TZID=Europe/Berlin:20150217T150000
TRANSP:OPAQUE
BEGIN:VALARM
DESCRIPTION:
ACTION:DISPLAY
TRIGGER;VALUE=DURATION:-PT15M
X-KDE-KCALCORE-ENABLED:TRUE
END:VALARM
END:VEVENT
END:VCALENDAR

2015-02-17 12:41:56 skhorde (at) smail (dot) inf (dot) fh-bonn-rhein-sieg (dot) de Comment #1
Type ⇒ Bug
State ⇒ Unconfirmed
Priority ⇒ 1. Low
Summary ⇒ CalDAV PUT parses multiple VTIMEZONE data incorrectly, even its own ones
Queue ⇒ Synchronization
Milestone ⇒
Patch ⇒ No
Reply to this comment
In short, Lighning works, other CalDAV clients do not; the difference 
seems to be the number of timezone entries only.

kronolith                    4.2.5   stable
Horde_Timezone               1.0.9   stable
Horde_Dav                    1.1.2   stable
pear install HTTP_WebDAV_Server
pear/HTTP_WebDAV_Server is already installed and is the same as the 
released version 1.0.0RC8

If I re-upload an event from CalDAV:

GET url
PUT url
GET url

url is 
horde/rpc.php/calendars/uid/calendar~PEjFLdNBNhUO66MpM2Dlew5/1423834453.R724.ics

The time of the 2nd GET is 1 hour louer than on the 1st GET. My 
default timezone is GMT+1, so I guess the PUT parses the time as UTC. 
Below you'll find the content of the 2nd GET. If you strip all 
TIMEZONE entries, but these two ones:

BEGIN:VTIMEZONE
TZID:Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19810329T010000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19961027T010000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CET
END:STANDARD
END:VTIMEZONE

To re-upload the event via PUT does _not_change the time. So it seems 
that other timezone definitions confuse the CalDAV parser. I have the 
same problem with the KDE Korganizer v4.14.2, because it also uploads 
many timezone entries, but _not_ with Lightning, which uploads only 
the two effective ones above.

BEGIN:VCALENDAR
VERSION:2.0
X-WR-CALNAME:Calendar of dvtest6
PRODID:-//The Horde Project//Horde iCalendar Library//EN
BEGIN:VEVENT
DTSTART;TZID=Europe/Berlin:20150217T140000
DTEND;TZID=Europe/Berlin:20150217T150000
DTSTAMP:20150217T104721Z
UID:a2b4e780-195e-4b8c-bd71-0c674794b5ab
CREATED:20150213T133323Z
LAST-MODIFIED:20150217T104721Z
SUMMARY:perl 15:00 1424169619
CLASS:PUBLIC
STATUS:CONFIRMED
TRANSP:OPAQUE
END:VEVENT
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19160430T230000
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19161001T010000
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19170416T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYMONTHDAY=15,16,17,18,19,20,21;BYDAY=1MO;UNTIL
  =19180421T000000Z
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19170917T020000
RRULE:FREQ=YEARLY;BYMONTH=9;BYMONTHDAY=15,16,17,18,19,20,21;BYDAY=1MO;UNTIL
  =19180915T000000Z
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19400401T020000
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19421102T020000
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19430329T020000
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19431004T020000
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19440403T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1MO;UNTIL=19450401T010000Z
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19441002T020000
TZNAME:CE-T
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
DTSTART:19450916T020000
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0300
DTSTART:19450524T020000
TZNAME:CEMT
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
DTSTART:19450924T030000
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19451118T020000
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19460414T020000
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19461007T020000
TZNAME:CE-T
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
DTSTART:19471005T020000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19491002T010000Z
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19470406T030000
TZNAME:CEST
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
DTSTART:19470511T020000
TZNAME:CEMT
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
DTSTART:19470629T030000
TZNAME:CEST
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
TZOFFSETTO:+0200
DTSTART:19480418T020000
TZNAME:CEST
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
TZOFFSETTO:+0200
DTSTART:19490410T020000
TZNAME:CEST
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19800406T010000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=19800406T000000Z
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19800928T010000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9;UNTIL=19950923T230000Z
TZNAME:CE-T
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19810329T010000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZNAME:CEST
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19961027T010000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:CE-T
END:STANDARD
END:VTIMEZONE
END:VCALENDAR

Saved Queries