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