File indexing completed on 2024-09-15 03:37:13
0001 /* 0002 This file is part of the KContacts framework. 0003 SPDX-FileCopyrightText: 2003 Helge Deller <deller@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 /* 0009 Useful links: 0010 - http://tldp.org/HOWTO/LDAP-Implementation-HOWTO/schemas.html 0011 - http://www.faqs.org/rfcs/rfc2849.html 0012 0013 Not yet handled items: 0014 - objectclass microsoftaddressbook 0015 - info, 0016 - initials, 0017 - otherfacsimiletelephonenumber, 0018 - otherpager, 0019 - physicaldeliveryofficename, 0020 */ 0021 0022 #include "ldifconverter.h" 0023 #include "address.h" 0024 #include "kcontacts_debug.h" 0025 #include "vcardconverter.h" 0026 0027 #include "ldif_p.h" 0028 0029 #include <KCountry> 0030 #include <KLocalizedString> 0031 0032 #include <QIODevice> 0033 #include <QStringList> 0034 #include <QTextStream> 0035 0036 using namespace KContacts; 0037 0038 /* internal functions - do not use !! */ 0039 0040 namespace KContacts 0041 { 0042 /** 0043 @internal 0044 0045 Evaluates @p fieldname and sets the @p value at the addressee or the address 0046 objects when appropriate. 0047 0048 @param a The addressee to store information into 0049 @param homeAddr The home address to store respective information into 0050 @param workAddr The work address to store respective information into 0051 @param fieldname LDIF field name to evaluate 0052 @param value The value of the field addressed by @p fieldname 0053 */ 0054 void evaluatePair(Addressee &a, 0055 Address &homeAddr, 0056 Address &workAddr, 0057 QString &fieldname, 0058 QString &value, 0059 int &birthday, 0060 int &birthmonth, 0061 int &birthyear, 0062 ContactGroup &contactGroup); 0063 } 0064 0065 /* generate LDIF stream */ 0066 0067 static void ldif_out(QTextStream &t, const QString &formatStr, const QString &value) 0068 { 0069 if (value.isEmpty()) { 0070 return; 0071 } 0072 0073 const QByteArray txt = Ldif::assembleLine(formatStr, value, 72); 0074 0075 // write the string 0076 t << QString::fromUtf8(txt) << "\n"; 0077 } 0078 0079 bool LDIFConverter::addresseeAndContactGroupToLDIF(const AddresseeList &addrList, const ContactGroup::List &contactGroupList, QString &str) 0080 { 0081 bool result = addresseeToLDIF(addrList, str); 0082 if (!contactGroupList.isEmpty()) { 0083 result = (contactGroupToLDIF(contactGroupList, str) || result); // order matters 0084 } 0085 return result; 0086 } 0087 0088 bool LDIFConverter::contactGroupToLDIF(const ContactGroup &contactGroup, QString &str) 0089 { 0090 if (contactGroup.dataCount() <= 0) { 0091 return false; 0092 } 0093 QTextStream t(&str, QIODevice::WriteOnly | QIODevice::Append); 0094 0095 t << "objectclass: top\n"; 0096 t << "objectclass: groupOfNames\n"; 0097 0098 for (int i = 0; i < contactGroup.dataCount(); ++i) { 0099 const ContactGroup::Data &data = contactGroup.data(i); 0100 const QString value = QStringLiteral("cn=%1,mail=%2").arg(data.name(), data.email()); 0101 ldif_out(t, QStringLiteral("member"), value); 0102 } 0103 0104 t << "\n"; 0105 return true; 0106 } 0107 0108 bool LDIFConverter::contactGroupToLDIF(const ContactGroup::List &contactGroupList, QString &str) 0109 { 0110 if (contactGroupList.isEmpty()) { 0111 return false; 0112 } 0113 0114 bool result = true; 0115 for (const ContactGroup &group : contactGroupList) { 0116 result = (contactGroupToLDIF(group, str) || result); // order matters 0117 } 0118 return result; 0119 } 0120 0121 bool LDIFConverter::addresseeToLDIF(const AddresseeList &addrList, QString &str) 0122 { 0123 if (addrList.isEmpty()) { 0124 return false; 0125 } 0126 0127 bool result = true; 0128 for (const Addressee &addr : addrList) { 0129 result = (addresseeToLDIF(addr, str) || result); // order matters 0130 } 0131 return result; 0132 } 0133 0134 static QString countryName(const QString &isoCodeOrName) 0135 { 0136 const auto c = KCountry::fromAlpha2(isoCodeOrName); 0137 return c.isValid() ? c.name() : isoCodeOrName; 0138 } 0139 0140 bool LDIFConverter::addresseeToLDIF(const Addressee &addr, QString &str) 0141 { 0142 if (addr.isEmpty()) { 0143 return false; 0144 } 0145 0146 QTextStream t(&str, QIODevice::WriteOnly | QIODevice::Append); 0147 0148 const Address homeAddr = addr.address(Address::Home); 0149 const Address workAddr = addr.address(Address::Work); 0150 0151 ldif_out(t, QStringLiteral("dn"), QStringLiteral("cn=%1,mail=%2").arg(addr.formattedName().simplified(), addr.preferredEmail())); 0152 t << "objectclass: top\n"; 0153 t << "objectclass: person\n"; 0154 t << "objectclass: organizationalPerson\n"; 0155 0156 ldif_out(t, QStringLiteral("givenname"), addr.givenName()); 0157 ldif_out(t, QStringLiteral("sn"), addr.familyName()); 0158 ldif_out(t, QStringLiteral("cn"), addr.formattedName().simplified()); 0159 ldif_out(t, QStringLiteral("uid"), addr.uid()); 0160 ldif_out(t, QStringLiteral("nickname"), addr.nickName()); 0161 ldif_out(t, QStringLiteral("xmozillanickname"), addr.nickName()); 0162 ldif_out(t, QStringLiteral("mozillanickname"), addr.nickName()); 0163 0164 ldif_out(t, QStringLiteral("mail"), addr.preferredEmail()); 0165 const QStringList emails = addr.emails(); 0166 const int numEmails = emails.count(); 0167 for (int i = 1; i < numEmails; ++i) { 0168 if (i == 0) { 0169 // nothing 0170 } else if (i == 1) { 0171 ldif_out(t, QStringLiteral("mozillasecondemail"), emails[1]); 0172 } else { 0173 ldif_out(t, QStringLiteral("othermailbox"), emails[i]); 0174 } 0175 } 0176 // ldif_out( t, "mozilla_AIMScreenName: %1\n", "screen_name" ); 0177 0178 ldif_out(t, QStringLiteral("telephonenumber"), addr.phoneNumber(PhoneNumber::Work).number()); 0179 ldif_out(t, QStringLiteral("facsimiletelephonenumber"), addr.phoneNumber(PhoneNumber::Fax).number()); 0180 ldif_out(t, QStringLiteral("homephone"), addr.phoneNumber(PhoneNumber::Home).number()); 0181 ldif_out(t, QStringLiteral("mobile"), 0182 addr.phoneNumber(PhoneNumber::Cell).number()); // Netscape 7 0183 ldif_out(t, QStringLiteral("cellphone"), 0184 addr.phoneNumber(PhoneNumber::Cell).number()); // Netscape 4.x 0185 ldif_out(t, QStringLiteral("pager"), addr.phoneNumber(PhoneNumber::Pager).number()); 0186 ldif_out(t, QStringLiteral("pagerphone"), addr.phoneNumber(PhoneNumber::Pager).number()); 0187 0188 ldif_out(t, QStringLiteral("streethomeaddress"), homeAddr.street()); 0189 ldif_out(t, QStringLiteral("postalcode"), workAddr.postalCode()); 0190 ldif_out(t, QStringLiteral("postofficebox"), workAddr.postOfficeBox()); 0191 0192 QStringList streets = homeAddr.street().split(QLatin1Char('\n')); 0193 const int numberOfStreets(streets.count()); 0194 if (numberOfStreets > 0) { 0195 ldif_out(t, QStringLiteral("homepostaladdress"), streets.at(0)); // Netscape 7 0196 } 0197 if (numberOfStreets > 1) { 0198 ldif_out(t, QStringLiteral("mozillahomepostaladdress2"), streets.at(1)); // Netscape 7 0199 } 0200 ldif_out(t, QStringLiteral("mozillahomelocalityname"), homeAddr.locality()); // Netscape 7 0201 ldif_out(t, QStringLiteral("mozillahomestate"), homeAddr.region()); 0202 ldif_out(t, QStringLiteral("mozillahomepostalcode"), homeAddr.postalCode()); 0203 ldif_out(t, QStringLiteral("mozillahomecountryname"), countryName(homeAddr.country())); 0204 ldif_out(t, QStringLiteral("locality"), workAddr.locality()); 0205 ldif_out(t, QStringLiteral("streetaddress"), workAddr.street()); // Netscape 4.x 0206 0207 streets = workAddr.street().split(QLatin1Char('\n')); 0208 const int streetsCount = streets.count(); 0209 if (streetsCount > 0) { 0210 ldif_out(t, QStringLiteral("street"), streets.at(0)); 0211 } 0212 if (streetsCount > 1) { 0213 ldif_out(t, QStringLiteral("mozillaworkstreet2"), streets.at(1)); 0214 } 0215 ldif_out(t, QStringLiteral("countryname"), countryName(workAddr.country())); 0216 ldif_out(t, QStringLiteral("l"), workAddr.locality()); 0217 ldif_out(t, QStringLiteral("c"), countryName(workAddr.country())); 0218 ldif_out(t, QStringLiteral("st"), workAddr.region()); 0219 0220 ldif_out(t, QStringLiteral("title"), addr.title()); 0221 ldif_out(t, QStringLiteral("vocation"), addr.prefix()); 0222 ldif_out(t, QStringLiteral("ou"), addr.role()); 0223 ldif_out(t, QStringLiteral("o"), addr.organization()); 0224 ldif_out(t, QStringLiteral("organization"), addr.organization()); 0225 ldif_out(t, QStringLiteral("organizationname"), addr.organization()); 0226 0227 // Compatibility with older kabc versions. 0228 if (!addr.department().isEmpty()) { 0229 ldif_out(t, QStringLiteral("department"), addr.department()); 0230 } else { 0231 ldif_out(t, QStringLiteral("department"), addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Department"))); 0232 } 0233 0234 ldif_out(t, QStringLiteral("workurl"), addr.url().url().toDisplayString()); 0235 ldif_out(t, QStringLiteral("homeurl"), addr.url().url().toDisplayString()); 0236 ldif_out(t, QStringLiteral("mozillahomeurl"), addr.url().url().toDisplayString()); 0237 0238 ldif_out(t, QStringLiteral("description"), addr.note()); 0239 if (addr.revision().isValid()) { 0240 ldif_out(t, QStringLiteral("modifytimestamp"), dateToVCardString(addr.revision())); 0241 } 0242 0243 const QDate birthday = addr.birthday().date(); 0244 if (birthday.isValid()) { 0245 const int year = birthday.year(); 0246 if (year > 0) { 0247 ldif_out(t, QStringLiteral("birthyear"), QString::number(year)); 0248 } 0249 ldif_out(t, QStringLiteral("birthmonth"), QString::number(birthday.month())); 0250 ldif_out(t, QStringLiteral("birthday"), QString::number(birthday.day())); 0251 } 0252 0253 t << "\n"; 0254 0255 return true; 0256 } 0257 0258 /* convert from LDIF stream */ 0259 bool LDIFConverter::LDIFToAddressee(const QString &str, AddresseeList &addrList, ContactGroup::List &contactGroupList, const QDateTime &dt) 0260 { 0261 if (str.isEmpty()) { 0262 return true; 0263 } 0264 0265 bool endldif = false; 0266 bool end = false; 0267 Ldif ldif; 0268 Ldif::ParseValue ret; 0269 Addressee a; 0270 Address homeAddr; 0271 Address workAddr; 0272 int birthday = -1; 0273 int birthmonth = -1; 0274 int birthyear = -1; 0275 ContactGroup contactGroup; 0276 ldif.setLdif(str.toLatin1()); 0277 QDateTime qdt = dt; 0278 if (!qdt.isValid()) { 0279 qdt = QDateTime::currentDateTime(); 0280 } 0281 a.setRevision(qdt); 0282 homeAddr = Address(Address::Home); 0283 workAddr = Address(Address::Work); 0284 0285 do { 0286 ret = ldif.nextItem(); 0287 switch (ret) { 0288 case Ldif::Item: { 0289 QString fieldname = ldif.attr().toLower(); 0290 QString value = QString::fromUtf8(ldif.value()); 0291 evaluatePair(a, homeAddr, workAddr, fieldname, value, birthday, birthmonth, birthyear, contactGroup); 0292 break; 0293 } 0294 case Ldif::EndEntry: 0295 if (contactGroup.count() == 0) { 0296 // if the new address is not empty, append it 0297 QDate birthDate(birthyear, birthmonth, birthday); 0298 if (birthDate.isValid()) { 0299 a.setBirthday(birthDate); 0300 } 0301 0302 if (!a.formattedName().isEmpty() || !a.name().isEmpty() || !a.familyName().isEmpty()) { 0303 if (!homeAddr.isEmpty()) { 0304 a.insertAddress(homeAddr); 0305 } 0306 if (!workAddr.isEmpty()) { 0307 a.insertAddress(workAddr); 0308 } 0309 addrList.append(a); 0310 } 0311 } else { 0312 contactGroupList.append(contactGroup); 0313 } 0314 a = Addressee(); 0315 contactGroup = ContactGroup(); 0316 a.setRevision(qdt); 0317 homeAddr = Address(Address::Home); 0318 workAddr = Address(Address::Work); 0319 break; 0320 case Ldif::MoreData: 0321 if (endldif) { 0322 end = true; 0323 } else { 0324 ldif.endLdif(); 0325 endldif = true; 0326 break; 0327 } 0328 default: 0329 break; 0330 } 0331 } while (!end); 0332 0333 return true; 0334 } 0335 0336 void KContacts::evaluatePair(Addressee &a, 0337 Address &homeAddr, 0338 Address &workAddr, 0339 QString &fieldname, 0340 QString &value, 0341 int &birthday, 0342 int &birthmonth, 0343 int &birthyear, 0344 ContactGroup &contactGroup) 0345 { 0346 if (fieldname == QLatin1String("dn")) { // ignore 0347 return; 0348 } 0349 0350 if (fieldname.startsWith(QLatin1Char('#'))) { 0351 return; 0352 } 0353 0354 if (fieldname.isEmpty() && !a.note().isEmpty()) { 0355 // some LDIF export filters are broken and add additional 0356 // comments on stand-alone lines. Just add them to the notes for now. 0357 a.setNote(a.note() + QLatin1Char('\n') + value); 0358 return; 0359 } 0360 0361 if (fieldname == QLatin1String("givenname")) { 0362 a.setGivenName(value); 0363 return; 0364 } 0365 0366 if (fieldname == QLatin1String("xmozillanickname") // 0367 || fieldname == QLatin1String("nickname") // 0368 || fieldname == QLatin1String("mozillanickname")) { 0369 a.setNickName(value); 0370 return; 0371 } 0372 0373 if (fieldname == QLatin1String("sn")) { 0374 a.setFamilyName(value); 0375 return; 0376 } 0377 0378 if (fieldname == QLatin1String("uid")) { 0379 a.setUid(value); 0380 return; 0381 } 0382 if (fieldname == QLatin1String("mail") // 0383 || fieldname == QLatin1String("mozillasecondemail") /* mozilla */ 0384 || fieldname == QLatin1String("othermailbox") /*TheBat!*/) { 0385 if (a.emails().indexOf(value) == -1) { 0386 a.addEmail(value); 0387 } 0388 return; 0389 } 0390 0391 if (fieldname == QLatin1String("title")) { 0392 a.setTitle(value); 0393 return; 0394 } 0395 0396 if (fieldname == QLatin1String("vocation")) { 0397 a.setPrefix(value); 0398 return; 0399 } 0400 0401 if (fieldname == QLatin1String("cn")) { 0402 a.setFormattedName(value); 0403 return; 0404 } 0405 0406 if (fieldname == QLatin1Char('o') || fieldname == QLatin1String("organization") // Exchange 0407 || fieldname == QLatin1String("organizationname")) { // Exchange 0408 a.setOrganization(value); 0409 return; 0410 } 0411 0412 // clang-format off 0413 if (fieldname == QLatin1String("description") 0414 || fieldname == QLatin1String("mozillacustom1") 0415 || fieldname == QLatin1String("mozillacustom2") 0416 || fieldname == QLatin1String("mozillacustom3") 0417 || fieldname == QLatin1String("mozillacustom4") 0418 || fieldname == QLatin1String("custom1") 0419 || fieldname == QLatin1String("custom2") 0420 || fieldname == QLatin1String("custom3") 0421 || fieldname == QLatin1String("custom4")) { 0422 if (!a.note().isEmpty()) { 0423 a.setNote(a.note() + QLatin1Char('\n')); 0424 } 0425 a.setNote(a.note() + value); 0426 return; 0427 } 0428 // clang-format on 0429 0430 if (fieldname == QLatin1String("homeurl") // 0431 || fieldname == QLatin1String("workurl") // 0432 || fieldname == QLatin1String("mozillahomeurl")) { 0433 if (a.url().url().isEmpty()) { 0434 ResourceLocatorUrl url; 0435 url.setUrl(QUrl(value)); 0436 a.setUrl(url); 0437 return; 0438 } 0439 if (a.url().url().toDisplayString() == QUrl(value).toDisplayString()) { 0440 return; 0441 } 0442 // TODO: current version of kabc only supports one URL. 0443 // TODO: change this with KDE 4 0444 } 0445 0446 if (fieldname == QLatin1String("homephone")) { 0447 a.insertPhoneNumber(PhoneNumber(value, PhoneNumber::Home)); 0448 return; 0449 } 0450 0451 if (fieldname == QLatin1String("telephonenumber")) { 0452 a.insertPhoneNumber(PhoneNumber(value, PhoneNumber::Work)); 0453 return; 0454 } 0455 if (fieldname == QLatin1String("mobile") /* mozilla/Netscape 7 */ 0456 || fieldname == QLatin1String("cellphone")) { 0457 a.insertPhoneNumber(PhoneNumber(value, PhoneNumber::Cell)); 0458 return; 0459 } 0460 0461 if (fieldname == QLatin1String("pager") // mozilla 0462 || fieldname == QLatin1String("pagerphone")) { // mozilla 0463 a.insertPhoneNumber(PhoneNumber(value, PhoneNumber::Pager)); 0464 return; 0465 } 0466 0467 if (fieldname == QLatin1String("facsimiletelephonenumber")) { 0468 a.insertPhoneNumber(PhoneNumber(value, PhoneNumber::Fax)); 0469 return; 0470 } 0471 0472 if (fieldname == QLatin1String("xmozillaanyphone")) { // mozilla 0473 a.insertPhoneNumber(PhoneNumber(value, PhoneNumber::Work)); 0474 return; 0475 } 0476 0477 if (fieldname == QLatin1String("streethomeaddress") // 0478 || fieldname == QLatin1String("mozillahomestreet")) { // thunderbird 0479 homeAddr.setStreet(value); 0480 return; 0481 } 0482 0483 if (fieldname == QLatin1String("street") // 0484 || fieldname == QLatin1String("postaladdress")) { // mozilla 0485 workAddr.setStreet(value); 0486 return; 0487 } 0488 if (fieldname == QLatin1String("mozillapostaladdress2") // 0489 || fieldname == QLatin1String("mozillaworkstreet2")) { // mozilla 0490 workAddr.setStreet(workAddr.street() + QLatin1Char('\n') + value); 0491 return; 0492 } 0493 0494 if (fieldname == QLatin1String("postalcode")) { 0495 workAddr.setPostalCode(value); 0496 return; 0497 } 0498 0499 if (fieldname == QLatin1String("postofficebox")) { 0500 workAddr.setPostOfficeBox(value); 0501 return; 0502 } 0503 0504 if (fieldname == QLatin1String("homepostaladdress")) { // Netscape 7 0505 homeAddr.setStreet(value); 0506 return; 0507 } 0508 0509 if (fieldname == QLatin1String("mozillahomepostaladdress2")) { // mozilla 0510 homeAddr.setStreet(homeAddr.street() + QLatin1Char('\n') + value); 0511 return; 0512 } 0513 0514 if (fieldname == QLatin1String("mozillahomelocalityname")) { // mozilla 0515 homeAddr.setLocality(value); 0516 return; 0517 } 0518 0519 if (fieldname == QLatin1String("mozillahomestate")) { // mozilla 0520 homeAddr.setRegion(value); 0521 return; 0522 } 0523 0524 if (fieldname == QLatin1String("mozillahomepostalcode")) { // mozilla 0525 homeAddr.setPostalCode(value); 0526 return; 0527 } 0528 0529 if (fieldname == QLatin1String("mozillahomecountryname")) { // mozilla 0530 if (value.length() <= 2) { 0531 value = countryName(value); 0532 } 0533 homeAddr.setCountry(value); 0534 return; 0535 } 0536 0537 if (fieldname == QLatin1String("locality")) { 0538 workAddr.setLocality(value); 0539 return; 0540 } 0541 0542 if (fieldname == QLatin1String("streetaddress")) { // Netscape 4.x 0543 workAddr.setStreet(value); 0544 return; 0545 } 0546 0547 if (fieldname == QLatin1String("countryname") // 0548 || fieldname == QLatin1Char('c')) { // mozilla 0549 if (value.length() <= 2) { 0550 value = countryName(value); 0551 } 0552 workAddr.setCountry(value); 0553 return; 0554 } 0555 0556 if (fieldname == QLatin1Char('l')) { // mozilla 0557 workAddr.setLocality(value); 0558 return; 0559 } 0560 0561 if (fieldname == QLatin1String("st")) { 0562 workAddr.setRegion(value); 0563 return; 0564 } 0565 0566 if (fieldname == QLatin1String("ou")) { 0567 a.setRole(value); 0568 return; 0569 } 0570 0571 if (fieldname == QLatin1String("department")) { 0572 a.setDepartment(value); 0573 return; 0574 } 0575 0576 if (fieldname == QLatin1String("member")) { 0577 // this is a mozilla list member (cn=xxx, mail=yyy) 0578 const QStringList list = value.split(QLatin1Char(',')); 0579 QString name; 0580 QString email; 0581 0582 const QLatin1String cnTag("cn="); 0583 const QLatin1String mailTag("mail="); 0584 for (const auto &str : list) { 0585 if (str.startsWith(cnTag)) { 0586 name = QStringView(str).mid(cnTag.size()).trimmed().toString(); 0587 } else if (str.startsWith(mailTag)) { 0588 email = QStringView(str).mid(mailTag.size()).trimmed().toString(); 0589 } 0590 } 0591 0592 if (!name.isEmpty() && !email.isEmpty()) { 0593 email = QLatin1String(" <") + email + QLatin1Char('>'); 0594 } 0595 ContactGroup::Data data; 0596 data.setEmail(email); 0597 data.setName(name); 0598 contactGroup.append(data); 0599 return; 0600 } 0601 0602 if (fieldname == QLatin1String("modifytimestamp")) { 0603 if (value == QLatin1String("0Z")) { // ignore 0604 return; 0605 } 0606 QDateTime dt = VCardStringToDate(value); 0607 if (dt.isValid()) { 0608 a.setRevision(dt); 0609 return; 0610 } 0611 } 0612 0613 if (fieldname == QLatin1String("display-name")) { 0614 contactGroup.setName(value); 0615 return; 0616 } 0617 0618 if (fieldname == QLatin1String("objectclass")) { // ignore 0619 return; 0620 } 0621 0622 if (fieldname == QLatin1String("birthyear")) { 0623 bool ok; 0624 birthyear = value.toInt(&ok); 0625 if (!ok) { 0626 birthyear = -1; 0627 } 0628 return; 0629 } 0630 if (fieldname == QLatin1String("birthmonth")) { 0631 birthmonth = value.toInt(); 0632 return; 0633 } 0634 if (fieldname == QLatin1String("birthday")) { 0635 birthday = value.toInt(); 0636 return; 0637 } 0638 if (fieldname == QLatin1String("xbatbirthday")) { 0639 const QStringView str{value}; 0640 QDate dt(str.mid(0, 4).toInt(), str.mid(4, 2).toInt(), str.mid(6, 2).toInt()); 0641 if (dt.isValid()) { 0642 a.setBirthday(dt); 0643 } 0644 return; 0645 } 0646 qCWarning(KCONTACTS_LOG) << QStringLiteral("LDIFConverter: Unknown field for '%1': '%2=%3'\n").arg(a.formattedName(), fieldname, value); 0647 }