ldifconverter.cpp
00001 /* 00002 This file is part of libkabc. 00003 Copyright (c) 2003 Helge Deller <deller@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 00022 /* 00023 Useful links: 00024 - http://tldp.org/HOWTO/LDAP-Implementation-HOWTO/schemas.html 00025 - http://www.faqs.org/rfcs/rfc2849.html 00026 00027 Not yet handled items: 00028 - objectclass microsoftaddressbook 00029 - info, 00030 - initials, 00031 - otherfacsimiletelephonenumber, 00032 - otherpager, 00033 - physicaldeliveryofficename, 00034 */ 00035 00036 #include <qstring.h> 00037 #include <qstringlist.h> 00038 #include <qregexp.h> 00039 #include <qtextstream.h> 00040 00041 #include <klocale.h> 00042 #include <kdebug.h> 00043 #include <kmdcodec.h> 00044 00045 #include "addressee.h" 00046 #include "address.h" 00047 00048 #include "ldif.h" 00049 #include "ldifconverter.h" 00050 #include "vcardconverter.h" 00051 00052 using namespace KABC; 00053 00054 /* generate LDIF stream */ 00055 00056 bool LDIFConverter::addresseeToLDIF( const AddresseeList &addrList, QString &str ) 00057 { 00058 AddresseeList::ConstIterator it; 00059 for ( it = addrList.begin(); it != addrList.end(); ++it ) { 00060 addresseeToLDIF( *it, str ); 00061 } 00062 return true; 00063 } 00064 00065 00066 00067 static void ldif_out( QTextStream &t, QString formatStr, QString value ) 00068 { 00069 if ( value.isEmpty() ) 00070 return; 00071 00072 QCString txt = LDIF::assembleLine( formatStr, value, 72 ); 00073 00074 // write the string 00075 t << QString::fromUtf8(txt) << "\n"; 00076 } 00077 00078 bool LDIFConverter::addresseeToLDIF( const Addressee &addr, QString &str ) 00079 { 00080 if ( addr.isEmpty() ) 00081 return false; 00082 00083 QTextStream t( str, IO_WriteOnly|IO_Append ); 00084 t.setEncoding( QTextStream::UnicodeUTF8 ); 00085 00086 const Address homeAddr = addr.address( Address::Home ); 00087 const Address workAddr = addr.address( Address::Work ); 00088 00089 ldif_out( t, "dn", QString( "cn=%1,mail=%2" ) 00090 .arg( addr.formattedName().simplifyWhiteSpace() ) 00091 .arg( addr.preferredEmail() ) ); 00092 ldif_out( t, "givenname", addr.givenName() ); 00093 ldif_out( t, "sn", addr.familyName() ); 00094 ldif_out( t, "cn", addr.formattedName().simplifyWhiteSpace() ); 00095 ldif_out( t, "uid", addr.uid() ); 00096 ldif_out( t, "nickname", addr.nickName() ); 00097 ldif_out( t, "xmozillanickname", addr.nickName() ); 00098 00099 ldif_out( t, "mail", addr.preferredEmail() ); 00100 if ( addr.emails().count() > 1 ) 00101 ldif_out( t, "mozillasecondemail", addr.emails()[ 1 ] ); 00102 //ldif_out( t, "mozilla_AIMScreenName: %1\n", "screen_name" ); 00103 00104 ldif_out( t, "telephonenumber", addr.phoneNumber( PhoneNumber::Work ).number() ); 00105 ldif_out( t, "facsimiletelephonenumber", addr.phoneNumber( PhoneNumber::Fax ).number() ); 00106 ldif_out( t, "homephone", addr.phoneNumber( PhoneNumber::Home ).number() ); 00107 ldif_out( t, "mobile", addr.phoneNumber( PhoneNumber::Cell ).number() ); // Netscape 7 00108 ldif_out( t, "cellphone", addr.phoneNumber( PhoneNumber::Cell ).number() ); // Netscape 4.x 00109 ldif_out( t, "pager", addr.phoneNumber( PhoneNumber::Pager ).number() ); 00110 ldif_out( t, "pagerphone", addr.phoneNumber( PhoneNumber::Pager ).number() ); 00111 00112 ldif_out( t, "streethomeaddress", homeAddr.street() ); 00113 ldif_out( t, "postalcode", workAddr.postalCode() ); 00114 ldif_out( t, "postofficebox", workAddr.postOfficeBox() ); 00115 00116 QStringList streets = QStringList::split( '\n', homeAddr.street() ); 00117 if ( streets.count() > 0 ) 00118 ldif_out( t, "homepostaladdress", streets[ 0 ] ); // Netscape 7 00119 if ( streets.count() > 1 ) 00120 ldif_out( t, "mozillahomepostaladdress2", streets[ 1 ] ); // Netscape 7 00121 ldif_out( t, "mozillahomelocalityname", homeAddr.locality() ); // Netscape 7 00122 ldif_out( t, "mozillahomestate", homeAddr.region() ); 00123 ldif_out( t, "mozillahomepostalcode", homeAddr.postalCode() ); 00124 ldif_out( t, "mozillahomecountryname", Address::ISOtoCountry(homeAddr.country()) ); 00125 ldif_out( t, "locality", workAddr.locality() ); 00126 ldif_out( t, "streetaddress", workAddr.street() ); // Netscape 4.x 00127 00128 streets = QStringList::split( '\n', workAddr.street() ); 00129 if ( streets.count() > 0 ) 00130 ldif_out( t, "postaladdress", streets[ 0 ] ); 00131 if ( streets.count() > 1 ) 00132 ldif_out( t, "mozillapostaladdress2", streets[ 1 ] ); 00133 ldif_out( t, "countryname", Address::ISOtoCountry(workAddr.country()) ); 00134 ldif_out( t, "l", workAddr.locality() ); 00135 ldif_out( t, "c", Address::ISOtoCountry(workAddr.country()) ); 00136 ldif_out( t, "st", workAddr.region() ); 00137 00138 ldif_out( t, "title", addr.title() ); 00139 ldif_out( t, "vocation", addr.prefix() ); 00140 ldif_out( t, "ou", addr.role() ); 00141 ldif_out( t, "o", addr.organization() ); 00142 ldif_out( t, "organization", addr.organization() ); 00143 ldif_out( t, "organizationname", addr.organization() ); 00144 00145 // Compatibility with older kabc versions. 00146 if ( !addr.department().isEmpty() ) 00147 ldif_out( t, "department", addr.department() ); 00148 else 00149 ldif_out( t, "department", addr.custom("KADDRESSBOOK", "X-Department") ); 00150 00151 ldif_out( t, "workurl", addr.url().prettyURL() ); 00152 ldif_out( t, "homeurl", addr.url().prettyURL() ); 00153 ldif_out( t, "description", addr.note() ); 00154 if (addr.revision().isValid()) 00155 ldif_out(t, "modifytimestamp", dateToVCardString( addr.revision()) ); 00156 00157 t << "objectclass: top\n"; 00158 t << "objectclass: person\n"; 00159 t << "objectclass: organizationalPerson\n"; 00160 00161 t << "\n"; 00162 00163 return true; 00164 } 00165 00166 00167 /* convert from LDIF stream */ 00168 00169 bool LDIFConverter::LDIFToAddressee( const QString &str, AddresseeList &addrList, QDateTime dt ) 00170 { 00171 if (str.isEmpty()) 00172 return true; 00173 00174 bool endldif = false, end = false; 00175 LDIF ldif; 00176 LDIF::ParseVal ret; 00177 const char *latinstr = str.latin1(); 00178 int latinstrlen = qstrlen( latinstr ); 00179 QByteArray data; 00180 Addressee a; 00181 Address homeAddr, workAddr; 00182 00183 data.setRawData( latinstr, latinstrlen ); 00184 ldif.setLDIF( data ); 00185 if (!dt.isValid()) 00186 dt = QDateTime::currentDateTime(); 00187 a.setRevision(dt); 00188 homeAddr = Address( Address::Home ); 00189 workAddr = Address( Address::Work ); 00190 00191 do { 00192 ret = ldif.nextItem(); 00193 switch ( ret ) { 00194 case LDIF::Item: { 00195 QString fieldname = ldif.attr().lower(); 00196 QString value = QString::fromUtf8( ldif.val(), ldif.val().size() ); 00197 evaluatePair( a, homeAddr, workAddr, fieldname, value ); 00198 break; 00199 } 00200 case LDIF::EndEntry: 00201 // if the new address is not empty, append it 00202 if ( !a.formattedName().isEmpty() || !a.name().isEmpty() || 00203 !a.familyName().isEmpty() ) { 00204 if ( !homeAddr.isEmpty() ) 00205 a.insertAddress( homeAddr ); 00206 if ( !workAddr.isEmpty() ) 00207 a.insertAddress( workAddr ); 00208 addrList.append( a ); 00209 } 00210 a = Addressee(); 00211 a.setRevision(dt); 00212 homeAddr = Address( Address::Home ); 00213 workAddr = Address( Address::Work ); 00214 break; 00215 case LDIF::MoreData: { 00216 if ( endldif ) 00217 end = true; 00218 else { 00219 ldif.endLDIF(); 00220 endldif = true; 00221 break; 00222 } 00223 } 00224 default: 00225 break; 00226 } 00227 } while ( !end ); 00228 00229 data.resetRawData( latinstr, latinstrlen ); 00230 00231 return true; 00232 } 00233 00234 bool LDIFConverter::evaluatePair( Addressee &a, Address &homeAddr, 00235 Address &workAddr, 00236 QString &fieldname, QString &value ) 00237 { 00238 if ( fieldname == QString::fromLatin1( "dn" ) ) // ignore & return false! 00239 return false; 00240 00241 if ( fieldname.startsWith("#") ) { 00242 return true; 00243 } 00244 00245 if ( fieldname.isEmpty() && !a.note().isEmpty() ) { 00246 // some LDIF export filters are borken and add additional 00247 // comments on stand-alone lines. Just add them to the notes for now. 00248 a.setNote( a.note() + "\n" + value ); 00249 return true; 00250 } 00251 00252 if ( fieldname == QString::fromLatin1( "givenname" ) ) { 00253 a.setGivenName( value ); 00254 return true; 00255 } 00256 00257 if ( fieldname == QString::fromLatin1( "xmozillanickname") || 00258 fieldname == QString::fromLatin1( "nickname") ) { 00259 a.setNickName( value ); 00260 return true; 00261 } 00262 00263 if ( fieldname == QString::fromLatin1( "sn" ) ) { 00264 a.setFamilyName( value ); 00265 return true; 00266 } 00267 00268 if ( fieldname == QString::fromLatin1( "uid" ) ) { 00269 a.setUid( value ); 00270 return true; 00271 } 00272 if ( fieldname == QString::fromLatin1( "mail" ) || 00273 fieldname == QString::fromLatin1( "mozillasecondemail" ) ) { // mozilla 00274 if ( a.emails().findIndex( value ) == -1 ) 00275 a.insertEmail( value ); 00276 return true; 00277 } 00278 00279 if ( fieldname == QString::fromLatin1( "title" ) ) { 00280 a.setTitle( value ); 00281 return true; 00282 } 00283 00284 if ( fieldname == QString::fromLatin1( "vocation" ) ) { 00285 a.setPrefix( value ); 00286 return true; 00287 } 00288 00289 if ( fieldname == QString::fromLatin1( "cn" ) ) { 00290 a.setFormattedName( value ); 00291 return true; 00292 } 00293 00294 if ( fieldname == QString::fromLatin1( "o" ) || 00295 fieldname == QString::fromLatin1( "organization" ) || // Exchange 00296 fieldname == QString::fromLatin1( "organizationname" ) ) { // Exchange 00297 a.setOrganization( value ); 00298 return true; 00299 } 00300 00301 if ( fieldname == QString::fromLatin1( "description" ) ) { 00302 addComment: 00303 if ( !a.note().isEmpty() ) 00304 a.setNote( a.note() + "\n" ); 00305 a.setNote( a.note() + value ); 00306 return true; 00307 } 00308 00309 if ( fieldname == QString::fromLatin1( "custom1" ) || 00310 fieldname == QString::fromLatin1( "custom2" ) || 00311 fieldname == QString::fromLatin1( "custom3" ) || 00312 fieldname == QString::fromLatin1( "custom4" ) ) { 00313 goto addComment; 00314 } 00315 00316 if ( fieldname == QString::fromLatin1( "homeurl" ) || 00317 fieldname == QString::fromLatin1( "workurl" ) ) { 00318 if (a.url().isEmpty()) { 00319 a.setUrl( KURL( value ) ); 00320 return true; 00321 } 00322 if ( a.url().prettyURL() == KURL(value).prettyURL() ) 00323 return true; 00324 // TODO: current version of kabc only supports one URL. 00325 // TODO: change this with KDE 4 00326 } 00327 00328 if ( fieldname == QString::fromLatin1( "homephone" ) ) { 00329 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Home ) ); 00330 return true; 00331 } 00332 00333 if ( fieldname == QString::fromLatin1( "telephonenumber" ) ) { 00334 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Work ) ); 00335 return true; 00336 } 00337 00338 if ( fieldname == QString::fromLatin1( "mobile" ) ) { // mozilla/Netscape 7 00339 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Cell ) ); 00340 return true; 00341 } 00342 00343 if ( fieldname == QString::fromLatin1( "cellphone" ) ) { 00344 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Cell ) ); 00345 return true; 00346 } 00347 00348 if ( fieldname == QString::fromLatin1( "pager" ) || // mozilla 00349 fieldname == QString::fromLatin1( "pagerphone" ) ) { // mozilla 00350 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Pager ) ); 00351 return true; 00352 } 00353 00354 if ( fieldname == QString::fromLatin1( "facsimiletelephonenumber" ) ) { 00355 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Fax ) ); 00356 return true; 00357 } 00358 00359 if ( fieldname == QString::fromLatin1( "xmozillaanyphone" ) ) { // mozilla 00360 a.insertPhoneNumber( PhoneNumber( value, PhoneNumber::Work ) ); 00361 return true; 00362 } 00363 00364 if ( fieldname == QString::fromLatin1( "street" ) || 00365 fieldname == QString::fromLatin1( "streethomeaddress" ) ) { 00366 homeAddr.setStreet( value ); 00367 return true; 00368 } 00369 00370 if ( fieldname == QString::fromLatin1( "postaladdress" ) ) { // mozilla 00371 workAddr.setStreet( value ); 00372 return true; 00373 } 00374 00375 if ( fieldname == QString::fromLatin1( "mozillapostaladdress2" ) ) { // mozilla 00376 workAddr.setStreet( workAddr.street() + QString::fromLatin1( "\n" ) + value ); 00377 return true; 00378 } 00379 00380 if ( fieldname == QString::fromLatin1( "postalcode" ) ) { 00381 workAddr.setPostalCode( value ); 00382 return true; 00383 } 00384 00385 if ( fieldname == QString::fromLatin1( "postofficebox" ) ) { 00386 workAddr.setPostOfficeBox( value ); 00387 return true; 00388 } 00389 00390 if ( fieldname == QString::fromLatin1( "homepostaladdress" ) ) { // Netscape 7 00391 homeAddr.setStreet( value ); 00392 return true; 00393 } 00394 00395 if ( fieldname == QString::fromLatin1( "mozillahomepostaladdress2" ) ) { // mozilla 00396 homeAddr.setStreet( homeAddr.street() + QString::fromLatin1( "\n" ) + value ); 00397 return true; 00398 } 00399 00400 if ( fieldname == QString::fromLatin1( "mozillahomelocalityname" ) ) { // mozilla 00401 homeAddr.setLocality( value ); 00402 return true; 00403 } 00404 00405 if ( fieldname == QString::fromLatin1( "mozillahomestate" ) ) { // mozilla 00406 homeAddr.setRegion( value ); 00407 return true; 00408 } 00409 00410 if ( fieldname == QString::fromLatin1( "mozillahomepostalcode" ) ) { // mozilla 00411 homeAddr.setPostalCode( value ); 00412 return true; 00413 } 00414 00415 if ( fieldname == QString::fromLatin1( "mozillahomecountryname" ) ) { // mozilla 00416 if ( value.length() <= 2 ) 00417 value = Address::ISOtoCountry(value); 00418 homeAddr.setCountry( value ); 00419 return true; 00420 } 00421 00422 if ( fieldname == QString::fromLatin1( "locality" ) ) { 00423 workAddr.setLocality( value ); 00424 return true; 00425 } 00426 00427 if ( fieldname == QString::fromLatin1( "streetaddress" ) ) { // Netscape 4.x 00428 workAddr.setStreet( value ); 00429 return true; 00430 } 00431 00432 if ( fieldname == QString::fromLatin1( "countryname" ) || 00433 fieldname == QString::fromLatin1( "c" ) ) { // mozilla 00434 if ( value.length() <= 2 ) 00435 value = Address::ISOtoCountry(value); 00436 workAddr.setCountry( value ); 00437 return true; 00438 } 00439 00440 if ( fieldname == QString::fromLatin1( "l" ) ) { // mozilla 00441 workAddr.setLocality( value ); 00442 return true; 00443 } 00444 00445 if ( fieldname == QString::fromLatin1( "st" ) ) { 00446 workAddr.setRegion( value ); 00447 return true; 00448 } 00449 00450 if ( fieldname == QString::fromLatin1( "ou" ) ) { 00451 a.setRole( value ); 00452 return true; 00453 } 00454 00455 if ( fieldname == QString::fromLatin1( "department" ) ) { 00456 a.setDepartment( value ); 00457 return true; 00458 } 00459 00460 if ( fieldname == QString::fromLatin1( "member" ) ) { 00461 // this is a mozilla list member (cn=xxx, mail=yyy) 00462 QStringList list( QStringList::split( ',', value ) ); 00463 QString name, email; 00464 00465 QStringList::Iterator it; 00466 for ( it = list.begin(); it != list.end(); ++it ) { 00467 if ( (*it).startsWith( "cn=" ) ) 00468 name = (*it).mid( 3 ).stripWhiteSpace(); 00469 if ( (*it).startsWith( "mail=" ) ) 00470 email = (*it).mid( 5 ).stripWhiteSpace(); 00471 } 00472 if ( !name.isEmpty() && !email.isEmpty() ) 00473 email = " <" + email + ">"; 00474 a.insertEmail( name + email ); 00475 a.insertCategory( i18n( "List of Emails" ) ); 00476 return true; 00477 } 00478 00479 if ( fieldname == QString::fromLatin1( "modifytimestamp" ) ) { 00480 if (value == QString::fromLatin1("0Z")) // ignore 00481 return true; 00482 QDateTime dt = VCardStringToDate( value ); 00483 if ( dt.isValid() ) { 00484 a.setRevision(dt); 00485 return true; 00486 } 00487 } 00488 00489 if ( fieldname == QString::fromLatin1( "objectclass" ) ) // ignore 00490 return true; 00491 00492 kdWarning() << QString("LDIFConverter: Unknown field for '%1': '%2=%3'\n") 00493 .arg(a.formattedName()).arg(fieldname).arg(value); 00494 00495 return true; 00496 } 00497 00498 /* The following functions are obsoleted. Similar functionality can be found 00499 * in the LDIF class */ 00500 00501 bool LDIFConverter::parseSingleLine( Addressee &a, Address &homeAddr, 00502 Address &workAddr, QString &line ) 00503 { 00504 if ( line.isEmpty() ) 00505 return true; 00506 00507 QString fieldname, value; 00508 QByteArray val; 00509 00510 LDIF::splitLine( line.latin1(), fieldname, val ); 00511 value = QString::fromUtf8( val.data(), val.size() ); 00512 return evaluatePair( a, homeAddr, workAddr, fieldname, value); 00513 } 00514 00515 00516 bool LDIFConverter::splitLine( QString &line, QString &fieldname, QString &value) 00517 { 00518 QByteArray val; 00519 bool ret = LDIF::splitLine( line.latin1(), fieldname, val ); 00520 value = QString::fromUtf8( val.data(), val.size() ); 00521 return ret; 00522 } 00523 00524 00525 QString LDIFConverter::makeLDIFfieldString( QString formatStr, QString value, bool allowEncode ) 00526 { 00527 if ( value.isEmpty() ) 00528 return QString(); 00529 00530 // append format if not given 00531 if (formatStr.find(':') == -1) 00532 formatStr.append(": %1\n"); 00533 00534 // check if base64-encoding is needed 00535 bool printable = true; 00536 unsigned int i, len; 00537 len = value.length(); 00538 for (i = 0; i<len; ++i ) { 00539 if (!value[i].isPrint()) { 00540 printable = false; 00541 break; 00542 } 00543 } 00544 00545 if (printable) // always encode if we find special chars... 00546 printable = (value.find('\n') == -1); 00547 00548 if (!printable && allowEncode) { 00549 // encode to base64 00550 value = KCodecs::base64Encode( value.utf8() ); 00551 int p = formatStr.find(':'); 00552 if (p>=0) 00553 formatStr.insert(p, ':'); 00554 } 00555 00556 // generate the new string and split it to 72 chars/line 00557 QCString txt = (formatStr.arg(value)).utf8(); 00558 00559 if (allowEncode) { 00560 len = txt.length(); 00561 if (len && txt[len-1] == '\n') 00562 --len; 00563 i = 72; 00564 while (i < len) { 00565 txt.insert(i, "\n "); 00566 i += 72+1; 00567 len += 2; 00568 } 00569 } 00570 00571 return QString::fromUtf8(txt); 00572 } 00573