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/KMimeMessage>
22
23#include <gpgme++/encryptionresult.h>
24#include <gpgme++/global.h>
25#include <gpgme++/signingresult.h>
26#include <sstream>
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);
138 if (job->error()) {
139 return;
140 }
141 d->content = pJob->content();
142 });
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) {
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
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 encoding = d->content->contentTransferEncoding()->encoding();
208 if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !d->content->contentType(false)) {
209 QByteArray body = d->content->encodedBody();
210 bool changed = false;
211 QList<QByteArray> search;
212 search.reserve(3);
214 replacements.reserve(3);
215 search << "From "
216 << "from "
217 << "-";
218 replacements << "From=20"
219 << "from=20"
220 << "=2D";
221
222 if (d->content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
223 for (int i = 0, total = search.size(); i < total; ++i) {
224 const auto pos = body.indexOf(search[i]);
225 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
226 changed = true;
227 break;
228 }
229 }
230 if (changed) {
231 d->content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
232 d->content->assemble();
233 body = d->content->encodedBody();
234 }
235 }
236
237 for (int i = 0; i < search.size(); ++i) {
238 const auto pos = body.indexOf(search[i]);
239 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
240 changed = true;
241 body.replace(pos, search[i].size(), replacements[i]);
242 }
243 }
244
245 if (changed) {
246 qCDebug(MESSAGECOMPOSER_LOG) << "Content changed";
247 d->content->setBody(body);
248 d->content->contentTransferEncoding()->setDecoded(false);
249 }
250 }
251
252 content = KMime::LFtoCRLF(d->content->encodedContent());
253 } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
255 }
256
257 QGpgME::SignJob *job(proto->signJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat));
259 job,
260 &QGpgME::SignJob::result,
261 this,
262 [this, d](const GpgME::SigningResult &result, const QByteArray &signature, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
263 Q_UNUSED(auditLogAsHtml)
264 Q_UNUSED(auditLogError)
265 if (result.error().code()) {
266 qCDebug(MESSAGECOMPOSER_LOG) << "signing failed:" << result.error().asString();
267 // job->showErrorDialog( globalPart()->parentWidgetForGui() );
268 setError(result.error().code());
269 setErrorText(QString::fromLocal8Bit(result.error().asString()));
270 emitResult();
271 return;
272 }
273
274 QByteArray signatureHashAlgo = result.createdSignature(0).hashAlgorithmAsString();
275 d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, signature, d->format, true, signatureHashAlgo);
276
277 emitResult();
278 });
279
280 const auto error = job->start(d->signers, content, d->signingMode(d->format));
281 if (error.code()) {
282 job->deleteLater();
283 setError(error.code());
285 emitResult();
286 }
287}
288
289#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)
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(int i) const const
int indexOf(char ch, int from) const const
QByteArray & replace(int pos, int len, const char *after)
void reserve(int alloc)
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void deleteLater()
T qobject_cast(QObject *object)
QString fromLocal8Bit(const char *str, int size)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:37:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.