KHealthCertificate

shcparser.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "shcparser_p.h"
7#include "jwtparser_p.h"
8#include "kvaccinationcertificate.h"
9#include "logging.h"
10
11#include <QByteArray>
12#include <QDebug>
13#include <QFile>
14#include <QJsonArray>
15#include <QJsonDocument>
16#include <QJsonObject>
17#include <QVariant>
18
19void ShcParser::init()
20{
21 Q_INIT_RESOURCE(shc_certs);
22 Q_INIT_RESOURCE(shc_certs_manual);
23 Q_INIT_RESOURCE(shc_data);
24}
25
26QVariant ShcParser::parse(const QByteArray &data)
27{
28 if (!data.startsWith("shc:/")) {
29 return {};
30 }
31
32 if (data.indexOf('/', 5) > 0) {
33 qCWarning(Log) << "SHC chunked data not supported yet!";
34 return {};
35 }
36
37 QByteArray unpacked;
38 unpacked.reserve(data.size() / 2);
39 for (int i = 5; i < data.size() - 1; i += 2) {
40 unpacked.push_back((data[i] - '0') * 10 + (data[i + 1] - '0') + 45);
41 }
42
43 JwtParser jwt;
44 jwt.parse(unpacked);
45
46 const auto nbf = QDateTime::fromSecsSinceEpoch(jwt.payload().value(QLatin1String("nbf")).toDouble());
47 const auto vc = jwt.payload().value(QLatin1String("vc")).toObject();
48 const auto types = vc.value(QLatin1String("type")).toArray();
49 //qDebug().noquote() << QJsonDocument(vc).toJson();
50 for (const auto &type : types) {
51 if (type.toString() == QLatin1String("https://smarthealth.cards#immunization")) {
52 auto cert = parseImmunization(vc.value(QLatin1String("credentialSubject")).toObject());
53 cert.setCertificateIssueDate(nbf);
54 cert.setCertificateIssuer(jwt.payload().value(QLatin1String("iss")).toString());
55 cert.setRawData(data);
56 cert.setSignatureState(jwt.signatureState());
57 return cert;
58 }
59 }
60
61 return {};
62}
63
64KVaccinationCertificate ShcParser::parseImmunization(const QJsonObject &obj)
65{
66 const auto entries = obj.value(QLatin1String("fhirBundle")).toObject().value(QLatin1String("entry")).toArray();
68 for (const auto &entryV : entries) {
69 const auto res = entryV.toObject().value(QLatin1String("resource")).toObject();
70 const auto resourceType = res.value(QLatin1String("resourceType")).toString();
71 if (resourceType == QLatin1String("Patient")) {
72 cert.setDateOfBirth(QDate::fromString(res.value(QLatin1String("birthDate")).toString(), Qt::ISODate));
73 const auto nameArray = res.value(QLatin1String("name")).toArray();
74 if (nameArray.size() != 1) {
75 return {};
76 }
77 const auto nameObj = nameArray.at(0).toObject();
78 const auto given = nameObj.value(QLatin1String("given")).toArray();
79 QStringList nameParts;
80 nameParts.reserve(given.size() + 1);
81 for (const auto &givenV : given) {
82 nameParts.push_back(givenV.toString());
83 }
84 nameParts.push_back(nameObj.value(QLatin1String("family")).toString());
85 cert.setName(nameParts.join(QLatin1Char(' ')));
86 }
87 else if (resourceType == QLatin1String("Immunization")) {
88 if (res.value(QLatin1String("status")).toString() != QLatin1String("completed")) {
89 continue;
90 }
91 const auto dt = QDate::fromString(res.value(QLatin1String("occurrenceDateTime")).toString(), Qt::ISODate);
92 if (cert.date().isValid() && cert.date() > dt) { // TODO alternatively, emit two certs, one for each dose?
93 cert.setDose(cert.dose() + 1);
94 continue;
95 }
96
97 cert.setDate(dt);
98 cert.setDose(std::max(1, cert.dose() + 1));
99
100 const auto vacCode = res.value(QLatin1String("vaccineCode")).toObject().value(QLatin1String("coding")).toArray();
101 if (vacCode.size() != 1) {
102 continue;
103 }
104 const auto vacObj = vacCode.at(0).toObject();
105 QJsonObject cvxData;
106 if (vacObj.value(QLatin1String("system")).toString() == QLatin1String("http://hl7.org/fhir/sid/cvx")) {
107 QFile cvxFile(QStringLiteral(":/org.kde.khealthcertificate/shc/hl7-cvx-codes.json"));
108 if (!cvxFile.open(QFile::ReadOnly)) {
109 qCWarning(Log) << cvxFile.errorString();
110 }
111 const auto cvxDb = QJsonDocument::fromJson(cvxFile.readAll()).object();
112 cvxData = cvxDb.value(vacObj.value(QLatin1String("code")).toString()).toObject();
113 }
114
115 if (cvxData.isEmpty()) {
116 cert.setVaccine(vacObj.value(QLatin1String("system")).toString() + QLatin1Char('/') + vacObj.value(QLatin1String("code")).toString());
117 } else {
118 cert.setVaccine(cvxData.value(QLatin1String("n")).toString());
119 cert.setDisease(cvxData.value(QLatin1String("d")).toString());
120 cert.setManufacturer(cvxData.value(QLatin1String("m")).toString());
121 }
122 }
123 else {
124 qCDebug(Log) << "unhandled resource type:" << resourceType << res;
125 }
126 }
127 return cert;
128}
A vaccination certificate.
Type type(const QSqlDatabase &db)
char * toString(const EngineQuery &query)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
void push_back(QByteArrayView str)
void reserve(qsizetype size)
qsizetype size() const const
bool startsWith(QByteArrayView bv) const const
QDate fromString(QStringView string, QStringView format, QCalendar cal)
QDateTime fromSecsSinceEpoch(qint64 secs)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
bool isEmpty() const const
QJsonValue value(QLatin1StringView key) const const
QJsonArray toArray() const const
QJsonObject toObject() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:13:32 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.