Bug Summary

File:activesyncd/eas-daemon/libeas/eas-cal-info-translator.c
Warning:line 3080, column 3
Value stored to 'tzid' is never read

Annotated Source Code

1/*
2 * ActiveSync core protocol library
3 *
4 * Copyright © 2011 Intel Corporation.
5 *
6 * Authors: Mobica Ltd. <www.mobica.com>
7 *
8 * This file is provided under a dual Apache/LGPLv2.1 licence. When
9 * using or redistributing this file, you may do so under either
10 * licence.
11 *
12 *
13 * LGPLv2.1 LICENCE SUMMARY
14 *
15 * Copyright © Intel Corporation, dates as above.
16 *
17 * This library is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public
19 * License as published by the Free Software Foundation; either
20 * version 2.1 of the License, or (at your option) any later
21 * version.
22 *
23 * This library is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * Lesser General Public License for more details.
27 *
28 * You should have received a copy of the GNU Lesser General Public
29 * License along with this library; if not, write to the Free
30 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
31 * Boston, MA 02110-1301 USA
32 *
33 *
34 * APACHE LICENCE SUMMARY
35 *
36 * Copyright © Intel Corporation, dates as above.
37 *
38 * Licensed under the Apache License, Version 2.0 (the "License");
39 * you may not use this file except in compliance with the License.
40 * You may obtain a copy of the License at
41 *
42 * http://www.apache.org/licenses/LICENSE-2.0
43 *
44 * Unless required by applicable law or agreed to in writing, software
45 * distributed under the License is distributed on an "AS IS" BASIS,
46 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
47 * See the License for the specific language governing permissions and
48 * limitations under the License.
49 *
50 */
51
52#include "eas-cal-info-translator.h"
53
54#include <libical/ical.h>
55
56#include <wbxml/wbxml.h>
57
58// Values for converting icaldurationtype into a number of minutes.
59// Note that code using these values is almost certainly wrong.
60// Days involving daylight saving time changes will have 23 or 25
61// hours, etc.
62const gint SECONDS_PER_MINUTE = 60;
63const gint MINUTES_PER_HOUR = 60;
64const gint MINUTES_PER_DAY = 60 * 24;
65const gint SECONDS_PER_DAY = 60 * 60 * 24;
66const gint MINUTES_PER_WEEK = 60 * 24 * 7;
67const gint DAYS_PER_WEEK = 7;
68const gint EPOCH_START_YEAR = 1970;
69
70// Constants for <Calendar> parsing
71const guint DAY_OF_WEEK_SUNDAY = 0x00000001;
72const guint DAY_OF_WEEK_MONDAY = 0x00000002;
73const guint DAY_OF_WEEK_TUESDAY = 0x00000004;
74const guint DAY_OF_WEEK_WEDNESDAY = 0x00000008;
75const guint DAY_OF_WEEK_THURSDAY = 0x00000010;
76const guint DAY_OF_WEEK_FRIDAY = 0x00000020;
77const guint DAY_OF_WEEK_SATURDAY = 0x00000040;
78const guint DAY_OF_WEEK_LAST_OF_MONTH = 0x0000007F; // 127
79
80/* summary for synthesized events, see eas_cal_info_translator_parse_request() */
81#define ACTIVESYNCD_PSEUDO_EVENT"[[activesyncd pseudo event - ignore me]]" "[[activesyncd pseudo event - ignore me]]"
82
83// EAS string value definitions
84#define EAS_NAMESPACE_CALENDAR"calendar:" "calendar:"
85#define EAS_NAMESPACE_AIRSYNCBASE"airsyncbase:" "airsyncbase:"
86#define X_NAMESPACE_ACTIVESYNCD"activesyncd:" "activesyncd:"
87#define X_ELEMENT_ICALEXENSIONS"iCalExtensions" "iCalExtensions"
88#define EAS_ELEMENT_APPLICATIONDATA"ApplicationData" "ApplicationData"
89#define EAS_ELEMENT_TIMEZONE"TimeZone" "TimeZone"
90#define EAS_ELEMENT_ALLDAYEVENT"AllDayEvent" "AllDayEvent"
91#define EAS_ELEMENT_BUSYSTATUS"BusyStatus" "BusyStatus"
92#define EAS_ELEMENT_ORGANIZER_NAME"Organizer_Name" "Organizer_Name"
93#define EAS_ELEMENT_ORGANIZER_EMAIL"Organizer_Email" "Organizer_Email"
94#define EAS_ELEMENT_DTSTAMP"DTStamp" "DTStamp"
95#define EAS_ELEMENT_ENDTIME"EndTime" "EndTime"
96#define EAS_ELEMENT_LOCATION"Location" "Location"
97#define EAS_ELEMENT_REMINDER"Reminder" "Reminder"
98#define EAS_ELEMENT_SENSITIVITY"Sensitivity" "Sensitivity"
99#define EAS_ELEMENT_SUBJECT"Subject" "Subject"
100#define EAS_ELEMENT_STARTTIME"StartTime" "StartTime"
101#define EAS_ELEMENT_UID"UID" "UID"
102#define EAS_ELEMENT_MEETINGSTATUS"MeetingStatus" "MeetingStatus"
103#define EAS_ELEMENT_ATTENDEES"Attendees" "Attendees"
104#define EAS_ELEMENT_ATTENDEE"Attendee" "Attendee"
105#define EAS_ELEMENT_ATTENDEE_EMAIL"Attendee_Email" "Attendee_Email"
106#define EAS_ELEMENT_ATTENDEE_NAME"Attendee_Name" "Attendee_Name"
107#define EAS_ELEMENT_ATTENDEE_STATUS"Attendee_Status" "Attendee_Status"
108#define EAS_ELEMENT_ATTENDEE_TYPE"Attendee_Type" "Attendee_Type"
109#define EAS_ELEMENT_CATEGORIES"Categories" "Categories"
110#define EAS_ELEMENT_CATEGORY"Category" "Category"
111#define EAS_ELEMENT_RECURRENCE"Recurrence" "Recurrence"
112#define EAS_ELEMENT_TYPE"Recurrence_Type" "Recurrence_Type"
113#define EAS_ELEMENT_OCCURRENCES"Recurrence_Occurrences" "Recurrence_Occurrences"
114#define EAS_ELEMENT_INTERVAL"Recurrence_Interval" "Recurrence_Interval"
115#define EAS_ELEMENT_WEEKOFMONTH"Recurrence_WeekOfMonth" "Recurrence_WeekOfMonth"
116#define EAS_ELEMENT_DAYOFWEEK"Recurrence_DayOfWeek" "Recurrence_DayOfWeek"
117#define EAS_ELEMENT_MONTHOFYEAR"Recurrence_MonthOfYear" "Recurrence_MonthOfYear"
118#define EAS_ELEMENT_UNTIL"Recurrence_Until" "Recurrence_Until"
119#define EAS_ELEMENT_DAYOFMONTH"Recurrence_DayOfMonth" "Recurrence_DayOfMonth"
120#define EAS_ELEMENT_CALENDARTYPE"CalendarType" "CalendarType"
121#define EAS_ELEMENT_ISLEAPMONTH"IsLeapMonth" "IsLeapMonth"
122#define EAS_ELEMENT_FIRSTDAYOFWEEK"FirstDayOfWeek" "FirstDayOfWeek"
123#define EAS_ELEMENT_EXCEPTIONS"Exceptions" "Exceptions"
124#define EAS_ELEMENT_EXCEPTION"Exception" "Exception"
125#define EAS_ELEMENT_DELETED"Exception_Deleted" "Exception_Deleted"
126#define EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime" "Exception_StartTime"
127#define EAS_ELEMENT_APPOINTMENTREPLYTIME"AppointmentReplyTime" "AppointmentReplyTime"
128#define EAS_ELEMENT_RESPONSETYPE"ResponseType" "ResponseType"
129#define EAS_ELEMENT_BODY"Body" "Body"
130#define EAS_ELEMENT_RESPONSEREQUESTED"ResponseRequested" "ResponseRequested"
131#define EAS_ELEMENT_NATIVEBODYTYPE"NativeBodyType" "NativeBodyType"
132#define EAS_ELEMENT_ONLINEMEETINGCONFLINK"OnlineMeetingConfLink" "OnlineMeetingConfLink"
133#define EAS_ELEMENT_ONLINEMEETINGEXTERNALLINK"OnlineMeetingExternalLink" "OnlineMeetingExternalLink"
134#define EAS_ELEMENT_DATA"Data" "Data"
135#define EAS_ELEMENT_TRUNCATED"Truncated" "Truncated"
136#define EAS_ELEMENT_BODY_TYPE"Type" "Type"
137
138#define EAS_SENSITIVITY_NORMAL"0" "0"
139#define EAS_SENSITIVITY_PERSONAL"1" "1"
140#define EAS_SENSITIVITY_PRIVATE"2" "2"
141#define EAS_SENSITIVITY_CONFIDENTIAL"3" "3"
142
143#define EAS_BUSYSTATUS_FREE"0" "0"
144#define EAS_BUSYSTATUS_TENTATIVE"1" "1"
145#define EAS_BUSYSTATUS_BUSY"2" "2"
146#define EAS_BUSYSTATUS_OUTOFOFFICE"3" "3"
147
148#define EAS_BODY_TYPE_PLAINTEXT"1" "1"
149
150#define EAS_BOOLEAN_FALSE"0" "0"
151#define EAS_BOOLEAN_TRUE"1" "1"
152
153// Other assorted string constants
154#define ICAL_PROPERTY_PRODID"-//Meego//ActiveSyncD 1.0//EN" "-//Meego//ActiveSyncD 1.0//EN"
155#define ICAL_PROPERTY_VERSION"2.0" "2.0"
156#define ICAL_EXTENSION_PROPERTY_PREFIX"X-MEEGO-ACTIVESYNCD-" "X-MEEGO-ACTIVESYNCD-"
157#define ICAL_DEFAULT_TZID"Standard Timezone" "Standard Timezone" // Only used internally, not visible to user
158#define ICAL_DEFAULT_REMINDER_NAME"Reminder" "Reminder"
159
160
161
162
163
164#define compile_time_assert(cond, msg)char msg[(cond)?1:-1] \
165 char msg[(cond)?1:-1]
166
167typedef struct {
168 guint16 Year;
169 guint16 Month;
170 guint16 DayOfWeek;
171 guint16 Day;
172 guint16 Hour;
173 guint16 Minute;
174 guint16 Second;
175 guint16 Millisecond;
176} __attribute__ ( (packed)) EasSystemTime;
177
178compile_time_assert ( (sizeof (EasSystemTime) == 16), EasSystemTime_not_expected_size)char EasSystemTime_not_expected_size[((sizeof (EasSystemTime)
== 16))?1:-1]
;
179
180/* From ActiveSync Protocol Doc MS-ASDTYPE
181 * The required values are Bias, which is the offset from UTC, in minutes, and
182 * the StandardDate and DaylightDate, which are needed when the biases take
183 * effect. For example, the bias for Pacific Time is 480.
184 */
185typedef struct {
186 gint32 Bias; // 4
187 guint16 StandardName[32]; // 64
188 EasSystemTime StandardDate; // 16
189 gint32 StandardBias; // 4
190 guint16 DaylightName[32]; // 64
191 EasSystemTime DaylightDate; // 16
192 gint32 DaylightBias; // 4
193} __attribute__ ( (packed)) EasTimeZone; // 172
194
195compile_time_assert ( (sizeof (EasTimeZone) == 172), EasTimeZone_not_expected_size)char EasTimeZone_not_expected_size[((sizeof (EasTimeZone) == 172
))?1:-1]
;
196
197/* Check if a contact field allready set in the applicationdata xml children*/
198static gboolean
199is_element_set (xmlNodePtr appData, const gchar* name)
200{
201 xmlNodePtr node = NULL((void*)0);
202 g_return_val_if_fail (appData != NULL && name != NULL, FALSE)do{ if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if ((appData != ((void*)0) && name != ((void*)0))) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) { } else
{ g_return_if_fail_warning ("libeas", ((const char*) (__func__
)), "appData != NULL && name != NULL"); return ((0));
}; }while (0)
;
203 node = appData;
204 for (node = appData->children; node ; node = node->next)
205 if (!strcmp ( (char*) node->name, name)__extension__ ({ size_t __s1_len, __s2_len; (__builtin_constant_p
((char*) node->name) && __builtin_constant_p (name
) && (__s1_len = __builtin_strlen ((char*) node->name
), __s2_len = __builtin_strlen (name), (!((size_t)(const void
*)(((char*) node->name) + 1) - (size_t)(const void *)((char
*) node->name) == 1) || __s1_len >= 4) && (!((size_t
)(const void *)((name) + 1) - (size_t)(const void *)(name) ==
1) || __s2_len >= 4)) ? __builtin_strcmp ((char*) node->
name, name) : (__builtin_constant_p ((char*) node->name) &&
((size_t)(const void *)(((char*) node->name) + 1) - (size_t
)(const void *)((char*) node->name) == 1) && (__s1_len
= __builtin_strlen ((char*) node->name), __s1_len < 4)
? (__builtin_constant_p (name) && ((size_t)(const void
*)((name) + 1) - (size_t)(const void *)(name) == 1) ? __builtin_strcmp
((char*) node->name, name) : (__extension__ ({ const unsigned
char *__s2 = (const unsigned char *) (const char *) (name); int
__result = (((const unsigned char *) (const char *) ((char*)
node->name))[0] - __s2[0]); if (__s1_len > 0 &&
__result == 0) { __result = (((const unsigned char *) (const
char *) ((char*) node->name))[1] - __s2[1]); if (__s1_len
> 1 && __result == 0) { __result = (((const unsigned
char *) (const char *) ((char*) node->name))[2] - __s2[2]
); if (__s1_len > 2 && __result == 0) __result = (
((const unsigned char *) (const char *) ((char*) node->name
))[3] - __s2[3]); } } __result; }))) : (__builtin_constant_p (
name) && ((size_t)(const void *)((name) + 1) - (size_t
)(const void *)(name) == 1) && (__s2_len = __builtin_strlen
(name), __s2_len < 4) ? (__builtin_constant_p ((char*) node
->name) && ((size_t)(const void *)(((char*) node->
name) + 1) - (size_t)(const void *)((char*) node->name) ==
1) ? __builtin_strcmp ((char*) node->name, name) : -(__extension__
({ const unsigned char *__s2 = (const unsigned char *) (const
char *) ((char*) node->name); int __result = (((const unsigned
char *) (const char *) (name))[0] - __s2[0]); if (__s2_len >
0 && __result == 0) { __result = (((const unsigned char
*) (const char *) (name))[1] - __s2[1]); if (__s2_len > 1
&& __result == 0) { __result = (((const unsigned char
*) (const char *) (name))[2] - __s2[2]); if (__s2_len > 2
&& __result == 0) __result = (((const unsigned char *
) (const char *) (name))[3] - __s2[3]); } } __result; }))) : __builtin_strcmp
((char*) node->name, name)))); })
)
206 return TRUE(!(0));
207
208 return FALSE(0);
209}
210
211/** @brief Get a DATE or DATE-TIME property as an icaltime
212 *
213 * If the property is a DATE-TIME with a timezone parameter and a
214 * corresponding VTIMEZONE is present in the component, the
215 * returned component will already be in the correct timezone;
216 * otherwise the caller is responsible for converting it.
217 *
218 * FIXME this is useless until we can flag the failure
219 *
220 * A copy of icalcomponent_get_datetime(), which is not part of the libical API.
221 */
222static struct icaltimetype
223_eas2cal_get_datetime (const icalcomponent *comp, const icalproperty *prop) {
224
225 const icalcomponent *c;
226 icalparameter *param;
227 struct icaltimetype ret;
228
229 ret = icalvalue_get_datetime(icalproperty_get_value(prop));
230
231 if ((param = icalproperty_get_first_parameter((icalproperty *)prop, ICAL_TZID_PARAMETER))
232 != NULL((void*)0)) {
233 const char *tzid = icalparameter_get_tzid(param);
234 icaltimezone *tz = NULL((void*)0);
235
236 for (c = comp; c != NULL((void*)0); c = icalcomponent_get_parent((icalcomponent *)c)) {
237 tz = icalcomponent_get_timezone((icalcomponent *)c, tzid);
238 if (tz != NULL((void*)0))
239 break;
240 }
241
242 if (tz == NULL((void*)0))
243 tz = icaltimezone_get_builtin_timezone_from_tzid(tzid);
244
245 if (tz != NULL((void*)0))
246 ret = icaltime_set_timezone(&ret, tz);
247 }
248
249 return ret;
250}
251
252/** missing in libical header files?! */
253extern icalcomponent *icalproperty_get_parent (const icalproperty *prop);
254
255/**
256 * In contrast to icalproperty_get_recurrenceid(), this function sets the
257 * time zone of the returned value based on the TZID and VTIMEZONE of
258 * the calender in which the property is set.
259 */
260static struct icaltimetype
261_eas2cal_property_get_recurrenceid (const icalproperty *prop)
262{
263 return _eas2cal_get_datetime (icalproperty_get_parent (prop),
264 prop);
265}
266
267static struct icaltimetype
268_eas2cal_component_get_recurrenceid (const icalcomponent *comp)
269{
270 icalproperty *prop = icalcomponent_get_first_property ((icalcomponent *)comp, ICAL_RECURRENCEID_PROPERTY);
271 return prop ?
272 _eas2cal_get_datetime (comp, prop) :
273 icaltime_null_time();
274}
275
276/**
277 * Convert a <Sensitivity> value from EAS XML into an iCal CLASS property value
278 */
279static icalproperty_class _eas2ical_convert_sensitivity_to_class (const gchar* sensitivityValue)
280{
281 if (g_strcmp0 (sensitivityValue, EAS_SENSITIVITY_CONFIDENTIAL"3") == 0) {
282 return ICAL_CLASS_CONFIDENTIAL;
283 } else if (g_strcmp0 (sensitivityValue, EAS_SENSITIVITY_PRIVATE"2") == 0) {
284 return ICAL_CLASS_PRIVATE;
285 } else { // Personal or Normal (iCal doesn't distinguish between them)
286 return ICAL_CLASS_PUBLIC;
287 }
288}
289
290
291/**
292 * Convert a <BusyStatus> value from EAS XML into an iCal TRANSP property value
293 */
294static icalproperty_transp _eas2ical_convert_busystatus_to_transp (const gchar* busystatusValue)
295{
296 if (g_strcmp0 (busystatusValue, EAS_BUSYSTATUS_FREE"0") == 0) {
297 return ICAL_TRANSP_TRANSPARENT;
298 } else { // Tentative, Busy or Out of Office
299 return ICAL_TRANSP_OPAQUE;
300 }
301}
302
303
304/**
305 * The by_day member of icalrecurrencetype is a composite value, encoding
306 * both the day to repeat on and (in the case of monthly recurrence) the position
307 * of the day to repeat on (e.g. 2nd Monday in each month). libical doesn't provide
308 * an API to encode this, so we've had to write our own by reverse engineering
309 * the implementation of icalrecurrencetype_day_day_of_week() and
310 * icalrecurrencetype_day_position() in icalrecur.c.
311 *
312 * @param byDayDayOfWeek
313 * The day of the week to repeat on
314 * @param byDayPos
315 * The position of this day in the month on which to repeat (or 0 if not applicable)
316 */
317static short _eas2ical_make_rrule_by_day_value (icalrecurrencetype_weekday byDayDayOfWeek, short byDayPos)
318{
319 // Now calculate the composite value as follows:
320 // - Multiply byDayPos by 8
321 // - Add byDayDayOfWeek if byDayPos is positive, or subtract if negative
322 short dowShort = (short) byDayDayOfWeek;
323 return
324 (byDayPos * 8) +
325 ( (byDayPos >= 0) ? dowShort : (-1 * dowShort));
326}
327
328
329/**
330 * Private function to decode an EasSystemTime struct containing details of a recurring
331 * timezone change pattern, modify it to a format iCalendar expects and create the
332 * required RRULE property value.
333 *
334 * @param date
335 * Pointer to the EasSystemTime struct. This will be modified in this
336 * function to contain the first instance of the recurring sequence.
337 * @param rrule
338 * Pointer to an icalrecurrencetype struct to store the recurrence rule in.
339 */
340static void _eas2ical_convert_relative_timezone_date (EasSystemTime* date, struct icalrecurrencetype* rrule)
341{
342 // Microsoft's SYSTEMTIME (implemented here as EasSystemTime) stores a recurring date pattern as follows:
343 // - Year set to 0
344 // - DayOfWeek set to the weekday to repeat on (0 = Sunday,...)
345 // - Day set to the nth weekday to repeat on (1 = first DayOfWeek of the month ... 5 = last of the month)
346 //
347 // For iCalendar, we have to convert this into the following:
348 // - A DSTART property identifying the first date of the recurring sequence in a year
349 // guaranteed to fall before any events using this timezone pattern. (We're using
350 // 1970 as the start of the Unix epoch)
351 // - An RRULE property defining the recurring sequence, in the following format:
352 //
353 // FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
354 //
355 // In the RRULE, the number in the BYDAY value denotes the nth occurrence in the month,
356 // with a negatve value meaning the nth *last* occurrence in the month.
357 //
358 // So, we have all the information we need in the SYSTEMTIME but there's quite a bit of
359 // conversin to do to get to the iCalendar properties.
360
361 EasSystemTime modifiedDate;
362 GDate* recurrenceStartDate;
363 GDateWeekday weekday;
364 gint occurrence;
365 short byDayPos;
366 icalrecurrencetype_weekday byDayDayOfWeek;
367
368 // We're going to modify the value of date to reflect the iCal values. (e.g. the Year
369 // will change from 0 to 1970 and the Day will change from a 1..5 "nth occurrence in the
370 // month" value to an actual date of the required month in 1970. we'll use a copy to do
371 // the modifications on then assign it back before we return.
372 memcpy (&modifiedDate, date, sizeof (EasSystemTime));
373
374 modifiedDate.Year = EPOCH_START_YEAR;
375
376 // Now adjust the DayOfWeek.
377 // SYSTEMTIME's wDayOfWeek has 0 = Sunday, 1 = Monday,... http://msdn.microsoft.com/en-us/library/ms724950(v=vs.85).aspx
378 // GDateWeekDay has 0 = G_DATE_BAD_WEEKDAY, 1 = Monday,... http://developer.gnome.org/glib/stable/glib-Date-and-Time-Functions.html#GDateWeekday
379 if (modifiedDate.DayOfWeek == 0) {
380 modifiedDate.DayOfWeek = (guint16) G_DATE_SUNDAY;
381 // Now our DayOfWeek matches GDateWeekDay (see below)
382 }
383
384 // date->DayOfWeek will give us the day to repeat on
385 // date->Day will give us the nth occurrence in the month of this day
386 // (where 5 == last, even if there are only 4)
387
388 // We're going to build a GDate object to calculate the actual date in 1970 that represents
389 // the first instance of this recurrence pattern.
390 // Start by initialising to the 1st of of the month
391 recurrenceStartDate = g_date_new_dmy (1, modifiedDate.Month, modifiedDate.Year);
392
393 // Now seek for the first occurrence of the day the sequence repeats on.
394 weekday = (GDateWeekday) modifiedDate.DayOfWeek;
395 while (g_date_get_weekday (recurrenceStartDate) != weekday) {
396 g_date_add_days (recurrenceStartDate, 1);
397 }
398
399 // Now we've got the FIRST occurence of the correct weekday in the correct month in 1970.
400 // Finally we need to seek for the nth occurrence (where 5th = last, even if there are only 4)
401 occurrence = 1;
402 while (occurrence++ < date->Day) {
403 g_date_add_days (recurrenceStartDate, DAYS_PER_WEEK);
404
405 // Check we havn't overrun the end of the month
406 // (If we have, just roll back and we're done: we're on the last occurrence)
407 if (g_date_get_month (recurrenceStartDate) != modifiedDate.Month) {
408 g_date_subtract_days (recurrenceStartDate, DAYS_PER_WEEK);
409 break;
410 }
411 }
412
413 modifiedDate.Day = g_date_get_day (recurrenceStartDate);
414
415 g_free (recurrenceStartDate);
416
417 // Now populate the rrule value before modifying date->Day
418 rrule->freq = ICAL_YEARLY_RECURRENCE;
419 rrule->by_month[0] = modifiedDate.Month;
420
421 // The by_day value in icalrecurrencetype is a short containing two
422 // fields: the day, and the position in the month. Weirdly there's no
423 // API to encode this (only to decode it) so we'll have to do it by hand.
424 // Unfortunately the comment in icalrecur.c ("The day's position in the
425 // period (Nth-ness) and the numerical value of the day are encoded
426 // together as: pos*7 + dow") is wrong. :-\ From the code we can see it's
427 // actually as implemented below.
428 byDayPos = (short) date->Day;
429 if (byDayPos == 5) {
430 byDayPos = -1;
431 }
432 // Here the day is represented by the enum icalrecurrencetype_weekday, which has the
433 // range 0 = ICAL_NO_WEEKDAY, 1 = ICAL_SUNDAY_WEEKDAY, 2 = ICAL_MONDAY_WEEKDAY...
434 // so we can just add 1 to the EasSystemTime value (see above).
435 byDayDayOfWeek = (icalrecurrencetype_weekday) (date->DayOfWeek + 1);
436
437 // Now calculate the composite value as follows:
438 // - Multiply byDayPos by 8
439 // - Add byDayDayOfWeek if byDayPos is positive, or subtract if negative
440 rrule->by_day[0] = _eas2ical_make_rrule_by_day_value (byDayDayOfWeek, byDayPos);
441
442 // Finally, copy the modified version back into the date that was passed in
443 memcpy (date, &modifiedDate, sizeof (EasSystemTime));
444}
445
446/**
447 * Turn UTC date-time value into time relative to zone (if given),
448 * strip down to date-only (if all-day). Add TZID as needed.
449 *
450 * @return TRUE if zone definition is needed
451 */
452static gboolean _eas2ical_convert_datetime_property(icalproperty *prop,
453 icaltimezone *icaltz,
454 gboolean isAllDayEvent,
455 struct icaltimetype (*get)(const icalproperty *prop),
456 void (*set)(icalproperty *prop, struct icaltimetype))
457{
458 gboolean needtz = FALSE(0);
459 // NOP without either of these
460 if (icaltz || isAllDayEvent) {
461 struct icaltimetype tt = get (prop);
462 if (icaltz) {
463 struct icaltimetype localtt = icaltime_convert_to_zone (tt, icaltz);
464 // Sanity check for all day events: should align with midnight after conversion.
465 // If it doesn't, something is wrong. Happened with Exchange 2010
466 // on 123together.com, but only for events created via ActiveSync,
467 // not for events created via OWA. If the check fails, then don't
468 // do the conversion to local time.
469 if (isAllDayEvent) {
470 if (localtt.hour || localtt.minute || localtt.second)
471 g_warning("All day event does not start/finish at local midnight: %s. Reverting to EAS specified time: %s",
472 icaltime_as_ical_string(localtt),
473 icaltime_as_ical_string(tt));
474 else tt=localtt;
475 } else tt=localtt;
476 } else {
477 if (tt.hour || tt.minute || tt.second)
478 g_warning("All day event with no timezone does not start at UTC midnight: %s", icaltime_as_ical_string(tt));
479 }
480 // If the sanity check (see above) failed, don't mark as a date
481 if (isAllDayEvent && !tt.hour && !tt.minute && !tt.second)
482 tt.is_date = 1;
483 set (prop, tt);
484
485 // Date-based events can and should be defined in local time without time zone.
486 // UTC time stamps don't need a TZID.
487 if (icaltz &&
488 !tt.is_date &&
489 !icaltime_is_utc (tt)) {
490 const char *tzid = icaltimezone_get_tzid (icaltz);
491 if (tzid && strlen (tzid)) {
492 icalparameter *param = icalparameter_new_tzid (tzid);
493 icalproperty_add_parameter (prop, param);
494 needtz = TRUE(!(0));
495 }
496 }
497 }
498 return needtz;
499}
500
501/**
502 * Fix all date-time values (DTSTART/END, RECURRENCE-ID, EXDATE)
503 * and the RRULE UNTIL clause.
504 *
505 * @return TRUE if zone definition is needed
506 */
507static gboolean _eas2ical_convert_component(icalcomponent *vevent,
508 icaltimezone *icaltz,
509 gboolean isAllDayEvent,
510 gboolean parentIsAllDayEvent)
511{
512 gboolean needtz = FALSE(0);
513 icalproperty *prop;
514
515 for (prop = icalcomponent_get_first_property (vevent, ICAL_DTEND_PROPERTY);
516 prop;
517 prop = icalcomponent_get_next_property (vevent, ICAL_DTEND_PROPERTY)) {
518 if (_eas2ical_convert_datetime_property (prop, icaltz, isAllDayEvent,
519 icalproperty_get_dtend,
520 icalproperty_set_dtend))
521 needtz = TRUE(!(0));
522 }
523 for (prop = icalcomponent_get_first_property (vevent, ICAL_DTSTART_PROPERTY);
524 prop;
525 prop = icalcomponent_get_next_property (vevent, ICAL_DTSTART_PROPERTY)) {
526 if (_eas2ical_convert_datetime_property (prop, icaltz, isAllDayEvent,
527 icalproperty_get_dtstart,
528 icalproperty_set_dtstart))
529 needtz = TRUE(!(0));
530 }
531 for (prop = icalcomponent_get_first_property (vevent, ICAL_RECURRENCEID_PROPERTY);
532 prop;
533 prop = icalcomponent_get_next_property (vevent, ICAL_RECURRENCEID_PROPERTY)) {
534 // Must match all-day status of *parent* here!
535 if (_eas2ical_convert_datetime_property (prop, icaltz, parentIsAllDayEvent,
536 _eas2cal_property_get_recurrenceid,
537 icalproperty_set_recurrenceid))
538 needtz = TRUE(!(0));
539 }
540 for (prop = icalcomponent_get_first_property (vevent, ICAL_EXDATE_PROPERTY);
541 prop;
542 prop = icalcomponent_get_next_property (vevent, ICAL_EXDATE_PROPERTY)) {
543 if (_eas2ical_convert_datetime_property (prop, icaltz, isAllDayEvent,
544 icalproperty_get_exdate,
545 icalproperty_set_exdate))
546 needtz = TRUE(!(0));
547 }
548 if (isAllDayEvent) {
549 for (prop = icalcomponent_get_first_property (vevent, ICAL_RRULE_PROPERTY);
550 prop;
551 prop = icalcomponent_get_next_property (vevent, ICAL_RRULE_PROPERTY)) {
552 struct icalrecurrencetype recur = icalproperty_get_rrule (prop);
553 if (!icaltime_is_null_time (recur.until)) {
554 if (icaltz)
555 recur.until = icaltime_convert_to_zone (recur.until,
556 icaltz);
557 recur.until.is_date = 1;
558 icalproperty_set_rrule (prop, recur);
559 }
560 }
561 }
562 return needtz;
563}
564
565/**
566 * Process the <Attendees> element during parsing of an EAS XML document
567 *
568 * @param n
569 * An XML node pointing at the <Attendees> element
570 * @param vevent
571 * The VEVENT iCal component to add the parsed attendees to
572 */
573static void _eas2ical_process_attendees (xmlNodePtr n, icalcomponent* vevent)
574{
575 gchar* value = NULL((void*)0);
576 icalproperty* prop = NULL((void*)0);
577 icalparameter* param = NULL((void*)0);
578
579 xmlNode* attendeeNode = NULL((void*)0);
580
581 g_debug ("Attendees element found in EAS XML");
582
583 for (attendeeNode = n->children; attendeeNode; attendeeNode = attendeeNode->next) {
584 // Variables for attendee properties and parameters
585 gchar* email = NULL((void*)0);
586 gchar* name = NULL((void*)0);
587 icalparameter_partstat partstat = ICAL_PARTSTAT_NONE; // Participatant Status parameter
588 icalparameter_role role = ICAL_ROLE_NONE;
589 xmlNodePtr subNode = NULL((void*)0);
590
591 g_debug ("Attendee element found in EAS XML");
592
593 for (subNode = attendeeNode->children; subNode; subNode = subNode->next) {
594 if (subNode->type == XML_ELEMENT_NODE && g_strcmp0 ( (gchar*) subNode->name, EAS_ELEMENT_ATTENDEE_EMAIL"Attendee_Email") == 0) {
595 if (email) xmlFree (email);
596 email = (gchar*) xmlNodeGetContent (subNode);
597 } else if (subNode->type == XML_ELEMENT_NODE && g_strcmp0 ( (gchar*) subNode->name, EAS_ELEMENT_ATTENDEE_NAME"Attendee_Name") == 0) {
598 if (name) xmlFree (name);
599 name = (gchar*) xmlNodeGetContent (subNode);
600 } else if (subNode->type == XML_ELEMENT_NODE && g_strcmp0 ( (gchar*) subNode->name, EAS_ELEMENT_ATTENDEE_STATUS"Attendee_Status") == 0) {
601 int status = 0;
602 value = (gchar*) xmlNodeGetContent (subNode);
603 status = atoi (value);
604 switch (status) {
605 case 0: // Response unknown
606 case 5: // Not responded
607 partstat = ICAL_PARTSTAT_NEEDSACTION;
608 break;
609 case 2: // Tentative
610 partstat = ICAL_PARTSTAT_TENTATIVE;
611 break;
612 case 3: // Accept
613 partstat = ICAL_PARTSTAT_ACCEPTED;
614 break;
615 case 4: // Decline
616 partstat = ICAL_PARTSTAT_DECLINED;
617 break;
618 default:
619 partstat = ICAL_PARTSTAT_NONE;
620 g_warning ("unrecognised attendee status received");
621 break;
622 }// end switch status
623
624 xmlFree (value);
625 value = NULL((void*)0);
626 } else if (subNode->type == XML_ELEMENT_NODE && !g_strcmp0 ( (gchar*) subNode->name, EAS_ELEMENT_ATTENDEE_TYPE"Attendee_Type")) {
627 int roleValue = 0;
628 value = (gchar*) xmlNodeGetContent (subNode);
629 roleValue = atoi (value);
630 switch (roleValue) {
631 // TODO create an enum for these values
632 case 1: //Required
633 role = ICAL_ROLE_REQPARTICIPANT;
634 break;
635 case 2: //Optional
636 role = ICAL_ROLE_OPTPARTICIPANT;
637 break;
638 case 3: //Resource
639 role = ICAL_ROLE_NONPARTICIPANT;
640 break;
641 default:
642 role = ICAL_ROLE_NONE;
643 g_warning ("unrecognised attendee type received");
644 break;
645 }// end switch type
646
647 xmlFree (value);
648 value = NULL((void*)0);
649 }
650
651 }// end for subNodes
652
653 // Now finally build and add the property, assuming we have at least an e-mail address
654 if (email && strlen (email)) {
655 prop = icalproperty_new_attendee (email);
656
657 if (email && name != NULL((void*)0) && strlen (name)) {
658 param = icalparameter_new_cn (name);
659 icalproperty_add_parameter (prop, param);
660 }
661 if (partstat != ICAL_PARTSTAT_NONE) {
662 param = icalparameter_new_partstat (partstat);
663 icalproperty_add_parameter (prop, param);
664 }
665 if (role != ICAL_ROLE_NONE) {
666 param = icalparameter_new_role (role);
667 icalproperty_add_parameter (prop, param);
668 }
669
670 icalcomponent_add_property (vevent, prop);
671 }
672
673 if (email) xmlFree (email);
674 if (name) xmlFree (name);
675 }
676}
677
678
679/**
680 * Process the <Timezone> element during parsing of an EAS XML document
681 *
682 * @param n
683 * An XML node pointing to the <Recurrence> element
684 * @param vtimezone
685 * The iCalendar VTIMEZONE component to add the parsed timezone properties to
686 * @param tzid
687 * Pointer to a buffer to initialise with the parsed TZID (timezone ID) string
688 * @return
689 icaltimezone after successful parsing or NULL in case of failure, must be freed by caller
690 */
691static icaltimezone* _eas2ical_process_timezone (xmlNodePtr n, icalcomponent* vtimezone, gchar** tzid)
692{
693 gchar* value = NULL((void*)0);
694 icalproperty* prop = NULL((void*)0);
695 xmlChar* timeZoneBase64Buffer = xmlNodeGetContent (n);
696 gsize timeZoneRawBytesSize = 0;
697 guchar* timeZoneRawBytes = g_base64_decode ( (const gchar*) timeZoneBase64Buffer, &timeZoneRawBytesSize);
698 EasTimeZone timeZoneStruct;
699 icaltimezone* icaltz = NULL((void*)0);
700
701 // TODO Check decode of timezone for endianess problems
702
703 if (timeZoneRawBytesSize == sizeof (EasTimeZone)) {
704 memcpy (&timeZoneStruct, timeZoneRawBytes, timeZoneRawBytesSize);
705 g_free (timeZoneRawBytes);
706 timeZoneRawBytes = NULL((void*)0);
707
708 {
709 char *standard = g_utf16_to_utf8 ( (const gunichar2*) timeZoneStruct.StandardName,
710 (sizeof (timeZoneStruct.StandardName) / sizeof (guint16)), NULL((void*)0), NULL((void*)0), NULL((void*)0));
711 char *daylight = g_utf16_to_utf8 ( (const gunichar2*) timeZoneStruct.DaylightName,
712 (sizeof (timeZoneStruct.DaylightName) / sizeof (guint16)), NULL((void*)0), NULL((void*)0), NULL((void*)0));
713 g_debug ("process timezone %s => bias %d, standard bias %d, daylight bias %d, standard '%s', daylight '%s'",
714 timeZoneBase64Buffer,
715 timeZoneStruct.Bias,
716 timeZoneStruct.StandardBias,
717 timeZoneStruct.DaylightBias,
718 standard, daylight);
719 g_free (standard);
720 g_free (daylight);
721 }
722
723 {
724 // Calculate the timezone offsets. See _ical2eas_process_xstandard_xdaylight()
725 // comments for a full explanation of how EAS Bias relates to iCal UTC offsets
726 const gint32 standardUtcOffsetMins = -1 * (timeZoneStruct.Bias + timeZoneStruct.StandardBias);
727 const gint32 daylightUtcOffsetMins = -1 * (timeZoneStruct.Bias + timeZoneStruct.DaylightBias);
728 icalcomponent* xstandard = NULL((void*)0);
729 icalcomponent* xdaylight = NULL((void*)0);
730 struct icalrecurrencetype rrule;
731 struct icaltimetype time;
732
733 if (standardUtcOffsetMins == 0 &&
734 daylightUtcOffsetMins == 0) {
735 // is UTC time zone, ignore it
736 goto done;
737 }
738
739 // Using StandardName as the TZID
740 // (Doesn't matter if it's not an exact description: this field is only used internally
741 // during iCalendar encoding/decoding)
742 // Note: using tzid here rather than value, as we need it elsewhere in this function
743 *tzid = g_utf16_to_utf8 ( (const gunichar2*) timeZoneStruct.StandardName,
744 (sizeof (timeZoneStruct.StandardName) / sizeof (guint16)), NULL((void*)0), NULL((void*)0), NULL((void*)0));
745 // If no StandardName was supplied, we can just use a temporary name instead.
746 // No need to support more than one: the EAS calendar will only have one Timezone
747 // element. And no need to localise, as it's only used internally.
748 if (*tzid == NULL((void*)0) || strlen (*tzid) == 0) {
749 g_free (*tzid);
750 *tzid = g_strdup (ICAL_DEFAULT_TZID"Standard Timezone");
751 }
752 prop = icalproperty_new_tzid (*tzid);
753 icalcomponent_add_property (vtimezone, prop);
754
755
756 //
757 // STANDARD component
758 //
759
760 xstandard = icalcomponent_new (ICAL_XSTANDARD_COMPONENT);
761
762 icalrecurrencetype_clear (&rrule);
763
764 // If timeZoneStruct.StandardDate.Year == 0 we need to convert it into
765 // the start date of a recurring sequence, and add an RRULE.
766 if (timeZoneStruct.StandardDate.Year == 0 &&
767 timeZoneStruct.StandardDate.Month != 0) {
768 _eas2ical_convert_relative_timezone_date (&timeZoneStruct.StandardDate, &rrule);
769 }
770
771 // Add the DTSTART property
772 time = icaltime_null_time();
773 time.year = timeZoneStruct.StandardDate.Year;
774 time.month = timeZoneStruct.StandardDate.Month;
775 time.day = timeZoneStruct.StandardDate.Day;
776 time.hour = timeZoneStruct.StandardDate.Hour;
777 time.minute = timeZoneStruct.StandardDate.Minute;
778 time.second = timeZoneStruct.StandardDate.Second;
779 prop = icalproperty_new_dtstart (time);
780 icalcomponent_add_property (xstandard, prop);
781
782 // Add the RRULE (if required)
783 if (rrule.freq != ICAL_NO_RECURRENCE) {
784 prop = icalproperty_new_rrule (rrule);
785 icalcomponent_add_property (xstandard, prop);
786 }
787
788 // Add TZOFFSETFROM and TZOFFSETTO
789 // Note that libical expects these properties in seconds.
790 prop = icalproperty_new_tzoffsetfrom (daylightUtcOffsetMins * SECONDS_PER_MINUTE);
791 icalcomponent_add_property (xstandard, prop);
792 prop = icalproperty_new_tzoffsetto (standardUtcOffsetMins * SECONDS_PER_MINUTE);
793 icalcomponent_add_property (xstandard, prop);
794
795 value = g_utf16_to_utf8 ( (const gunichar2*) timeZoneStruct.StandardName, (sizeof (timeZoneStruct.StandardName) / sizeof (guint16)), NULL((void*)0), NULL((void*)0), NULL((void*)0));
796 if (value) {
797 if (strlen (value)) {
798 prop = icalproperty_new_tzname (value);
799 icalcomponent_add_property (xstandard, prop);
800 }
801 g_free (value);
802 value = NULL((void*)0);
803 }
804
805 // And now add the STANDARD component to the VTIMEZONE
806 icalcomponent_add_component (vtimezone, xstandard);
807
808
809 //
810 // DAYLIGHT component
811 //
812 // FIXME: How does it indicate that the daylight zone doesn't
813 // really exist?
814 if (timeZoneStruct.DaylightDate.Month != 0) {
815
816 xdaylight = icalcomponent_new (ICAL_XDAYLIGHT_COMPONENT);
817
818 // Reset the RRULE
819 icalrecurrencetype_clear (&rrule);
820
821 // If timeZoneStruct.DaylightDate.Year == 0 we need to convert it into
822 // the start date of a recurring sequence, and add an RRULE.
823 if (timeZoneStruct.DaylightDate.Year == 0) {
824 _eas2ical_convert_relative_timezone_date (&timeZoneStruct.DaylightDate, &rrule);
825 }
826
827 // Add the DTSTART property
828 time = icaltime_null_time();
829 time.year = timeZoneStruct.DaylightDate.Year;
830 time.month = timeZoneStruct.DaylightDate.Month;
831 time.day = timeZoneStruct.DaylightDate.Day;
832 time.hour = timeZoneStruct.DaylightDate.Hour;
833 time.minute = timeZoneStruct.DaylightDate.Minute;
834 time.second = timeZoneStruct.DaylightDate.Second;
835 prop = icalproperty_new_dtstart (time);
836 icalcomponent_add_property (xdaylight, prop);
837
838 // Add the RRULE (if required)
839 if (rrule.freq != ICAL_NO_RECURRENCE) {
840 prop = icalproperty_new_rrule (rrule);
841 icalcomponent_add_property (xdaylight, prop);
842 }
843
844 // Add TZOFFSETFROM and TZOFFSETTO
845 // Note that libical expects these properties in seconds.
846 prop = icalproperty_new_tzoffsetfrom (standardUtcOffsetMins * SECONDS_PER_MINUTE);
847 icalcomponent_add_property (xdaylight, prop);
848 prop = icalproperty_new_tzoffsetto (daylightUtcOffsetMins * SECONDS_PER_MINUTE);
849 icalcomponent_add_property (xdaylight, prop);
850
851 value = g_utf16_to_utf8 ( (const gunichar2*) timeZoneStruct.DaylightName, (sizeof (timeZoneStruct.DaylightName) / sizeof (guint16)), NULL((void*)0), NULL((void*)0), NULL((void*)0));
852 if (value) {
853 if (strlen (value)) {
854 prop = icalproperty_new_tzname (value);
855 icalcomponent_add_property (xdaylight, prop);
856 }
857 g_free (value);
858 value = NULL((void*)0);
859 }
860
861 // And now add the DAYLIGHT component to the VTIMEZONE
862 icalcomponent_add_component (vtimezone, xdaylight);
863 }
864 }
865
866 icaltz = icaltimezone_new();
867 if (icaltz)
868 icaltimezone_set_component(icaltz, vtimezone);
869 } // timeZoneRawBytesSize == sizeof(timeZoneStruct)
870 else {
871 g_critical ("TimeZone BLOB did not match sizeof(EasTimeZone)");
872 }
873
874 done:
875 xmlFree (timeZoneBase64Buffer);
876 return icaltz;
877}
878
879
880/**
881 * Process the <Recurrence> element during parsing of an EAS XML document
882 *
883 * @param n
884 * An XML node pointing to the <Recurrence> element
885 * @param vevent
886 * The iCalendar VEVENT component to add the parsed RRULE property to
887 */
888static void _eas2ical_process_recurrence (xmlNodePtr n, icalcomponent* vevent)
889{
890 gchar* value = NULL((void*)0);
891 icalproperty* prop = NULL((void*)0);
892 xmlNode* subNode = NULL((void*)0);
893 struct icalrecurrencetype recur;
894
895 // Ensure the icalrecurrencetype is null
896 icalrecurrencetype_clear (&recur);
897
898 g_debug ("Recurrence element found in EAS XML");
899
900 for (subNode = n->children; subNode; subNode = subNode->next) {
901 const gchar* elemName = (const gchar*) subNode->name;
902 value = (gchar*) xmlNodeGetContent (subNode);
903
904
905 // nothing to do
906 if (subNode->type != XML_ELEMENT_NODE) {
907 }
908
909 // Type
910 else if (g_strcmp0 (elemName, EAS_ELEMENT_TYPE"Recurrence_Type") == 0) {
911 int typeInt = atoi (value);
912 switch (typeInt) {
913 case 0: // Recurs daily
914 recur.freq = ICAL_DAILY_RECURRENCE;
915 break;
916 case 1: // Recurs weekly
917 recur.freq = ICAL_WEEKLY_RECURRENCE;
918 break;
919 case 2: // Recurs monthly
920 recur.freq = ICAL_MONTHLY_RECURRENCE;
921 break;
922 case 3: // Recurs monthly on the nth day
923 recur.freq = ICAL_MONTHLY_RECURRENCE;
924 break;
925 case 5: // Recurs yearly
926 recur.freq = ICAL_YEARLY_RECURRENCE;
927 break;
928 case 6: // Recurs yearly on the nth day
929 recur.freq = ICAL_YEARLY_RECURRENCE;
930 break;
931 }
932 }
933
934 // Occurrences
935 else if (g_strcmp0 (elemName, EAS_ELEMENT_OCCURRENCES"Recurrence_Occurrences") == 0) {
936 // From [MS-ASCAL]:
937 // The Occurrences element and the Until element (section 2.2.2.18.7) are mutually exclusive. It is
938 // recommended that only one of these elements be included in a Recurrence element (section
939 // 2.2.2.18) in a Sync command request. If both elements are included, then the server MUST respect
940 // the value of the Occurrences element and ignore the Until element.
941 recur.count = atoi (value);
942 recur.until = icaltime_null_time();
943 }
944
945 // Interval
946 else if (g_strcmp0 (elemName, EAS_ELEMENT_INTERVAL"Recurrence_Interval") == 0) {
947 recur.interval = (short) atoi (value);
948 }
949
950 // WeekOfMonth
951 else if (g_strcmp0 (elemName, EAS_ELEMENT_WEEKOFMONTH"Recurrence_WeekOfMonth") == 0) {
952 g_warning ("DATA LOSS: Cannot handle <Calendar><Recurrence><WeekOfMonth> element (value=%d)", atoi (value));
953 }
954
955 // DayOfWeek
956 else if (g_strcmp0 (elemName, EAS_ELEMENT_DAYOFWEEK"Recurrence_DayOfWeek") == 0) {
957 // A recurrence rule can target any combination of the 7 week days.
958 // EAS encodes these as a bit set in a single int value.
959 // libical encodes them as an array (max size 7) of icalrecurrencetype_weekday
960 // enum values.
961 // This block of code converts the former into the latter.
962
963 int dayOfWeek = atoi (value);
964 int index = 0;
965
966 // Note: must use IF for subsequent blocks, not ELSE IF
967 if (dayOfWeek & DAY_OF_WEEK_MONDAY) {
968 recur.by_day[index++] = _eas2ical_make_rrule_by_day_value (ICAL_MONDAY_WEEKDAY, 0);
969 }
970 if (dayOfWeek & DAY_OF_WEEK_TUESDAY) {
971 recur.by_day[index++] = _eas2ical_make_rrule_by_day_value (ICAL_TUESDAY_WEEKDAY, 0);
972 }
973 if (dayOfWeek & DAY_OF_WEEK_WEDNESDAY) {
974 recur.by_day[index++] = _eas2ical_make_rrule_by_day_value (ICAL_WEDNESDAY_WEEKDAY, 0);
975 }
976 if (dayOfWeek & DAY_OF_WEEK_THURSDAY) {
977 recur.by_day[index++] = _eas2ical_make_rrule_by_day_value (ICAL_THURSDAY_WEEKDAY, 0);
978 }
979 if (dayOfWeek & DAY_OF_WEEK_FRIDAY) {
980 recur.by_day[index++] = _eas2ical_make_rrule_by_day_value (ICAL_FRIDAY_WEEKDAY, 0);
981 }
982 if (dayOfWeek & DAY_OF_WEEK_SATURDAY) {
983 recur.by_day[index++] = _eas2ical_make_rrule_by_day_value (ICAL_SATURDAY_WEEKDAY, 0);
984 }
985 if (dayOfWeek & DAY_OF_WEEK_SUNDAY) {
986 recur.by_day[index++] = _eas2ical_make_rrule_by_day_value (ICAL_SUNDAY_WEEKDAY, 0);
987 }
988 }
989
990 // MonthOfYear
991 else if (g_strcmp0 (elemName, EAS_ELEMENT_MONTHOFYEAR"Recurrence_MonthOfYear") == 0) {
992 recur.by_month[0] = (short) atoi (value);
993 }
994
995 // Until
996 else if (g_strcmp0 (elemName, EAS_ELEMENT_UNTIL"Recurrence_Until") == 0) {
997 // From [MS-ASCAL]:
998 // The Occurrences element and the Until element (section 2.2.2.18.7) are mutually exclusive. It is
999 // recommended that only one of these elements be included in a Recurrence element (section
1000 // 2.2.2.18) in a Sync command request. If both elements are included, then the server MUST respect
1001 // the value of the Occurrences element and ignore the Until element.
1002 if (recur.count == 0) {
1003 // fix inclusive vs. exclusive difference by subtracting one second
1004 recur.until = icaltime_from_string (value);
1005 }
1006 }
1007
1008 // DayOfMonth
1009 else if (g_strcmp0 (elemName, EAS_ELEMENT_DAYOFMONTH"Recurrence_DayOfMonth") == 0) {
1010 recur.by_month_day[0] = (short) atoi (value);
1011 }
1012
1013 // CalendarType
1014 else if (g_strcmp0 (elemName, EAS_ELEMENT_CALENDARTYPE"CalendarType") == 0) {
1015 const int calType = atoi (value);
1016
1017 if ( (calType != 0) // Default
1018 &&
1019 (calType != 1)) { // Gregorian
1020 // No way of handling in iCal
1021 g_warning ("DATA LOSS: Encountered a calendar type we can't handle in the <Calendar><Recurrence><CalendarType> element: %d", calType);
1022 }
1023 }
1024
1025 // IsLeapMonth
1026 else if (g_strcmp0 (elemName, EAS_ELEMENT_ISLEAPMONTH"IsLeapMonth") == 0) {
1027 if (atoi (value) == 1) {
1028 // This has nothing to do with Gregorian calendar leap years (ie. 29 days in Feb).
1029 // It only applies to non-Gregorian calendars so can't be handled in iCal.
1030 g_warning ("DATA LOSS: Cannot handle <Calendar><Recurrence><IsLeapMonth> element");
1031 }
1032 }
1033
1034 // FirstDayOfWeek
1035 else if (g_strcmp0 (elemName, EAS_ELEMENT_FIRSTDAYOFWEEK"FirstDayOfWeek") == 0) {
1036 int firstDayOfWeek = atoi (value);
1037
1038 // EAS value is in range 0=Sunday..6=Saturday
1039 // iCal value is in range 0=NoWeekday, 1=Sunday..7=Saturday
1040 recur.week_start = (icalrecurrencetype_weekday) firstDayOfWeek + 1;
1041 }
1042
1043 // Other fields...
1044 else {
1045 g_warning ("DATA LOSS: Unknown element encountered in <Recurrence> element: %s", elemName);
1046 }
1047
1048 xmlFree (value);
1049 }
1050
1051 prop = icalproperty_new_rrule (recur);
1052 icalcomponent_add_property (vevent, prop);
1053}
1054
1055
1056/**
1057 * Process the <Exceptions> element during parsing of an EAS XML document
1058 *
1059 * @param n
1060 * An XML node pointing to the <Exceptions> element
1061 * @param vevent
1062 * The iCalendar VEVENT component to add the parsed items property to
1063 */
1064static GSList* _eas2ical_process_exceptions (xmlNodePtr n, icalcomponent* vevent)
1065{
1066 // This is a bit tricky.
1067 //
1068 // - If the EAS <Exception> element ONLY contains <ExceptionStartTime> and
1069 // <Deleted> (with the latter's value set to 1), it's a simple exception
1070 // that just deletes one of the occurrences of the recurrence (ie. an
1071 // EXDATE in iCal);
1072 //
1073 // - If it just contains an <ExceptionStartTime> (and optionally a <Deleted>
1074 // element with the value 0), it represents an *additional* occurrence of
1075 // the event, with all other attributes identical to those of the recurring
1076 // event (ie. an RDATE in iCal);
1077 //
1078 // - However, if it contains additional elements, it represents an exception
1079 // where properties have been changed (eg. where the start time or subject
1080 // have been changed for one occurrence only). These aren't supported in
1081 // iCal so we have to create a whole new event. We can't do that here (as
1082 // we're still part-way through parsing the event) so we return a list of
1083 // exception event details so they can be processed later. Each exception
1084 // event we return is stored as a hash table of property names/values.
1085 // (So this function returns a list of hash tables.)
1086
1087
1088 gchar* value = NULL((void*)0);
1089 xmlNode* exceptionNode = NULL((void*)0);
1090 xmlNode* subNode = NULL((void*)0);
1091 GSList* listOfNewEventEvents = NULL((void*)0);
1092
1093 g_debug ("Exceptions element found in EAS XML");
1094
1095 // Iterate through the <Exception> elements
1096 for (exceptionNode = n->children; exceptionNode; exceptionNode = exceptionNode->next) {
1097 GHashTable* newEventValues = NULL((void*)0);
1098 gchar* exceptionStartTime = NULL((void*)0);
1099 gboolean deleted = FALSE(0);
1100
1101 if (exceptionNode->type != XML_ELEMENT_NODE)
1102 continue;
1103
1104 // Iterate through each Exception's properties
1105 for (subNode = exceptionNode->children; subNode; subNode = subNode->next) {
1106 const gchar* name;
1107
1108 if (subNode->type != XML_ELEMENT_NODE)
1109 continue;
1110
1111 name = (const gchar*) subNode->name;
1112 value = (gchar*) xmlNodeGetContent (subNode);
1113
1114 if (g_strcmp0 (name, EAS_ELEMENT_DELETED"Exception_Deleted") == 0) {
1115 deleted = (g_strcmp0 (value, EAS_BOOLEAN_TRUE"1") == 0);
1116 } else if (g_strcmp0 (name, EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime") == 0) {
1117 exceptionStartTime = g_strdup (value);
1118 }
1119 // only content of <Data> present in <Body> is required to be stored in hashTable
1120 else if (g_strcmp0 (name, EAS_ELEMENT_BODY"Body") == 0) {
1121 xmlNodePtr bodySubNode = NULL((void*)0);
1122 for (bodySubNode = subNode->children; bodySubNode; bodySubNode = bodySubNode->next) {
1123 if (bodySubNode->type == XML_ELEMENT_NODE && !g_strcmp0 ( (gchar*) bodySubNode->name, EAS_ELEMENT_DATA"Data")) {
1124 if (value) xmlFree (value);
1125 value = (gchar*) xmlNodeGetContent (bodySubNode);
1126 if (newEventValues == NULL((void*)0)) {
1127 newEventValues = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1128 }
1129 g_hash_table_insert (newEventValues, g_strdup (name), g_strdup (value));
1130 break;
1131 }
1132 }
1133 }
1134 // contents of all <Category> present in <Categories> is required to be stored in hashTable
1135 else if (g_strcmp0 (name, EAS_ELEMENT_CATEGORIES"Categories") == 0) {
1136 xmlNodePtr catNode = NULL((void*)0);
1137 GString* categories = g_string_new ("");
1138 int categoryLength, index = 0;
1139 for (catNode = subNode->children; catNode; catNode = catNode->next) {
1140 if (catNode->type == XML_ELEMENT_NODE && !g_strcmp0 ( (gchar*) catNode->name, EAS_ELEMENT_CATEGORY"Category")) {
1141 if (value) xmlFree (value);
1142 value = (gchar*) xmlNodeGetContent (catNode);
1143 for (index = 0, categoryLength = strlen (value); index < categoryLength; index++) {
1144 if (value[index] == ',')
1145 categories = g_string_append (categories, "/,");
1146 else
1147 categories = g_string_append_c (categories, value[index])g_string_append_c_inline (categories, value[index]);
1148 }
1149 categories = g_string_append_c (categories, ',')g_string_append_c_inline (categories, ',');
1150 }
1151
1152 }
1153 categories = g_string_erase (categories, categories->len - 1, -1);
1154 if (newEventValues == NULL((void*)0)) {
1155 newEventValues = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1156 }
1157 g_hash_table_insert (newEventValues, g_strdup (name), g_string_free (categories, FALSE(0)));
1158 } else if (strlen (value) > 0) {
1159 // We've got a non-trivial exception that will
1160 // require adding a new event: build a hash of its values
1161 // and add to the list
1162 if (newEventValues == NULL((void*)0)) {
1163 newEventValues = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1164 }
1165 g_hash_table_insert (newEventValues, g_strdup (name), g_strdup (value));
1166 }
1167
1168 xmlFree (value);
1169 }
1170
1171 // ExceptionStartTime is mandatory so check we've got one
1172 if (exceptionStartTime == NULL((void*)0)) {
1173 g_warning ("DATA LOSS: <Exception> element found with no ExceptionStartTime.");
1174 continue;
1175 }
1176
1177 // If the <Deleted> value is set to 1, it's dead easy: it maps straight onto an
1178 // EXDATE an we can ignore the other elements.
1179 if (deleted) {
1180 // Add an EXDATE property
1181
1182 // I'm ASSUMING here that we add multiple single-value EXDATE properties and
1183 // libical takes care of merging them into one (just as it splits them when
1184 // we're reading an iCal). TODO: check this during testing...
1185 icalproperty* exdate = icalproperty_new_exdate (icaltime_from_string (exceptionStartTime));
1186 icalcomponent_add_property (vevent, exdate); // vevent takes ownership of exdate
1187 }
1188 // If it's not deleted, but the only other element present is ExceptionStartTime,
1189 // then it's an RDATE (i.e. just a one-off recurrence of the same event but not
1190 // included in the regular recurrence sequence)
1191 else if (newEventValues == NULL((void*)0)) {
1192 icalproperty* rdate = NULL((void*)0);
1193
1194 // Same assumption as for EXDATE: that we just add multiple properties
1195 // and libical takes care of merging them. TODO: check this during testing...
1196 struct icaldatetimeperiodtype dtper;
1197 dtper.period = icalperiodtype_null_period();
1198 dtper.time = icaltime_from_string (exceptionStartTime);
1199 rdate = icalproperty_new_rdate (dtper);
1200 icalcomponent_add_property (vevent, rdate); // vevent takes ownership of rdate
1201 }
1202 // Otherwise it's neither an EXDATE or an RDATE: it's a new instance of the
1203 // event with more substantial changes (e.g. start time/end time/subject/etc.
1204 // has changed)
1205 else {
1206 // Add ExceptionStartTime to the hash now too
1207 g_hash_table_insert (newEventValues, g_strdup (EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime"), g_strdup (exceptionStartTime));
1208
1209 // And add the hash table to the list to be returned
1210 // (Using prepend rather than append for efficiency)
1211 // (No need to new the list first, it's allocated during prepend:
1212 // http://developer.gnome.org/glib/stable/glib-Singly-Linked-Lists.html#g-slist-alloc)
1213 listOfNewEventEvents = g_slist_prepend (listOfNewEventEvents, newEventValues);
1214 }
1215
1216 g_free (exceptionStartTime);
1217 }// end of for loop
1218
1219 return listOfNewEventEvents;
1220}
1221
1222
1223/**
1224 * Add additional VEVENT components to the VCALENDAR for non-trivial recurrence exceptions.
1225 *
1226 * EAS supports non-trivial exceptions to a recurrence rule (i.e. just just deleted recurrences,
1227 * but recurrences where field values have changed, such as subject or start time/end time).
1228 * The only way we can support these in iCalendar is to create them as additional VEVENTS.
1229 * So we gather all the properties of these exceptions into a list during parsing (see
1230 * _eas2ical_process_exceptions()) then pass thm to this function which converts them into
1231 * VEVENTS.
1232 *
1233 * We try to maintain the link to the original VEVENT in the UID field: each of the new "child"
1234 * VEVENTS has the original VEVENT's UID with the exception's start time appended, e.g.
1235 *
1236 * Original VEVENT UID: 0123456789ABCDEF
1237 * Child VEVENT UIDs: 0123456789ABCDEF_20110102T103000Z
1238 * 0123456789ABCDEF_20110103T103000Z
1239 * etc.
1240 *
1241 * TODO: look for this format of UID when parsing VEVENTS to try and match them up again.
1242 *
1243 * @param vcalendar
1244 * The outer VCALENDAR component which owns the "parent" VEVENT (and into which
1245 * we will add the new "child" VEVENTs)
1246 * @param vevent
1247 * The "parent" VEVENT, fully converted from EAS XML format
1248 * @param exceptionEvents
1249 * A list of hash tables, each containing the changed field values for a single exception
1250 *
1251 * @return
1252 * TRUE if time zone defition is needed
1253 */
1254static gboolean _eas2ical_add_exception_events (icalcomponent* vcalendar,
1255 icalcomponent* vevent,
1256 GSList* exceptionEvents,
1257 icaltimezone *icaltz,
1258 gboolean parentIsAllDayEvent)
1259{
1260 gboolean needtz = FALSE(0);
1261
1262 if (vcalendar && vevent && exceptionEvents) {
1263 const guint newEventCount = g_slist_length (exceptionEvents);
1264 guint index = 0;
1265
1266 // Iterate through the list adding each exception event in turn
1267 for (index = 0; index < newEventCount; index++) {
1268 icalcomponent* newEvent = icalcomponent_new_clone (vevent);
1269 GHashTable* exceptionProperties = (GHashTable*) g_slist_nth_data (exceptionEvents, index);
1270 icalproperty* prop = NULL((void*)0);
1271 gchar* value = NULL((void*)0);
1272 gboolean newIsAllDayEvent = parentIsAllDayEvent;
1273
1274 if (exceptionProperties == NULL((void*)0)) {
1275 g_warning ("_eas2ical_add_exception_events(): NULL hash table found in exceptionEvents.");
1276 break;
1277 }
1278
1279 // Remove any recurrence (RRULE, RDATE and EXDATE) properies from the new event
1280 while ( (prop = icalcomponent_get_first_property (newEvent, ICAL_RRULE_PROPERTY)) != NULL((void*)0)) {
1281 icalcomponent_remove_property (newEvent, prop);
1282 icalproperty_free (prop);
1283 prop = NULL((void*)0);
1284 }
1285 while ( (prop = icalcomponent_get_first_property (newEvent, ICAL_RDATE_PROPERTY)) != NULL((void*)0)) {
1286 icalcomponent_remove_property (newEvent, prop);
1287 icalproperty_free (prop);
1288 prop = NULL((void*)0);
1289 }
1290 while ( (prop = icalcomponent_get_first_property (newEvent, ICAL_EXDATE_PROPERTY)) != NULL((void*)0)) {
1291 icalcomponent_remove_property (newEvent, prop);
1292 icalproperty_free (prop);
1293 prop = NULL((void*)0);
1294 }
1295
1296 // Form a new UID for the new event as follows:
1297 // {Original event UID}_{Exception start time}
1298 prop = icalcomponent_get_first_property (newEvent, ICAL_UID_PROPERTY); // Retains ownership of the pointer
1299 icalproperty_set_uid (prop, (const gchar*) icalproperty_get_uid (prop));
1300 prop = NULL((void*)0);
1301
1302 // TODO: as we're parsing these from the <Exceptions> element, I think we need to
1303 // add an EXDATE for this ExceptionStartTime. I think ExceptionStartTime identifies
1304 // the recurrence this is *replacing*: StartTime specifies this exception's own
1305 // start time.
1306
1307
1308 // Add the other properties from the hash
1309
1310 // StartTime
1311 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_STARTTIME"StartTime")) != NULL((void*)0)) {
1312 // If this exception has a new start time, use that. Otherwise we default
1313 // to the start time of the original event (as per [MS-ASCAL]).
1314 // TODO: this needs testing: [MS-ASCAL] is a bit ambiguous around
1315 // <StartTime> vs. <ExceptionStartTime>
1316 struct icaltimetype dateTime;
1317
1318 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_DTSTART_PROPERTY)) != NULL((void*)0)) {
1319 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1320 icalproperty_free (prop);
1321 prop = NULL((void*)0);
1322 }
1323 dateTime = icaltime_from_string (value);
1324 prop = icalproperty_new_dtstart (dateTime);
1325 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1326 }
1327
1328 // EndTime
1329 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_ENDTIME"EndTime")) != NULL((void*)0)) {
1330 struct icaltimetype dateTime;
1331
1332 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_DTEND_PROPERTY)) != NULL((void*)0)) {
1333 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1334 icalproperty_free (prop);
1335 prop = NULL((void*)0);
1336 }
1337 dateTime = icaltime_from_string (value);
1338 prop = icalproperty_new_dtend (dateTime);
1339 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1340 }
1341
1342 // Subject
1343 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_SUBJECT"Subject")) != NULL((void*)0)) {
1344 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_SUMMARY_PROPERTY)) != NULL((void*)0)) {
1345 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1346 icalproperty_free (prop);
1347 prop = NULL((void*)0);
1348 }
1349 prop = icalproperty_new_summary (value);
1350 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1351 }
1352
1353 // Location
1354 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_LOCATION"Location")) != NULL((void*)0)) {
1355 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_LOCATION_PROPERTY)) != NULL((void*)0)) {
1356 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1357 icalproperty_free (prop);
1358 prop = NULL((void*)0);
1359 }
1360 prop = icalproperty_new_location (value);
1361 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1362 }
1363
1364 // Categories
1365 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_CATEGORIES"Categories")) != NULL((void*)0)) {
1366 GString* category = g_string_new ("");
1367 int index, categoriesLength;
1368 for (index = 0, categoriesLength = strlen (value); index < categoriesLength; index++) {
1369 if (value[index] == '/' && index + 1 != categoriesLength && value[index + 1] == ',') {
1370 // ignore
1371 } else if (value[index] == ',' && index != 0 && value[index - 1] == '/')
1372 category = g_string_append_c (category, ',')g_string_append_c_inline (category, ',');
1373 else if (value[index] == ',' && index != 0 && value[index - 1] != '/') {
1374
1375
1376 prop = icalproperty_new_categories (category->str);
1377 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1378 g_string_free (category, TRUE(!(0)));
1379 category = g_string_new ("");
1380
1381 } else
1382 category = g_string_append_c (category, index[value])g_string_append_c_inline (category, index[value]);
1383
1384 }
1385
1386 prop = icalproperty_new_categories (category->str);
1387 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1388 g_string_free (category, TRUE(!(0)));
1389 }
1390
1391 // Sensitivity
1392 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_SENSITIVITY"Sensitivity")) != NULL((void*)0)) {
1393 // Clear out any existing property
1394 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_CLASS_PROPERTY)) != NULL((void*)0)) {
1395 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1396 icalproperty_free (prop);
1397 prop = NULL((void*)0);
1398 }
1399 prop = icalproperty_new_class (_eas2ical_convert_sensitivity_to_class (value));
1400 icalcomponent_add_property (newEvent, prop);
1401 }
1402
1403 // BusyStatus
1404 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_BUSYSTATUS"BusyStatus")) != NULL((void*)0)) {
1405 // Clear out any existing property
1406 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_TRANSP_PROPERTY)) != NULL((void*)0)) {
1407 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1408 icalproperty_free (prop);
1409 prop = NULL((void*)0);
1410 }
1411 prop = icalproperty_new_transp (_eas2ical_convert_busystatus_to_transp (value));
1412 icalcomponent_add_property (newEvent, prop);
1413 }
1414
1415 // AllDayEvent
1416 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_ALLDAYEVENT"AllDayEvent")) != NULL((void*)0)) {
1417 // TODO
1418 }
1419
1420 // Reminder
1421 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_REMINDER"Reminder")) != NULL((void*)0)) {
1422 icalcomponent* valarm = NULL((void*)0);
1423 struct icaltriggertype trigger;
1424
1425 // Remove any existing VALARM
1426 if ( (valarm = icalcomponent_get_first_component (newEvent, ICAL_VALARM_COMPONENT)) != NULL((void*)0)) {
1427 icalcomponent_remove_component (newEvent, valarm); // We now have ownership of valarm
1428 icalcomponent_free (valarm);
1429 valarm = NULL((void*)0);
1430 }
1431
1432 valarm = icalcomponent_new (ICAL_VALARM_COMPONENT);
1433
1434 // TODO: find a way of merging this with the other VALARM creation
1435 // code to avoid the duplication
1436
1437 // Build an icaltriggertype structure
1438 trigger = icaltriggertype_from_int (0); // Null the fields first
1439 trigger.duration.is_neg = 1;
1440 trigger.duration.minutes = (unsigned int) atoi (value);
1441
1442 prop = icalproperty_new_action (ICAL_ACTION_DISPLAY);
1443 icalcomponent_add_property (valarm, prop);
1444
1445 prop = icalproperty_new_description (ICAL_DEFAULT_REMINDER_NAME"Reminder"); // TODO: make this configurable
1446 icalcomponent_add_property (valarm, prop);
1447
1448 prop = icalproperty_new_trigger (trigger);
1449 icalcomponent_add_property (valarm, prop);
1450
1451 icalcomponent_add_component (newEvent, valarm);
1452 }
1453
1454 // DtStamp
1455 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_DTSTAMP"DTStamp")) != NULL((void*)0)) {
1456 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_DTSTAMP_PROPERTY)) != NULL((void*)0)) {
1457 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1458 icalproperty_free (prop);
1459 prop = NULL((void*)0);
1460 }
1461 prop = icalproperty_new_dtstamp (icaltime_from_string (value));
1462 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1463 }
1464
1465 // ExceptionStartTime -> convert to RecurrenceID
1466 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime")) != NULL((void*)0)) {
1467 prop = icalproperty_new_recurrenceid(icaltime_from_string (value));
1468 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1469 }
1470
1471 // MeetingStatus
1472 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_MEETINGSTATUS"MeetingStatus")) != NULL((void*)0)) {
1473 // TODO
1474 }
1475
1476 // AppointmentReplyTime
1477 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_APPOINTMENTREPLYTIME"AppointmentReplyTime")) != NULL((void*)0)) {
1478 // TODO
1479 }
1480
1481 // ResponseType
1482 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_RESPONSETYPE"ResponseType")) != NULL((void*)0)) {
1483 // TODO
1484 }
1485
1486 // Body
1487 if ( (value = (gchar*) g_hash_table_lookup (exceptionProperties, EAS_ELEMENT_BODY"Body")) != NULL((void*)0)) {
1488 if ( (prop = icalcomponent_get_first_property (newEvent, ICAL_DESCRIPTION_PROPERTY)) != NULL((void*)0)) {
1489 icalcomponent_remove_property (newEvent, prop); // Now we have ownership of prop
1490 icalproperty_free (prop);
1491 prop = NULL((void*)0);
1492 }
1493 prop = icalproperty_new_description (value);
1494 icalcomponent_add_property (newEvent, prop); // vevent takes ownership
1495 }
1496
1497
1498 // Finally, destroy the hash and replace the list entry with NULL
1499 g_hash_table_destroy (exceptionProperties);
1500 g_slist_nth (exceptionEvents, index)->data = NULL((void*)0);
1501
1502 if (_eas2ical_convert_component (newEvent, icaltz, newIsAllDayEvent, parentIsAllDayEvent))
1503 needtz = TRUE(!(0));
1504
1505 // Add the new event to the parent VCALENDAR
1506 icalcomponent_add_component (vcalendar, newEvent);
1507
1508 // Debug output
1509 g_debug ("Added new exception VEVENT to the VCALENDAR:n%s", icalcomponent_as_ical_string (newEvent));
1510 }
1511 }
1512
1513 return needtz;
1514}
1515
1516
1517
1518/**
1519 * Parse an XML-formatted calendar object received from ActiveSync and return
1520 * it as a serialised iCalendar object.
1521 *
1522 * In the first pass, the XML representation is copied into:
1523 * - an icalcomponent with little to no translations (in particular, all time stamps in
1524 * the original UTC date-time format)
1525 * - meta-information (all-day flag, icaltimezone)
1526 * - list of detached recurrences
1527 *
1528 * Then once the time zone and all-day flag are known, the UTC time stamps
1529 * are adapted:
1530 * - DTSTART/END/RECURRENCE-ID converted to zone,
1531 * stripped down to date-only, TZID added if needed
1532 * - if all-day, RRULE UNTIL is converted to zone and converted to date-only
1533 * (otherwise it remains as UTC)
1534 *
1535 * @param node
1536 * ActiveSync XML <ApplicationData> object containing a calendar.
1537 * @param server_id
1538 * The ActiveSync server ID from the response
1539 */
1540gchar* eas_cal_info_translator_parse_response (xmlNodePtr node, gchar* server_id)
1541{
1542 // Variable for the return value
1543 gchar* result = NULL((void*)0);
1544
1545 // Variable to store the TZID value when decoding a <calendar:Timezone> element
1546 // so we can use it in the rest of the iCal's date/time fields.
1547 gchar* tzid = NULL((void*)0);
1548
1549 // iCalendar objects
1550 struct icaltimetype dateTime;
1551 struct icaltriggertype trigger;
1552
1553
1554 xmlNodePtr n = node;
1555
1556 EasItemInfo* calInfo = NULL((void*)0);
1557
1558 // iCalendar objects
1559 icalcomponent* vcalendar = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
1560 icalcomponent* vevent = icalcomponent_new (ICAL_VEVENT_COMPONENT);
1561 icalcomponent* valarm = icalcomponent_new (ICAL_VALARM_COMPONENT);
1562 icalcomponent* vtimezone = icalcomponent_new (ICAL_VTIMEZONE_COMPONENT);
1563 icaltimezone* icaltz = NULL((void*)0);
1564 icalproperty* prop = NULL((void*)0);
1565 icalparameter* param = NULL((void*)0);
1566 gboolean isAllDayEvent = FALSE(0);
1567 gboolean needtz = FALSE(0);
1568 gchar* organizerName = NULL((void*)0);
1569 gchar* organizerEmail = NULL((void*)0);
1570 GSList* newExceptionEvents = NULL((void*)0);
1571
1572 // TODO: get all these strings into constants/#defines
1573
1574 // TODO: make the PRODID configurable somehow
1575 prop = icalproperty_new_prodid (ICAL_PROPERTY_PRODID"-//Meego//ActiveSyncD 1.0//EN");
1576 icalcomponent_add_property (vcalendar, prop);
1577
1578 prop = icalproperty_new_version (ICAL_PROPERTY_VERSION"2.0");
1579 icalcomponent_add_property (vcalendar, prop);
1580
1581 prop = icalproperty_new_method (ICAL_METHOD_PUBLISH);
1582 icalcomponent_add_property (vcalendar, prop);
1583
1584 for (n = n->children; n; n = n->next) {
1585 if (n->type == XML_ELEMENT_NODE) {
1586 const gchar* name = (const gchar*) (n->name);
1587 gchar* value = NULL((void*)0);
1588
1589 //
1590 // Subject
1591 //
1592 if (g_strcmp0 (name, EAS_ELEMENT_SUBJECT"Subject") == 0) {
1593 value = (gchar*) xmlNodeGetContent (n);
1594 prop = icalproperty_new_summary (value);
1595 icalcomponent_add_property (vevent, prop);
1596 }
1597
1598 //
1599 // StartTime
1600 //
1601 else if (g_strcmp0 (name, EAS_ELEMENT_STARTTIME"StartTime") == 0) {
1602 value = (gchar*) xmlNodeGetContent (n);
1603 dateTime = icaltime_from_string (value);
1604 prop = icalproperty_new_dtstart (dateTime);
1605 icalcomponent_add_property (vevent, prop);
1606 }
1607
1608 //
1609 // EndTime
1610 //
1611 else if (g_strcmp0 (name, EAS_ELEMENT_ENDTIME"EndTime") == 0) {
1612 value = (gchar*) xmlNodeGetContent (n);
1613 dateTime = icaltime_from_string (value);
1614 prop = icalproperty_new_dtend (dateTime);
1615 icalcomponent_add_property (vevent, prop);
1616 }
1617
1618 //
1619 // DtStamp
1620 //
1621 else if (g_strcmp0 (name, EAS_ELEMENT_DTSTAMP"DTStamp") == 0) {
1622 value = (gchar*) xmlNodeGetContent (n);
1623 dateTime = icaltime_from_string (value);
1624 prop = icalproperty_new_dtstamp (dateTime);
1625 icalcomponent_add_property (vevent, prop);
1626 }
1627
1628 //
1629 // UID
1630 //
1631 else if (g_strcmp0 (name, EAS_ELEMENT_UID"UID") == 0) {
1632 value = (gchar*) xmlNodeGetContent (n);
1633 prop = icalproperty_new_uid (value);
1634 icalcomponent_add_property (vevent, prop);
1635 }
1636
1637 //
1638 // Location
1639 //
1640 else if (g_strcmp0 (name, EAS_ELEMENT_LOCATION"Location") == 0) {
1641 value = (gchar*) xmlNodeGetContent (n);
1642 prop = icalproperty_new_location (value);
1643 icalcomponent_add_property (vevent, prop);
1644 }
1645
1646 //
1647 // Body
1648 //
1649 else if (g_strcmp0 (name, EAS_ELEMENT_BODY"Body") == 0) {
1650 xmlNodePtr subNode = NULL((void*)0);
1651 for (subNode = n->children; subNode; subNode = subNode->next) {
1652 if (subNode->type == XML_ELEMENT_NODE && !g_strcmp0 ( (gchar*) subNode->name, EAS_ELEMENT_DATA"Data")) {
1653 if (value) xmlFree (value);
1654 value = (gchar*) xmlNodeGetContent (subNode);
1655 prop = icalproperty_new_description (value);
1656 icalcomponent_add_property (vevent, prop);
1657 break;
1658 }
1659 }
1660 }
1661
1662 //
1663 // Sensitivity
1664 //
1665 else if (g_strcmp0 (name, EAS_ELEMENT_SENSITIVITY"Sensitivity") == 0) {
1666 value = (gchar*) xmlNodeGetContent (n);
1667 prop = icalproperty_new_class (_eas2ical_convert_sensitivity_to_class (value));
1668 icalcomponent_add_property (vevent, prop);
1669 }
1670
1671 //
1672 // BusyStatus
1673 //
1674 else if (g_strcmp0 (name, EAS_ELEMENT_BUSYSTATUS"BusyStatus") == 0) {
1675 value = (gchar*) xmlNodeGetContent (n);
1676 prop = icalproperty_new_transp (_eas2ical_convert_busystatus_to_transp (value));
1677 icalcomponent_add_property (vevent, prop);
1678 }
1679
1680 //
1681 // Categories
1682 //
1683 else if (g_strcmp0 (name, EAS_ELEMENT_CATEGORIES"Categories") == 0) {
1684 xmlNode* catNode = NULL((void*)0);
1685 for (catNode = n->children; catNode; catNode = catNode->next) {
1686 if (catNode->type != XML_ELEMENT_NODE)
1687 continue;
1688
1689 if (value) xmlFree (value);
1690 value = (gchar*) xmlNodeGetContent (catNode);
1691 prop = icalproperty_new_categories (value);
1692 icalcomponent_add_property (vevent, prop);
1693 }
1694 }
1695
1696 //
1697 // Reminder
1698 //
1699 else if (g_strcmp0 (name, EAS_ELEMENT_REMINDER"Reminder") == 0) {
1700 value = (gchar*) xmlNodeGetContent (n);
1701
1702 // Build an icaltriggertype structure
1703 trigger = icaltriggertype_from_int (0); // Null the fields first
1704 trigger.duration.is_neg = 1;
1705 trigger.duration.minutes = (unsigned int) atoi (value);
1706
1707 prop = icalproperty_new_action (ICAL_ACTION_DISPLAY);
1708 icalcomponent_add_property (valarm, prop);
1709
1710 prop = icalproperty_new_description (ICAL_DEFAULT_REMINDER_NAME"Reminder"); // TODO: make this configurable
1711 icalcomponent_add_property (valarm, prop);
1712
1713 prop = icalproperty_new_trigger (trigger);
1714 icalcomponent_add_property (valarm, prop);
1715 }
1716
1717 //
1718 // AllDayEvent
1719 //
1720 else if (g_strcmp0 (name, EAS_ELEMENT_ALLDAYEVENT"AllDayEvent") == 0) {
1721 value = (gchar*) xmlNodeGetContent (n);
1722 isAllDayEvent = atoi (value) == 1;
1723 }
1724
1725 //
1726 // OrganizerName
1727 //
1728 else if (g_strcmp0 (name, EAS_ELEMENT_ORGANIZER_NAME"Organizer_Name") == 0) {
1729 organizerName = (gchar*) xmlNodeGetContent (n);
1730 // That's all for now: deal with it after the loop completes so we
1731 // have both OrganizerName and OrganizerEmail
1732 }
1733
1734 //
1735 // OrganizerEmail
1736 //
1737 else if (g_strcmp0 (name, EAS_ELEMENT_ORGANIZER_EMAIL"Organizer_Email") == 0) {
1738 organizerEmail = (gchar*) xmlNodeGetContent (n);
1739 // That's all for now: deal with it after the loop completes so we
1740 // have both OrganizerName and OrganizerEmail
1741 }
1742
1743 //
1744 // Attendees
1745 //
1746 else if (g_strcmp0 (name, EAS_ELEMENT_ATTENDEES"Attendees") == 0) {
1747 _eas2ical_process_attendees (n, vevent);
1748 }
1749
1750 //
1751 // TimeZone
1752 //
1753 else if (g_strcmp0 (name, EAS_ELEMENT_TIMEZONE"TimeZone") == 0) {
1754 icaltz = _eas2ical_process_timezone (n, vtimezone, &tzid);
1755 }
1756
1757 //
1758 // Recurrence
1759 //
1760 else if (g_strcmp0 (name, EAS_ELEMENT_RECURRENCE"Recurrence") == 0) {
1761 _eas2ical_process_recurrence (n, vevent);
1762 }
1763
1764 //
1765 // Exceptions
1766 //
1767 else if (g_strcmp0 (name, EAS_ELEMENT_EXCEPTIONS"Exceptions") == 0) {
1768 newExceptionEvents = _eas2ical_process_exceptions (n, vevent);
1769 // This is dealt with below...
1770 }
1771
1772 //
1773 // Unmapped data fields
1774 //
1775 else {
1776 // Build a new custom property called X-MEEGO-ACTIVESYNCD-{ElementName}
1777 gchar* propertyName = g_strconcat (ICAL_EXTENSION_PROPERTY_PREFIX"X-MEEGO-ACTIVESYNCD-", name, NULL((void*)0));
1778 value = (gchar*) xmlNodeGetContent (n);
1779 prop = icalproperty_new (ICAL_X_PROPERTY);
1780
1781 g_debug ("Found EAS element that doesn't map to a VEVENT property. Creating X property %s:%s", propertyName, value);
1782
1783 icalproperty_set_x_name (prop, propertyName);
1784 icalproperty_set_value (prop, icalvalue_new_from_string (ICAL_X_VALUE, value));
1785 icalcomponent_add_property (vevent, prop);
1786 g_free (propertyName);
1787 propertyName = NULL((void*)0);
1788 }
1789
1790 if (value) xmlFree (value);
1791 value = NULL((void*)0);
1792 }
1793 }
1794
1795 // Deal with OrganizerName and OrganizerEmail
1796 if (organizerEmail) {
1797 prop = icalproperty_new_organizer (organizerEmail);
1798 xmlFree (organizerEmail);
1799 organizerEmail = NULL((void*)0);
1800
1801 if (organizerName) {
1802 param = icalparameter_new_cn (organizerName);
1803 icalproperty_add_parameter (prop, param);
1804 xmlFree (organizerName);
1805 organizerName = NULL((void*)0);
1806 }
1807
1808 icalcomponent_add_property (vevent, prop);
1809 }
1810
1811 // Check organizerName again, so we free it if we had a name but no e-mail
1812 if (organizerName) {
1813 // TODO: Is there any way we can use the name without the e-mail address?
1814 g_warning ("OrganizerName element found but no OrganizerEmail");
1815 xmlFree (organizerName);
1816 organizerName = NULL((void*)0);
1817 }
1818
1819 // fix parent event
1820 if (_eas2ical_convert_component (vevent, icaltz, isAllDayEvent, isAllDayEvent))
1821 needtz = TRUE(!(0));
1822
1823 // Add the subcomponents to their parent components
1824 icalcomponent_add_component (vcalendar, vevent);
1825 if (icalcomponent_count_properties (valarm, ICAL_ANY_PROPERTY) > 0) {
1826 icalcomponent_add_component (vevent, valarm);
1827 }
1828
1829 // Now handle any non-trivial exception events we found in the <Exceptions> element
1830 if (newExceptionEvents) {
1831 _eas2ical_add_exception_events (vcalendar, vevent, newExceptionEvents, icaltz, isAllDayEvent);
1832 // _eas2ical_add_exception_events() destroys the hash tables as it goes
1833 // so all we need to do here is free the list
1834 g_slist_free (newExceptionEvents);
1835
1836 // remove synthesized parent (see eas_cal_info_translator_parse_request())
1837 if (!g_strcmp0 (icalcomponent_get_summary (vevent), ACTIVESYNCD_PSEUDO_EVENT"[[activesyncd pseudo event - ignore me]]") &&
1838 g_getenv ("EAS_DEBUG_DETACHED_RECURRENCES") == NULL((void*)0)) {
1839 icalcomponent_remove_component (vcalendar, vevent);
1840 icalcomponent_free (vevent);
1841 vevent = NULL((void*)0);
1842 }
1843 }
1844
1845 if (needtz)
1846 icalcomponent_add_component (vcalendar, icalcomponent_new_clone (vtimezone));
1847
1848 // Now insert the server ID and iCalendar into an EasCalInfo object and serialise it
1849 calInfo = eas_item_info_new();
1850 calInfo->data = (gchar*) icalcomponent_as_ical_string_r (vcalendar); // Ownership passes to the EasCalInfo
1851 calInfo->server_id = (gchar*) server_id;
1852 if (!eas_item_info_serialise (calInfo, &result)) {
1853 // TODO: log error
1854 result = NULL((void*)0);
1855 }
1856
1857 // Free the EasCalInfo GObject
1858 g_object_unref (calInfo);
1859
1860 // Free the libical components
1861 // (It's not clear if freeing a component also frees its children, but in any case
1862 // some of these (e.g. vtimezone & valarm) won't have been added as children if they
1863 // weren't present in the XML.)
1864 // Note: the libical examples show that a property doesn't have to be freed once added to a component
1865 icalcomponent_free (valarm);
1866 icalcomponent_free (vevent);
1867 icalcomponent_free (vcalendar);
1868 // icaltimezone_free() below will free the component if it uses it
1869 if (!icaltz || vtimezone != icaltimezone_get_component (icaltz))
1870 icalcomponent_free (vtimezone);
1871 g_free (tzid);
1872 if (icaltz)
1873 icaltimezone_free (icaltz, TRUE(!(0)));
1874
1875 return result;
1876}
1877
1878/**
1879 * Convert icaltimetype to string in UTC format. Date-only values are set
1880 * to occur at 00:00:00 of the given day.
1881 *
1882 * @param tt
1883 * the value which needs to be converted, either defined in UTC or relative to icaltz
1884 * @param icaltz
1885 * the default time zone for the event (see eas_cal_info_translator_parse_request()
1886 * comment about events with more than one time zone)
1887 * @return
1888 * allocated string, caller must free it *using free()* (not g_free())
1889 */
1890static char *_ical2eas_convert_icaltime_to_utcstr(icaltimetype tt, const icaltimezone* icaltz)
1891{
1892 char* timestamp = NULL((void*)0);
1893
1894 // first tell libical what we know about the time zone
1895 if (icaltz && !icaltime_is_utc(tt))
1896 tt = icaltime_set_timezone (&tt, icaltz);
1897
1898 // then make it a date-time value
1899 tt.is_date = 0;
1900
1901 // finally convert to UTC
1902 tt = icaltime_convert_to_zone(tt, icaltimezone_get_utc_timezone());
1903
1904 // don't depend on libical string buffer, allocate anew
1905 timestamp = icaltime_as_ical_string_r (tt);
1906 return timestamp;
1907}
1908
1909/**
1910 * Process the RRULE (recurrence rule) property during parsing of an iCalendar VEVENT component
1911 *
1912 * @param prop
1913 * Pointer to the RRULE property
1914 * @param appData
1915 * Pointer to the <ApplicationData> element to add parsed elements to
1916 */
1917static void _ical2eas_process_rrule (icalproperty* prop, xmlNodePtr appData, struct icaltimetype *startTime,
1918 const icaltimezone* icaltz)
1919{
1920 // Get the iCal RRULE property
1921 struct icalrecurrencetype rrule = icalproperty_get_rrule (prop);
1922
1923 // Create a new <Recurrence> element to contain the recurrence sub-elements
1924 xmlNodePtr recurNode = xmlNewChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_RECURRENCE"Recurrence", NULL((void*)0));
1925
1926 // Other declarations
1927 int recurType = 0;
1928 gchar* xmlValue = NULL((void*)0);
1929 int index = 0;
1930 guint dayOfWeek = 0;
1931 gint weekOfMonth = 0;
1932 gint monthOfYear = 0;
1933 gint dayOfMonth = 0;
1934
1935 //
1936 // Type element
1937 //
1938 switch (rrule.freq) {
1939 case ICAL_SECONDLY_RECURRENCE:
1940 case ICAL_MINUTELY_RECURRENCE:
1941 case ICAL_HOURLY_RECURRENCE:
1942 g_warning ("DATA LOSS: cannot encode secondly/minutely/hourly recurrence in EAS.");
1943 break;
1944 case ICAL_DAILY_RECURRENCE:
1945 recurType = 0;
1946 break;
1947 case ICAL_WEEKLY_RECURRENCE:
1948 recurType = 1;
1949 break;
1950 case ICAL_MONTHLY_RECURRENCE:
1951 recurType = 2;
1952 break;
1953 case ICAL_YEARLY_RECURRENCE:
1954 recurType = 5;
1955 break;
1956 case ICAL_NO_RECURRENCE:
1957 default:
1958 g_warning ("RRULE with no recurrence type.");
1959 break;
1960 }
1961 // Note: don't add this to the XML yet: if we encounter an "nth day in the month" value
1962 // blow we need to change this
1963
1964 //
1965 // COUNT & UNTIL
1966 //
1967 // Note: count and until are mutually exclusive in both formats, with count taking precedence
1968 if (rrule.count) {
1969 // EAS specifies a maximum value of 999 for the Occurrences element
1970 if (rrule.count > 999) {
1971 g_warning ("DATA LOSS: RRULE had recurrence count of %d, maximum is 999.", rrule.count);
1972 rrule.count = 999;
1973 }
1974 xmlValue = g_strdup_printf ("%d", rrule.count);
1975 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_OCCURRENCES"Recurrence_Occurrences", (const xmlChar*) xmlValue);
1976 g_free (xmlValue);
1977 xmlValue = NULL((void*)0);
1978 } else if (!icaltime_is_null_time (rrule.until)) {
1979 /* Exchange seems to have exclusive end date, while iCalendar is inclusive. Add one second. */
1980 struct icaltimetype until = rrule.until;
1981 char *modified = _ical2eas_convert_icaltime_to_utcstr(until, icaltz);
1982 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_UNTIL"Recurrence_Until", (const xmlChar*)modified);
1983 free (modified);
1984 }
1985
1986 //
1987 // INTERVAL
1988 //
1989 if (rrule.interval) {
1990 // EAS specifies a maximum value of 999 for the Interval element
1991 if (rrule.interval > 999) {
1992 g_warning ("DATA LOSS: RRULE had recurrence interval of %d, maximum is 999.", rrule.interval);
1993 rrule.interval = 999;
1994 }
1995 // Only write the Interval element if it's greater than 1;
1996 // 1 is te default (i.e. every day)
1997 if (rrule.interval > 1) {
1998 xmlValue = g_strdup_printf ("%d", rrule.interval);
1999 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_INTERVAL"Recurrence_Interval", (const xmlChar*) xmlValue);
2000 g_free (xmlValue);
2001 xmlValue = NULL((void*)0);
2002 }
2003 }
2004
2005 //
2006 // BYDAY
2007 //
2008 // icalrecurrencetype arrays are terminated with ICAL_RECURRENCE_ARRAY_MAX unless they're full
2009 for (index = 0;
2010 (index < ICAL_BY_DAY_SIZE7*(56 -1)+1) && (rrule.by_day[index] != ICAL_RECURRENCE_ARRAY_MAX);
2011 index++) {
2012 enum icalrecurrencetype_weekday icalDayOfWeek = icalrecurrencetype_day_day_of_week (rrule.by_day[index]);
2013 gint icalDayPosition = icalrecurrencetype_day_position (rrule.by_day[index]);
2014
2015 switch (icalDayOfWeek) {
2016 case ICAL_SUNDAY_WEEKDAY:
2017 dayOfWeek |= DAY_OF_WEEK_SUNDAY;
2018 break;
2019 case ICAL_MONDAY_WEEKDAY:
2020 dayOfWeek |= DAY_OF_WEEK_MONDAY;
2021 break;
2022 case ICAL_TUESDAY_WEEKDAY:
2023 dayOfWeek |= DAY_OF_WEEK_TUESDAY;
2024 break;
2025 case ICAL_WEDNESDAY_WEEKDAY:
2026 dayOfWeek |= DAY_OF_WEEK_WEDNESDAY;
2027 break;
2028 case ICAL_THURSDAY_WEEKDAY:
2029 dayOfWeek |= DAY_OF_WEEK_THURSDAY;
2030 break;
2031 case ICAL_FRIDAY_WEEKDAY:
2032 dayOfWeek |= DAY_OF_WEEK_FRIDAY;
2033 break;
2034 case ICAL_SATURDAY_WEEKDAY:
2035 dayOfWeek |= DAY_OF_WEEK_SATURDAY;
2036 break;
2037 case ICAL_NO_WEEKDAY:
2038 default:
2039 g_warning ("Found by-day RRULE with an empty day value");
2040 break;
2041 }
2042
2043 // Now process the position part
2044 if (icalDayPosition != 0) {
2045 if (icalDayPosition < -1) {
2046 g_warning ("DATA LOSS: EAS cannot encode RRULE position value of %d", icalDayPosition);
2047 // For now, convert all large naegative values (meaning nth from the end of the month)
2048 // to 5 (meaning last of the month)
2049 icalDayPosition = 5;
2050 } else if (icalDayPosition == -1) {
2051 // Convert to the equivalent EAS value
2052 // (both mean "last instance in the month")
2053 icalDayPosition = 5;
2054 }
2055
2056 // Check if we've already processed a position part from one of the other recurrence days
2057 // (EAS has no way of encoding different position values for different days)
2058 if (weekOfMonth && (weekOfMonth != icalDayPosition)) {
2059 g_warning ("DATA LOSS: Position %d already stored for this recurrence; ignoring value of %d", weekOfMonth, icalDayPosition);
2060 } else {
2061 weekOfMonth = icalDayPosition;
2062 }
2063 }
2064 }// end of for loop
2065
2066 if (dayOfWeek) {
2067 //g_debug("RECURRENCE: DayOfWeek value = %d (0x%08X)", dayOfWeek, dayOfWeek);
2068 xmlValue = g_strdup_printf ("%d", dayOfWeek);
2069 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_DAYOFWEEK"Recurrence_DayOfWeek", (const xmlChar*) xmlValue);
2070 g_free (xmlValue);
2071 xmlValue = NULL((void*)0);
2072 }
2073 if (weekOfMonth) {
2074 // Set the Type value to 3 ("Recurs monthly on the nth day")
2075 recurType = 3;
2076
2077 xmlValue = g_strdup_printf ("%d", weekOfMonth);
2078 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_WEEKOFMONTH"Recurrence_WeekOfMonth", (const xmlChar*) xmlValue);
2079 g_free (xmlValue);
2080 xmlValue = NULL((void*)0);
2081 }
2082
2083 // And now we can add the Type element too
2084 xmlValue = g_strdup_printf ("%d", recurType);
2085 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_TYPE"Recurrence_Type", (const xmlChar*) xmlValue);
2086 g_free (xmlValue);
2087 xmlValue = NULL((void*)0);
2088
2089 //
2090 // WKST
2091 //
2092 /*
2093 if (rrule.week_start)
2094 {
2095 // EAS value is 0=Sunday..6=Saturday
2096 // libical value is 0=NoDay, 1=Sunday..7=Saturday
2097 xmlValue = g_strdup_printf("%d", rrule.week_start - 1);
2098 xmlNewTextChild(recurNode, NULL, (const xmlChar*)EAS_NAMESPACE_CALENDAR EAS_ELEMENT_FIRSTDAYOFWEEK, (const xmlChar*)xmlValue);
2099 g_free(xmlValue); xmlValue = NULL;
2100 } */
2101
2102 //
2103 // BYMONTH
2104 //
2105 for (index = 0;
2106 (index < ICAL_BY_MONTH_SIZE14) && (rrule.by_month[index] != ICAL_RECURRENCE_ARRAY_MAX);
2107 index++) {
2108 if (monthOfYear == 0) {
2109 monthOfYear = rrule.by_month[index];
2110 } else {
2111 // We've already set monthOfyear: EAS only supports a single monthly recurrence
2112 // (unlike days where we can repeat on many days of the week)
2113 g_warning ("DATA LOSS: Already set to recur on month %d, discarding recurrence info for month %d", monthOfYear, rrule.by_month[index]);
2114 }
2115 }
2116
2117 if (monthOfYear <= 0 && (recurType == 5 || recurType == 6)) {
2118 //if we have yearly recurrence, it is mandatory to have a month item - get it from startTime
2119 monthOfYear = startTime->month;
2120 }
2121
2122 if (monthOfYear > 0) {
2123 xmlValue = g_strdup_printf ("%d", monthOfYear);
2124 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_MONTHOFYEAR"Recurrence_MonthOfYear", (const xmlChar*) xmlValue);
2125 g_free (xmlValue);
2126 xmlValue = NULL((void*)0);
2127 }
2128
2129 //
2130 // BYMONTHDAY
2131 //
2132 for (index = 0;
2133 (index < ICAL_BY_MONTHDAY_SIZE32) && (rrule.by_month_day[index] != ICAL_RECURRENCE_ARRAY_MAX);
2134 index++) {
2135 if (dayOfMonth == 0) {
2136 dayOfMonth = rrule.by_month_day[index];
2137 } else {
2138 // EAS only supports a single occurrence of DayOfMonth
2139 g_warning ("DATA LOSS: Already set to recur on day %d of the month, discarding recurrence info for day %d of month", dayOfMonth, rrule.by_month_day[index]);
2140 }
2141 }
2142 if (dayOfMonth <= 0 && (recurType == 2 || recurType == 5)) {
2143 //if we have monthly or yearly recurrence, but have not yet set the day, we need to do this
2144 //get it from start date
2145 dayOfMonth = startTime->day;
2146 }
2147
2148
2149
2150 if (dayOfMonth > 0) {
2151 xmlValue = g_strdup_printf ("%d", dayOfMonth);
2152 xmlNewTextChild (recurNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_DAYOFMONTH"Recurrence_DayOfMonth", (const xmlChar*) xmlValue);
2153 g_free (xmlValue);
2154 xmlValue = NULL((void*)0);
2155 }
2156
2157
2158}
2159
2160static void set_xml_body_text (xmlNodePtr appData, const char *text)
2161{
2162 xmlNodePtr bodyNode = xmlNewChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_AIRSYNCBASE"airsyncbase:" EAS_ELEMENT_BODY"Body", NULL((void*)0));
2163 xmlNewTextChild (bodyNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_AIRSYNCBASE"airsyncbase:" EAS_ELEMENT_BODY_TYPE"Type", (const xmlChar*) EAS_BODY_TYPE_PLAINTEXT"1");
2164 xmlNewTextChild (bodyNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_AIRSYNCBASE"airsyncbase:" EAS_ELEMENT_TRUNCATED"Truncated", (const xmlChar*) EAS_BOOLEAN_FALSE"0");
2165 xmlNewTextChild (bodyNode, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_AIRSYNCBASE"airsyncbase:" EAS_ELEMENT_DATA"Data", (const xmlChar*) text);
2166 // All other fields are optional
2167}
2168
2169
2170/**
2171 * Ensure that all XML properties are set. Otherwise removing
2172 * properties is not possible. It would be nice if this could be
2173 * limited to properties which exist on the server, but the daemon
2174 * doesn't track that information.
2175 */
2176static void
2177set_missing_calendar_properties (xmlNodePtr node, gboolean exception)
2178{
2179 /*
2180 * The default values must match the iCalendar 2.0 defaults,
2181 * so that a missing iCalendar 2.0 property leads to the right
2182 * explicit value in XML.
2183 */
2184 static const struct {
2185 const char *name;
2186 const char *def;
2187 gboolean notForExceptions;
2188 } elements[] = {
2189 { EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_SUBJECT"Subject", "" },
2190 /* no useful defaults for start and end time */
2191 /* EAS_NAMESPACE_CALENDAR EAS_ELEMENT_STARTTIME, */
2192 /* EAS_NAMESPACE_CALENDAR EAS_ELEMENT_ENDTIME, */
2193 { EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_LOCATION"Location", "" },
2194 { EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_SENSITIVITY"Sensitivity", EAS_SENSITIVITY_NORMAL"0" },
2195 { EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_BUSYSTATUS"BusyStatus", EAS_BUSYSTATUS_BUSY"2" },
2196
2197 /* can only be set on parent event */
2198 { EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ALLDAYEVENT"AllDayEvent", "0", TRUE(!(0)) },
2199
2200 /*
2201 * organizer information is added back by Exchange
2202 * anyway (by setting the calendar owner), so don't
2203 * bother sending empty properties
2204 */
2205 /* EAS_NAMESPACE_CALENDAR EAS_ELEMENT_ORGANIZER_EMAIL, */
2206 /* EAS_NAMESPACE_CALENDAR EAS_ELEMENT_ORGANIZER_NAME, */
2207
2208 /* adding <Reminder> doesn't seem to be necessary to remove a reminder */
2209 /* EAS_NAMESPACE_CALENDAR EAS_ELEMENT_REMINDER */
2210
2211 /* Can be set like a text element, but not on
2212 exceptions. <Categories> cannot be added without
2213 entries to an exception (which is allowed for the
2214 parent). Has the effect that exceptions cannot
2215 remove the categories of their parent. Attendees
2216 also cannot differ from the parent.
2217 */
2218 { EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_CATEGORIES"Categories", "", TRUE(!(0)) },
2219 { EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ATTENDEES"Attendees", "", TRUE(!(0)) },
2220
2221 /*
2222 * removing a recurrence rule is more difficult, not
2223 * currently supported: a <Type> has to be set, but there
2224 * is no value for "does not recur"
2225 */
2226 /* { EAS_NAMESPACE_CALENDAR EAS_ELEMENT_RECURRENCE, "" }, */
2227
2228 /*
2229 * An empty <Exceptions> element is okay, but doesn't
2230 * help for removing EXDATE or detached recurrence: we
2231 * would have to send an explicit <Exception> with
2232 * <Exception_Deleted>0 and the right
2233 * <Exception_StartTime> to reset an exception on the
2234 * server. The daemon doesn't know about stored
2235 * exceptions and the iCalendar 2.0 item doesn't tell
2236 * us, so we can't do that here.
2237 *
2238 * TODO BMC #24290: The solution might be to add special
2239 * X-ACTIVESYNCD-OLD-EXDATE values when sending data to
2240 * the local client. Assuming that we get them back, we
2241 * could then add the right <Exception> elements.
2242 */
2243 /* { EAS_NAMESPACE_CALENDAR EAS_ELEMENT_EXCEPTIONS, "" }, */
2244 { NULL((void*)0), NULL((void*)0) }
2245 };
2246 int i;
2247
2248 for (i = 0; elements[i].name; i++)
2249 if ((!exception || !elements[i].notForExceptions) &&
2250 !is_element_set (node, elements[i].name))
2251 xmlNewTextChild (node, NULL((void*)0), (const xmlChar *)elements[i].name, (const xmlChar *)elements[i].def);
2252
2253 /* special case for body */
2254 if (!is_element_set (node, EAS_NAMESPACE_AIRSYNCBASE"airsyncbase:" EAS_ELEMENT_BODY"Body"))
2255 set_xml_body_text (node, "");
2256}
2257
2258/**
2259 * Process the VEVENT component during parsing of an iCalendar
2260 *
2261 * @param vevent
2262 * Pointer to the iCalendar VEVENT component
2263 * @param appData
2264 * Pointer to the <ApplicationData> element to add parsed elements to
2265 */
2266static void _ical2eas_process_vevent (icalcomponent* vevent, xmlNodePtr appData, icaltimezone* icaltz, gboolean exception)
2267{
2268 if (vevent) {
2269 xmlNodePtr categories = NULL((void*)0);
2270 xmlNodePtr exceptions = NULL((void*)0);
2271 xmlNodePtr attendees = NULL((void*)0);
2272 //xmlNodePtr icalExtns = NULL;
2273 struct icaltimetype startTime, endTime;
2274 icalproperty* prop;
2275
2276 // Should be set when iterating over properties, but better don't
2277 // rely on it and initialize upfront before checking for "all day"
2278 // property after the loop. Keeps static code analysis happy.
2279 memset (&startTime, 0, sizeof (startTime));
2280 memset (&endTime, 0, sizeof (endTime));
2281
2282 for (prop = icalcomponent_get_first_property (vevent, ICAL_ANY_PROPERTY);
2283 prop;
2284 prop = icalcomponent_get_next_property (vevent, ICAL_ANY_PROPERTY)) {
2285 const icalproperty_kind prop_type = icalproperty_isa (prop);
2286 switch (prop_type) {
2287 // SUMMARY
2288 case ICAL_SUMMARY_PROPERTY:
2289 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_SUBJECT"Subject", (const xmlChar*) icalproperty_get_summary (prop));
2290 break;
2291
2292 // DTSTAMP
2293 case ICAL_DTSTAMP_PROPERTY: {
2294 gchar* modified = NULL((void*)0);
2295 const gchar* timestamp = icalproperty_get_value_as_string (prop);
2296 if (!g_str_has_suffix (timestamp, "Z")) {
2297 modified = g_strconcat (timestamp, "Z", NULL((void*)0));
2298 } else {
2299 modified = g_strdup (timestamp);
2300 }
2301 if (!exception)
2302 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_DTSTAMP"DTStamp", (const xmlChar*) modified);
2303 g_debug ("dtstamp cleanup");
2304 g_free (modified);
2305 }
2306 break;
2307
2308 // DTSTART
2309 case ICAL_DTSTART_PROPERTY: {
2310 struct icaltimetype tt;
2311 char* modified = NULL((void*)0);
2312
2313 //get start time, convert it to UTC and suffix Z onto it
2314 tt = icalproperty_get_dtstart (prop);
2315 modified = _ical2eas_convert_icaltime_to_utcstr(tt, icaltz);
2316 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_STARTTIME"StartTime", (const xmlChar*) modified);
2317 // And additionally store the start time so we can calculate the AllDayEvent value later
2318 startTime = tt;
2319 g_debug ("dtstart cleanup");
2320 free (modified);
2321 }
2322 break;
2323
2324 // DTEND
2325 case ICAL_DTEND_PROPERTY: {
2326 struct icaltimetype tt;
2327 char* modified = NULL((void*)0);
2328
2329 //get end time, convert it to UTC and suffix Z onto it
2330 tt = icalproperty_get_dtend (prop);
2331 modified = _ical2eas_convert_icaltime_to_utcstr(tt, icaltz);
2332 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ENDTIME"EndTime", (const xmlChar*) modified);
2333 // And additionally store the end time so we can calculate the AllDayEvent value later
2334 endTime = icalproperty_get_dtend (prop);
2335 free (modified);
2336 }
2337 break;
2338
2339 // LOCATION
2340 case ICAL_LOCATION_PROPERTY:
2341 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_LOCATION"Location", (const xmlChar*) icalproperty_get_value_as_string (prop));
2342 break;
2343
2344 // UID
2345 case ICAL_UID_PROPERTY:
2346 if (!exception)
2347 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_UID"UID", (const xmlChar*) icalproperty_get_value_as_string (prop));
2348 break;
2349
2350 // CLASS
2351 case ICAL_CLASS_PROPERTY: {
2352 icalproperty_class classValue = icalproperty_get_class (prop);
2353 switch (classValue) {
2354 case ICAL_CLASS_CONFIDENTIAL:
2355 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_SENSITIVITY"Sensitivity", (const xmlChar*) EAS_SENSITIVITY_CONFIDENTIAL"3");
2356 break;
2357 case ICAL_CLASS_PRIVATE:
2358 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_SENSITIVITY"Sensitivity", (const xmlChar*) EAS_SENSITIVITY_PRIVATE"2");
2359 break;
2360 default: // PUBLIC or NONE (iCalendar doesn't distinguish between 0 (Normal) and 1 (Personal))
2361 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_SENSITIVITY"Sensitivity", (const xmlChar*) EAS_SENSITIVITY_NORMAL"0");
2362 break;
2363 }
2364 }
2365 break;
2366
2367 // TRANSP
2368 case ICAL_TRANSP_PROPERTY: {
2369 icalproperty_transp transp = icalproperty_get_transp (prop);
2370 if (transp == ICAL_TRANSP_TRANSPARENT) {
2371 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_BUSYSTATUS"BusyStatus", (const xmlChar*) EAS_BUSYSTATUS_FREE"0");
2372 } else { // OPAQUE
2373 // iCalendar doesn't distinguish between 1 (Tentative), 2 (Busy), 3 (Out of Office)
2374 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_BUSYSTATUS"BusyStatus", (const xmlChar*) EAS_BUSYSTATUS_BUSY"2");
2375 }
2376 }
2377 break;
2378
2379 // CATEGORIES
2380 case ICAL_CATEGORIES_PROPERTY: {
2381 if (categories == NULL((void*)0)) {
2382 categories = xmlNewChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_CATEGORIES"Categories", NULL((void*)0));
2383 }
2384 xmlNewTextChild (categories, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_CATEGORY"Category", (const xmlChar*) icalproperty_get_categories (prop));
2385 }
2386 break;
2387
2388 // ORGANIZER
2389 case ICAL_ORGANIZER_PROPERTY: {
2390 icalparameter* cnParam = NULL((void*)0);
2391
2392 // Get the e-mail address
2393 if (!exception)
2394 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ORGANIZER_EMAIL"Organizer_Email", (const xmlChar*) icalproperty_get_organizer (prop));
2395
2396 // Now check for a name in the (optional) CN parameter
2397 cnParam = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER);
2398 if (cnParam) {
2399 if (!exception)
2400 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ORGANIZER_NAME"Organizer_Name", (const xmlChar*) icalparameter_get_cn (cnParam));
2401 }
2402 }
2403 break;
2404 // ATTENDEES
2405 case ICAL_ATTENDEE_PROPERTY: {
2406 icalparameter* param = NULL((void*)0);
2407 xmlNodePtr attendee = NULL((void*)0);
2408 if (attendees == NULL((void*)0)) {
2409 attendees = xmlNewChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ATTENDEES"Attendees", NULL((void*)0));
2410 }
2411 attendee = xmlNewChild (attendees, NULL((void*)0), (const xmlChar*) EAS_ELEMENT_ATTENDEE"Attendee", NULL((void*)0));
2412
2413 // Get the e-mail address
2414 xmlNewTextChild (attendee, NULL((void*)0), (const xmlChar*) EAS_ELEMENT_ATTENDEE_EMAIL"Attendee_Email", (const xmlChar*) icalproperty_get_value_as_string (prop));
2415
2416 // Now check for a name in the CN parameter
2417 // Name is a required element for Activesync, so if it is not present, then use the email
2418 param = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER);
2419 if (param) {
2420 xmlNewTextChild (attendee, NULL((void*)0), (const xmlChar*) EAS_ELEMENT_ATTENDEE_NAME"Attendee_Name", (const xmlChar*) icalparameter_get_cn (param));
2421 } else {
2422 xmlNewTextChild (attendee, NULL((void*)0), (const xmlChar*) EAS_ELEMENT_ATTENDEE_NAME"Attendee_Name", (const xmlChar*) icalproperty_get_value_as_string (prop));
2423 }
2424 }
2425 break;
2426 // RRULE
2427 case ICAL_RRULE_PROPERTY: {
2428 _ical2eas_process_rrule (prop, appData, &startTime, icaltz);
2429 }
2430 break;
2431
2432 // RDATE
2433 case ICAL_RDATE_PROPERTY: {
2434
2435 const gchar* start = NULL((void*)0);
2436 gchar *modified = NULL((void*)0);
2437
2438 xmlNodePtr exception = NULL((void*)0);
2439
2440 // Create the <Exceptions> container element if not already present
2441 if (exceptions == NULL((void*)0)) {
2442 exceptions = xmlNewChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONS"Exceptions", NULL((void*)0));
2443 }
2444
2445 // Now create the <Exception> element
2446 exception = xmlNewChild (exceptions, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTION"Exception", NULL((void*)0));
2447
2448 start = icalproperty_get_value_as_string (prop);
2449 if (strlen (start) <= 9) {
2450 if (!g_str_has_suffix (start, "Z")) {
2451 modified = g_strconcat (start, "T000000Z", NULL((void*)0));
2452 } else {
2453 //need to add midnight timestamp before last characters
2454 //first remove last character
2455 gchar * temp = g_strndup (start, (strlen (start) - 1));
2456 //then concatenate timestamp + "Z"
2457 modified = g_strconcat (temp, "T000000", "Z", NULL((void*)0));
2458 g_free (temp);
2459 }
2460 } else {
2461 if (!g_str_has_suffix (start, "Z")) {
2462 modified = g_strconcat (start, "Z", NULL((void*)0));
2463 } else {
2464 modified = g_strdup (start);
2465 }
2466 }
2467 xmlNewTextChild (exception, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime", (const xmlChar*) modified);
2468 g_free (modified);
2469 }
2470 break;
2471
2472 // EXDATE
2473 case ICAL_EXDATE_PROPERTY: {
2474 // EXDATE consists of a list of date/times, comma separated.
2475 // However, libical breaks this up for us and converts it into
2476 // a number of single-value properties.
2477 struct icaltimetype tt;
2478 char *modified = NULL((void*)0);
2479
2480 xmlNodePtr exception = NULL((void*)0);
2481
2482 // Create the <Exceptions> container element if not already present
2483 if (exceptions == NULL((void*)0)) {
2484 exceptions = xmlNewChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONS"Exceptions", NULL((void*)0));
2485 }
2486
2487 // Now create the <Exception> element
2488 exception = xmlNewChild (exceptions, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTION"Exception", NULL((void*)0));
2489 xmlNewTextChild (exception, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_DELETED"Exception_Deleted", (const xmlChar*) EAS_BOOLEAN_TRUE"1");
2490
2491 tt = icalproperty_get_exdate (prop);
2492 // TODO: handle VALUE=DATE for events which are not all-day events
2493 modified = _ical2eas_convert_icaltime_to_utcstr (tt, icaltz);
2494 xmlNewTextChild (exception, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime", (const xmlChar*) modified);
2495 free (modified);
2496 }
2497 break;
2498
2499 // DESCRIPTION
2500 case ICAL_DESCRIPTION_PROPERTY: {
2501
2502 // See [MS-ASAIRS] for format of the <Body> element:
2503 // http://msdn.microsoft.com/en-us/library/dd299454(v=EXCHG.80).aspx
2504 set_xml_body_text (appData, icalproperty_get_description(prop));
2505 }
2506 break;
2507
2508 default: {
2509 // Any other unmapped properties get stored as extension elements under the
2510 // <activesyncd:iCalExtensions> collection element.
2511 /*
2512 gchar* elementName = NULL;
2513
2514 // Create the collecion element when first needed
2515 if (icalExtns == NULL)
2516 {
2517 icalExtns = xmlNewChild(appData, NULL, (const xmlChar*)X_NAMESPACE_ACTIVESYNCD X_ELEMENT_ICALEXENSIONS, NULL);
2518 }
2519
2520 // Note: icalproperty_as_ical_string() keeps ownership of the string so we don't have to delete
2521 g_debug("Found unsupported iCalendar property (%s): adding as extension element under <iCalExtensions>", icalproperty_as_ical_string(prop));
2522
2523 elementName = g_strconcat(X_NAMESPACE_ACTIVESYNCD, icalproperty_get_property_name(prop), NULL);
2524 xmlNewTextChild(icalExtns, NULL, (const xmlChar*)elementName, (const xmlChar*)icalproperty_get_value_as_string(prop));
2525 g_free(elementName); elementName = NULL;
2526 */
2527 }
2528 break;
2529 }// end of switch
2530 }// end of for loop
2531
2532
2533
2534
2535 // Add an <AllDayEvent> element if both the start and end dates have no times
2536 // (ie. just dates, with time set to midnight) and are 1 day apart
2537 // (from [MS-ASCAL]: "An item marked as an all day event is understood to begin
2538 // on midnight of the current day and to end on midnight of the next day.")
2539 // This description seems to limit AllDayEvent to events covering only one
2540 // day. In practice, the end time may also be at midnight of any of the following
2541 // days, to allow for multi-day all-day events.
2542 if (startTime.hour == 0 && startTime.minute == 0 && startTime.second == 0 && endTime.hour == 0 && endTime.minute == 0 && endTime.second == 0) {
2543 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ALLDAYEVENT"AllDayEvent", (const xmlChar*) EAS_BOOLEAN_TRUE"1");
2544 } else {
2545 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_ALLDAYEVENT"AllDayEvent", (const xmlChar*) EAS_BOOLEAN_FALSE"0");
2546 }
2547
2548
2549 // ensure that all properties are set so that missing ones
2550 // really get removed on the server
2551 set_missing_calendar_properties (appData, exception);
2552 }
2553}
2554
2555
2556/**
2557 * Process the VALARM component during parsing of an iCalendar
2558 *
2559 * @param valarm
2560 * Pointer to the iCalendar VALARM component
2561 * @param appData
2562 * Pointer to the <ApplicationData> element to add parsed elements to
2563 */
2564static void _ical2eas_process_valarm (icalcomponent* valarm, xmlNodePtr appData)
2565{
2566 if (valarm) {
2567 // Just need to get the TRIGGER property
2568 icalproperty* prop = icalcomponent_get_first_property (valarm, ICAL_TRIGGER_PROPERTY);
2569 if (prop) {
2570 struct icaltriggertype trigger = icalproperty_get_trigger (prop);
2571
2572 // TRIGGER can be either a period of time before the event, OR a specific date/time.
2573 // calendar:Reminder only accepts a number of minutes
2574
2575 // For now I'm ASSUMING it'll be the former.
2576 // TODO: handle the latter as well
2577
2578 // As ActiveSync only accepts minutes we'll ignore the seconds value altogether
2579 guint minutes = trigger.duration.minutes
2580 + (trigger.duration.hours * MINUTES_PER_HOUR)
2581 + (trigger.duration.days * MINUTES_PER_DAY)
2582 + (trigger.duration.weeks * MINUTES_PER_WEEK);
2583
2584 char minutes_buf[6];
2585 g_snprintf (minutes_buf, 6, "%d", minutes);
2586
2587 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_REMINDER"Reminder", (const xmlChar*) minutes_buf);
2588 }
2589 }
2590}
2591
2592
2593/**
2594 * Parse the STANDARD and DAYLIGHT subcomponents of VTIMEZONE.
2595 * Using one function for both as their formats are identical.
2596 *
2597 * @param subcomponent
2598 * The STANDARD or DAYLIGHT subcomponent of the VTIMEZONE component
2599 * @param timezone
2600 * The EAS timezone structure to parse this subcomponent into
2601 * @param type
2602 * Determines whether subcomponent is a STANDARD or a DAYLIGHT
2603 */
2604static void _ical2eas_process_xstandard_xdaylight (icalcomponent* subcomponent, EasTimeZone* timezone, icalcomponent_kind type)
2605{
2606 if (subcomponent) {
2607 // Determine whether we've been passed a STANDARD or DAYLIGHT component
2608 // and get a pointer to the appropriate time struct
2609 const gboolean isStandardTime = (type == ICAL_XSTANDARD_COMPONENT);
2610 EasSystemTime* easTimeStruct = (isStandardTime ? (& (timezone->StandardDate)) : (& (timezone->DaylightDate)));
2611
2612 // Get the properties we're interested in. Note RRULE is optional but the rest are mandatory
2613 icalproperty* dtStart = icalcomponent_get_first_property (subcomponent, ICAL_DTSTART_PROPERTY);
2614 icalproperty* rrule = icalcomponent_get_first_property (subcomponent, ICAL_RRULE_PROPERTY);
2615 icalproperty* tzOffsetTo = icalcomponent_get_first_property (subcomponent, ICAL_TZOFFSETTO_PROPERTY);
2616 icalproperty* tzOffsetFrom = icalcomponent_get_first_property (subcomponent, ICAL_TZOFFSETFROM_PROPERTY);
2617
2618 // Get the values of the properties
2619 // Note: icalproperty_get_tzoffsetto() and icalproperty_get_tzofsetfrom() return offsets as seconds.
2620 const icaltimetype dtStartValue = icalproperty_get_dtstart (dtStart);
2621 const int tzOffsetToValueMins = icalproperty_get_tzoffsetto (tzOffsetTo) / SECONDS_PER_MINUTE;
2622 const int tzOffsetFromValueMins = icalproperty_get_tzoffsetfrom (tzOffsetFrom) / SECONDS_PER_MINUTE;
2623
2624 struct icalrecurrencetype rruleValue;
2625 if (rrule) {
2626 rruleValue = icalproperty_get_rrule (rrule);
2627 } else {
2628 icalrecurrencetype_clear (&rruleValue);
2629 }
2630
2631 if (isStandardTime) {
2632 // Calculate the EAS bias value. Bias represents a UTC offset, but expressed the opposite way
2633 // round to the usual notation. In EAS, Bias is the offset added to the local timezone to get to
2634 // UTC, whereas the conventional notation is the offset added to UTC to get to the local time.
2635 // For example, Pacific Standard Time is at UTC-8 (8 hours behind UTC - deduct 8 from a UTC time to
2636 // get the corresponding PST time), but in EAS that's expressed as a Bias of +480 minutes (i.e. add
2637 // 8 hours to a PST time to get back to UTC). Likewise, Central European Time (UTC+1) has a Bias
2638 // value of -60. See http://msdn.microsoft.com/en-us/library/ms725481(v=vs.85).aspx for more details.
2639 timezone->Bias = -1 * tzOffsetToValueMins;
2640
2641 // Standard bias is always 0 (it's the Standard time's offset from the Bias)
2642 timezone->StandardBias = 0; // Always zero
2643 } else { // It's daylight time
2644 // As with the bias above, this value is inverted from our usual understanding of it. e.g. If a
2645 // daylight saving phase adds 1 hour to the standard phase, the DaylightBias value is -60. Note
2646 // that DaylightBias and StandardBias are the additional offsets *relative to the base Bias value*
2647 // (rather than absolute offsets from UTC). We can calculate the daylight bias easily as follows:
2648 timezone->DaylightBias = tzOffsetFromValueMins - tzOffsetToValueMins;
2649 }
2650
2651 // Handle recurrence information if present
2652 if (rrule) {
2653 // We can assume FREQ=YEARLY: EAS only supports annually recurring timezone changes
2654 short byMonth = rruleValue.by_month[0];
2655 short byDayRaw = rruleValue.by_day[0];
2656 icalrecurrencetype_weekday byDayWeekday = icalrecurrencetype_day_day_of_week (byDayRaw);
2657 /** 0 == any of day of week. 1 == first, 2 = second, -2 == second to last, etc */
2658 int byDayPosition = icalrecurrencetype_day_position (byDayRaw);
2659
2660 easTimeStruct->Year = 0; // Always 0 if we have recurrence
2661
2662 //AG - We can't assume that the month field is in the RRULE, so check if the month is valid
2663 // and if not, then get it from the dtStartValue
2664 if (1 <= byMonth && byMonth <= 12) {
2665 easTimeStruct->Month = byMonth;
2666 } else {
2667 easTimeStruct->Month = dtStartValue.month;
2668 }
2669
2670 // The day is the tricky bit...
2671 // Both formats use this to represent nth occurrence of a day in the month
2672 // (ie. it's NOT an absolute date). However, iCal supports this notation:
2673 // +1 = First of the month
2674 // +2 = 2nd in the month
2675 // -1 = Last in the month
2676 // -2 = 2nd-to-last in the month
2677 // etc.
2678 //
2679 // Whilst EAS uses:
2680 // 1 = First in month
2681 // 2 = 2nd in month
2682 // ...
2683 // 5 = Last in month, even if there are only 4 occurrences
2684 //
2685 // In other words, EAS cannot encode "2nd-to-last in month" etc.
2686 // The best we can do is to add negative iCal values to 6, so
2687 // iCal -1 => EAS 5
2688 // iCal -2 => EAS 4
2689 // etc. even though 2nd-to-last isn't always 4th.
2690
2691 // Every day occurs in a month at least 4 times (a non-leap February has 4 full weeks)
2692 // and no more than 5 times (5 full weeks = 35 days, longer than any month). So the day
2693 // position must be in the range +/-(1..5)
2694 g_assert ( (-5 <= byDayPosition && byDayPosition <= -1) || (1 <= byDayPosition && byDayPosition <= 5))do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (((-5 <= byDayPosition && byDayPosition <=
-1) || (1 <= byDayPosition && byDayPosition <=
5))) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 1)) ; else g_assertion_message_expr ("libeas", "/data/runtests/work/sources/activesyncd/eas-daemon/libeas/eas-cal-info-translator.c"
, 2694, ((const char*) (__func__)), "(-5 <= byDayPosition && byDayPosition <= -1) || (1 <= byDayPosition && byDayPosition <= 5)"
); } while (0)
;
2695 if (byDayPosition > 0) {
2696 easTimeStruct->Day = byDayPosition;
2697 } else { // byDayPosition is negative
2698 // Convert -1 to 5, -2 to 4, etc. (see above for reason why)
2699 easTimeStruct->Day = 6 + byDayPosition;
2700 }
2701
2702 // Don't want to rely on enum values in icalrecurrencetype_weekday so use a switch statement to set the DayOfWeek
2703 switch (byDayWeekday) {
2704 case ICAL_NO_WEEKDAY:
2705 case ICAL_SUNDAY_WEEKDAY:
2706 easTimeStruct->DayOfWeek = 0;
2707 break;
2708 case ICAL_MONDAY_WEEKDAY:
2709 easTimeStruct->DayOfWeek = 1;
2710 break;
2711 case ICAL_TUESDAY_WEEKDAY:
2712 easTimeStruct->DayOfWeek = 2;
2713 break;
2714 case ICAL_WEDNESDAY_WEEKDAY:
2715 easTimeStruct->DayOfWeek = 3;
2716 break;
2717 case ICAL_THURSDAY_WEEKDAY:
2718 easTimeStruct->DayOfWeek = 4;
2719 break;
2720 case ICAL_FRIDAY_WEEKDAY:
2721 easTimeStruct->DayOfWeek = 5;
2722 break;
2723 case ICAL_SATURDAY_WEEKDAY:
2724 easTimeStruct->DayOfWeek = 6;
2725 break;
2726 }
2727
2728 // Date set. Time is set below...
2729 } else { // No recurrence information: just a one-off time change, so we set an absolute date value for EAS
2730 easTimeStruct->Year = dtStartValue.year;
2731 easTimeStruct->Month = dtStartValue.month; // Both use 1 (Jan) ... 12 (Dec)
2732 easTimeStruct->Day = dtStartValue.day;
2733 // Date set. Time is set below...
2734 }
2735
2736 // Set the time fields
2737 easTimeStruct->Hour = dtStartValue.hour;
2738 easTimeStruct->Minute = dtStartValue.minute;
2739 easTimeStruct->Second = dtStartValue.second;
2740 easTimeStruct->Millisecond = 0;
2741
2742 // Destroy the property objects
2743 icalproperty_free (dtStart);
2744 icalproperty_free (tzOffsetFrom);
2745 icalproperty_free (tzOffsetTo);
2746 if (rrule) {
2747 icalproperty_free (rrule);
2748 }
2749 }
2750}
2751
2752/**
2753 * Process the VTIMEZONE component during parsing of an iCalendar
2754 *
2755 * @param vtimezone
2756 * Pointer to the iCalendar VTIMEZONE component
2757 * @param forceTimezone
2758 * Send UTC if vtimezone is empty; useful
2759 * in one particular case (recurring all-day property with
2760 * exceptions), otherwise Exchange didn't handle the event
2761 * properly (dropped exceptions, see BMC #22780), and
2762 * in another where not sending any time zone information
2763 * caused some default to be added (123together.com)
2764 * @param appData
2765 * Pointer to the <ApplicationData> element to add parsed elements to
2766 */
2767static void _ical2eas_process_vtimezone (icalcomponent* vtimezone, gboolean forceTimezone, xmlNodePtr appData)
2768{
2769 if (vtimezone) {
2770 EasTimeZone timezoneStruct;
2771 gchar* timezoneBase64 = NULL((void*)0);
2772 icalcomponent* subcomponent = NULL((void*)0);
2773 icalproperty* tzid = icalcomponent_get_first_property (vtimezone, ICAL_TZID_PROPERTY);
2774
2775 // all empty == UTC as default
2776 memset (&timezoneStruct, 0, sizeof (EasTimeZone));
2777
2778 // Only one property in a VTIMEZONE: the TZID
2779 if (tzid) {
2780 // Get the ASCII value from the iCal
2781 const char* tzidValue8 = icalproperty_get_value_as_string (tzid);
2782
2783 // Convert to Unicode, max. 32 chars (including the trailing 0)
2784 glong words;
2785 gunichar2* tzidValue16 = g_utf8_to_utf16 ((const gchar *)tzidValue8, 31, NULL((void*)0), &words, NULL((void*)0));
2786
2787 // Copy this into the EasTimeZone struct as both StandardName and DaylightName
2788 if (tzidValue16) {
2789 memcpy (& (timezoneStruct.StandardName), tzidValue16,
2790 MIN(sizeof (gunichar2) * words, sizeof(timezoneStruct.StandardName))(((sizeof (gunichar2) * words) < (sizeof(timezoneStruct.StandardName
))) ? (sizeof (gunichar2) * words) : (sizeof(timezoneStruct.StandardName
)))
);
2791 memcpy (& (timezoneStruct.DaylightName), tzidValue16,
2792 MIN(sizeof (gunichar2) * words, sizeof(timezoneStruct.DaylightName))(((sizeof (gunichar2) * words) < (sizeof(timezoneStruct.DaylightName
))) ? (sizeof (gunichar2) * words) : (sizeof(timezoneStruct.DaylightName
)))
);
2793 g_free (tzidValue16);
2794 }
2795 }
2796
2797 // Now process the STANDARD and DAYLIGHT subcomponents
2798 for (subcomponent = icalcomponent_get_first_component (vtimezone, ICAL_ANY_COMPONENT);
2799 subcomponent;
2800 subcomponent = icalcomponent_get_next_component (vtimezone, ICAL_ANY_COMPONENT)) {
2801 _ical2eas_process_xstandard_xdaylight (subcomponent, &timezoneStruct, icalcomponent_isa (subcomponent));
2802 }
2803
2804 // Write the timezone into the XML, base64-encoded.
2805 timezoneBase64 = g_base64_encode ( (const guchar *) (&timezoneStruct), sizeof (EasTimeZone));
2806 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_TIMEZONE"TimeZone", (const xmlChar*) timezoneBase64);
2807 g_free (timezoneBase64);
2808 } else if (forceTimezone) {
2809 // This is Exchange 2010's idea of UTC. Corresponds to:
2810 // bias 0, standard bias 0, daylight bias 0,
2811 // standard '(UTC) Coordinated Universal Time',
2812 // daylight '(UTC) Coordinated Universal Time'
2813 xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_TIMEZONE"TimeZone", (const xmlChar*)"AAAAACgAVQBUAEMAKQAgAEMAbwBvAHIAZABpAG4AYQB0AGUAZAAgAFUAbgBpAHYAZQByAHMAYQBsACAAVABpAG0AZQAAAAAAAAAAAAAAAAAAAAAAAAAAACgAVQBUAEMAKQAgAEMAbwBvAHIAZABpAG4AYQB0AGUAZAAgAFUAbgBpAHYAZQByAHMAYQBsACAAVABpAG0AZQAAAAAAAAAAAAAAAAAAAAAAAAAAAA==");
2814 }
2815}
2816
2817/** compare icaltimetype in qsort() */
2818static int comparetimes(const void *a, const void *b)
2819{
2820 return icaltime_compare (*(const icaltimetype *)a,
2821 *(const icaltimetype *)b);
2822}
2823
2824/**
2825 * Check whether a recurrence rule covers all detached recurrences.
2826 * Parameters see calculateRecurrence().
2827 *
2828 * Done by expanding the rrule with the first detached recurrence
2829 * as start time and then checking against the next recurrence
2830 * until one is found which is not covered or we are done.
2831 */
2832static gboolean
2833rruleMatches (const struct icalrecurrencetype *recur,
2834 int numDetached, const icaltimetype *detached)
2835{
2836 icalrecur_iterator* ritr = icalrecur_iterator_new (*recur, detached[0]);
2837 struct icaltimetype instance;
2838 int next = 0;
2839 gboolean res = TRUE(!(0));
2840 while (res &&
2841 next < numDetached &&
2842 (instance = icalrecur_iterator_next (ritr),
2843 !icaltime_is_null_time (instance))) {
2844 int cmp = icaltime_compare (detached[next], instance);
2845 if (cmp == 0) {
2846 /* okay, move on to next detached recurrence */
2847 next++;
2848 } else if (cmp < 0) {
2849 /* failure, the current recurrence was not covered */
2850 res = FALSE(0);
2851 }
2852 }
2853
2854 if (res &&
2855 (next < numDetached /* iterator did not return enough results */ ||
2856 (instance = icalrecur_iterator_next (ritr),
2857 !icaltime_is_null_time (instance)) /* iterator returns too many results */))
2858 res = FALSE(0);
2859
2860 icalrecur_iterator_free (ritr);
2861 return res;
2862}
2863
2864/**
2865 * Generate recurrence rule covering the detached recurrence times.
2866 * The time zone of the first detached recurrence is used.
2867 *
2868 * @param numDetached
2869 * Number of entries in detached array, > 0.
2870 * @param detached
2871 * date-times of detached recurrences, with time zone,
2872 * sorted from oldest to most recent
2873 */
2874static struct icalrecurrencetype
2875calculateRecurrence (int numDetached, const icaltimetype *detached)
2876{
2877 static const struct icalrecurrencetype recur = {
2878 .freq = ICAL_NO_RECURRENCE,
2879 .interval = 1,
2880 .week_start = ICAL_MONDAY_WEEKDAY,
2881 .by_second = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2882 .by_minute = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2883 .by_hour = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2884 .by_day = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2885 .by_month_day = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2886 .by_year_day = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2887 .by_week_no = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2888 .by_month = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX },
2889 .by_set_pos = { ICAL_RECURRENCE_ARRAY_MAX, ICAL_RECURRENCE_ARRAY_MAX }
2890 };
2891 int freq;
2892 struct icaldurationtype delta;
2893 if (numDetached >= 2)
2894 delta = icaltime_subtract (detached[1], detached[0]);
2895 else
2896 // keep compiler happy (doesn't detect that delta is not used in this case)
2897 memset (&delta, 0, sizeof(delta));
2898
2899 // try yearly, monthly, ... , secondly recurrence
2900 for (freq = ICAL_YEARLY_RECURRENCE;
2901 freq >= ICAL_SECONDLY_RECURRENCE;
2902 freq--) {
2903 struct icalrecurrencetype res = recur;
2904 res.freq = freq;
2905 res.until = detached[numDetached - 1];
2906 /* icaltime_convert_to_zone (detached[numDetached - 1],
2907 icaltimezone_get_utc_timezone ()); */
2908
2909 switch (freq) {
2910 case ICAL_YEARLY_RECURRENCE:
2911 res.by_month[0] = detached[0].month;
2912 res.by_month_day[0] = detached[0].day;
2913 break;
2914 case ICAL_MONTHLY_RECURRENCE:
2915 res.by_month_day[0] = detached[0].day;
2916 break;
2917 case ICAL_WEEKLY_RECURRENCE:
2918 res.by_day[0] = icaltime_day_of_week(detached[0]);
2919 break;
2920 }
2921
2922 // Try special case first:
2923 // calculate delta between first and second recurrence,
2924 // use that to set an interval. If we only have two
2925 // recurrences, then this will result in an rrule with
2926 // no need for EXDATEs. If we have more, perhaps we are
2927 // lucky and the rule still matches the rest.
2928 if (numDetached >= 2) {
2929 res.interval = 0;
2930 switch (freq) {
2931 /*
2932 * interval > 1 replaced with interval = 1 by Exchange, avoid generating such rules
2933 * case ICAL_YEARLY_RECURRENCE:
2934 * res.interval = detached[1].year - detached[0].year;
2935 * break;
2936 */
2937 case ICAL_MONTHLY_RECURRENCE: {
2938 int months = detached[1].month - detached[0].month;
2939 if (months <= 0) {
2940 months += 12;
2941 }
2942 res.interval = months;
2943 break;
2944 }
2945 case ICAL_DAILY_RECURRENCE:
2946 res.interval = delta.weeks * 7 + delta.days;
2947 break;
2948 case ICAL_HOURLY_RECURRENCE:
2949 res.interval = (delta.weeks * 7 + delta.days) * 24 + delta.hours;
2950 break;
2951 case ICAL_MINUTELY_RECURRENCE:
2952 res.interval = ((delta.weeks * 7 + delta.days) * 24 + delta.hours) * 60 + delta.minutes;
2953 break;
2954 case ICAL_SECONDLY_RECURRENCE:
2955 res.interval = (((delta.weeks * 7 + delta.days) * 24 + delta.hours) * 60 + delta.minutes) * 60 + delta.seconds;
2956 break;
2957 }
2958 if (res.interval) {
2959 /*
2960 * start with interval + 1, because if a time zone change
2961 * happens between the two detached recurrences, the actual
2962 * delta in time will have +- 1 hour
2963 */
2964 for (res.interval++;
2965 res.interval > 1;
2966 res.interval--) {
2967 if (rruleMatches (&res, numDetached, detached))
2968 return res;
2969 }
2970 }
2971 }
2972
2973 // general case
2974 res.interval = 1;
2975 if (rruleMatches (&res, numDetached, detached))
2976 return res;
2977 }
2978
2979 // nothing found, will probably fail later somewhere
2980 return recur;
2981}
2982
2983/**
2984 * Iterate over all regular recurrences of an artificial parent
2985 * event and add EXDATEs for all recurrences which do not
2986 * have a detached recurrence.
2987 */
2988static void calculateExdates (icalcomponent *parent,
2989 const struct icalrecurrencetype *recur,
2990 int numDetached,
2991 icaltimetype *detached)
2992{
2993 icalrecur_iterator* ritr = icalrecur_iterator_new (*recur, detached[0]);
2994 struct icaltimetype instance;
2995 int next = 0;
2996 icalproperty *dtstart = icalcomponent_get_first_property (parent, ICAL_DTSTART_PROPERTY);
2997 icalparameter *tzid = icalproperty_get_first_parameter (dtstart, ICAL_TZID_PROPERTY);
2998 while ((instance = icalrecur_iterator_next (ritr),
2999 !icaltime_is_null_time (instance))) {
3000 int cmp = icaltime_compare (detached[next], instance);
3001 if (cmp == 0) {
3002 /* has detached recurrence, move on to next one */
3003 next++;
3004 } else if (cmp < 0) {
3005 /* Not matched?! Shouldn't happen, but so be it. */
3006 next++;
3007 } else {
3008 icalproperty *prop = icalproperty_vanew_exdate (instance,
3009 tzid ? icalparameter_new_clone (tzid) : (void *)0,
3010 (void *)0);
3011 icalcomponent_add_property (parent, prop);
3012 }
3013 }
3014 icalrecur_iterator_free (ritr);
3015}
3016
3017/**
3018 * Parse an EasCalInfo structure and convert to EAS XML format
3019 *
3020 * @param doc
3021 * REDUNDANT PARAMETER: only required for debug output. TODO: remove this
3022 * @param appData
3023 * The top-level <ApplicationData> XML element in which to store all the parsed elements
3024 * @param calInfo
3025 * The EasCalInfo struct containing the iCalendar string to parse (plus a server ID)
3026 */
3027gboolean eas_cal_info_translator_parse_request (xmlDocPtr doc, xmlNodePtr appData, EasItemInfo* calInfo)
3028{
3029 gboolean success = FALSE(0);
3030 icalcomponent* ical = NULL((void*)0);
3031 icaltimezone* icaltz = NULL((void*)0);
3032 icaltimetype *recurrenceTimes = NULL((void*)0);
3033
3034 g_debug (" Cal Data: %s", calInfo->data);
3035
3036 if (doc &&
3037 appData &&
3038 calInfo &&
3039 (appData->type == XML_ELEMENT_NODE) &&
3040 (g_strcmp0 ( (char*) (appData->name), EAS_ELEMENT_APPLICATIONDATA"ApplicationData") == 0) &&
3041 (ical = icalparser_parse_string (calInfo->data)) &&
3042 (icalcomponent_isa (ical) == ICAL_VCALENDAR_COMPONENT)) {
3043 icalcomponent* vevent = NULL((void*)0);
3044 icalcomponent* exceptionvevent = NULL((void*)0);
3045 icalcomponent* vtimezone = NULL((void*)0);
3046 icalproperty* tzid = NULL((void*)0);
3047 struct icaltimetype tt;
3048 icalproperty* prop = NULL((void*)0);
3049 gboolean forceTimezone = FALSE(0);
3050 xmlNodePtr exceptions = NULL((void*)0);
3051 xmlNodePtr subNode = NULL((void*)0);
3052 icalcomponent* comp;
3053
3054 // The code here assumes that there is either no or one VTIMEZONE,
3055 // and that if there is one, all time stamps use that zone. This is a slight
3056 // oversimplification (iCalendar 2.0 allows multiple different VTIMEZONEs
3057 // to be used, and both the Evolution and Google Calendar UI support that).
3058 //
3059 // TODO: choose one time zone for ActiveSync, properly convert times with other
3060 // time zones into it.
3061
3062 vtimezone = icalcomponent_get_first_component (ical, ICAL_VTIMEZONE_COMPONENT);
3063 if (vtimezone)
3064 tzid = icalcomponent_get_first_property (vtimezone, ICAL_TZID_PROPERTY);
3065
3066 if (tzid) {
3067 icaltz = icaltimezone_new();
3068 icaltimezone_set_component(icaltz, vtimezone);
3069 }
3070
3071 // Use UTC time zone as fallback. May happen for all-day events (which need
3072 // no time zone) or events which truly were defined as local time. In the
3073 // latter case it would be better to use the system time zone, but we don't
3074 // know what that is. Besides, such events are broken by design and shouldn't occur.
3075 if (!icaltz)
3076 icaltz = icaltimezone_get_utc_timezone();
3077 if (!icaltz)
3078 goto error;
3079 vtimezone = icaltimezone_get_component (icaltz);
3080 tzid = vtimezone ?
Value stored to 'tzid' is never read
3081 icalcomponent_get_first_property (vtimezone, ICAL_TZID_PROPERTY) :
3082 NULL((void*)0);
3083
3084 // Process the components of the VCALENDAR.
3085 // Don't make assumptions about any particular order, check RECURRENCE-ID
3086 // to find exceptions.
3087 for (comp = icalcomponent_get_first_component (ical, ICAL_VEVENT_COMPONENT);
3088 comp;
3089 comp = icalcomponent_get_next_component (ical, ICAL_VEVENT_COMPONENT)) {
3090 if (!icalcomponent_get_first_property (comp, ICAL_RECURRENCEID_PROPERTY)) {
3091 vevent = comp;
3092 break;
3093 }
3094 }
3095
3096 /* regenerate artificial parent */
3097 if (vevent &&
3098 !g_strcmp0 (icalcomponent_get_summary (vevent), ACTIVESYNCD_PSEUDO_EVENT"[[activesyncd pseudo event - ignore me]]")) {
3099 icalcomponent_remove_component (ical, vevent);
3100 icalcomponent_free (vevent);
3101 vevent = NULL((void*)0);
3102 }
3103
3104 if (!vevent) {
3105 // Create fake parent with same time zone and same UID
3106 // as first detached recurrence. It must have a recurrence
3107 // pattern which covers all detached recurrences (because
3108 // Exchange rejects those otherwise). In addition, it must
3109 // never be visible at any of the recurrences. Add EXDATE
3110 // for those recurrences not covered by detached recurrences.
3111
3112 // Calculate "minimal" recurrence rule (= produces as little
3113 // recurrences as possible) based on RECURRENCE-IDs of the
3114 // the detached recurrences. The relevant time zone for
3115 // recurrences is the one of the fictious parent event.
3116 int numDetached = 1;
3117 struct icalrecurrencetype recur;
3118 int i;
3119 icalproperty *rid;
3120 icalparameter *tzid;
3121
3122 vevent = icalcomponent_get_first_component (ical, ICAL_VEVENT_COMPONENT);
3123 if (!vevent)
3124 goto error;
3125 while (icalcomponent_get_next_component (ical, ICAL_VEVENT_COMPONENT))
3126 numDetached++;
3127 recurrenceTimes = g_malloc (sizeof(icaltimetype) * numDetached);
3128 for (vevent = icalcomponent_get_first_component (ical, ICAL_VEVENT_COMPONENT), i = 0;
3129 vevent;
3130 vevent = icalcomponent_get_next_component (ical, ICAL_VEVENT_COMPONENT), i++)
3131 recurrenceTimes[i] = _eas2cal_component_get_recurrenceid (vevent);
3132 qsort (recurrenceTimes, numDetached, sizeof(icaltimetype),
3133 comparetimes);
3134
3135 recur = calculateRecurrence (numDetached, recurrenceTimes);
3136
3137 /* take tzid from any of the detached recurrences, but the
3138 start time must come the oldest one */
3139 vevent = icalcomponent_get_first_component (ical, ICAL_VEVENT_COMPONENT);
3140 rid = icalcomponent_get_first_property (vevent, ICAL_RECURRENCEID_PROPERTY);
3141 tzid = icalproperty_get_first_parameter (rid, ICAL_TZID_PARAMETER);
3142
3143 vevent =
3144 icalcomponent_vanew (ICAL_VEVENT_COMPONENT,
3145 icalproperty_vanew_dtstart (recurrenceTimes[0],
3146 tzid ? icalparameter_new_clone (tzid) : (void *)0,
3147 (void *)0),
3148 icalproperty_vanew_dtend (recurrenceTimes[0],
3149 tzid ? icalparameter_new_clone (tzid) : (void *)0,
3150 (void *)0),
3151 icalproperty_new_uid (icalcomponent_get_uid (vevent)),
3152 icalproperty_new_summary (ACTIVESYNCD_PSEUDO_EVENT"[[activesyncd pseudo event - ignore me]]"),
3153 icalproperty_new_transp (ICAL_TRANSP_TRANSPARENT),
3154 icalproperty_new_rrule (recur),
3155 (void *)0
3156 );
3157 if (!vevent)
3158 goto error;
3159
3160 // add EXDATE properties
3161 calculateExdates (vevent, &recur, numDetached, recurrenceTimes);
3162
3163 icalcomponent_add_component (ical, vevent);
3164 }
3165 prop = icalcomponent_get_first_property (vevent, ICAL_DTSTART_PROPERTY);
3166 if (!prop)
3167 goto error;
3168 tt = icalproperty_get_dtstart (prop);
3169
3170 // Recurring all-day events with detached recurrences got mangled
3171 // by Exchange 2010 (stored without error, but came back without
3172 // the exceptions). Sending a dummy UTC time zone definition avoided
3173 // the problem (see BMC #22780). We ignore the time zone when receiving all-day
3174 // events, so it is safe to send it (conversion back will produce
3175 // "nice" all-day event without time zone again, other peers should
3176 // deal with it okay, too - verified with Exchange/OWA).
3177 //
3178 // Also explicitly send a dummy UTC timezone if we use UTC.
3179 // Some Exchange 2010 servers were fine without it (time stamps
3180 // are in UTC anyway), but the installation on 123together.com
3181 // added its own local time when no explicit timezone was sent.
3182 if (tt.is_date || icaltz == icaltimezone_get_utc_timezone ())
3183 forceTimezone = TRUE(!(0));
3184 _ical2eas_process_vtimezone (vtimezone, forceTimezone, appData);
3185 _ical2eas_process_vevent (vevent, appData, icaltz, FALSE(0));
3186
3187 // Always include <Exceptions> even if empty. Might have helped with removing
3188 // existing exceptions in an update (SyncEvolution testLinkedItemsRemoveNormal)
3189 // but didn't (BMC #22849).
3190 //
3191 // check if <Exceptions> node Already exists otherwise create a new <Exceptions> Node
3192 // TODO: why should it exist? We haven't created one yet, have we?
3193 for (subNode = appData->children; subNode; subNode = subNode->next) {
3194 if (subNode->type == XML_ELEMENT_NODE && g_strcmp0 ( (gchar*) subNode->name, EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONS"Exceptions") == 0) {
3195 exceptions = subNode;
3196 break;
3197 }
3198 }
3199 if (!exceptions)
3200 exceptions = xmlNewTextChild (appData, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONS"Exceptions", NULL((void*)0));
3201
3202
3203 for (exceptionvevent = icalcomponent_get_first_component (ical, ICAL_VEVENT_COMPONENT);
3204 exceptionvevent;
3205 exceptionvevent = icalcomponent_get_next_component (ical, ICAL_VEVENT_COMPONENT)) {
3206 xmlNodePtr exception;
3207
3208 // ignore parent
3209 if (!icalcomponent_get_first_property (exceptionvevent, ICAL_RECURRENCEID_PROPERTY))
3210 continue;
3211
3212 exception = xmlNewTextChild (exceptions, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTION"Exception", NULL((void*)0));
3213 g_debug ("Processing multiple vevents as exceptions");
3214
3215 _ical2eas_process_vevent (exceptionvevent, exception, icaltz, TRUE(!(0)));
3216 _ical2eas_process_valarm (icalcomponent_get_first_component (exceptionvevent, ICAL_VALARM_COMPONENT), exception);
3217
3218 //get recurrenceID ( which is a timestamp), convert it to UTC and suffix Z onto it
3219 prop = icalcomponent_get_first_property (exceptionvevent, ICAL_RECURRENCEID_PROPERTY);
3220 if(prop){
3221 char* modified = NULL((void*)0);
3222 tt = icaltime_from_string(icalproperty_get_value_as_string (prop));
3223 modified = _ical2eas_convert_icaltime_to_utcstr(tt, icaltz);
3224 xmlNewTextChild (exception, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime", (const xmlChar *) modified);
3225 free (modified);
3226 }else{
3227 xmlNewTextChild (exception, NULL((void*)0), (const xmlChar*) EAS_NAMESPACE_CALENDAR"calendar:" EAS_ELEMENT_EXCEPTIONSTARTTIME"Exception_StartTime", NULL((void*)0));
3228 }
3229 }
3230
3231 _ical2eas_process_valarm (icalcomponent_get_first_component (vevent, ICAL_VALARM_COMPONENT), appData);
3232
3233 if (getenv ("EAS_DEBUG") && (atoi (g_getenv ("EAS_DEBUG")) >= 4)) {
3234 xmlChar* dump_buffer = NULL((void*)0);
3235 int dump_buffer_size = 0;
3236 xmlIndentTreeOutput(*(__xmlIndentTreeOutput())) = 1;
3237 xmlDocDumpFormatMemory (doc, &dump_buffer, &dump_buffer_size, 1);
3238 g_debug ("XML DOCUMENT DUMPED:\n%s", dump_buffer);
3239 xmlFree (dump_buffer);
3240 }
3241
3242 success = TRUE(!(0));
3243 }
3244
3245 error:
3246 if (icaltz && icaltz != icaltimezone_get_utc_timezone ())
3247 icaltimezone_free (icaltz, TRUE(!(0)));
3248 if (ical) {
3249 icalcomponent_free (ical);
3250 }
3251 g_free (recurrenceTimes);
3252
3253 return success;
3254}
3255