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
198 QByteArray content;
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
246 content = d->content->encodedContent();
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
KJob(QObject *parent=nullptr)
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.
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-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:47:39 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.