1/*
2 This file is part of the KContacts framework.
3 SPDX-FileCopyrightText: 2003 Tobias Koenig <tokoe@kde.org>
4 SPDX-FileCopyrightText: 2015-2019 Laurent Montel <montel@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "fieldgroup.h"
10#include "gender.h"
11#include "kcontacts_debug.h"
12#include "key.h"
13#include "lang.h"
14#include "picture.h"
15#include "related.h"
16#include "secrecy.h"
17#include "sound.h"
18#include "vcardtool_p.h"
19
20#include <QString>
21#include <QTimeZone>
22
23using namespace KContacts;
24
25static bool needsEncoding(const QString &value)
26{
27 int length = value.length();
28 for (int i = 0; i < length; ++i) {
29 char c = value.at(i).toLatin1();
30 if ((c < 33 || c > 126) && c != ' ' && c != '=') {
31 return true;
32 }
33 }
34
35 return false;
36}
37
38struct AddressTypeInfo {
39 const char *addressType;
40 Address::TypeFlag flag;
41};
42
43static const AddressTypeInfo s_addressTypes[] = {
44 {.addressType: "dom", .flag: Address::Dom},
45 {.addressType: "home", .flag: Address::Home},
46 {.addressType: "intl", .flag: Address::Intl},
47 {.addressType: "parcel", .flag: Address::Parcel},
48 {.addressType: "postal", .flag: Address::Postal},
49 {.addressType: "pref", .flag: Address::Pref},
50 {.addressType: "work", .flag: Address::Work},
51};
52
53static Address::TypeFlag stringToAddressType(const QString &str)
54{
55 auto it = std::find_if(first: std::begin(arr: s_addressTypes), last: std::end(arr: s_addressTypes), pred: [&str](const AddressTypeInfo &info) {
56 return str == QLatin1String(info.addressType);
57 });
58 return it != std::end(arr: s_addressTypes) ? it->flag : Address::TypeFlag{};
59}
60
61struct PhoneTypeInfo {
62 const char *phoneType;
63 PhoneNumber::TypeFlag flag;
64};
65
66static const PhoneTypeInfo s_phoneTypes[] = {
67 {.phoneType: "BBS", .flag: PhoneNumber::Bbs},
68 {.phoneType: "CAR", .flag: PhoneNumber::Car},
69 {.phoneType: "CELL", .flag: PhoneNumber::Cell},
70 {.phoneType: "FAX", .flag: PhoneNumber::Fax},
71 {.phoneType: "HOME", .flag: PhoneNumber::Home},
72 {.phoneType: "ISDN", .flag: PhoneNumber::Isdn},
73 {.phoneType: "MODEM", .flag: PhoneNumber::Modem},
74 {.phoneType: "MSG", .flag: PhoneNumber::Msg},
75 {.phoneType: "PAGER", .flag: PhoneNumber::Pager},
76 {.phoneType: "PCS", .flag: PhoneNumber::Pcs},
77 {.phoneType: "PREF", .flag: PhoneNumber::Pref},
78 {.phoneType: "VIDEO", .flag: PhoneNumber::Video},
79 {.phoneType: "VOICE", .flag: PhoneNumber::Voice},
80 {.phoneType: "WORK", .flag: PhoneNumber::Work},
81};
82
83static PhoneNumber::TypeFlag stringToPhoneType(const QString &str)
84{
85 auto it = std::find_if(first: std::begin(arr: s_phoneTypes), last: std::end(arr: s_phoneTypes), pred: [&str](const PhoneTypeInfo &info) {
86 return str == QLatin1String(info.phoneType);
87 });
88 return it != std::end(arr: s_phoneTypes) ? it->flag : PhoneNumber::TypeFlag{};
89}
90
91VCardTool::VCardTool() = default;
92
93VCardTool::~VCardTool() = default;
94
95QByteArray VCardTool::exportVCards(const Addressee::List &list, VCard::Version version) const
96{
97 return createVCards(list, version, exportVcard: true /*export vcard*/);
98}
99
100QByteArray VCardTool::createVCards(const Addressee::List &list, VCard::Version version) const
101{
102 return createVCards(list, version, exportVcard: false /*don't export*/);
103}
104
105void VCardTool::addParameter(VCardLine *line, VCard::Version version, const QString &key, const QStringList &valueStringList) const
106{
107 if (version == VCard::v2_1) {
108 for (const QString &valueStr : valueStringList) {
109 line->addParameter(param: valueStr, value: QString());
110 }
111 } else if (version == VCard::v3_0) {
112 line->addParameter(param: key, value: valueStringList.join(sep: QLatin1Char(',')));
113 } else {
114 if (valueStringList.count() < 2) {
115 line->addParameter(param: key, value: valueStringList.join(sep: QLatin1Char(',')));
116 } else {
117 line->addParameter(param: key, value: QLatin1Char('"') + valueStringList.join(sep: QLatin1Char(',')) + QLatin1Char('"'));
118 }
119 }
120}
121
122void VCardTool::processAddresses(const Address::List &addresses, VCard::Version version, VCard *card) const
123{
124 for (const auto &addr : addresses) {
125 QStringList address;
126
127 // clang-format off
128 const bool isEmpty = addr.postOfficeBox().isEmpty()
129 && addr.extended().isEmpty()
130 && addr.street().isEmpty()
131 && addr.locality().isEmpty()
132 && addr.region().isEmpty()
133 && addr.postalCode().isEmpty()
134 && addr.country().isEmpty();
135 // clang-format on
136
137 address.append(t: addr.postOfficeBox().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
138 address.append(t: addr.extended().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
139 address.append(t: addr.street().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
140 address.append(t: addr.locality().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
141 address.append(t: addr.region().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
142 address.append(t: addr.postalCode().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
143 address.append(t: addr.country().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
144
145 const QString addressJoined(address.join(sep: QLatin1Char(';')));
146 VCardLine adrLine(QStringLiteral("ADR"), addressJoined);
147 if (version == VCard::v2_1 && needsEncoding(value: addressJoined)) {
148 adrLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
149 adrLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
150 }
151
152 const bool hasLabel = !addr.label().isEmpty();
153 QStringList addreLineType;
154 QStringList labelLineType;
155
156 for (const auto &info : s_addressTypes) {
157 if (info.flag & addr.type()) {
158 const QString str = QString::fromLatin1(ba: info.addressType);
159 addreLineType << str;
160 if (hasLabel) {
161 labelLineType << str;
162 }
163 }
164 }
165
166 if (hasLabel) {
167 if (version == VCard::v4_0) {
168 adrLine.addParameter(QStringLiteral("LABEL"), QStringLiteral("\"%1\"").arg(a: addr.label()));
169 } else {
170 VCardLine labelLine(QStringLiteral("LABEL"), addr.label());
171 if (version == VCard::v2_1 && needsEncoding(value: addr.label())) {
172 labelLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
173 labelLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
174 }
175 addParameter(line: &labelLine, version, QStringLiteral("TYPE"), valueStringList: labelLineType);
176 card->addLine(line: labelLine);
177 }
178 }
179 if (version == VCard::v4_0) {
180 Geo geo = addr.geo();
181 if (geo.isValid()) {
182 QString str = QString::asprintf(format: "\"geo:%.6f,%.6f\"", geo.latitude(), geo.longitude());
183 adrLine.addParameter(QStringLiteral("GEO"), value: str);
184 }
185 }
186 if (!isEmpty) {
187 addParameter(line: &adrLine, version, QStringLiteral("TYPE"), valueStringList: addreLineType);
188 card->addLine(line: adrLine);
189 }
190 }
191}
192
193void VCardTool::processEmailList(const Email::List &emailList, VCard::Version version, VCard *card) const
194{
195 for (const auto &email : emailList) {
196 VCardLine line(QStringLiteral("EMAIL"), email.mail());
197 const ParameterMap pMap = email.params();
198 for (const auto &[param, l] : pMap) {
199 QStringList list = l;
200 if (version == VCard::v2_1) {
201 if (param.toLower() == QLatin1String("type")) {
202 bool hasPreferred = false;
203 const int removeItems = list.removeAll(QStringLiteral("PREF"));
204 if (removeItems > 0) {
205 hasPreferred = true;
206 }
207 if (!list.isEmpty()) {
208 addParameter(line: &line, version, key: param, valueStringList: list);
209 }
210 if (hasPreferred) {
211 line.addParameter(QStringLiteral("PREF"), value: QString());
212 }
213 } else {
214 line.addParameter(param, value: list.join(sep: QLatin1Char(',')));
215 }
216 } else {
217 line.addParameter(param, value: list.join(sep: QLatin1Char(',')));
218 }
219 }
220 card->addLine(line);
221 }
222}
223
224void VCardTool::processOrganizations(const Addressee &addressee, VCard::Version version, VCard *card) const
225{
226 const QList<Org> lstOrg = addressee.extraOrganizationList();
227 for (const Org &org : lstOrg) {
228 QStringList organization{org.organization().replace(c: QLatin1Char(';'), after: QLatin1String("\\;"))};
229 if (!addressee.department().isEmpty()) {
230 organization.append(t: addressee.department().replace(c: QLatin1Char(';'), after: QLatin1String("\\;")));
231 }
232 const QString orgStr = organization.join(sep: QLatin1Char(';'));
233 VCardLine orgLine(QStringLiteral("ORG"), orgStr);
234 if (version == VCard::v2_1 && needsEncoding(value: orgStr)) {
235 orgLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
236 orgLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
237 }
238 orgLine.addParameters(params: org.params());
239 card->addLine(line: orgLine);
240 }
241}
242
243void VCardTool::processPhoneNumbers(const PhoneNumber::List &phoneNumbers, VCard::Version version, VCard *card) const
244{
245 for (const auto &phone : phoneNumbers) {
246 VCardLine line(QStringLiteral("TEL"), phone.number());
247 const ParameterMap paramsMap = phone.params();
248 for (const auto &[param, list] : paramsMap) {
249 if (param.toUpper() != QLatin1String("TYPE")) {
250 line.addParameter(param, value: list.join(sep: QLatin1Char(',')));
251 }
252 }
253
254 const PhoneNumber::Type type = phone.type();
255 QStringList lst;
256 for (const auto &pType : s_phoneTypes) {
257 if (pType.flag & type) {
258 const QString str = QString::fromLatin1(ba: pType.phoneType);
259 if (version == VCard::v4_0) {
260 lst << str.toLower();
261 } else {
262 lst << str;
263 }
264 }
265 }
266 if (!lst.isEmpty()) {
267 addParameter(line: &line, version, QStringLiteral("TYPE"), valueStringList: lst);
268 }
269 card->addLine(line);
270 }
271}
272
273void VCardTool::processCustoms(const QStringList &customs, VCard::Version version, VCard *card, bool exportVcard) const
274{
275 for (const auto &str : customs) {
276 QString identifier = QLatin1String("X-") + QStringView(str).left(n: str.indexOf(ch: QLatin1Char(':')));
277 const QString value = str.mid(position: str.indexOf(ch: QLatin1Char(':')) + 1);
278 if (value.isEmpty()) {
279 continue;
280 }
281 // Convert to standard identifier
282 if (exportVcard) {
283 if (identifier == QLatin1String("X-messaging/aim-All")) {
284 identifier = QStringLiteral("X-AIM");
285 } else if (identifier == QLatin1String("X-messaging/icq-All")) {
286 identifier = QStringLiteral("X-ICQ");
287 } else if (identifier == QLatin1String("X-messaging/xmpp-All")) {
288 identifier = QStringLiteral("X-JABBER");
289 } else if (identifier == QLatin1String("X-messaging/msn-All")) {
290 identifier = QStringLiteral("X-MSN");
291 } else if (identifier == QLatin1String("X-messaging/yahoo-All")) {
292 identifier = QStringLiteral("X-YAHOO");
293 } else if (identifier == QLatin1String("X-messaging/gadu-All")) {
294 identifier = QStringLiteral("X-GADUGADU");
295 } else if (identifier == QLatin1String("X-messaging/skype-All")) {
296 identifier = QStringLiteral("X-SKYPE");
297 } else if (identifier == QLatin1String("X-messaging/groupwise-All")) {
298 identifier = QStringLiteral("X-GROUPWISE");
299 } else if (identifier == QLatin1String("X-messaging/sms-All")) {
300 identifier = QStringLiteral("X-SMS");
301 } else if (identifier == QLatin1String("X-messaging/meanwhile-All")) {
302 identifier = QStringLiteral("X-MEANWHILE");
303 } else if (identifier == QLatin1String("X-messaging/irc-All")) {
304 identifier = QStringLiteral("X-IRC"); // Not defined by rfc but need for fixing #300869
305 } else if (identifier == QLatin1String("X-messaging/googletalk-All")) {
306 // Not defined by rfc but need for fixing #300869
307 identifier = QStringLiteral("X-GTALK");
308 } else if (identifier == QLatin1String("X-messaging/twitter-All")) {
309 identifier = QStringLiteral("X-TWITTER");
310 }
311 }
312
313 if (identifier.toLower() == QLatin1String("x-kaddressbook-x-anniversary") && version == VCard::v4_0) {
314 // ANNIVERSARY
315 const QDate date = QDate::fromString(string: value, format: Qt::ISODate);
316 QDateTime dt = QDateTime(date.startOfDay());
317 dt.setTime(time: QTime());
318 VCardLine line(QStringLiteral("ANNIVERSARY"));
319 line.addParameter(QStringLiteral("VALUE"), QStringLiteral("DATE"));
320 line.setValue(createDateTime(dateTime: dt, version, withTime: true));
321 card->addLine(line);
322 } else if (identifier.toLower() == QLatin1String("x-kaddressbook-x-spousesname") && version == VCard::v4_0) {
323 VCardLine line(QStringLiteral("RELATED"), QStringLiteral(";"));
324 line.addParameter(QStringLiteral("TYPE"), QStringLiteral("spouse"));
325 line.addParameter(QStringLiteral("VALUE"), value);
326 card->addLine(line);
327 } else {
328 VCardLine line(identifier, value);
329 if (version == VCard::v2_1 && needsEncoding(value)) {
330 line.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
331 line.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
332 }
333 card->addLine(line);
334 }
335 }
336}
337
338QByteArray VCardTool::createVCards(const Addressee::List &list, VCard::Version version, bool exportVcard) const
339{
340 VCard::List vCardList;
341
342 for (const auto &addressee : list) {
343 VCard card;
344 // VERSION
345 if (version == VCard::v2_1) {
346 card.addLine(line: VCardLine(QStringLiteral("VERSION"), QStringLiteral("2.1")));
347 } else if (version == VCard::v3_0) {
348 card.addLine(line: VCardLine(QStringLiteral("VERSION"), QStringLiteral("3.0")));
349 } else if (version == VCard::v4_0) {
350 card.addLine(line: VCardLine(QStringLiteral("VERSION"), QStringLiteral("4.0")));
351 }
352
353 // ADR + LABEL
354 const Address::List addresses = addressee.addresses();
355 processAddresses(addresses, version, card: &card);
356
357 // BDAY
358 if (version == VCard::v4_0) {
359 VCardLine line(QStringLiteral("BDAY"));
360 line.addParameter(QStringLiteral("VALUE"), QStringLiteral("DATE"));
361 const bool withTime = addressee.birthdayHasTime();
362 line.setValue(createDateTime(dateTime: addressee.birthday(), version, withTime));
363 card.addLine(line);
364 } else {
365 const bool withTime = addressee.birthdayHasTime();
366 const QString birthdayString = createDateTime(dateTime: addressee.birthday(), version, withTime);
367 card.addLine(line: VCardLine(QStringLiteral("BDAY"), birthdayString));
368 }
369 // DEATHDATE
370 if (version == VCard::v4_0) {
371 VCardLine line(QStringLiteral("DEATHDATE"));
372 line.setValue(createDateTime(dateTime: addressee.deathDate(), version));
373 card.addLine(line);
374 }
375 // CATEGORIES only > 2.1
376 if (version != VCard::v2_1) {
377 QStringList categories = addressee.categories();
378 for (auto &cat : categories) {
379 cat.replace(c: QLatin1Char(','), after: QLatin1String("\\,"));
380 }
381
382 VCardLine catLine(QStringLiteral("CATEGORIES"), categories.join(sep: QLatin1Char(',')));
383 card.addLine(line: catLine);
384 }
385 // MEMBER (only in 4.0)
386 if (version == VCard::v4_0) {
387 // The KIND property must be set to "group" in order to use this property.
388 if (addressee.kind().toLower() == QLatin1String("group")) {
389 const QStringList lst = addressee.members();
390 for (const QString &member : lst) {
391 card.addLine(line: VCardLine(QStringLiteral("MEMBER"), member));
392 }
393 }
394 }
395 // SOURCE
396 const QList<QUrl> lstUrl = addressee.sourcesUrlList();
397 for (const QUrl &url : lstUrl) {
398 VCardLine line = VCardLine(QStringLiteral("SOURCE"), url.url());
399 card.addLine(line);
400 }
401
402 const Related::List relatedList = addressee.relationships();
403 for (const auto &rel : relatedList) {
404 VCardLine line(QStringLiteral("RELATED"), rel.related());
405 line.addParameters(params: rel.params());
406 card.addLine(line);
407 }
408 // CLASS only for version == 3.0
409 if (version == VCard::v3_0) {
410 card.addLine(line: createSecrecy(secrecy: addressee.secrecy()));
411 }
412 // LANG only for version == 4.0
413 if (version == VCard::v4_0) {
414 const Lang::List langList = addressee.langs();
415 for (const auto &lang : langList) {
416 VCardLine line(QStringLiteral("LANG"), lang.language());
417 line.addParameters(params: lang.params());
418 card.addLine(line);
419 }
420 // CLIENTPIDMAP
421 if (version == VCard::v4_0) {
422 const ClientPidMap::List clientpidmapList = addressee.clientPidMapList();
423 for (const auto &pMap : clientpidmapList) {
424 VCardLine line(QStringLiteral("CLIENTPIDMAP"), pMap.clientPidMap());
425 line.addParameters(params: pMap.params());
426 card.addLine(line);
427 }
428 }
429 }
430 // EMAIL
431 const Email::List emailList = addressee.emailList();
432 processEmailList(emailList, version, card: &card);
433
434 // FN required for only version > 2.1
435 VCardLine fnLine(QStringLiteral("FN"), addressee.formattedName());
436 if (version == VCard::v2_1 && needsEncoding(value: addressee.formattedName())) {
437 fnLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
438 fnLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
439 }
440 card.addLine(line: fnLine);
441
442 // GEO
443 const Geo geo = addressee.geo();
444 if (geo.isValid()) {
445 QString str;
446 if (version == VCard::v4_0) {
447 str = QString::asprintf(format: "geo:%.6f,%.6f", geo.latitude(), geo.longitude());
448 } else {
449 str = QString::asprintf(format: "%.6f;%.6f", geo.latitude(), geo.longitude());
450 }
451 card.addLine(line: VCardLine(QStringLiteral("GEO"), str));
452 }
453
454 // KEY
455 const Key::List keys = addressee.keys();
456 for (const auto &k : keys) {
457 card.addLine(line: createKey(key: k, version));
458 }
459
460 // LOGO
461 if (version == VCard::v4_0) {
462 card.addLine(line: createPicturev4(QStringLiteral("LOGO"), pic: addressee.logo()));
463 const QList<Picture> = addressee.extraLogoList();
464 for (const Picture & : lstLogo) {
465 card.addLine(line: createPicturev4(QStringLiteral("LOGO"), pic: logo));
466 }
467 } else {
468 card.addLine(line: createPicture(QStringLiteral("LOGO"), pic: addressee.logo(), version));
469 const QList<Picture> = addressee.extraLogoList();
470 for (const Picture & : lstLogo) {
471 card.addLine(line: createPicture(QStringLiteral("LOGO"), pic: logo, version));
472 }
473 }
474
475 // MAILER only for version < 4.0
476 if (version != VCard::v4_0) {
477 VCardLine mailerLine(QStringLiteral("MAILER"), addressee.mailer());
478 if (version == VCard::v2_1 && needsEncoding(value: addressee.mailer())) {
479 mailerLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
480 mailerLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
481 }
482 card.addLine(line: mailerLine);
483 }
484
485 // N required for only version < 4.0
486 QStringList name;
487 name.append(t: addressee.familyName().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
488 name.append(t: addressee.givenName().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
489 name.append(t: addressee.additionalName().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
490 name.append(t: addressee.prefix().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
491 name.append(t: addressee.suffix().replace(c: QLatin1Char(';'), QStringLiteral("\\;")));
492
493 VCardLine nLine(QStringLiteral("N"), name.join(sep: QLatin1Char(';')));
494 if (version == VCard::v2_1 && needsEncoding(value: name.join(sep: QLatin1Char(';')))) {
495 nLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
496 nLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
497 }
498 if (version == VCard::v4_0 && !addressee.sortString().isEmpty()) {
499 nLine.addParameter(QStringLiteral("SORT-AS"), value: addressee.sortString());
500 }
501
502 card.addLine(line: nLine);
503
504 // NAME only for version < 4.0
505 if (version != VCard::v4_0) {
506 VCardLine nameLine(QStringLiteral("NAME"), addressee.name());
507 if (version == VCard::v2_1 && needsEncoding(value: addressee.name())) {
508 nameLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
509 nameLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
510 }
511 card.addLine(line: nameLine);
512 }
513
514 // NICKNAME only for version > 2.1
515 if (version != VCard::v2_1) {
516 const QList<NickName> lstNickName = addressee.extraNickNameList();
517 for (const NickName &nickName : lstNickName) {
518 VCardLine nickNameLine(QStringLiteral("NICKNAME"), nickName.nickname());
519 nickNameLine.addParameters(params: nickName.params());
520
521 card.addLine(line: nickNameLine);
522 }
523 }
524
525 // NOTE
526 VCardLine noteLine(QStringLiteral("NOTE"), addressee.note());
527 if (version == VCard::v2_1 && needsEncoding(value: addressee.note())) {
528 noteLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
529 noteLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
530 }
531 card.addLine(line: noteLine);
532
533 // ORG
534 processOrganizations(addressee, version, card: &card);
535
536 // PHOTO
537 if (version == VCard::v4_0) {
538 card.addLine(line: createPicturev4(QStringLiteral("PHOTO"), pic: addressee.photo()));
539 const QList<Picture> lstExtraPhoto = addressee.extraPhotoList();
540 for (const Picture &photo : lstExtraPhoto) {
541 card.addLine(line: createPicturev4(QStringLiteral("PHOTO"), pic: photo));
542 }
543 } else {
544 card.addLine(line: createPicture(QStringLiteral("PHOTO"), pic: addressee.photo(), version));
545 const QList<Picture> lstExtraPhoto = addressee.extraPhotoList();
546 for (const Picture &photo : lstExtraPhoto) {
547 card.addLine(line: createPicture(QStringLiteral("PHOTO"), pic: photo, version));
548 }
549 }
550
551 // PROID only for version > 2.1
552 if (version != VCard::v2_1) {
553 card.addLine(line: VCardLine(QStringLiteral("PRODID"), addressee.productId()));
554 }
555
556 // REV
557 card.addLine(line: VCardLine(QStringLiteral("REV"), createDateTime(dateTime: addressee.revision(), version)));
558
559 // ROLE
560 const QList<Role> lstExtraRole = addressee.extraRoleList();
561 for (const Role &role : lstExtraRole) {
562 VCardLine roleLine(QStringLiteral("ROLE"), role.role());
563 if (version == VCard::v2_1 && needsEncoding(value: role.role())) {
564 roleLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
565 roleLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
566 }
567 roleLine.addParameters(params: role.params());
568 card.addLine(line: roleLine);
569 }
570
571 // SORT-STRING
572 if (version == VCard::v3_0) {
573 card.addLine(line: VCardLine(QStringLiteral("SORT-STRING"), addressee.sortString()));
574 }
575
576 // SOUND
577 if (version == VCard::v4_0) {
578 // TODO
579 } else {
580 card.addLine(line: createSound(snd: addressee.sound(), version));
581 const QList<Sound> lstSound = addressee.extraSoundList();
582 for (const Sound &sound : lstSound) {
583 card.addLine(line: createSound(snd: sound, version));
584 }
585 }
586
587 // TEL
588 const PhoneNumber::List phoneNumbers = addressee.phoneNumbers();
589 processPhoneNumbers(phoneNumbers, version, card: &card);
590
591 // TITLE
592 const QList<Title> lstTitle = addressee.extraTitleList();
593 for (const Title &title : lstTitle) {
594 VCardLine titleLine(QStringLiteral("TITLE"), title.title());
595 if (version == VCard::v2_1 && needsEncoding(value: title.title())) {
596 titleLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
597 titleLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
598 }
599 titleLine.addParameters(params: title.params());
600
601 card.addLine(line: titleLine);
602 }
603
604 // TZ
605 // TODO Add vcard4.0 support
606 const TimeZone timeZone = addressee.timeZone();
607 if (timeZone.isValid()) {
608 int neg = 1;
609 if (timeZone.offset() < 0) {
610 neg = -1;
611 }
612
613 QString str =
614 QString::asprintf(format: "%c%02d:%02d", (timeZone.offset() >= 0 ? '+' : '-'), (timeZone.offset() / 60) * neg, (timeZone.offset() % 60) * neg);
615
616 card.addLine(line: VCardLine(QStringLiteral("TZ"), str));
617 }
618
619 // UID
620 card.addLine(line: VCardLine(QStringLiteral("UID"), addressee.uid()));
621
622 // URL
623 const QList<ResourceLocatorUrl> lstExtraUrl = addressee.extraUrlList();
624 for (const ResourceLocatorUrl &url : lstExtraUrl) {
625 VCardLine line(QStringLiteral("URL"), url.url());
626 line.addParameters(params: url.params());
627 card.addLine(line);
628 }
629 if (version == VCard::v4_0) {
630 // GENDER
631 const Gender gender = addressee.gender();
632 if (gender.isValid()) {
633 QString genderStr;
634 if (!gender.gender().isEmpty()) {
635 genderStr = gender.gender();
636 }
637 if (!gender.comment().isEmpty()) {
638 genderStr += QLatin1Char(';') + gender.comment();
639 }
640 VCardLine line(QStringLiteral("GENDER"), genderStr);
641 card.addLine(line);
642 }
643 // KIND
644 if (!addressee.kind().isEmpty()) {
645 VCardLine line(QStringLiteral("KIND"), addressee.kind());
646 card.addLine(line);
647 }
648 const QList<CalendarUrl> lstCalendarUrl = addressee.calendarUrlList();
649 for (const CalendarUrl &url : lstCalendarUrl) {
650 if (url.isValid()) {
651 QString type;
652 switch (url.type()) {
653 case CalendarUrl::Unknown:
654 case CalendarUrl::EndCalendarType:
655 break;
656 case CalendarUrl::FBUrl:
657 type = QStringLiteral("FBURL");
658 break;
659 case CalendarUrl::CALUri:
660 type = QStringLiteral("CALURI");
661 break;
662 case CalendarUrl::CALADRUri:
663 type = QStringLiteral("CALADRURI");
664 break;
665 }
666 if (!type.isEmpty()) {
667 VCardLine line(type, url.url().toDisplayString());
668 line.addParameters(params: url.params());
669 card.addLine(line);
670 }
671 }
672 }
673 }
674
675 // FieldGroup
676 const QList<FieldGroup> lstGroup = addressee.fieldGroupList();
677 for (const FieldGroup &group : lstGroup) {
678 VCardLine line(group.fieldGroupName(), group.value());
679 line.addParameters(params: group.params());
680 card.addLine(line);
681 }
682
683 // IMPP (supported in vcard 3 too)
684 const QList<Impp> lstImpp = addressee.imppList();
685 for (const Impp &impp : lstImpp) {
686 VCardLine line(QStringLiteral("IMPP"), impp.address().url());
687 const ParameterMap pMap = impp.params();
688 for (const auto &[param, list] : pMap) {
689 if (param.toLower() != QLatin1String("x-service-type")) {
690 line.addParameter(param, value: list.join(sep: QLatin1Char(',')));
691 }
692 }
693 card.addLine(line);
694 }
695
696 // X-
697 const QStringList customs = addressee.customs();
698 processCustoms(customs, version, card: &card, exportVcard);
699
700 vCardList.append(t: card);
701 }
702
703 return VCardParser::createVCards(list: vCardList);
704}
705
706Addressee::List VCardTool::parseVCards(const QByteArray &vcard) const
707{
708 static const QLatin1Char semicolonSep(';');
709 static const QLatin1Char commaSep(',');
710 QString identifier;
711 QString group;
712 Addressee::List addrList;
713 const VCard::List vCardList = VCardParser::parseVCards(text: vcard);
714
715 VCard::List::ConstIterator cardIt;
716 VCard::List::ConstIterator listEnd(vCardList.end());
717 for (cardIt = vCardList.begin(); cardIt != listEnd; ++cardIt) {
718 Addressee addr;
719
720 const QStringList idents = (*cardIt).identifiers();
721 QStringList::ConstIterator identIt;
722 QStringList::ConstIterator identEnd(idents.end());
723 for (identIt = idents.begin(); identIt != identEnd; ++identIt) {
724 const VCardLine::List lines = (*cardIt).lines(identifier: (*identIt));
725 VCardLine::List::ConstIterator lineIt;
726
727 // iterate over the lines
728 for (lineIt = lines.begin(); lineIt != lines.end(); ++lineIt) {
729 identifier = (*lineIt).identifier().toLower();
730 group = (*lineIt).group();
731 if (!group.isEmpty() && identifier != QLatin1String("adr")) {
732 KContacts::FieldGroup groupField(group + QLatin1Char('.') + (*lineIt).identifier());
733 groupField.setParams((*lineIt).parameterMap());
734 groupField.setValue((*lineIt).value().toString());
735 addr.insertFieldGroup(fieldGroup: groupField);
736 }
737 // ADR
738 else if (identifier == QLatin1String("adr")) {
739 Address address;
740 const QStringList addrParts = splitString(sep: semicolonSep, value: (*lineIt).value().toString());
741 const int addrPartsCount(addrParts.count());
742 if (addrPartsCount > 0) {
743 address.setPostOfficeBox(addrParts.at(i: 0));
744 }
745 if (addrPartsCount > 1) {
746 address.setExtended(addrParts.at(i: 1));
747 }
748 if (addrPartsCount > 2) {
749 address.setStreet(addrParts.at(i: 2));
750 }
751 if (addrPartsCount > 3) {
752 address.setLocality(addrParts.at(i: 3));
753 }
754 if (addrPartsCount > 4) {
755 address.setRegion(addrParts.at(i: 4));
756 }
757 if (addrPartsCount > 5) {
758 address.setPostalCode(addrParts.at(i: 5));
759 }
760 if (addrPartsCount > 6) {
761 address.setCountry(addrParts.at(i: 6));
762 }
763
764 Address::Type type;
765
766 const QStringList types = (*lineIt).parameters(QStringLiteral("type"));
767 QStringList::ConstIterator end(types.end());
768 for (QStringList::ConstIterator it = types.begin(); it != end; ++it) {
769 type |= stringToAddressType(str: (*it).toLower());
770 }
771
772 address.setType(type);
773 QString label = (*lineIt).parameter(QStringLiteral("label"));
774 if (!label.isEmpty()) {
775 if (label.length() > 1) {
776 if (label.at(i: 0) == QLatin1Char('"') && label.at(i: label.length() - 1) == QLatin1Char('"')) {
777 label = label.mid(position: 1, n: label.length() - 2);
778 }
779 }
780 address.setLabel(label);
781 }
782 QString geoStr = (*lineIt).parameter(QStringLiteral("geo"));
783 if (!geoStr.isEmpty()) {
784 geoStr.remove(c: QLatin1Char('\"'));
785 geoStr.remove(QStringLiteral("geo:"));
786 if (geoStr.contains(c: QLatin1Char(','))) {
787 QStringList arguments = geoStr.split(sep: QLatin1Char(','));
788 KContacts::Geo geo;
789 geo.setLatitude(arguments.at(i: 0).toDouble());
790 geo.setLongitude(arguments.at(i: 1).toDouble());
791 address.setGeo(geo);
792 }
793 }
794 addr.insertAddress(address);
795 }
796 // ANNIVERSARY
797 else if (identifier == QLatin1String("anniversary")) {
798 const QString t = (*lineIt).value().toString();
799 const QDateTime dt(parseDateTime(str: t));
800 addr.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Anniversary"), value: dt.date().toString(format: Qt::ISODate));
801 }
802 // BDAY
803 else if (identifier == QLatin1String("bday")) {
804 bool withTime;
805 const QDateTime bday = parseDateTime(str: (*lineIt).value().toString(), timeValid: &withTime);
806 addr.setBirthday(birthday: bday, withTime);
807 }
808 // DEATHDATE
809 else if (identifier == QLatin1String("deathdate")) {
810 const QDateTime deathDate = parseDateTime(str: (*lineIt).value().toString());
811 addr.setDeathDate(deathDate);
812 }
813 // CATEGORIES
814 else if (identifier == QLatin1String("categories")) {
815 const QStringList categories = splitString(sep: commaSep, value: (*lineIt).value().toString());
816 addr.setCategories(categories);
817 }
818 // FBURL
819 else if (identifier == QLatin1String("fburl")) {
820 CalendarUrl calurl;
821 calurl.setType(CalendarUrl::FBUrl);
822 const QUrl url = QUrl((*lineIt).value().toString());
823 calurl.setUrl(url);
824 calurl.setParams((*lineIt).parameterMap());
825 addr.insertCalendarUrl(calendarUrl: calurl);
826 }
827 // CALADRURI
828 else if (identifier == QLatin1String("caladruri")) {
829 CalendarUrl calurl;
830 calurl.setType(CalendarUrl::CALADRUri);
831 const QUrl url = QUrl((*lineIt).value().toString());
832 calurl.setUrl(url);
833 calurl.setParams((*lineIt).parameterMap());
834 addr.insertCalendarUrl(calendarUrl: calurl);
835 }
836 // CALURI
837 else if (identifier == QLatin1String("caluri")) {
838 CalendarUrl calurl;
839 calurl.setType(CalendarUrl::CALUri);
840 const QUrl url = QUrl((*lineIt).value().toString());
841 calurl.setUrl(url);
842 calurl.setParams((*lineIt).parameterMap());
843 addr.insertCalendarUrl(calendarUrl: calurl);
844 }
845 // IMPP
846 else if (identifier == QLatin1String("impp")) {
847 QUrl imppUrl((*lineIt).value().toString());
848 Impp impp;
849 impp.setParams((*lineIt).parameterMap());
850 if (!(*lineIt).parameter(QStringLiteral("x-service-type")).isEmpty() && imppUrl.scheme().isEmpty()) {
851 imppUrl.setScheme(normalizeImppServiceType(serviceType: (*lineIt).parameter(QStringLiteral("x-service-type")).toLower()));
852 }
853 impp.setAddress(imppUrl);
854 addr.insertImpp(impp);
855 }
856 // CLASS
857 else if (identifier == QLatin1String("class")) {
858 addr.setSecrecy(parseSecrecy(line: *lineIt));
859 }
860 // GENDER
861 else if (identifier == QLatin1String("gender")) {
862 QString genderStr = (*lineIt).value().toString();
863 if (!genderStr.isEmpty()) {
864 Gender gender;
865 if (genderStr.at(i: 0) != QLatin1Char(';')) {
866 gender.setGender(genderStr.at(i: 0));
867 if (genderStr.length() > 2 && (genderStr.at(i: 1) == QLatin1Char(';'))) {
868 gender.setComment(genderStr.right(n: genderStr.length() - 2));
869 }
870 } else {
871 gender.setComment(genderStr.right(n: genderStr.length() - 1));
872 }
873 addr.setGender(gender);
874 }
875 }
876 // LANG
877 else if (identifier == QLatin1String("lang")) {
878 Lang lang;
879 lang.setLanguage((*lineIt).value().toString());
880 lang.setParams((*lineIt).parameterMap());
881 addr.insertLang(language: lang);
882 }
883 // EMAIL
884 else if (identifier == QLatin1String("email")) {
885 Email mail((*lineIt).value().toString());
886 mail.setParams((*lineIt).parameterMap());
887 addr.addEmail(email: mail);
888 }
889 // KIND
890 else if (identifier == QLatin1String("kind")) {
891 addr.setKind((*lineIt).value().toString());
892 }
893 // FN
894 else if (identifier == QLatin1String("fn")) {
895 addr.setFormattedName((*lineIt).value().toString());
896 }
897 // GEO
898 else if (identifier == QLatin1String("geo")) {
899 Geo geo;
900 QString lineStr = (*lineIt).value().toString();
901 if (lineStr.startsWith(s: QLatin1String("geo:"))) { // VCard 4.0
902 lineStr.remove(QStringLiteral("geo:"));
903 const QStringList geoParts = lineStr.split(sep: QLatin1Char(','), behavior: Qt::KeepEmptyParts);
904 if (geoParts.size() >= 2) {
905 geo.setLatitude(geoParts.at(i: 0).toFloat());
906 geo.setLongitude(geoParts.at(i: 1).toFloat());
907 addr.setGeo(geo);
908 }
909 } else {
910 const QStringList geoParts = lineStr.split(sep: QLatin1Char(';'), behavior: Qt::KeepEmptyParts);
911 if (geoParts.size() >= 2) {
912 geo.setLatitude(geoParts.at(i: 0).toFloat());
913 geo.setLongitude(geoParts.at(i: 1).toFloat());
914 addr.setGeo(geo);
915 }
916 }
917 }
918 // KEY
919 else if (identifier == QLatin1String("key")) {
920 addr.insertKey(key: parseKey(line: *lineIt));
921 }
922 // LABEL
923 else if (identifier == QLatin1String("label")) {
924 Address::Type type;
925
926 const QStringList types = (*lineIt).parameters(QStringLiteral("type"));
927 QStringList::ConstIterator end(types.end());
928 for (QStringList::ConstIterator it = types.begin(); it != end; ++it) {
929 type |= stringToAddressType(str: (*it).toLower());
930 }
931
932 bool available = false;
933 KContacts::Address::List addressList = addr.addresses();
934 for (KContacts::Address::List::Iterator it = addressList.begin(); it != addressList.end(); ++it) {
935 if ((*it).type() == type) {
936 (*it).setLabel((*lineIt).value().toString());
937 addr.insertAddress(address: *it);
938 available = true;
939 break;
940 }
941 }
942
943 if (!available) { // a standalone LABEL tag
944 KContacts::Address address(type);
945 address.setLabel((*lineIt).value().toString());
946 addr.insertAddress(address);
947 }
948 }
949 // LOGO
950 else if (identifier == QLatin1String("logo")) {
951 Picture picture = parsePicture(line: *lineIt);
952 if (addr.logo().isEmpty()) {
953 addr.setLogo(picture);
954 } else {
955 addr.insertExtraLogo(logo: picture);
956 }
957 }
958 // MAILER
959 else if (identifier == QLatin1String("mailer")) {
960 addr.setMailer((*lineIt).value().toString());
961 }
962 // N
963 else if (identifier == QLatin1Char('n')) {
964 const QStringList nameParts = splitString(sep: semicolonSep, value: (*lineIt).value().toString());
965 const int numberOfParts(nameParts.count());
966 if (numberOfParts > 0) {
967 addr.setFamilyName(nameParts.at(i: 0));
968 }
969 if (numberOfParts > 1) {
970 addr.setGivenName(nameParts.at(i: 1));
971 }
972 if (numberOfParts > 2) {
973 addr.setAdditionalName(nameParts.at(i: 2));
974 }
975 if (numberOfParts > 3) {
976 addr.setPrefix(nameParts.at(i: 3));
977 }
978 if (numberOfParts > 4) {
979 addr.setSuffix(nameParts.at(i: 4));
980 }
981 if (!(*lineIt).parameter(QStringLiteral("sort-as")).isEmpty()) {
982 addr.setSortString((*lineIt).parameter(QStringLiteral("sort-as")));
983 }
984 }
985 // NAME
986 else if (identifier == QLatin1String("name")) {
987 addr.setName((*lineIt).value().toString());
988 }
989 // NICKNAME
990 else if (identifier == QLatin1String("nickname")) {
991 NickName nickName((*lineIt).value().toString());
992 nickName.setParams((*lineIt).parameterMap());
993 addr.insertExtraNickName(nickName);
994 }
995 // NOTE
996 else if (identifier == QLatin1String("note")) {
997 addr.setNote((*lineIt).value().toString());
998 }
999 // ORGANIZATION
1000 else if (identifier == QLatin1String("org")) {
1001 const QStringList orgParts = splitString(sep: semicolonSep, value: (*lineIt).value().toString());
1002 const int orgPartsCount(orgParts.count());
1003 if (orgPartsCount > 0) {
1004 Org organization(orgParts.at(i: 0));
1005 organization.setParams((*lineIt).parameterMap());
1006 addr.insertExtraOrganization(organization);
1007 }
1008 if (orgPartsCount > 1) {
1009 addr.setDepartment(orgParts.at(i: 1));
1010 }
1011 if (!(*lineIt).parameter(QStringLiteral("sort-as")).isEmpty()) {
1012 addr.setSortString((*lineIt).parameter(QStringLiteral("sort-as")));
1013 }
1014 }
1015 // PHOTO
1016 else if (identifier == QLatin1String("photo")) {
1017 const Picture picture = parsePicture(line: *lineIt);
1018 if (addr.photo().isEmpty()) {
1019 addr.setPhoto(picture);
1020 } else {
1021 addr.insertExtraPhoto(picture);
1022 }
1023 }
1024 // PROID
1025 else if (identifier == QLatin1String("prodid")) {
1026 addr.setProductId((*lineIt).value().toString());
1027 }
1028 // REV
1029 else if (identifier == QLatin1String("rev")) {
1030 addr.setRevision(parseDateTime(str: (*lineIt).value().toString()));
1031 }
1032 // ROLE
1033 else if (identifier == QLatin1String("role")) {
1034 Role role((*lineIt).value().toString());
1035 role.setParams((*lineIt).parameterMap());
1036 addr.insertExtraRole(role);
1037 }
1038 // SORT-STRING
1039 else if (identifier == QLatin1String("sort-string")) {
1040 addr.setSortString((*lineIt).value().toString());
1041 }
1042 // SOUND
1043 else if (identifier == QLatin1String("sound")) {
1044 Sound sound = parseSound(line: *lineIt);
1045 if (addr.sound().isEmpty()) {
1046 addr.setSound(sound);
1047 } else {
1048 addr.insertExtraSound(sound);
1049 }
1050 }
1051 // TEL
1052 else if (identifier == QLatin1String("tel")) {
1053 PhoneNumber phone;
1054 phone.setNumber((*lineIt).value().toString());
1055
1056 PhoneNumber::Type type;
1057 bool foundType = false;
1058 const QStringList types = (*lineIt).parameters(QStringLiteral("type"));
1059 QStringList::ConstIterator typeEnd(types.constEnd());
1060 for (QStringList::ConstIterator it = types.constBegin(); it != typeEnd; ++it) {
1061 type |= stringToPhoneType(str: (*it).toUpper());
1062 foundType = true;
1063 }
1064 phone.setType(foundType ? type : PhoneNumber::Undefined);
1065 phone.setParams((*lineIt).parameterMap());
1066
1067 addr.insertPhoneNumber(phoneNumber: phone);
1068 }
1069 // TITLE
1070 else if (identifier == QLatin1String("title")) {
1071 Title title((*lineIt).value().toString());
1072 title.setParams((*lineIt).parameterMap());
1073 addr.insertExtraTitle(title);
1074 }
1075 // TZ
1076 else if (identifier == QLatin1String("tz")) {
1077 // TODO add vcard4 support
1078 TimeZone tz;
1079 const QString date = (*lineIt).value().toString();
1080
1081 if (!date.isEmpty()) {
1082 const QStringView dateView(date);
1083 int hours = dateView.mid(pos: 1, n: 2).toInt();
1084 int minutes = dateView.mid(pos: 4, n: 2).toInt();
1085 int offset = (hours * 60) + minutes;
1086 offset = offset * (date[0] == QLatin1Char('+') ? 1 : -1);
1087
1088 tz.setOffset(offset);
1089 addr.setTimeZone(tz);
1090 }
1091 }
1092 // UID
1093 else if (identifier == QLatin1String("uid")) {
1094 addr.setUid((*lineIt).value().toString());
1095 }
1096 // URL
1097 else if (identifier == QLatin1String("url")) {
1098 const QUrl url = QUrl((*lineIt).value().toString());
1099 ResourceLocatorUrl resourceLocatorUrl;
1100 resourceLocatorUrl.setUrl(url);
1101 resourceLocatorUrl.setParams((*lineIt).parameterMap());
1102 addr.insertExtraUrl(url: resourceLocatorUrl);
1103 }
1104 // SOURCE
1105 else if (identifier == QLatin1String("source")) {
1106 const QUrl url = QUrl((*lineIt).value().toString());
1107 addr.insertSourceUrl(url);
1108 }
1109 // MEMBER (vcard 4.0)
1110 else if (identifier == QLatin1String("member")) {
1111 addr.insertMember(member: (*lineIt).value().toString());
1112 }
1113 // RELATED (vcard 4.0)
1114 else if (identifier == QLatin1String("related")) {
1115 Related related;
1116 related.setRelated((*lineIt).value().toString());
1117 related.setParams((*lineIt).parameterMap());
1118 addr.insertRelationship(related);
1119 }
1120 // CLIENTPIDMAP (vcard 4.0)
1121 else if (identifier == QLatin1String("clientpidmap")) {
1122 ClientPidMap clientpidmap;
1123 clientpidmap.setClientPidMap((*lineIt).value().toString());
1124 clientpidmap.setParams((*lineIt).parameterMap());
1125 addr.insertClientPidMap(clientpidmap);
1126 }
1127 // X-
1128 // TODO import X-GENDER
1129 else if (identifier.startsWith(s: QLatin1String("x-"))) {
1130 QString ident = (*lineIt).identifier();
1131 // clang-format off
1132 //X-Evolution
1133 // also normalize case of our own extensions, some backends "adjust" that
1134 if (identifier == QLatin1String("x-evolution-spouse")
1135 || identifier == QLatin1String("x-spouse")) {
1136 ident = QStringLiteral("X-KADDRESSBOOK-X-SpousesName");
1137 } else if (identifier == QLatin1String("x-evolution-blog-url") || identifier.compare(other: QLatin1String("X-KADDRESSBOOK-BLOGFEED"), cs: Qt::CaseInsensitive) == 0) {
1138 ident = QStringLiteral("X-KADDRESSBOOK-BlogFeed");
1139 } else if (identifier == QLatin1String("x-evolution-assistant")
1140 || identifier == QLatin1String("x-assistant")
1141 || identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-ASSISTANTSNAME"), cs: Qt::CaseInsensitive) == 0) {
1142 ident = QStringLiteral("X-KADDRESSBOOK-X-AssistantsName");
1143 } else if (identifier == QLatin1String("x-evolution-anniversary")
1144 || identifier == QLatin1String("x-anniversary")
1145 || identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-ANNIVERSARY"), cs: Qt::CaseInsensitive) == 0) {
1146 ident = QStringLiteral("X-KADDRESSBOOK-X-Anniversary");
1147 } else if (identifier == QLatin1String("x-evolution-manager")
1148 || identifier == QLatin1String("x-manager")
1149 || identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-MANAGERSNAME"), cs: Qt::CaseInsensitive) == 0) {
1150 // clang-format on
1151 ident = QStringLiteral("X-KADDRESSBOOK-X-ManagersName");
1152 } else if (identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-PROFESSION"), cs: Qt::CaseInsensitive) == 0) {
1153 ident = QStringLiteral("X-KADDRESSBOOK-X-Profession");
1154 } else if (identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-OFFICE"), cs: Qt::CaseInsensitive) == 0) {
1155 ident = QStringLiteral("X-KADDRESSBOOK-X-Office");
1156 } else if (identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-SPOUSESNAME"), cs: Qt::CaseInsensitive) == 0) {
1157 ident = QStringLiteral("X-KADDRESSBOOK-X-SpousesName");
1158 } else if (identifier == QLatin1String("x-aim")) {
1159 ident = QStringLiteral("X-messaging/aim-All");
1160 } else if (identifier == QLatin1String("x-icq")) {
1161 ident = QStringLiteral("X-messaging/icq-All");
1162 } else if (identifier == QLatin1String("x-jabber")) {
1163 ident = QStringLiteral("X-messaging/xmpp-All");
1164 } else if (identifier == QLatin1String("x-jabber")) {
1165 ident = QStringLiteral("X-messaging/xmpp-All");
1166 } else if (identifier == QLatin1String("x-msn")) {
1167 ident = QStringLiteral("X-messaging/msn-All");
1168 } else if (identifier == QLatin1String("x-yahoo")) {
1169 ident = QStringLiteral("X-messaging/yahoo-All");
1170 } else if (identifier == QLatin1String("x-gadugadu")) {
1171 ident = QStringLiteral("X-messaging/gadu-All");
1172 } else if (identifier == QLatin1String("x-skype")) {
1173 ident = QStringLiteral("X-messaging/skype-All");
1174 } else if (identifier == QLatin1String("x-groupwise")) {
1175 ident = QStringLiteral("X-messaging/groupwise-All");
1176 } else if (identifier == QLatin1String("x-sms")) {
1177 ident = QStringLiteral("X-messaging/sms-All");
1178 } else if (identifier == QLatin1String("x-meanwhile")) {
1179 ident = QStringLiteral("X-messaging/meanwhile-All");
1180 } else if (identifier == QLatin1String("x-irc")) {
1181 ident = QStringLiteral("X-messaging/irc-All");
1182 } else if (identifier == QLatin1String("x-gtalk")) {
1183 ident = QStringLiteral("X-messaging/googletalk-All");
1184 } else if (identifier == QLatin1String("x-twitter")) {
1185 ident = QStringLiteral("X-messaging/twitter-All");
1186 }
1187
1188 const QString key = ident.mid(position: 2);
1189 const int dash = key.indexOf(ch: QLatin1Char('-'));
1190
1191 // convert legacy messaging fields into IMPP ones
1192 if (key.startsWith(s: QLatin1String("messaging/"))) {
1193 QUrl url;
1194 url.setScheme(normalizeImppServiceType(serviceType: key.mid(position: 10, n: dash - 10)));
1195 const auto values = (*lineIt).value().toString().split(sep: QChar(0xE000), behavior: Qt::SkipEmptyParts);
1196 for (const auto &value : values) {
1197 url.setPath(path: value);
1198 Impp impp;
1199 impp.setParams((*lineIt).parameterMap());
1200 impp.setAddress(url);
1201 addr.insertImpp(impp);
1202 }
1203 } else {
1204 addr.insertCustom(app: key.left(n: dash), name: key.mid(position: dash + 1), value: (*lineIt).value().toString());
1205 }
1206 }
1207 }
1208 }
1209
1210 addrList.append(t: addr);
1211 }
1212
1213 return addrList;
1214}
1215
1216QDateTime VCardTool::parseDateTime(const QString &str, bool *timeValid)
1217{
1218 static const QLatin1Char sep('-');
1219
1220 const int posT = str.indexOf(ch: QLatin1Char('T'));
1221 QString dateString = posT >= 0 ? str.left(n: posT) : str;
1222 const bool noYear = dateString.startsWith(s: QLatin1String("--"));
1223 dateString.remove(c: QLatin1Char('-'));
1224 QDate date;
1225
1226 const QStringView dstr{dateString};
1227 if (noYear) {
1228 date.setDate(year: -1, month: dstr.mid(pos: 0, n: 2).toInt(), day: dstr.mid(pos: 2, n: 2).toInt());
1229 } else {
1230 // E.g. 20160120
1231 date.setDate(year: dstr.mid(pos: 0, n: 4).toInt(), month: dstr.mid(pos: 4, n: 2).toInt(), day: dstr.mid(pos: 6, n: 2).toInt());
1232 }
1233
1234 QTime time;
1235 QTimeZone tz = QTimeZone::LocalTime;
1236 if (posT >= 0) {
1237 QString timeString = str.mid(position: posT + 1);
1238 timeString.remove(c: QLatin1Char(':'));
1239 const int zPos = timeString.indexOf(ch: QLatin1Char('Z'));
1240 const int plusPos = timeString.indexOf(ch: QLatin1Char('+'));
1241 const int minusPos = timeString.indexOf(ch: sep);
1242 const int tzPos = qMax(a: qMax(a: zPos, b: plusPos), b: minusPos);
1243 const QStringView hhmmssString = tzPos >= 0 ? QStringView(timeString).left(n: tzPos) : QStringView(timeString);
1244 int hour = 0;
1245 int minutes = 0;
1246 int seconds = 0;
1247 switch (hhmmssString.size()) {
1248 case 2:
1249 hour = hhmmssString.toInt();
1250 break;
1251 case 4:
1252 hour = hhmmssString.mid(pos: 0, n: 2).toInt();
1253 minutes = hhmmssString.mid(pos: 2, n: 2).toInt();
1254 break;
1255 case 6:
1256 hour = hhmmssString.mid(pos: 0, n: 2).toInt();
1257 minutes = hhmmssString.mid(pos: 2, n: 2).toInt();
1258 seconds = hhmmssString.mid(pos: 4, n: 2).toInt();
1259 break;
1260 }
1261 time.setHMS(h: hour, m: minutes, s: seconds);
1262
1263 if (tzPos >= 0) {
1264 if (zPos >= 0) {
1265 tz = QTimeZone::UTC;
1266 } else {
1267 int offsetSecs = 0;
1268 const auto offsetString = QStringView(timeString).mid(pos: tzPos + 1);
1269 switch (offsetString.size()) {
1270 case 2: // format: "hh"
1271 offsetSecs = offsetString.left(n: 2).toInt() * 3600;
1272 break;
1273 case 4: // format: "hhmm"
1274 offsetSecs = offsetString.left(n: 2).toInt() * 3600 + offsetString.mid(pos: 2, n: 2).toInt() * 60;
1275 break;
1276 }
1277 if (minusPos >= 0) {
1278 offsetSecs *= -1;
1279 }
1280 tz = QTimeZone::fromSecondsAheadOfUtc(offset: offsetSecs);
1281 }
1282 }
1283 }
1284 if (timeValid) {
1285 *timeValid = time.isValid();
1286 }
1287
1288 return QDateTime(date, time, tz);
1289}
1290
1291QString VCardTool::createDateTime(const QDateTime &dateTime, VCard::Version version, bool withTime)
1292{
1293 if (!dateTime.date().isValid()) {
1294 return QString();
1295 }
1296 QString str = createDate(date: dateTime.date(), version);
1297 if (!withTime) {
1298 return str;
1299 }
1300 str += createTime(time: dateTime.time(), version);
1301 if (dateTime.timeSpec() == Qt::UTC) {
1302 str += QLatin1Char('Z');
1303 } else if (dateTime.timeSpec() == Qt::OffsetFromUTC) {
1304 const int offsetSecs = dateTime.offsetFromUtc();
1305 if (offsetSecs >= 0) {
1306 str += QLatin1Char('+');
1307 } else {
1308 str += QLatin1Char('-');
1309 }
1310 const QTime offsetTime = QTime(0, 0).addSecs(secs: abs(x: offsetSecs));
1311 if (version == VCard::v4_0) {
1312 str += offsetTime.toString(QStringLiteral("HHmm"));
1313 } else {
1314 str += offsetTime.toString(QStringLiteral("HH:mm"));
1315 }
1316 }
1317 return str;
1318}
1319
1320QString VCardTool::createDate(const QDate &date, VCard::Version version)
1321{
1322 QString format;
1323 if (date.year() > 0) {
1324 format = QStringLiteral("yyyyMMdd");
1325 } else {
1326 format = QStringLiteral("--MMdd");
1327 }
1328 if (version != VCard::v4_0) {
1329 format.replace(QStringLiteral("yyyy"), QStringLiteral("yyyy-"));
1330 format.replace(QStringLiteral("MM"), QStringLiteral("MM-"));
1331 }
1332 return date.toString(format);
1333}
1334
1335QString VCardTool::createTime(const QTime &time, VCard::Version version)
1336{
1337 QString format;
1338 if (version == VCard::v4_0) {
1339 format = QStringLiteral("HHmmss");
1340 } else {
1341 format = QStringLiteral("HH:mm:ss");
1342 }
1343 return QLatin1Char('T') + time.toString(format);
1344}
1345
1346Picture VCardTool::parsePicture(const VCardLine &line) const
1347{
1348 Picture pic;
1349
1350 const QStringList params = line.parameterList();
1351 if (params.isEmpty()) { // Vcard4
1352 if (line.value().isValid()) { // Load picture url
1353 const QByteArray value = line.value().toByteArray();
1354 if (const int dataPos = value.indexOf(bv: "data:"); dataPos > -1) { // Data element
1355 // data:image/png;base64,
1356 const int indexOfBase64 = value.indexOf(bv: ";base64,");
1357 if (indexOfBase64 > -1) {
1358 const QByteArray type = value.mid(index: 5, len: indexOfBase64 - 4);
1359 const QByteArray f = value.fromBase64(base64: value.last(n: value.length() - 22));
1360 pic.setRawData(rawData: f, type: QString::fromLatin1(ba: type));
1361 }
1362 } else {
1363 pic.setUrl(line.value().toString());
1364 }
1365 }
1366 } else {
1367 QString type;
1368 if (params.contains(str: QLatin1String("type"))) {
1369 type = line.parameter(QStringLiteral("type"));
1370 }
1371 if (params.contains(str: QLatin1String("encoding"))) {
1372 pic.setRawData(rawData: line.value().toByteArray(), type);
1373 } else if (params.contains(str: QLatin1String("value"))) {
1374 if (line.parameter(QStringLiteral("value")).toLower() == QLatin1String("uri")) {
1375 pic.setUrl(line.value().toString());
1376 }
1377 }
1378 }
1379
1380 return pic;
1381}
1382
1383VCardLine VCardTool::createPicturev4(const QString &identifier, const Picture &pic) const
1384{
1385 if (pic.isIntern()) {
1386 const QString pictureIdentifier = identifier + QStringLiteral(":data") + QStringLiteral(":image/") + pic.type();
1387 VCardLine line(pictureIdentifier);
1388 line.addParameter(QStringLiteral("type"), value: pic.type());
1389 line.setBase64Value(pic.rawData().toBase64());
1390 return line;
1391 } else {
1392 VCardLine line(identifier);
1393 line.setValue(pic.url());
1394 return line;
1395 }
1396 return {};
1397}
1398
1399VCardLine VCardTool::createPicture(const QString &identifier, const Picture &pic, VCard::Version version) const
1400{
1401 VCardLine line(identifier);
1402
1403 if (pic.isEmpty()) {
1404 return line;
1405 }
1406
1407 if (pic.isIntern()) {
1408 line.setValue(pic.rawData());
1409 switch (version) {
1410 case VCard::v2_1:
1411 line.addParameter(QStringLiteral("ENCODING"), QStringLiteral("BASE64"));
1412 line.addParameter(param: pic.type(), value: QString());
1413 break;
1414 case VCard::v3_0:
1415 line.addParameter(QStringLiteral("encoding"), QStringLiteral("b"));
1416 line.addParameter(QStringLiteral("type"), value: pic.type());
1417 break;
1418 case VCard::v4_0:
1419 // Nothing use createPicturev4
1420 break;
1421 }
1422 } else {
1423 line.setValue(pic.url());
1424 line.addParameter(QStringLiteral("value"), QStringLiteral("URI"));
1425 }
1426
1427 return line;
1428}
1429
1430Sound VCardTool::parseSound(const VCardLine &line) const
1431{
1432 Sound snd;
1433
1434 const QStringList params = line.parameterList();
1435 if (params.contains(str: QLatin1String("encoding"))) {
1436 snd.setData(line.value().toByteArray());
1437 } else if (params.contains(str: QLatin1String("value"))) {
1438 if (line.parameter(QStringLiteral("value")).toLower() == QLatin1String("uri")) {
1439 snd.setUrl(line.value().toString());
1440 }
1441 }
1442
1443 /* TODO: support sound types
1444 if ( params.contains( "type" ) )
1445 snd.setType( line.parameter( "type" ) );
1446 */
1447
1448 return snd;
1449}
1450
1451VCardLine VCardTool::createSound(const Sound &snd, VCard::Version version) const
1452{
1453 VCardLine line(QStringLiteral("SOUND"));
1454
1455 if (snd.isIntern()) {
1456 if (!snd.data().isEmpty()) {
1457 line.setValue(snd.data());
1458 if (version == VCard::v2_1) {
1459 line.addParameter(QStringLiteral("ENCODING"), QStringLiteral("BASE64"));
1460 } else {
1461 line.addParameter(QStringLiteral("encoding"), QStringLiteral("b"));
1462 }
1463 // TODO: need to store sound type!!!
1464 }
1465 } else if (!snd.url().isEmpty()) {
1466 line.setValue(snd.url());
1467 line.addParameter(QStringLiteral("value"), QStringLiteral("URI"));
1468 }
1469
1470 return line;
1471}
1472
1473Key VCardTool::parseKey(const VCardLine &line) const
1474{
1475 Key key;
1476
1477 const QStringList params = line.parameterList();
1478 if (params.contains(str: QLatin1String("encoding"))) {
1479 key.setBinaryData(line.value().toByteArray());
1480 } else {
1481 key.setTextData(line.value().toString());
1482 }
1483
1484 if (params.contains(str: QLatin1String("type"))) {
1485 if (line.parameter(QStringLiteral("type")).toLower() == QLatin1String("x509")) {
1486 key.setType(Key::X509);
1487 } else if (line.parameter(QStringLiteral("type")).toLower() == QLatin1String("pgp")) {
1488 key.setType(Key::PGP);
1489 } else {
1490 key.setType(Key::Custom);
1491 key.setCustomTypeString(line.parameter(QStringLiteral("type")));
1492 }
1493 } else if (params.contains(str: QLatin1String("mediatype"))) {
1494 const QString param = line.parameter(QStringLiteral("mediatype")).toLower();
1495 if (param == QLatin1String("application/x-x509-ca-cert")) {
1496 key.setType(Key::X509);
1497 } else if (param == QLatin1String("application/pgp-keys")) {
1498 key.setType(Key::PGP);
1499 } else {
1500 key.setType(Key::Custom);
1501 key.setCustomTypeString(line.parameter(QStringLiteral("type")));
1502 }
1503 }
1504
1505 return key;
1506}
1507
1508VCardLine VCardTool::createKey(const Key &key, VCard::Version version) const
1509{
1510 VCardLine line(QStringLiteral("KEY"));
1511
1512 if (key.isBinary()) {
1513 if (!key.binaryData().isEmpty()) {
1514 line.setValue(key.binaryData());
1515 if (version == VCard::v2_1) {
1516 line.addParameter(QStringLiteral("ENCODING"), QStringLiteral("BASE64"));
1517 } else {
1518 line.addParameter(QStringLiteral("encoding"), QStringLiteral("b"));
1519 }
1520 }
1521 } else if (!key.textData().isEmpty()) {
1522 line.setValue(key.textData());
1523 }
1524
1525 if (version == VCard::v4_0) {
1526 if (key.type() == Key::X509) {
1527 line.addParameter(QStringLiteral("MEDIATYPE"), QStringLiteral("application/x-x509-ca-cert"));
1528 } else if (key.type() == Key::PGP) {
1529 line.addParameter(QStringLiteral("MEDIATYPE"), QStringLiteral("application/pgp-keys"));
1530 } else if (key.type() == Key::Custom) {
1531 line.addParameter(QStringLiteral("MEDIATYPE"), value: key.customTypeString());
1532 }
1533 } else {
1534 if (key.type() == Key::X509) {
1535 line.addParameter(QStringLiteral("type"), QStringLiteral("X509"));
1536 } else if (key.type() == Key::PGP) {
1537 line.addParameter(QStringLiteral("type"), QStringLiteral("PGP"));
1538 } else if (key.type() == Key::Custom) {
1539 line.addParameter(QStringLiteral("type"), value: key.customTypeString());
1540 }
1541 }
1542
1543 return line;
1544}
1545
1546Secrecy VCardTool::parseSecrecy(const VCardLine &line) const
1547{
1548 Secrecy secrecy;
1549
1550 const QString value = line.value().toString().toLower();
1551 if (value == QLatin1String("public")) {
1552 secrecy.setType(Secrecy::Public);
1553 } else if (value == QLatin1String("private")) {
1554 secrecy.setType(Secrecy::Private);
1555 } else if (value == QLatin1String("confidential")) {
1556 secrecy.setType(Secrecy::Confidential);
1557 }
1558
1559 return secrecy;
1560}
1561
1562VCardLine VCardTool::createSecrecy(const Secrecy &secrecy) const
1563{
1564 VCardLine line(QStringLiteral("CLASS"));
1565
1566 int type = secrecy.type();
1567
1568 if (type == Secrecy::Public) {
1569 line.setValue(QStringLiteral("PUBLIC"));
1570 } else if (type == Secrecy::Private) {
1571 line.setValue(QStringLiteral("PRIVATE"));
1572 } else if (type == Secrecy::Confidential) {
1573 line.setValue(QStringLiteral("CONFIDENTIAL"));
1574 }
1575
1576 return line;
1577}
1578
1579QStringList VCardTool::splitString(QChar sep, const QString &str) const
1580{
1581 QStringList list;
1582 QString value(str);
1583
1584 int start = 0;
1585 int pos = value.indexOf(ch: sep, from: start);
1586
1587 while (pos != -1) {
1588 if (pos == 0 || value[pos - 1] != QLatin1Char('\\')) {
1589 if (pos > start && pos <= value.length()) {
1590 list << value.mid(position: start, n: pos - start);
1591 } else {
1592 list << QString();
1593 }
1594
1595 start = pos + 1;
1596 pos = value.indexOf(ch: sep, from: start);
1597 } else {
1598 value.replace(i: pos - 1, len: 2, after: sep);
1599 pos = value.indexOf(ch: sep, from: pos);
1600 }
1601 }
1602
1603 int l = value.length() - 1;
1604 const QString mid = value.mid(position: start, n: l - start + 1);
1605 if (!mid.isEmpty()) {
1606 list << mid;
1607 } else {
1608 list << QString();
1609 }
1610
1611 return list;
1612}
1613
1614QString VCardTool::normalizeImppServiceType(const QString &serviceType) const
1615{
1616 if (serviceType == QLatin1String("jabber")) {
1617 return QStringLiteral("xmpp");
1618 }
1619 if (serviceType == QLatin1String("yahoo")) {
1620 return QStringLiteral("ymsgr");
1621 }
1622 if (serviceType == QLatin1String("gadugadu")) {
1623 return QStringLiteral("gg");
1624 }
1625 return serviceType;
1626}
1627

source code of kcontacts/src/vcardtool.cpp