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-2010, 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 
Generated by  doxygen 1.6.2-20100208