KIdentityManagement

signature.cpp
1/*
2 SPDX-FileCopyrightText: 2002-2004 Marc Mutz <mutz@kde.org>
3 SPDX-FileCopyrightText: 2007 Tom Albers <tomalbers@kde.nl>
4 SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "signature.h"
10
11#include "kidentitymanagementcore_debug.h"
12#include <KConfigGroup>
13#include <KLocalizedString>
14#include <KProcess>
15
16#include <QDir>
17#include <QTextBlock>
18#include <QTextDocument>
19
20#include <cassert>
21
22using namespace KIdentityManagementCore;
23using namespace Qt::Literals::StringLiterals;
24
25class KIdentityManagementCore::SignaturePrivate
26{
27public:
28 explicit SignaturePrivate(Signature *qq)
29 : q(qq)
30 {
31 }
32
33 void assignFrom(const KIdentityManagementCore::Signature &that);
34 void cleanupImages();
35 void saveImages() const;
36 [[nodiscard]] QString textFromFile(bool *ok) const;
37 [[nodiscard]] QString textFromCommand(bool *ok, QString *errorMessage) const;
38
39 /// List of images that belong to this signature. Either added by addImage() or
40 /// by readConfig().
42
43 /// The directory where the images will be saved to.
44 QString saveLocation;
45 QString path;
46 QString text;
47 Signature::Type type = Signature::Disabled;
48 bool enabled = false;
49 bool inlinedHtml = false;
50 Signature *const q;
51};
52
53// Returns the names of all images in the HTML code
54static QStringList findImageNames(const QString &htmlCode)
55{
56 QStringList imageNames;
57 QTextDocument doc;
58 doc.setHtml(htmlCode);
59 for (auto block = doc.begin(); block.isValid(); block = block.next()) {
60 for (auto it = block.begin(); !it.atEnd(); ++it) {
61 const auto fragment = it.fragment();
62 if (fragment.isValid()) {
63 const auto imageFormat = fragment.charFormat().toImageFormat();
64 if (imageFormat.isValid() && !imageFormat.name().startsWith("http"_L1) && !imageNames.contains(imageFormat.name())) {
65 imageNames.push_back(imageFormat.name());
66 }
67 }
68 }
69 }
70 return imageNames;
71}
72
73void SignaturePrivate::assignFrom(const KIdentityManagementCore::Signature &that)
74{
75 path = that.path();
76 inlinedHtml = that.isInlinedHtml();
77 text = that.text();
78 type = that.type();
79 enabled = that.isEnabledSignature();
80 saveLocation = that.imageLocation();
81 embeddedImages = that.embeddedImages();
82}
83
84void SignaturePrivate::cleanupImages()
85{
86 // Remove any images from the internal structure that are no longer there
87 if (inlinedHtml) {
88 auto it = std::remove_if(embeddedImages.begin(), embeddedImages.end(), [this](const Signature::EmbeddedImagePtr &imageInList) {
89 const QStringList lstImage = findImageNames(text);
90 for (const QString &imageInHtml : lstImage) {
91 if (imageInHtml == imageInList->name) {
92 return false;
93 }
94 }
95 return true;
96 });
97 embeddedImages.erase(it, embeddedImages.end());
98 }
99
100 // Delete all the old image files
101 if (!saveLocation.isEmpty()) {
102 QDir dir(saveLocation);
104 for (const QString &fileName : lst) {
105 if (fileName.endsWith(".png"_L1, Qt::CaseInsensitive)) {
106 qCDebug(KIDENTITYMANAGEMENT_LOG) << "Deleting old image" << dir.path() + fileName;
107 dir.remove(fileName);
108 }
109 }
110 }
111}
112
113void SignaturePrivate::saveImages() const
114{
115 if (inlinedHtml && !saveLocation.isEmpty()) {
116 for (const Signature::EmbeddedImagePtr &image : std::as_const(embeddedImages)) {
117 const QString location = saveLocation + QLatin1Char('/') + image->name;
118 if (!image->image.save(location, "PNG")) {
119 qCWarning(KIDENTITYMANAGEMENT_LOG) << "Failed to save image" << location;
120 }
121 }
122 }
123}
124
125QString SignaturePrivate::textFromFile(bool *ok) const
126{
127 assert(type == Signature::FromFile);
128
129 QFile f(path);
130 if (!f.open(QIODevice::ReadOnly)) {
131 qCWarning(KIDENTITYMANAGEMENT_LOG) << "Failed to open" << path << ":" << f.errorString();
132 if (ok) {
133 *ok = false;
134 }
135 return {};
136 }
137
138 if (ok) {
139 *ok = true;
140 }
141 const QByteArray ba = f.readAll();
142 return QString::fromLocal8Bit(ba.data(), ba.size());
143}
144
145QString SignaturePrivate::textFromCommand(bool *ok, QString *errorMessage) const
146{
147 assert(type == Signature::FromCommand);
148
149 // handle pathological cases:
150 if (path.isEmpty()) {
151 if (ok) {
152 *ok = true;
153 }
154 return {};
155 }
156
157 // create a shell process:
158 KProcess proc;
160 proc.setShellCommand(path);
161 int rc = proc.execute();
162
163 // handle errors, if any:
164 if (rc != 0) {
165 if (ok) {
166 *ok = false;
167 }
168 if (errorMessage) {
170 "<qt>Failed to execute signature script<p><b>%1</b>:</p>"
171 "<p>%2</p></qt>",
172 path,
174 }
175 return {};
176 }
177
178 // no errors:
179 if (ok) {
180 *ok = true;
181 }
182
183 // get output:
184 const QByteArray output = proc.readAllStandardOutput();
185
186 // TODO: hmm, should we allow other encodings, too?
187 return QString::fromLocal8Bit(output.data(), output.size());
188}
189
191{
192 return stream << img->image << img->name;
193}
194
196{
197 return stream >> img->image >> img->name;
198}
199
201 : d(new SignaturePrivate(this))
202{
203 d->type = Disabled;
204 d->inlinedHtml = false;
205}
206
208 : d(new SignaturePrivate(this))
209{
210 d->type = Inlined;
211 d->inlinedHtml = false;
212 d->text = text;
213}
214
215Signature::Signature(const QString &path, bool isExecutable)
216 : d(new SignaturePrivate(this))
217{
218 d->type = isExecutable ? FromCommand : FromFile;
219 d->path = path;
220}
221
223 : d(new SignaturePrivate(this))
224{
225 d->assignFrom(that);
226}
227
229{
230 if (this == &that) {
231 return *this;
232 }
233
234 d->assignFrom(that);
235 return *this;
236}
237
238Signature::~Signature() = default;
239
240QString Signature::rawText(bool *ok, QString *errorMessage) const
241{
242 switch (d->type) {
243 case Disabled:
244 if (ok) {
245 *ok = true;
246 }
247 return {};
248 case Inlined:
249 if (ok) {
250 *ok = true;
251 }
252 return d->text;
253 case FromFile:
254 return d->textFromFile(ok);
255 case FromCommand:
256 return d->textFromCommand(ok, errorMessage);
257 }
258 qCritical() << "Signature::type() returned unknown value!";
259 return {}; // make compiler happy
260}
261
262QString Signature::withSeparator(bool *ok, QString *errorMessage) const
263{
264 QString signature = rawText(ok, errorMessage);
265 if (ok && (*ok) == false) {
266 return {};
267 }
268
269 if (signature.isEmpty()) {
270 return signature; // don't add a separator in this case
271 }
272
273 const bool htmlSig = (isInlinedHtml() && d->type == Inlined);
274 QString newline = htmlSig ? QStringLiteral("<br>") : QStringLiteral("\n");
275 if (htmlSig && signature.startsWith("<p"_L1)) {
276 newline.clear();
277 }
278
279 if (signature.startsWith("-- "_L1 + newline) || (signature.indexOf(newline + "-- "_L1 + newline) != -1)) {
280 // already have signature separator at start of sig or inside sig:
281 return signature;
282 } else {
283 // need to prepend one:
284 return "-- "_L1 + newline + signature;
285 }
286}
287
288void Signature::setPath(const QString &path, bool isExecutable)
289{
290 d->path = path;
291 d->type = isExecutable ? FromCommand : FromFile;
292}
293
295{
296 d->inlinedHtml = isHtml;
297}
298
300{
301 return d->inlinedHtml;
302}
303
304// config keys and values:
305static const char sigTypeKey[] = "Signature Type";
306static const char sigTypeInlineValue[] = "inline";
307static const char sigTypeFileValue[] = "file";
308static const char sigTypeCommandValue[] = "command";
309static const char sigTypeDisabledValue[] = "disabled";
310static const char sigTextKey[] = "Inline Signature";
311static const char sigFileKey[] = "Signature File";
312static const char sigCommandKey[] = "Signature Command";
313static const char sigTypeInlinedHtmlKey[] = "Inlined Html";
314static const char sigImageLocation[] = "Image Location";
315static const char sigEnabled[] = "Signature Enabled";
316
317void Signature::readConfig(const KConfigGroup &config)
318{
319 QString sigType = config.readEntry(sigTypeKey);
320 if (sigType == QLatin1StringView(sigTypeInlineValue)) {
321 d->type = Inlined;
322 d->inlinedHtml = config.readEntry(sigTypeInlinedHtmlKey, false);
323 } else if (sigType == QLatin1StringView(sigTypeFileValue)) {
324 d->type = FromFile;
325 d->path = config.readPathEntry(sigFileKey, QString());
326 } else if (sigType == QLatin1StringView(sigTypeCommandValue)) {
327 d->type = FromCommand;
328 d->path = config.readPathEntry(sigCommandKey, QString());
329 } else if (sigType == QLatin1StringView(sigTypeDisabledValue)) {
330 d->enabled = false;
331 }
332 if (d->type != Disabled) {
333 d->enabled = config.readEntry(sigEnabled, true);
334 }
335
336 d->text = config.readEntry(sigTextKey);
337 d->saveLocation = config.readEntry(sigImageLocation);
338
339 if (isInlinedHtml() && !d->saveLocation.isEmpty()) {
340 QDir dir(d->saveLocation);
342 for (const QString &fileName : lst) {
343 if (fileName.endsWith(".png"_L1, Qt::CaseInsensitive)) {
344 QImage image;
345 if (image.load(dir.path() + QLatin1Char('/') + fileName)) {
346 addImage(image, fileName);
347 } else {
348 qCWarning(KIDENTITYMANAGEMENT_LOG) << "Unable to load image" << dir.path() + QLatin1Char('/') + fileName;
349 }
350 }
351 }
352 }
353}
354
355void Signature::writeConfig(KConfigGroup &config) const
356{
357 switch (d->type) {
358 case Inlined:
359 config.writeEntry(sigTypeKey, sigTypeInlineValue);
360 config.writeEntry(sigTypeInlinedHtmlKey, d->inlinedHtml);
361 break;
362 case FromFile:
363 config.writeEntry(sigTypeKey, sigTypeFileValue);
364 config.writePathEntry(sigFileKey, d->path);
365 break;
366 case FromCommand:
367 config.writeEntry(sigTypeKey, sigTypeCommandValue);
368 config.writePathEntry(sigCommandKey, d->path);
369 break;
370 default:
371 break;
372 }
373 config.writeEntry(sigTextKey, d->text);
374 config.writeEntry(sigImageLocation, d->saveLocation);
375 config.writeEntry(sigEnabled, d->enabled);
376
377 d->cleanupImages();
378 d->saveImages();
379}
380
381QList<Signature::EmbeddedImagePtr> Signature::embeddedImages() const
382{
383 return d->embeddedImages;
384}
385
386void Signature::setEmbeddedImages(const QList<Signature::EmbeddedImagePtr> &embedded)
387{
388 d->embeddedImages = embedded;
389}
390
391// --------------------- Operators -------------------//
392
393QDataStream &KIdentityManagementCore::operator<<(QDataStream &stream, const KIdentityManagementCore::Signature &sig)
394{
395 return stream << static_cast<quint8>(sig.type()) << sig.path() << sig.text() << sig.imageLocation() << sig.embeddedImages() << sig.isEnabledSignature();
396}
397
398QDataStream &KIdentityManagementCore::operator>>(QDataStream &stream, KIdentityManagementCore::Signature &sig)
399{
400 quint8 s;
402 QString text;
403 QString saveLocation;
405 bool enabled;
406 stream >> s >> path >> text >> saveLocation >> lst >> enabled;
407 sig.setText(text);
408 sig.setPath(path);
409 sig.setImageLocation(saveLocation);
410 sig.setEmbeddedImages(lst);
411 sig.setEnabledSignature(enabled);
412 sig.setType(static_cast<Signature::Type>(s));
413 return stream;
414}
415
416bool Signature::operator==(const Signature &other) const
417{
418 if (d->type != other.type()) {
419 return false;
420 }
421
422 if (d->enabled != other.isEnabledSignature()) {
423 return false;
424 }
425
426 if (d->type == Inlined && d->inlinedHtml) {
427 if (d->saveLocation != other.imageLocation()) {
428 return false;
429 }
430 if (d->embeddedImages != other.embeddedImages()) {
431 return false;
432 }
433 }
434
435 switch (d->type) {
436 case Inlined:
437 return d->text == other.text();
438 case FromFile:
439 case FromCommand:
440 return d->path == other.path();
441 default:
442 case Disabled:
443 return true;
444 }
445}
446
448{
449 QString sigText = rawText();
450 if (!sigText.isEmpty() && isInlinedHtml() && type() == Inlined) {
451 // Use a QTextDocument as a helper, it does all the work for us and
452 // strips all HTML tags.
453 QTextDocument helper;
454 QTextCursor helperCursor(&helper);
455 helperCursor.insertHtml(sigText);
456 sigText = helper.toPlainText();
457 }
458 return sigText;
459}
460
461void Signature::addImage(const QImage &imageData, const QString &imageName)
462{
463 Q_ASSERT(!(d->saveLocation.isEmpty()));
464 Signature::EmbeddedImagePtr image(new Signature::EmbeddedImage());
465 image->image = imageData;
466 image->name = imageName;
467 d->embeddedImages.append(image);
468}
469
471{
472 d->saveLocation = path;
473}
474
475QString Signature::imageLocation() const
476{
477 return d->saveLocation;
478}
479
480// --------------- Getters -----------------------//
481
482QString Signature::text() const
483{
484 return d->text;
485}
486
487QString Signature::path() const
488{
489 return d->path;
490}
491
493{
494 return d->type;
495}
496
497// --------------- Setters -----------------------//
498
500{
501 d->text = text;
502 d->type = Inlined;
503}
504
505void Signature::setType(Type type)
506{
507 d->type = type;
508}
509
511{
512 d->enabled = enabled;
513}
514
515bool Signature::isEnabledSignature() const
516{
517 return d->enabled;
518}
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
void writePathEntry(const char *Key, const QString &path, WriteConfigFlags pFlags=Normal)
QString readPathEntry(const char *key, const QString &aDefault) const
QString readEntry(const char *key, const char *aDefault=nullptr) const
Abstraction of a signature (aka "footer").
Definition signature.h:61
bool operator==(const Signature &other) const
Used for comparison.
void setPath(const QString &path, bool isExecutable=false)
Set the signature URL and mark this signature as being of "from file" resp.
void setText(const QString &text)
Set the signature text and mark this signature as being of "inline text" type.
void setImageLocation(const QString &path)
Sets the location where the copies of the signature images will be stored.
void setInlinedHtml(bool isHtml)
Sets the inlined signature to text or html.
QString rawText(bool *ok=nullptr, QString *errorMessage=nullptr) const
Signature()
Constructor for disabled signature.
void setEnabledSignature(bool enabled)
setEnabledSignature
QString toPlainText() const
Returns the text of the signature.
QString withSeparator(bool *ok=nullptr, QString *errorMessage=nullptr) const
Type
Type of signature (ie.
Definition signature.h:69
void addImage(const QImage &image, const QString &imageName)
Adds the given image to the signature.
Signature & operator=(const Signature &that)
Assignment operator.
void setShellCommand(const QString &cmd)
void setOutputChannelMode(OutputChannelMode mode)
int execute(int msecs=-1)
QString i18n(const char *text, const TYPE &arg...)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QVariant location(const QVariant &res)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QDebug operator<<(QDebug dbg, const PerceptualColor::MultiSpinBoxSection &value)
char * data()
qsizetype size() const const
bool load(QIODevice *device, const char *format)
void push_back(parameter_type value)
QByteArray readAllStandardError()
QByteArray readAllStandardOutput()
QString & append(QChar ch)
void clear()
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseInsensitive
bool isValid() const const
void insertHtml(const QString &html)
QTextBlock begin() const const
void setHtml(const QString &html)
QString toPlainText() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:11:25 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.