Messagelib

signjob.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
3 SPDX-FileCopyrightText: 2009 Leo Franchi <lfranchi@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "job/signjob.h"
9
10#include "contentjobbase_p.h"
11#include "job/protectedheadersjob.h"
12#include "utils/util_p.h"
13
14#include <QGpgME/Protocol>
15#include <QGpgME/SignJob>
16#include <QList>
17
18#include "messagecomposer_debug.h"
19#include <KMime/Content>
20#include <KMime/Headers>
21#include <KMime/Message>
22#include <Libkleo/Formatting>
23
24#include <gpgme++/encryptionresult.h>
25#include <gpgme++/global.h>
26#include <gpgme++/signingresult.h>
27
28using namespace MessageComposer;
29
30class MessageComposer::SignJobPrivate : public ContentJobBasePrivate
31{
32public:
33 SignJobPrivate(SignJob *qq)
34 : ContentJobBasePrivate(qq)
35 {
36 }
37
38 KMime::Content *content = nullptr;
39 KMime::Message *skeletonMessage = nullptr;
40 std::vector<GpgME::Key> signers;
41 Kleo::CryptoMessageFormat format;
42
43 bool protectedHeaders = true;
44
45 // copied from messagecomposer.cpp
46 [[nodiscard]] bool binaryHint(Kleo::CryptoMessageFormat f)
47 {
48 switch (f) {
49 case Kleo::SMIMEFormat:
50 case Kleo::SMIMEOpaqueFormat:
51 return true;
52 default:
53 case Kleo::OpenPGPMIMEFormat:
54 case Kleo::InlineOpenPGPFormat:
55 return false;
56 }
57 }
58
59 [[nodiscard]] GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
60 {
61 switch (f) {
62 case Kleo::SMIMEOpaqueFormat:
63 return GpgME::NormalSignatureMode;
64 case Kleo::InlineOpenPGPFormat:
65 return GpgME::Clearsigned;
66 default:
67 case Kleo::SMIMEFormat:
68 case Kleo::OpenPGPMIMEFormat:
69 return GpgME::Detached;
70 }
71 }
72
73 Q_DECLARE_PUBLIC(SignJob)
74};
75
76SignJob::SignJob(QObject *parent)
77 : ContentJobBase(*new SignJobPrivate(this), parent)
78{
79}
80
81SignJob::~SignJob() = default;
82
83void SignJob::setContent(KMime::Content *content)
84{
85 Q_D(SignJob);
86
87 d->content = content;
88}
89
90void SignJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format)
91{
92 Q_D(SignJob);
93
94 // There *must* be a concrete format set at this point.
95 Q_ASSERT(format == Kleo::OpenPGPMIMEFormat || format == Kleo::InlineOpenPGPFormat || format == Kleo::SMIMEFormat || format == Kleo::SMIMEOpaqueFormat);
96 d->format = format;
97}
98
99void SignJob::setSigningKeys(const std::vector<GpgME::Key> &signers)
100{
101 Q_D(SignJob);
102
103 d->signers = signers;
104}
105
106void SignJob::setSkeletonMessage(KMime::Message *skeletonMessage)
107{
108 Q_D(SignJob);
109
110 d->skeletonMessage = skeletonMessage;
111}
112
113void SignJob::setProtectedHeaders(bool protectedHeaders)
114{
115 Q_D(SignJob);
116
117 d->protectedHeaders = protectedHeaders;
118}
119
120KMime::Content *SignJob::origContent()
121{
122 Q_D(SignJob);
123
124 return d->content;
125}
126
127void SignJob::doStart()
128{
129 Q_D(SignJob);
130 Q_ASSERT(d->resultContent == nullptr); // Not processed before.
131
132 if (d->protectedHeaders && d->skeletonMessage && d->format & Kleo::OpenPGPMIMEFormat) {
133 auto pJob = new ProtectedHeadersJob;
134 pJob->setContent(d->content);
135 pJob->setSkeletonMessage(d->skeletonMessage);
136 pJob->setObvoscate(false);
137 QObject::connect(pJob, &ProtectedHeadersJob::finished, this, [d, pJob](KJob *job) {
138 if (job->error()) {
139 return;
140 }
141 d->content = pJob->content();
142 });
143 appendSubjob(pJob);
144 }
145
147}
148
149void SignJob::slotResult(KJob *job)
150{
151 if (error() || job->error()) {
152 ContentJobBase::slotResult(job);
153 return;
154 }
155 if (subjobs().size() == 2) {
156 auto pjob = static_cast<ProtectedHeadersJob *>(subjobs().last());
157 if (pjob) {
158 auto cjob = qobject_cast<ContentJobBase *>(job);
159 Q_ASSERT(cjob);
160 pjob->setContent(cjob->content());
161 }
162 }
163
164 ContentJobBase::slotResult(job);
165}
166
167void SignJob::process()
168{
169 Q_D(SignJob);
170 Q_ASSERT(d->resultContent == nullptr); // Not processed before.
171
172 // if setContent hasn't been called, we assume that a subjob was added
173 // and we want to use that
174 if (!d->content) {
175 Q_ASSERT(d->subjobContents.size() == 1);
176 d->content = d->subjobContents.constFirst();
177 }
178
179 // d->resultContent = new KMime::Content;
180
181 const QGpgME::Protocol *proto = nullptr;
182 if (d->format & Kleo::AnyOpenPGP) {
183 proto = QGpgME::openpgp();
184 } else if (d->format & Kleo::AnySMIME) {
185 proto = QGpgME::smime();
186 }
187
188 Q_ASSERT(proto);
189
190 qCDebug(MESSAGECOMPOSER_LOG) << "creating signJob from:" << proto->name() << proto->displayName();
191 // for now just do the main recipients
192 QByteArray signature;
193
194 d->content->assemble();
195
196 // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
197 // according to RfC 2633, 3.1.1 Canonicalization
199 if (d->format & Kleo::InlineOpenPGPFormat) {
200 content = d->content->body();
201 } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
202 // replace "From " and "--" at the beginning of lines
203 // with encoded versions according to RfC 3156, 3
204 // Note: If any line begins with the string "From ", it is strongly
205 // suggested that either the Quoted-Printable or Base64 MIME encoding
206 // be applied.
207 const auto cte = d->content->contentTransferEncoding(false);
208 const auto encoding = cte ? cte->encoding() : KMime::Headers::CE7Bit;
209 if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !d->content->contentType(false)) {
210 QByteArray body = d->content->encodedBody();
211 bool changed = false;
212 constexpr auto search = std::to_array<QByteArrayView>({"From ", "from ", "-"});
213 constexpr auto replacements = std::to_array<QByteArrayView>({"From=20", "from=20", "=2D"});
214
215 if (encoding == KMime::Headers::CE7Bit) {
216 for (size_t i = 0, total = search.size(); i < total; ++i) {
217 const auto pos = body.indexOf(search[i]);
218 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
219 changed = true;
220 break;
221 }
222 }
223 if (changed) {
224 d->content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
225 d->content->assemble();
226 body = d->content->encodedBody();
227 }
228 }
229
230 for (size_t i = 0, total = search.size(); i < total; ++i) {
231 const auto pos = body.indexOf(search[i]);
232 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
233 changed = true;
234 body.replace(pos, search[i].size(), replacements[i]);
235 }
236 }
237
238 if (changed) {
239 qCDebug(MESSAGECOMPOSER_LOG) << "Content changed";
240 d->content->setEncodedBody(body);
241 }
242 }
243
244 content = KMime::LFtoCRLF(d->content->encodedContent());
245 } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
247 }
248
249 QGpgME::SignJob *job(proto->signJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat));
251 job,
252 &QGpgME::SignJob::result,
253 this,
254 [this, d](const GpgME::SigningResult &result, const QByteArray &signature, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
255 Q_UNUSED(auditLogAsHtml)
256 Q_UNUSED(auditLogError)
257 if (result.error().code()) {
258 qCDebug(MESSAGECOMPOSER_LOG) << "signing failed:" << Kleo::Formatting::errorAsString(result.error());
259 // job->showErrorDialog( globalPart()->parentWidgetForGui() );
260 setError(result.error().code());
261 setErrorText(Kleo::Formatting::errorAsString(result.error()));
262 emitResult();
263 return;
264 }
265
266 QByteArray signatureHashAlgo = result.createdSignature(0).hashAlgorithmAsString();
267 d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, signature, d->format, true, signatureHashAlgo);
268
269 emitResult();
270 });
271
272 const auto error = job->start(d->signers, content, d->signingMode(d->format));
273 if (error.code()) {
274 job->deleteLater();
275 setError(error.code());
276 setErrorText(Kleo::Formatting::errorAsString(error));
277 emitResult();
278 }
279}
280
281#include "moc_signjob.cpp"
const QList< KJob * > & subjobs() const
void setErrorText(const QString &errorText)
void emitResult()
int error() const
void result(KJob *job)
void finished(KJob *job)
void setError(int errorCode)
virtual Q_SCRIPTABLE void start()=0
Content * content(const ContentIndex &index) const
QByteArray body() const
QByteArray encodedContent(bool useCrLf=false) const
The ContentJobBase class.
virtual void doStart()
Reimplement to do additional stuff before processing children, such as adding more subjobs.
bool appendSubjob(ContentJobBase *job)
This is meant to be used instead of KCompositeJob::addSubjob(), making it possible to add subjobs fro...
KMime::Content * content() const
Get the resulting KMime::Content that the ContentJobBase has generated.
Copies headers from skeleton message to content.
Signs the contents of a message.
Definition signjob.h:32
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
char at(qsizetype i) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
T qobject_cast(QObject *object)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:08:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.