lib: fixed parsing of recurring VEVENTS: DAILY and interval support
[barry/progweb.git] / src / r_calendar.cc
bloba4d3e0ce2af5372a1814fbd476146ac6dbdace83
1 ///
2 /// \file r_calendar.cc
3 /// Blackberry database record parser class for calendar records.
4 ///
6 /*
7 Copyright (C) 2005-2012, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "r_calendar.h"
23 #include "r_recur_base-int.h"
24 #include "record-internal.h"
25 #include "protocol.h"
26 #include "protostructs.h"
27 #include "data.h"
28 #include "time.h"
29 #include "error.h"
30 #include "endian.h"
31 #include "iconv.h"
32 #include <ostream>
33 #include <iomanip>
34 #include <time.h>
35 #include <string.h>
36 #include <stdexcept>
37 #include "ios_state.h"
39 #define __DEBUG_MODE__
40 #include "debug.h"
42 using namespace std;
43 using namespace Barry::Protocol;
45 namespace Barry {
48 ///////////////////////////////////////////////////////////////////////////////
49 // Calendar class, static members
52 // Note! These functions currently only pass the same values through.
53 // In actuality, these are technically two different values:
54 // one on the raw protocol side, and the other part of the
55 // guaranteed Barry API. If the Blackberry ever changes the
56 // meanings for these codes, do the translation here.
59 Calendar::FreeBusyFlagType Calendar::FreeBusyFlagProto2Rec(uint8_t f)
61 return (FreeBusyFlagType)f;
64 uint8_t Calendar::FreeBusyFlagRec2Proto(FreeBusyFlagType f)
66 return f;
69 Calendar::ClassFlagType Calendar::ClassFlagProto2Rec(uint8_t f)
71 return (ClassFlagType)f;
74 uint8_t Calendar::ClassFlagRec2Proto(ClassFlagType f)
76 return f;
81 ///////////////////////////////////////////////////////////////////////////////
82 // Calendar class
84 // calendar field codes
85 #define CALFC_APPT_TYPE_FLAG 0x01
86 #define CALFC_SUBJECT 0x02
87 #define CALFC_NOTES 0x03
88 #define CALFC_LOCATION 0x04
89 #define CALFC_NOTIFICATION_TIME 0x05
90 #define CALFC_START_TIME 0x06
91 #define CALFC_END_TIME 0x07
92 #define CALFC_ACCEPTED_BY 0x0b
93 #define CALFC_VERSION_DATA 0x10
94 #define CALFC_INVITED 0x15
95 #define CALFC_ORGANIZER 0x16
96 #define CALFC_NOTIFICATION_DATA 0x1a
97 #define CALFC_FREEBUSY_FLAG 0x1c
98 #define CALFC_TIMEZONE_CODE 0x1e // only seems to show up if recurring
99 #define CALFC_CLASS_FLAG 0x28 // private flag from outlook
100 #define CALFC_CALENDAR_ID 0x2b // Calendar using (new devices have several calendar)
101 #define CALFC_ALLDAYEVENT_FLAG 0xff
102 #define CALFC_END 0xffff
104 static FieldLink<Calendar> CalendarFieldLinks[] = {
105 { CALFC_SUBJECT, "Subject", 0, 0, &Calendar::Subject, 0, 0, 0, 0, true },
106 { CALFC_NOTES, "Notes", 0, 0, &Calendar::Notes, 0, 0, 0, 0, true },
107 { CALFC_LOCATION, "Location", 0, 0, &Calendar::Location, 0, 0, 0, 0, true },
108 { CALFC_NOTIFICATION_TIME,"Notification Time",0,0, 0, 0, &Calendar::NotificationTime, 0, 0, false },
109 { CALFC_START_TIME, "Start Time", 0, 0, 0, 0, &Calendar::StartTime, 0, 0, false },
110 { CALFC_END_TIME, "End Time", 0, 0, 0, 0, &Calendar::EndTime, 0, 0, false },
111 { CALFC_ORGANIZER, "Organizer", 0, 0, 0, &Calendar::Organizer, 0, 0, 0, true },
112 { CALFC_ACCEPTED_BY,"Accepted By",0, 0, 0, &Calendar::AcceptedBy, 0, 0, 0, true },
113 { CALFC_INVITED, "Invited", 0, 0, 0, &Calendar::Invited, 0, 0, 0, true },
114 { CALFC_END, "End of List",0, 0, 0, 0, 0, 0, 0, false }
117 Calendar::Calendar()
119 Clear();
122 Calendar::~Calendar()
126 const unsigned char* Calendar::ParseField(const unsigned char *begin,
127 const unsigned char *end,
128 const IConverter *ic)
130 const CommonField *field = (const CommonField *) begin;
132 // advance and check size
133 begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
134 if( begin > end ) // if begin==end, we are ok
135 return begin;
137 if( !btohs(field->size) ) // if field has no size, something's up
138 return begin;
140 // cycle through the type table
141 for( FieldLink<Calendar> *b = CalendarFieldLinks;
142 b->type != CALFC_END;
143 b++ )
145 if( b->type == field->type ) {
146 if( b->strMember ) {
147 std::string &s = this->*(b->strMember);
148 s = ParseFieldString(field);
149 if( b->iconvNeeded && ic )
150 s = ic->FromBB(s);
151 return begin; // done!
153 else if( b->timeMember && btohs(field->size) == 4 ) {
154 TimeT &t = this->*(b->timeMember);
155 dout("min1900: " << field->u.min1900);
156 t.Time = min2time(field->u.min1900);
157 return begin;
159 else if( b->addrMember ) {
161 // parse email address
162 // get dual addr+name string first
163 // Note: this is a different format than
164 // used in r_message*.cc
166 std::string dual((const char*)field->u.raw, btohs(field->size));
168 EmailAddress a;
170 // assign first string, using null terminator
171 // letting std::string add it for us if it
172 // doesn't exist
173 a.Email = dual.c_str();
175 // assign second string, using first size
176 // as starting point
177 a.Name = dual.c_str() + a.Email.size() + 1;
179 // if the address is non-empty, add to list
180 if( a.size() ) {
181 // i18n convert if needed
182 if( b->iconvNeeded && ic ) {
183 a.Name = ic->FromBB(a.Name);
184 a.Email = ic->FromBB(a.Email);
187 EmailAddressList &al = this->*(b->addrMember);
188 al.push_back(a);
191 return begin;
196 // handle special cases
197 switch( field->type )
199 case CALFC_APPT_TYPE_FLAG:
200 switch( field->u.raw[0] )
202 case 'a': // regular non-recurring appointment
203 Recurring = false;
204 return begin;
206 case '*': // recurring appointment
207 Recurring = true;
208 return begin;
210 default:
211 throw Error("Calendar::ParseField: unknown appointment type");
213 break;
215 case CALFC_ALLDAYEVENT_FLAG:
216 AllDayEvent = field->u.raw[0] == 1;
217 return begin;
219 case CALFC_TIMEZONE_CODE:
220 if( btohs(field->size) == 2 ) {
221 // good data
222 TimeZoneCode = btohs(field->u.code);
223 TimeZoneValid = true;
225 else {
226 throw Error("Calendar::ParseField: not enough data in time zone code field");
228 return begin;
230 case CALFC_FREEBUSY_FLAG:
231 if( field->u.raw[0] > CR_FREEBUSY_RANGE_HIGH ) {
232 throw Error("Calendar::ParseField: FreeBusyFlag out of range" );
234 FreeBusyFlag = FreeBusyFlagProto2Rec(field->u.raw[0]);
235 return begin;
237 case CALFC_CALENDAR_ID:
238 if( btohs(field->size) == 8 ) {
239 CalendarID = btohll(field->u.uint64);
241 else {
242 throw Error("Calendar::ParseField: size data unknown in calendar field");
244 return begin;
246 case CALFC_CLASS_FLAG:
247 if( field->u.raw[0] > CR_CLASS_RANGE_HIGH ) {
248 throw Error("Calendar::ParseField: ClassFlag out of range" );
250 ClassFlag = ClassFlagProto2Rec(field->u.raw[0]);
251 return begin;
254 // base class handles recurring data
255 if( RecurBase::ParseField(field->type, field->u.raw, btohs(field->size), ic) )
256 return begin;
258 // if still not handled, add to the Unknowns list
259 UnknownField uf;
260 uf.type = field->type;
261 uf.data.assign((const char*)field->u.raw, btohs(field->size));
262 Unknowns.push_back(uf);
264 // return new pointer for next field
265 return begin;
268 void Calendar::ParseHeader(const Data &data, size_t &offset)
270 // no header in Calendar records
273 void Calendar::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
275 const unsigned char *finish = ParseCommonFields(*this,
276 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
277 offset += finish - (data.GetData() + offset);
280 void Calendar::Validate() const
282 RecurBase::Validate();
285 void Calendar::BuildHeader(Data &data, size_t &offset) const
287 // no header in Calendar records
291 // Build
293 /// Build fields part of record.
295 void Calendar::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
297 data.Zap();
299 // output the type first
300 BuildField(data, offset, CALFC_APPT_TYPE_FLAG, Recurring ? '*' : 'a');
302 // output all day event flag only if set
303 if( AllDayEvent )
304 BuildField(data, offset, CALFC_ALLDAYEVENT_FLAG, (char)1);
306 // cycle through the type table
307 for( const FieldLink<Calendar> *b = CalendarFieldLinks;
308 b->type != CALFC_END;
309 b++ )
311 if( b->strMember ) {
312 const std::string &s = this->*(b->strMember);
313 if( s.size() )
314 BuildField(data, offset, b->type, (b->iconvNeeded && ic) ? ic->ToBB(s) : s);
316 else if( b->timeMember ) {
317 TimeT t = this->*(b->timeMember);
318 if( t.Time > 0 )
319 BuildField1900(data, offset, b->type, t.Time);
321 else if( b->addrMember ) {
322 const EmailAddressList &al = this->*(b->addrMember);
323 EmailAddressList::const_iterator lb = al.begin(), le = al.end();
325 // add all entries in list
326 for( ; lb != le; ++lb ) {
328 // skip empty entries
329 if( !lb->size() )
330 continue;
332 std::string Name = lb->Name,
333 Email = lb->Email;
335 // do i18n conversion only if needed
336 if( b->iconvNeeded && ic ) {
337 Name = ic->ToBB(Name);
338 Email = ic->ToBB(Email);
342 // Build an addr+name field, each string
343 // null terminated.
344 // Note: this is a different format than
345 // what is used in r_message*.cc
347 std::string field(lb->Email.c_str(), lb->Email.size() + 1);
348 field.append(lb->Name.c_str(), lb->Name.size() + 1);
349 BuildField(data, offset, b->type, field.data(), field.size());
354 // handle special cases
355 if( Recurring ) {
356 CalendarRecurrenceDataField recur;
357 BuildRecurrenceData(StartTime.Time, &recur);
358 BuildField(data, offset, RecurBase::RecurringFieldType(),
359 &recur, CALENDAR_RECURRENCE_DATA_FIELD_SIZE);
362 if( TimeZoneValid )
363 BuildField(data, offset, CALFC_TIMEZONE_CODE, TimeZoneCode);
365 BuildField(data, offset, CALFC_FREEBUSY_FLAG, FreeBusyFlagRec2Proto(FreeBusyFlag));
366 BuildField(data, offset, CALFC_CLASS_FLAG, ClassFlagRec2Proto(ClassFlag));
368 // If CalendarID is defined and most of supported !
369 // (by default 0xffff ffff ffff ffff)
370 if( CalendarID != (uint64_t) -1 )
371 BuildField(data, offset, CALFC_CALENDAR_ID, CalendarID);
373 // and finally save unknowns
374 UnknownsType::const_iterator
375 ub = Unknowns.begin(), ue = Unknowns.end();
376 for( ; ub != ue; ub++ ) {
377 BuildField(data, offset, *ub);
380 data.ReleaseBuffer(offset);
383 void Calendar::Clear()
385 // clear the base class too
386 RecurBase::Clear();
388 // clear our fields
389 RecType = GetDefaultRecType();
390 RecordId = 0;
392 AllDayEvent = false;
393 Subject.clear();
394 Notes.clear();
395 Location.clear();
396 NotificationTime.clear();
397 StartTime.clear();
398 EndTime.clear();
399 Organizer.clear();
400 AcceptedBy.clear();
401 Invited.clear();
403 FreeBusyFlag = Free;
404 ClassFlag = Public;
406 CalendarID = btohll((uint64_t) -1);
408 TimeZoneCode = GetStaticTimeZoneCode(0, 0); // default to GMT
409 TimeZoneValid = false;
411 Unknowns.clear();
414 const FieldHandle<Calendar>::ListT& Calendar::GetFieldHandles()
416 static FieldHandle<Calendar>::ListT fhv;
418 if( fhv.size() )
419 return fhv;
421 #undef CONTAINER_OBJECT_NAME
422 #define CONTAINER_OBJECT_NAME fhv
424 #undef RECORD_CLASS_NAME
425 #define RECORD_CLASS_NAME Calendar
427 #define ALL_COMMON_CALENDAR_FIELDS \
428 FHP(RecType, "Record Type Code"); \
429 FHP(RecordId, "Unique Record ID"); \
431 FHP(AllDayEvent, "All Day Event"); \
432 FHD(Subject, "Subject", CALFC_SUBJECT, true); \
433 FHD(Notes, "Notes", CALFC_NOTES, true); \
434 FHD(Location, "Location", CALFC_LOCATION, true); \
435 FHD(NotificationTime, "Notification Time (0 is off)", \
436 CALFC_NOTIFICATION_TIME, false); \
437 FHD(StartTime, "Start Time", CALFC_START_TIME, false); \
438 FHD(EndTime, "End Time", CALFC_END_TIME, false); \
439 FHD(Organizer, "Organizer", CALFC_ORGANIZER, true); \
440 FHD(AcceptedBy, "Accepted By", CALFC_ACCEPTED_BY, true); \
441 FHD(Invited, "Invited", CALFC_INVITED, true); \
443 FHE(fbf, FreeBusyFlagType, FreeBusyFlag, "Free or Busy Flag"); \
444 FHE_CONST(fbf, Free, "Free"); \
445 FHE_CONST(fbf, Tentative, "Tentative"); \
446 FHE_CONST(fbf, Busy, "Busy"); \
447 FHE_CONST(fbf, OutOfOffice, "Out of Office"); \
449 FHE(cf, ClassFlagType, ClassFlag, "Event Class"); \
450 FHE_CONST(cf, Public, "Public"); \
451 FHE_CONST(cf, Confidential, "Confidential"); \
452 FHE_CONST(cf, Private, "Private"); \
454 FHP(TimeZoneCode, "Time Zone Code"); \
455 FHP(TimeZoneValid, "Time Zone Validity"); \
457 FHP(Unknowns, "Unknown Fields");
459 ALL_COMMON_CALENDAR_FIELDS
461 // the fields unique to Calendar, or different in CalendarALL
462 FHD(CalendarID, "Calendar ID", CALFC_CALENDAR_ID, false);
464 // and finally, the RecurBase fields
465 RECUR_BASE_FIELD_HANDLES
467 return fhv;
470 std::string Calendar::GetDescription() const
472 return Subject;
475 void Calendar::DumpSpecialFields(std::ostream &os) const
477 ios_format_state state(os);
479 static const char *ClassTypes[] = { "Public", "Confidential", "Private" };
480 static const char *FreeBusy[] = { "Free", "Tentative", "Busy", "Out of Office" };
482 os << " Calendar ID: 0x" << setbase(16) << CalendarID << "\n";
483 os << " All Day Event: " << (AllDayEvent ? "yes" : "no") << "\n";
484 os << " Class: " << ClassTypes[ClassFlag] << "\n";
485 os << " Free/Busy: " << FreeBusy[FreeBusyFlag] << "\n";
486 if( TimeZoneValid )
487 os << " Time Zone: " << GetStaticTimeZone(TimeZoneCode)->Name << "\n";
490 void Calendar::Dump(std::ostream &os) const
492 ios_format_state state(os);
494 // FIXME - need a "check all data" function that make sure that all
495 // recurrence data is within range. Then call that before using
496 // the data, such as in Build and in Dump.
498 os << "Calendar entry: 0x" << setbase(16) << RecordId
499 << " (" << (unsigned int)RecType << ")\n";
500 DumpSpecialFields(os);
502 // cycle through the type table
503 for( const FieldLink<Calendar> *b = CalendarFieldLinks;
504 b->type != CALFC_END;
505 b++ )
507 if( b->strMember ) {
508 const std::string &s = this->*(b->strMember);
509 if( s.size() )
510 os << " " << b->name << ": " << s << "\n";
512 else if( b->timeMember ) {
513 TimeT t = this->*(b->timeMember);
514 if( t.Time > 0 )
515 os << " " << b->name << ": " << t << "\n";
516 else
517 os << " " << b->name << ": disabled\n";
519 else if( b->addrMember ) {
520 const EmailAddressList &al = this->*(b->addrMember);
521 EmailAddressList::const_iterator lb = al.begin(), le = al.end();
523 for( ; lb != le; ++lb ) {
524 if( !lb->size() )
525 continue;
527 os << " " << b->name << ": " << *lb << "\n";
532 // print recurrence data if available
533 RecurBase::Dump(os);
535 // print any unknowns
536 os << Unknowns;
539 bool Calendar::operator<(const Calendar &other) const
541 if( StartTime < other.StartTime )
542 return true;
543 else if( other.StartTime < StartTime )
544 return false;
546 // times are equal, so secondary sort based on Subject + Location
547 int cmp = Subject.compare(other.Subject);
548 if( cmp == 0 )
549 cmp = Location.compare(other.Location);
550 return cmp < 0;
554 ///////////////////////////////////////////////////////////////////////////////
555 // Calendar-All class
557 // calendar-all field codes
558 #define CALALLFC_CALENDAR_ID 0x02 // Calendar using (new devices have several calendar)
559 #define CALALLFC_MAIL_ACCOUNT 0x03
560 #define CALALLFC_UNIQUEID 0x05
561 #define CALALLFC_CAL_OBJECT 0x0a
562 #define CALALLFC_END 0xffff
564 void CalendarAll::Clear()
566 Calendar::Clear();
568 MailAccount.clear();
571 const FieldHandle<CalendarAll>::ListT& CalendarAll::GetFieldHandles()
573 static FieldHandle<CalendarAll>::ListT fhv;
575 if( fhv.size() )
576 return fhv;
578 #undef CONTAINER_OBJECT_NAME
579 #define CONTAINER_OBJECT_NAME fhv
581 #undef RECORD_CLASS_NAME
582 #define RECORD_CLASS_NAME CalendarAll
584 ALL_COMMON_CALENDAR_FIELDS
586 // Calendar:: field, but with a CalendarAll ID
587 FHD(CalendarID, "Calendar ID", CALALLFC_CALENDAR_ID, false);
589 // add the fields specific to CalendarAll
590 FHD(MailAccount, "Mail Account", CALALLFC_MAIL_ACCOUNT, true);
592 // and finally, the RecurBase fields
593 RECUR_BASE_FIELD_HANDLES
595 return fhv;
598 void CalendarAll::ParseHeader(const Data &data, size_t &offset)
600 const unsigned char *b = (const unsigned char*) (data.GetData() + offset);
601 const unsigned char *e = (const unsigned char*) (data.GetData() + data.GetSize());
603 while( (b + COMMON_FIELD_HEADER_SIZE) < e ) {
604 const CommonField *field = (const CommonField *) b;
606 // advance and check size
607 b += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
608 if( b > e ) // if begin==end, we are ok
609 continue;
611 if( !btohs(field->size) ) // if field has no size, something's up
612 continue;
614 // handle special cases
615 if( field->type == CALALLFC_CAL_OBJECT )
617 b -= btohs(field->size);
618 // end of header
619 break;
622 switch( field->type )
624 case CALALLFC_CALENDAR_ID:
625 if( btohs(field->size) == 8 ) {
626 CalendarID = btohll(field->u.uint64);
628 else {
629 throw Error("CalendarAll::ParseField: size data unknown in calendar field");
631 continue;
633 case CALALLFC_MAIL_ACCOUNT:
634 MailAccount = ParseFieldString(field);
635 continue;
637 case CALALLFC_UNIQUEID:
638 if( btohs(field->size) == 4 ) {
639 RecordId = btohl(field->u.uint32);
641 else {
642 throw Error("CalendarAll::ParseHeader: size data unknown in calendar field");
644 continue;
647 // if still not handled, add to the Unknowns list
648 UnknownField uf;
649 uf.type = field->type;
650 uf.data.assign((const char*)field->u.raw, btohs(field->size));
651 Unknowns.push_back(uf);
654 offset += b - (data.GetData() + offset);
657 void CalendarAll::DumpSpecialFields(std::ostream &os) const
659 ios_format_state state(os);
661 Calendar::DumpSpecialFields(os);
662 os << " Mail Account: " << MailAccount << "\n";
665 } // namespace Barry