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