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

KDE's Doxygen guidelines are available online.