KContacts

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

KDE's Doxygen guidelines are available online.