File: | activesyncd/eas-daemon/libeas/eas-cal-info-translator.c |
Warning: | line 203, column 2 Value stored to 'node' is never read |
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. |
62 | const gint SECONDS_PER_MINUTE = 60; |
63 | const gint MINUTES_PER_HOUR = 60; |
64 | const gint MINUTES_PER_DAY = 60 * 24; |
65 | const gint SECONDS_PER_DAY = 60 * 60 * 24; |
66 | const gint MINUTES_PER_WEEK = 60 * 24 * 7; |
67 | const gint DAYS_PER_WEEK = 7; |
68 | const gint EPOCH_START_YEAR = 1970; |
69 | |
70 | // Constants for <Calendar> parsing |
71 | const guint DAY_OF_WEEK_SUNDAY = 0x00000001; |
72 | const guint DAY_OF_WEEK_MONDAY = 0x00000002; |
73 | const guint DAY_OF_WEEK_TUESDAY = 0x00000004; |
74 | const guint DAY_OF_WEEK_WEDNESDAY = 0x00000008; |
75 | const guint DAY_OF_WEEK_THURSDAY = 0x00000010; |
76 | const guint DAY_OF_WEEK_FRIDAY = 0x00000020; |
77 | const guint DAY_OF_WEEK_SATURDAY = 0x00000040; |
78 | const 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 | |
167 | typedef 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 | |
178 | compile_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 | */ |
185 | typedef 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 | |
195 | compile_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*/ |
198 | static gboolean |
199 | is_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; |
Value stored to 'node' is never read | |
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 | */ |
222 | static 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?! */ |
253 | extern 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 | */ |
260 | static struct icaltimetype |
261 | _eas2cal_property_get_recurrenceid (const icalproperty *prop) |
262 | { |
263 | return _eas2cal_get_datetime (icalproperty_get_parent (prop), |
264 | prop); |
265 | } |
266 | |
267 | static 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 | */ |
279 | static 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 | */ |
294 | static 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 | */ |
317 | static 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 | */ |
340 | static 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 | */ |
452 | static 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 | */ |
507 | static 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 | */ |
573 | static 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 | */ |
691 | static 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 | */ |
888 | static 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 | */ |
1064 | static 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 | */ |
1254 | static 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 | */ |
1540 | gchar* 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 | */ |
1890 | static 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 | */ |
1917 | static 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 | |
2160 | static 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 | */ |
2176 | static void |
2177 | set_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 | */ |
2266 | static 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 | */ |
2564 | static 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 | */ |
2604 | static 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 | */ |
2767 | static 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() */ |
2818 | static 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 | */ |
2832 | static gboolean |
2833 | rruleMatches (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 | */ |
2874 | static struct icalrecurrencetype |
2875 | calculateRecurrence (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 | */ |
2988 | static 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 | */ |
3027 | gboolean 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 ? |
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 |