- fixed null pointer access in opensync's HasMultipleVEvents()
[barry.git] / opensync-plugin / src / vevent.cc
blob1a26889f4b879a73219e5a401b732c21533edaab
1 //
2 // \file vevent.cc
3 // Conversion routines for vevents (VCALENDAR, etc)
4 //
6 /*
7 Copyright (C) 2006-2007, 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 "vevent.h"
23 #include "environment.h"
24 #include "trace.h"
25 #include "vformat.h" // comes from opensync, but not a public header yet
26 #include <stdint.h>
27 #include <glib.h>
28 #include <sstream>
31 //////////////////////////////////////////////////////////////////////////////
32 // vCalendar
34 vCalendar::vCalendar()
35 : m_gCalData(0),
36 m_format(0)
40 vCalendar::~vCalendar()
42 if( m_gCalData ) {
43 g_free(m_gCalData);
46 if( m_format ) {
47 vformat_free(m_format);
51 vAttrPtr vCalendar::NewAttr(const char *name)
53 Trace trace("vCalendar::NewAttr");
55 trace.logf("creating valueless attr: %s", name);
57 vAttrPtr attr(vformat_attribute_new(NULL, name));
58 if( !attr.Get() )
59 throw ConvertError("resource error allocating vformat attribute");
60 return attr;
63 vAttrPtr vCalendar::NewAttr(const char *name, const char *value)
65 Trace trace("vCalendar::NewAttr");
67 if( strlen(value) == 0 ) {
68 trace.logf("attribute '%s' contains no data, skipping", name);
69 return vAttrPtr();
72 trace.logf("creating attr: %s, %s", name, value);
74 vAttrPtr attr(vformat_attribute_new(NULL, name));
75 if( !attr.Get() )
76 throw ConvertError("resource error allocating vformat attribute");
78 vformat_attribute_add_value(attr.Get(), value);
79 return attr;
82 void vCalendar::AddAttr(vAttrPtr attr)
84 Trace trace("vCalendar::AddAttr");
86 if( !attr.Get() ) {
87 trace.log("attribute contains no data, skipping");
88 return;
91 vformat_add_attribute(m_format, attr.Extract());
94 void vCalendar::AddParam(vAttrPtr &attr, const char *name, const char *value)
96 Trace trace("vCalendar::AddParam");
98 if( !attr.Get() ) {
99 trace.log("attribute pointer contains no data, skipping");
100 return;
102 if( strlen(value) == 0 ) {
103 trace.log("parameter value is empty, skipping");
104 return;
107 VFormatParam *pParam = vformat_attribute_param_new(name);
108 vformat_attribute_param_add_value(pParam, value);
109 vformat_attribute_add_param(attr.Get(), pParam);
112 std::string vCalendar::GetAttr(const char *attrname)
114 Trace trace("vCalendar::GetAttr");
115 trace.logf("getting attr: %s", attrname);
117 std::string ret;
119 VFormatAttribute *attr = vformat_find_attribute(m_format, attrname);
120 if( attr ) {
121 if( vformat_attribute_is_single_valued(attr) ) {
122 ret = vformat_attribute_get_value(attr);
124 else {
125 // FIXME - does this ever happen?
126 ret = vformat_attribute_get_nth_value(attr, 0);
130 trace.logf("attr value: %s", ret.c_str());
132 return ret;
136 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
138 unsigned short vCalendar::GetWeekDayIndex(const char *dayname)
140 for( int i = 0; i < 7; i++ ) {
141 if( strcasecmp(dayname, WeekDays[i]) == 0 )
142 return i;
144 return 0;
147 bool vCalendar::HasMultipleVEvents() const
149 int count = 0;
150 GList *attrs = m_format ? vformat_get_attributes(m_format) : 0;
151 for( ; attrs; attrs = attrs->next ) {
152 VFormatAttribute *attr = (VFormatAttribute*) attrs->data;
153 if( strcasecmp(vformat_attribute_get_name(attr), "BEGIN") == 0 &&
154 strcasecmp(vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
156 count++;
159 return count > 1;
162 void vCalendar::RecurToVCal()
164 using namespace Barry;
165 using namespace std;
166 Barry::Calendar &cal = m_BarryCal;
168 if( !cal.Recurring )
169 return;
171 vAttrPtr attr = NewAttr("RRULE");
173 switch( cal.RecurringType )
175 case Calendar::Day: // eg. every day
176 AddParam(attr, "FREQ", "DAILY");
177 break;
179 case Calendar::MonthByDate: // eg. every month on the 12th
180 // see: DayOfMonth
181 AddParam(attr, "FREQ", "MONTHLY");
183 ostringstream oss;
184 oss << cal.DayOfMonth;
185 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
187 break;
189 case Calendar::MonthByDay: // eg. every month on 3rd Wed
190 // see: DayOfWeek and WeekOfMonth
191 AddParam(attr, "FREQ", "MONTHLY");
192 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
193 ostringstream oss;
194 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
195 AddParam(attr, "BYDAY", oss.str().c_str());
197 break;
199 case Calendar::YearByDate: // eg. every year on March 5
200 // see: DayOfMonth and MonthOfYear
201 AddParam(attr, "FREQ", "YEARLY");
203 ostringstream oss;
204 oss << cal.MonthOfYear;
205 AddParam(attr, "BYMONTH", oss.str().c_str());
208 ostringstream oss;
209 oss << cal.DayOfMonth;
210 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
212 break;
214 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
215 // see: DayOfWeek, WeekOfMonth, and
216 // MonthOfYear
217 AddParam(attr, "FREQ", "YEARLY");
218 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
219 ostringstream oss;
220 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
221 AddParam(attr, "BYDAY", oss.str().c_str());
223 oss.str("");
224 oss << cal.MonthOfYear;
225 AddParam(attr, "BYMONTH", oss.str().c_str());
227 break;
229 case Calendar::Week: // eg. every week on Mon and Fri
230 // see: WeekDays
231 AddParam(attr, "FREQ", "WEEKLY");
233 ostringstream oss;
234 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
235 if( cal.WeekDays & bm ) {
236 if( cnt )
237 oss << ",";
238 oss << WeekDays[i];
239 cnt++;
242 AddParam(attr, "BYDAY", oss.str().c_str());
244 break;
246 default:
247 throw ConvertError("Unknown RecurringType in Barry Calendar object");
250 // add some common parameters
251 if( cal.Interval > 1 ) {
252 ostringstream oss;
253 oss << cal.Interval;
254 AddParam(attr, "INTERVAL", oss.str().c_str());
256 if( !cal.Perpetual ) {
257 gStringPtr rend(osync_time_unix2vtime(&cal.RecurringEndTime));
258 AddParam(attr, "UNTIL", rend.Get());
261 AddAttr(attr);
264 bool AllDayEvent;
267 /// Recurring data
269 /// Note: interval can be used on all of these recurring types to
270 /// make it happen "every other time" or more, etc.
273 bool Recurring;
274 RecurringCodeType RecurringType;
275 unsigned short Interval; // must be >= 1
276 time_t RecurringEndTime; // only pertains if Recurring is true
277 // sets the date and time when
278 // recurrence of this appointment
279 // should no longer occur
280 // If a perpetual appointment, this
281 // is 0xFFFFFFFF in the low level data
282 // Instead, set the following flag.
283 bool Perpetual; // if true, this will always recur
284 unsigned short TimeZoneCode; // the time zone originally used
285 // for the recurrence data...
286 // seems to have little use, but
287 // set to your current time zone
288 // as a good default
290 unsigned short // recurring details, depending on type
291 DayOfWeek, // 0-6
292 WeekOfMonth, // 1-5
293 DayOfMonth, // 1-31
294 MonthOfYear; // 1-12
295 unsigned char WeekDays; // bitmask, bit 0 = sunday
297 #define CAL_WD_SUN 0x01
298 #define CAL_WD_MON 0x02
299 #define CAL_WD_TUE 0x04
300 #define CAL_WD_WED 0x08
301 #define CAL_WD_THU 0x10
302 #define CAL_WD_FRI 0x20
303 #define CAL_WD_SAT 0x40
309 void vCalendar::RecurToBarryCal()
311 // FIXME - needs to be implemented
313 // GetWeekDayIndex()
316 // Main conversion routine for converting from Barry::Calendar to
317 // a vCalendar string of data.
318 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
320 // start fresh
321 Clear();
322 m_format = vformat_new();
323 if( !m_format )
324 throw ConvertError("resource error allocating vformat");
326 // store the Barry object we're working with
327 m_BarryCal = cal;
329 // begin building vCalendar data
330 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
331 AddAttr(NewAttr("BEGIN", "VEVENT"));
332 AddAttr(NewAttr("SEQUENCE", "0"));
333 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
334 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
335 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
337 gStringPtr start(osync_time_unix2vtime(&cal.StartTime));
338 gStringPtr end(osync_time_unix2vtime(&cal.EndTime));
339 gStringPtr notify(osync_time_unix2vtime(&cal.NotificationTime));
341 AddAttr(NewAttr("DTSTART", start.Get()));
342 AddAttr(NewAttr("DTEND", end.Get()));
343 // FIXME - add a truly globally unique "UID" string?
346 AddAttr(NewAttr("BEGIN", "VALARM"));
347 AddAttr(NewAttr("ACTION", "AUDIO"));
349 // notify must be UTC, when specified in DATE-TIME
350 vAttrPtr trigger = NewAttr("TRIGGER", notify.Get());
351 AddParam(trigger, "VALUE", "DATE-TIME");
352 AddAttr(trigger);
354 AddAttr(NewAttr("END", "VALARM"));
357 if( cal.Recurring ) {
358 RecurToVCal();
361 AddAttr(NewAttr("END", "VEVENT"));
363 // generate the raw VCALENDAR data
364 m_gCalData = vformat_to_string(m_format, VFORMAT_EVENT_20);
365 m_vCalData = m_gCalData;
367 return m_vCalData;
370 // Main conversion routine for converting from vCalendar data string
371 // to a Barry::Calendar object.
372 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
374 using namespace std;
376 Trace trace("vCalendar::ToBarry");
378 // we only handle vCalendar data with one vevent block
379 if( HasMultipleVEvents() )
380 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
382 // start fresh
383 Clear();
385 // store the vCalendar raw data
386 m_vCalData = vcal;
388 // create format parser structures
389 m_format = vformat_new_from_string(vcal);
390 if( !m_format )
391 throw ConvertError("resource error allocating vformat");
393 string start = GetAttr("DTSTART");
394 trace.logf("DTSTART attr retrieved: %s", start.c_str());
395 string end = GetAttr("DTEND");
396 trace.logf("DTEND attr retrieved: %s", end.c_str());
397 string subject = GetAttr("SUMMARY");
398 trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
399 if( subject.size() == 0 ) {
400 subject = "<blank subject>";
401 trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
404 if( !start.size() || !end.size() ) {
405 // FIXME - DTEND is actually optional! According to the
406 // RFC, a DTSTART with no DTEND should be treated
407 // like a "special day" like an anniversary, which occupies
408 // no time.
409 throw ConvertError("Blank DTSTART or DTEND");
412 // FIXME - we are assuming that any non-UTC timestamps
413 // in the vcalendar record will be in the current timezone...
414 // This is wrong! fix this later.
416 // Also, we current ignore any time zone
417 // parameters that might be in the vcalendar format... this
418 // must be fixed.
420 time_t now = time(NULL);
421 int zoneoffset = osync_time_timezone_diff(localtime(&now));
423 Barry::Calendar &rec = m_BarryCal;
424 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
425 rec.StartTime = osync_time_vtime2unix(start.c_str(), zoneoffset);
426 rec.EndTime = osync_time_vtime2unix(end.c_str(), zoneoffset);
427 // FIXME - until notification time is supported, we assume 15 min
428 // in advance
429 rec.NotificationTime = rec.StartTime - 15 * 60;
430 rec.Subject = subject;
432 return m_BarryCal;
435 // Transfers ownership of m_gCalData to the caller.
436 char* vCalendar::ExtractVCal()
438 char *ret = m_gCalData;
439 m_gCalData = 0;
440 return ret;
443 void vCalendar::Clear()
445 m_vCalData.clear();
446 m_BarryCal.Clear();
448 if( m_gCalData ) {
449 g_free(m_gCalData);
450 m_gCalData = 0;
453 if( m_format ) {
454 vformat_free(m_format);
455 m_format = 0;
461 //////////////////////////////////////////////////////////////////////////////
464 VEventConverter::VEventConverter()
465 : m_Data(0)
469 VEventConverter::VEventConverter(uint32_t newRecordId)
470 : m_Data(0),
471 m_RecordId(newRecordId)
475 VEventConverter::~VEventConverter()
477 if( m_Data )
478 g_free(m_Data);
481 // Transfers ownership of m_Data to the caller
482 char* VEventConverter::ExtractData()
484 Trace trace("VEventConverter::ExtractData");
485 char *ret = m_Data;
486 m_Data = 0;
487 return ret;
490 bool VEventConverter::ParseData(const char *data)
492 Trace trace("VEventConverter::ParseData");
494 try {
496 vCalendar vcal;
497 m_Cal = vcal.ToBarry(data, m_RecordId);
500 catch( vCalendar::ConvertError &ce ) {
501 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
502 return false;
505 return true;
508 // Barry storage operator
509 void VEventConverter::operator()(const Barry::Calendar &rec)
511 Trace trace("VEventConverter::operator()");
513 // Delete data if some already exists
514 if( m_Data ) {
515 g_free(m_Data);
516 m_Data = 0;
519 try {
521 vCalendar vcal;
522 vcal.ToVCal(rec);
523 m_Data = vcal.ExtractVCal();
526 catch( vCalendar::ConvertError &ce ) {
527 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
531 // Barry builder operator
532 bool VEventConverter::operator()(Barry::Calendar &rec, unsigned int dbId)
534 Trace trace("VEventConverter::builder operator()");
536 rec = m_Cal;
537 return true;
540 // Handles calling of the Barry::Controller to fetch a specific
541 // record, indicated by index (into the RecordStateTable).
542 // Returns a g_malloc'd string of data containing the vevent20
543 // data. It is the responsibility of the caller to free it.
544 // This is intended to be passed into the GetChanges() function.
545 char* VEventConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
546 Barry::RecordStateTable::IndexType index)
548 Trace trace("VEventConverter::GetRecordData()");
550 using namespace Barry;
552 VEventConverter cal2event;
553 RecordParser<Calendar, VEventConverter> parser(cal2event);
554 env->m_pCon->GetRecord(dbId, index, parser);
555 return cal2event.ExtractData();
558 bool VEventConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
559 Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
560 const char *data, bool add, std::string &errmsg)
562 Trace trace("VEventConverter::CommitRecordData()");
564 uint32_t newRecordId;
565 if( add ) {
566 // use given id if possible
567 if( recordId && !env->m_CalendarSync.m_Table.GetIndex(recordId) ) {
568 // recordId is unique and non-zero
569 newRecordId = recordId;
571 else {
572 trace.log("Can't use recommended recordId, generating new one.");
573 newRecordId = env->m_CalendarSync.m_Table.MakeNewRecordId();
576 else {
577 newRecordId = env->m_CalendarSync.m_Table.StateMap[StateIndex].RecordId;
579 trace.logf("newRecordId: %lu", newRecordId);
581 VEventConverter convert(newRecordId);
582 if( !convert.ParseData(data) ) {
583 std::ostringstream oss;
584 oss << "unable to parse change data for new RecordId: "
585 << newRecordId << " data: " << data;
586 errmsg = oss.str();
587 trace.logf(errmsg.c_str());
588 return false;
591 Barry::RecordBuilder<Barry::Calendar, VEventConverter> builder(convert);
593 if( add ) {
594 trace.log("adding record");
595 env->m_pCon->AddRecord(dbId, builder);
597 else {
598 trace.log("setting record");
599 env->m_pCon->SetRecord(dbId, StateIndex, builder);
600 trace.log("clearing dirty flag");
601 env->m_pCon->ClearDirty(dbId, StateIndex);
604 return true;