diff --git a/autotests/data/vcard_legacy_messaging_fields.vcf b/autotests/data/vcard_legacy_messaging_fields.vcf
new file mode 100644
index 00000000..c9831298
--- /dev/null
+++ b/autotests/data/vcard_legacy_messaging_fields.vcf
@@ -0,0 +1,9 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:My Name
+N:Name;My;;;
+UID:12345
+X-messaging/twitter-All:kdecommunity
+X-messaging/irc-All:konqui
+X-messaging/xmpp-All:one@test.orgtwo@test.org
+END:VCARD
diff --git a/autotests/data/vcard_legacy_messaging_fields.vcf.ref b/autotests/data/vcard_legacy_messaging_fields.vcf.ref
new file mode 100644
index 00000000..d10b9d7c
--- /dev/null
+++ b/autotests/data/vcard_legacy_messaging_fields.vcf.ref
@@ -0,0 +1,11 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:My Name
+IMPP:irc:konqui
+IMPP:twitter:kdecommunity
+IMPP:xmpp:one@test.org
+IMPP:xmpp:two@test.org
+N:Name;My;;;
+UID:12345
+END:VCARD
+
diff --git a/autotests/testroundtrip.qrc b/autotests/testroundtrip.qrc
index a074e4b0..1cde1317 100644
--- a/autotests/testroundtrip.qrc
+++ b/autotests/testroundtrip.qrc
@@ -1,67 +1,69 @@
data/vcard1.vcf
data/vcard2.vcf
data/vcard3.vcf
data/vcard4.vcf
data/vcard5.vcf
data/vcard6.vcf
data/vcard7.vcf
data/vcard8.vcf
data/vcard9.vcf
data/vcard10.vcf
data/vcard11.vcf
data/vcard12.vcf
data/vcard13.vcf
data/vcard14.vcf
data/vcard15.vcf
data/vcard_extension_case_normalization.vcf
+ data/vcard_legacy_messaging_fields.vcf
data/vcard1.vcf.2_1ref
data/vcard2.vcf.2_1ref
data/vcard3.vcf.2_1ref
data/vcard6.vcf.2_1ref
data/vcard7.vcf.2_1ref
data/vcard12.vcf.2_1ref
data/vcard14.vcf.2_1ref
data/vcard1.vcf.ref
data/vcard2.vcf.ref
data/vcard3.vcf.ref
data/vcard4.vcf.ref
data/vcard5.vcf.ref
data/vcard6.vcf.ref
data/vcard7.vcf.ref
data/vcard8.vcf.ref
data/vcard9.vcf.ref
data/vcard10.vcf.ref
data/vcard11.vcf.ref
data/vcard12.vcf.ref
data/vcard13.vcf.ref
data/vcard14.vcf.ref
data/vcard15.vcf.ref
data/vcard_extension_case_normalization.vcf.ref
+ data/vcard_legacy_messaging_fields.vcf.ref
data/v4_0.vcard1.vcf.ref
data/v4_0.vcard2.vcf.ref
data/v4_0.vcard3.vcf.ref
data/v4_0.vcard4.vcf.ref
data/v4_0.vcard5.vcf.ref
data/v4_0.vcard6.vcf.ref
data/v4_0.vcard7.vcf.ref
data/v4_0.vcard8.vcf.ref
diff --git a/src/vcardtool.cpp b/src/vcardtool.cpp
index 512a9a11..fff04ce3 100644
--- a/src/vcardtool.cpp
+++ b/src/vcardtool.cpp
@@ -1,1591 +1,1605 @@
/*
This file is part of the KContacts framework.
Copyright (c) 2003 Tobias Koenig
Copyright (C) 2015-2019 Laurent Montel
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "vcardtool_p.h"
#include "key.h"
#include "picture.h"
#include "secrecy.h"
#include "sound.h"
#include "lang.h"
#include "gender.h"
#include "related.h"
#include "fieldgroup.h"
#include "kcontacts_debug.h"
#include
using namespace KContacts;
static bool needsEncoding(const QString &value)
{
int length = value.length();
for (int i = 0; i < length; ++i) {
char c = value.at(i).toLatin1();
if ((c < 33 || c > 126) && c != ' ' && c != '=') {
return true;
}
}
return false;
}
static const struct {
const char *addressType;
Address::TypeFlag flag;
} s_addressTypes[] = {
{ "dom", Address::Dom },
{ "home", Address::Home },
{ "intl", Address::Intl },
{ "parcel", Address::Parcel },
{ "postal", Address::Postal },
{ "pref", Address::Pref },
{ "work", Address::Work },
};
static const unsigned int s_numAddressTypes
= sizeof s_addressTypes / sizeof *s_addressTypes;
static Address::TypeFlag stringToAddressType(const QString &str)
{
for (unsigned int i = 0; i < s_numAddressTypes; ++i) {
if (str == QLatin1String(s_addressTypes[i].addressType)) {
return s_addressTypes[i].flag;
}
}
return {};
}
static const struct {
const char *phoneType;
PhoneNumber::TypeFlag flag;
} s_phoneTypes[] = {
{ "BBS", PhoneNumber::Bbs },
{ "CAR", PhoneNumber::Car },
{ "CELL", PhoneNumber::Cell },
{ "FAX", PhoneNumber::Fax },
{ "HOME", PhoneNumber::Home },
{ "ISDN", PhoneNumber::Isdn },
{ "MODEM", PhoneNumber::Modem },
{ "MSG", PhoneNumber::Msg },
{ "PAGER", PhoneNumber::Pager },
{ "PCS", PhoneNumber::Pcs },
{ "PREF", PhoneNumber::Pref },
{ "VIDEO", PhoneNumber::Video },
{ "VOICE", PhoneNumber::Voice },
{ "WORK", PhoneNumber::Work },
};
static const unsigned int s_numPhoneTypes
= sizeof s_phoneTypes / sizeof *s_phoneTypes;
static PhoneNumber::TypeFlag stringToPhoneType(const QString &str)
{
for (unsigned int i = 0; i < s_numPhoneTypes; ++i) {
if (str == QLatin1String(s_phoneTypes[i].phoneType)) {
return s_phoneTypes[i].flag;
}
}
return {};
}
VCardTool::VCardTool()
{
}
VCardTool::~VCardTool()
{
}
QByteArray VCardTool::exportVCards(const Addressee::List &list, VCard::Version version) const
{
return createVCards(list, version, true /*export vcard*/);
}
QByteArray VCardTool::createVCards(const Addressee::List &list, VCard::Version version) const
{
return createVCards(list, version, false /*don't export*/);
}
void VCardTool::addParameters(VCardLine &line, const QMap ¶ms) const
{
QMapIterator i(params);
while (i.hasNext()) {
i.next();
line.addParameter(i.key(), i.value().join(QLatin1Char(',')));
}
}
void VCardTool::addParameter(VCardLine &line, VCard::Version version, const QString &key, const QStringList &valueStringList) const
{
if (version == VCard::v2_1) {
for (const QString &valueStr : valueStringList) {
line.addParameter(valueStr, QString());
}
} else if (version == VCard::v3_0) {
line.addParameter(key, valueStringList.join(QLatin1Char(',')));
} else {
if (valueStringList.count() < 2) {
line.addParameter(key, valueStringList.join(QLatin1Char(',')));
} else {
line.addParameter(key, QLatin1Char('"') + valueStringList.join(QLatin1Char(',')) + QLatin1Char('"'));
}
}
}
QByteArray VCardTool::createVCards(const Addressee::List &list, VCard::Version version, bool exportVcard) const
{
VCard::List vCardList;
Addressee::List::ConstIterator addrIt;
Addressee::List::ConstIterator listEnd(list.constEnd());
for (addrIt = list.constBegin(); addrIt != listEnd; ++addrIt) {
VCard card;
QStringList::ConstIterator strIt;
// VERSION
if (version == VCard::v2_1) {
card.addLine(VCardLine(QStringLiteral("VERSION"), QStringLiteral("2.1")));
} else if (version == VCard::v3_0) {
card.addLine(VCardLine(QStringLiteral("VERSION"), QStringLiteral("3.0")));
} else if (version == VCard::v4_0) {
card.addLine(VCardLine(QStringLiteral("VERSION"), QStringLiteral("4.0")));
}
// ADR + LABEL
const Address::List addresses = (*addrIt).addresses();
Address::List::ConstIterator end(addresses.end());
for (Address::List::ConstIterator it = addresses.begin(); it != end; ++it) {
QStringList address;
const bool isEmpty = ((*it).postOfficeBox().isEmpty()
&& (*it).extended().isEmpty()
&& (*it).street().isEmpty()
&& (*it).locality().isEmpty()
&& (*it).region().isEmpty()
&& (*it).postalCode().isEmpty()
&& (*it).country().isEmpty());
address.append((*it).postOfficeBox().replace(QLatin1Char(';'),
QStringLiteral("\\;")));
address.append((*it).extended().replace(QLatin1Char(';'),
QStringLiteral("\\;")));
address.append((*it).street().replace(QLatin1Char(';'),
QStringLiteral("\\;")));
address.append((*it).locality().replace(QLatin1Char(';'),
QStringLiteral("\\;")));
address.append((*it).region().replace(QLatin1Char(';'),
QStringLiteral("\\;")));
address.append((*it).postalCode().replace(QLatin1Char(';'),
QStringLiteral("\\;")));
address.append((*it).country().replace(QLatin1Char(';'),
QStringLiteral("\\;")));
const QString addressJoined(address.join(QLatin1Char(';')));
VCardLine adrLine(QStringLiteral("ADR"), addressJoined);
if (version == VCard::v2_1 && needsEncoding(addressJoined)) {
adrLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
adrLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
}
const bool hasLabel = !(*it).label().isEmpty();
QStringList addreLineType;
QStringList labelLineType;
for (unsigned int i = 0; i < s_numAddressTypes; ++i) {
if (s_addressTypes[i].flag & (*it).type()) {
const QString str = QString::fromLatin1(s_addressTypes[i].addressType);
addreLineType << str;
if (hasLabel) {
labelLineType << str;
}
}
}
if (hasLabel) {
if (version == VCard::v4_0) {
if (!(*it).label().isEmpty()) {
adrLine.addParameter(QStringLiteral("LABEL"), QStringLiteral("\"%1\"").arg((*it).label()));
}
} else {
VCardLine labelLine(QStringLiteral("LABEL"), (*it).label());
if (version == VCard::v2_1 && needsEncoding((*it).label())) {
labelLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
labelLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
}
addParameter(labelLine, version, QStringLiteral("TYPE"), labelLineType);
card.addLine(labelLine);
}
}
if (version == VCard::v4_0) {
Geo geo = (*it).geo();
if (geo.isValid()) {
QString str = QString::asprintf("\"geo:%.6f,%.6f\"", geo.latitude(), geo.longitude());
adrLine.addParameter(QStringLiteral("GEO"), str);
}
}
if (!isEmpty) {
addParameter(adrLine, version, QStringLiteral("TYPE"), addreLineType);
card.addLine(adrLine);
}
}
// BDAY
const bool withTime = (*addrIt).birthdayHasTime();
const QString birthdayString = createDateTime((*addrIt).birthday(), version, withTime);
card.addLine(VCardLine(QStringLiteral("BDAY"), birthdayString));
//Laurent: 31 Jan 2015. Not necessary to export it. When Categories were changes as AkonadiTag nobody thought that it was break categorie support...
//=> not necessary to export just tag...
// CATEGORIES only > 2.1
if (!exportVcard) {
if (version != VCard::v2_1) {
QStringList categories = (*addrIt).categories();
QStringList::Iterator catIt;
QStringList::Iterator catEnd(categories.end());
for (catIt = categories.begin(); catIt != catEnd; ++catIt) {
(*catIt).replace(QLatin1Char(','), QStringLiteral("\\,"));
}
VCardLine catLine(QStringLiteral("CATEGORIES"), categories.join(QLatin1Char(',')));
card.addLine(catLine);
}
}
// MEMBER (only in 4.0)
if (version == VCard::v4_0) {
// The KIND property must be set to "group" in order to use this property.
if ((*addrIt).kind().toLower() == QLatin1String("group")) {
const QStringList lst = (*addrIt).members();
for (const QString &member : lst) {
VCardLine line(QStringLiteral("MEMBER"), member);
card.addLine(line);
}
}
}
// SOURCE
const QVector lstUrl = (*addrIt).sourcesUrlList();
for (const QUrl &url : lstUrl) {
VCardLine line = VCardLine(QStringLiteral("SOURCE"), url.url());
card.addLine(line);
}
const Related::List relatedList = (*addrIt).relationships();
Related::List::ConstIterator relatedIt;
Related::List::ConstIterator relatedEnd(relatedList.end());
for (relatedIt = relatedList.begin(); relatedIt != relatedEnd; ++relatedIt) {
VCardLine line(QStringLiteral("RELATED"), (*relatedIt).related());
addParameters(line, (*relatedIt).parameters());
card.addLine(line);
}
// CLASS only for version == 3.0
if (version == VCard::v3_0) {
card.addLine(createSecrecy((*addrIt).secrecy()));
}
// LANG only for version == 4.0
if (version == VCard::v4_0) {
const Lang::List langList = (*addrIt).langs();
Lang::List::ConstIterator langIt;
Lang::List::ConstIterator langEnd(langList.end());
for (langIt = langList.begin(); langIt != langEnd; ++langIt) {
VCardLine line(QStringLiteral("LANG"), (*langIt).language());
addParameters(line, (*langIt).parameters());
card.addLine(line);
}
}
// CLIENTPIDMAP
if (version == VCard::v4_0) {
const ClientPidMap::List clientpidmapList = (*addrIt).clientPidMapList();
ClientPidMap::List::ConstIterator clientPidMapIt;
ClientPidMap::List::ConstIterator clientPidMapEnd(clientpidmapList.end());
for (clientPidMapIt = clientpidmapList.begin(); clientPidMapIt != clientPidMapEnd; ++clientPidMapIt) {
VCardLine line(QStringLiteral("CLIENTPIDMAP"), (*clientPidMapIt).clientPidMap());
addParameters(line, (*clientPidMapIt).parameters());
card.addLine(line);
}
}
// EMAIL
const Email::List emailList = (*addrIt).emailList();
Email::List::ConstIterator emailIt;
Email::List::ConstIterator emailEnd(emailList.end());
for (emailIt = emailList.begin(); emailIt != emailEnd; ++emailIt) {
VCardLine line(QStringLiteral("EMAIL"), (*emailIt).mail());
QMapIterator i((*emailIt).parameters());
while (i.hasNext()) {
i.next();
if (version == VCard::v2_1) {
if (i.key().toLower() == QLatin1String("type")) {
QStringList valueStringList = i.value();
bool hasPreferred = false;
const int removeItems = valueStringList.removeAll(QStringLiteral("PREF"));
if (removeItems > 0) {
hasPreferred = true;
}
if (!valueStringList.isEmpty()) {
addParameter(line, version, i.key(), valueStringList);
}
if (hasPreferred) {
line.addParameter(QStringLiteral("PREF"), QString());
}
} else {
line.addParameter(i.key(), i.value().join(QLatin1Char(',')));
}
} else {
line.addParameter(i.key(), i.value().join(QLatin1Char(',')));
}
}
card.addLine(line);
}
// FN required for only version > 2.1
VCardLine fnLine(QStringLiteral("FN"), (*addrIt).formattedName());
if (version == VCard::v2_1 && needsEncoding((*addrIt).formattedName())) {
fnLine.addParameter(QStringLiteral("charset"), QStringLiteral("UTF-8"));
fnLine.addParameter(QStringLiteral("encoding"), QStringLiteral("QUOTED-PRINTABLE"));
}
card.addLine(fnLine);
// GEO
const Geo geo = (*addrIt).geo();
if (geo.isValid()) {
QString str;
if (version == VCard::v4_0) {
str = QString::asprintf("geo:%.6f,%.6f", geo.latitude(), geo.longitude());
} else {
str = QString::asprintf("%.6f;%.6f", geo.latitude(), geo.longitude());
}
card.addLine(VCardLine(QStringLiteral("GEO"), str));
}
// KEY
const Key::List keys = (*addrIt).keys();
Key::List::ConstIterator keyIt;
Key::List::ConstIterator keyEnd(keys.end());
for (keyIt = keys.begin(); keyIt != keyEnd; ++keyIt) {
card.addLine(createKey(*keyIt, version));
}
// LOGO
card.addLine(createPicture(QStringLiteral("LOGO"), (*addrIt).logo(), version));
const QVector