1 /*
2     SPDX-FileCopyrightText: 2015-2017 Krzysztof Nowicki <krissn@op.pl>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "ewscreatemailjob.h"
8 
9 #include <KLocalizedString>
10 
11 #include <Akonadi/AgentManager>
12 #include <Akonadi/Collection>
13 #include <Akonadi/Item>
14 #include <Akonadi/KMime/SpecialMailCollections>
15 #include <KMime/Message>
16 
17 #include "ewscreateitemrequest.h"
18 #include "ewsmailhandler.h"
19 #include "ewsmoveitemrequest.h"
20 #include "ewspropertyfield.h"
21 #include "ewsresource_debug.h"
22 
23 using namespace Akonadi;
24 
25 static const EwsPropertyField propPidMessageFlags(0x0e07, EwsPropTypeInteger);
26 
EwsCreateMailJob(EwsClient & client,const Akonadi::Item & item,const Akonadi::Collection & collection,EwsTagStore * tagStore,EwsResource * parent)27 EwsCreateMailJob::EwsCreateMailJob(EwsClient &client,
28                                    const Akonadi::Item &item,
29                                    const Akonadi::Collection &collection,
30                                    EwsTagStore *tagStore,
31                                    EwsResource *parent)
32     : EwsCreateItemJob(client, item, collection, tagStore, parent)
33 {
34 }
35 
~EwsCreateMailJob()36 EwsCreateMailJob::~EwsCreateMailJob()
37 {
38 }
39 
doStart()40 void EwsCreateMailJob::doStart()
41 {
42     if (!mItem.hasPayload<KMime::Message::Ptr>()) {
43         setErrorMsg(QStringLiteral("Expected MIME message payload"));
44         emitResult();
45     }
46 
47     auto req = new EwsCreateItemRequest(mClient, this);
48 
49     auto msg = mItem.payload<KMime::Message::Ptr>();
50     /* Exchange doesn't just store whatever MIME content that was sent to it - it will parse it and send
51      * further the version assembled back from the parsed parts. It seems that this parsing doesn't work well
52      * with the quoted-printable encoding, which KMail prefers. This results in malformed encoding, which the
53      * sender doesn't even see.
54      * As a workaround force encoding of the body (or in case of multipart - all parts) to Base64. */
55     if (msg->contents().isEmpty()) {
56         msg->changeEncoding(KMime::Headers::CEbase64);
57         msg->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64);
58     } else {
59         const auto contents = msg->contents();
60         for (KMime::Content *content : contents) {
61             content->changeEncoding(KMime::Headers::CEbase64);
62             content->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64);
63         }
64     }
65     msg->assemble();
66     QByteArray mimeContent = msg->encodedContent(true);
67     bool sentItemsCreateWorkaround = false;
68     EwsItem item;
69     item.setType(EwsItemTypeMessage);
70     item.setField(EwsItemFieldMimeContent, mimeContent);
71     if (!mSend) {
72         /* When creating items using the CreateItem request Exchange will by default mark the  message
73          * as draft. Setting the extended property below causes the message to appear normally. */
74         item.setProperty(propPidMessageFlags, QStringLiteral("1"));
75         const Akonadi::AgentInstance &inst = Akonadi::AgentManager::self()->instance(mCollection.resource());
76 
77         /* WORKAROUND: The "Sent Items" folder is a little "special" when it comes to creating items.
78          * Unlike other folders when creating items there the creation date/time is always set to the
79          * current date/time instead of the value from the MIME Date header. This causes mail that
80          * was copied from other folders to appear with the current date/time instead of the original one.
81          * To work around this create the item in the "Drafts" folder first and then move it to "Sent Items". */
82         if (mCollection == SpecialMailCollections::self()->collection(SpecialMailCollections::SentMail, inst)) {
83             qCInfoNC(EWSRES_LOG) << "Move to \"Sent Items\" detected - activating workaround.";
84             const Collection &draftColl = SpecialMailCollections::self()->collection(SpecialMailCollections::Drafts, inst);
85             req->setSavedFolderId(EwsId(draftColl.remoteId(), draftColl.remoteRevision()));
86             sentItemsCreateWorkaround = true;
87         } else {
88             req->setSavedFolderId(EwsId(mCollection.remoteId(), mCollection.remoteRevision()));
89         }
90     }
91     // Set flags
92     QHash<EwsPropertyField, QVariant> propertyHash = EwsMailHandler::writeFlags(mItem.flags());
93     for (auto it = propertyHash.cbegin(), end = propertyHash.cend(); it != end; ++it) {
94         if (!it.value().isNull()) {
95             if (it.key().type() == EwsPropertyField::ExtendedField) {
96                 item.setProperty(it.key(), it.value());
97             } else if (it.key().type() == EwsPropertyField::Field) {
98                 /* TODO: Currently EwsItem distinguishes between regular fields and extended fields
99                  * and keeps them in separate lists. Someday it will make more sense to unify them.
100                  * Until that the code below needs to manually translate the field names into
101                  * EwsItemField enum items.
102                  */
103                 if (it.key().uri() == QLatin1String("message:IsRead")) {
104                     item.setField(EwsItemFieldIsRead, it.value());
105                 }
106             }
107         }
108     }
109 
110     populateCommonProperties(item);
111 
112     req->setItems(EwsItem::List() << item);
113     req->setMessageDisposition(mSend ? EwsDispSendOnly : EwsDispSaveOnly);
114     connect(req,
115             &EwsCreateItemRequest::finished,
116             this,
117             sentItemsCreateWorkaround ? &EwsCreateMailJob::mailCreateWorkaroundFinished : &EwsCreateMailJob::mailCreateFinished);
118     addSubjob(req);
119     req->start();
120 }
121 
mailCreateFinished(KJob * job)122 void EwsCreateMailJob::mailCreateFinished(KJob *job)
123 {
124     auto req = qobject_cast<EwsCreateItemRequest *>(job);
125     if (job->error()) {
126         setErrorMsg(job->errorString());
127         emitResult();
128         return;
129     }
130 
131     if (!req) {
132         setErrorMsg(QStringLiteral("Invalid EwsCreateItemRequest job object"));
133         emitResult();
134         return;
135     }
136 
137     if (req->responses().count() != 1) {
138         setErrorMsg(QStringLiteral("Invalid number of responses received from server."));
139         emitResult();
140         return;
141     }
142 
143     EwsCreateItemRequest::Response resp = req->responses().first();
144     if (resp.isSuccess()) {
145         EwsId id = resp.itemId();
146         mItem.setRemoteId(id.id());
147         mItem.setRemoteRevision(id.changeKey());
148         mItem.setParentCollection(mCollection);
149     } else {
150         setErrorMsg(i18n("Failed to create mail item"));
151     }
152 
153     emitResult();
154 }
155 
mailCreateWorkaroundFinished(KJob * job)156 void EwsCreateMailJob::mailCreateWorkaroundFinished(KJob *job)
157 {
158     auto req = qobject_cast<EwsCreateItemRequest *>(job);
159     if (job->error()) {
160         setErrorMsg(job->errorString());
161         emitResult();
162         return;
163     }
164 
165     if (!req) {
166         setErrorMsg(QStringLiteral("Invalid EwsCreateItemRequest job object"));
167         emitResult();
168         return;
169     }
170 
171     if (req->responses().count() != 1) {
172         setErrorMsg(QStringLiteral("Invalid number of responses received from server."));
173         emitResult();
174         return;
175     }
176 
177     EwsCreateItemRequest::Response resp = req->responses().first();
178     if (resp.isSuccess()) {
179         EwsId id = resp.itemId();
180         auto moveItemReq = new EwsMoveItemRequest(mClient, this);
181         moveItemReq->setItemIds(EwsId::List() << id);
182         moveItemReq->setDestinationFolderId(EwsId(mCollection.remoteId()));
183         connect(moveItemReq, &EwsCreateItemRequest::finished, this, &EwsCreateMailJob::mailMoveWorkaroundFinished);
184         addSubjob(moveItemReq);
185         moveItemReq->start();
186     } else {
187         setErrorMsg(i18n("Failed to create mail item"));
188         emitResult();
189     }
190 }
191 
mailMoveWorkaroundFinished(KJob * job)192 void EwsCreateMailJob::mailMoveWorkaroundFinished(KJob *job)
193 {
194     auto req = qobject_cast<EwsMoveItemRequest *>(job);
195     if (job->error()) {
196         setErrorMsg(job->errorString());
197         emitResult();
198         return;
199     }
200 
201     if (!req) {
202         setErrorMsg(QStringLiteral("Invalid EwsMoveItemRequest job object"));
203         emitResult();
204         return;
205     }
206 
207     if (req->responses().count() != 1) {
208         setErrorMsg(QStringLiteral("Invalid number of responses received from server."));
209         emitResult();
210         return;
211     }
212 
213     EwsMoveItemRequest::Response resp = req->responses().first();
214     if (resp.isSuccess()) {
215         EwsId id = resp.itemId();
216         mItem.setRemoteId(id.id());
217         mItem.setRemoteRevision(id.changeKey());
218         mItem.setParentCollection(mCollection);
219     } else {
220         setErrorMsg(i18n("Failed to create mail item"));
221     }
222 
223     emitResult();
224 }
225 
setSend(bool send)226 bool EwsCreateMailJob::setSend(bool send)
227 {
228     mSend = send;
229     return true;
230 }
231