File indexing completed on 2024-05-19 05:17:28
0001 /* 0002 SPDX-FileCopyrightText: 2002-2004 Marc Mutz <mutz@kde.org> 0003 SPDX-FileCopyrightText: 2007 Tom Albers <tomalbers@kde.nl> 0004 SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "signature.h" 0010 0011 #include "kidentitymanagementcore_debug.h" 0012 #include <KConfigGroup> 0013 #include <KLocalizedString> 0014 #include <KProcess> 0015 0016 #include <QDir> 0017 #include <QTextBlock> 0018 #include <QTextDocument> 0019 0020 #include <cassert> 0021 0022 using namespace KIdentityManagementCore; 0023 0024 class KIdentityManagementCore::SignaturePrivate 0025 { 0026 public: 0027 explicit SignaturePrivate(Signature *qq) 0028 : q(qq) 0029 { 0030 } 0031 0032 void assignFrom(const KIdentityManagementCore::Signature &that); 0033 void cleanupImages(); 0034 void saveImages() const; 0035 [[nodiscard]] QString textFromFile(bool *ok) const; 0036 [[nodiscard]] QString textFromCommand(bool *ok, QString *errorMessage) const; 0037 0038 /// List of images that belong to this signature. Either added by addImage() or 0039 /// by readConfig(). 0040 QList<Signature::EmbeddedImagePtr> embeddedImages; 0041 0042 /// The directory where the images will be saved to. 0043 QString saveLocation; 0044 QString path; 0045 QString text; 0046 Signature::Type type = Signature::Disabled; 0047 bool enabled = false; 0048 bool inlinedHtml = false; 0049 Signature *const q; 0050 }; 0051 0052 // Returns the names of all images in the HTML code 0053 static QStringList findImageNames(const QString &htmlCode) 0054 { 0055 QStringList imageNames; 0056 QTextDocument doc; 0057 doc.setHtml(htmlCode); 0058 for (auto block = doc.begin(); block.isValid(); block = block.next()) { 0059 for (auto it = block.begin(); !it.atEnd(); ++it) { 0060 const auto fragment = it.fragment(); 0061 if (fragment.isValid()) { 0062 const auto imageFormat = fragment.charFormat().toImageFormat(); 0063 if (imageFormat.isValid() && !imageFormat.name().startsWith(QLatin1StringView("http")) && !imageNames.contains(imageFormat.name())) { 0064 imageNames.push_back(imageFormat.name()); 0065 } 0066 } 0067 } 0068 } 0069 return imageNames; 0070 } 0071 0072 void SignaturePrivate::assignFrom(const KIdentityManagementCore::Signature &that) 0073 { 0074 path = that.path(); 0075 inlinedHtml = that.isInlinedHtml(); 0076 text = that.text(); 0077 type = that.type(); 0078 enabled = that.isEnabledSignature(); 0079 saveLocation = that.imageLocation(); 0080 embeddedImages = that.embeddedImages(); 0081 } 0082 0083 void SignaturePrivate::cleanupImages() 0084 { 0085 // Remove any images from the internal structure that are no longer there 0086 if (inlinedHtml) { 0087 auto it = std::remove_if(embeddedImages.begin(), embeddedImages.end(), [this](const Signature::EmbeddedImagePtr &imageInList) { 0088 const QStringList lstImage = findImageNames(text); 0089 for (const QString &imageInHtml : lstImage) { 0090 if (imageInHtml == imageInList->name) { 0091 return false; 0092 } 0093 } 0094 return true; 0095 }); 0096 embeddedImages.erase(it, embeddedImages.end()); 0097 } 0098 0099 // Delete all the old image files 0100 if (!saveLocation.isEmpty()) { 0101 QDir dir(saveLocation); 0102 const QStringList lst = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); 0103 for (const QString &fileName : lst) { 0104 if (fileName.endsWith(QLatin1StringView(".png"), Qt::CaseInsensitive)) { 0105 qCDebug(KIDENTITYMANAGEMENT_LOG) << "Deleting old image" << dir.path() + fileName; 0106 dir.remove(fileName); 0107 } 0108 } 0109 } 0110 } 0111 0112 void SignaturePrivate::saveImages() const 0113 { 0114 if (inlinedHtml && !saveLocation.isEmpty()) { 0115 for (const Signature::EmbeddedImagePtr &image : std::as_const(embeddedImages)) { 0116 const QString location = saveLocation + QLatin1Char('/') + image->name; 0117 if (!image->image.save(location, "PNG")) { 0118 qCWarning(KIDENTITYMANAGEMENT_LOG) << "Failed to save image" << location; 0119 } 0120 } 0121 } 0122 } 0123 0124 QString SignaturePrivate::textFromFile(bool *ok) const 0125 { 0126 assert(type == Signature::FromFile); 0127 0128 QFile f(path); 0129 if (!f.open(QIODevice::ReadOnly)) { 0130 qCWarning(KIDENTITYMANAGEMENT_LOG) << "Failed to open" << path << ":" << f.errorString(); 0131 if (ok) { 0132 *ok = false; 0133 } 0134 return {}; 0135 } 0136 0137 if (ok) { 0138 *ok = true; 0139 } 0140 const QByteArray ba = f.readAll(); 0141 return QString::fromLocal8Bit(ba.data(), ba.size()); 0142 } 0143 0144 QString SignaturePrivate::textFromCommand(bool *ok, QString *errorMessage) const 0145 { 0146 assert(type == Signature::FromCommand); 0147 0148 // handle pathological cases: 0149 if (path.isEmpty()) { 0150 if (ok) { 0151 *ok = true; 0152 } 0153 return {}; 0154 } 0155 0156 // create a shell process: 0157 KProcess proc; 0158 proc.setOutputChannelMode(KProcess::SeparateChannels); 0159 proc.setShellCommand(path); 0160 int rc = proc.execute(); 0161 0162 // handle errors, if any: 0163 if (rc != 0) { 0164 if (ok) { 0165 *ok = false; 0166 } 0167 if (errorMessage) { 0168 *errorMessage = i18n( 0169 "<qt>Failed to execute signature script<p><b>%1</b>:</p>" 0170 "<p>%2</p></qt>", 0171 path, 0172 QString::fromUtf8(proc.readAllStandardError())); 0173 } 0174 return {}; 0175 } 0176 0177 // no errors: 0178 if (ok) { 0179 *ok = true; 0180 } 0181 0182 // get output: 0183 const QByteArray output = proc.readAllStandardOutput(); 0184 0185 // TODO: hmm, should we allow other encodings, too? 0186 return QString::fromLocal8Bit(output.data(), output.size()); 0187 } 0188 0189 QDataStream &operator<<(QDataStream &stream, const KIdentityManagementCore::Signature::EmbeddedImagePtr &img) 0190 { 0191 return stream << img->image << img->name; 0192 } 0193 0194 QDataStream &operator>>(QDataStream &stream, const KIdentityManagementCore::Signature::EmbeddedImagePtr &img) 0195 { 0196 return stream >> img->image >> img->name; 0197 } 0198 0199 Signature::Signature() 0200 : d(new SignaturePrivate(this)) 0201 { 0202 d->type = Disabled; 0203 d->inlinedHtml = false; 0204 } 0205 0206 Signature::Signature(const QString &text) 0207 : d(new SignaturePrivate(this)) 0208 { 0209 d->type = Inlined; 0210 d->inlinedHtml = false; 0211 d->text = text; 0212 } 0213 0214 Signature::Signature(const QString &path, bool isExecutable) 0215 : d(new SignaturePrivate(this)) 0216 { 0217 d->type = isExecutable ? FromCommand : FromFile; 0218 d->path = path; 0219 } 0220 0221 Signature::Signature(const Signature &that) 0222 : d(new SignaturePrivate(this)) 0223 { 0224 d->assignFrom(that); 0225 } 0226 0227 Signature &Signature::operator=(const KIdentityManagementCore::Signature &that) 0228 { 0229 if (this == &that) { 0230 return *this; 0231 } 0232 0233 d->assignFrom(that); 0234 return *this; 0235 } 0236 0237 Signature::~Signature() = default; 0238 0239 QString Signature::rawText(bool *ok, QString *errorMessage) const 0240 { 0241 switch (d->type) { 0242 case Disabled: 0243 if (ok) { 0244 *ok = true; 0245 } 0246 return {}; 0247 case Inlined: 0248 if (ok) { 0249 *ok = true; 0250 } 0251 return d->text; 0252 case FromFile: 0253 return d->textFromFile(ok); 0254 case FromCommand: 0255 return d->textFromCommand(ok, errorMessage); 0256 } 0257 qCritical() << "Signature::type() returned unknown value!"; 0258 return {}; // make compiler happy 0259 } 0260 0261 QString Signature::withSeparator(bool *ok, QString *errorMessage) const 0262 { 0263 QString signature = rawText(ok, errorMessage); 0264 if (ok && (*ok) == false) { 0265 return {}; 0266 } 0267 0268 if (signature.isEmpty()) { 0269 return signature; // don't add a separator in this case 0270 } 0271 0272 const bool htmlSig = (isInlinedHtml() && d->type == Inlined); 0273 QString newline = htmlSig ? QStringLiteral("<br>") : QStringLiteral("\n"); 0274 if (htmlSig && signature.startsWith(QLatin1StringView("<p"))) { 0275 newline.clear(); 0276 } 0277 0278 if (signature.startsWith(QLatin1StringView("-- ") + newline) || (signature.indexOf(newline + QLatin1StringView("-- ") + newline) != -1)) { 0279 // already have signature separator at start of sig or inside sig: 0280 return signature; 0281 } else { 0282 // need to prepend one: 0283 return QLatin1StringView("-- ") + newline + signature; 0284 } 0285 } 0286 0287 void Signature::setPath(const QString &path, bool isExecutable) 0288 { 0289 d->path = path; 0290 d->type = isExecutable ? FromCommand : FromFile; 0291 } 0292 0293 void Signature::setInlinedHtml(bool isHtml) 0294 { 0295 d->inlinedHtml = isHtml; 0296 } 0297 0298 bool Signature::isInlinedHtml() const 0299 { 0300 return d->inlinedHtml; 0301 } 0302 0303 // config keys and values: 0304 static const char sigTypeKey[] = "Signature Type"; 0305 static const char sigTypeInlineValue[] = "inline"; 0306 static const char sigTypeFileValue[] = "file"; 0307 static const char sigTypeCommandValue[] = "command"; 0308 static const char sigTypeDisabledValue[] = "disabled"; 0309 static const char sigTextKey[] = "Inline Signature"; 0310 static const char sigFileKey[] = "Signature File"; 0311 static const char sigCommandKey[] = "Signature Command"; 0312 static const char sigTypeInlinedHtmlKey[] = "Inlined Html"; 0313 static const char sigImageLocation[] = "Image Location"; 0314 static const char sigEnabled[] = "Signature Enabled"; 0315 0316 void Signature::readConfig(const KConfigGroup &config) 0317 { 0318 QString sigType = config.readEntry(sigTypeKey); 0319 if (sigType == QLatin1StringView(sigTypeInlineValue)) { 0320 d->type = Inlined; 0321 d->inlinedHtml = config.readEntry(sigTypeInlinedHtmlKey, false); 0322 } else if (sigType == QLatin1StringView(sigTypeFileValue)) { 0323 d->type = FromFile; 0324 d->path = config.readPathEntry(sigFileKey, QString()); 0325 } else if (sigType == QLatin1StringView(sigTypeCommandValue)) { 0326 d->type = FromCommand; 0327 d->path = config.readPathEntry(sigCommandKey, QString()); 0328 } else if (sigType == QLatin1StringView(sigTypeDisabledValue)) { 0329 d->enabled = false; 0330 } 0331 if (d->type != Disabled) { 0332 d->enabled = config.readEntry(sigEnabled, true); 0333 } 0334 0335 d->text = config.readEntry(sigTextKey); 0336 d->saveLocation = config.readEntry(sigImageLocation); 0337 0338 if (isInlinedHtml() && !d->saveLocation.isEmpty()) { 0339 QDir dir(d->saveLocation); 0340 const QStringList lst = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); 0341 for (const QString &fileName : lst) { 0342 if (fileName.endsWith(QLatin1StringView(".png"), Qt::CaseInsensitive)) { 0343 QImage image; 0344 if (image.load(dir.path() + QLatin1Char('/') + fileName)) { 0345 addImage(image, fileName); 0346 } else { 0347 qCWarning(KIDENTITYMANAGEMENT_LOG) << "Unable to load image" << dir.path() + QLatin1Char('/') + fileName; 0348 } 0349 } 0350 } 0351 } 0352 } 0353 0354 void Signature::writeConfig(KConfigGroup &config) const 0355 { 0356 switch (d->type) { 0357 case Inlined: 0358 config.writeEntry(sigTypeKey, sigTypeInlineValue); 0359 config.writeEntry(sigTypeInlinedHtmlKey, d->inlinedHtml); 0360 break; 0361 case FromFile: 0362 config.writeEntry(sigTypeKey, sigTypeFileValue); 0363 config.writePathEntry(sigFileKey, d->path); 0364 break; 0365 case FromCommand: 0366 config.writeEntry(sigTypeKey, sigTypeCommandValue); 0367 config.writePathEntry(sigCommandKey, d->path); 0368 break; 0369 default: 0370 break; 0371 } 0372 config.writeEntry(sigTextKey, d->text); 0373 config.writeEntry(sigImageLocation, d->saveLocation); 0374 config.writeEntry(sigEnabled, d->enabled); 0375 0376 d->cleanupImages(); 0377 d->saveImages(); 0378 } 0379 0380 QList<Signature::EmbeddedImagePtr> Signature::embeddedImages() const 0381 { 0382 return d->embeddedImages; 0383 } 0384 0385 void Signature::setEmbeddedImages(const QList<Signature::EmbeddedImagePtr> &embedded) 0386 { 0387 d->embeddedImages = embedded; 0388 } 0389 0390 // --------------------- Operators -------------------// 0391 0392 QDataStream &KIdentityManagementCore::operator<<(QDataStream &stream, const KIdentityManagementCore::Signature &sig) 0393 { 0394 return stream << static_cast<quint8>(sig.type()) << sig.path() << sig.text() << sig.imageLocation() << sig.embeddedImages() << sig.isEnabledSignature(); 0395 } 0396 0397 QDataStream &KIdentityManagementCore::operator>>(QDataStream &stream, KIdentityManagementCore::Signature &sig) 0398 { 0399 quint8 s; 0400 QString path; 0401 QString text; 0402 QString saveLocation; 0403 QList<Signature::EmbeddedImagePtr> lst; 0404 bool enabled; 0405 stream >> s >> path >> text >> saveLocation >> lst >> enabled; 0406 sig.setText(text); 0407 sig.setPath(path); 0408 sig.setImageLocation(saveLocation); 0409 sig.setEmbeddedImages(lst); 0410 sig.setEnabledSignature(enabled); 0411 sig.setType(static_cast<Signature::Type>(s)); 0412 return stream; 0413 } 0414 0415 bool Signature::operator==(const Signature &other) const 0416 { 0417 if (d->type != other.type()) { 0418 return false; 0419 } 0420 0421 if (d->enabled != other.isEnabledSignature()) { 0422 return false; 0423 } 0424 0425 if (d->type == Inlined && d->inlinedHtml) { 0426 if (d->saveLocation != other.imageLocation()) { 0427 return false; 0428 } 0429 if (d->embeddedImages != other.embeddedImages()) { 0430 return false; 0431 } 0432 } 0433 0434 switch (d->type) { 0435 case Inlined: 0436 return d->text == other.text(); 0437 case FromFile: 0438 case FromCommand: 0439 return d->path == other.path(); 0440 default: 0441 case Disabled: 0442 return true; 0443 } 0444 } 0445 0446 QString Signature::toPlainText() const 0447 { 0448 QString sigText = rawText(); 0449 if (!sigText.isEmpty() && isInlinedHtml() && type() == Inlined) { 0450 // Use a QTextDocument as a helper, it does all the work for us and 0451 // strips all HTML tags. 0452 QTextDocument helper; 0453 QTextCursor helperCursor(&helper); 0454 helperCursor.insertHtml(sigText); 0455 sigText = helper.toPlainText(); 0456 } 0457 return sigText; 0458 } 0459 0460 void Signature::addImage(const QImage &imageData, const QString &imageName) 0461 { 0462 Q_ASSERT(!(d->saveLocation.isEmpty())); 0463 Signature::EmbeddedImagePtr image(new Signature::EmbeddedImage()); 0464 image->image = imageData; 0465 image->name = imageName; 0466 d->embeddedImages.append(image); 0467 } 0468 0469 void Signature::setImageLocation(const QString &path) 0470 { 0471 d->saveLocation = path; 0472 } 0473 0474 QString Signature::imageLocation() const 0475 { 0476 return d->saveLocation; 0477 } 0478 0479 // --------------- Getters -----------------------// 0480 0481 QString Signature::text() const 0482 { 0483 return d->text; 0484 } 0485 0486 QString Signature::path() const 0487 { 0488 return d->path; 0489 } 0490 0491 Signature::Type Signature::type() const 0492 { 0493 return d->type; 0494 } 0495 0496 // --------------- Setters -----------------------// 0497 0498 void Signature::setText(const QString &text) 0499 { 0500 d->text = text; 0501 d->type = Inlined; 0502 } 0503 0504 void Signature::setType(Type type) 0505 { 0506 d->type = type; 0507 } 0508 0509 void Signature::setEnabledSignature(bool enabled) 0510 { 0511 d->enabled = enabled; 0512 } 0513 0514 bool Signature::isEnabledSignature() const 0515 { 0516 return d->enabled; 0517 }