File indexing completed on 2025-02-02 05:02:28

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "config-itinerary.h"
0008 #include "healthcertificatemanager.h"
0009 
0010 #if HAVE_KHEALTHCERTIFICATE
0011 #include <KHealthCertificate/KHealthCertificateParser>
0012 #include <KHealthCertificate/KRecoveryCertificate>
0013 #include <KHealthCertificate/KTestCertificate>
0014 #include <KHealthCertificate/KVaccinationCertificate>
0015 #endif
0016 
0017 #include <KLocalizedString>
0018 
0019 #include <QDebug>
0020 #include <QDir>
0021 #include <QDirIterator>
0022 #include <QFile>
0023 #include <QStandardPaths>
0024 #include <QUuid>
0025 
0026 HealthCertificateManager::HealthCertificateManager(QObject *parent)
0027     : QAbstractListModel(parent)
0028 {
0029     loadCertificates();
0030 }
0031 
0032 HealthCertificateManager::~HealthCertificateManager() = default;
0033 
0034 bool HealthCertificateManager::isAvailable()
0035 {
0036 #if HAVE_KHEALTHCERTIFICATE
0037     return true;
0038 #else
0039     return false;
0040 #endif
0041 }
0042 
0043 static QString basePath()
0044 {
0045     return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1StringView("/health-certificates/");
0046 }
0047 
0048 bool HealthCertificateManager::importCertificate(const QByteArray &rawData)
0049 {
0050     // check whether we know this certificate already
0051     for (const auto &c : m_certificates) {
0052         if (certificateRawData(c) == rawData) {
0053             return true;
0054         }
0055     }
0056 #if HAVE_KHEALTHCERTIFICATE
0057     CertData certData;
0058     certData.cert = KHealthCertificateParser::parse(rawData);
0059     if (certData.cert.isNull()) {
0060         return false;
0061     }
0062 
0063     auto path = basePath();
0064     QDir().mkpath(path);
0065     certData.name = QUuid::createUuid().toString(QUuid::WithoutBraces);
0066     path += QLatin1Char('/') + certData.name;
0067 
0068     QFile f(path);
0069     if (!f.open(QFile::WriteOnly)) {
0070         qWarning() << f.errorString() << f.fileName();
0071         return false;
0072     }
0073     f.write(rawData);
0074     f.close();
0075 
0076     const auto it = std::lower_bound(m_certificates.begin(), m_certificates.end(), certData, certLessThan);
0077     const auto row = std::distance(m_certificates.begin(), it);
0078     beginInsertRows({}, row, row);
0079     m_certificates.insert(it, std::move(certData));
0080     endInsertRows();
0081     Q_EMIT newCertificateLoaded(row);
0082     return true;
0083 #else
0084     return false;
0085 #endif
0086 }
0087 
0088 void HealthCertificateManager::removeCertificate(int row)
0089 {
0090     beginRemoveRows({}, row, row);
0091     const auto it = m_certificates.begin() + row;
0092     QFile::remove(basePath() + QLatin1Char('/') + (*it).name);
0093     m_certificates.erase(it);
0094     endRemoveRows();
0095 }
0096 
0097 int HealthCertificateManager::rowCount(const QModelIndex& parent) const
0098 {
0099     if (parent.isValid()) {
0100         return 0;
0101     }
0102     return m_certificates.size();
0103 }
0104 
0105 QVariant HealthCertificateManager::data(const QModelIndex &index, int role) const
0106 {
0107     if (!checkIndex(index) || index.row() < 0) {
0108         return {};
0109     }
0110 
0111     const auto &v = m_certificates[index.row()];
0112     switch (role) {
0113         case Qt::DisplayRole:
0114 #if HAVE_KHEALTHCERTIFICATE
0115             if (v.cert.userType() == qMetaTypeId<KVaccinationCertificate>()) {
0116                 const auto cert = v.cert.value<KVaccinationCertificate>();
0117                 if (cert.dose() > 0 && cert.totalDoses() > 0) {
0118                     return i18n("Vaccination %1/%2 (%3)", cert.dose(), cert.totalDoses(), cert.name());
0119                 }
0120                 return i18n("Vaccination (%1)", cert.name());
0121             }
0122             if (v.cert.userType() == qMetaTypeId<KTestCertificate>()) {
0123                 const auto cert = v.cert.value<KTestCertificate>();
0124                 return i18n("Test %1 (%2)", QLocale().toString(cert.date().isValid() ? cert.date() : cert.certificateIssueDate().date(), QLocale::NarrowFormat), cert.name());
0125             }
0126             if (v.cert.userType() == qMetaTypeId<KRecoveryCertificate>()) {
0127                 const auto cert = v.cert.value<KRecoveryCertificate>();
0128                 return i18n("Recovery (%1)", cert.name());
0129             }
0130 #endif
0131             return {};
0132         case CertificateRole:
0133             return v.cert;
0134         case RawDataRole:
0135             return certificateRawData(v);
0136         case StorageIdRole:
0137             return v.name;
0138     }
0139     return {};
0140 }
0141 
0142 QHash<int, QByteArray> HealthCertificateManager::roleNames() const
0143 {
0144     auto rns = QAbstractListModel::roleNames();
0145     rns.insert(CertificateRole, "certificate");
0146     rns.insert(RawDataRole, "rawData");
0147     rns.insert(StorageIdRole, "storageId");
0148     return rns;
0149 }
0150 
0151 void HealthCertificateManager::loadCertificates()
0152 {
0153     beginResetModel();
0154     for (QDirIterator it(basePath(), QDir::Files); it.hasNext();) {
0155         QFile f(it.next());
0156         if (!f.open(QFile::ReadOnly)) {
0157             qWarning() << f.errorString() << f.fileName();
0158             continue;
0159         }
0160 
0161         const auto rawData = f.readAll();
0162         CertData certData;
0163         certData.name = it.fileName();
0164 #if HAVE_KHEALTHCERTIFICATE
0165         certData.cert = KHealthCertificateParser::parse(rawData);
0166 #endif
0167         if (certData.cert.isNull()) {
0168             continue;
0169         }
0170 
0171         m_certificates.push_back(std::move(certData));
0172     }
0173     std::sort(m_certificates.begin(), m_certificates.end(), certLessThan);
0174     endResetModel();
0175 }
0176 
0177 QByteArray HealthCertificateManager::certificateRawData([[maybe_unused]] const CertData &certData) const
0178 {
0179 #if HAVE_KHEALTHCERTIFICATE
0180     if (certData.cert.userType() == qMetaTypeId<KVaccinationCertificate>()) {
0181         return certData.cert.value<KVaccinationCertificate>().rawData();
0182     }
0183     if (certData.cert.userType() == qMetaTypeId<KTestCertificate>()) {
0184         return certData.cert.value<KTestCertificate>().rawData();
0185     }
0186     if (certData.cert.userType() == qMetaTypeId<KRecoveryCertificate>()) {
0187         return certData.cert.value<KRecoveryCertificate>().rawData();
0188     }
0189 #endif
0190     return {};
0191 }
0192 
0193 bool HealthCertificateManager::certLessThan(const CertData &lhs, const CertData &rhs)
0194 {
0195 #if HAVE_KHEALTHCERTIFICATE
0196     const auto lhsDt = KHealthCertificate::relevantUntil(lhs.cert);
0197     const auto rhsDt = KHealthCertificate::relevantUntil(rhs.cert);
0198     if (lhsDt == rhsDt) {
0199         return lhs.name < rhs.name;
0200     }
0201     if (!lhsDt.isValid()) {
0202         return false;
0203     }
0204     return !rhsDt.isValid() || lhsDt > rhsDt;
0205 #else
0206     return lhs.name < rhs.name;
0207 #endif
0208 }
0209 
0210 #include "moc_healthcertificatemanager.cpp"