Updated ChangeLog
[barry.git] / src / vcard.cc
blob385bced853ab9905d14f74a068be7531d4cc3811
1 ///
2 /// \file vcard.cc
3 /// Conversion routines for vcards
4 ///
6 /*
7 Copyright (C) 2006-2010, 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 "vcard.h"
23 #include <string.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <sstream>
28 namespace Barry { namespace Sync {
30 //////////////////////////////////////////////////////////////////////////////
31 // Utility functions
33 namespace {
35 void ToLower(std::string &str)
37 size_t i = 0;
38 while( i < str.size() ) {
39 str[i] = tolower(str[i]);
40 i++;
46 //////////////////////////////////////////////////////////////////////////////
47 // vCard
49 vCard::vCard()
50 : m_gCardData(0)
54 vCard::~vCard()
56 if( m_gCardData ) {
57 g_free(m_gCardData);
61 void vCard::AddAddress(const char *rfc_type, const Barry::PostalAddress &address)
63 // add label first
64 vAttrPtr label = NewAttr("LABEL");
65 AddParam(label, "TYPE", rfc_type);
66 AddValue(label, address.GetLabel().c_str());
67 AddAttr(label);
69 // add breakout address form
70 vAttrPtr adr = NewAttr("ADR"); // RFC 2426, 3.2.1
71 AddParam(adr, "TYPE", rfc_type);
72 AddValue(adr, address.Address3.c_str()); // PO Box
73 AddValue(adr, address.Address2.c_str()); // Extended address
74 AddValue(adr, address.Address1.c_str()); // Street address
75 AddValue(adr, address.City.c_str()); // Locality (city)
76 AddValue(adr, address.Province.c_str()); // Region (province)
77 AddValue(adr, address.PostalCode.c_str()); // Postal code
78 AddValue(adr, address.Country.c_str()); // Country name
79 AddAttr(adr);
82 void vCard::AddCategories(const Barry::CategoryList &categories)
84 if( !categories.size() )
85 return;
87 vAttrPtr cat = NewAttr("CATEGORIES"); // RFC 2426, 3.6.1
88 Barry::CategoryList::const_iterator i = categories.begin();
89 for( ; i < categories.end(); ++i ) {
90 AddValue(cat, i->c_str());
92 AddAttr(cat);
95 /// Add phone conditionally, only if phone has data in it. This version
96 /// does not add a TYPE parameter to the item.
97 void vCard::AddPhoneCond(const std::string &phone)
99 if( phone.size() ) {
100 vAttrPtr tel = NewAttr("TEL", phone.c_str());
101 AddAttr(tel);
105 /// Add phone conditionally, only if phone has data in it
106 void vCard::AddPhoneCond(const char *rfc_type, const std::string &phone)
108 if( phone.size() ) {
109 vAttrPtr tel = NewAttr("TEL", phone.c_str());
110 AddParam(tel, "TYPE", rfc_type);
111 AddAttr(tel);
115 void vCard::ParseAddress(vAttr &adr, Barry::PostalAddress &address)
117 // RFC 2426, 3.2.1
118 address.Address3 = adr.GetValue(0); // PO Box
119 address.Address2 = adr.GetValue(1); // Extended address
120 address.Address1 = adr.GetValue(2); // Street address
121 address.City = adr.GetValue(3); // Locality (city)
122 address.Province = adr.GetValue(4); // Region (province)
123 address.PostalCode = adr.GetValue(5); // Postal code
124 address.Country = adr.GetValue(6); // Country name
127 void vCard::ParseCategories(vAttr &cat, Barry::CategoryList &cats)
129 int i = 0;
130 std::string value = cat.GetValue(i);
131 while( value.size() ) {
132 cats.push_back(value);
133 i++;
134 value = cat.GetValue(i);
140 // Main conversion routine for converting from Barry::Contact to
141 // a vCard string of data.
142 const std::string& vCard::ToVCard(const Barry::Contact &con)
144 // Trace trace("vCard::ToVCard");
145 std::ostringstream oss;
146 con.Dump(oss);
147 // trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str());
149 // start fresh
150 Clear();
151 SetFormat( b_vformat_new() );
152 if( !Format() )
153 throw ConvertError("resource error allocating vformat");
155 // store the Barry object we're working with
156 m_BarryContact = con;
159 // begin building vCard data
162 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN"));
164 std::string fullname = con.GetFullName();
165 if( fullname.size() ) {
166 AddAttr(NewAttr("FN", fullname.c_str()));
168 else {
170 // RFC 2426, 3.1.1 states that FN MUST be present in the
171 // vcard object. Unfortunately, the Blackberry doesn't
172 // require a name, only a name or company name.
174 // In this case we do nothing, and generate an invalid
175 // vcard, since if we try to fix our output here, we'll
176 // likely end up with duplicated company names in the
177 // Blackberry record after a few syncs.
181 if( con.FirstName.size() || con.LastName.size() ) {
182 vAttrPtr name = NewAttr("N"); // RFC 2426, 3.1.2
183 AddValue(name, con.LastName.c_str()); // Family Name
184 AddValue(name, con.FirstName.c_str()); // Given Name
185 AddValue(name, ""); // Additional Names
186 AddValue(name, con.Prefix.c_str()); // Honorific Prefixes
187 AddValue(name, ""); // Honorific Suffixes
188 AddAttr(name);
191 if( con.WorkAddress.HasData() )
192 AddAddress("work", con.WorkAddress);
193 if( con.HomeAddress.HasData() )
194 AddAddress("home", con.HomeAddress);
196 // add all applicable phone numbers... there can be multiple
197 // TEL fields, even with the same TYPE value... therefore, the
198 // second TEL field with a TYPE=work, will be stored in WorkPhone2
199 AddPhoneCond("voice,pref", con.Phone);
200 AddPhoneCond("fax", con.Fax);
201 AddPhoneCond("voice,work", con.WorkPhone);
202 AddPhoneCond("voice,work", con.WorkPhone2);
203 AddPhoneCond("voice,home", con.HomePhone);
204 AddPhoneCond("voice,home", con.HomePhone2);
205 AddPhoneCond("msg,cell", con.MobilePhone);
206 AddPhoneCond("msg,pager", con.Pager);
207 AddPhoneCond("voice", con.OtherPhone);
209 // add all email addresses, marking first one as "pref"
210 Barry::Contact::EmailList::const_iterator eai = con.EmailAddresses.begin();
211 for( unsigned int i = 0; eai != con.EmailAddresses.end(); ++eai, ++i ) {
212 const std::string& e = con.GetEmail(i);
213 if( e.size() ) {
214 vAttrPtr email = NewAttr("EMAIL", e.c_str());
215 if( i == 0 ) {
216 AddParam(email, "TYPE", "internet,pref");
218 else {
219 AddParam(email, "TYPE", "internet");
221 AddAttr(email);
225 if( con.JobTitle.size() ) {
226 AddAttr(NewAttr("TITLE", con.JobTitle.c_str()));
227 AddAttr(NewAttr("ROLE", con.JobTitle.c_str()));
230 if( con.Company.size() ) {
231 // RFC 2426, 3.5.5
232 vAttrPtr org = NewAttr("ORG", con.Company.c_str()); // Organization name
233 AddValue(org, ""); // Division name
234 AddAttr(org);
237 if( con.Birthday.HasData() )
238 AddAttr(NewAttr("BDAY", con.Birthday.ToYYYYMMDD().c_str()));
240 if( con.Notes.size() )
241 AddAttr(NewAttr("NOTE", con.Notes.c_str()));
242 if( con.URL.size() )
243 AddAttr(NewAttr("URL", con.URL.c_str()));
244 if( con.Categories.size() )
245 AddCategories(con.Categories);
247 // Image / Photo
248 if (con.Image.size()) {
249 vAttrPtr photo = NewAttr("PHOTO");
250 AddEncodedValue(photo, VF_ENCODING_BASE64, con.Image.c_str(), con.Image.size());
251 AddParam(photo, "ENCODING", "BASE64");
252 AddAttr(photo);
255 // generate the raw VCARD data
256 m_gCardData = b_vformat_to_string(Format(), VFORMAT_CARD_30);
257 m_vCardData = m_gCardData;
259 // trace.logf("ToVCard, resulting vcard data: %s", m_vCardData.c_str());
260 return m_vCardData;
264 // NOTE:
265 // Treat the following pairs of variables like
266 // sliding buffers, where higher priority values
267 // can push existings values from 1 to 2, or from
268 // 2 to oblivion:
270 // HomePhone + HomePhone2
271 // WorkPhone + WorkPhone2
272 // Phone + OtherPhone
275 // class SlidingPair
277 // This class handles the sliding pair logic for a pair of std::strings.
279 class SlidingPair
281 std::string &m_first;
282 std::string &m_second;
283 int m_evolutionSlot1, m_evolutionSlot2;
284 public:
285 static const int DefaultSlot = 99;
286 SlidingPair(std::string &first, std::string &second)
287 : m_first(first)
288 , m_second(second)
289 , m_evolutionSlot1(DefaultSlot)
290 , m_evolutionSlot2(DefaultSlot)
294 bool assign(const std::string &value, const char *type_str, int evolutionSlot)
296 bool used = false;
298 if( strstr(type_str, "pref") || evolutionSlot < m_evolutionSlot1 ) {
299 m_second = m_first;
300 m_evolutionSlot2 = m_evolutionSlot1;
302 m_first = value;
303 m_evolutionSlot1 = evolutionSlot;
305 used = true;
307 else if( evolutionSlot < m_evolutionSlot2 ) {
308 m_second = value;
309 m_evolutionSlot2 = evolutionSlot;
310 used = true;
312 else if( m_first.size() == 0 ) {
313 m_first = value;
314 m_evolutionSlot1 = evolutionSlot;
315 used = true;
317 else if( m_second.size() == 0 ) {
318 m_second = value;
319 m_evolutionSlot2 = evolutionSlot;
320 used = true;
323 return used;
328 // Main conversion routine for converting from vCard data string
329 // to a Barry::Contact object.
330 const Barry::Contact& vCard::ToBarry(const char *vcard, uint32_t RecordId)
332 using namespace std;
334 // Trace trace("vCard::ToBarry");
335 // trace.logf("ToBarry, working on vcard data: %s", vcard);
337 // start fresh
338 Clear();
340 // store the vCard raw data
341 m_vCardData = vcard;
343 // create format parser structures
344 SetFormat( b_vformat_new_from_string(vcard) );
345 if( !Format() )
346 throw ConvertError("resource error allocating vformat");
350 // Parse the vcard data
353 Barry::Contact &con = m_BarryContact;
354 con.SetIds(Barry::Contact::GetDefaultRecType(), RecordId);
356 vAttr name = GetAttrObj("N");
357 if( name.Get() ) {
358 // RFC 2426, 3.1.2
359 con.LastName = name.GetValue(0); // Family Name
360 con.FirstName = name.GetValue(1); // Given Name
361 con.Prefix = name.GetValue(3); // Honorific Prefixes
364 vAttr adr = GetAttrObj("ADR");
365 for( int i = 0; adr.Get(); adr = GetAttrObj("ADR", ++i) )
367 std::string type = adr.GetAllParams("TYPE");
368 ToLower(type);
370 // do not use "else" here, since TYPE can have multiple keys
371 if( strstr(type.c_str(), "work") )
372 ParseAddress(adr, con.WorkAddress);
373 if( strstr(type.c_str(), "home") )
374 ParseAddress(adr, con.HomeAddress);
379 // NOTE:
380 // Treat the following pairs of variables like
381 // sliding buffers, where higher priority values
382 // can push existings values from 1 to 2, or from
383 // 2 to oblivion:
385 // HomePhone + HomePhone2
386 // WorkPhone + WorkPhone2
387 // Phone + OtherPhone
389 SlidingPair HomePair(con.HomePhone, con.HomePhone2);
390 SlidingPair WorkPair(con.WorkPhone, con.WorkPhone2);
391 SlidingPair OtherPair(con.Phone, con.OtherPhone);
393 // add all applicable phone numbers... there can be multiple
394 // TEL fields, even with the same TYPE value... therefore, the
395 // second TEL field with a TYPE=work, will be stored in WorkPhone2
396 vAttr tel = GetAttrObj("TEL");
397 for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) )
399 // grab all parameter values for this param name
400 std::string stype = tel.GetAllParams("TYPE");
402 // grab evolution-specific parameter... evolution is too
403 // lazy to sort its VCARD output, but instead it does
404 // its own non-standard tagging... so we try to
405 // accommodate it, so Work and Home phone numbers keep
406 // their order if possible
407 int evolutionSlot = atoi(tel.GetAllParams("X-EVOLUTION-UI-SLOT").c_str());
408 if( evolutionSlot == 0 )
409 evolutionSlot = SlidingPair::DefaultSlot;
411 // turn to lower case for comparison
412 // FIXME - is this i18n safe?
413 ToLower(stype);
415 // state
416 const char *type = stype.c_str();
417 bool used = false;
419 // Check for possible TYPE conflicts:
420 // pager can coexist with cell/pcs/car
421 // fax conflicts with cell/pcs/car
422 // fax conflicts with pager
423 bool mobile_type = strstr(type, "cell") ||
424 strstr(type, "pcs") ||
425 strstr(type, "car");
426 bool fax_type = strstr(type, "fax");
427 bool pager_type = strstr(type, "pager");
428 if( fax_type && (mobile_type || pager_type) ) {
429 // conflict found, log and skip
430 // trace.logf("ToBarry: skipping phone number due to TYPE conflict: fax cannot coexist with %s: %s",
431 // mobile_type ? "cell/pcs/car" : "pager",
432 // type);
433 continue;
436 // If phone number has the "pref" flag
437 if( strstr(type, "pref") ) {
438 // Always use cell phone if the "pref" flag is set
439 if( strstr(type, "cell") ) {
440 used = OtherPair.assign(tel.GetValue(), type, evolutionSlot);
442 // Otherwise, the phone has to be "voice" type
443 else if( strstr(type, "voice") && con.Phone.size() == 0 ) {
444 used = OtherPair.assign(tel.GetValue(), type, evolutionSlot);
448 // For each known phone type
449 // Fax :
450 if( strstr(type, "fax") && (strstr(type, "pref") || con.Fax.size() == 0) ) {
451 con.Fax = tel.GetValue();
452 used = true;
454 // Mobile phone :
455 else if( mobile_type && (strstr(type, "pref") || con.MobilePhone.size() == 0) ) {
456 con.MobilePhone = tel.GetValue();
457 used = true;
459 // Pager :
460 else if( strstr(type, "pager") && (strstr(type, "pref") || con.Pager.size() == 0) ) {
461 con.Pager = tel.GetValue();
462 used = true;
464 // Check for any TEL-ignore types, and use other phone field if possible
465 // bbs/video/modem entire TEL ignored by Barry
466 // isdn entire TEL ignored by Barry
467 else if( strstr(type, "bbs") || strstr(type, "video") || strstr(type, "modem") ) {
469 else if( strstr(type, "isdn") ) {
471 // Voice telephone :
472 else {
473 if( strstr(type, "work") ) {
474 used = WorkPair.assign(tel.GetValue(), type, evolutionSlot);
477 if( strstr(type, "home") ) {
478 used = HomePair.assign(tel.GetValue(), type, evolutionSlot);
482 // if this value has not been claimed by any of the
483 // cases above, claim it now as "OtherPhone"
484 if( !used && con.OtherPhone.size() == 0 ) {
485 OtherPair.assign(tel.GetValue(), type, evolutionSlot);
489 // scan for all email addresses... append addresses to the
490 // list by default, but prepend if its type is set to "pref"
491 // i.e. we want the preferred email address first
492 vAttr email = GetAttrObj("EMAIL");
493 for( int i = 0; email.Get(); email = GetAttrObj("EMAIL", ++i) )
495 std::string type = email.GetAllParams("TYPE");
496 ToLower(type);
498 bool of_interest = (i == 0 || strstr(type.c_str(), "pref"));
499 bool x400 = strstr(type.c_str(), "x400");
501 if( of_interest && !x400 ) {
502 con.EmailAddresses.insert(con.EmailAddresses.begin(), email.GetValue());
504 else {
505 con.EmailAddresses.push_back( email.GetValue() );
509 // figure out which company title we want to keep...
510 // favour the TITLE field, but if it's empty, use ROLE
511 con.JobTitle = GetAttr("TITLE");
512 if( !con.JobTitle.size() )
513 con.JobTitle = GetAttr("ROLE");
515 con.Company = GetAttr("ORG");
516 con.Notes = GetAttr("NOTE");
517 con.URL = GetAttr("URL");
518 if( GetAttr("BDAY").size() && !con.Birthday.FromYYYYMMDD( GetAttr("BDAY") ) )
519 throw ConvertError("Unable to parse BDAY field");
521 // Photo vCard ?
522 vAttr photo = GetAttrObj("PHOTO");
523 if (photo.Get()) {
524 std::string sencoding = photo.GetAllParams("ENCODING");
526 ToLower(sencoding);
528 const char *encoding = sencoding.c_str();
530 if (strstr(encoding, "quoted-printable")) {
531 photo.Get()->encoding = VF_ENCODING_QP;
533 con.Image = photo.GetDecodedValue();
535 else if (strstr(encoding, "b")) {
536 photo.Get()->encoding = VF_ENCODING_BASE64;
538 con.Image = photo.GetDecodedValue();
540 // Else
541 // We ignore the photo, I don't know decoded !
544 vAttr cat = GetAttrObj("CATEGORIES");
545 if( cat.Get() )
546 ParseCategories(cat, con.Categories);
548 // Last sanity check: Blackberry requires that at least
549 // name or Company has data.
550 if( !con.GetFullName().size() && !con.Company.size() )
551 throw ConvertError("FN and ORG fields both blank in VCARD data");
553 return m_BarryContact;
556 // Transfers ownership of m_gCardData to the caller.
557 char* vCard::ExtractVCard()
559 char *ret = m_gCardData;
560 m_gCardData = 0;
561 return ret;
564 void vCard::Clear()
566 vBase::Clear();
567 m_vCardData.clear();
568 m_BarryContact.Clear();
570 if( m_gCardData ) {
571 g_free(m_gCardData);
572 m_gCardData = 0;
576 }} // namespace Barry::Sync