HEAD: merged recent changes from r3_80.
[wvapps.git] / wvmapi / wvtnefconv.cc
blobf338285b33d443a3eaeec89dbeba81dd8c52f2cf
1 #include "wvtnefconv.h"
2 #include "wvtnefconv_private.h"
4 #include "strutils.h"
5 #include "wvstringlist.h"
7 #include <string>
8 #include <time.h>
9 #include <ctype.h>
11 /* This is a collection of functions used for converting
12 * TNEFs into VCards/VCals/VEvents (etc) and back again,
13 * using WvTnef. */
15 using namespace std;
17 // Reading integers from a buffer
18 uint64_t read8u(WvDynBuf &buf)
20 const uint64_t *ptr = (const uint64_t *)buf.get(8);
22 if (NULL == ptr)
23 return 0;
24 return (uint64_t)swap8(*ptr);
27 uint32_t read4u(WvDynBuf &buf)
29 const uint32_t *ptr = (const uint32_t *)buf.get(4);
31 if (NULL == ptr)
32 return 0;
33 return (uint32_t)swap4(*ptr);
36 uint16_t read2u(WvDynBuf &buf)
38 const uint16_t *ptr = (const uint16_t *)buf.get(2);
40 if (NULL == ptr)
41 return 0;
42 return (uint16_t)swap2(*ptr);
45 int64_t read8(WvDynBuf &buf)
47 const int64_t *ptr = (const int64_t *)buf.get(8);
49 if (NULL == ptr)
50 return 0;
51 return (int64_t)swap8(*ptr);
54 int32_t read4(WvDynBuf &buf)
56 const int32_t *ptr = (const int32_t *)buf.get(4);
58 if (NULL == ptr)
59 return 0;
60 return (int32_t)swap4(*ptr);
63 int16_t read2(WvDynBuf &buf)
65 const int16_t *ptr = (const int16_t *)buf.get(2);
67 if (NULL == ptr)
68 return 0;
69 return (int16_t)swap2(*ptr);
72 // Appending integers to a buffer
73 void append2(uint16_t i, WvDynBuf &buf)
75 i = swap2(i);
76 buf.put(&i, 2);
79 void append4(uint32_t i, WvDynBuf &buf)
81 i = swap4(i);
82 buf.put(&i, 4);
85 void append8(uint64_t i, WvDynBuf &buf)
87 i = swap8(i);
88 buf.put(&i, 8);
91 inline WvString escape_newline(WvStringParm str)
93 WvString s("");
94 for(unsigned int i = 0; i < str.len(); ++i)
95 if(i == '\n')
96 s.append("=0A");
97 else if(i == '\r')
99 else
100 s.append("%c", str[i]);
101 return s;
104 bool is_vcard(const WvTnef &tnef)
106 string const *s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_MESSAGE_CLASS));
107 return (s && WvString("IPM.Contact") == (char *)s->c_str());
110 bool is_vcal(const WvTnef &tnef)
112 string const *s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_MESSAGE_CLASS));
113 return (s && WvString("IPM.Appointment") == (char *)s->c_str());
116 WvString get_vcal(const WvTnef &tnef)
118 if (!is_vcal(tnef))
119 return WvString::null;
121 string defguid = str2guid(GUID_1);
122 string wvidguid = str2guid(WVIDGUID);
124 string const *s;
126 WvString ret("BEGIN:VEVENT\n");
128 s = tnef.get_property(wvidguid, str2ustr("EvoId"));
129 if (s && (*s)[0])
130 ret.append("UID:%s\n", s->c_str());
132 s = tnef.get_property(defguid, COMMON_START);
133 if (s && s->length())
135 ret.append("DTSTART:");
136 Date dtr = get_dateinfo(s);
137 ret.append("%04s%02s%02sT%02s%02s%02s\n",
138 dtr.year, dtr.month, dtr.day,
139 dtr.hour, dtr.minute, dtr.second);
142 s = tnef.get_property(defguid, COMMON_END);
143 if (s && s->length())
145 ret.append("DTEND:");
146 Date dtr = get_dateinfo(s);
147 ret.append("%04s%02s%02sT%02s%02s%02s\n",
148 dtr.year, dtr.month, dtr.day,
149 dtr.hour, dtr.minute, dtr.second);
152 // Hmmm.. always?
153 ret.append("TRANSP:OPAQUE\n");
154 ret.append("SEQUENCE:2\n");
156 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_SUBJECT));
157 if (s && (*s)[0])
158 ret.append("SUMMARY:%s\n", s->c_str());
160 // Hmmm.. always?
161 ret.append("CLASS:PUBLIC\n");
163 s = tnef.get_property(defguid, LOCATION);
164 if (s && (*s)[0])
165 ret.append("LOCATION:%s\n", s->c_str());
167 // ret.append("CATEGORIES:%s\n", tnef.get_property(MAPI_TAG());
169 s = tnef.get_property(MAPI_TAG(PT_SYSTIME, PR_LAST_MODIFICATION_TIME));
170 if (s && s->length())
172 ret.append("LAST-MODIFIED:");
173 Date dtr = get_dateinfo(s);
174 ret.append("%04s%02s%02sT%02s%02s%02s\n",
175 dtr.year, dtr.month, dtr.day,
176 dtr.hour, dtr.minute, dtr.second);
179 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_BODY));
180 if (s && (*s)[0])
181 ret.append("DESCRIPTION:%s\n", s->c_str());
183 // Whee! Recurrences are fun.. mmm'kay?
184 s = tnef.get_property(defguid, RECURRENCE);
185 if (s && s->length())
187 Recurrence recur = get_recurrence(s);
188 ret.append(get_rrule(recur));
189 ret.append(get_exdates(recur));
192 ret.append("END:VEVENT\n");
193 return ret;
196 WvString get_vcard(const WvTnef &tnef)
198 if (!is_vcard(tnef))
199 return WvString::null;
201 string defguid = str2guid(GUID_2);
203 string const *s;
204 WvString ret("BEGIN:VCARD\n"
205 "VERSION:2.1\n");
207 append_to_card(ret, "X-EVOLUTION-FILE-AS:%s\n", defguid, INDEX_NAME, tnef);
208 append_to_card(ret, "FN:%s\n", MAPI_TAG(PT_STRING8, PR_DISPLAY_NAME), tnef);
210 ret.append("N:");
211 append_to_card(ret, "%s", MAPI_TAG(PT_STRING8, PR_SURNAME), tnef);
212 ret.append(";");
213 append_to_card(ret, "%s", MAPI_TAG(PT_STRING8, PR_GIVEN_NAME), tnef);
214 ret.append(";");
215 append_to_card(ret, "%s", MAPI_TAG(PT_STRING8, PR_MIDDLE_NAME), tnef);
216 ret.append(";");
217 append_to_card(ret, "%s", MAPI_TAG(PT_STRING8, PR_DISPLAY_NAME_PREFIX), tnef);
218 ret.append(";");
219 append_to_card(ret, "%s", MAPI_TAG(PT_STRING8, PR_GENERATION), tnef);
220 ret.append("\n");
222 append_to_card(ret, "NOTE:%s\n",
223 MAPI_TAG(PT_STRING8, PR_BODY), tnef);
224 append_to_card(ret, "ROLE:%s\n",
225 MAPI_TAG(PT_STRING8, PR_PROFESSION), tnef);
226 append_to_card(ret, "TITLE:%s\n",
227 MAPI_TAG(PT_STRING8, PR_TITLE), tnef);
228 append_to_card(ret, "NICKNAME:%s\n",
229 MAPI_TAG(PT_STRING8, PR_NICKNAME), tnef);
230 append_to_card(ret, "X-EVOLUTION-SPOUSE:%s\n",
231 MAPI_TAG(PT_STRING8, PR_SPOUSE_NAME), tnef);
232 append_to_card(ret, "X-EVOLUTION-MANAGER:%s\n",
233 MAPI_TAG(PT_STRING8, PR_MANAGER_NAME), tnef);
234 append_to_card(ret, "X-EVOLUTION-ASSISTANT:%s\n",
235 MAPI_TAG(PT_STRING8, PR_ASSISTANT), tnef);
236 append_to_card(ret, "X-EVOLUTION-OFFICE:%s\n",
237 MAPI_TAG(PT_STRING8, PR_OFFICE_LOCATION), tnef);
238 append_to_card(ret, "URL:%s\n", defguid, COMMON_URL, tnef);
240 // Just line up the email addies; Evo makes an EList outta them
241 append_to_card(ret, "EMAIL;INTERNET:%s\n", defguid, EMAIL_1, tnef);
242 append_to_card(ret, "EMAIL;INTERNET:%s\n", defguid, EMAIL_2, tnef);
243 append_to_card(ret, "EMAIL:INTERNET:%s\n", defguid, EMAIL_3, tnef);
245 append_to_card(ret, "FBURL:%s\n", defguid, COMMON_FBURL, tnef);
247 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_COMPANY_NAME));
248 if(s && (*s)[0])
250 ret.append("ORG:%s", s->c_str());
251 string const *s2 = tnef.get_property(MAPI_TAG(PT_STRING8, PR_DEPARTMENT_NAME));
252 if(s2 && (*s2)[0])
253 ret.append(";%s", s2->c_str());
254 ret.append("\n");
257 ret.append("ADR;QUOTED-PRINTABLE;WORK:");
259 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_POST_OFFICE_BOX));
260 if(s && (*s)[0])
261 ret.append("%s", escape_newline(s->c_str()));
262 ret.append(";;");
264 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_STREET_ADDRESS));
265 if(s && (*s)[0])
266 ret.append("%s", escape_newline((char *)s->c_str()));
267 ret.append(";");
269 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_LOCALITY));
270 if(s && (*s)[0])
271 ret.append("%s", escape_newline((char *)s->c_str()));
272 ret.append(";");
274 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_STATE_OR_PROVINCE));
275 if(s && (*s)[0])
276 ret.append("%s", escape_newline((char *)s->c_str()));
277 ret.append(";");
279 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_POSTAL_CODE));
280 if(s && (*s)[0])
281 ret.append("%s", escape_newline((char *)s->c_str()));
282 ret.append(";");
284 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_COUNTRY));
285 if(s && (*s)[0])
286 ret.append("%s", escape_newline((char *)s->c_str()));
287 ret.append("\n");
289 ret.append("ADR;QUOTED-PRINTABLE;HOME:");
291 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_HOME_ADDRESS_POST_OFFICE_BOX));
292 if(s && (*s)[0])
293 ret.append("%s", escape_newline((char *)s->c_str()));
294 ret.append(";;");
296 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_HOME_ADDRESS_STREET));
297 if(s && (*s)[0])
298 ret.append("%s", escape_newline((char *)s->c_str()));
299 ret.append(";");
301 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_HOME_ADDRESS_CITY));
302 if(s && (*s)[0])
303 ret.append("%s", escape_newline((char *)s->c_str()));
304 ret.append(";");
306 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_HOME_ADDRESS_STATE_OR_PROVINCE));
307 if(s && (*s)[0])
308 ret.append("%s", escape_newline((char *)s->c_str()));
309 ret.append(";");
311 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_HOME_ADDRESS_POSTAL_CODE));
312 if(s && (*s)[0])
313 ret.append("%s", escape_newline((char *)s->c_str()));
314 ret.append(";");
316 s = tnef.get_property(MAPI_TAG(PT_STRING8, PR_HOME_ADDRESS_COUNTRY));
317 if(s && (*s)[0])
318 ret.append("%s", escape_newline((char *)s->c_str()));
319 ret.append("\n");
321 ret.append("ADR;QUOTED-PRINTABLE;OTHER:");
323 append_to_card(ret, "%s",
324 MAPI_TAG(PT_STRING8, PR_OTHER_ADDRESS_POST_OFFICE_BOX), tnef);
325 ret.append(";;");
326 append_to_card(ret, "%s",
327 MAPI_TAG(PT_STRING8, PR_OTHER_ADDRESS_STREET), tnef);
328 ret.append(";");
329 append_to_card(ret, "%s",
330 MAPI_TAG(PT_STRING8, PR_OTHER_ADDRESS_CITY), tnef);
331 ret.append(";");
332 append_to_card(ret, "%s",
333 MAPI_TAG(PT_STRING8, PR_OTHER_ADDRESS_STATE_OR_PROVINCE), tnef);
334 ret.append(";");
335 append_to_card(ret, "%s",
336 MAPI_TAG(PT_STRING8, PR_OTHER_ADDRESS_POSTAL_CODE), tnef);
337 ret.append(";");
338 append_to_card(ret, "%s",
339 MAPI_TAG(PT_STRING8, PR_OTHER_ADDRESS_COUNTRY), tnef);
340 ret.append("\n");
342 append_to_card(ret, "TEL;X-EVOLUTION-TTYTDD:%s\n",
343 MAPI_TAG(PT_STRING8, PR_TTYTDD_PHONE_NUMBER), tnef);
344 append_to_card(ret, "TEL;X-EVOLUTION-TELEX:%s\n",
345 MAPI_TAG(PT_STRING8, PR_TELEX_NUMBER), tnef);
346 append_to_card(ret, "TEL;X-EVOLUTION-ASSISTANT:%s\n",
347 MAPI_TAG(PT_STRING8, PR_ASSISTANT_TELEPHONE_NUMBER), tnef);
348 append_to_card(ret, "TEL;X-EVOLUTION-CALLBACK:%s\n",
349 MAPI_TAG(PT_STRING8, PR_CALLBACK_TELEPHONE_NUMBER), tnef);
350 append_to_card(ret, "TEL;X-EVOLUTION-RADIO:%s\n",
351 MAPI_TAG(PT_STRING8, PR_RADIO_TELEPHONE_NUMBER), tnef);
352 append_to_card(ret, "TEL;PREF:%s\n",
353 MAPI_TAG(PT_STRING8, PR_PRIMARY_TELEPHONE_NUMBER), tnef);
354 append_to_card(ret, "TEL;CELL:%s\n",
355 MAPI_TAG(PT_STRING8, PR_MOBILE_TELEPHONE_NUMBER), tnef);
356 append_to_card(ret, "TEL;CAR:%s\n",
357 MAPI_TAG(PT_STRING8, PR_CAR_TELEPHONE_NUMBER), tnef);
358 append_to_card(ret, "TEL;VOICE:%s\n",
359 MAPI_TAG(PT_STRING8, PR_OTHER_TELEPHONE_NUMBER), tnef);
360 append_to_card(ret, "TEL;PAGER:%s\n",
361 MAPI_TAG(PT_STRING8, PR_PAGER_TELEPHONE_NUMBER), tnef);
362 append_to_card(ret, "TEL;ISDN:%s\n",
363 MAPI_TAG(PT_STRING8, PR_ISDN_NUMBER), tnef);
364 append_to_card(ret, "TEL;HOME:%s\n",
365 MAPI_TAG(PT_STRING8, PR_HOME2_TELEPHONE_NUMBER), tnef);
366 append_to_card(ret, "TEL;HOME;VOICE:%s\n",
367 MAPI_TAG(PT_STRING8, PR_HOME_TELEPHONE_NUMBER), tnef);
368 append_to_card(ret, "TEL;WORK:%s\n",
369 MAPI_TAG(PT_STRING8, PR_COMPANY_MAIN_PHONE_NUMBER), tnef);
370 append_to_card(ret, "TEL;WORK;VOICE:%s\n",
371 MAPI_TAG(PT_STRING8, PR_BUSINESS2_TELEPHONE_NUMBER), tnef);
372 append_to_card(ret, "TEL;PREF;FAX:%s\n",
373 MAPI_TAG(PT_STRING8, PR_PRIMARY_FAX_NUMBER), tnef);
374 append_to_card(ret, "TEL;WORK;FAX:%s\n",
375 MAPI_TAG(PT_STRING8, PR_BUSINESS_FAX_NUMBER), tnef);
376 append_to_card(ret, "TEL;HOME;FAX:%s\n",
377 MAPI_TAG(PT_STRING8, PR_HOME_FAX_NUMBER), tnef);
378 append_to_card(ret, "URL:%s\n",
379 MAPI_TAG(PT_STRING8, PR_BUSINESS_HOME_PAGE), tnef);
381 s = tnef.get_property(MAPI_TAG(PT_SYSTIME, PR_BIRTHDAY));
382 if(s && s->length())
384 Date d = get_dateinfo(s);
385 ret.append("BDAY:%s-%02s-%02s\n", d.year, d.month, d.day);
388 s = tnef.get_property(MAPI_TAG(PT_SYSTIME, PR_WEDDING_ANNIVERSARY));
389 if(s && s->length())
391 Date d = get_dateinfo(s);
392 ret.append("X-EVOLUTION-ANNIVERSARY:%s-%02s-%02s\n",
393 d.year, d.month, d.day);
395 ret.append("END:VCARD\n");
397 return ret;
400 inline void append_to_card(WvString &dest, WvStringParm format,
401 uint32_t property, const WvTnef &tnef)
403 string const *s = tnef.get_property(property);
404 if (s)
405 dest.append(format, s->c_str());
408 inline void append_to_card(WvString &dest, WvStringParm format,
409 const string &guid, uint32_t id,
410 const WvTnef &tnef)
412 string const *s = tnef.get_property(guid, id);
413 if (s)
414 dest.append(format, s->c_str());
417 inline void append_to_card(WvString &dest, WvStringParm format,
418 const string &guid, const string &name_str,
419 const WvTnef &tnef)
421 string const *s = tnef.get_property(guid, name_str);
422 if (s)
423 dest.append(format, s->c_str());
426 time_t get_timet(string const *data)
428 uint64_t d = swap8(*(uint64_t *)(data->data()));
430 if (MAX_OUTLOOK_TIME_S == d)
432 // Maximum Outlook time; return maximum UNIX time
433 return MAX_UNIX_TIME;
436 // Subtract bias, convert from hundreds of nanoseconds to seconds
437 time_t timet;
438 d -= EPOCH_BIAS;
439 d /= HUNDREDS_OF_NANOSECONDS_PER_SECOND;
440 timet = d;
442 return timet;
445 string *get_outlook_time(time_t timet)
447 uint64_t d;
449 if (timet == MAX_UNIX_TIME)
450 d = MAX_OUTLOOK_TIME_S;
451 else
453 d = timet;
454 // Add bias, convert from seconds to hundreds of nanoseconds
455 d *= HUNDREDS_OF_NANOSECONDS_PER_SECOND;
456 d += EPOCH_BIAS;
459 d = swap8(d);
460 char *x = (char *)(&d);
461 string *time_string = new string(x, 8);
463 return time_string;
466 string *create_tnefdate(WvStringParm date_property)
468 return get_outlook_time(vcaldate_to_time(date_property, true));
471 Date get_dateinfo(string const *data)
473 Date ret;
475 if (NULL == data)
476 return ret;
478 time_t timet = get_timet(data);
479 struct tm *time = localtime(&timet);
481 ret.second = time->tm_sec;
482 ret.minute = time->tm_min;
483 ret.hour = time->tm_hour;
484 ret.year = time->tm_year + 1900;
485 ret.weekday = time->tm_wday;
486 ret.month = time->tm_mon + 1;
487 ret.day = time->tm_mday;
489 return ret;
492 // Pretty much the same as mich's split_binary8216
493 Recurrence get_recurrence(string const *data)
495 WvLog log("get_recurrence", WvLog::Debug3);
496 log("data = %s", data->c_str());
498 WvDynBuf recbuf;
499 Recurrence ret;
500 ret.recur_type = 0;
502 if (NULL == data)
503 return ret;
505 recbuf.put(data->data(), data->length());
507 // skip 4 bytes
508 recbuf.get(4);
509 ret.recur_type = read4u(recbuf);
511 // skip 2 bytes
512 recbuf.get(2);
513 ret.cycle_position = read4u(recbuf);
514 ret.interval = read4u(recbuf);
516 // skip 4 bytes
517 recbuf.get(4);
519 ret.day_info = 0;
520 ret.relative_position = 0;
522 if (ret.recur_type != DAILY_NDAYS)
523 ret.day_info = read4u(recbuf);
524 if (ret.recur_type == MONTHLY_NTH_FOO_MTH_MONTH
525 || ret.recur_type == YEARLY_NTH_FOO_MTH_MONTH)
526 ret.relative_position = read4u(recbuf);
528 ret.end_date_mode = read4u(recbuf);
529 ret.occurrences = read4u(recbuf);
531 // skip 4 bytes
532 recbuf.get(4);
533 ret.occurrences_count = read4u(recbuf);
535 for (unsigned int i = 0; i < ret.occurrences_count; i++)
536 ret.occurrence_dates->append(new uint32_t(read4u(recbuf)), true);
538 ret.exceptions_count = read4u(recbuf);
540 for (unsigned int i = 0; i < ret.exceptions_count; i++)
541 ret.exceptions_dates->append(new uint32_t(read4u(recbuf)), true);
543 ret.recur_start_day_minutes = read4u(recbuf);
544 ret.recur_end_day_minutes = read4u(recbuf);
546 // skip 8 bytes
547 recbuf.get(8);
549 ret.start_minutes = read4u(recbuf);
550 ret.end_minutes = read4u(recbuf);
551 ret.exceptions_details_count = read2u(recbuf);
553 int left = recbuf.used();
554 ret.exceptions_details.assign((const char *)recbuf.get(left), left);
556 return ret;
559 WvString get_rrule(const Recurrence &recur)
561 WvString ret("RRULE:");
563 if (0 == recur.recur_type)
564 return WvString::null;
566 WvString freq;
567 WvString until;
568 WvString interval;
569 WvString byday;
570 WvString bymonthday;
571 WvString byyearday;
572 WvString byweekno;
573 WvString bymonth;
574 WvString bysetpos;
575 WvString wkst;
577 // recur end day in time_t and struct tm format
578 time_t end_day_timet;
579 struct tm *end_day_tm;
581 // recur start day in time_t and struct tm format
582 time_t start_day_timet = (recur.recur_start_day_minutes - EPOCH_DIFF_M)*60;
583 struct tm *start_day_tm = gmtime(&start_day_timet);
584 start_day_tm->tm_year += 1900;
585 start_day_tm->tm_mon += 1;
587 if (MAX_OUTLOOK_TIME_M != recur.recur_end_day_minutes)
588 end_day_timet = (recur.recur_end_day_minutes - EPOCH_DIFF_M)*60;
589 else
590 end_day_timet = MAX_UNIX_TIME;
592 end_day_tm = gmtime(&end_day_timet);
593 end_day_tm->tm_year += 1900;
594 end_day_tm->tm_mon += 1;
596 if (MAX_OUTLOOK_TIME_M != recur.recur_end_day_minutes)
597 until = WvString("%04s%02s%02s",
598 end_day_tm->tm_year,
599 end_day_tm->tm_mon,
600 end_day_tm->tm_mday);
602 switch(recur.recur_type)
604 // daily every n days
605 case DAILY_NDAYS:
607 freq = WvString("DAILY");
609 // 1440 minutes in one day
610 interval = WvString("%s", (recur.interval / 1440));
611 break;
613 // daily every weekday (actually weekly)
614 case DAILY_WEEKDAYS:
616 freq = WvString("WEEKLY");
617 byday = WvString("MO,TU,WE,TH,FR");
618 break;
620 // weekly every n weeks
621 case WEEKLY_NWEEKS:
623 freq = WvString("WEEKLY");
624 byday = get_byday(recur.day_info);
625 break;
627 // monthly day n of every mth month
628 case MONTHLY_N_MTH_MONTH:
630 freq = WvString("MONTHLY");
631 bymonthday = WvString("%s", recur.day_info);
632 break;
634 // monthly the nth foo of every mth month
635 // (eg. "the second tuesday of every other month")
636 case MONTHLY_NTH_FOO_MTH_MONTH:
638 freq = WvString("MONTHLY");
639 byday = get_byday(recur.day_info);
640 bymonth = start_day_tm->tm_mon;
641 bysetpos = recur.relative_position;
642 break;
644 // yearly every month, day
645 // (eg. "every January 2nd")
646 case YEARLY_MONTH_DAY:
648 freq = WvString("YEARLY");
649 bymonthday = WvString("%s", recur.day_info);
650 break;
652 // yearly the nth foo of every mth month
653 // (eg. "the third Sunday of October every year")
654 case YEARLY_NTH_FOO_MTH_MONTH:
656 freq = WvString("YEARLY");
657 byday = get_byday(recur.day_info);
658 bymonth = start_day_tm->tm_mon;
659 bysetpos = recur.relative_position;
660 break;
662 default:
663 // er, we're kinda screwed
664 return WvString::null;
665 break;
668 // shouldn't be possible, just to be sure
669 if (!freq)
670 return WvString::null;
672 // we maybe set it for daily, otherwise set it here
673 if (!interval)
674 interval = WvString("%s", recur.interval);
676 ret.append("FREQ=%s", freq);
678 if (!!until) ret.append(";UNTIL=%s", until);
679 if (!!interval) ret.append(";INTERVAL=%s", interval);
680 if (!!byday) ret.append(";BYDAY=%s", byday);
681 if (!!bymonthday) ret.append(";BYMONTHDAY=%s", bymonthday);
682 if (!!byyearday) ret.append(";BYYEARDAY=%s", byyearday);
683 if (!!byweekno) ret.append(";BYWEEKNO=%s", byweekno);
684 if (!!bymonth) ret.append(";BYMONTH=%s", bymonth);
685 if (!!bysetpos) ret.append(";BYSETPOS=%s", bysetpos);
686 if (!!wkst) ret.append(";WKST=%s", wkst);
688 ret.append("\n");
690 return ret;
693 WvString get_exdates(const Recurrence &recur)
695 WvString ret;
696 WvStringList exdates;
698 WvMap<uint32_t, uint32_t, OpEqComp, WvScatterHash> exceptions_dates(32);
699 Uint32List::Iter i(*recur.exceptions_dates);
701 // Do it the same way mich does; set all the exceptions_dates
702 // in a hash table, making it easy to check if a certain date
703 // exists or not
704 for (i.rewind(); i.next(); )
705 exceptions_dates.set(*i, 1, true);
707 i = Uint32List::Iter(*recur.occurrence_dates);
708 Uint32List deleted_occurrences;
710 for (i.rewind(); i.next(); )
712 if (!exceptions_dates.exists(*i))
713 deleted_occurrences.append(new uint32_t(*i), true);
716 i = Uint32List::Iter(deleted_occurrences);
718 for (i.rewind(); i.next(); )
720 time_t timet = (*i - EPOCH_DIFF_M)*60;
721 struct tm *time = gmtime(&timet);
723 time->tm_year += 1900;
724 time->tm_mon += 1;
726 // FIXME: do exception dates actually deserve times,
727 // and not just dates? evolution doesn't seem to pay
728 // attention to them
729 exdates.append(new WvString("%04s%02s%02s",
730 time->tm_year, time->tm_mon,
731 time->tm_mday), true);
734 exceptions_dates.zap();
736 if (0 == exdates.count())
737 return WvString::null;
739 WvStringList::Iter j(exdates);
741 for (j.rewind(); j.next(); )
742 ret.append("EXDATE;VALUE=DATE:%s\n", *j);
744 return ret;
747 WvString get_byday(uint32_t day_info)
749 WvStringList daylist;
750 WvString byday;
752 if (day_info & 1)
753 daylist.append("SU");
754 if (day_info & 2)
755 daylist.append("MO");
756 if (day_info & 4)
757 daylist.append("TU");
758 if (day_info & 8)
759 daylist.append("WE");
760 if (day_info & 16)
761 daylist.append("TH");
762 if (day_info & 32)
763 daylist.append("FR");
764 if (day_info & 64)
765 daylist.append("SA");
767 if (!daylist.isempty())
768 return daylist.join(",");
769 else
770 return WvString::null;
773 string *pack_recur(const Recurrence &recur)
775 WvDynBuf buf;
777 append4(0x30043004, buf);
778 append4(recur.recur_type, buf);
779 append2(0, buf);
780 append4(recur.cycle_position, buf);
781 append4(recur.interval, buf);
782 append4(0, buf);
784 if (recur.recur_type != DAILY_NDAYS)
785 append4(recur.day_info, buf);
786 if (recur.recur_type == MONTHLY_NTH_FOO_MTH_MONTH ||
787 recur.recur_type == YEARLY_NTH_FOO_MTH_MONTH)
788 append4(recur.relative_position, buf);
790 append4(recur.end_date_mode, buf);
791 append4(recur.occurrences, buf);
792 append4(0, buf);
793 append4(recur.occurrences_count, buf);
795 Uint32List::Iter i(*recur.occurrence_dates);
796 for (i.rewind(); i.next(); )
797 append4(*i, buf);
799 append4(recur.exceptions_count, buf);
801 i = Uint32List::Iter(*recur.exceptions_dates);
802 for (i.rewind(); i.next(); )
803 append4(*i, buf);
805 append4(recur.recur_start_day_minutes, buf);
806 append4(recur.recur_end_day_minutes, buf);
807 append4(0x3006, buf);
808 append4(0x3007, buf);
809 append4(recur.start_minutes, buf);
810 append4(recur.end_minutes, buf);
811 append2(recur.exceptions_details_count, buf);
813 buf.put(recur.exceptions_details.data(),
814 recur.exceptions_details.length());
816 return new string((const char *)buf.get(buf.used()), buf.used());
819 time_t vcaldate_to_time(WvStringParm str, const bool local)
821 if (str.len() != 8 && str.len() != 15)
822 return 0;
824 struct tm time;
826 if (str.len() == 8)
828 sscanf(str.cstr(), "%04d%02d%02d",
829 &time.tm_year, &time.tm_mon, &time.tm_mday);
830 time.tm_hour = time.tm_min = time.tm_sec = 0;
832 else
834 sscanf(str.cstr(), "%04d%02d%02dT%02d%02d%02d",
835 &time.tm_year, &time.tm_mon, &time.tm_mday,
836 &time.tm_hour, &time.tm_min, &time.tm_sec);
838 // adjust into tm format
839 time.tm_wday = 0; // FIXME: claiming info not available
840 time.tm_yday = 0; // FIXME: claiming info not available
841 time.tm_year -= 1900;
842 time.tm_mon -= 1;
843 time.tm_isdst = (-1); // FIXME: claiming info not available
845 if (local)
846 return timelocal(&time);
847 else
848 return timegm(&time);
851 time_t vcaldate_to_timelocal(WvStringParm str)
853 return vcaldate_to_time(str, true);
856 time_t vcaldate_to_timegm(WvStringParm str)
858 return vcaldate_to_time(str, false);
861 uint32_t time_to_minutes(time_t t, bool local)
863 struct tm *time;
865 (local) ? time = localtime(&t) : time = gmtime(&t);
867 return time->tm_hour*60 + time->tm_min;
870 uint32_t time_to_minutes_local(time_t t)
872 return time_to_minutes(t, true);
875 uint32_t time_to_minutes_gm(time_t t)
877 return time_to_minutes(t, false);
880 uint64_t time_to_systime(time_t t)
882 uint64_t ret = t;
883 ret += EPOCH_DIFF_S;
884 ret *= 10000;
886 return ret;
889 Uint32List *only_in(const Uint32List *a, const Uint32List *b, bool in_a)
891 if (NULL == a || NULL == b)
892 return NULL;
894 Uint32List::Iter i(in_a ? *a : *b);
895 Uint32List::Iter j(in_a ? *b : *a);
897 Uint32List *ret = new Uint32List;
899 for (i.rewind(); i.next(); )
901 bool unique = true;
903 for (j.rewind(); j.next(); )
905 if (*i == *j)
907 unique = false;
908 break;
912 if (unique)
913 ret->append(new uint32_t(*i), true);
916 return ret;
919 Uint32List *only_in_a(const Uint32List *a, const Uint32List *b)
921 return only_in(a, b, true);
924 Uint32List *only_in_b(const Uint32List *a, const Uint32List *b)
926 return only_in(a, b, false);
929 void tnef_set_type(WvTnef &tnef, WvStringParm type)
931 string *s = new string(type);
932 tnef.set_property(MAPI_TAG(PT_STRING8, PR_MESSAGE_CLASS), s);
935 void tnef_set_uid(WvTnef &tnef, WvStringParm uid)
937 string *s = new string(uid);
938 string wvidguid = str2guid(WVIDGUID);
939 tnef.set_property(wvidguid, str2ustr("EvoId"),
940 s, PT_STRING8);
943 void tnef_set_dtstart(WvTnef &tnef, WvStringParm date, const bool is_recurring)
945 string *s = create_tnefdate(date);
946 tnef.set_property(str2guid(GUID_1), COMMON_START,
947 s, PT_SYSTIME);
948 if (is_recurring)
949 tnef.set_property(str2guid(GUID_3), RECUR_START_DAY,
950 new string(*s), PT_SYSTIME);
953 void tnef_set_dtend(WvTnef &tnef, WvStringParm date, const bool is_recurring)
955 string *s = create_tnefdate(date);
956 tnef.set_property(str2guid(GUID_1), COMMON_END,
957 s, PT_SYSTIME);
959 if (is_recurring)
960 tnef.set_property(str2guid(GUID_3), RECUR_END_DAY,
961 new string(*s), PT_SYSTIME);
964 void tnef_set_summary(WvTnef &tnef, WvStringParm summary)
966 string *s = new string(summary);
967 tnef.set_property(MAPI_TAG(PT_STRING8, PR_SUBJECT), s);
970 void tnef_set_location(WvTnef &tnef, WvStringParm location)
972 string *s = new string(location);
973 tnef.set_property(str2guid(GUID_1), LOCATION,
974 s, PT_STRING8);
977 void tnef_set_description(WvTnef &tnef, WvStringParm description)
979 string *s = new string(description);
980 tnef.set_property(MAPI_TAG(PT_STRING8, PR_BODY), s);
983 void tnef_set_last_modified(WvTnef &tnef, WvStringParm date)
985 string *s = create_tnefdate(date);
986 tnef.set_property(MAPI_TAG(PT_SYSTIME, PR_LAST_MODIFICATION_TIME), s);
989 // This function takes a rrule, and exdates as input, and rewrites the
990 // recurrence inside a tnef.
991 // What makes this function particularly ugly is that you can not
992 // straighforwardly take properties out of the vcal and write them to
993 // the TNEF: some of the variables needed to rewrite the TNEF may depend on
994 // other data we've extracted from the vcal (and then modified).
995 bool tnef_rewrite_recur(WvStringParm rrule, WvStringList *exdates, WvTnef &tnef)
997 WvString vcal_freq;
998 WvString vcal_interval;
999 WvString vcal_until;
1000 WvString vcal_byday;
1001 WvString vcal_bymonthday;
1002 WvString vcal_byyearday;
1003 WvString vcal_byweekno;
1004 WvString vcal_bymonth;
1005 WvString vcal_bysetpos;
1006 WvString vcal_wkst;
1008 uint32_t recur_type = 0;
1009 uint32_t interval = 1;
1010 uint32_t recur_interval = 0;
1011 uint32_t cycle_length = 0;
1012 uint32_t pattern_type = 0;
1013 uint32_t event_recurdays = 0;
1014 uint32_t relative_position = 0;
1016 time_t event_start;
1017 time_t event_end;
1018 WvString recur_string("");
1019 WvLog log("rewrite_recur", WvLog::Debug1);
1021 // Get start + end times
1022 if (!_tnef_prop_to_timet(event_start, tnef, COMMON_START) ||
1023 !_tnef_prop_to_timet(event_end, tnef, COMMON_END)) {
1024 return false;
1027 _vcal_parse_rrule_string(rrule, vcal_freq,
1028 vcal_interval, vcal_until,
1029 vcal_byday, vcal_bymonthday,
1030 vcal_byyearday, vcal_byweekno,
1031 vcal_bymonth, vcal_bysetpos,
1032 vcal_wkst);
1034 _vcal_get_recur_interval(vcal_interval, recur_interval);
1035 _vcal_get_recur_interval(vcal_interval, interval); // EXPERIMENTAL: shoved in to fix yearly recurrences
1037 if (!_vcal_get_recur_types(vcal_freq, vcal_byday, vcal_bysetpos,
1038 recur_interval, recur_type, pattern_type))
1039 return false;
1041 _vcal_get_recur_days(vcal_byday, event_recurdays);
1043 if (!!vcal_bysetpos)
1044 relative_position = vcal_bysetpos.num();
1046 log("event_end: %s", event_end);
1048 _vcal_parse_freq(vcal_freq, recur_interval,
1049 event_start, event_end,
1050 event_recurdays, interval,
1051 cycle_length, recur_string,
1052 pattern_type);
1054 // Helper variables for calculating the start + end of the
1055 // recurrence
1056 time_t recur_start_day;
1057 uint32_t start_year, start_month, start_day;
1058 // The following values are defaults in case there is no end date
1059 uint32_t recur_minutes_span;
1060 uint32_t occurrences = 10;
1061 uint32_t end_date_mode = 0x2023;
1062 uint32_t recur_end_day_minutes = 0x5ae980df;
1063 uint32_t recur_start_day_minutes = 0;
1064 time_t recur_end_day = 0x7fffffff;
1066 _get_recur_start_time(event_start, recur_start_day, recur_start_day_minutes,
1067 start_year, start_month, start_day);
1069 _get_recur_end_time(vcal_freq, vcal_until, event_start, recur_start_day, interval, event_recurdays,
1070 recur_start_day_minutes,
1071 start_year, start_month, start_day,
1072 recur_end_day, end_date_mode, recur_end_day_minutes,
1073 recur_minutes_span, occurrences);
1075 uint32_t start_minutes = time_to_minutes_local(event_start);
1076 uint32_t end_minutes = time_to_minutes_local(event_end);
1078 uint32_t cycle_position;
1080 _get_recur_cycle_position(vcal_freq, recur_start_day_minutes, interval, event_start,
1081 cycle_length, pattern_type, start_day,
1082 start_month, start_year, cycle_position, event_recurdays);
1084 _update_recur_string_for_pattern(pattern_type, interval, start_day, start_month,
1085 start_year, recur_string);
1088 Recurrence recur;
1090 uint32_t occurrences_count;
1091 uint32_t exceptions_count, exceptions_details_count; // Lengths of all the lists
1093 _get_recur_exceptions(exdates, tnef, recur, occurrences_count,
1094 exceptions_count, exceptions_details_count);
1096 // Set all the fields of the recurrence
1097 recur.recur_type = recur_type;
1098 recur.cycle_position = cycle_position;
1099 recur.interval = interval;
1100 recur.day_info = event_recurdays;
1101 recur.relative_position = relative_position;
1102 recur.end_date_mode = end_date_mode;
1103 recur.occurrences = occurrences;
1104 recur.occurrences_count = occurrences_count;
1105 recur.exceptions_count = exceptions_count;
1106 recur.recur_start_day_minutes = recur_start_day_minutes;
1107 recur.recur_end_day_minutes = recur_end_day_minutes;
1108 recur.start_minutes = start_minutes;
1109 recur.end_minutes = end_minutes;
1110 recur.exceptions_details_count = exceptions_details_count;
1112 /* Information useful for debugging, but which just gets in the way normally */
1113 #if 0
1114 log("recur_type: %s, cycle_position: %s, interval: %s, event_recurdays: %s, relative_position: %s, end_date_mode: %s, occurences: %s\n",
1115 recur_type, cycle_position, interval, event_recurdays, relative_position, end_date_mode, occurrences);
1116 log("occurrences_count: %s, exceptions_count: %s, recur_start_day_minutes: %s, recur_end_day_minutes: %s, start_minutes: %s, end_minutes: %s\n",
1117 occurrences_count, exceptions_count, recur_start_day_minutes, recur_end_day_minutes, start_minutes, end_minutes);
1118 log("exceptions_details_count: %s\n", exceptions_details_count);
1119 log("recur_string: %s\n", recur_string.cstr());
1120 log("pattern_type: %s\n", pattern_type);
1122 log("freq: %s until: %s, event_start: %s, recur_start_day: %s, interval: %s, event_recurdays: %s, recur_start_day_minutes: %s\n",
1123 vcal_freq, vcal_until, event_start, recur_start_day, interval, event_recurdays, recur_start_day_minutes);
1124 log("start_year: %s, start_month: %s, start_day: %s, recur_end_day: %s, end_date_mode: %s, recur_end_day_minutes: %s\n",
1125 start_year, start_month, start_day, recur_end_day, end_date_mode, recur_end_day_minutes);
1126 log("recur_minutes_span: %s, occurences: %s\n", recur_minutes_span, occurrences);
1127 #endif
1128 uint32_t istrue = 1;
1130 uint64_t start_day_sys = time_to_systime(recur_start_day);
1131 uint64_t end_day_sys = time_to_systime(recur_end_day);
1133 tnef.set_property(str2guid(GUID_1), RECURRENCE, pack_recur(recur));
1134 tnef.set_property(str2guid(GUID_1), RECUR_PATTERN,
1135 new string((const char *)&pattern_type, 4), PT_LONG);
1136 tnef.set_property(str2guid(GUID_1), RECUR_START_DAY,
1137 new string((const char *)&start_day_sys, 8), PT_SYSTIME);
1138 tnef.set_property(str2guid(GUID_1), RECUR_END_DAY,
1139 new string((const char *)&end_day_sys, 8), PT_SYSTIME);
1140 tnef.set_property(str2guid(GUID_1), RECUR_STRING,
1141 new string(recur_string), PT_STRING8);
1142 tnef.set_property(str2guid(GUID_1), IS_RECURRING,
1143 new string((const char *)&istrue, 4), PT_BOOLEAN);
1145 return true;