# vcard.cc

Go to the documentation of this file.

    00001 ///
    00002 /// \file       vcard.cc
    00003 ///             Conversion routines for vcards
    00004 ///
    00005
    00006 /*
    00007     Copyright (C) 2006-2011, Net Direct Inc. (http://www.netdirect.ca/)
    00008
    00009     This program is free software; you can redistribute it and/or modify
    00010     it under the terms of the GNU General Public License as published by
    00011     the Free Software Foundation; either version 2 of the License, or
    00012     (at your option) any later version.
    00013
    00014     This program is distributed in the hope that it will be useful,
    00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
    00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00017
    00018     See the GNU General Public License in the COPYING file at the
    00019     root directory of this project for more details.
    00020 */
    00021
    00022 #include "vcard.h"
    00023 #include <string.h>
    00024 #include <stdlib.h>
    00025 #include <ctype.h>
    00026 #include <sstream>
    00027
    00028 namespace Barry { namespace Sync {
    00029
    00030 //////////////////////////////////////////////////////////////////////////////
    00031 // Utility functions
    00032
    00033 namespace {
    00034
    00035         void ToLower(std::string &str)
    00036         {
    00037                 size_t i = 0;
    00038                 while( i < str.size() ) {
    00039                         str[i] = tolower(str[i]);
    00040                         i++;
    00041                 }
    00042         }
    00043
    00044 }
    00045
    00046 //////////////////////////////////////////////////////////////////////////////
    00047 // vCard
    00048
    00049 vCard::vCard()
    00050         : m_gCardData(0)
    00051 {
    00052 }
    00053
    00054 vCard::~vCard()
    00055 {
    00056         if( m_gCardData ) {
    00057                 g_free(m_gCardData);
    00058         }
    00059 }
    00060
    00061 void vCard::AddAddress(const char *rfc_type, const Barry::PostalAddress &address)
    00062 {
    00063         // add label first
    00064         vAttrPtr label = NewAttr("LABEL");
    00065         AddParam(label, "TYPE", rfc_type);
    00066         AddValue(label, address.GetLabel().c_str());
    00067         AddAttr(label);
    00068
    00069         // add breakout address form
    00070         vAttrPtr adr = NewAttr("ADR");                  // RFC 2426, 3.2.1
    00071         AddParam(adr, "TYPE", rfc_type);
    00072         AddValue(adr, address.Address3.c_str());        // PO Box
    00073         AddValue(adr, address.Address2.c_str());        // Extended address
    00074         AddValue(adr, address.Address1.c_str());        // Street address
    00075         AddValue(adr, address.City.c_str());            // Locality (city)
    00076         AddValue(adr, address.Province.c_str());        // Region (province)
    00077         AddValue(adr, address.PostalCode.c_str());      // Postal code
    00078         AddValue(adr, address.Country.c_str());         // Country name
    00079         AddAttr(adr);
    00080 }
    00081
    00082 void vCard::AddCategories(const Barry::CategoryList &categories)
    00083 {
    00084         if( !categories.size() )
    00085                 return;
    00086
    00087         vAttrPtr cat = NewAttr("CATEGORIES");           // RFC 2426, 3.6.1
    00088         Barry::CategoryList::const_iterator i = categories.begin();
    00089         for( ; i < categories.end(); ++i ) {
    00090                 AddValue(cat, i->c_str());
    00091         }
    00092         AddAttr(cat);
    00093 }
    00094
    00095 /// Add phone conditionally, only if phone has data in it.  This version
    00096 /// does not add a TYPE parameter to the item.
    00097 void vCard::AddPhoneCond(const std::string &phone)
    00098 {
    00099         if( phone.size() ) {
    00100                 vAttrPtr tel = NewAttr("TEL", phone.c_str());
    00101                 AddAttr(tel);
    00102         }
    00103 }
    00104
    00105 /// Add phone conditionally, only if phone has data in it
    00106 void vCard::AddPhoneCond(const char *rfc_type, const std::string &phone)
    00107 {
    00108         if( phone.size() ) {
    00109                 vAttrPtr tel = NewAttr("TEL", phone.c_str());
    00110                 AddParam(tel, "TYPE", rfc_type);
    00111                 AddAttr(tel);
    00112         }
    00113 }
    00114
    00115 void vCard::ParseAddress(vAttr &adr, Barry::PostalAddress &address)
    00116 {
    00117         // RFC 2426, 3.2.1
    00118         address.Address3 = adr.GetValue(0);             // PO Box
    00119         address.Address2 = adr.GetValue(1);             // Extended address
    00120         address.Address1 = adr.GetValue(2);             // Street address
    00121         address.City = adr.GetValue(3);                 // Locality (city)
    00122         address.Province = adr.GetValue(4);             // Region (province)
    00123         address.PostalCode = adr.GetValue(5);           // Postal code
    00124         address.Country = adr.GetValue(6);              // Country name
    00125 }
    00126
    00127 void vCard::ParseCategories(vAttr &cat, Barry::CategoryList &cats)
    00128 {
    00129         int i = 0;
    00130         std::string value = cat.GetValue(i);
    00131         while( value.size() ) {
    00132                 cats.push_back(value);
    00133                 i++;
    00134                 value = cat.GetValue(i);
    00135         }
    00136 }
    00137
    00138
    00139
    00140 // Main conversion routine for converting from Barry::Contact to
    00141 // a vCard string of data.
    00142 const std::string& vCard::ToVCard(const Barry::Contact &con)
    00143 {
    00144 //      Trace trace("vCard::ToVCard");
    00145         std::ostringstream oss;
    00146         con.Dump(oss);
    00147 //      trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str());
    00148
    00149         // start fresh
    00150         Clear();
    00151         SetFormat( b_vformat_new() );
    00152         if( !Format() )
    00153                 throw ConvertError("resource error allocating vformat");
    00154
    00155         // store the Barry object we're working with
    00156         m_BarryContact = con;
    00157
    00158         //
    00159         // begin building vCard data
    00160         //
    00161
    00162         AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN"));
    00163
    00164         std::string fullname = con.GetFullName();
    00165         if( fullname.size() ) {
    00166                 AddAttr(NewAttr("FN", fullname.c_str()));
    00167         }
    00168         else {
    00169                 //
    00170                 // RFC 2426, 3.1.1 states that FN MUST be present in the
    00171                 // vcard object.  Unfortunately, the Blackberry doesn't
    00172                 // require a name, only a name or company name.
    00173                 //
    00174                 // In this case we do nothing, and generate an invalid
    00175                 // vcard, since if we try to fix our output here, we'll
    00176                 // likely end up with duplicated company names in the
    00177                 // Blackberry record after a few syncs.
    00178                 //
    00179         }
    00180
    00181         if( con.FirstName.size() || con.LastName.size() ) {
    00182                 vAttrPtr name = NewAttr("N");           // RFC 2426, 3.1.2
    00183                 AddValue(name, con.LastName.c_str());   // Family Name
    00184                 AddValue(name, con.FirstName.c_str());  // Given Name
    00185                 AddValue(name, "");                     // Additional Names
    00186                 AddValue(name, con.Prefix.c_str());     // Honorific Prefixes
    00187                 AddValue(name, "");                     // Honorific Suffixes
    00188                 AddAttr(name);
    00189         }
    00190
    00191         if( con.WorkAddress.HasData() )
    00192                 AddAddress("work", con.WorkAddress);
    00193         if( con.HomeAddress.HasData() )
    00194                 AddAddress("home", con.HomeAddress);
    00195
    00196         // add all applicable phone numbers... there can be multiple
    00197         // TEL fields, even with the same TYPE value... therefore, the
    00198         // second TEL field with a TYPE=work, will be stored in WorkPhone2
    00199         AddPhoneCond("voice,pref", con.Phone);
    00200         AddPhoneCond("fax", con.Fax);
    00201         AddPhoneCond("voice,work", con.WorkPhone);
    00202         AddPhoneCond("voice,work", con.WorkPhone2);
    00203         AddPhoneCond("voice,home", con.HomePhone);
    00204         AddPhoneCond("voice,home", con.HomePhone2);
    00205         AddPhoneCond("msg,cell", con.MobilePhone);
    00206         AddPhoneCond("msg,pager", con.Pager);
    00207         AddPhoneCond("voice", con.OtherPhone);
    00208
    00209         // add all email addresses, marking first one as "pref"
    00210         Barry::Contact::EmailList::const_iterator eai = con.EmailAddresses.begin();
    00211         for( unsigned int i = 0; eai != con.EmailAddresses.end(); ++eai, ++i ) {
    00212                 const std::string& e = con.GetEmail(i);
    00213                 if( e.size() ) {
    00214                         vAttrPtr email = NewAttr("EMAIL", e.c_str());
    00215                         if( i == 0 ) {
    00216                                 AddParam(email, "TYPE", "internet,pref");
    00217                         }
    00218                         else {
    00219                                 AddParam(email, "TYPE", "internet");
    00220                         }
    00221                         AddAttr(email);
    00222                 }
    00223         }
    00224
    00225         if( con.JobTitle.size() ) {
    00226                 AddAttr(NewAttr("TITLE", con.JobTitle.c_str()));
    00227                 AddAttr(NewAttr("ROLE", con.JobTitle.c_str()));
    00228         }
    00229
    00230         if( con.Company.size() ) {
    00231                 // RFC 2426, 3.5.5
    00232                 vAttrPtr org = NewAttr("ORG", con.Company.c_str()); // Organization name
    00233                 AddValue(org, "");                      // Division name
    00234                 AddAttr(org);
    00235         }
    00236
    00237         if( con.Birthday.HasData() )
    00238                 AddAttr(NewAttr("BDAY", con.Birthday.ToYYYYMMDD().c_str()));
    00239
    00240         if( con.Notes.size() )
    00241                 AddAttr(NewAttr("NOTE", con.Notes.c_str()));
    00242         if( con.URL.size() )
    00243                 AddAttr(NewAttr("URL", con.URL.c_str()));
    00244         if( con.Categories.size() )
    00245                 AddCategories(con.Categories);
    00246
    00247         // Image / Photo
    00248         if (con.Image.size()) {
    00249                 vAttrPtr photo = NewAttr("PHOTO");
    00250                 AddEncodedValue(photo, VF_ENCODING_BASE64, con.Image.c_str(), con.Image.size());
    00251                 AddParam(photo, "ENCODING", "BASE64");
    00252                 AddAttr(photo);
    00253         }
    00254
    00255         // generate the raw VCARD data
    00256         m_gCardData = b_vformat_to_string(Format(), VFORMAT_CARD_30);
    00257         m_vCardData = m_gCardData;
    00258
    00259 //      trace.logf("ToVCard, resulting vcard data: %s", m_vCardData.c_str());
    00260         return m_vCardData;
    00261 }
    00262
    00263 //
    00264 // NOTE:
    00265 //      Treat the following pairs of variables like
    00266 //      sliding buffers, where higher priority values
    00267 //      can push existings values from 1 to 2, or from
    00268 //      2 to oblivion:
    00269 //
    00270 //              HomePhone + HomePhone2
    00271 //              WorkPhone + WorkPhone2
    00272 //              Phone + OtherPhone
    00273 //
    00274 //
    00275 // class SlidingPair
    00276 //
    00277 // This class handles the sliding pair logic for a pair of std::strings.
    00278 //
    00279 class SlidingPair
    00280 {
    00281         std::string &m_first;
    00282         std::string &m_second;
    00283         int m_evolutionSlot1, m_evolutionSlot2;
    00284 public:
    00285         static const int DefaultSlot = 99;
    00286         SlidingPair(std::string &first, std::string &second)
    00287                 : m_first(first)
    00288                 , m_second(second)
    00289                 , m_evolutionSlot1(DefaultSlot)
    00290                 , m_evolutionSlot2(DefaultSlot)
    00291         {
    00292         }
    00293
    00294         bool assign(const std::string &value, const char *type_str, int evolutionSlot)
    00295         {
    00296                 bool used = false;
    00297
    00298                 if( strstr(type_str, "pref") || evolutionSlot < m_evolutionSlot1 ) {
    00299                         m_second = m_first;
    00300                         m_evolutionSlot2 = m_evolutionSlot1;
    00301
    00302                         m_first = value;
    00303                         m_evolutionSlot1 = evolutionSlot;
    00304
    00305                         used = true;
    00306                 }
    00307                 else if( evolutionSlot < m_evolutionSlot2 ) {
    00308                         m_second = value;
    00309                         m_evolutionSlot2 = evolutionSlot;
    00310                         used = true;
    00311                 }
    00312                 else if( m_first.size() == 0 ) {
    00313                         m_first = value;
    00314                         m_evolutionSlot1 = evolutionSlot;
    00315                         used = true;
    00316                 }
    00317                 else if( m_second.size() == 0 ) {
    00318                         m_second = value;
    00319                         m_evolutionSlot2 = evolutionSlot;
    00320                         used = true;
    00321                 }
    00322
    00323                 return used;
    00324         }
    00325 };
    00326
    00327
    00328 // Main conversion routine for converting from vCard data string
    00329 // to a Barry::Contact object.
    00330 const Barry::Contact& vCard::ToBarry(const char *vcard, uint32_t RecordId)
    00331 {
    00332         using namespace std;
    00333
    00334 //      Trace trace("vCard::ToBarry");
    00335 //      trace.logf("ToBarry, working on vcard data: %s", vcard);
    00336
    00337         // start fresh
    00338         Clear();
    00339
    00340         // store the vCard raw data
    00341         m_vCardData = vcard;
    00342
    00343         // create format parser structures
    00344         SetFormat( b_vformat_new_from_string(vcard) );
    00345         if( !Format() )
    00346                 throw ConvertError("resource error allocating vformat");
    00347
    00348
    00349         //
    00350         // Parse the vcard data
    00351         //
    00352
    00353         Barry::Contact &con = m_BarryContact;
    00354         con.SetIds(Barry::Contact::GetDefaultRecType(), RecordId);
    00355
    00356         vAttr name = GetAttrObj("N");
    00357         if( name.Get() ) {
    00358                                                         // RFC 2426, 3.1.2
    00359                 con.LastName = name.GetValue(0);        // Family Name
    00360                 con.FirstName = name.GetValue(1);       // Given Name
    00361                 con.Prefix = name.GetValue(3);          // Honorific Prefixes
    00362         }
    00363
    00364         vAttr adr = GetAttrObj("ADR");
    00365         for( int i = 0; adr.Get(); adr = GetAttrObj("ADR", ++i) )
    00366         {
    00367                 std::string type = adr.GetAllParams("TYPE");
    00368                 ToLower(type);
    00369
    00370                 // do not use "else" here, since TYPE can have multiple keys
    00371                 if( strstr(type.c_str(), "work") )
    00372                         ParseAddress(adr, con.WorkAddress);
    00373                 if( strstr(type.c_str(), "home") )
    00374                         ParseAddress(adr, con.HomeAddress);
    00375         }
    00376
    00377
    00378         //
    00379         // NOTE:
    00380         //      Treat the following pairs of variables like
    00381         //      sliding buffers, where higher priority values
    00382         //      can push existings values from 1 to 2, or from
    00383         //      2 to oblivion:
    00384         //
    00385         //              HomePhone + HomePhone2
    00386         //              WorkPhone + WorkPhone2
    00387         //              Phone + OtherPhone
    00388         //
    00389         SlidingPair HomePair(con.HomePhone, con.HomePhone2);
    00390         SlidingPair WorkPair(con.WorkPhone, con.WorkPhone2);
    00391         SlidingPair OtherPair(con.Phone, con.OtherPhone);
    00392
    00393         // add all applicable phone numbers... there can be multiple
    00394         // TEL fields, even with the same TYPE value... therefore, the
    00395         // second TEL field with a TYPE=work, will be stored in WorkPhone2
    00396         vAttr tel = GetAttrObj("TEL");
    00397         for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) )
    00398         {
    00399                 // grab all parameter values for this param name
    00400                 std::string stype = tel.GetAllParams("TYPE");
    00401
    00402                 // grab evolution-specific parameter... evolution is too
    00403                 // lazy to sort its VCARD output, but instead it does
    00404                 // its own non-standard tagging... so we try to
    00405                 // accommodate it, so Work and Home phone numbers keep
    00406                 // their order if possible
    00407                 int evolutionSlot = atoi(tel.GetAllParams("X-EVOLUTION-UI-SLOT").c_str());
    00408                 if( evolutionSlot == 0 )
    00409                         evolutionSlot = SlidingPair::DefaultSlot;
    00410
    00411                 // turn to lower case for comparison
    00412                 // FIXME - is this i18n safe?
    00413                 ToLower(stype);
    00414
    00415                 // state
    00416                 const char *type = stype.c_str();
    00417                 bool used = false;
    00418
    00419                 // Check for possible TYPE conflicts:
    00420                 //    pager can coexist with cell/pcs/car
    00421                 //    fax conflicts with cell/pcs/car
    00422                 //    fax conflicts with pager
    00423                 bool mobile_type = strstr(type, "cell") ||
    00424                         strstr(type, "pcs") ||
    00425                         strstr(type, "car");
    00426                 bool fax_type = strstr(type, "fax");
    00427                 bool pager_type = strstr(type, "pager");
    00428                 if( fax_type && (mobile_type || pager_type) ) {
    00429                         // conflict found, log and skip
    00430 //                      trace.logf("ToBarry: skipping phone number due to TYPE conflict: fax cannot coexist with %s: %s",
    00431 //                              mobile_type ? "cell/pcs/car" : "pager",
    00432 //                              type);
    00433                         continue;
    00434                 }
    00435
    00436                 // If phone number has the "pref" flag
    00437                 if( strstr(type, "pref") ) {
    00438                         // Always use cell phone if the "pref" flag is set
    00439                         if( strstr(type, "cell") ) {
    00440                                 used = OtherPair.assign(tel.GetValue(), type, evolutionSlot);
    00441                         }
    00442                         // Otherwise, the phone has to be "voice" type
    00443                         else if( strstr(type, "voice") && con.Phone.size() == 0 ) {
    00444                                 used = OtherPair.assign(tel.GetValue(), type, evolutionSlot);
    00445                         }
    00446                 }
    00447
    00448                 // For each known phone type
    00449                 // Fax :
    00450                 if( strstr(type, "fax") && (strstr(type, "pref") || con.Fax.size() == 0) ) {
    00451                         con.Fax = tel.GetValue();
    00452                         used = true;
    00453                 }
    00454                 // Mobile phone :
    00455                 else if( mobile_type && (strstr(type, "pref") || con.MobilePhone.size() == 0) ) {
    00456                         con.MobilePhone = tel.GetValue();
    00457                         used = true;
    00458                 }
    00459                 // Pager :
    00460                 else if( strstr(type, "pager") && (strstr(type, "pref") || con.Pager.size() == 0) ) {
    00461                         con.Pager = tel.GetValue();
    00462                         used = true;
    00463                 }
    00464                 // Check for any TEL-ignore types, and use other phone field if possible
    00465                 // bbs/video/modem   entire TEL ignored by Barry
    00466                 // isdn              entire TEL ignored by Barry
    00467                 else if( strstr(type, "bbs") || strstr(type, "video") || strstr(type, "modem") ) {
    00468                 }
    00469                 else if( strstr(type, "isdn") ) {
    00470                 }
    00471                 // Voice telephone :
    00472                 else {
    00473                         if( strstr(type, "work") ) {
    00474                                 used = WorkPair.assign(tel.GetValue(), type, evolutionSlot);
    00475                         }
    00476
    00477                         if( strstr(type, "home") ) {
    00478                                 used = HomePair.assign(tel.GetValue(), type, evolutionSlot);
    00479                         }
    00480                 }
    00481
    00482                 // if this value has not been claimed by any of the
    00483                 // cases above, claim it now as "OtherPhone"
    00484                 if( !used && con.OtherPhone.size() == 0 ) {
    00485                         OtherPair.assign(tel.GetValue(), type, evolutionSlot);
    00486                 }
    00487         }
    00488
    00489         // scan for all email addresses... append addresses to the
    00490         // list by default, but prepend if its type is set to "pref"
    00491         // i.e. we want the preferred email address first
    00492         vAttr email = GetAttrObj("EMAIL");
    00493         for( int i = 0; email.Get(); email = GetAttrObj("EMAIL", ++i) )
    00494         {
    00495                 std::string type = email.GetAllParams("TYPE");
    00496                 ToLower(type);
    00497
    00498                 bool of_interest = (i == 0 || strstr(type.c_str(), "pref"));
    00499                 bool x400 = strstr(type.c_str(), "x400");
    00500
    00501                 if( of_interest && !x400 ) {
    00502                         con.EmailAddresses.insert(con.EmailAddresses.begin(), email.GetValue());
    00503                 }
    00504                 else {
    00505                         con.EmailAddresses.push_back( email.GetValue() );
    00506                 }
    00507         }
    00508
    00509         // figure out which company title we want to keep...
    00510         // favour the TITLE field, but if it's empty, use ROLE
    00511         con.JobTitle = GetAttr("TITLE");
    00512         if( !con.JobTitle.size() )
    00513                 con.JobTitle = GetAttr("ROLE");
    00514
    00515         con.Company = GetAttr("ORG");
    00516         con.Notes = GetAttr("NOTE");
    00517         con.URL = GetAttr("URL");
    00518         if( GetAttr("BDAY").size() && !con.Birthday.FromYYYYMMDD( GetAttr("BDAY") ) )
    00519                 throw ConvertError("Unable to parse BDAY field");
    00520
    00521         // Photo vCard ?
    00522         vAttr photo = GetAttrObj("PHOTO");
    00523         if (photo.Get()) {
    00524                 std::string sencoding = photo.GetAllParams("ENCODING");
    00525
    00526                 ToLower(sencoding);
    00527
    00528                 const char *encoding = sencoding.c_str();
    00529
    00530                 if (strstr(encoding, "quoted-printable")) {
    00531                         photo.Get()->encoding = VF_ENCODING_QP;
    00532
    00533                         con.Image = photo.GetDecodedValue();
    00534                 }
    00535                 else if (strstr(encoding, "b")) {
    00536                         photo.Get()->encoding = VF_ENCODING_BASE64;
    00537
    00538                         con.Image = photo.GetDecodedValue();
    00539                 }
    00540                 // Else
    00541                 // We ignore the photo, I don't know decoded !
    00542         }
    00543
    00544         vAttr cat = GetAttrObj("CATEGORIES");
    00545         if( cat.Get() )
    00546                 ParseCategories(cat, con.Categories);
    00547
    00548         // Last sanity check: Blackberry requires that at least
    00549         // name or Company has data.
    00550         if( !con.GetFullName().size() && !con.Company.size() )
    00551                 throw ConvertError("FN and ORG fields both blank in VCARD data");
    00552
    00553         return m_BarryContact;
    00554 }
    00555
    00556 // Transfers ownership of m_gCardData to the caller.
    00557 char* vCard::ExtractVCard()
    00558 {
    00559         char *ret = m_gCardData;
    00560         m_gCardData = 0;
    00561         return ret;
    00562 }
    00563
    00564 void vCard::Clear()
    00565 {
    00566         vBase::Clear();
    00567         m_vCardData.clear();
    00568         m_BarryContact.Clear();
    00569
    00570         if( m_gCardData ) {
    00571                 g_free(m_gCardData);
    00572                 m_gCardData = 0;
    00573         }
    00574 }
    00575
    00576 }} // namespace Barry::Sync
    00577