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