1 /*
2 SPDX-FileCopyrightText: 2020 Sandro Knauß <sknauss@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "job/protectedheadersjob.h"
8
9 #include "contentjobbase_p.h"
10 #include "job/singlepartjob.h"
11 #include "utils/util_p.h"
12
13 #include "messagecomposer_debug.h"
14
15 #include <KMime/Content>
16 #include <KMime/KMimeMessage>
17
18 using namespace MessageComposer;
19
20 class MessageComposer::ProtectedHeadersJobPrivate : public ContentJobBasePrivate
21 {
22 public:
ProtectedHeadersJobPrivate(ProtectedHeadersJob * qq)23 ProtectedHeadersJobPrivate(ProtectedHeadersJob *qq)
24 : ContentJobBasePrivate(qq)
25 {
26 }
27
28 KMime::Content *content = nullptr;
29 KMime::Message *skeletonMessage = nullptr;
30
31 bool obvoscate = false;
32
33 Q_DECLARE_PUBLIC(ProtectedHeadersJob)
34 };
35
ProtectedHeadersJob(QObject * parent)36 ProtectedHeadersJob::ProtectedHeadersJob(QObject *parent)
37 : ContentJobBase(*new ProtectedHeadersJobPrivate(this), parent)
38 {
39 }
40
~ProtectedHeadersJob()41 ProtectedHeadersJob::~ProtectedHeadersJob()
42 {
43 }
44
setContent(KMime::Content * content)45 void ProtectedHeadersJob::setContent(KMime::Content *content)
46 {
47 Q_D(ProtectedHeadersJob);
48
49 d->content = content;
50 if (content) {
51 d->content->assemble();
52 }
53 }
54
setSkeletonMessage(KMime::Message * skeletonMessage)55 void ProtectedHeadersJob::setSkeletonMessage(KMime::Message *skeletonMessage)
56 {
57 Q_D(ProtectedHeadersJob);
58
59 d->skeletonMessage = skeletonMessage;
60 }
61
setObvoscate(bool obvoscate)62 void ProtectedHeadersJob::setObvoscate(bool obvoscate)
63 {
64 Q_D(ProtectedHeadersJob);
65
66 d->obvoscate = obvoscate;
67 }
68
doStart()69 void ProtectedHeadersJob::doStart()
70 {
71 Q_D(ProtectedHeadersJob);
72 Q_ASSERT(d->resultContent == nullptr); // Not processed before.
73 Q_ASSERT(d->skeletonMessage); // We need a skeletonMessage to proceed
74
75 auto subject = d->skeletonMessage->header<KMime::Headers::Subject>();
76 if (d->obvoscate && subject) {
77 // Create protected header lagacy mimepart with replaced headers
78 auto cjob = new SinglepartJob;
79 auto ct = cjob->contentType();
80 ct->setMimeType("text/plain");
81 ct->setCharset(subject->rfc2047Charset());
82 ct->setParameter(QStringLiteral("protected-headers"), QStringLiteral("v1"));
83 cjob->contentDisposition()->setDisposition(KMime::Headers::contentDisposition::CDinline);
84 cjob->setData(subject->type() + QByteArray(": ") + subject->asUnicodeString().toUtf8());
85
86 QObject::connect(cjob, &SinglepartJob::finished, this, [d, cjob]() {
87 auto mixedPart = new KMime::Content();
88 const QByteArray boundary = KMime::multiPartBoundary();
89 mixedPart->contentType()->setMimeType("multipart/mixed");
90 mixedPart->contentType(false)->setBoundary(boundary);
91 mixedPart->addContent(cjob->content());
92
93 // if setContent hasn't been called, we assume that a subjob was added
94 // and we want to use that
95 if (!d->content || !d->content->hasContent()) {
96 Q_ASSERT(d->subjobContents.size() == 1);
97 d->content = d->subjobContents.constFirst();
98 }
99
100 mixedPart->addContent(d->content);
101 d->content = mixedPart;
102 });
103 appendSubjob(cjob);
104 }
105
106 ContentJobBase::doStart();
107 }
108
process()109 void ProtectedHeadersJob::process()
110 {
111 Q_D(ProtectedHeadersJob);
112
113 // if setContent hasn't been called, we assume that a subjob was added
114 // and we want to use that
115 if (!d->content || !d->content->hasContent()) {
116 Q_ASSERT(d->subjobContents.size() == 1);
117 d->content = d->subjobContents.constFirst();
118 }
119
120 auto subject = d->skeletonMessage->header<KMime::Headers::Subject>();
121 const auto headers = d->skeletonMessage->headers();
122 for (const auto &header : headers) {
123 const QByteArray headerType(header->type());
124 if (headerType.startsWith("X-KMail-")) {
125 continue;
126 }
127 if (headerType == "MIME-Version") {
128 continue;
129 }
130 if (headerType == "Bcc") {
131 continue;
132 }
133 if (headerType.startsWith("Content-")) {
134 continue;
135 }
136 // A workaround for #439958
137 // KMime strips sometimes the newlines from long headers, if those
138 // headers are in the signature block, this breaks the signature.
139 // The simplest workaround is not to sign those headers until this
140 // get fixed in KMime.
141 if (header->as7BitString().length() > 70) {
142 continue;
143 }
144 auto copyHeader = KMime::Headers::createHeader(headerType);
145 if (!copyHeader) {
146 copyHeader = new KMime::Headers::Generic(headerType.constData(), headerType.size());
147 }
148 copyHeader->from7BitString(header->as7BitString(false));
149 d->content->appendHeader(copyHeader);
150 }
151
152 if (d->obvoscate && subject) {
153 subject->clear();
154 subject->from7BitString("...");
155 }
156 auto contentType = d->content->header<KMime::Headers::ContentType>();
157 contentType->setParameter(QStringLiteral("protected-headers"), QStringLiteral("v1"));
158
159 d->resultContent = d->content;
160
161 emitResult();
162 }
163