File indexing completed on 2025-01-19 04:46:51

0001 /*
0002    SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <MessageViewer/MessagePartRenderPlugin>
0008 #include <MessageViewer/MessagePartRendererBase>
0009 #include <MessageViewer/MessagePartRendererManager>
0010 
0011 #include <MessageViewer/HtmlWriter>
0012 #include <MimeTreeParser/MessagePart>
0013 
0014 #include <KPkPass/Barcode>
0015 #include <KPkPass/BoardingPass>
0016 
0017 #include <KTextTemplate/MetaType>
0018 #include <KTextTemplate/Template>
0019 
0020 #include <Prison/Barcode>
0021 
0022 #include <QGuiApplication>
0023 #include <QImage>
0024 #include <QUrl>
0025 
0026 static bool isPkPassContent(KMime::Content *content)
0027 {
0028     const auto ct = content->contentType(false);
0029     const QByteArray mimetype = ct ? ct->mimeType() : QByteArray();
0030     if (mimetype == "application/vnd.apple.pkpass") {
0031         return true;
0032     }
0033     if (mimetype != "application/octet-stream" && mimetype != "application/zip") {
0034         return false;
0035     }
0036     if (ct && ct->name().endsWith(QLatin1StringView("pkpass"))) {
0037         return true;
0038     }
0039     const auto cd = content->contentDisposition(false);
0040     return cd && cd->filename().endsWith(QLatin1StringView("pkpass"));
0041 }
0042 
0043 namespace
0044 {
0045 class Formatter : public MessageViewer::MessagePartRendererBase
0046 {
0047 public:
0048     bool render(const MimeTreeParser::MessagePartPtr &msgPart, MessageViewer::HtmlWriter *htmlWriter, MessageViewer::RenderContext *context) const override
0049     {
0050         Q_UNUSED(context)
0051         auto mp = msgPart.dynamicCast<MimeTreeParser::AttachmentMessagePart>();
0052         if (!mp || context->isHiddenHint(msgPart) || !msgPart->content() || !isPkPassContent(msgPart->content())) {
0053             return false;
0054         }
0055 
0056         std::unique_ptr<KPkPass::Pass> pass(KPkPass::Pass::fromData(msgPart->content()->decodedContent()));
0057         if (!pass) {
0058             return false;
0059         }
0060         const auto dir = mp->nodeHelper()->createTempDir(QStringLiteral("pkpass"));
0061         const auto logo = pass->logo();
0062         if (!logo.isNull()) {
0063             const QString fileName = dir + QStringLiteral("/logo.png");
0064             logo.save(fileName);
0065             pass->setProperty("logoUrl", QUrl::fromLocalFile(fileName));
0066             mp->nodeHelper()->addTempFile(fileName);
0067         }
0068         const auto strip = pass->strip();
0069         if (!strip.isNull()) {
0070             const QString fileName = dir + QStringLiteral("/strip.png");
0071             strip.save(fileName);
0072             pass->setProperty("stripUrl", QUrl::fromLocalFile(fileName));
0073             mp->nodeHelper()->addTempFile(fileName);
0074         }
0075         const auto background = pass->background();
0076         if (!background.isNull()) {
0077             const QString fileName = dir + QStringLiteral("/background.png");
0078             background.save(fileName);
0079             pass->setProperty("backgroundUrl", QUrl::fromLocalFile(fileName));
0080             mp->nodeHelper()->addTempFile(fileName);
0081         }
0082         const auto footer = pass->footer();
0083         if (!footer.isNull()) {
0084             const QString fileName = dir + QStringLiteral("/footer.png");
0085             footer.save(fileName);
0086             pass->setProperty("footerUrl", QUrl::fromLocalFile(fileName));
0087             mp->nodeHelper()->addTempFile(fileName);
0088         }
0089         const auto thumbnail = pass->thumbnail();
0090         if (!thumbnail.isNull()) {
0091             const QString fileName = dir + QStringLiteral("/thumbnail.png");
0092             thumbnail.save(fileName);
0093             pass->setProperty("thumbnailUrl", QUrl::fromLocalFile(fileName));
0094             mp->nodeHelper()->addTempFile(fileName);
0095         }
0096 
0097         const auto barcodes = pass->barcodes();
0098         if (!barcodes.isEmpty()) {
0099             const auto barcode = barcodes.at(0);
0100             std::optional<Prison::Barcode> code;
0101             switch (barcode.format()) {
0102             case KPkPass::Barcode::QR:
0103                 code = Prison::Barcode::create(Prison::QRCode);
0104                 break;
0105             case KPkPass::Barcode::Aztec:
0106                 code = Prison::Barcode::create(Prison::Aztec);
0107                 break;
0108             case KPkPass::Barcode::PDF417:
0109                 code = Prison::Barcode::create(Prison::PDF417);
0110                 break;
0111             case KPkPass::Barcode::Code128:
0112                 code = Prison::Barcode::create(Prison::Code128);
0113                 break;
0114             default:
0115                 break;
0116             }
0117 
0118             if (code) {
0119                 code->setData(barcode.message());
0120 
0121                 const QString fileName = dir + QStringLiteral("/barcode.png");
0122                 code->toImage(code->preferredSize(qGuiApp->devicePixelRatio())).save(fileName);
0123 
0124                 pass->setProperty("barcodeUrl", QUrl::fromLocalFile(fileName));
0125                 mp->nodeHelper()->addTempFile(fileName);
0126             }
0127         }
0128 
0129         // Grantlee can't handle QColor...
0130         if (pass->foregroundColor().isValid()) {
0131             pass->setProperty("foregroundColorName", pass->foregroundColor().name());
0132         }
0133         if (pass->backgroundColor().isValid()) {
0134             pass->setProperty("backgroundColorName", pass->backgroundColor().name());
0135         }
0136         if (pass->labelColor().isValid()) {
0137             pass->setProperty("labelColorName", pass->labelColor().name());
0138         }
0139 
0140         auto c = MessageViewer::MessagePartRendererManager::self()->createContext();
0141         c.insert(QStringLiteral("block"), mp.data());
0142         c.insert(QStringLiteral("pass"), pass.get());
0143         KTextTemplate::Template t;
0144         if (qobject_cast<KPkPass::BoardingPass *>(pass.get())) {
0145             t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("org.kde.messageviewer/pkpass/boardingpass.html"));
0146         } else if (pass->type() == KPkPass::Pass::EventTicket) {
0147             t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("org.kde.messageviewer/pkpass/eventticket.html"));
0148         } else if (pass->type() == KPkPass::Pass::Generic) {
0149             t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("org.kde.messageviewer/pkpass/generic.html"));
0150         } else {
0151             // unknown pass type we have no template for
0152             return false;
0153         }
0154         KTextTemplate::OutputStream s(htmlWriter->stream());
0155         t->render(&s, &c);
0156         return true;
0157     }
0158 };
0159 
0160 class Plugin : public QObject, public MessageViewer::MessagePartRenderPlugin
0161 {
0162     Q_OBJECT
0163     Q_INTERFACES(MessageViewer::MessagePartRenderPlugin)
0164     Q_PLUGIN_METADATA(IID "com.kde.messageviewer.bodypartformatter" FILE "pkpass_plugin.json")
0165 public:
0166     explicit Plugin(QObject *parent = nullptr)
0167         : QObject(parent)
0168     {
0169     }
0170 
0171     MessageViewer::MessagePartRendererBase *renderer(int index) override
0172     {
0173         return index == 0 ? new Formatter() : nullptr;
0174     }
0175 };
0176 }
0177 
0178 #include "pkpass_plugin.moc"