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