1 /*
2  * SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com>
3  *
4  * SPDX-License-Identifier: LGPL-3.0-or-later
5  */
6 
7 #include "mimeutils.h"
8 #include "kolabformat/kolabdefinitions.h"
9 #include <QDateTime>
10 #include <qdom.h>
11 
12 #include "libkolab-version.h"
13 #include "pimkolab_debug.h"
14 #include <kolabformat.h>
15 namespace Kolab
16 {
17 namespace Mime
18 {
findContentByType(const KMime::Message::Ptr & data,const QByteArray & type)19 KMime::Content *findContentByType(const KMime::Message::Ptr &data, const QByteArray &type)
20 {
21     if (type.isEmpty()) {
22         qCCritical(PIMKOLAB_LOG) << "Empty type";
23         return nullptr;
24     }
25     Q_ASSERT(!data->contents().isEmpty());
26     const auto contents = data->contents();
27     for (KMime::Content *c : contents) {
28         //         qCDebug(PIMKOLAB_LOG) << c->contentType()->mimeType() << type;
29         if (c->contentType()->mimeType() == type) {
30             return c;
31         }
32     }
33     return nullptr;
34 }
35 
findContentByName(const KMime::Message::Ptr & data,const QString & name,QByteArray & type)36 KMime::Content *findContentByName(const KMime::Message::Ptr &data, const QString &name, QByteArray &type)
37 {
38     Q_ASSERT(!data->contents().isEmpty());
39     const auto contents = data->contents();
40     for (KMime::Content *c : contents) {
41         //         qCDebug(PIMKOLAB_LOG) << "searching: " << c->contentType()->name().toUtf8();
42         if (c->contentType()->name() == name) {
43             type = c->contentType()->mimeType();
44             return c;
45         }
46     }
47     return nullptr;
48 }
49 
findContentById(const KMime::Message::Ptr & data,const QByteArray & id,QByteArray & type,QString & name)50 KMime::Content *findContentById(const KMime::Message::Ptr &data, const QByteArray &id, QByteArray &type, QString &name)
51 {
52     if (id.isEmpty()) {
53         qCCritical(PIMKOLAB_LOG) << "looking for empty cid";
54         return nullptr;
55     }
56     Q_ASSERT(!data->contents().isEmpty());
57     const auto contents = data->contents();
58     for (KMime::Content *c : contents) {
59         //         qCDebug(PIMKOLAB_LOG) << "searching: " << c->contentID()->identifier();
60         if (c->contentID()->identifier() == id) {
61             type = c->contentType()->mimeType();
62             name = c->contentType()->name();
63             return c;
64         }
65     }
66     return nullptr;
67 }
68 
getContentMimeTypeList(const KMime::Message::Ptr & data)69 QList<QByteArray> getContentMimeTypeList(const KMime::Message::Ptr &data)
70 {
71     QList<QByteArray> typeList;
72     Q_ASSERT(!data->contents().isEmpty());
73     typeList.reserve(data->contents().count());
74     const auto contents = data->contents();
75     for (KMime::Content *c : contents) {
76         typeList.append(c->contentType()->mimeType());
77     }
78     return typeList;
79 }
80 
fromCid(const QString & cid)81 QString fromCid(const QString &cid)
82 {
83     if (cid.left(4) != QLatin1String("cid:")) { // Don't set if not a cid, happens when serializing format v2
84         return QString();
85     }
86     return cid.right(cid.size() - 4);
87 }
88 
createMessage(const QByteArray & mimetype,const QByteArray & xKolabType,const QByteArray & xml,bool v3,const QByteArray & productId,const QByteArray & fromEmail,const QString & fromName,const QString & subject)89 KMime::Message::Ptr createMessage(const QByteArray &mimetype,
90                                   const QByteArray &xKolabType,
91                                   const QByteArray &xml,
92                                   bool v3,
93                                   const QByteArray &productId,
94                                   const QByteArray &fromEmail,
95                                   const QString &fromName,
96                                   const QString &subject)
97 {
98     KMime::Message::Ptr message = createMessage(xKolabType, v3, productId);
99     message->subject()->fromUnicodeString(subject, "utf-8");
100     if (!fromEmail.isEmpty()) {
101         KMime::Types::Mailbox mb;
102         mb.setName(fromName);
103         mb.setAddress(fromEmail);
104         message->from()->addAddress(mb);
105     }
106     message->addContent(createMainPart(mimetype, xml));
107     return message;
108 }
109 
createMessage(const std::string & mimetype,const std::string & xKolabType,const std::string & xml,bool v3,const std::string & productId,const std::string & fromEmail,const std::string & fromName,const std::string & subject)110 KMime::Message::Ptr createMessage(const std::string &mimetype,
111                                   const std::string &xKolabType,
112                                   const std::string &xml,
113                                   bool v3,
114                                   const std::string &productId,
115                                   const std::string &fromEmail,
116                                   const std::string &fromName,
117                                   const std::string &subject)
118 {
119     return createMessage(QByteArray(mimetype.c_str()),
120                          QByteArray(xKolabType.c_str()),
121                          QByteArray(xml.c_str()),
122                          v3,
123                          QByteArray(productId.data()),
124                          QByteArray(fromEmail.c_str()),
125                          QString::fromStdString(fromName),
126                          QString::fromStdString(subject));
127 }
128 
129 KMime::Message::Ptr
createMessage(const QString & subject,const QString & mimetype,const QString & xKolabType,const QByteArray & xml,bool v3,const QString & prodid)130 createMessage(const QString &subject, const QString &mimetype, const QString &xKolabType, const QByteArray &xml, bool v3, const QString &prodid)
131 {
132     KMime::Message::Ptr message = createMessage(xKolabType.toLatin1(), v3, prodid.toLatin1());
133     if (!subject.isEmpty()) {
134         message->subject()->fromUnicodeString(subject, "utf-8");
135     }
136 
137     KMime::Content *content = createMainPart(mimetype.toLatin1(), xml);
138     message->addContent(content);
139 
140     message->assemble();
141     return message;
142 }
143 
createExplanationPart(bool v3)144 KMime::Content *createExplanationPart(bool v3)
145 {
146     Q_UNUSED(v3)
147     auto content = new KMime::Content();
148     content->contentType()->setMimeType("text/plain");
149     content->contentType()->setCharset("us-ascii");
150     content->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
151     content->setBody(
152         "This is a Kolab Groupware object.\n"
153         "To view this object you will need an email client that can understand the Kolab Groupware format.\n"
154         "For a list of such email clients please visit\n"
155         "http://www.kolab.org/get-kolab\n");
156     return content;
157 }
158 
createMessage(const QByteArray & xKolabType,bool v3,const QByteArray & prodid)159 KMime::Message::Ptr createMessage(const QByteArray &xKolabType, bool v3, const QByteArray &prodid)
160 {
161     KMime::Message::Ptr message(new KMime::Message);
162     message->date()->setDateTime(QDateTime::currentDateTimeUtc());
163     auto h = new KMime::Headers::Generic(X_KOLAB_TYPE_HEADER);
164     h->fromUnicodeString(QString::fromUtf8(xKolabType), "utf-8");
165     message->appendHeader(h);
166     if (v3) {
167         auto hv3 = new KMime::Headers::Generic(X_KOLAB_MIME_VERSION_HEADER);
168         hv3->fromUnicodeString(KOLAB_VERSION_V3, "utf-8");
169         message->appendHeader(hv3);
170     }
171     message->userAgent()->from7BitString(prodid);
172     message->contentType()->setMimeType("multipart/mixed");
173     message->contentType()->setBoundary(KMime::multiPartBoundary());
174     message->addContent(createExplanationPart(v3));
175     return message;
176 }
177 
createMainPart(const QByteArray & mimeType,const QByteArray & decodedContent)178 KMime::Content *createMainPart(const QByteArray &mimeType, const QByteArray &decodedContent)
179 {
180     auto content = new KMime::Content();
181     content->contentType()->setMimeType(mimeType);
182     content->contentType()->setName(KOLAB_OBJECT_FILENAME, "us-ascii");
183     content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
184     content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
185     content->contentDisposition()->setFilename(KOLAB_OBJECT_FILENAME);
186     content->setBody(decodedContent);
187     return content;
188 }
189 
createAttachmentPart(const QByteArray & cid,const QByteArray & mimeType,const QString & fileName,const QByteArray & base64EncodedContent)190 KMime::Content *createAttachmentPart(const QByteArray &cid, const QByteArray &mimeType, const QString &fileName, const QByteArray &base64EncodedContent)
191 {
192     auto content = new KMime::Content();
193     if (!cid.isEmpty()) {
194         content->contentID()->setIdentifier(cid);
195     }
196     content->contentType()->setMimeType(mimeType);
197     content->contentType()->setName(fileName, "utf-8");
198     content->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
199     content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
200     content->contentDisposition()->setFilename(fileName);
201     content->setBody(base64EncodedContent);
202     return content;
203 }
204 
getAttachment(const std::string & id,const KMime::Message::Ptr & mimeData)205 Kolab::Attachment getAttachment(const std::string &id, const KMime::Message::Ptr &mimeData)
206 {
207     if (!QString::fromStdString(id).contains(QLatin1String("cid:"))) {
208         qCCritical(PIMKOLAB_LOG) << "not a cid reference";
209         return Kolab::Attachment();
210     }
211     QByteArray type;
212     QString name;
213     KMime::Content *content = findContentById(mimeData, fromCid(QString::fromStdString(id)).toLatin1(), type, name);
214     if (!content) { // guard against malformed events with non-existent attachments
215         qCCritical(PIMKOLAB_LOG) << "could not find attachment: " << name << type;
216         return Kolab::Attachment();
217     }
218     // Debug() << id << content->decodedContent().toBase64().toStdString();
219     Kolab::Attachment attachment;
220     attachment.setData(content->decodedContent().toStdString(), type.toStdString());
221     attachment.setLabel(name.toStdString());
222     return attachment;
223 }
224 
getAttachmentByName(const QString & name,const KMime::Message::Ptr & mimeData)225 Kolab::Attachment getAttachmentByName(const QString &name, const KMime::Message::Ptr &mimeData)
226 {
227     QByteArray type;
228     KMime::Content *content = findContentByName(mimeData, name, type);
229     if (!content) { // guard against malformed events with non-existent attachments
230         qCWarning(PIMKOLAB_LOG) << "could not find attachment: " << name.toUtf8() << type;
231         return Kolab::Attachment();
232     }
233     Kolab::Attachment attachment;
234     attachment.setData(content->decodedContent().toStdString(), type.toStdString());
235     attachment.setLabel(name.toStdString());
236     return attachment;
237 }
238 } // Namespace
239 } // Namespace
240