1 /**
2  * @file sipe-cal.c
3  *
4  * pidgin-sipe
5  *
6  * Copyright (C) 2010-2018 SIPE Project <http://sipe.sourceforge.net/>
7  * Copyright (C) 2009 pier11 <pier11@operamail.com>
8  *
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 
33 #include <glib.h>
34 
35 #include "sipe-backend.h"
36 #include "sipe-buddy.h"
37 #include "sipe-common.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
40 #include "sipe-cal.h"
41 #include "sipe-http.h"
42 #include "sipe-nls.h"
43 #include "sipe-ocs2005.h"
44 #include "sipe-ocs2007.h"
45 #include "sipe-schedule.h"
46 #include "sipe-utils.h"
47 #include "sipe-xml.h"
48 
49 /* Calendar backends */
50 #ifdef _WIN32
51 #include "sipe-domino.h"
52 #endif
53 #include "sipe-ews.h"
54 
55 #define TIME_NULL   (time_t)-1
56 #define IS(time)    (time != TIME_NULL)
57 
58 /*
59 http://msdn.microsoft.com/en-us/library/aa565001.aspx
60 
61 <?xml version="1.0"?>
62 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
63   <TimeZone>
64     <Bias>480</Bias>
65     <StandardTime>
66       <Bias>0</Bias>
67       <Time>02:00:00</Time>
68       <DayOrder>1</DayOrder>
69       <Month>11</Month>
70       <DayOfWeek>Sunday</DayOfWeek>
71     </StandardTime>
72     <DaylightTime>
73       <Bias>-60</Bias>
74       <Time>02:00:00</Time>
75       <DayOrder>2</DayOrder>
76       <Month>3</Month>
77       <DayOfWeek>Sunday</DayOfWeek>
78     </DaylightTime>
79   </TimeZone>
80   <WorkingPeriodArray>
81     <WorkingPeriod>
82       <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
83       <StartTimeInMinutes>600</StartTimeInMinutes>
84       <EndTimeInMinutes>1140</EndTimeInMinutes>
85     </WorkingPeriod>
86   </WorkingPeriodArray>
87 </WorkingHours>
88 
89 Desc:
90 <StandardTime>
91    <Bias>int</Bias>
92    <Time>string</Time>
93    <DayOrder>short</DayOrder>
94    <Month>short</Month>
95    <DayOfWeek>Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday</DayOfWeek>
96    <Year>string</Year>
97 </StandardTime>
98 */
99 
100 struct sipe_cal_std_dst {
101 	int bias;           /* Ex.: -60 */
102 	gchar *time;        /* hh:mm:ss, 02:00:00 */
103 	int day_order;      /* 1..5 */
104 	int month;          /* 1..12 */
105 	gchar *day_of_week; /* Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday */
106 	gchar *year;        /* YYYY */
107 
108 	time_t switch_time;
109 };
110 
111 struct sipe_cal_working_hours {
112 	int bias;                     /* Ex.: 480 */
113 	struct sipe_cal_std_dst std;  /* StandardTime */
114 	struct sipe_cal_std_dst dst;  /* DaylightTime */
115 	gchar *days_of_week;          /* Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday separated by space */
116 	int start_time;               /* 0...1440 */
117 	int end_time;                 /* 0...1440 */
118 
119 	gchar *tz;                    /* aggregated timezone string as in TZ environment variable.
120 	                                 Ex.: TST+8TDT+7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
121 	/** separate simple strings for Windows platform as the proper TZ does not work there.
122 	 *  anyway, dynamic timezones would't work with just TZ
123 	 */
124 	gchar *tz_std;                /* Ex.: TST8 */
125 	gchar *tz_dst;                /* Ex.: TDT7 */
126 };
127 
128 /* not for translation, a part of XML Schema definitions */
129 static const char *wday_names[] = {"Sunday",
130 				   "Monday",
131 				   "Tuesday",
132 				   "Wednesday",
133 				   "Thursday",
134 				   "Friday",
135 				   "Saturday"};
136 static int
sipe_cal_get_wday(char * wday_name)137 sipe_cal_get_wday(char *wday_name)
138 {
139 	int i;
140 
141 	if (!wday_name) return -1;
142 
143 	for (i = 0; i < 7; i++) {
144 		if (sipe_strequal(wday_names[i], wday_name)) {
145 			return i;
146 		}
147 	}
148 
149 	return -1;
150 }
151 
152 void
sipe_cal_event_free(struct sipe_cal_event * cal_event)153 sipe_cal_event_free(struct sipe_cal_event* cal_event)
154 {
155 	if (!cal_event) return;
156 
157 	g_free(cal_event->subject);
158 	g_free(cal_event->location);
159 	g_free(cal_event);
160 }
161 
162 void
sipe_cal_events_free(GSList * cal_events)163 sipe_cal_events_free(GSList *cal_events)
164 {
165 	if (!cal_events) return;
166 	sipe_utils_slist_free_full(cal_events,
167 				   (GDestroyNotify) sipe_cal_event_free);
168 }
169 
170 void
sipe_cal_calendar_free(struct sipe_calendar * cal)171 sipe_cal_calendar_free(struct sipe_calendar *cal)
172 {
173 	if (cal) {
174 		g_free(cal->email);
175 		g_free(cal->legacy_dn);
176 		g_free(cal->as_url);
177 		g_free(cal->oof_url);
178 		g_free(cal->oab_url);
179 		g_free(cal->domino_url);
180 		g_free(cal->oof_state);
181 		g_free(cal->oof_note);
182 		g_free(cal->free_busy);
183 		g_free(cal->working_hours_xml_str);
184 
185 		sipe_cal_events_free(cal->cal_events);
186 
187 		if (cal->request)
188 			sipe_http_request_cancel(cal->request);
189 		sipe_http_session_close(cal->session);
190 
191 		g_free(cal);
192 	}
193 }
194 
195 void
sipe_cal_calendar_init(struct sipe_core_private * sipe_private)196 sipe_cal_calendar_init(struct sipe_core_private *sipe_private)
197 {
198 	if (!sipe_private->calendar) {
199 		struct sipe_calendar *cal;
200 		const char *value;
201 
202 		sipe_private->calendar = cal = g_new0(struct sipe_calendar, 1);
203 		cal->sipe_private = sipe_private;
204 
205 		cal->email = g_strdup(sipe_private->email);
206 
207 		/* user specified a service URL? */
208 		value = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
209 		if (!is_empty(value)) {
210 			cal->as_url  = g_strdup(value);
211 			cal->oof_url = g_strdup(value);
212 			cal->domino_url  = g_strdup(value);
213 		}
214 	}
215 	return;
216 }
217 
218 
219 void
sipe_cal_event_debug(const struct sipe_cal_event * cal_event,const gchar * label)220 sipe_cal_event_debug(const struct sipe_cal_event *cal_event,
221 		     const gchar *label)
222 {
223 	GString* str = g_string_new(NULL);
224 	const gchar *status = "";
225 
226 	switch(cal_event->cal_status) {
227 		case SIPE_CAL_FREE:		status = "SIPE_CAL_FREE";	break;
228 		case SIPE_CAL_TENTATIVE:	status = "SIPE_CAL_TENTATIVE";	break;
229 		case SIPE_CAL_BUSY:		status = "SIPE_CAL_BUSY";	break;
230 		case SIPE_CAL_OOF:		status = "SIPE_CAL_OOF";	break;
231 		case SIPE_CAL_NO_DATA:		status = "SIPE_CAL_NO_DATA";	break;
232 	}
233 
234 	g_string_append_printf(str, "\tstart_time: %s\n",
235 		IS(cal_event->start_time) ? sipe_utils_time_to_debug_str(localtime(&cal_event->start_time)) : "");
236 	g_string_append_printf(str, "\tend_time  : %s\n",
237 		IS(cal_event->end_time) ? sipe_utils_time_to_debug_str(localtime(&cal_event->end_time)) : "");
238 	g_string_append_printf(str, "\tcal_status: %s\n", status);
239 	g_string_append_printf(str, "\tsubject   : %s\n", cal_event->subject ? cal_event->subject : "");
240 	g_string_append_printf(str, "\tlocation  : %s\n", cal_event->location ? cal_event->location : "");
241 	/* last line must have no line break */
242 	g_string_append_printf(str, "\tis_meeting: %s", cal_event->is_meeting ? "TRUE" : "FALSE");
243 
244 	SIPE_DEBUG_INFO("%s%s", label, str->str);
245 	g_string_free(str, TRUE);
246 }
247 
248 char *
sipe_cal_event_hash(struct sipe_cal_event * event)249 sipe_cal_event_hash(struct sipe_cal_event* event)
250 {
251 	/* no end_time as it dos not get published */
252 	/* no cal_status as it can change on publication */
253 	return g_strdup_printf("<%d><%s><%s><%d>",
254 				(int)event->start_time,
255 				event->subject ? event->subject : "",
256 				event->location ? event->location : "",
257 				event->is_meeting);
258 }
259 
260 #define ENVIRONMENT_TIMEZONE "TZ"
261 
262 static gchar *
sipe_switch_tz(const char * tz)263 sipe_switch_tz(const char *tz)
264 {
265 	gchar *tz_orig;
266 
267 	tz_orig = g_strdup(g_getenv(ENVIRONMENT_TIMEZONE));
268 	g_setenv(ENVIRONMENT_TIMEZONE, tz, TRUE);
269 	tzset();
270 	return(tz_orig);
271 }
272 
273 static void
sipe_reset_tz(gchar * tz_orig)274 sipe_reset_tz(gchar *tz_orig)
275 {
276 	if (tz_orig) {
277 		g_setenv(ENVIRONMENT_TIMEZONE, tz_orig, TRUE);
278 		g_free(tz_orig);
279 	} else {
280 		g_unsetenv(ENVIRONMENT_TIMEZONE);
281 	}
282 	tzset();
283 }
284 
285 /**
286  * Converts struct tm to Epoch time_t considering timezone.
287  *
288  * @param tz as defined for TZ environment variable.
289  *
290  * Reference: see timegm(3) - Linux man page
291  */
292 time_t
sipe_mktime_tz(struct tm * tm,const char * tz)293 sipe_mktime_tz(struct tm *tm,
294 	       const char* tz)
295 {
296 	time_t ret;
297 	gchar *tz_orig;
298 
299 	tz_orig = sipe_switch_tz(tz);
300 	ret = mktime(tm);
301 	sipe_reset_tz(tz_orig);
302 
303 	return ret;
304 }
305 
306 /**
307  * Converts Epoch time_t to struct tm considering timezone.
308  *
309  * @param tz as defined for TZ environment variable.
310  *
311  * Reference: see timegm(3) - Linux man page
312  */
313 static struct tm *
sipe_localtime_tz(const time_t * time,const char * tz)314 sipe_localtime_tz(const time_t *time,
315 		  const char* tz)
316 {
317 	struct tm *ret;
318 	gchar *tz_orig;
319 
320 	tz_orig = sipe_switch_tz(tz);
321 	ret = localtime(time);
322 	sipe_reset_tz(tz_orig);
323 
324 	return ret;
325 }
326 
327 void
sipe_cal_free_working_hours(struct sipe_cal_working_hours * wh)328 sipe_cal_free_working_hours(struct sipe_cal_working_hours *wh)
329 {
330 	if (!wh) return;
331 
332 	g_free(wh->std.time);
333 	g_free(wh->std.day_of_week);
334 	g_free(wh->std.year);
335 
336 	g_free(wh->dst.time);
337 	g_free(wh->dst.day_of_week);
338 	g_free(wh->dst.year);
339 
340 	g_free(wh->days_of_week);
341 	g_free(wh->tz);
342 	g_free(wh->tz_std);
343 	g_free(wh->tz_dst);
344 	g_free(wh);
345 }
346 
347 /**
348  * Returns time_t of daylight savings time start/end
349  * in the provided timezone or otherwise
350  * (time_t)-1 if no daylight savings time.
351  */
352 static time_t
sipe_cal_get_std_dst_time(time_t now,int bias,struct sipe_cal_std_dst * std_dst,struct sipe_cal_std_dst * dst_std)353 sipe_cal_get_std_dst_time(time_t now,
354 			  int bias,
355 			  struct sipe_cal_std_dst* std_dst,
356 			  struct sipe_cal_std_dst* dst_std)
357 {
358 	struct tm switch_tm;
359 	time_t res = TIME_NULL;
360 	struct tm *gm_now_tm;
361 	gchar **time_arr;
362 
363 	if (std_dst->month == 0) return TIME_NULL;
364 
365 	gm_now_tm = gmtime(&now);
366 	time_arr = g_strsplit(std_dst->time, ":", 0);
367 
368 	switch_tm.tm_sec  = atoi(time_arr[2]);
369 	switch_tm.tm_min  = atoi(time_arr[1]);
370 	switch_tm.tm_hour = atoi(time_arr[0]);
371 	g_strfreev(time_arr);
372 	switch_tm.tm_mday  = std_dst->year ? std_dst->day_order : 1 /* to adjust later */ ;
373 	switch_tm.tm_mon   = std_dst->month - 1;
374 	switch_tm.tm_year  = std_dst->year ? atoi(std_dst->year) - 1900 : gm_now_tm->tm_year;
375 	switch_tm.tm_isdst = 0;
376 	/* to set tm_wday */
377 	res = sipe_mktime_tz(&switch_tm, "UTC");
378 
379 	/* if not dynamic, calculate right tm_mday */
380 	if (!std_dst->year) {
381 		int switch_wday = sipe_cal_get_wday(std_dst->day_of_week);
382 		int needed_month;
383 		/* get first desired wday in the month */
384 		int delta = switch_wday >= switch_tm.tm_wday ? (switch_wday - switch_tm.tm_wday) : (switch_wday + 7 - switch_tm.tm_wday);
385 		switch_tm.tm_mday = 1 + delta;
386 		/* try nth order */
387 		switch_tm.tm_mday += (std_dst->day_order - 1) * 7;
388 		needed_month = switch_tm.tm_mon;
389 		/* to set settle date if ahead of allowed month dates */
390 		res = sipe_mktime_tz(&switch_tm, "UTC");
391 		if (needed_month != switch_tm.tm_mon) {
392 			/* moving 1 week back to stay within required month */
393 			switch_tm.tm_mday -= 7;
394 			/* to fix date again */
395 			res = sipe_mktime_tz(&switch_tm, "UTC");
396 		}
397 	}
398 	/* note: bias is taken from "switch to" structure */
399 	return res + (bias + dst_std->bias)*60;
400 }
401 
402 static void
sipe_cal_parse_std_dst(const sipe_xml * xn_std_dst_time,struct sipe_cal_std_dst * std_dst)403 sipe_cal_parse_std_dst(const sipe_xml *xn_std_dst_time,
404 		       struct sipe_cal_std_dst *std_dst)
405 {
406 	const sipe_xml *node;
407 	gchar *tmp;
408 
409 	if (!xn_std_dst_time) return;
410 	if (!std_dst) return;
411 /*
412     <StandardTime>
413       <Bias>0</Bias>
414       <Time>02:00:00</Time>
415       <DayOrder>1</DayOrder>
416       <Month>11</Month>
417       <Year>2009</Year>
418       <DayOfWeek>Sunday</DayOfWeek>
419     </StandardTime>
420 */
421 
422 	if ((node = sipe_xml_child(xn_std_dst_time, "Bias"))) {
423 		std_dst->bias = atoi(tmp = sipe_xml_data(node));
424 		g_free(tmp);
425 	}
426 
427 	if ((node = sipe_xml_child(xn_std_dst_time, "Time"))) {
428 		std_dst->time = sipe_xml_data(node);
429 	}
430 
431 	if ((node = sipe_xml_child(xn_std_dst_time, "DayOrder"))) {
432 		std_dst->day_order = atoi(tmp = sipe_xml_data(node));
433 		g_free(tmp);
434 	}
435 
436 	if ((node = sipe_xml_child(xn_std_dst_time, "Month"))) {
437 		std_dst->month = atoi(tmp = sipe_xml_data(node));
438 		g_free(tmp);
439 	}
440 
441 	if ((node = sipe_xml_child(xn_std_dst_time, "DayOfWeek"))) {
442 		std_dst->day_of_week = sipe_xml_data(node);
443 	}
444 
445 	if ((node = sipe_xml_child(xn_std_dst_time, "Year"))) {
446 		std_dst->year = sipe_xml_data(node);
447 	}
448 }
449 
450 void
sipe_cal_parse_working_hours(const sipe_xml * xn_working_hours,struct sipe_buddy * buddy)451 sipe_cal_parse_working_hours(const sipe_xml *xn_working_hours,
452 			     struct sipe_buddy *buddy)
453 {
454 	const sipe_xml *xn_bias;
455 	const sipe_xml *xn_timezone;
456 	const sipe_xml *xn_working_period;
457 	const sipe_xml *xn_standard_time;
458 	const sipe_xml *xn_daylight_time;
459 	gchar *tmp;
460 	time_t now = time(NULL);
461 	struct sipe_cal_std_dst* std;
462 	struct sipe_cal_std_dst* dst;
463 
464 	if (!xn_working_hours) return;
465 /*
466 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
467   <TimeZone>
468     <Bias>480</Bias>
469     ...
470   </TimeZone>
471   <WorkingPeriodArray>
472     <WorkingPeriod>
473       <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
474       <StartTimeInMinutes>600</StartTimeInMinutes>
475       <EndTimeInMinutes>1140</EndTimeInMinutes>
476     </WorkingPeriod>
477   </WorkingPeriodArray>
478 </WorkingHours>
479 */
480 	sipe_cal_free_working_hours(buddy->cal_working_hours);
481 	buddy->cal_working_hours = g_new0(struct sipe_cal_working_hours, 1);
482 
483 	xn_timezone = sipe_xml_child(xn_working_hours, "TimeZone");
484 	xn_bias = sipe_xml_child(xn_timezone, "Bias");
485 	if (xn_bias) {
486 		buddy->cal_working_hours->bias = atoi(tmp = sipe_xml_data(xn_bias));
487 		g_free(tmp);
488 	}
489 
490 	xn_standard_time = sipe_xml_child(xn_timezone, "StandardTime");
491 	xn_daylight_time = sipe_xml_child(xn_timezone, "DaylightTime");
492 
493 	std = &((*buddy->cal_working_hours).std);
494 	dst = &((*buddy->cal_working_hours).dst);
495 	sipe_cal_parse_std_dst(xn_standard_time, std);
496 	sipe_cal_parse_std_dst(xn_daylight_time, dst);
497 
498 	xn_working_period = sipe_xml_child(xn_working_hours, "WorkingPeriodArray/WorkingPeriod");
499 	if (xn_working_period) {
500 		/* NOTE: this can be NULL! */
501 		buddy->cal_working_hours->days_of_week =
502 			sipe_xml_data(sipe_xml_child(xn_working_period, "DayOfWeek"));
503 
504 		buddy->cal_working_hours->start_time =
505 			atoi(tmp = sipe_xml_data(sipe_xml_child(xn_working_period, "StartTimeInMinutes")));
506 		g_free(tmp);
507 
508 		buddy->cal_working_hours->end_time =
509 			atoi(tmp = sipe_xml_data(sipe_xml_child(xn_working_period, "EndTimeInMinutes")));
510 		g_free(tmp);
511 	}
512 
513 	std->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, std, dst);
514 	dst->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, dst, std);
515 
516 	/* TST8TDT7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
517 	buddy->cal_working_hours->tz =
518 		g_strdup_printf("TST%dTDT%d,M%d.%d.%d/%s,M%d.%d.%d/%s",
519 				(buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60,
520 				(buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60,
521 
522 				buddy->cal_working_hours->dst.month,
523 				buddy->cal_working_hours->dst.day_order,
524 				sipe_cal_get_wday(buddy->cal_working_hours->dst.day_of_week),
525 				buddy->cal_working_hours->dst.time,
526 
527 				buddy->cal_working_hours->std.month,
528 				buddy->cal_working_hours->std.day_order,
529 				sipe_cal_get_wday(buddy->cal_working_hours->std.day_of_week),
530 				buddy->cal_working_hours->std.time
531 				);
532 	/* TST8 */
533 	buddy->cal_working_hours->tz_std =
534 		g_strdup_printf("TST%d",
535 				(buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60);
536 	/* TDT7 */
537 	buddy->cal_working_hours->tz_dst =
538 		g_strdup_printf("TDT%d",
539 				(buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60);
540 }
541 
542 struct sipe_cal_event*
sipe_cal_get_event(GSList * cal_events,time_t time_in_question)543 sipe_cal_get_event(GSList *cal_events,
544 		   time_t time_in_question)
545 {
546 	GSList *entry = cal_events;
547 	struct sipe_cal_event* cal_event;
548 	struct sipe_cal_event* res = NULL;
549 
550 	if (!cal_events || !IS(time_in_question)) return NULL;
551 
552 	while (entry) {
553 		cal_event = entry->data;
554 		/* event is in the past or in the future */
555 		if (cal_event->start_time >  time_in_question  ||
556 		    cal_event->end_time   <= time_in_question)
557 		{
558 			entry = entry->next;
559 			continue;
560 		}
561 
562 		if (!res) {
563 			res = cal_event;
564 		} else {
565 			int res_status = (res->cal_status == SIPE_CAL_NO_DATA) ? -1 : res->cal_status;
566 			int cal_status = (cal_event->cal_status == SIPE_CAL_NO_DATA) ? -1 : cal_event->cal_status;
567 			if (res_status < cal_status) {
568 				res = cal_event;
569 			}
570 		}
571 		entry = entry->next;
572 	}
573 	return res;
574 }
575 
576 static int
sipe_cal_get_status0(const gchar * free_busy,time_t cal_start,int granularity,time_t time_in_question,int * index)577 sipe_cal_get_status0(const gchar *free_busy,
578 		     time_t cal_start,
579 		     int granularity,
580 		     time_t time_in_question,
581 		     int *index)
582 {
583 	int res = SIPE_CAL_NO_DATA;
584 	int shift;
585 	time_t cal_end = cal_start + strlen(free_busy)*granularity*60 - 1;
586 
587 	if (!(time_in_question >= cal_start && time_in_question <= cal_end)) return res;
588 
589 	shift = (time_in_question - cal_start) / (granularity*60);
590 	if (index) {
591 		*index = shift;
592 	}
593 
594 	res = free_busy[shift] - '0';
595 
596 	return res;
597 }
598 
599 /**
600  * Returns time when current calendar state started
601  */
602 static time_t
sipe_cal_get_since_time(const gchar * free_busy,time_t calStart,int granularity,int index,int current_state)603 sipe_cal_get_since_time(const gchar *free_busy,
604 			time_t calStart,
605 			int granularity,
606 			int index,
607 			int current_state)
608 {
609 	int i;
610 
611 	if ((index < 0) || ((size_t)(index + 1) > strlen(free_busy))) return 0;
612 
613 	for (i = index; i >= 0; i--) {
614 		int temp_status = free_busy[i] - '0';
615 
616 		if (current_state != temp_status) {
617 			return calStart + (i + 1)*granularity*60;
618 		}
619 	}
620 
621 	return calStart;
622 }
623 
624 static char*
625 sipe_cal_get_free_busy(struct sipe_buddy *buddy);
626 
627 int
sipe_cal_get_status(struct sipe_buddy * buddy,time_t time_in_question,time_t * since)628 sipe_cal_get_status(struct sipe_buddy *buddy,
629 		    time_t time_in_question,
630 		    time_t *since)
631 {
632 	time_t cal_start;
633 	const char* free_busy;
634 	int ret = SIPE_CAL_NO_DATA;
635 	time_t state_since;
636 	int index = -1;
637 
638 	if (!buddy || !buddy->cal_start_time || !buddy->cal_granularity) {
639 		SIPE_DEBUG_INFO("sipe_cal_get_status: no calendar data1 for %s, exiting",
640 				  buddy ? (buddy->name ? buddy->name : "") : "");
641 		return SIPE_CAL_NO_DATA;
642 	}
643 
644 	if (!(free_busy = sipe_cal_get_free_busy(buddy))) {
645 		SIPE_DEBUG_INFO("sipe_cal_get_status: no calendar data2 for %s, exiting", buddy->name);
646 		return SIPE_CAL_NO_DATA;
647 	}
648 	SIPE_DEBUG_INFO("sipe_cal_get_description: buddy->cal_free_busy=\n%s", free_busy);
649 
650 	cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
651 
652 	ret = sipe_cal_get_status0(free_busy,
653 				   cal_start,
654 				   buddy->cal_granularity,
655 				   time_in_question,
656 				   &index);
657 	state_since = sipe_cal_get_since_time(free_busy,
658 					      cal_start,
659 					      buddy->cal_granularity,
660 					      index,
661 					      ret);
662 
663 	if (since) *since = state_since;
664 	return ret;
665 }
666 
667 static time_t
sipe_cal_get_switch_time(const gchar * free_busy,time_t calStart,int granularity,int index,int current_state,int * to_state)668 sipe_cal_get_switch_time(const gchar *free_busy,
669 			 time_t calStart,
670 			 int granularity,
671 			 int index,
672 			 int current_state,
673 			 int *to_state)
674 {
675 	size_t i;
676 	time_t ret = TIME_NULL;
677 
678 	if ((index < 0) || ((size_t) (index + 1) > strlen(free_busy))) {
679 		*to_state = SIPE_CAL_NO_DATA;
680 		return ret;
681 	}
682 
683 	for (i = index + 1; i < strlen(free_busy); i++) {
684 		int temp_status = free_busy[i] - '0';
685 
686 		if (current_state != temp_status) {
687 			*to_state = temp_status;
688 			return calStart + i*granularity*60;
689 		}
690 	}
691 
692 	return ret;
693 }
694 
695 static const char*
sipe_cal_get_tz(struct sipe_cal_working_hours * wh,time_t time_in_question)696 sipe_cal_get_tz(struct sipe_cal_working_hours *wh,
697                 time_t time_in_question)
698 {
699 	time_t dst_switch_time = (*wh).dst.switch_time;
700 	time_t std_switch_time = (*wh).std.switch_time;
701 	gboolean is_dst = FALSE;
702 
703 	/* No daylight savings */
704 	if (dst_switch_time == TIME_NULL) {
705 		return wh->tz_std;
706 	}
707 
708 	if (dst_switch_time < std_switch_time) { /* North hemosphere - Europe, US */
709 		if (time_in_question >= dst_switch_time && time_in_question < std_switch_time) {
710 			is_dst = TRUE;
711 		}
712 	} else { /* South hemisphere - Australia */
713 		if (time_in_question >= dst_switch_time || time_in_question < std_switch_time) {
714 			is_dst = TRUE;
715 		}
716 	}
717 
718 	if (is_dst) {
719 		return wh->tz_dst;
720 	} else {
721 		return wh->tz_std;
722 	}
723 }
724 
725 static time_t
sipe_cal_mktime_of_day(struct tm * sample_today_tm,const int shift_minutes,const char * tz)726 sipe_cal_mktime_of_day(struct tm *sample_today_tm,
727 		       const int shift_minutes,
728 		       const char *tz)
729 {
730 	sample_today_tm->tm_sec  = 0;
731 	sample_today_tm->tm_min  = shift_minutes % 60;
732 	sample_today_tm->tm_hour = shift_minutes / 60;
733 
734 	return sipe_mktime_tz(sample_today_tm, tz);
735 }
736 
737 /**
738  * Returns work day start and end in Epoch time
739  * considering the initial values are provided
740  * in contact's local time zone.
741  */
742 static void
sipe_cal_get_today_work_hours(struct sipe_cal_working_hours * wh,time_t * start,time_t * end,time_t * next_start)743 sipe_cal_get_today_work_hours(struct sipe_cal_working_hours *wh,
744 			      time_t *start,
745 			      time_t *end,
746 			      time_t *next_start)
747 {
748 	time_t now = time(NULL);
749 	const char *tz = sipe_cal_get_tz(wh, now);
750 	struct tm *remote_now_tm = sipe_localtime_tz(&now, tz);
751 
752 	if (!(wh->days_of_week && strstr(wh->days_of_week, wday_names[remote_now_tm->tm_wday]))) {
753 		/* not a work day */
754 		*start = TIME_NULL;
755 		*end = TIME_NULL;
756 		*next_start = TIME_NULL;
757 		return;
758 	}
759 
760 	*end = sipe_cal_mktime_of_day(remote_now_tm, wh->end_time, tz);
761 
762 	if (now < *end) {
763 		*start = sipe_cal_mktime_of_day(remote_now_tm, wh->start_time, tz);
764 		*next_start = TIME_NULL;
765 	} else { /* calculate start of tomorrow's work day if any */
766 		time_t tom = now + 24*60*60;
767 		struct tm *remote_tom_tm = sipe_localtime_tz(&tom, sipe_cal_get_tz(wh, tom));
768 
769 		if (!(wh->days_of_week && strstr(wh->days_of_week, wday_names[remote_tom_tm->tm_wday]))) {
770 			/* not a work day */
771 			*next_start = TIME_NULL;
772 		}
773 
774 		*next_start = sipe_cal_mktime_of_day(remote_tom_tm, wh->start_time, sipe_cal_get_tz(wh, tom));
775 		*start = TIME_NULL;
776 	}
777 }
778 
779 static int
sipe_cal_is_in_work_hours(const time_t time_in_question,const time_t start,const time_t end)780 sipe_cal_is_in_work_hours(const time_t time_in_question,
781 			  const time_t start,
782 			  const time_t end)
783 {
784 	return !((time_in_question >= end) || (IS(start) && time_in_question < start));
785 }
786 
787 /**
788  * Returns time closest to now. Choses only from times ahead of now.
789  * Returns TIME_NULL otherwise.
790  */
791 static time_t
sipe_cal_get_until(const time_t now,const time_t switch_time,const time_t start,const time_t end,const time_t next_start)792 sipe_cal_get_until(const time_t now,
793 		   const time_t switch_time,
794 		   const time_t start,
795 		   const time_t end,
796 		   const time_t next_start)
797 {
798 	time_t ret = TIME_NULL;
799 	int min_diff = now - ret;
800 
801 	if (IS(switch_time) && switch_time > now && (switch_time - now) < min_diff) {
802 		min_diff = switch_time - now;
803 		ret = switch_time;
804 	}
805 	if (IS(start) && start > now && (start - now) < min_diff) {
806 		min_diff = start - now;
807 		ret = start;
808 	}
809 	if (IS(end) && end > now && (end - now) < min_diff) {
810 		min_diff = end - now;
811 		ret = end;
812 	}
813 	if (IS(next_start) && next_start > now && (next_start - now) < min_diff) {
814 		min_diff = next_start - now;
815 		ret = next_start;
816 	}
817 	return ret;
818 }
819 
820 static char*
sipe_cal_get_free_busy(struct sipe_buddy * buddy)821 sipe_cal_get_free_busy(struct sipe_buddy *buddy)
822 {
823 /* do lazy decode if necessary */
824 	if (!buddy->cal_free_busy && buddy->cal_free_busy_base64) {
825 		gsize cal_dec64_len;
826 		guchar *cal_dec64;
827 		gsize i;
828 		int j = 0;
829 
830 		cal_dec64 = g_base64_decode(buddy->cal_free_busy_base64, &cal_dec64_len);
831 
832 		buddy->cal_free_busy = g_malloc0(cal_dec64_len * 4 + 1);
833 /*
834    http://msdn.microsoft.com/en-us/library/dd941537%28office.13%29.aspx
835 		00, Free (Fr)
836 		01, Tentative (Te)
837 		10, Busy (Bu)
838 		11, Out of facility (Oo)
839 
840    http://msdn.microsoft.com/en-us/library/aa566048.aspx
841 		0  Free
842 		1  Tentative
843 		2  Busy
844 		3  Out of Office (OOF)
845 		4  No data
846 */
847 		for (i = 0; i < cal_dec64_len; i++) {
848 #define TWO_BIT_MASK	0x03
849 			char tmp = cal_dec64[i];
850 			buddy->cal_free_busy[j++] = (tmp & TWO_BIT_MASK) + '0';
851 			buddy->cal_free_busy[j++] = ((tmp >> 2) & TWO_BIT_MASK) + '0';
852 			buddy->cal_free_busy[j++] = ((tmp >> 4) & TWO_BIT_MASK) + '0';
853 			buddy->cal_free_busy[j++] = ((tmp >> 6) & TWO_BIT_MASK) + '0';
854 		}
855 		buddy->cal_free_busy[j++] = '\0';
856 		g_free(cal_dec64);
857 	}
858 
859 	return buddy->cal_free_busy;
860 }
861 
862 char *
sipe_cal_get_freebusy_base64(const char * freebusy_hex)863 sipe_cal_get_freebusy_base64(const char* freebusy_hex)
864 {
865 	guint i = 0;
866 	guint j = 0;
867 	guint shift_factor = 0;
868 	guint len, res_len;
869 	guchar *res;
870 	gchar *res_base64;
871 
872 	if (!freebusy_hex) return NULL;
873 
874 	len = strlen(freebusy_hex);
875 	res_len = len / 4 + 1;
876 	res = g_malloc0(res_len);
877 	while (i < len) {
878 		res[j] |= (freebusy_hex[i++] - '0') << shift_factor;
879 		shift_factor += 2;
880 		if (shift_factor == 8) {
881 			shift_factor = 0;
882 			j++;
883 		}
884 	}
885 
886 	res_base64 = g_base64_encode(res, shift_factor ? res_len : res_len - 1);
887 	g_free(res);
888 	return res_base64;
889 }
890 
891 char *
sipe_cal_get_description(struct sipe_buddy * buddy)892 sipe_cal_get_description(struct sipe_buddy *buddy)
893 {
894 	time_t cal_start;
895 	time_t cal_end;
896 	int current_cal_state;
897 	time_t now = time(NULL);
898 	time_t start = TIME_NULL;
899 	time_t end = TIME_NULL;
900 	time_t next_start = TIME_NULL;
901 	time_t switch_time;
902 	int to_state = SIPE_CAL_NO_DATA;
903 	time_t until = TIME_NULL;
904 	int index = 0;
905 	gboolean has_working_hours = (buddy->cal_working_hours != NULL);
906 	const char *free_busy;
907 	const char *cal_states[] = {_("Free"),
908 				    _("Tentative"),
909 				    _("Busy"),
910 				    _("Out of office"),
911 				    _("No data")};
912 
913 	if (buddy->cal_granularity != 15) {
914 		SIPE_DEBUG_INFO("sipe_cal_get_description: granularity %d is unsupported, exiting.", buddy->cal_granularity);
915 		return NULL;
916 	}
917 
918 	/* to lazy load if needed */
919 	free_busy = sipe_cal_get_free_busy(buddy);
920 	SIPE_DEBUG_INFO("sipe_cal_get_description: buddy->cal_free_busy=\n%s", free_busy ? free_busy : "");
921 
922 	if (!buddy->cal_free_busy || !buddy->cal_granularity || !buddy->cal_start_time) {
923 		SIPE_DEBUG_INFO_NOFORMAT("sipe_cal_get_description: no calendar data, exiting");
924 		return NULL;
925 	}
926 
927 	cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
928 	cal_end = cal_start + 60 * (buddy->cal_granularity) * strlen(buddy->cal_free_busy);
929 
930 	current_cal_state = sipe_cal_get_status0(free_busy, cal_start, buddy->cal_granularity, time(NULL), &index);
931 	if (current_cal_state == SIPE_CAL_NO_DATA) {
932 		SIPE_DEBUG_INFO_NOFORMAT("sipe_cal_get_description: calendar is undefined for present moment, exiting.");
933 		return NULL;
934 	}
935 
936 	switch_time = sipe_cal_get_switch_time(free_busy, cal_start, buddy->cal_granularity, index, current_cal_state, &to_state);
937 
938 	SIPE_DEBUG_INFO_NOFORMAT("\n* Calendar *");
939 	if (buddy->cal_working_hours) {
940 		sipe_cal_get_today_work_hours(buddy->cal_working_hours, &start, &end, &next_start);
941 
942 		SIPE_DEBUG_INFO("Remote now timezone : %s", sipe_cal_get_tz(buddy->cal_working_hours, now));
943 		SIPE_DEBUG_INFO("std.switch_time(GMT): %s",
944 				IS((*buddy->cal_working_hours).std.switch_time) ? sipe_utils_time_to_debug_str(gmtime(&((*buddy->cal_working_hours).std.switch_time))) : "");
945 		SIPE_DEBUG_INFO("dst.switch_time(GMT): %s",
946 				IS((*buddy->cal_working_hours).dst.switch_time) ? sipe_utils_time_to_debug_str(gmtime(&((*buddy->cal_working_hours).dst.switch_time))) : "");
947 		SIPE_DEBUG_INFO("Remote now time     : %s",
948 			sipe_utils_time_to_debug_str(sipe_localtime_tz(&now, sipe_cal_get_tz(buddy->cal_working_hours, now))));
949 		SIPE_DEBUG_INFO("Remote start time   : %s",
950 			IS(start) ? sipe_utils_time_to_debug_str(sipe_localtime_tz(&start, sipe_cal_get_tz(buddy->cal_working_hours, start))) : "");
951 		SIPE_DEBUG_INFO("Remote end time     : %s",
952 			IS(end) ? sipe_utils_time_to_debug_str(sipe_localtime_tz(&end, sipe_cal_get_tz(buddy->cal_working_hours, end))) : "");
953 		SIPE_DEBUG_INFO("Rem. next_start time: %s",
954 			IS(next_start) ? sipe_utils_time_to_debug_str(sipe_localtime_tz(&next_start, sipe_cal_get_tz(buddy->cal_working_hours, next_start))) : "");
955 		SIPE_DEBUG_INFO("Remote switch time  : %s",
956 			IS(switch_time) ? sipe_utils_time_to_debug_str(sipe_localtime_tz(&switch_time, sipe_cal_get_tz(buddy->cal_working_hours, switch_time))) : "");
957 	} else {
958 		SIPE_DEBUG_INFO("Local now time      : %s",
959 			sipe_utils_time_to_debug_str(localtime(&now)));
960 		SIPE_DEBUG_INFO("Local switch time   : %s",
961 			IS(switch_time) ? sipe_utils_time_to_debug_str(localtime(&switch_time)) : "");
962 	}
963 	SIPE_DEBUG_INFO("Calendar End (GMT)  : %s", sipe_utils_time_to_debug_str(gmtime(&cal_end)));
964 	SIPE_DEBUG_INFO("current cal state   : %s", cal_states[current_cal_state]);
965 	SIPE_DEBUG_INFO("switch  cal state   : %s", cal_states[to_state]         );
966 
967 	/* Calendar: string calculations */
968 
969 	/*
970 	ALGORITHM (don't delete)
971 	(c)2009,2010 pier11 <pier11@operamail.com>
972 
973 	SOD =  Start of Work Day
974 	EOD =  End of Work Day
975 	NSOD = Start of tomorrow's Work Day
976 	SW =   Calendar status switch time
977 
978 	if current_cal_state == Free
979 		until = min_t of SOD, EOD, NSOD, SW (min_t(x) = min(x-now) where x>now only)
980 	else
981 		until = SW
982 
983 	if (!until && (cal_period_end > now + 8H))
984 		until = cal_period_end
985 
986 	if (!until)
987 		return "Currently %", current_cal_state
988 
989 	if (until - now > 8H)
990 		if (current_cal_state == Free && (work_hours && !in work_hours(now)))
991 			return "Outside of working hours for next 8 hours"
992 		else
993 			return "%s for next 8 hours", current_cal_state
994 
995 	if (current_cal_state == Free)
996 		if (work_hours && until !in work_hours(now))
997 			"Not working"
998 		else
999 			"%s", current_cal_state
1000 		" until %.2d:%.2d", until
1001 	else
1002 		"Currently %", current_cal_state
1003 		if (work_hours && until !in work_hours(until))
1004 			". Outside of working hours at at %.2d:%.2d", until
1005 		else
1006 			". %s at %.2d:%.2d", to_state, until
1007 	*/
1008 
1009 	if (current_cal_state < 1) { /* Free */
1010 		until = sipe_cal_get_until(now, switch_time, start, end, next_start);
1011 	} else {
1012 		until = switch_time;
1013 	}
1014 
1015 	if (!IS(until) && (cal_end - now > 8*60*60))
1016 		until = cal_end;
1017 
1018 	if (!IS(until)) {
1019 		return g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
1020 	}
1021 
1022 	if (until - now > 8*60*60) {
1023 		/* Free & outside work hours */
1024 		if (current_cal_state < 1 && has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
1025 			return g_strdup(_("Outside of working hours for next 8 hours"));
1026 		} else {
1027 			return g_strdup_printf(_("%s for next 8 hours"), cal_states[current_cal_state]);
1028 		}
1029 	}
1030 
1031 	if (current_cal_state < 1) { /* Free */
1032 		const char *tmp;
1033 		struct tm *until_tm = localtime(&until);
1034 
1035 		if (has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
1036 			tmp = _("Not working");
1037 		} else {
1038 			tmp = cal_states[current_cal_state];
1039 		}
1040 		return g_strdup_printf(_("%s until %.2d:%.2d"), tmp, until_tm->tm_hour, until_tm->tm_min);
1041 	} else { /* Tentative or Busy or OOF */
1042 		char *tmp;
1043 		char *res;
1044 		struct tm *until_tm = localtime(&until);
1045 
1046 		tmp = g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
1047 		if (has_working_hours && !sipe_cal_is_in_work_hours(until, start, end)) {
1048 			res = g_strdup_printf(_("%s. Outside of working hours at %.2d:%.2d"),
1049 					      tmp, until_tm->tm_hour, until_tm->tm_min);
1050 			g_free(tmp);
1051 			return res;
1052 		} else {
1053 			res = g_strdup_printf(_("%s. %s at %.2d:%.2d"), tmp, cal_states[to_state], until_tm->tm_hour, until_tm->tm_min);
1054 			g_free(tmp);
1055 			return res;
1056 		}
1057 	}
1058 	/* End of - Calendar: string calculations */
1059 }
1060 
1061 #define UPDATE_CALENDAR_INTERVAL (15*60) /* 15 min, default granularity for Exchange */
1062 #define UPDATE_CALENDAR_OFFSET       30  /* 30 seconds before next interval starts */
1063 
sipe_cal_update_cb(struct sipe_core_private * sipe_private,SIPE_UNUSED_PARAMETER gpointer data)1064 static void sipe_cal_update_cb(struct sipe_core_private *sipe_private,
1065 			       SIPE_UNUSED_PARAMETER gpointer data)
1066 {
1067 	sipe_core_update_calendar(SIPE_CORE_PUBLIC);
1068 }
1069 
sipe_core_update_calendar(struct sipe_core_public * sipe_public)1070 void sipe_core_update_calendar(struct sipe_core_public *sipe_public)
1071 {
1072 	time_t now, offset;
1073 
1074 	SIPE_LOG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
1075 
1076 	/* Do in parallel.
1077 	 * If failed, the branch will be disabled for subsequent calls.
1078 	 * Can't rely that user turned the functionality on in account settings.
1079 	 */
1080 	sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
1081 #ifdef _WIN32
1082 	/* @TODO: UNIX integration missing */
1083 	sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
1084 #endif
1085 
1086 	/* how long, in seconds, until the next calendar interval starts? */
1087 	now    = time(NULL);
1088 	offset = (now / UPDATE_CALENDAR_INTERVAL + 1) * UPDATE_CALENDAR_INTERVAL - now;
1089 
1090 	/* ensure that the update after the initial one is not too soon */
1091 	if (offset <= (UPDATE_CALENDAR_INTERVAL / 2))
1092 		offset += UPDATE_CALENDAR_INTERVAL;
1093 
1094 	/* schedule next update before a new calendar interval starts */
1095 	sipe_schedule_seconds(SIPE_CORE_PRIVATE,
1096 			      "<+update-calendar>",
1097 			      NULL,
1098 			      offset - UPDATE_CALENDAR_OFFSET,
1099 			      sipe_cal_update_cb,
1100 			      NULL);
1101 
1102 	SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
1103 }
1104 
sipe_cal_presence_publish(struct sipe_core_private * sipe_private,gboolean do_publish_calendar)1105 void sipe_cal_presence_publish(struct sipe_core_private *sipe_private,
1106 			       gboolean do_publish_calendar)
1107 {
1108 	if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1109 		if (do_publish_calendar)
1110 			sipe_ocs2007_presence_publish(sipe_private, NULL);
1111 		else
1112 			sipe_ocs2007_category_publish(sipe_private, FALSE);
1113 	} else {
1114 		sipe_ocs2005_presence_publish(sipe_private,
1115 					      do_publish_calendar);
1116 	}
1117 }
1118 
sipe_cal_delayed_calendar_update(struct sipe_core_private * sipe_private)1119 void sipe_cal_delayed_calendar_update(struct sipe_core_private *sipe_private)
1120 {
1121 #define UPDATE_CALENDAR_DELAY		1*60	/* 1 min */
1122 
1123 	/* only start periodic calendar updating if user hasn't disabled it */
1124 	if (!SIPE_CORE_PUBLIC_FLAG_IS(DONT_PUBLISH))
1125 		sipe_schedule_seconds(sipe_private,
1126 				      "<+update-calendar>",
1127 				      NULL,
1128 				      UPDATE_CALENDAR_DELAY,
1129 				      sipe_cal_update_cb,
1130 				      NULL);
1131 }
1132 
1133 /*
1134   Local Variables:
1135   mode: c
1136   c-file-style: "bsd"
1137   indent-tabs-mode: t
1138   tab-width: 8
1139   End:
1140 */
1141