| 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 | |
| 23 | using namespace KContacts; |
| 24 | |
| 25 | static 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 | |
| 38 | struct AddressTypeInfo { |
| 39 | const char *addressType; |
| 40 | Address::TypeFlag flag; |
| 41 | }; |
| 42 | |
| 43 | static 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 | |
| 53 | static 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 | |
| 61 | struct PhoneTypeInfo { |
| 62 | const char *phoneType; |
| 63 | PhoneNumber::TypeFlag flag; |
| 64 | }; |
| 65 | |
| 66 | static 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 | |
| 83 | static 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 | |
| 91 | VCardTool::VCardTool() |
| 92 | { |
| 93 | } |
| 94 | |
| 95 | VCardTool::~VCardTool() |
| 96 | { |
| 97 | } |
| 98 | |
| 99 | QByteArray VCardTool::exportVCards(const Addressee::List &list, VCard::Version version) const |
| 100 | { |
| 101 | return createVCards(list, version, exportVcard: true /*export vcard*/); |
| 102 | } |
| 103 | |
| 104 | QByteArray VCardTool::createVCards(const Addressee::List &list, VCard::Version version) const |
| 105 | { |
| 106 | return createVCards(list, version, exportVcard: false /*don't export*/); |
| 107 | } |
| 108 | |
| 109 | void 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(param: valueStr, value: QString()); |
| 114 | } |
| 115 | } else if (version == VCard::v3_0) { |
| 116 | line->addParameter(param: key, value: valueStringList.join(sep: QLatin1Char(','))); |
| 117 | } else { |
| 118 | if (valueStringList.count() < 2) { |
| 119 | line->addParameter(param: key, value: valueStringList.join(sep: QLatin1Char(','))); |
| 120 | } else { |
| 121 | line->addParameter(param: key, value: QLatin1Char('"') + valueStringList.join(sep: QLatin1Char(',')) + QLatin1Char('"')); |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | void VCardTool::processAddresses(const Address::List &addresses, VCard::Version version, VCard *card) const |
| 127 | { |
| 128 | for (const auto &addr : addresses) { |
| 129 | QStringList address; |
| 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(t: addr.postOfficeBox().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 142 | address.append(t: addr.extended().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 143 | address.append(t: addr.street().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 144 | address.append(t: addr.locality().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 145 | address.append(t: addr.region().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 146 | address.append(t: addr.postalCode().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 147 | address.append(t: addr.country().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 148 | |
| 149 | const QString addressJoined(address.join(sep: QLatin1Char(';'))); |
| 150 | VCardLine adrLine(QStringLiteral("ADR" ), addressJoined); |
| 151 | if (version == VCard::v2_1 && needsEncoding(value: 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(ba: 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(a: addr.label())); |
| 174 | } |
| 175 | } else { |
| 176 | VCardLine labelLine(QStringLiteral("LABEL" ), addr.label()); |
| 177 | if (version == VCard::v2_1 && needsEncoding(value: addr.label())) { |
| 178 | labelLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 179 | labelLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 180 | } |
| 181 | addParameter(line: &labelLine, version, QStringLiteral("TYPE" ), valueStringList: labelLineType); |
| 182 | card->addLine(line: labelLine); |
| 183 | } |
| 184 | } |
| 185 | if (version == VCard::v4_0) { |
| 186 | Geo geo = addr.geo(); |
| 187 | if (geo.isValid()) { |
| 188 | QString str = QString::asprintf(format: "\"geo:%.6f,%.6f\"" , geo.latitude(), geo.longitude()); |
| 189 | adrLine.addParameter(QStringLiteral("GEO" ), value: str); |
| 190 | } |
| 191 | } |
| 192 | if (!isEmpty) { |
| 193 | addParameter(line: &adrLine, version, QStringLiteral("TYPE" ), valueStringList: addreLineType); |
| 194 | card->addLine(line: adrLine); |
| 195 | } |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | void 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: &line, version, key: param, valueStringList: list); |
| 215 | } |
| 216 | if (hasPreferred) { |
| 217 | line.addParameter(QStringLiteral("PREF" ), value: QString()); |
| 218 | } |
| 219 | } else { |
| 220 | line.addParameter(param, value: list.join(sep: QLatin1Char(','))); |
| 221 | } |
| 222 | } else { |
| 223 | line.addParameter(param, value: list.join(sep: QLatin1Char(','))); |
| 224 | } |
| 225 | } |
| 226 | card->addLine(line); |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | void 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(c: QLatin1Char(';'), after: QLatin1String("\\;" ))}; |
| 235 | if (!addressee.department().isEmpty()) { |
| 236 | organization.append(t: addressee.department().replace(c: QLatin1Char(';'), after: QLatin1String("\\;" ))); |
| 237 | } |
| 238 | const QString orgStr = organization.join(sep: QLatin1Char(';')); |
| 239 | VCardLine orgLine(QStringLiteral("ORG" ), orgStr); |
| 240 | if (version == VCard::v2_1 && needsEncoding(value: orgStr)) { |
| 241 | orgLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 242 | orgLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 243 | } |
| 244 | orgLine.addParameters(params: org.params()); |
| 245 | card->addLine(line: orgLine); |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | void 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, value: list.join(sep: 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(ba: 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: &line, version, QStringLiteral("TYPE" ), valueStringList: lst); |
| 274 | } |
| 275 | card->addLine(line); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | void 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(n: str.indexOf(ch: QLatin1Char(':'))); |
| 283 | const QString value = str.mid(position: str.indexOf(ch: 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(string: value, format: Qt::ISODate); |
| 323 | QDateTime dt = QDateTime(date.startOfDay()); |
| 324 | dt.setTime(time: QTime()); |
| 325 | VCardLine line(QStringLiteral("ANNIVERSARY" ), createDateTime(dateTime: dt, version, withTime: 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 | |
| 346 | QByteArray 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(line: VCardLine(QStringLiteral("VERSION" ), QStringLiteral("2.1" ))); |
| 355 | } else if (version == VCard::v3_0) { |
| 356 | card.addLine(line: VCardLine(QStringLiteral("VERSION" ), QStringLiteral("3.0" ))); |
| 357 | } else if (version == VCard::v4_0) { |
| 358 | card.addLine(line: VCardLine(QStringLiteral("VERSION" ), QStringLiteral("4.0" ))); |
| 359 | } |
| 360 | |
| 361 | // ADR + LABEL |
| 362 | const Address::List addresses = addressee.addresses(); |
| 363 | processAddresses(addresses, version, card: &card); |
| 364 | |
| 365 | // BDAY |
| 366 | const bool withTime = addressee.birthdayHasTime(); |
| 367 | const QString birthdayString = createDateTime(dateTime: addressee.birthday(), version, withTime); |
| 368 | card.addLine(line: 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(c: QLatin1Char(','), after: QLatin1String("\\," )); |
| 375 | } |
| 376 | |
| 377 | VCardLine catLine(QStringLiteral("CATEGORIES" ), categories.join(sep: QLatin1Char(','))); |
| 378 | card.addLine(line: 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(line: 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(params: rel.params()); |
| 401 | card.addLine(line); |
| 402 | } |
| 403 | // CLASS only for version == 3.0 |
| 404 | if (version == VCard::v3_0) { |
| 405 | card.addLine(line: createSecrecy(secrecy: 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(params: 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(params: pMap.params()); |
| 422 | card.addLine(line); |
| 423 | } |
| 424 | } |
| 425 | // EMAIL |
| 426 | const Email::List emailList = addressee.emailList(); |
| 427 | processEmailList(emailList, version, card: &card); |
| 428 | |
| 429 | // FN required for only version > 2.1 |
| 430 | VCardLine fnLine(QStringLiteral("FN" ), addressee.formattedName()); |
| 431 | if (version == VCard::v2_1 && needsEncoding(value: addressee.formattedName())) { |
| 432 | fnLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 433 | fnLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 434 | } |
| 435 | card.addLine(line: 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(format: "geo:%.6f,%.6f" , geo.latitude(), geo.longitude()); |
| 443 | } else { |
| 444 | str = QString::asprintf(format: "%.6f;%.6f" , geo.latitude(), geo.longitude()); |
| 445 | } |
| 446 | card.addLine(line: VCardLine(QStringLiteral("GEO" ), str)); |
| 447 | } |
| 448 | |
| 449 | // KEY |
| 450 | const Key::List keys = addressee.keys(); |
| 451 | for (const auto &k : keys) { |
| 452 | card.addLine(line: createKey(key: k, version)); |
| 453 | } |
| 454 | |
| 455 | // LOGO |
| 456 | card.addLine(line: createPicture(QStringLiteral("LOGO" ), pic: addressee.logo(), version)); |
| 457 | const QList<Picture> lstLogo = addressee.extraLogoList(); |
| 458 | for (const Picture &logo : lstLogo) { |
| 459 | card.addLine(line: createPicture(QStringLiteral("LOGO" ), pic: 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(value: addressee.mailer())) { |
| 466 | mailerLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 467 | mailerLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 468 | } |
| 469 | card.addLine(line: mailerLine); |
| 470 | } |
| 471 | |
| 472 | // N required for only version < 4.0 |
| 473 | QStringList name; |
| 474 | name.append(t: addressee.familyName().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 475 | name.append(t: addressee.givenName().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 476 | name.append(t: addressee.additionalName().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 477 | name.append(t: addressee.prefix().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 478 | name.append(t: addressee.suffix().replace(c: QLatin1Char(';'), QStringLiteral("\\;" ))); |
| 479 | |
| 480 | VCardLine nLine(QStringLiteral("N" ), name.join(sep: QLatin1Char(';'))); |
| 481 | if (version == VCard::v2_1 && needsEncoding(value: name.join(sep: 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" ), value: addressee.sortString()); |
| 487 | } |
| 488 | |
| 489 | card.addLine(line: 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(value: addressee.name())) { |
| 495 | nameLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 496 | nameLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 497 | } |
| 498 | card.addLine(line: 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(params: nickName.params()); |
| 507 | |
| 508 | card.addLine(line: nickNameLine); |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | // NOTE |
| 513 | VCardLine noteLine(QStringLiteral("NOTE" ), addressee.note()); |
| 514 | if (version == VCard::v2_1 && needsEncoding(value: addressee.note())) { |
| 515 | noteLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 516 | noteLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 517 | } |
| 518 | card.addLine(line: noteLine); |
| 519 | |
| 520 | // ORG |
| 521 | processOrganizations(addressee, version, card: &card); |
| 522 | |
| 523 | // PHOTO |
| 524 | card.addLine(line: createPicture(QStringLiteral("PHOTO" ), pic: addressee.photo(), version)); |
| 525 | const QList<Picture> = addressee.extraPhotoList(); |
| 526 | for (const Picture &photo : lstExtraPhoto) { |
| 527 | card.addLine(line: createPicture(QStringLiteral("PHOTO" ), pic: photo, version)); |
| 528 | } |
| 529 | |
| 530 | // PROID only for version > 2.1 |
| 531 | if (version != VCard::v2_1) { |
| 532 | card.addLine(line: VCardLine(QStringLiteral("PRODID" ), addressee.productId())); |
| 533 | } |
| 534 | |
| 535 | // REV |
| 536 | card.addLine(line: VCardLine(QStringLiteral("REV" ), createDateTime(dateTime: addressee.revision(), version))); |
| 537 | |
| 538 | // ROLE |
| 539 | const QList<Role> = addressee.extraRoleList(); |
| 540 | for (const Role &role : lstExtraRole) { |
| 541 | VCardLine roleLine(QStringLiteral("ROLE" ), role.role()); |
| 542 | if (version == VCard::v2_1 && needsEncoding(value: role.role())) { |
| 543 | roleLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 544 | roleLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 545 | } |
| 546 | roleLine.addParameters(params: role.params()); |
| 547 | card.addLine(line: roleLine); |
| 548 | } |
| 549 | |
| 550 | // SORT-STRING |
| 551 | if (version == VCard::v3_0) { |
| 552 | card.addLine(line: VCardLine(QStringLiteral("SORT-STRING" ), addressee.sortString())); |
| 553 | } |
| 554 | |
| 555 | // SOUND |
| 556 | card.addLine(line: createSound(snd: addressee.sound(), version)); |
| 557 | const QList<Sound> lstSound = addressee.extraSoundList(); |
| 558 | for (const Sound &sound : lstSound) { |
| 559 | card.addLine(line: createSound(snd: sound, version)); |
| 560 | } |
| 561 | |
| 562 | // TEL |
| 563 | const PhoneNumber::List phoneNumbers = addressee.phoneNumbers(); |
| 564 | processPhoneNumbers(phoneNumbers, version, card: &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(value: title.title())) { |
| 571 | titleLine.addParameter(QStringLiteral("charset" ), QStringLiteral("UTF-8" )); |
| 572 | titleLine.addParameter(QStringLiteral("encoding" ), QStringLiteral("QUOTED-PRINTABLE" )); |
| 573 | } |
| 574 | titleLine.addParameters(params: title.params()); |
| 575 | |
| 576 | card.addLine(line: 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(format: "%c%02d:%02d" , (timeZone.offset() >= 0 ? '+' : '-'), (timeZone.offset() / 60) * neg, (timeZone.offset() % 60) * neg); |
| 590 | |
| 591 | card.addLine(line: VCardLine(QStringLiteral("TZ" ), str)); |
| 592 | } |
| 593 | |
| 594 | // UID |
| 595 | card.addLine(line: VCardLine(QStringLiteral("UID" ), addressee.uid())); |
| 596 | |
| 597 | // URL |
| 598 | const QList<ResourceLocatorUrl> = addressee.extraUrlList(); |
| 599 | for (const ResourceLocatorUrl &url : lstExtraUrl) { |
| 600 | VCardLine line(QStringLiteral("URL" ), url.url()); |
| 601 | line.addParameters(params: 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()) { |
| 629 | QString type; |
| 630 | switch (url.type()) { |
| 631 | case CalendarUrl::Unknown: |
| 632 | case CalendarUrl::EndCalendarType: |
| 633 | break; |
| 634 | case CalendarUrl::FBUrl: |
| 635 | type = QStringLiteral("FBURL" ); |
| 636 | break; |
| 637 | case CalendarUrl::CALUri: |
| 638 | type = QStringLiteral("CALURI" ); |
| 639 | break; |
| 640 | case CalendarUrl::CALADRUri: |
| 641 | type = QStringLiteral("CALADRURI" ); |
| 642 | break; |
| 643 | } |
| 644 | if (!type.isEmpty()) { |
| 645 | VCardLine line(type, url.url().toDisplayString()); |
| 646 | line.addParameters(params: 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(params: 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, value: list.join(sep: QLatin1Char(','))); |
| 669 | } |
| 670 | } |
| 671 | card.addLine(line); |
| 672 | } |
| 673 | |
| 674 | // X- |
| 675 | const QStringList customs = addressee.customs(); |
| 676 | processCustoms(customs, version, card: &card, exportVcard); |
| 677 | |
| 678 | vCardList.append(t: card); |
| 679 | } |
| 680 | |
| 681 | return VCardParser::createVCards(list: vCardList); |
| 682 | } |
| 683 | |
| 684 | Addressee::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(text: 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(); |
| 699 | QStringList::ConstIterator identIt; |
| 700 | QStringList::ConstIterator identEnd(idents.end()); |
| 701 | for (identIt = idents.begin(); identIt != identEnd; ++identIt) { |
| 702 | const VCardLine::List lines = (*cardIt).lines(identifier: (*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(fieldGroup: groupField); |
| 714 | } |
| 715 | // ADR |
| 716 | else if (identifier == QLatin1String("adr" )) { |
| 717 | Address address; |
| 718 | const QStringList addrParts = splitString(sep: semicolonSep, value: (*lineIt).value().toString()); |
| 719 | const int addrPartsCount(addrParts.count()); |
| 720 | if (addrPartsCount > 0) { |
| 721 | address.setPostOfficeBox(addrParts.at(i: 0)); |
| 722 | } |
| 723 | if (addrPartsCount > 1) { |
| 724 | address.setExtended(addrParts.at(i: 1)); |
| 725 | } |
| 726 | if (addrPartsCount > 2) { |
| 727 | address.setStreet(addrParts.at(i: 2)); |
| 728 | } |
| 729 | if (addrPartsCount > 3) { |
| 730 | address.setLocality(addrParts.at(i: 3)); |
| 731 | } |
| 732 | if (addrPartsCount > 4) { |
| 733 | address.setRegion(addrParts.at(i: 4)); |
| 734 | } |
| 735 | if (addrPartsCount > 5) { |
| 736 | address.setPostalCode(addrParts.at(i: 5)); |
| 737 | } |
| 738 | if (addrPartsCount > 6) { |
| 739 | address.setCountry(addrParts.at(i: 6)); |
| 740 | } |
| 741 | |
| 742 | Address::Type type; |
| 743 | |
| 744 | const QStringList types = (*lineIt).parameters(QStringLiteral("type" )); |
| 745 | QStringList::ConstIterator end(types.end()); |
| 746 | for (QStringList::ConstIterator it = types.begin(); it != end; ++it) { |
| 747 | type |= stringToAddressType(str: (*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(i: 0) == QLatin1Char('"') && label.at(i: label.length() - 1) == QLatin1Char('"')) { |
| 755 | label = label.mid(position: 1, n: label.length() - 2); |
| 756 | } |
| 757 | } |
| 758 | address.setLabel(label); |
| 759 | } |
| 760 | QString geoStr = (*lineIt).parameter(QStringLiteral("geo" )); |
| 761 | if (!geoStr.isEmpty()) { |
| 762 | geoStr.remove(c: QLatin1Char('\"')); |
| 763 | geoStr.remove(QStringLiteral("geo:" )); |
| 764 | if (geoStr.contains(c: QLatin1Char(','))) { |
| 765 | QStringList arguments = geoStr.split(sep: QLatin1Char(',')); |
| 766 | KContacts::Geo geo; |
| 767 | geo.setLatitude(arguments.at(i: 0).toDouble()); |
| 768 | geo.setLongitude(arguments.at(i: 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(str: t)); |
| 778 | addr.insertCustom(QStringLiteral("KADDRESSBOOK" ), QStringLiteral("X-Anniversary" ), value: dt.date().toString(format: Qt::ISODate)); |
| 779 | } |
| 780 | // BDAY |
| 781 | else if (identifier == QLatin1String("bday" )) { |
| 782 | bool withTime; |
| 783 | const QDateTime bday = parseDateTime(str: (*lineIt).value().toString(), timeValid: &withTime); |
| 784 | addr.setBirthday(birthday: bday, withTime); |
| 785 | } |
| 786 | // CATEGORIES |
| 787 | else if (identifier == QLatin1String("categories" )) { |
| 788 | const QStringList categories = splitString(sep: commaSep, value: (*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(calendarUrl: 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(calendarUrl: 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(calendarUrl: 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(serviceType: (*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(line: *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(i: 0) != QLatin1Char(';')) { |
| 839 | gender.setGender(genderStr.at(i: 0)); |
| 840 | if (genderStr.length() > 2 && (genderStr.at(i: 1) == QLatin1Char(';'))) { |
| 841 | gender.setComment(genderStr.right(n: genderStr.length() - 2)); |
| 842 | } |
| 843 | } else { |
| 844 | gender.setComment(genderStr.right(n: 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(language: 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(email: 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(s: QLatin1String("geo:" ))) { // VCard 4.0 |
| 876 | lineStr.remove(QStringLiteral("geo:" )); |
| 877 | const QStringList geoParts = lineStr.split(sep: QLatin1Char(','), behavior: Qt::KeepEmptyParts); |
| 878 | if (geoParts.size() >= 2) { |
| 879 | geo.setLatitude(geoParts.at(i: 0).toFloat()); |
| 880 | geo.setLongitude(geoParts.at(i: 1).toFloat()); |
| 881 | addr.setGeo(geo); |
| 882 | } |
| 883 | } else { |
| 884 | const QStringList geoParts = lineStr.split(sep: QLatin1Char(';'), behavior: Qt::KeepEmptyParts); |
| 885 | if (geoParts.size() >= 2) { |
| 886 | geo.setLatitude(geoParts.at(i: 0).toFloat()); |
| 887 | geo.setLongitude(geoParts.at(i: 1).toFloat()); |
| 888 | addr.setGeo(geo); |
| 889 | } |
| 890 | } |
| 891 | } |
| 892 | // KEY |
| 893 | else if (identifier == QLatin1String("key" )) { |
| 894 | addr.insertKey(key: parseKey(line: *lineIt)); |
| 895 | } |
| 896 | // LABEL |
| 897 | else if (identifier == QLatin1String("label" )) { |
| 898 | Address::Type type; |
| 899 | |
| 900 | const QStringList types = (*lineIt).parameters(QStringLiteral("type" )); |
| 901 | QStringList::ConstIterator end(types.end()); |
| 902 | for (QStringList::ConstIterator it = types.begin(); it != end; ++it) { |
| 903 | type |= stringToAddressType(str: (*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(address: *it); |
| 912 | available = true; |
| 913 | break; |
| 914 | } |
| 915 | } |
| 916 | |
| 917 | if (!available) { // a standalone LABEL tag |
| 918 | KContacts::Address address(type); |
| 919 | address.setLabel((*lineIt).value().toString()); |
| 920 | addr.insertAddress(address); |
| 921 | } |
| 922 | } |
| 923 | // LOGO |
| 924 | else if (identifier == QLatin1String("logo" )) { |
| 925 | Picture picture = parsePicture(line: *lineIt); |
| 926 | if (addr.logo().isEmpty()) { |
| 927 | addr.setLogo(picture); |
| 928 | } else { |
| 929 | addr.insertExtraLogo(logo: 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(sep: semicolonSep, value: (*lineIt).value().toString()); |
| 939 | const int numberOfParts(nameParts.count()); |
| 940 | if (numberOfParts > 0) { |
| 941 | addr.setFamilyName(nameParts.at(i: 0)); |
| 942 | } |
| 943 | if (numberOfParts > 1) { |
| 944 | addr.setGivenName(nameParts.at(i: 1)); |
| 945 | } |
| 946 | if (numberOfParts > 2) { |
| 947 | addr.setAdditionalName(nameParts.at(i: 2)); |
| 948 | } |
| 949 | if (numberOfParts > 3) { |
| 950 | addr.setPrefix(nameParts.at(i: 3)); |
| 951 | } |
| 952 | if (numberOfParts > 4) { |
| 953 | addr.setSuffix(nameParts.at(i: 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(sep: semicolonSep, value: (*lineIt).value().toString()); |
| 976 | const int orgPartsCount(orgParts.count()); |
| 977 | if (orgPartsCount > 0) { |
| 978 | Org organization(orgParts.at(i: 0)); |
| 979 | organization.setParams((*lineIt).parameterMap()); |
| 980 | addr.insertExtraOrganization(organization); |
| 981 | } |
| 982 | if (orgPartsCount > 1) { |
| 983 | addr.setDepartment(orgParts.at(i: 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(line: *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(str: (*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(line: *lineIt); |
| 1019 | if (addr.sound().isEmpty()) { |
| 1020 | addr.setSound(sound); |
| 1021 | } else { |
| 1022 | addr.insertExtraSound(sound); |
| 1023 | } |
| 1024 | } |
| 1025 | // TEL |
| 1026 | else if (identifier == QLatin1String("tel" )) { |
| 1027 | PhoneNumber phone; |
| 1028 | phone.setNumber((*lineIt).value().toString()); |
| 1029 | |
| 1030 | PhoneNumber::Type type; |
| 1031 | bool foundType = false; |
| 1032 | const QStringList types = (*lineIt).parameters(QStringLiteral("type" )); |
| 1033 | QStringList::ConstIterator typeEnd(types.constEnd()); |
| 1034 | for (QStringList::ConstIterator it = types.constBegin(); it != typeEnd; ++it) { |
| 1035 | type |= stringToPhoneType(str: (*it).toUpper()); |
| 1036 | foundType = true; |
| 1037 | } |
| 1038 | phone.setType(foundType ? type : PhoneNumber::Undefined); |
| 1039 | phone.setParams((*lineIt).parameterMap()); |
| 1040 | |
| 1041 | addr.insertPhoneNumber(phoneNumber: 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(pos: 1, n: 2).toInt(); |
| 1058 | int minutes = dateView.mid(pos: 4, n: 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(url: 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(member: (*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(s: 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(other: QLatin1String("X-KADDRESSBOOK-BLOGFEED" ), cs: 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(other: QLatin1String("X-KADDRESSBOOK-X-ASSISTANTSNAME" ), cs: 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(other: QLatin1String("X-KADDRESSBOOK-X-ANNIVERSARY" ), cs: 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(other: QLatin1String("X-KADDRESSBOOK-X-MANAGERSNAME" ), cs: Qt::CaseInsensitive) == 0) { |
| 1124 | // clang-format on |
| 1125 | ident = QStringLiteral("X-KADDRESSBOOK-X-ManagersName" ); |
| 1126 | } else if (identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-PROFESSION" ), cs: Qt::CaseInsensitive) == 0) { |
| 1127 | ident = QStringLiteral("X-KADDRESSBOOK-X-Profession" ); |
| 1128 | } else if (identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-OFFICE" ), cs: Qt::CaseInsensitive) == 0) { |
| 1129 | ident = QStringLiteral("X-KADDRESSBOOK-X-Office" ); |
| 1130 | } else if (identifier.compare(other: QLatin1String("X-KADDRESSBOOK-X-SPOUSESNAME" ), cs: 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(position: 2); |
| 1163 | const int dash = key.indexOf(ch: QLatin1Char('-')); |
| 1164 | |
| 1165 | // convert legacy messaging fields into IMPP ones |
| 1166 | if (key.startsWith(s: QLatin1String("messaging/" ))) { |
| 1167 | QUrl url; |
| 1168 | url.setScheme(normalizeImppServiceType(serviceType: key.mid(position: 10, n: dash - 10))); |
| 1169 | const auto values = (*lineIt).value().toString().split(sep: QChar(0xE000), behavior: Qt::SkipEmptyParts); |
| 1170 | for (const auto &value : values) { |
| 1171 | url.setPath(path: value); |
| 1172 | Impp impp; |
| 1173 | impp.setParams((*lineIt).parameterMap()); |
| 1174 | impp.setAddress(url); |
| 1175 | addr.insertImpp(impp); |
| 1176 | } |
| 1177 | } else { |
| 1178 | addr.insertCustom(app: key.left(n: dash), name: key.mid(position: dash + 1), value: (*lineIt).value().toString()); |
| 1179 | } |
| 1180 | } |
| 1181 | } |
| 1182 | } |
| 1183 | |
| 1184 | addrList.append(t: addr); |
| 1185 | } |
| 1186 | |
| 1187 | return addrList; |
| 1188 | } |
| 1189 | |
| 1190 | QDateTime VCardTool::parseDateTime(const QString &str, bool *timeValid) |
| 1191 | { |
| 1192 | static const QLatin1Char sep('-'); |
| 1193 | |
| 1194 | const int posT = str.indexOf(ch: QLatin1Char('T')); |
| 1195 | QString dateString = posT >= 0 ? str.left(n: posT) : str; |
| 1196 | const bool noYear = dateString.startsWith(s: QLatin1String("--" )); |
| 1197 | dateString.remove(c: QLatin1Char('-')); |
| 1198 | QDate date; |
| 1199 | |
| 1200 | const QStringView dstr{dateString}; |
| 1201 | if (noYear) { |
| 1202 | date.setDate(year: -1, month: dstr.mid(pos: 0, n: 2).toInt(), day: dstr.mid(pos: 2, n: 2).toInt()); |
| 1203 | } else { |
| 1204 | // E.g. 20160120 |
| 1205 | 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()); |
| 1206 | } |
| 1207 | |
| 1208 | QTime time; |
| 1209 | QTimeZone tz = QTimeZone::LocalTime; |
| 1210 | if (posT >= 0) { |
| 1211 | QString timeString = str.mid(position: posT + 1); |
| 1212 | timeString.remove(c: QLatin1Char(':')); |
| 1213 | const int zPos = timeString.indexOf(ch: QLatin1Char('Z')); |
| 1214 | const int plusPos = timeString.indexOf(ch: QLatin1Char('+')); |
| 1215 | const int minusPos = timeString.indexOf(ch: sep); |
| 1216 | const int tzPos = qMax(a: qMax(a: zPos, b: plusPos), b: minusPos); |
| 1217 | const QStringView hhmmssString = tzPos >= 0 ? QStringView(timeString).left(n: 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(pos: 0, n: 2).toInt(); |
| 1227 | minutes = hhmmssString.mid(pos: 2, n: 2).toInt(); |
| 1228 | break; |
| 1229 | case 6: |
| 1230 | hour = hhmmssString.mid(pos: 0, n: 2).toInt(); |
| 1231 | minutes = hhmmssString.mid(pos: 2, n: 2).toInt(); |
| 1232 | seconds = hhmmssString.mid(pos: 4, n: 2).toInt(); |
| 1233 | break; |
| 1234 | } |
| 1235 | time.setHMS(h: hour, m: minutes, s: 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(pos: tzPos + 1); |
| 1243 | switch (offsetString.size()) { |
| 1244 | case 2: // format: "hh" |
| 1245 | offsetSecs = offsetString.left(n: 2).toInt() * 3600; |
| 1246 | break; |
| 1247 | case 4: // format: "hhmm" |
| 1248 | offsetSecs = offsetString.left(n: 2).toInt() * 3600 + offsetString.mid(pos: 2, n: 2).toInt() * 60; |
| 1249 | break; |
| 1250 | } |
| 1251 | if (minusPos >= 0) { |
| 1252 | offsetSecs *= -1; |
| 1253 | } |
| 1254 | tz = QTimeZone::fromSecondsAheadOfUtc(offset: offsetSecs); |
| 1255 | } |
| 1256 | } |
| 1257 | } |
| 1258 | if (timeValid) { |
| 1259 | *timeValid = time.isValid(); |
| 1260 | } |
| 1261 | |
| 1262 | return QDateTime(date, time, tz); |
| 1263 | } |
| 1264 | |
| 1265 | QString VCardTool::createDateTime(const QDateTime &dateTime, VCard::Version version, bool withTime) |
| 1266 | { |
| 1267 | if (!dateTime.date().isValid()) { |
| 1268 | return QString(); |
| 1269 | } |
| 1270 | QString str = createDate(date: dateTime.date(), version); |
| 1271 | if (!withTime) { |
| 1272 | return str; |
| 1273 | } |
| 1274 | str += createTime(time: 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(secs: abs(x: offsetSecs)); |
| 1285 | if (version == VCard::v4_0) { |
| 1286 | str += offsetTime.toString(QStringLiteral("HHmm" )); |
| 1287 | } else { |
| 1288 | str += offsetTime.toString(QStringLiteral("HH:mm" )); |
| 1289 | } |
| 1290 | } |
| 1291 | return str; |
| 1292 | } |
| 1293 | |
| 1294 | QString VCardTool::createDate(const QDate &date, VCard::Version version) |
| 1295 | { |
| 1296 | QString format; |
| 1297 | if (date.year() > 0) { |
| 1298 | format = QStringLiteral("yyyyMMdd" ); |
| 1299 | } else { |
| 1300 | format = QStringLiteral("--MMdd" ); |
| 1301 | } |
| 1302 | if (version != VCard::v4_0) { |
| 1303 | format.replace(QStringLiteral("yyyy" ), QStringLiteral("yyyy-" )); |
| 1304 | format.replace(QStringLiteral("MM" ), QStringLiteral("MM-" )); |
| 1305 | } |
| 1306 | return date.toString(format); |
| 1307 | } |
| 1308 | |
| 1309 | QString VCardTool::createTime(const QTime &time, VCard::Version version) |
| 1310 | { |
| 1311 | QString format; |
| 1312 | if (version == VCard::v4_0) { |
| 1313 | format = QStringLiteral("HHmmss" ); |
| 1314 | } else { |
| 1315 | format = QStringLiteral("HH:mm:ss" ); |
| 1316 | } |
| 1317 | return QLatin1Char('T') + time.toString(format); |
| 1318 | } |
| 1319 | |
| 1320 | Picture VCardTool::parsePicture(const VCardLine &line) const |
| 1321 | { |
| 1322 | Picture pic; |
| 1323 | |
| 1324 | const QStringList params = line.parameterList(); |
| 1325 | QString type; |
| 1326 | if (params.contains(str: QLatin1String("type" ))) { |
| 1327 | type = line.parameter(QStringLiteral("type" )); |
| 1328 | } |
| 1329 | if (params.contains(str: QLatin1String("encoding" ))) { |
| 1330 | pic.setRawData(rawData: line.value().toByteArray(), type); |
| 1331 | } else if (params.contains(str: QLatin1String("value" ))) { |
| 1332 | if (line.parameter(QStringLiteral("value" )).toLower() == QLatin1String("uri" )) { |
| 1333 | pic.setUrl(line.value().toString()); |
| 1334 | } |
| 1335 | } |
| 1336 | |
| 1337 | return pic; |
| 1338 | } |
| 1339 | |
| 1340 | VCardLine VCardTool::createPicture(const QString &identifier, const Picture &pic, VCard::Version version) const |
| 1341 | { |
| 1342 | VCardLine line(identifier); |
| 1343 | |
| 1344 | if (pic.isEmpty()) { |
| 1345 | return line; |
| 1346 | } |
| 1347 | |
| 1348 | if (pic.isIntern()) { |
| 1349 | line.setValue(pic.rawData()); |
| 1350 | if (version == VCard::v2_1) { |
| 1351 | line.addParameter(QStringLiteral("ENCODING" ), QStringLiteral("BASE64" )); |
| 1352 | line.addParameter(param: pic.type(), value: QString()); |
| 1353 | } else { /*if (version == VCard::v3_0) */ |
| 1354 | line.addParameter(QStringLiteral("encoding" ), QStringLiteral("b" )); |
| 1355 | line.addParameter(QStringLiteral("type" ), value: pic.type()); |
| 1356 | #if 0 |
| 1357 | } else { //version 4.0 |
| 1358 | line.addParameter(QStringLiteral("data" ) + QStringLiteral(":image/" ) + pic.type(), QStringLiteral("base64" )); |
| 1359 | #endif |
| 1360 | } |
| 1361 | } else { |
| 1362 | line.setValue(pic.url()); |
| 1363 | line.addParameter(QStringLiteral("value" ), QStringLiteral("URI" )); |
| 1364 | } |
| 1365 | |
| 1366 | return line; |
| 1367 | } |
| 1368 | |
| 1369 | Sound VCardTool::parseSound(const VCardLine &line) const |
| 1370 | { |
| 1371 | Sound snd; |
| 1372 | |
| 1373 | const QStringList params = line.parameterList(); |
| 1374 | if (params.contains(str: QLatin1String("encoding" ))) { |
| 1375 | snd.setData(line.value().toByteArray()); |
| 1376 | } else if (params.contains(str: QLatin1String("value" ))) { |
| 1377 | if (line.parameter(QStringLiteral("value" )).toLower() == QLatin1String("uri" )) { |
| 1378 | snd.setUrl(line.value().toString()); |
| 1379 | } |
| 1380 | } |
| 1381 | |
| 1382 | /* TODO: support sound types |
| 1383 | if ( params.contains( "type" ) ) |
| 1384 | snd.setType( line.parameter( "type" ) ); |
| 1385 | */ |
| 1386 | |
| 1387 | return snd; |
| 1388 | } |
| 1389 | |
| 1390 | VCardLine VCardTool::createSound(const Sound &snd, VCard::Version version) const |
| 1391 | { |
| 1392 | Q_UNUSED(version); |
| 1393 | VCardLine line(QStringLiteral("SOUND" )); |
| 1394 | |
| 1395 | if (snd.isIntern()) { |
| 1396 | if (!snd.data().isEmpty()) { |
| 1397 | line.setValue(snd.data()); |
| 1398 | if (version == VCard::v2_1) { |
| 1399 | line.addParameter(QStringLiteral("ENCODING" ), QStringLiteral("BASE64" )); |
| 1400 | } else { |
| 1401 | line.addParameter(QStringLiteral("encoding" ), QStringLiteral("b" )); |
| 1402 | } |
| 1403 | // TODO: need to store sound type!!! |
| 1404 | } |
| 1405 | } else if (!snd.url().isEmpty()) { |
| 1406 | line.setValue(snd.url()); |
| 1407 | line.addParameter(QStringLiteral("value" ), QStringLiteral("URI" )); |
| 1408 | } |
| 1409 | |
| 1410 | return line; |
| 1411 | } |
| 1412 | |
| 1413 | Key VCardTool::parseKey(const VCardLine &line) const |
| 1414 | { |
| 1415 | Key key; |
| 1416 | |
| 1417 | const QStringList params = line.parameterList(); |
| 1418 | if (params.contains(str: QLatin1String("encoding" ))) { |
| 1419 | key.setBinaryData(line.value().toByteArray()); |
| 1420 | } else { |
| 1421 | key.setTextData(line.value().toString()); |
| 1422 | } |
| 1423 | |
| 1424 | if (params.contains(str: 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(str: QLatin1String("mediatype" ))) { |
| 1434 | const QString param = line.parameter(QStringLiteral("mediatype" )).toLower(); |
| 1435 | if (param == QLatin1String("application/x-x509-ca-cert" )) { |
| 1436 | key.setType(Key::X509); |
| 1437 | } else if (param == QLatin1String("application/pgp-keys" )) { |
| 1438 | key.setType(Key::PGP); |
| 1439 | } else { |
| 1440 | key.setType(Key::Custom); |
| 1441 | key.setCustomTypeString(line.parameter(QStringLiteral("type" ))); |
| 1442 | } |
| 1443 | } |
| 1444 | |
| 1445 | return key; |
| 1446 | } |
| 1447 | |
| 1448 | VCardLine VCardTool::createKey(const Key &key, VCard::Version version) const |
| 1449 | { |
| 1450 | VCardLine line(QStringLiteral("KEY" )); |
| 1451 | |
| 1452 | if (key.isBinary()) { |
| 1453 | if (!key.binaryData().isEmpty()) { |
| 1454 | line.setValue(key.binaryData()); |
| 1455 | if (version == VCard::v2_1) { |
| 1456 | line.addParameter(QStringLiteral("ENCODING" ), QStringLiteral("BASE64" )); |
| 1457 | } else { |
| 1458 | line.addParameter(QStringLiteral("encoding" ), QStringLiteral("b" )); |
| 1459 | } |
| 1460 | } |
| 1461 | } else if (!key.textData().isEmpty()) { |
| 1462 | line.setValue(key.textData()); |
| 1463 | } |
| 1464 | |
| 1465 | if (version == VCard::v4_0) { |
| 1466 | if (key.type() == Key::X509) { |
| 1467 | line.addParameter(QStringLiteral("MEDIATYPE" ), QStringLiteral("application/x-x509-ca-cert" )); |
| 1468 | } else if (key.type() == Key::PGP) { |
| 1469 | line.addParameter(QStringLiteral("MEDIATYPE" ), QStringLiteral("application/pgp-keys" )); |
| 1470 | } else if (key.type() == Key::Custom) { |
| 1471 | line.addParameter(QStringLiteral("MEDIATYPE" ), value: 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" ), value: key.customTypeString()); |
| 1480 | } |
| 1481 | } |
| 1482 | |
| 1483 | return line; |
| 1484 | } |
| 1485 | |
| 1486 | Secrecy VCardTool::parseSecrecy(const VCardLine &line) const |
| 1487 | { |
| 1488 | Secrecy secrecy; |
| 1489 | |
| 1490 | const QString value = line.value().toString().toLower(); |
| 1491 | if (value == QLatin1String("public" )) { |
| 1492 | secrecy.setType(Secrecy::Public); |
| 1493 | } else if (value == QLatin1String("private" )) { |
| 1494 | secrecy.setType(Secrecy::Private); |
| 1495 | } else if (value == QLatin1String("confidential" )) { |
| 1496 | secrecy.setType(Secrecy::Confidential); |
| 1497 | } |
| 1498 | |
| 1499 | return secrecy; |
| 1500 | } |
| 1501 | |
| 1502 | VCardLine VCardTool::createSecrecy(const Secrecy &secrecy) const |
| 1503 | { |
| 1504 | VCardLine line(QStringLiteral("CLASS" )); |
| 1505 | |
| 1506 | int type = secrecy.type(); |
| 1507 | |
| 1508 | if (type == Secrecy::Public) { |
| 1509 | line.setValue(QStringLiteral("PUBLIC" )); |
| 1510 | } else if (type == Secrecy::Private) { |
| 1511 | line.setValue(QStringLiteral("PRIVATE" )); |
| 1512 | } else if (type == Secrecy::Confidential) { |
| 1513 | line.setValue(QStringLiteral("CONFIDENTIAL" )); |
| 1514 | } |
| 1515 | |
| 1516 | return line; |
| 1517 | } |
| 1518 | |
| 1519 | QStringList VCardTool::splitString(QChar sep, const QString &str) const |
| 1520 | { |
| 1521 | QStringList list; |
| 1522 | QString value(str); |
| 1523 | |
| 1524 | int start = 0; |
| 1525 | int pos = value.indexOf(ch: sep, from: 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(position: start, n: pos - start); |
| 1531 | } else { |
| 1532 | list << QString(); |
| 1533 | } |
| 1534 | |
| 1535 | start = pos + 1; |
| 1536 | pos = value.indexOf(ch: sep, from: start); |
| 1537 | } else { |
| 1538 | value.replace(i: pos - 1, len: 2, after: sep); |
| 1539 | pos = value.indexOf(ch: sep, from: pos); |
| 1540 | } |
| 1541 | } |
| 1542 | |
| 1543 | int l = value.length() - 1; |
| 1544 | const QString mid = value.mid(position: start, n: l - start + 1); |
| 1545 | if (!mid.isEmpty()) { |
| 1546 | list << mid; |
| 1547 | } else { |
| 1548 | list << QString(); |
| 1549 | } |
| 1550 | |
| 1551 | return list; |
| 1552 | } |
| 1553 | |
| 1554 | QString VCardTool::normalizeImppServiceType(const QString &serviceType) const |
| 1555 | { |
| 1556 | if (serviceType == QLatin1String("jabber" )) { |
| 1557 | return QStringLiteral("xmpp" ); |
| 1558 | } |
| 1559 | if (serviceType == QLatin1String("yahoo" )) { |
| 1560 | return QStringLiteral("ymsgr" ); |
| 1561 | } |
| 1562 | if (serviceType == QLatin1String("gadugadu" )) { |
| 1563 | return QStringLiteral("gg" ); |
| 1564 | } |
| 1565 | return serviceType; |
| 1566 | } |
| 1567 | |