File indexing completed on 2025-01-19 03:39:54
0001 /* 0002 This file is part of the KContacts framework. 0003 SPDX-FileCopyrightText: 2003 Tobias Koenig <tokoe@kde.org> 0004 SPDX-FileCopyrightText: 2015-2019 Laurent Montel <montel@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "fieldgroup.h" 0010 #include "gender.h" 0011 #include "kcontacts_debug.h" 0012 #include "key.h" 0013 #include "lang.h" 0014 #include "picture.h" 0015 #include "related.h" 0016 #include "secrecy.h" 0017 #include "sound.h" 0018 #include "vcardtool_p.h" 0019 0020 #include <QString> 0021 #include <QTimeZone> 0022 0023 using namespace KContacts; 0024 0025 static bool needsEncoding(const QString &value) 0026 { 0027 int length = value.length(); 0028 for (int i = 0; i < length; ++i) { 0029 char c = value.at(i).toLatin1(); 0030 if ((c < 33 || c > 126) && c != ' ' && c != '=') { 0031 return true; 0032 } 0033 } 0034 0035 return false; 0036 } 0037 0038 struct AddressTypeInfo { 0039 const char *addressType; 0040 Address::TypeFlag flag; 0041 }; 0042 0043 static const AddressTypeInfo s_addressTypes[] = { 0044 {"dom", Address::Dom}, 0045 {"home", Address::Home}, 0046 {"intl", Address::Intl}, 0047 {"parcel", Address::Parcel}, 0048 {"postal", Address::Postal}, 0049 {"pref", Address::Pref}, 0050 {"work", Address::Work}, 0051 }; 0052 0053 static Address::TypeFlag stringToAddressType(const QString &str) 0054 { 0055 auto it = std::find_if(std::begin(s_addressTypes), std::end(s_addressTypes), [&str](const AddressTypeInfo &info) { 0056 return str == QLatin1String(info.addressType); 0057 }); 0058 return it != std::end(s_addressTypes) ? it->flag : Address::TypeFlag{}; 0059 } 0060 0061 struct PhoneTypeInfo { 0062 const char *phoneType; 0063 PhoneNumber::TypeFlag flag; 0064 }; 0065 0066 static const PhoneTypeInfo s_phoneTypes[] = { 0067 {"BBS", PhoneNumber::Bbs}, 0068 {"CAR", PhoneNumber::Car}, 0069 {"CELL", PhoneNumber::Cell}, 0070 {"FAX", PhoneNumber::Fax}, 0071 {"HOME", PhoneNumber::Home}, 0072 {"ISDN", PhoneNumber::Isdn}, 0073 {"MODEM", PhoneNumber::Modem}, 0074 {"MSG", PhoneNumber::Msg}, 0075 {"PAGER", PhoneNumber::Pager}, 0076 {"PCS", PhoneNumber::Pcs}, 0077 {"PREF", PhoneNumber::Pref}, 0078 {"VIDEO", PhoneNumber::Video}, 0079 {"VOICE", PhoneNumber::Voice}, 0080 {"WORK", PhoneNumber::Work}, 0081 }; 0082 0083 static PhoneNumber::TypeFlag stringToPhoneType(const QString &str) 0084 { 0085 auto it = std::find_if(std::begin(s_phoneTypes), std::end(s_phoneTypes), [&str](const PhoneTypeInfo &info) { 0086 return str == QLatin1String(info.phoneType); 0087 }); 0088 return it != std::end(s_phoneTypes) ? it->flag : PhoneNumber::TypeFlag{}; 0089 } 0090 0091 VCardTool::VCardTool() 0092 { 0093 } 0094 0095 VCardTool::~VCardTool() 0096 { 0097 } 0098 0099 QByteArray VCardTool::exportVCards(const Addressee::List &list, VCard::Version version) const 0100 { 0101 return createVCards(list, version, true /*export vcard*/); 0102 } 0103 0104 QByteArray VCardTool::createVCards(const Addressee::List &list, VCard::Version version) const 0105 { 0106 return createVCards(list, version, false /*don't export*/); 0107 } 0108 0109 void VCardTool::addParameter(VCardLine *line, VCard::Version version, const QString &key, const QStringList &valueStringList) const 0110 { 0111 if (version == VCard::v2_1) { 0112 for (const QString &valueStr : valueStringList) { 0113 line->addParameter(valueStr, QString()); 0114 } 0115 } else if (version == VCard::v3_0) { 0116 line->addParameter(key, valueStringList.join(QLatin1Char(','))); 0117 } else { 0118 if (valueStringList.count() < 2) { 0119 line->addParameter(key, valueStringList.join(QLatin1Char(','))); 0120 } else { 0121 line->addParameter(key, QLatin1Char('"') + valueStringList.join(QLatin1Char(',')) + QLatin1Char('"')); 0122 } 0123 } 0124 } 0125 0126 void VCardTool::processAddresses(const Address::List &addresses, VCard::Version version, VCard *card) const 0127 { 0128 for (const auto &addr : addresses) { 0129 QStringList address; 0130 0131 // clang-format off 0132 const bool isEmpty = addr.postOfficeBox().isEmpty() 0133 && addr.extended().isEmpty() 0134 && addr.street().isEmpty() 0135 && addr.locality().isEmpty() 0136 && addr.region().isEmpty() 0137 && addr.postalCode().isEmpty() 0138 && addr.country().isEmpty(); 0139 // clang-format on 0140 0141 address.append(addr.postOfficeBox().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0142 address.append(addr.extended().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0143 address.append(addr.street().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0144 address.append(addr.locality().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0145 address.append(addr.region().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0146 address.append(addr.postalCode().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0147 address.append(addr.country().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0148 0149 const QString addressJoined(address.join(QLatin1Char(';'))); 0150 VCardLine adrLine(QStringLiteral("ADR"), addressJoined); 0151 if (version == VCard::v2_1 && needsEncoding(addressJoined)) { 0152 adrLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0153 adrLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0154 } 0155 0156 const bool hasLabel = !addr.label().isEmpty(); 0157 QStringList addreLineType; 0158 QStringList labelLineType; 0159 0160 for (const auto &info : s_addressTypes) { 0161 if (info.flag & addr.type()) { 0162 const QString str = QString::fromLatin1(info.addressType); 0163 addreLineType << str; 0164 if (hasLabel) { 0165 labelLineType << str; 0166 } 0167 } 0168 } 0169 0170 if (hasLabel) { 0171 if (version == VCard::v4_0) { 0172 if (!addr.label().isEmpty()) { 0173 adrLine.addParameter(QStringLiteral("LABEL"), QStringLiteral("\"%1\"").arg(addr.label())); 0174 } 0175 } else { 0176 VCardLine labelLine(QStringLiteral("LABEL"), addr.label()); 0177 if (version == VCard::v2_1 && needsEncoding(addr.label())) { 0178 labelLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0179 labelLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0180 } 0181 addParameter(&labelLine, version, QStringLiteral("TYPE"), labelLineType); 0182 card->addLine(labelLine); 0183 } 0184 } 0185 if (version == VCard::v4_0) { 0186 Geo geo = addr.geo(); 0187 if (geo.isValid()) { 0188 QString str = QString::asprintf("\"geo:%.6f,%.6f\"", geo.latitude(), geo.longitude()); 0189 adrLine.addParameter(QStringLiteral("GEO"), str); 0190 } 0191 } 0192 if (!isEmpty) { 0193 addParameter(&adrLine, version, QStringLiteral("TYPE"), addreLineType); 0194 card->addLine(adrLine); 0195 } 0196 } 0197 } 0198 0199 void VCardTool::processEmailList(const Email::List &emailList, VCard::Version version, VCard *card) const 0200 { 0201 for (const auto &email : emailList) { 0202 VCardLine line(QStringLiteral("EMAIL"), email.mail()); 0203 const ParameterMap pMap = email.params(); 0204 for (const auto &[param, l] : pMap) { 0205 QStringList list = l; 0206 if (version == VCard::v2_1) { 0207 if (param.toLower() == QLatin1String("type")) { 0208 bool hasPreferred = false; 0209 const int removeItems = list.removeAll(QStringLiteral("PREF")); 0210 if (removeItems > 0) { 0211 hasPreferred = true; 0212 } 0213 if (!list.isEmpty()) { 0214 addParameter(&line, version, param, list); 0215 } 0216 if (hasPreferred) { 0217 line.addParameter(QStringLiteral("PREF"), QString()); 0218 } 0219 } else { 0220 line.addParameter(param, list.join(QLatin1Char(','))); 0221 } 0222 } else { 0223 line.addParameter(param, list.join(QLatin1Char(','))); 0224 } 0225 } 0226 card->addLine(line); 0227 } 0228 } 0229 0230 void VCardTool::processOrganizations(const Addressee &addressee, VCard::Version version, VCard *card) const 0231 { 0232 const QList<Org> lstOrg = addressee.extraOrganizationList(); 0233 for (const Org &org : lstOrg) { 0234 QStringList organization{org.organization().replace(QLatin1Char(';'), QLatin1String("\\;"))}; 0235 if (!addressee.department().isEmpty()) { 0236 organization.append(addressee.department().replace(QLatin1Char(';'), QLatin1String("\\;"))); 0237 } 0238 const QString orgStr = organization.join(QLatin1Char(';')); 0239 VCardLine orgLine(QStringLiteral("ORG"), orgStr); 0240 if (version == VCard::v2_1 && needsEncoding(orgStr)) { 0241 orgLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0242 orgLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0243 } 0244 orgLine.addParameters(org.params()); 0245 card->addLine(orgLine); 0246 } 0247 } 0248 0249 void VCardTool::processPhoneNumbers(const PhoneNumber::List &phoneNumbers, VCard::Version version, VCard *card) const 0250 { 0251 for (const auto &phone : phoneNumbers) { 0252 VCardLine line(QStringLiteral("TEL"), phone.number()); 0253 const ParameterMap paramsMap = phone.params(); 0254 for (const auto &[param, list] : paramsMap) { 0255 if (param.toUpper() != QLatin1String("TYPE")) { 0256 line.addParameter(param, list.join(QLatin1Char(','))); 0257 } 0258 } 0259 0260 const PhoneNumber::Type type = phone.type(); 0261 QStringList lst; 0262 for (const auto &pType : s_phoneTypes) { 0263 if (pType.flag & type) { 0264 const QString str = QString::fromLatin1(pType.phoneType); 0265 if (version == VCard::v4_0) { 0266 lst << str.toLower(); 0267 } else { 0268 lst << str; 0269 } 0270 } 0271 } 0272 if (!lst.isEmpty()) { 0273 addParameter(&line, version, QStringLiteral("TYPE"), lst); 0274 } 0275 card->addLine(line); 0276 } 0277 } 0278 0279 void VCardTool::processCustoms(const QStringList &customs, VCard::Version version, VCard *card, bool exportVcard) const 0280 { 0281 for (const auto &str : customs) { 0282 QString identifier = QLatin1String("X-") + QStringView(str).left(str.indexOf(QLatin1Char(':'))); 0283 const QString value = str.mid(str.indexOf(QLatin1Char(':')) + 1); 0284 if (value.isEmpty()) { 0285 continue; 0286 } 0287 // Convert to standard identifier 0288 if (exportVcard) { 0289 if (identifier == QLatin1String("X-messaging/aim-All")) { 0290 identifier = QStringLiteral("X-AIM"); 0291 } else if (identifier == QLatin1String("X-messaging/icq-All")) { 0292 identifier = QStringLiteral("X-ICQ"); 0293 } else if (identifier == QLatin1String("X-messaging/xmpp-All")) { 0294 identifier = QStringLiteral("X-JABBER"); 0295 } else if (identifier == QLatin1String("X-messaging/msn-All")) { 0296 identifier = QStringLiteral("X-MSN"); 0297 } else if (identifier == QLatin1String("X-messaging/yahoo-All")) { 0298 identifier = QStringLiteral("X-YAHOO"); 0299 } else if (identifier == QLatin1String("X-messaging/gadu-All")) { 0300 identifier = QStringLiteral("X-GADUGADU"); 0301 } else if (identifier == QLatin1String("X-messaging/skype-All")) { 0302 identifier = QStringLiteral("X-SKYPE"); 0303 } else if (identifier == QLatin1String("X-messaging/groupwise-All")) { 0304 identifier = QStringLiteral("X-GROUPWISE"); 0305 } else if (identifier == QLatin1String("X-messaging/sms-All")) { 0306 identifier = QStringLiteral("X-SMS"); 0307 } else if (identifier == QLatin1String("X-messaging/meanwhile-All")) { 0308 identifier = QStringLiteral("X-MEANWHILE"); 0309 } else if (identifier == QLatin1String("X-messaging/irc-All")) { 0310 identifier = QStringLiteral("X-IRC"); // Not defined by rfc but need for fixing #300869 0311 } else if (identifier == QLatin1String("X-messaging/googletalk-All")) { 0312 // Not defined by rfc but need for fixing #300869 0313 identifier = QStringLiteral("X-GTALK"); 0314 } else if (identifier == QLatin1String("X-messaging/twitter-All")) { 0315 identifier = QStringLiteral("X-TWITTER"); 0316 } 0317 } 0318 0319 if (identifier.toLower() == QLatin1String("x-kaddressbook-x-anniversary") && version == VCard::v4_0) { 0320 // ANNIVERSARY 0321 if (!value.isEmpty()) { 0322 const QDate date = QDate::fromString(value, Qt::ISODate); 0323 QDateTime dt = QDateTime(date.startOfDay()); 0324 dt.setTime(QTime()); 0325 VCardLine line(QStringLiteral("ANNIVERSARY"), createDateTime(dt, version, false)); 0326 card->addLine(line); 0327 } 0328 } else if (identifier.toLower() == QLatin1String("x-kaddressbook-x-spousesname") && version == VCard::v4_0) { 0329 if (!value.isEmpty()) { 0330 VCardLine line(QStringLiteral("RELATED"), QStringLiteral(";")); 0331 line.addParameter(QStringLiteral("TYPE"), QStringLiteral("spouse")); 0332 line.addParameter(QStringLiteral("VALUE"), value); 0333 card->addLine(line); 0334 } 0335 } else { 0336 VCardLine line(identifier, value); 0337 if (version == VCard::v2_1 && needsEncoding(value)) { 0338 line.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0339 line.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0340 } 0341 card->addLine(line); 0342 } 0343 } 0344 } 0345 0346 QByteArray VCardTool::createVCards(const Addressee::List &list, VCard::Version version, bool exportVcard) const 0347 { 0348 VCard::List vCardList; 0349 0350 for (const auto &addressee : list) { 0351 VCard card; 0352 // VERSION 0353 if (version == VCard::v2_1) { 0354 card.addLine(VCardLine(QStringLiteral("VERSION"), QStringLiteral("2.1"))); 0355 } else if (version == VCard::v3_0) { 0356 card.addLine(VCardLine(QStringLiteral("VERSION"), QStringLiteral("3.0"))); 0357 } else if (version == VCard::v4_0) { 0358 card.addLine(VCardLine(QStringLiteral("VERSION"), QStringLiteral("4.0"))); 0359 } 0360 0361 // ADR + LABEL 0362 const Address::List addresses = addressee.addresses(); 0363 processAddresses(addresses, version, &card); 0364 0365 // BDAY 0366 const bool withTime = addressee.birthdayHasTime(); 0367 const QString birthdayString = createDateTime(addressee.birthday(), version, withTime); 0368 card.addLine(VCardLine(QStringLiteral("BDAY"), birthdayString)); 0369 0370 // CATEGORIES only > 2.1 0371 if (version != VCard::v2_1) { 0372 QStringList categories = addressee.categories(); 0373 for (auto &cat : categories) { 0374 cat.replace(QLatin1Char(','), QLatin1String("\\,")); 0375 } 0376 0377 VCardLine catLine(QStringLiteral("CATEGORIES"), categories.join(QLatin1Char(','))); 0378 card.addLine(catLine); 0379 } 0380 // MEMBER (only in 4.0) 0381 if (version == VCard::v4_0) { 0382 // The KIND property must be set to "group" in order to use this property. 0383 if (addressee.kind().toLower() == QLatin1String("group")) { 0384 const QStringList lst = addressee.members(); 0385 for (const QString &member : lst) { 0386 card.addLine(VCardLine(QStringLiteral("MEMBER"), member)); 0387 } 0388 } 0389 } 0390 // SOURCE 0391 const QList<QUrl> lstUrl = addressee.sourcesUrlList(); 0392 for (const QUrl &url : lstUrl) { 0393 VCardLine line = VCardLine(QStringLiteral("SOURCE"), url.url()); 0394 card.addLine(line); 0395 } 0396 0397 const Related::List relatedList = addressee.relationships(); 0398 for (const auto &rel : relatedList) { 0399 VCardLine line(QStringLiteral("RELATED"), rel.related()); 0400 line.addParameters(rel.params()); 0401 card.addLine(line); 0402 } 0403 // CLASS only for version == 3.0 0404 if (version == VCard::v3_0) { 0405 card.addLine(createSecrecy(addressee.secrecy())); 0406 } 0407 // LANG only for version == 4.0 0408 if (version == VCard::v4_0) { 0409 const Lang::List langList = addressee.langs(); 0410 for (const auto &lang : langList) { 0411 VCardLine line(QStringLiteral("LANG"), lang.language()); 0412 line.addParameters(lang.params()); 0413 card.addLine(line); 0414 } 0415 } 0416 // CLIENTPIDMAP 0417 if (version == VCard::v4_0) { 0418 const ClientPidMap::List clientpidmapList = addressee.clientPidMapList(); 0419 for (const auto &pMap : clientpidmapList) { 0420 VCardLine line(QStringLiteral("CLIENTPIDMAP"), pMap.clientPidMap()); 0421 line.addParameters(pMap.params()); 0422 card.addLine(line); 0423 } 0424 } 0425 // EMAIL 0426 const Email::List emailList = addressee.emailList(); 0427 processEmailList(emailList, version, &card); 0428 0429 // FN required for only version > 2.1 0430 VCardLine fnLine(QStringLiteral("FN"), addressee.formattedName()); 0431 if (version == VCard::v2_1 && needsEncoding(addressee.formattedName())) { 0432 fnLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0433 fnLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0434 } 0435 card.addLine(fnLine); 0436 0437 // GEO 0438 const Geo geo = addressee.geo(); 0439 if (geo.isValid()) { 0440 QString str; 0441 if (version == VCard::v4_0) { 0442 str = QString::asprintf("geo:%.6f,%.6f", geo.latitude(), geo.longitude()); 0443 } else { 0444 str = QString::asprintf("%.6f;%.6f", geo.latitude(), geo.longitude()); 0445 } 0446 card.addLine(VCardLine(QStringLiteral("GEO"), str)); 0447 } 0448 0449 // KEY 0450 const Key::List keys = addressee.keys(); 0451 for (const auto &k : keys) { 0452 card.addLine(createKey(k, version)); 0453 } 0454 0455 // LOGO 0456 card.addLine(createPicture(QStringLiteral("LOGO"), addressee.logo(), version)); 0457 const QList<Picture> lstLogo = addressee.extraLogoList(); 0458 for (const Picture &logo : lstLogo) { 0459 card.addLine(createPicture(QStringLiteral("LOGO"), logo, version)); 0460 } 0461 0462 // MAILER only for version < 4.0 0463 if (version != VCard::v4_0) { 0464 VCardLine mailerLine(QStringLiteral("MAILER"), addressee.mailer()); 0465 if (version == VCard::v2_1 && needsEncoding(addressee.mailer())) { 0466 mailerLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0467 mailerLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0468 } 0469 card.addLine(mailerLine); 0470 } 0471 0472 // N required for only version < 4.0 0473 QStringList name; 0474 name.append(addressee.familyName().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0475 name.append(addressee.givenName().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0476 name.append(addressee.additionalName().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0477 name.append(addressee.prefix().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0478 name.append(addressee.suffix().replace(QLatin1Char(';'), QStringLiteral("\\;"))); 0479 0480 VCardLine nLine(QStringLiteral("N"), name.join(QLatin1Char(';'))); 0481 if (version == VCard::v2_1 && needsEncoding(name.join(QLatin1Char(';')))) { 0482 nLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0483 nLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0484 } 0485 if (version == VCard::v4_0 && !addressee.sortString().isEmpty()) { 0486 nLine.addParameter(QStringLiteral("SORT-AS"), addressee.sortString()); 0487 } 0488 0489 card.addLine(nLine); 0490 0491 // NAME only for version < 4.0 0492 if (version != VCard::v4_0) { 0493 VCardLine nameLine(QStringLiteral("NAME"), addressee.name()); 0494 if (version == VCard::v2_1 && needsEncoding(addressee.name())) { 0495 nameLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0496 nameLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0497 } 0498 card.addLine(nameLine); 0499 } 0500 0501 // NICKNAME only for version > 2.1 0502 if (version != VCard::v2_1) { 0503 const QList<NickName> lstNickName = addressee.extraNickNameList(); 0504 for (const NickName &nickName : lstNickName) { 0505 VCardLine nickNameLine(QStringLiteral("NICKNAME"), nickName.nickname()); 0506 nickNameLine.addParameters(nickName.params()); 0507 0508 card.addLine(nickNameLine); 0509 } 0510 } 0511 0512 // NOTE 0513 VCardLine noteLine(QStringLiteral("NOTE"), addressee.note()); 0514 if (version == VCard::v2_1 && needsEncoding(addressee.note())) { 0515 noteLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0516 noteLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0517 } 0518 card.addLine(noteLine); 0519 0520 // ORG 0521 processOrganizations(addressee, version, &card); 0522 0523 // PHOTO 0524 card.addLine(createPicture(QStringLiteral("PHOTO"), addressee.photo(), version)); 0525 const QList<Picture> lstExtraPhoto = addressee.extraPhotoList(); 0526 for (const Picture &photo : lstExtraPhoto) { 0527 card.addLine(createPicture(QStringLiteral("PHOTO"), photo, version)); 0528 } 0529 0530 // PROID only for version > 2.1 0531 if (version != VCard::v2_1) { 0532 card.addLine(VCardLine(QStringLiteral("PRODID"), addressee.productId())); 0533 } 0534 0535 // REV 0536 card.addLine(VCardLine(QStringLiteral("REV"), createDateTime(addressee.revision(), version))); 0537 0538 // ROLE 0539 const QList<Role> lstExtraRole = addressee.extraRoleList(); 0540 for (const Role &role : lstExtraRole) { 0541 VCardLine roleLine(QStringLiteral("ROLE"), role.role()); 0542 if (version == VCard::v2_1 && needsEncoding(role.role())) { 0543 roleLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0544 roleLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0545 } 0546 roleLine.addParameters(role.params()); 0547 card.addLine(roleLine); 0548 } 0549 0550 // SORT-STRING 0551 if (version == VCard::v3_0) { 0552 card.addLine(VCardLine(QStringLiteral("SORT-STRING"), addressee.sortString())); 0553 } 0554 0555 // SOUND 0556 card.addLine(createSound(addressee.sound(), version)); 0557 const QList<Sound> lstSound = addressee.extraSoundList(); 0558 for (const Sound &sound : lstSound) { 0559 card.addLine(createSound(sound, version)); 0560 } 0561 0562 // TEL 0563 const PhoneNumber::List phoneNumbers = addressee.phoneNumbers(); 0564 processPhoneNumbers(phoneNumbers, version, &card); 0565 0566 // TITLE 0567 const QList<Title> lstTitle = addressee.extraTitleList(); 0568 for (const Title &title : lstTitle) { 0569 VCardLine titleLine(QStringLiteral("TITLE"), title.title()); 0570 if (version == VCard::v2_1 && needsEncoding(title.title())) { 0571 titleLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8")); 0572 titleLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE")); 0573 } 0574 titleLine.addParameters(title.params()); 0575 0576 card.addLine(titleLine); 0577 } 0578 0579 // TZ 0580 // TODO Add vcard4.0 support 0581 const TimeZone timeZone = addressee.timeZone(); 0582 if (timeZone.isValid()) { 0583 int neg = 1; 0584 if (timeZone.offset() < 0) { 0585 neg = -1; 0586 } 0587 0588 QString str = 0589 QString::asprintf("%c%02d:%02d", (timeZone.offset() >= 0 ? '+' : '-'), (timeZone.offset() / 60) * neg, (timeZone.offset() % 60) * neg); 0590 0591 card.addLine(VCardLine(QStringLiteral("TZ"), str)); 0592 } 0593 0594 // UID 0595 card.addLine(VCardLine(QStringLiteral("UID"), addressee.uid())); 0596 0597 // URL 0598 const QList<ResourceLocatorUrl> lstExtraUrl = addressee.extraUrlList(); 0599 for (const ResourceLocatorUrl &url : lstExtraUrl) { 0600 VCardLine line(QStringLiteral("URL"), url.url()); 0601 line.addParameters(url.params()); 0602 card.addLine(line); 0603 } 0604 if (version == VCard::v4_0) { 0605 // GENDER 0606 const Gender gender = addressee.gender(); 0607 if (gender.isValid()) { 0608 QString genderStr; 0609 if (!gender.gender().isEmpty()) { 0610 genderStr = gender.gender(); 0611 } 0612 if (!gender.comment().isEmpty()) { 0613 genderStr += QLatin1Char(';') + gender.comment(); 0614 } 0615 VCardLine line(QStringLiteral("GENDER"), genderStr); 0616 card.addLine(line); 0617 } 0618 // KIND 0619 if (!addressee.kind().isEmpty()) { 0620 VCardLine line(QStringLiteral("KIND"), addressee.kind()); 0621 card.addLine(line); 0622 } 0623 } 0624 // From vcard4. 0625 if (version == VCard::v4_0) { 0626 const QList<CalendarUrl> lstCalendarUrl = addressee.calendarUrlList(); 0627 for (const CalendarUrl &url : lstCalendarUrl) { 0628 if (url.isValid()) { 0629 QString type; 0630 switch (url.type()) { 0631 case CalendarUrl::Unknown: 0632 case CalendarUrl::EndCalendarType: 0633 break; 0634 case CalendarUrl::FBUrl: 0635 type = QStringLiteral("FBURL"); 0636 break; 0637 case CalendarUrl::CALUri: 0638 type = QStringLiteral("CALURI"); 0639 break; 0640 case CalendarUrl::CALADRUri: 0641 type = QStringLiteral("CALADRURI"); 0642 break; 0643 } 0644 if (!type.isEmpty()) { 0645 VCardLine line(type, url.url().toDisplayString()); 0646 line.addParameters(url.params()); 0647 card.addLine(line); 0648 } 0649 } 0650 } 0651 } 0652 0653 // FieldGroup 0654 const QList<FieldGroup> lstGroup = addressee.fieldGroupList(); 0655 for (const FieldGroup &group : lstGroup) { 0656 VCardLine line(group.fieldGroupName(), group.value()); 0657 line.addParameters(group.params()); 0658 card.addLine(line); 0659 } 0660 0661 // IMPP (supported in vcard 3 too) 0662 const QList<Impp> lstImpp = addressee.imppList(); 0663 for (const Impp &impp : lstImpp) { 0664 VCardLine line(QStringLiteral("IMPP"), impp.address().url()); 0665 const ParameterMap pMap = impp.params(); 0666 for (const auto &[param, list] : pMap) { 0667 if (param.toLower() != QLatin1String("x-service-type")) { 0668 line.addParameter(param, list.join(QLatin1Char(','))); 0669 } 0670 } 0671 card.addLine(line); 0672 } 0673 0674 // X- 0675 const QStringList customs = addressee.customs(); 0676 processCustoms(customs, version, &card, exportVcard); 0677 0678 vCardList.append(card); 0679 } 0680 0681 return VCardParser::createVCards(vCardList); 0682 } 0683 0684 Addressee::List VCardTool::parseVCards(const QByteArray &vcard) const 0685 { 0686 static const QLatin1Char semicolonSep(';'); 0687 static const QLatin1Char commaSep(','); 0688 QString identifier; 0689 QString group; 0690 Addressee::List addrList; 0691 const VCard::List vCardList = VCardParser::parseVCards(vcard); 0692 0693 VCard::List::ConstIterator cardIt; 0694 VCard::List::ConstIterator listEnd(vCardList.end()); 0695 for (cardIt = vCardList.begin(); cardIt != listEnd; ++cardIt) { 0696 Addressee addr; 0697 0698 const QStringList idents = (*cardIt).identifiers(); 0699 QStringList::ConstIterator identIt; 0700 QStringList::ConstIterator identEnd(idents.end()); 0701 for (identIt = idents.begin(); identIt != identEnd; ++identIt) { 0702 const VCardLine::List lines = (*cardIt).lines((*identIt)); 0703 VCardLine::List::ConstIterator lineIt; 0704 0705 // iterate over the lines 0706 for (lineIt = lines.begin(); lineIt != lines.end(); ++lineIt) { 0707 identifier = (*lineIt).identifier().toLower(); 0708 group = (*lineIt).group(); 0709 if (!group.isEmpty() && identifier != QLatin1String("adr")) { 0710 KContacts::FieldGroup groupField(group + QLatin1Char('.') + (*lineIt).identifier()); 0711 groupField.setParams((*lineIt).parameterMap()); 0712 groupField.setValue((*lineIt).value().toString()); 0713 addr.insertFieldGroup(groupField); 0714 } 0715 // ADR 0716 else if (identifier == QLatin1String("adr")) { 0717 Address address; 0718 const QStringList addrParts = splitString(semicolonSep, (*lineIt).value().toString()); 0719 const int addrPartsCount(addrParts.count()); 0720 if (addrPartsCount > 0) { 0721 address.setPostOfficeBox(addrParts.at(0)); 0722 } 0723 if (addrPartsCount > 1) { 0724 address.setExtended(addrParts.at(1)); 0725 } 0726 if (addrPartsCount > 2) { 0727 address.setStreet(addrParts.at(2)); 0728 } 0729 if (addrPartsCount > 3) { 0730 address.setLocality(addrParts.at(3)); 0731 } 0732 if (addrPartsCount > 4) { 0733 address.setRegion(addrParts.at(4)); 0734 } 0735 if (addrPartsCount > 5) { 0736 address.setPostalCode(addrParts.at(5)); 0737 } 0738 if (addrPartsCount > 6) { 0739 address.setCountry(addrParts.at(6)); 0740 } 0741 0742 Address::Type type; 0743 0744 const QStringList types = (*lineIt).parameters(QStringLiteral("type")); 0745 QStringList::ConstIterator end(types.end()); 0746 for (QStringList::ConstIterator it = types.begin(); it != end; ++it) { 0747 type |= stringToAddressType((*it).toLower()); 0748 } 0749 0750 address.setType(type); 0751 QString label = (*lineIt).parameter(QStringLiteral("label")); 0752 if (!label.isEmpty()) { 0753 if (label.length() > 1) { 0754 if (label.at(0) == QLatin1Char('"') && label.at(label.length() - 1) == QLatin1Char('"')) { 0755 label = label.mid(1, label.length() - 2); 0756 } 0757 } 0758 address.setLabel(label); 0759 } 0760 QString geoStr = (*lineIt).parameter(QStringLiteral("geo")); 0761 if (!geoStr.isEmpty()) { 0762 geoStr.remove(QLatin1Char('\"')); 0763 geoStr.remove(QStringLiteral("geo:")); 0764 if (geoStr.contains(QLatin1Char(','))) { 0765 QStringList arguments = geoStr.split(QLatin1Char(',')); 0766 KContacts::Geo geo; 0767 geo.setLatitude(arguments.at(0).toDouble()); 0768 geo.setLongitude(arguments.at(1).toDouble()); 0769 address.setGeo(geo); 0770 } 0771 } 0772 addr.insertAddress(address); 0773 } 0774 // ANNIVERSARY 0775 else if (identifier == QLatin1String("anniversary")) { 0776 const QString t = (*lineIt).value().toString(); 0777 const QDateTime dt(parseDateTime(t)); 0778 addr.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Anniversary"), dt.date().toString(Qt::ISODate)); 0779 } 0780 // BDAY 0781 else if (identifier == QLatin1String("bday")) { 0782 bool withTime; 0783 const QDateTime bday = parseDateTime((*lineIt).value().toString(), &withTime); 0784 addr.setBirthday(bday, withTime); 0785 } 0786 // CATEGORIES 0787 else if (identifier == QLatin1String("categories")) { 0788 const QStringList categories = splitString(commaSep, (*lineIt).value().toString()); 0789 addr.setCategories(categories); 0790 } 0791 // FBURL 0792 else if (identifier == QLatin1String("fburl")) { 0793 CalendarUrl calurl; 0794 calurl.setType(CalendarUrl::FBUrl); 0795 const QUrl url = QUrl((*lineIt).value().toString()); 0796 calurl.setUrl(url); 0797 calurl.setParams((*lineIt).parameterMap()); 0798 addr.insertCalendarUrl(calurl); 0799 } 0800 // CALADRURI 0801 else if (identifier == QLatin1String("caladruri")) { 0802 CalendarUrl calurl; 0803 calurl.setType(CalendarUrl::CALADRUri); 0804 const QUrl url = QUrl((*lineIt).value().toString()); 0805 calurl.setUrl(url); 0806 calurl.setParams((*lineIt).parameterMap()); 0807 addr.insertCalendarUrl(calurl); 0808 } 0809 // CALURI 0810 else if (identifier == QLatin1String("caluri")) { 0811 CalendarUrl calurl; 0812 calurl.setType(CalendarUrl::CALUri); 0813 const QUrl url = QUrl((*lineIt).value().toString()); 0814 calurl.setUrl(url); 0815 calurl.setParams((*lineIt).parameterMap()); 0816 addr.insertCalendarUrl(calurl); 0817 } 0818 // IMPP 0819 else if (identifier == QLatin1String("impp")) { 0820 QUrl imppUrl((*lineIt).value().toString()); 0821 Impp impp; 0822 impp.setParams((*lineIt).parameterMap()); 0823 if (!(*lineIt).parameter(QStringLiteral("x-service-type")).isEmpty() && imppUrl.scheme().isEmpty()) { 0824 imppUrl.setScheme(normalizeImppServiceType((*lineIt).parameter(QStringLiteral("x-service-type")).toLower())); 0825 } 0826 impp.setAddress(imppUrl); 0827 addr.insertImpp(impp); 0828 } 0829 // CLASS 0830 else if (identifier == QLatin1String("class")) { 0831 addr.setSecrecy(parseSecrecy(*lineIt)); 0832 } 0833 // GENDER 0834 else if (identifier == QLatin1String("gender")) { 0835 QString genderStr = (*lineIt).value().toString(); 0836 if (!genderStr.isEmpty()) { 0837 Gender gender; 0838 if (genderStr.at(0) != QLatin1Char(';')) { 0839 gender.setGender(genderStr.at(0)); 0840 if (genderStr.length() > 2 && (genderStr.at(1) == QLatin1Char(';'))) { 0841 gender.setComment(genderStr.right(genderStr.length() - 2)); 0842 } 0843 } else { 0844 gender.setComment(genderStr.right(genderStr.length() - 1)); 0845 } 0846 addr.setGender(gender); 0847 } 0848 } 0849 // LANG 0850 else if (identifier == QLatin1String("lang")) { 0851 Lang lang; 0852 lang.setLanguage((*lineIt).value().toString()); 0853 lang.setParams((*lineIt).parameterMap()); 0854 addr.insertLang(lang); 0855 } 0856 // EMAIL 0857 else if (identifier == QLatin1String("email")) { 0858 const QStringList types = (*lineIt).parameters(QStringLiteral("type")); 0859 Email mail((*lineIt).value().toString()); 0860 mail.setParams((*lineIt).parameterMap()); 0861 addr.addEmail(mail); 0862 } 0863 // KIND 0864 else if (identifier == QLatin1String("kind")) { 0865 addr.setKind((*lineIt).value().toString()); 0866 } 0867 // FN 0868 else if (identifier == QLatin1String("fn")) { 0869 addr.setFormattedName((*lineIt).value().toString()); 0870 } 0871 // GEO 0872 else if (identifier == QLatin1String("geo")) { 0873 Geo geo; 0874 QString lineStr = (*lineIt).value().toString(); 0875 if (lineStr.startsWith(QLatin1String("geo:"))) { // VCard 4.0 0876 lineStr.remove(QStringLiteral("geo:")); 0877 const QStringList geoParts = lineStr.split(QLatin1Char(','), Qt::KeepEmptyParts); 0878 if (geoParts.size() >= 2) { 0879 geo.setLatitude(geoParts.at(0).toFloat()); 0880 geo.setLongitude(geoParts.at(1).toFloat()); 0881 addr.setGeo(geo); 0882 } 0883 } else { 0884 const QStringList geoParts = lineStr.split(QLatin1Char(';'), Qt::KeepEmptyParts); 0885 if (geoParts.size() >= 2) { 0886 geo.setLatitude(geoParts.at(0).toFloat()); 0887 geo.setLongitude(geoParts.at(1).toFloat()); 0888 addr.setGeo(geo); 0889 } 0890 } 0891 } 0892 // KEY 0893 else if (identifier == QLatin1String("key")) { 0894 addr.insertKey(parseKey(*lineIt)); 0895 } 0896 // LABEL 0897 else if (identifier == QLatin1String("label")) { 0898 Address::Type type; 0899 0900 const QStringList types = (*lineIt).parameters(QStringLiteral("type")); 0901 QStringList::ConstIterator end(types.end()); 0902 for (QStringList::ConstIterator it = types.begin(); it != end; ++it) { 0903 type |= stringToAddressType((*it).toLower()); 0904 } 0905 0906 bool available = false; 0907 KContacts::Address::List addressList = addr.addresses(); 0908 for (KContacts::Address::List::Iterator it = addressList.begin(); it != addressList.end(); ++it) { 0909 if ((*it).type() == type) { 0910 (*it).setLabel((*lineIt).value().toString()); 0911 addr.insertAddress(*it); 0912 available = true; 0913 break; 0914 } 0915 } 0916 0917 if (!available) { // a standalone LABEL tag 0918 KContacts::Address address(type); 0919 address.setLabel((*lineIt).value().toString()); 0920 addr.insertAddress(address); 0921 } 0922 } 0923 // LOGO 0924 else if (identifier == QLatin1String("logo")) { 0925 Picture picture = parsePicture(*lineIt); 0926 if (addr.logo().isEmpty()) { 0927 addr.setLogo(picture); 0928 } else { 0929 addr.insertExtraLogo(picture); 0930 } 0931 } 0932 // MAILER 0933 else if (identifier == QLatin1String("mailer")) { 0934 addr.setMailer((*lineIt).value().toString()); 0935 } 0936 // N 0937 else if (identifier == QLatin1Char('n')) { 0938 const QStringList nameParts = splitString(semicolonSep, (*lineIt).value().toString()); 0939 const int numberOfParts(nameParts.count()); 0940 if (numberOfParts > 0) { 0941 addr.setFamilyName(nameParts.at(0)); 0942 } 0943 if (numberOfParts > 1) { 0944 addr.setGivenName(nameParts.at(1)); 0945 } 0946 if (numberOfParts > 2) { 0947 addr.setAdditionalName(nameParts.at(2)); 0948 } 0949 if (numberOfParts > 3) { 0950 addr.setPrefix(nameParts.at(3)); 0951 } 0952 if (numberOfParts > 4) { 0953 addr.setSuffix(nameParts.at(4)); 0954 } 0955 if (!(*lineIt).parameter(QStringLiteral("sort-as")).isEmpty()) { 0956 addr.setSortString((*lineIt).parameter(QStringLiteral("sort-as"))); 0957 } 0958 } 0959 // NAME 0960 else if (identifier == QLatin1String("name")) { 0961 addr.setName((*lineIt).value().toString()); 0962 } 0963 // NICKNAME 0964 else if (identifier == QLatin1String("nickname")) { 0965 NickName nickName((*lineIt).value().toString()); 0966 nickName.setParams((*lineIt).parameterMap()); 0967 addr.insertExtraNickName(nickName); 0968 } 0969 // NOTE 0970 else if (identifier == QLatin1String("note")) { 0971 addr.setNote((*lineIt).value().toString()); 0972 } 0973 // ORGANIZATION 0974 else if (identifier == QLatin1String("org")) { 0975 const QStringList orgParts = splitString(semicolonSep, (*lineIt).value().toString()); 0976 const int orgPartsCount(orgParts.count()); 0977 if (orgPartsCount > 0) { 0978 Org organization(orgParts.at(0)); 0979 organization.setParams((*lineIt).parameterMap()); 0980 addr.insertExtraOrganization(organization); 0981 } 0982 if (orgPartsCount > 1) { 0983 addr.setDepartment(orgParts.at(1)); 0984 } 0985 if (!(*lineIt).parameter(QStringLiteral("sort-as")).isEmpty()) { 0986 addr.setSortString((*lineIt).parameter(QStringLiteral("sort-as"))); 0987 } 0988 } 0989 // PHOTO 0990 else if (identifier == QLatin1String("photo")) { 0991 Picture picture = parsePicture(*lineIt); 0992 if (addr.photo().isEmpty()) { 0993 addr.setPhoto(picture); 0994 } else { 0995 addr.insertExtraPhoto(picture); 0996 } 0997 } 0998 // PROID 0999 else if (identifier == QLatin1String("prodid")) { 1000 addr.setProductId((*lineIt).value().toString()); 1001 } 1002 // REV 1003 else if (identifier == QLatin1String("rev")) { 1004 addr.setRevision(parseDateTime((*lineIt).value().toString())); 1005 } 1006 // ROLE 1007 else if (identifier == QLatin1String("role")) { 1008 Role role((*lineIt).value().toString()); 1009 role.setParams((*lineIt).parameterMap()); 1010 addr.insertExtraRole(role); 1011 } 1012 // SORT-STRING 1013 else if (identifier == QLatin1String("sort-string")) { 1014 addr.setSortString((*lineIt).value().toString()); 1015 } 1016 // SOUND 1017 else if (identifier == QLatin1String("sound")) { 1018 Sound sound = parseSound(*lineIt); 1019 if (addr.sound().isEmpty()) { 1020 addr.setSound(sound); 1021 } else { 1022 addr.insertExtraSound(sound); 1023 } 1024 } 1025 // TEL 1026 else if (identifier == QLatin1String("tel")) { 1027 PhoneNumber phone; 1028 phone.setNumber((*lineIt).value().toString()); 1029 1030 PhoneNumber::Type type; 1031 bool foundType = false; 1032 const QStringList types = (*lineIt).parameters(QStringLiteral("type")); 1033 QStringList::ConstIterator typeEnd(types.constEnd()); 1034 for (QStringList::ConstIterator it = types.constBegin(); it != typeEnd; ++it) { 1035 type |= stringToPhoneType((*it).toUpper()); 1036 foundType = true; 1037 } 1038 phone.setType(foundType ? type : PhoneNumber::Undefined); 1039 phone.setParams((*lineIt).parameterMap()); 1040 1041 addr.insertPhoneNumber(phone); 1042 } 1043 // TITLE 1044 else if (identifier == QLatin1String("title")) { 1045 Title title((*lineIt).value().toString()); 1046 title.setParams((*lineIt).parameterMap()); 1047 addr.insertExtraTitle(title); 1048 } 1049 // TZ 1050 else if (identifier == QLatin1String("tz")) { 1051 // TODO add vcard4 support 1052 TimeZone tz; 1053 const QString date = (*lineIt).value().toString(); 1054 1055 if (!date.isEmpty()) { 1056 const QStringView dateView(date); 1057 int hours = dateView.mid(1, 2).toInt(); 1058 int minutes = dateView.mid(4, 2).toInt(); 1059 int offset = (hours * 60) + minutes; 1060 offset = offset * (date[0] == QLatin1Char('+') ? 1 : -1); 1061 1062 tz.setOffset(offset); 1063 addr.setTimeZone(tz); 1064 } 1065 } 1066 // UID 1067 else if (identifier == QLatin1String("uid")) { 1068 addr.setUid((*lineIt).value().toString()); 1069 } 1070 // URL 1071 else if (identifier == QLatin1String("url")) { 1072 const QUrl url = QUrl((*lineIt).value().toString()); 1073 ResourceLocatorUrl resourceLocatorUrl; 1074 resourceLocatorUrl.setUrl(url); 1075 resourceLocatorUrl.setParams((*lineIt).parameterMap()); 1076 addr.insertExtraUrl(resourceLocatorUrl); 1077 } 1078 // SOURCE 1079 else if (identifier == QLatin1String("source")) { 1080 const QUrl url = QUrl((*lineIt).value().toString()); 1081 addr.insertSourceUrl(url); 1082 } 1083 // MEMBER (vcard 4.0) 1084 else if (identifier == QLatin1String("member")) { 1085 addr.insertMember((*lineIt).value().toString()); 1086 } 1087 // RELATED (vcard 4.0) 1088 else if (identifier == QLatin1String("related")) { 1089 Related related; 1090 related.setRelated((*lineIt).value().toString()); 1091 related.setParams((*lineIt).parameterMap()); 1092 addr.insertRelationship(related); 1093 } 1094 // CLIENTPIDMAP (vcard 4.0) 1095 else if (identifier == QLatin1String("clientpidmap")) { 1096 ClientPidMap clientpidmap; 1097 clientpidmap.setClientPidMap((*lineIt).value().toString()); 1098 clientpidmap.setParams((*lineIt).parameterMap()); 1099 addr.insertClientPidMap(clientpidmap); 1100 } 1101 // X- 1102 // TODO import X-GENDER 1103 else if (identifier.startsWith(QLatin1String("x-"))) { 1104 QString ident = (*lineIt).identifier(); 1105 // clang-format off 1106 //X-Evolution 1107 // also normalize case of our own extensions, some backends "adjust" that 1108 if (identifier == QLatin1String("x-evolution-spouse") 1109 || identifier == QLatin1String("x-spouse")) { 1110 ident = QStringLiteral("X-KADDRESSBOOK-X-SpousesName"); 1111 } else if (identifier == QLatin1String("x-evolution-blog-url") || identifier.compare(QLatin1String("X-KADDRESSBOOK-BLOGFEED"), Qt::CaseInsensitive) == 0) { 1112 ident = QStringLiteral("X-KADDRESSBOOK-BlogFeed"); 1113 } else if (identifier == QLatin1String("x-evolution-assistant") 1114 || identifier == QLatin1String("x-assistant") 1115 || identifier.compare(QLatin1String("X-KADDRESSBOOK-X-ASSISTANTSNAME"), Qt::CaseInsensitive) == 0) { 1116 ident = QStringLiteral("X-KADDRESSBOOK-X-AssistantsName"); 1117 } else if (identifier == QLatin1String("x-evolution-anniversary") 1118 || identifier == QLatin1String("x-anniversary") 1119 || identifier.compare(QLatin1String("X-KADDRESSBOOK-X-ANNIVERSARY"), Qt::CaseInsensitive) == 0) { 1120 ident = QStringLiteral("X-KADDRESSBOOK-X-Anniversary"); 1121 } else if (identifier == QLatin1String("x-evolution-manager") 1122 || identifier == QLatin1String("x-manager") 1123 || identifier.compare(QLatin1String("X-KADDRESSBOOK-X-MANAGERSNAME"), Qt::CaseInsensitive) == 0) { 1124 // clang-format on 1125 ident = QStringLiteral("X-KADDRESSBOOK-X-ManagersName"); 1126 } else if (identifier.compare(QLatin1String("X-KADDRESSBOOK-X-PROFESSION"), Qt::CaseInsensitive) == 0) { 1127 ident = QStringLiteral("X-KADDRESSBOOK-X-Profession"); 1128 } else if (identifier.compare(QLatin1String("X-KADDRESSBOOK-X-OFFICE"), Qt::CaseInsensitive) == 0) { 1129 ident = QStringLiteral("X-KADDRESSBOOK-X-Office"); 1130 } else if (identifier.compare(QLatin1String("X-KADDRESSBOOK-X-SPOUSESNAME"), Qt::CaseInsensitive) == 0) { 1131 ident = QStringLiteral("X-KADDRESSBOOK-X-SpousesName"); 1132 } else if (identifier == QLatin1String("x-aim")) { 1133 ident = QStringLiteral("X-messaging/aim-All"); 1134 } else if (identifier == QLatin1String("x-icq")) { 1135 ident = QStringLiteral("X-messaging/icq-All"); 1136 } else if (identifier == QLatin1String("x-jabber")) { 1137 ident = QStringLiteral("X-messaging/xmpp-All"); 1138 } else if (identifier == QLatin1String("x-jabber")) { 1139 ident = QStringLiteral("X-messaging/xmpp-All"); 1140 } else if (identifier == QLatin1String("x-msn")) { 1141 ident = QStringLiteral("X-messaging/msn-All"); 1142 } else if (identifier == QLatin1String("x-yahoo")) { 1143 ident = QStringLiteral("X-messaging/yahoo-All"); 1144 } else if (identifier == QLatin1String("x-gadugadu")) { 1145 ident = QStringLiteral("X-messaging/gadu-All"); 1146 } else if (identifier == QLatin1String("x-skype")) { 1147 ident = QStringLiteral("X-messaging/skype-All"); 1148 } else if (identifier == QLatin1String("x-groupwise")) { 1149 ident = QStringLiteral("X-messaging/groupwise-All"); 1150 } else if (identifier == QLatin1String("x-sms")) { 1151 ident = QStringLiteral("X-messaging/sms-All"); 1152 } else if (identifier == QLatin1String("x-meanwhile")) { 1153 ident = QStringLiteral("X-messaging/meanwhile-All"); 1154 } else if (identifier == QLatin1String("x-irc")) { 1155 ident = QStringLiteral("X-messaging/irc-All"); 1156 } else if (identifier == QLatin1String("x-gtalk")) { 1157 ident = QStringLiteral("X-messaging/googletalk-All"); 1158 } else if (identifier == QLatin1String("x-twitter")) { 1159 ident = QStringLiteral("X-messaging/twitter-All"); 1160 } 1161 1162 const QString key = ident.mid(2); 1163 const int dash = key.indexOf(QLatin1Char('-')); 1164 1165 // convert legacy messaging fields into IMPP ones 1166 if (key.startsWith(QLatin1String("messaging/"))) { 1167 QUrl url; 1168 url.setScheme(normalizeImppServiceType(key.mid(10, dash - 10))); 1169 const auto values = (*lineIt).value().toString().split(QChar(0xE000), Qt::SkipEmptyParts); 1170 for (const auto &value : values) { 1171 url.setPath(value); 1172 Impp impp; 1173 impp.setParams((*lineIt).parameterMap()); 1174 impp.setAddress(url); 1175 addr.insertImpp(impp); 1176 } 1177 } else { 1178 addr.insertCustom(key.left(dash), key.mid(dash + 1), (*lineIt).value().toString()); 1179 } 1180 } 1181 } 1182 } 1183 1184 addrList.append(addr); 1185 } 1186 1187 return addrList; 1188 } 1189 1190 QDateTime VCardTool::parseDateTime(const QString &str, bool *timeValid) 1191 { 1192 static const QLatin1Char sep('-'); 1193 1194 const int posT = str.indexOf(QLatin1Char('T')); 1195 QString dateString = posT >= 0 ? str.left(posT) : str; 1196 const bool noYear = dateString.startsWith(QLatin1String("--")); 1197 dateString.remove(QLatin1Char('-')); 1198 QDate date; 1199 1200 const QStringView dstr{dateString}; 1201 if (noYear) { 1202 date.setDate(-1, dstr.mid(0, 2).toInt(), dstr.mid(2, 2).toInt()); 1203 } else { 1204 // E.g. 20160120 1205 date.setDate(dstr.mid(0, 4).toInt(), dstr.mid(4, 2).toInt(), dstr.mid(6, 2).toInt()); 1206 } 1207 1208 QTime time; 1209 QTimeZone tz = QTimeZone::LocalTime; 1210 if (posT >= 0) { 1211 QString timeString = str.mid(posT + 1); 1212 timeString.remove(QLatin1Char(':')); 1213 const int zPos = timeString.indexOf(QLatin1Char('Z')); 1214 const int plusPos = timeString.indexOf(QLatin1Char('+')); 1215 const int minusPos = timeString.indexOf(sep); 1216 const int tzPos = qMax(qMax(zPos, plusPos), minusPos); 1217 const QStringView hhmmssString = tzPos >= 0 ? QStringView(timeString).left(tzPos) : QStringView(timeString); 1218 int hour = 0; 1219 int minutes = 0; 1220 int seconds = 0; 1221 switch (hhmmssString.size()) { 1222 case 2: 1223 hour = hhmmssString.toInt(); 1224 break; 1225 case 4: 1226 hour = hhmmssString.mid(0, 2).toInt(); 1227 minutes = hhmmssString.mid(2, 2).toInt(); 1228 break; 1229 case 6: 1230 hour = hhmmssString.mid(0, 2).toInt(); 1231 minutes = hhmmssString.mid(2, 2).toInt(); 1232 seconds = hhmmssString.mid(4, 2).toInt(); 1233 break; 1234 } 1235 time.setHMS(hour, minutes, seconds); 1236 1237 if (tzPos >= 0) { 1238 if (zPos >= 0) { 1239 tz = QTimeZone::UTC; 1240 } else { 1241 int offsetSecs = 0; 1242 const auto offsetString = QStringView(timeString).mid(tzPos + 1); 1243 switch (offsetString.size()) { 1244 case 2: // format: "hh" 1245 offsetSecs = offsetString.left(2).toInt() * 3600; 1246 break; 1247 case 4: // format: "hhmm" 1248 offsetSecs = offsetString.left(2).toInt() * 3600 + offsetString.mid(2, 2).toInt() * 60; 1249 break; 1250 } 1251 if (minusPos >= 0) { 1252 offsetSecs *= -1; 1253 } 1254 tz = QTimeZone::fromSecondsAheadOfUtc(offsetSecs); 1255 } 1256 } 1257 } 1258 if (timeValid) { 1259 *timeValid = time.isValid(); 1260 } 1261 1262 return QDateTime(date, time, tz); 1263 } 1264 1265 QString VCardTool::createDateTime(const QDateTime &dateTime, VCard::Version version, bool withTime) 1266 { 1267 if (!dateTime.date().isValid()) { 1268 return QString(); 1269 } 1270 QString str = createDate(dateTime.date(), version); 1271 if (!withTime) { 1272 return str; 1273 } 1274 str += createTime(dateTime.time(), version); 1275 if (dateTime.timeSpec() == Qt::UTC) { 1276 str += QLatin1Char('Z'); 1277 } else if (dateTime.timeSpec() == Qt::OffsetFromUTC) { 1278 const int offsetSecs = dateTime.offsetFromUtc(); 1279 if (offsetSecs >= 0) { 1280 str += QLatin1Char('+'); 1281 } else { 1282 str += QLatin1Char('-'); 1283 } 1284 QTime offsetTime = QTime(0, 0).addSecs(abs(offsetSecs)); 1285 if (version == VCard::v4_0) { 1286 str += offsetTime.toString(QStringLiteral("HHmm")); 1287 } else { 1288 str += offsetTime.toString(QStringLiteral("HH:mm")); 1289 } 1290 } 1291 return str; 1292 } 1293 1294 QString VCardTool::createDate(const QDate &date, VCard::Version version) 1295 { 1296 QString format; 1297 if (date.year() > 0) { 1298 format = QStringLiteral("yyyyMMdd"); 1299 } else { 1300 format = QStringLiteral("--MMdd"); 1301 } 1302 if (version != VCard::v4_0) { 1303 format.replace(QStringLiteral("yyyy"), QStringLiteral("yyyy-")); 1304 format.replace(QStringLiteral("MM"), QStringLiteral("MM-")); 1305 } 1306 return date.toString(format); 1307 } 1308 1309 QString VCardTool::createTime(const QTime &time, VCard::Version version) 1310 { 1311 QString format; 1312 if (version == VCard::v4_0) { 1313 format = QStringLiteral("HHmmss"); 1314 } else { 1315 format = QStringLiteral("HH:mm:ss"); 1316 } 1317 return QLatin1Char('T') + time.toString(format); 1318 } 1319 1320 Picture VCardTool::parsePicture(const VCardLine &line) const 1321 { 1322 Picture pic; 1323 1324 const QStringList params = line.parameterList(); 1325 QString type; 1326 if (params.contains(QLatin1String("type"))) { 1327 type = line.parameter(QStringLiteral("type")); 1328 } 1329 if (params.contains(QLatin1String("encoding"))) { 1330 pic.setRawData(line.value().toByteArray(), type); 1331 } else if (params.contains(QLatin1String("value"))) { 1332 if (line.parameter(QStringLiteral("value")).toLower() == QLatin1String("uri")) { 1333 pic.setUrl(line.value().toString()); 1334 } 1335 } 1336 1337 return pic; 1338 } 1339 1340 VCardLine VCardTool::createPicture(const QString &identifier, const Picture &pic, VCard::Version version) const 1341 { 1342 VCardLine line(identifier); 1343 1344 if (pic.isEmpty()) { 1345 return line; 1346 } 1347 1348 if (pic.isIntern()) { 1349 line.setValue(pic.rawData()); 1350 if (version == VCard::v2_1) { 1351 line.addParameter(QStringLiteral("ENCODING"), QStringLiteral("BASE64")); 1352 line.addParameter(pic.type(), QString()); 1353 } else { /*if (version == VCard::v3_0) */ 1354 line.addParameter(QStringLiteral("encoding"), QStringLiteral("b")); 1355 line.addParameter(QStringLiteral("type"), pic.type()); 1356 #if 0 1357 } else { //version 4.0 1358 line.addParameter(QStringLiteral("data") + QStringLiteral(":image/") + pic.type(), QStringLiteral("base64")); 1359 #endif 1360 } 1361 } else { 1362 line.setValue(pic.url()); 1363 line.addParameter(QStringLiteral("value"), QStringLiteral("URI")); 1364 } 1365 1366 return line; 1367 } 1368 1369 Sound VCardTool::parseSound(const VCardLine &line) const 1370 { 1371 Sound snd; 1372 1373 const QStringList params = line.parameterList(); 1374 if (params.contains(QLatin1String("encoding"))) { 1375 snd.setData(line.value().toByteArray()); 1376 } else if (params.contains(QLatin1String("value"))) { 1377 if (line.parameter(QStringLiteral("value")).toLower() == QLatin1String("uri")) { 1378 snd.setUrl(line.value().toString()); 1379 } 1380 } 1381 1382 /* TODO: support sound types 1383 if ( params.contains( "type" ) ) 1384 snd.setType( line.parameter( "type" ) ); 1385 */ 1386 1387 return snd; 1388 } 1389 1390 VCardLine VCardTool::createSound(const Sound &snd, VCard::Version version) const 1391 { 1392 Q_UNUSED(version); 1393 VCardLine line(QStringLiteral("SOUND")); 1394 1395 if (snd.isIntern()) { 1396 if (!snd.data().isEmpty()) { 1397 line.setValue(snd.data()); 1398 if (version == VCard::v2_1) { 1399 line.addParameter(QStringLiteral("ENCODING"), QStringLiteral("BASE64")); 1400 } else { 1401 line.addParameter(QStringLiteral("encoding"), QStringLiteral("b")); 1402 } 1403 // TODO: need to store sound type!!! 1404 } 1405 } else if (!snd.url().isEmpty()) { 1406 line.setValue(snd.url()); 1407 line.addParameter(QStringLiteral("value"), QStringLiteral("URI")); 1408 } 1409 1410 return line; 1411 } 1412 1413 Key VCardTool::parseKey(const VCardLine &line) const 1414 { 1415 Key key; 1416 1417 const QStringList params = line.parameterList(); 1418 if (params.contains(QLatin1String("encoding"))) { 1419 key.setBinaryData(line.value().toByteArray()); 1420 } else { 1421 key.setTextData(line.value().toString()); 1422 } 1423 1424 if (params.contains(QLatin1String("type"))) { 1425 if (line.parameter(QStringLiteral("type")).toLower() == QLatin1String("x509")) { 1426 key.setType(Key::X509); 1427 } else if (line.parameter(QStringLiteral("type")).toLower() == QLatin1String("pgp")) { 1428 key.setType(Key::PGP); 1429 } else { 1430 key.setType(Key::Custom); 1431 key.setCustomTypeString(line.parameter(QStringLiteral("type"))); 1432 } 1433 } else if (params.contains(QLatin1String("mediatype"))) { 1434 const QString param = line.parameter(QStringLiteral("mediatype")).toLower(); 1435 if (param == QLatin1String("application/x-x509-ca-cert")) { 1436 key.setType(Key::X509); 1437 } else if (param == QLatin1String("application/pgp-keys")) { 1438 key.setType(Key::PGP); 1439 } else { 1440 key.setType(Key::Custom); 1441 key.setCustomTypeString(line.parameter(QStringLiteral("type"))); 1442 } 1443 } 1444 1445 return key; 1446 } 1447 1448 VCardLine VCardTool::createKey(const Key &key, VCard::Version version) const 1449 { 1450 VCardLine line(QStringLiteral("KEY")); 1451 1452 if (key.isBinary()) { 1453 if (!key.binaryData().isEmpty()) { 1454 line.setValue(key.binaryData()); 1455 if (version == VCard::v2_1) { 1456 line.addParameter(QStringLiteral("ENCODING"), QStringLiteral("BASE64")); 1457 } else { 1458 line.addParameter(QStringLiteral("encoding"), QStringLiteral("b")); 1459 } 1460 } 1461 } else if (!key.textData().isEmpty()) { 1462 line.setValue(key.textData()); 1463 } 1464 1465 if (version == VCard::v4_0) { 1466 if (key.type() == Key::X509) { 1467 line.addParameter(QStringLiteral("MEDIATYPE"), QStringLiteral("application/x-x509-ca-cert")); 1468 } else if (key.type() == Key::PGP) { 1469 line.addParameter(QStringLiteral("MEDIATYPE"), QStringLiteral("application/pgp-keys")); 1470 } else if (key.type() == Key::Custom) { 1471 line.addParameter(QStringLiteral("MEDIATYPE"), key.customTypeString()); 1472 } 1473 } else { 1474 if (key.type() == Key::X509) { 1475 line.addParameter(QStringLiteral("type"), QStringLiteral("X509")); 1476 } else if (key.type() == Key::PGP) { 1477 line.addParameter(QStringLiteral("type"), QStringLiteral("PGP")); 1478 } else if (key.type() == Key::Custom) { 1479 line.addParameter(QStringLiteral("type"), key.customTypeString()); 1480 } 1481 } 1482 1483 return line; 1484 } 1485 1486 Secrecy VCardTool::parseSecrecy(const VCardLine &line) const 1487 { 1488 Secrecy secrecy; 1489 1490 const QString value = line.value().toString().toLower(); 1491 if (value == QLatin1String("public")) { 1492 secrecy.setType(Secrecy::Public); 1493 } else if (value == QLatin1String("private")) { 1494 secrecy.setType(Secrecy::Private); 1495 } else if (value == QLatin1String("confidential")) { 1496 secrecy.setType(Secrecy::Confidential); 1497 } 1498 1499 return secrecy; 1500 } 1501 1502 VCardLine VCardTool::createSecrecy(const Secrecy &secrecy) const 1503 { 1504 VCardLine line(QStringLiteral("CLASS")); 1505 1506 int type = secrecy.type(); 1507 1508 if (type == Secrecy::Public) { 1509 line.setValue(QStringLiteral("PUBLIC")); 1510 } else if (type == Secrecy::Private) { 1511 line.setValue(QStringLiteral("PRIVATE")); 1512 } else if (type == Secrecy::Confidential) { 1513 line.setValue(QStringLiteral("CONFIDENTIAL")); 1514 } 1515 1516 return line; 1517 } 1518 1519 QStringList VCardTool::splitString(QChar sep, const QString &str) const 1520 { 1521 QStringList list; 1522 QString value(str); 1523 1524 int start = 0; 1525 int pos = value.indexOf(sep, start); 1526 1527 while (pos != -1) { 1528 if (pos == 0 || value[pos - 1] != QLatin1Char('\\')) { 1529 if (pos > start && pos <= value.length()) { 1530 list << value.mid(start, pos - start); 1531 } else { 1532 list << QString(); 1533 } 1534 1535 start = pos + 1; 1536 pos = value.indexOf(sep, start); 1537 } else { 1538 value.replace(pos - 1, 2, sep); 1539 pos = value.indexOf(sep, pos); 1540 } 1541 } 1542 1543 int l = value.length() - 1; 1544 const QString mid = value.mid(start, l - start + 1); 1545 if (!mid.isEmpty()) { 1546 list << mid; 1547 } else { 1548 list << QString(); 1549 } 1550 1551 return list; 1552 } 1553 1554 QString VCardTool::normalizeImppServiceType(const QString &serviceType) const 1555 { 1556 if (serviceType == QLatin1String("jabber")) { 1557 return QStringLiteral("xmpp"); 1558 } 1559 if (serviceType == QLatin1String("yahoo")) { 1560 return QStringLiteral("ymsgr"); 1561 } 1562 if (serviceType == QLatin1String("gadugadu")) { 1563 return QStringLiteral("gg"); 1564 } 1565 return serviceType; 1566 }