1 /*
2  * SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com>
3  *
4  * SPDX-License-Identifier: LGPL-3.0-or-later
5  */
6 
7 #include "kolabobject.h"
8 #include "libkolab-version.h"
9 #include "pimkolab_debug.h"
10 #include "v2helpers.h"
11 
12 #include "kolabbase.h"
13 
14 #include <Akonadi/Notes/NoteUtils>
15 
16 #include <conversion/commonconversion.h>
17 #include <conversion/kabcconversion.h>
18 #include <conversion/kcalconversion.h>
19 #include <conversion/kolabconversion.h>
20 #include <kolabformat.h>
21 #include <kolabformat/mimeobject.h>
22 #include <kolabformatV2/contact.h>
23 #include <kolabformatV2/distributionlist.h>
24 #include <kolabformatV2/event.h>
25 #include <kolabformatV2/journal.h>
26 #include <kolabformatV2/note.h>
27 #include <kolabformatV2/task.h>
28 #include <mime/mimeutils.h>
29 
30 #include <QUrlQuery>
31 
32 namespace Kolab
33 {
eventKolabType()34 static inline QString eventKolabType()
35 {
36     return QStringLiteral(KOLAB_TYPE_EVENT);
37 }
38 
todoKolabType()39 static inline QString todoKolabType()
40 {
41     return QStringLiteral(KOLAB_TYPE_TASK);
42 }
43 
journalKolabType()44 static inline QString journalKolabType()
45 {
46     return QStringLiteral(KOLAB_TYPE_JOURNAL);
47 }
48 
contactKolabType()49 static inline QString contactKolabType()
50 {
51     return QStringLiteral(KOLAB_TYPE_CONTACT);
52 }
53 
distlistKolabType()54 static inline QString distlistKolabType()
55 {
56     return QStringLiteral(KOLAB_TYPE_DISTLIST);
57 }
58 
distlistKolabTypeCompat()59 static inline QString distlistKolabTypeCompat()
60 {
61     return QStringLiteral(KOLAB_TYPE_DISTLIST_V2);
62 }
63 
noteKolabType()64 static inline QString noteKolabType()
65 {
66     return QStringLiteral(KOLAB_TYPE_NOTE);
67 }
68 
configurationKolabType()69 static inline QString configurationKolabType()
70 {
71     return QStringLiteral(KOLAB_TYPE_CONFIGURATION);
72 }
73 
dictKolabType()74 static inline QString dictKolabType()
75 {
76     return QStringLiteral(KOLAB_TYPE_DICT);
77 }
78 
freebusyKolabType()79 static inline QString freebusyKolabType()
80 {
81     return QStringLiteral(KOLAB_TYPE_FREEBUSY);
82 }
83 
relationKolabType()84 static inline QString relationKolabType()
85 {
86     return QStringLiteral(KOLAB_TYPE_RELATION);
87 }
88 
xCalMimeType()89 static inline QString xCalMimeType()
90 {
91     return QStringLiteral(MIME_TYPE_XCAL);
92 }
93 
xCardMimeType()94 static inline QString xCardMimeType()
95 {
96     return QStringLiteral(MIME_TYPE_XCARD);
97 }
98 
kolabMimeType()99 static inline QString kolabMimeType()
100 {
101     return QStringLiteral(MIME_TYPE_KOLAB);
102 }
103 
readV2EventXML(const QByteArray & xmlData,QStringList & attachments)104 KCalendarCore::Event::Ptr readV2EventXML(const QByteArray &xmlData, QStringList &attachments)
105 {
106     return fromXML<KCalendarCore::Event::Ptr, KolabV2::Event>(xmlData, attachments);
107 }
108 
ownUrlDecode(QByteArray encodedParam)109 QString ownUrlDecode(QByteArray encodedParam)
110 {
111     encodedParam.replace('+', ' ');
112     return QUrl::fromPercentEncoding(encodedParam);
113 }
114 
parseMemberUrl(const QString & string)115 RelationMember parseMemberUrl(const QString &string)
116 {
117     if (string.startsWith(QLatin1String("urn:uuid:"))) {
118         RelationMember member;
119         member.gid = string.mid(9);
120         return member;
121     }
122     QUrl url(QUrl::fromEncoded(string.toLatin1()));
123     QList<QByteArray> path;
124     const QList<QByteArray> fragments = url.path(QUrl::FullyEncoded).toLatin1().split('/');
125     path.reserve(fragments.count());
126     for (const QByteArray &fragment : fragments) {
127         path.append(ownUrlDecode(fragment).toUtf8());
128     }
129     // qCDebug(PIMKOLAB_LOG) << path;
130     bool isShared = false;
131     int start = path.indexOf("user");
132     if (start < 0) {
133         start = path.indexOf("shared");
134         isShared = true;
135     }
136     if (start < 0) {
137         qCWarning(PIMKOLAB_LOG) << R"(Couldn't find "user" or "shared" in path: )" << path;
138         return RelationMember();
139     }
140     path = path.mid(start + 1);
141     if (path.size() < 2) {
142         qCWarning(PIMKOLAB_LOG) << "Incomplete path: " << path;
143         return RelationMember();
144     }
145     RelationMember member;
146     if (!isShared) {
147         member.user = QString::fromUtf8(path.takeFirst());
148     }
149     member.uid = path.takeLast().toLong();
150     member.mailbox = path;
151     QUrlQuery query(url);
152     member.messageId = ownUrlDecode(query.queryItemValue(QStringLiteral("message-id"), QUrl::FullyEncoded).toUtf8());
153     member.subject = ownUrlDecode(query.queryItemValue(QStringLiteral("subject"), QUrl::FullyEncoded).toUtf8());
154     member.date = ownUrlDecode(query.queryItemValue(QStringLiteral("date"), QUrl::FullyEncoded).toUtf8());
155     // qCDebug(PIMKOLAB_LOG) << member.uid << member.mailbox;
156     return member;
157 }
158 
join(const QList<QByteArray> & list,const QByteArray & c)159 static QByteArray join(const QList<QByteArray> &list, const QByteArray &c)
160 {
161     QByteArray result;
162     for (const QByteArray &a : list) {
163         result += a + c;
164     }
165     result.chop(c.size());
166     return result;
167 }
168 
generateMemberUrl(const RelationMember & member)169 KOLAB_EXPORT QString generateMemberUrl(const RelationMember &member)
170 {
171     if (!member.gid.isEmpty()) {
172         return QStringLiteral("urn:uuid:%1").arg(member.gid);
173     }
174     QUrl url;
175     url.setScheme(QStringLiteral("imap"));
176     QList<QByteArray> path;
177     path << "/";
178     if (!member.user.isEmpty()) {
179         path << "user";
180         path << QUrl::toPercentEncoding(member.user);
181     } else {
182         path << "shared";
183     }
184     const auto memberMailbox{member.mailbox};
185     for (const QByteArray &mb : memberMailbox) {
186         path << QUrl::toPercentEncoding(QString::fromUtf8(mb));
187     }
188     path << QByteArray::number(member.uid);
189     url.setPath(QString::fromUtf8('/' + join(path, "/")), QUrl::TolerantMode);
190 
191     QUrlQuery query;
192     query.addQueryItem(QStringLiteral("message-id"), member.messageId);
193     query.addQueryItem(QStringLiteral("subject"), member.subject);
194     query.addQueryItem(QStringLiteral("date"), member.date);
195     url.setQuery(query);
196 
197     return QString::fromLatin1(url.toEncoded());
198 }
199 
200 //@cond PRIVATE
201 class KolabObjectReaderPrivate
202 {
203 public:
KolabObjectReaderPrivate()204     KolabObjectReaderPrivate()
205         : mAddressee(KContacts::Addressee())
206         , mObjectType(InvalidObject)
207         , mVersion(KolabV3)
208         , mOverrideObjectType(InvalidObject)
209     {
210     }
211 
212     KCalendarCore::Incidence::Ptr mIncidence;
213     KContacts::Addressee mAddressee;
214     KContacts::ContactGroup mContactGroup;
215     KMime::Message::Ptr mNote;
216     QStringList mDictionary;
217     QString mDictionaryLanguage;
218     ObjectType mObjectType;
219     Version mVersion;
220     Kolab::Freebusy mFreebusy;
221     ObjectType mOverrideObjectType;
222     Version mOverrideVersion;
223     bool mDoOverrideVersion = false;
224     Akonadi::Relation mRelation;
225     Akonadi::Tag mTag;
226     QStringList mTagMembers;
227 };
228 //@endcond
229 
KolabObjectReader()230 KolabObjectReader::KolabObjectReader()
231     : d(new KolabObjectReaderPrivate)
232 {
233 }
234 
KolabObjectReader(const KMime::Message::Ptr & msg)235 KolabObjectReader::KolabObjectReader(const KMime::Message::Ptr &msg)
236     : d(new KolabObjectReaderPrivate)
237 {
238     parseMimeMessage(msg);
239 }
240 
241 KolabObjectReader::~KolabObjectReader() = default;
242 
setObjectType(ObjectType type)243 void KolabObjectReader::setObjectType(ObjectType type)
244 {
245     d->mOverrideObjectType = type;
246 }
247 
setVersion(Version version)248 void KolabObjectReader::setVersion(Version version)
249 {
250     d->mOverrideVersion = version;
251     d->mDoOverrideVersion = true;
252 }
253 
printMessageDebugInfo(const KMime::Message::Ptr & msg)254 void printMessageDebugInfo(const KMime::Message::Ptr &msg)
255 {
256     // TODO replace by Debug stream for Mimemessage
257     qCDebug(PIMKOLAB_LOG) << "MessageId: " << msg->messageID()->asUnicodeString();
258     qCDebug(PIMKOLAB_LOG) << "Subject: " << msg->subject()->asUnicodeString();
259     //     Debug() << msg->encodedContent();
260 }
261 
parseMimeMessage(const KMime::Message::Ptr & msg)262 ObjectType KolabObjectReader::parseMimeMessage(const KMime::Message::Ptr &msg)
263 {
264     ErrorHandler::clearErrors();
265     d->mObjectType = InvalidObject;
266     if (msg->contents().isEmpty()) {
267         qCCritical(PIMKOLAB_LOG) << "message has no contents (we likely failed to parse it correctly)";
268         printMessageDebugInfo(msg);
269         return InvalidObject;
270     }
271     const std::string message = msg->encodedContent().toStdString();
272     Kolab::MIMEObject mimeObject;
273     mimeObject.setObjectType(d->mOverrideObjectType);
274     if (d->mDoOverrideVersion) {
275         mimeObject.setVersion(d->mOverrideVersion);
276     }
277     d->mObjectType = mimeObject.parseMessage(message);
278     d->mVersion = mimeObject.getVersion();
279     switch (mimeObject.getType()) {
280     case EventObject: {
281         const Kolab::Event &event = mimeObject.getEvent();
282         d->mIncidence = Kolab::Conversion::toKCalendarCore(event);
283         break;
284     }
285     case TodoObject: {
286         const Kolab::Todo &event = mimeObject.getTodo();
287         d->mIncidence = Kolab::Conversion::toKCalendarCore(event);
288         break;
289     }
290     case JournalObject: {
291         const Kolab::Journal &event = mimeObject.getJournal();
292         d->mIncidence = Kolab::Conversion::toKCalendarCore(event);
293         break;
294     }
295     case ContactObject: {
296         const Kolab::Contact &contact = mimeObject.getContact();
297         d->mAddressee = Kolab::Conversion::toKABC(contact); // TODO extract attachments
298         break;
299     }
300     case DistlistObject: {
301         const Kolab::DistList &distlist = mimeObject.getDistlist();
302         d->mContactGroup = Kolab::Conversion::toKABC(distlist);
303         break;
304     }
305     case NoteObject: {
306         const Kolab::Note &note = mimeObject.getNote();
307         d->mNote = Kolab::Conversion::toNote(note);
308         break;
309     }
310     case DictionaryConfigurationObject: {
311         const Kolab::Configuration &configuration = mimeObject.getConfiguration();
312         const Kolab::Dictionary &dictionary = configuration.dictionary();
313         d->mDictionary.clear();
314         const std::vector<std::string> entries = dictionary.entries();
315         d->mDictionary.reserve(entries.size());
316         for (const std::string &entry : entries) {
317             d->mDictionary.append(Conversion::fromStdString(entry));
318         }
319         d->mDictionaryLanguage = Conversion::fromStdString(dictionary.language());
320         break;
321     }
322     case FreebusyObject: {
323         const Kolab::Freebusy &fb = mimeObject.getFreebusy();
324         d->mFreebusy = fb;
325         break;
326     }
327     case RelationConfigurationObject: {
328         const Kolab::Configuration &configuration = mimeObject.getConfiguration();
329         const Kolab::Relation &relation = configuration.relation();
330 
331         if (relation.type() == "tag") {
332             d->mTag = Akonadi::Tag();
333             d->mTag.setName(Conversion::fromStdString(relation.name()));
334             d->mTag.setGid(Conversion::fromStdString(configuration.uid()).toLatin1());
335             d->mTag.setType(Akonadi::Tag::GENERIC);
336 
337             d->mTagMembers.reserve(relation.members().size());
338             const auto members{relation.members()};
339             for (const std::string &member : members) {
340                 d->mTagMembers << Conversion::fromStdString(member);
341             }
342         } else if (relation.type() == "generic") {
343             if (relation.members().size() == 2) {
344                 d->mRelation = Akonadi::Relation();
345                 d->mRelation.setRemoteId(Conversion::fromStdString(configuration.uid()).toLatin1());
346                 d->mRelation.setType(Akonadi::Relation::GENERIC);
347 
348                 d->mTagMembers.reserve(relation.members().size());
349                 const auto members{relation.members()};
350                 for (const std::string &member : members) {
351                     d->mTagMembers << Conversion::fromStdString(member);
352                 }
353             } else {
354                 qCCritical(PIMKOLAB_LOG) << "generic relation had wrong number of members:" << relation.members().size();
355                 printMessageDebugInfo(msg);
356             }
357         } else {
358             qCCritical(PIMKOLAB_LOG) << "unknown configuration object type" << relation.type();
359             printMessageDebugInfo(msg);
360         }
361         break;
362     }
363     default:
364         qCCritical(PIMKOLAB_LOG) << "no kolab object found ";
365         printMessageDebugInfo(msg);
366         break;
367     }
368     return d->mObjectType;
369 }
370 
getVersion() const371 Version KolabObjectReader::getVersion() const
372 {
373     return d->mVersion;
374 }
375 
getType() const376 ObjectType KolabObjectReader::getType() const
377 {
378     return d->mObjectType;
379 }
380 
getEvent() const381 KCalendarCore::Event::Ptr KolabObjectReader::getEvent() const
382 {
383     return d->mIncidence.dynamicCast<KCalendarCore::Event>();
384 }
385 
getTodo() const386 KCalendarCore::Todo::Ptr KolabObjectReader::getTodo() const
387 {
388     return d->mIncidence.dynamicCast<KCalendarCore::Todo>();
389 }
390 
getJournal() const391 KCalendarCore::Journal::Ptr KolabObjectReader::getJournal() const
392 {
393     return d->mIncidence.dynamicCast<KCalendarCore::Journal>();
394 }
395 
getIncidence() const396 KCalendarCore::Incidence::Ptr KolabObjectReader::getIncidence() const
397 {
398     return d->mIncidence;
399 }
400 
getContact() const401 KContacts::Addressee KolabObjectReader::getContact() const
402 {
403     return d->mAddressee;
404 }
405 
getDistlist() const406 KContacts::ContactGroup KolabObjectReader::getDistlist() const
407 {
408     return d->mContactGroup;
409 }
410 
getNote() const411 KMime::Message::Ptr KolabObjectReader::getNote() const
412 {
413     return d->mNote;
414 }
415 
getDictionary(QString & lang) const416 QStringList KolabObjectReader::getDictionary(QString &lang) const
417 {
418     lang = d->mDictionaryLanguage;
419     return d->mDictionary;
420 }
421 
getFreebusy() const422 Freebusy KolabObjectReader::getFreebusy() const
423 {
424     return d->mFreebusy;
425 }
426 
isTag() const427 bool KolabObjectReader::isTag() const
428 {
429     return !d->mTag.gid().isEmpty();
430 }
431 
getTag() const432 Akonadi::Tag KolabObjectReader::getTag() const
433 {
434     return d->mTag;
435 }
436 
getTagMembers() const437 QStringList KolabObjectReader::getTagMembers() const
438 {
439     return d->mTagMembers;
440 }
441 
isRelation() const442 bool KolabObjectReader::isRelation() const
443 {
444     return d->mRelation.isValid();
445 }
446 
getRelation() const447 Akonadi::Relation KolabObjectReader::getRelation() const
448 {
449     return d->mRelation;
450 }
451 
createMimeMessage(const std::string & mimeMessage)452 static KMime::Message::Ptr createMimeMessage(const std::string &mimeMessage)
453 {
454     KMime::Message::Ptr msg(new KMime::Message);
455     msg->setContent(QByteArray(mimeMessage.c_str()));
456     msg->parse();
457     return msg;
458 }
459 
writeEvent(const KCalendarCore::Event::Ptr & i,Version v,const QString & productId,const QString &)460 KMime::Message::Ptr KolabObjectWriter::writeEvent(const KCalendarCore::Event::Ptr &i, Version v, const QString &productId, const QString &)
461 {
462     ErrorHandler::clearErrors();
463     if (!i) {
464         qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
465         return KMime::Message::Ptr();
466     }
467     const Kolab::Event &event = Kolab::Conversion::fromKCalendarCore(*i);
468     Kolab::MIMEObject mimeObject;
469     const std::string mimeMessage = mimeObject.writeEvent(event, v, productId.toStdString());
470     return createMimeMessage(mimeMessage);
471 }
472 
writeTodo(const KCalendarCore::Todo::Ptr & i,Version v,const QString & productId,const QString &)473 KMime::Message::Ptr KolabObjectWriter::writeTodo(const KCalendarCore::Todo::Ptr &i, Version v, const QString &productId, const QString &)
474 {
475     ErrorHandler::clearErrors();
476     if (!i) {
477         qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
478         return KMime::Message::Ptr();
479     }
480     const Kolab::Todo &todo = Kolab::Conversion::fromKCalendarCore(*i);
481     Kolab::MIMEObject mimeObject;
482     const std::string mimeMessage = mimeObject.writeTodo(todo, v, productId.toStdString());
483     return createMimeMessage(mimeMessage);
484 }
485 
writeJournal(const KCalendarCore::Journal::Ptr & i,Version v,const QString & productId,const QString &)486 KMime::Message::Ptr KolabObjectWriter::writeJournal(const KCalendarCore::Journal::Ptr &i, Version v, const QString &productId, const QString &)
487 {
488     ErrorHandler::clearErrors();
489     if (!i) {
490         qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
491         return KMime::Message::Ptr();
492     }
493     const Kolab::Journal &journal = Kolab::Conversion::fromKCalendarCore(*i);
494     Kolab::MIMEObject mimeObject;
495     const std::string mimeMessage = mimeObject.writeJournal(journal, v, productId.toStdString());
496     return createMimeMessage(mimeMessage);
497 }
498 
writeIncidence(const KCalendarCore::Incidence::Ptr & i,Version v,const QString & productId,const QString & tz)499 KMime::Message::Ptr KolabObjectWriter::writeIncidence(const KCalendarCore::Incidence::Ptr &i, Version v, const QString &productId, const QString &tz)
500 {
501     if (!i) {
502         qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
503         return KMime::Message::Ptr();
504     }
505     switch (i->type()) {
506     case KCalendarCore::IncidenceBase::TypeEvent:
507         return writeEvent(i.dynamicCast<KCalendarCore::Event>(), v, productId, tz);
508     case KCalendarCore::IncidenceBase::TypeTodo:
509         return writeTodo(i.dynamicCast<KCalendarCore::Todo>(), v, productId, tz);
510     case KCalendarCore::IncidenceBase::TypeJournal:
511         return writeJournal(i.dynamicCast<KCalendarCore::Journal>(), v, productId, tz);
512     default:
513         qCCritical(PIMKOLAB_LOG) << "unknown incidence type";
514     }
515     return KMime::Message::Ptr();
516 }
517 
writeContact(const KContacts::Addressee & addressee,Version v,const QString & productId)518 KMime::Message::Ptr KolabObjectWriter::writeContact(const KContacts::Addressee &addressee, Version v, const QString &productId)
519 {
520     ErrorHandler::clearErrors();
521     const Kolab::Contact &contact = Kolab::Conversion::fromKABC(addressee);
522     Kolab::MIMEObject mimeObject;
523     const std::string mimeMessage = mimeObject.writeContact(contact, v, productId.toStdString());
524     return createMimeMessage(mimeMessage);
525 }
526 
writeDistlist(const KContacts::ContactGroup & kDistList,Version v,const QString & productId)527 KMime::Message::Ptr KolabObjectWriter::writeDistlist(const KContacts::ContactGroup &kDistList, Version v, const QString &productId)
528 {
529     ErrorHandler::clearErrors();
530     const Kolab::DistList &distlist = Kolab::Conversion::fromKABC(kDistList);
531     Kolab::MIMEObject mimeObject;
532     const std::string mimeMessage = mimeObject.writeDistlist(distlist, v, productId.toStdString());
533     return createMimeMessage(mimeMessage);
534 }
535 
writeNote(const KMime::Message::Ptr & n,Version v,const QString & productId)536 KMime::Message::Ptr KolabObjectWriter::writeNote(const KMime::Message::Ptr &n, Version v, const QString &productId)
537 {
538     ErrorHandler::clearErrors();
539     if (!n) {
540         qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
541         return KMime::Message::Ptr();
542     }
543     const Kolab::Note &note = Kolab::Conversion::fromNote(n);
544     Kolab::MIMEObject mimeObject;
545     const std::string mimeMessage = mimeObject.writeNote(note, v, productId.toStdString());
546     return createMimeMessage(mimeMessage);
547 }
548 
writeDictionary(const QStringList & entries,const QString & lang,Version v,const QString & productId)549 KMime::Message::Ptr KolabObjectWriter::writeDictionary(const QStringList &entries, const QString &lang, Version v, const QString &productId)
550 {
551     ErrorHandler::clearErrors();
552 
553     Kolab::Dictionary dictionary(Conversion::toStdString(lang));
554     std::vector<std::string> ent;
555     ent.reserve(entries.count());
556     for (const QString &e : entries) {
557         ent.push_back(Conversion::toStdString(e));
558     }
559     dictionary.setEntries(ent);
560     Kolab::Configuration configuration(dictionary); // TODO preserve creation/lastModified date
561     Kolab::MIMEObject mimeObject;
562     const std::string mimeMessage = mimeObject.writeConfiguration(configuration, v, productId.toStdString());
563     return createMimeMessage(mimeMessage);
564 }
565 
writeFreebusy(const Freebusy & freebusy,Version v,const QString & productId)566 KMime::Message::Ptr KolabObjectWriter::writeFreebusy(const Freebusy &freebusy, Version v, const QString &productId)
567 {
568     ErrorHandler::clearErrors();
569     Kolab::MIMEObject mimeObject;
570     const std::string mimeMessage = mimeObject.writeFreebusy(freebusy, v, productId.toStdString());
571     return createMimeMessage(mimeMessage);
572 }
573 
writeRelationHelper(const Kolab::Relation & relation,const QByteArray & uid,const QString & productId)574 KMime::Message::Ptr writeRelationHelper(const Kolab::Relation &relation, const QByteArray &uid, const QString &productId)
575 {
576     ErrorHandler::clearErrors();
577     Kolab::MIMEObject mimeObject;
578 
579     Kolab::Configuration configuration(relation); // TODO preserve creation/lastModified date
580     configuration.setUid(uid.constData());
581     const std::string mimeMessage = mimeObject.writeConfiguration(configuration, Kolab::KolabV3, Conversion::toStdString(productId));
582     return createMimeMessage(mimeMessage);
583 }
584 
writeTag(const Akonadi::Tag & tag,const QStringList & members,Version v,const QString & productId)585 KMime::Message::Ptr KolabObjectWriter::writeTag(const Akonadi::Tag &tag, const QStringList &members, Version v, const QString &productId)
586 {
587     ErrorHandler::clearErrors();
588     if (v != KolabV3) {
589         qCCritical(PIMKOLAB_LOG) << "only v3 implementation available";
590     }
591 
592     Kolab::Relation relation(Conversion::toStdString(tag.name()), "tag");
593     std::vector<std::string> m;
594     m.reserve(members.count());
595     for (const QString &member : members) {
596         m.push_back(Conversion::toStdString(member));
597     }
598     relation.setMembers(m);
599 
600     return writeRelationHelper(relation, tag.gid(), productId);
601 }
602 
writeRelation(const Akonadi::Relation & relation,const QStringList & items,Version v,const QString & productId)603 KMime::Message::Ptr KolabObjectWriter::writeRelation(const Akonadi::Relation &relation, const QStringList &items, Version v, const QString &productId)
604 {
605     ErrorHandler::clearErrors();
606     if (v != KolabV3) {
607         qCCritical(PIMKOLAB_LOG) << "only v3 implementation available";
608     }
609 
610     if (items.size() != 2) {
611         qCCritical(PIMKOLAB_LOG) << "Wrong number of members for generic relation.";
612         return KMime::Message::Ptr();
613     }
614 
615     Kolab::Relation kolabRelation(std::string(), "generic");
616     std::vector<std::string> m;
617     m.reserve(2);
618     m.push_back(Conversion::toStdString(items.at(0)));
619     m.push_back(Conversion::toStdString(items.at(1)));
620     kolabRelation.setMembers(m);
621 
622     return writeRelationHelper(kolabRelation, relation.remoteId(), productId);
623 }
624 } // Namespace
625