AusweisApp2/src/configuration/ReaderConfigurationParser.cpp

263 lines
6.8 KiB
C++

/*!
* \copyright Copyright (c) 2015-2018 Governikus GmbH & Co. KG, Germany
*/
#include "ReaderConfigurationParser.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QVersionNumber>
using namespace governikus;
Q_DECLARE_LOGGING_CATEGORY(card_drivers)
ReaderConfigurationParser::EntryParser::EntryParser(const QJsonValue& pJsonValue)
: mJsonValue(pJsonValue)
{
}
ReaderConfigurationParser::EntryParser::~EntryParser()
{
}
QString ReaderConfigurationParser::EntryParser::getDriverUrl(const QJsonObject& pObject) const
{
const QJsonValue driversValue = pObject.value(QLatin1String("Drivers"));
if (!driversValue.isArray())
{
qCWarning(card_drivers) << "The value of 'Drivers' must be an array";
return QString();
}
const QJsonArray& driversArray = driversValue.toArray();
for (const auto& entry : driversArray)
{
const QJsonObject& obj = entry.toObject();
if (obj.isEmpty())
{
qCWarning(card_drivers) << "Drivers entry must be a valid object";
return QString();
}
const QJsonValue& platforms = obj.value(QLatin1String("Platforms"));
if (!platforms.isArray())
{
qCWarning(card_drivers) << "Invalid or missing Platforms tag";
return QString();
}
const QString& url = obj.value(QLatin1String("URL")).toString();
if (url.isEmpty())
{
qCWarning(card_drivers) << "Invalid or missing URL tag";
return QString();
}
if (matchPlatform(platforms.toArray()))
{
return url;
}
}
// No URL for this platform found, but entry is syntactically correct:
// return empty non-null string.
return QStringLiteral("");
}
bool ReaderConfigurationParser::EntryParser::matchPlatform(const QJsonArray& pPlatforms, const QOperatingSystemVersion& pCurrentVersion) const
{
QString currentOS;
switch (pCurrentVersion.type())
{
case QOperatingSystemVersion::Windows:
currentOS = QStringLiteral("win");
break;
case QOperatingSystemVersion::MacOS:
currentOS = QStringLiteral("mac");
break;
default:
currentOS = QStringLiteral("unknown");
}
const auto& parseSystemVersion = [&pCurrentVersion](const QString& pVersion) -> QOperatingSystemVersion {
if (pVersion.isEmpty())
{
return pCurrentVersion;
}
const auto& number = QVersionNumber::fromString(pVersion);
const auto minor = number.segmentCount() > 1 ? number.minorVersion() : -1;
const auto micro = number.segmentCount() > 2 ? number.microVersion() : -1;
return QOperatingSystemVersion(pCurrentVersion.type(), number.majorVersion(), minor, micro);
};
for (const auto& entry : pPlatforms)
{
const auto& obj = entry.toObject();
if (obj.value(QLatin1String("os")).toString() == currentOS)
{
const auto& min = obj.value(QLatin1String("min")).toString();
const auto& max = obj.value(QLatin1String("max")).toString();
if (pCurrentVersion >= parseSystemVersion(min) && pCurrentVersion <= parseSystemVersion(max))
{
return true;
}
}
}
return false;
}
ReaderConfigurationInfo ReaderConfigurationParser::EntryParser::parse() const
{
if (!mJsonValue.isObject())
{
return fail(QStringLiteral("Cannot parse Json value: object expected"));
}
const QJsonObject& object = mJsonValue.toObject();
bool parseOk = false;
const uint vendorId = object.value(QLatin1String("VendorId")).toString().toUInt(&parseOk, 16);
if (!parseOk)
{
return fail(QStringLiteral("Invalid or missing vendor id"));
}
const uint productId = object.value(QLatin1String("ProductId")).toString().toUInt(&parseOk, 16);
if (!parseOk)
{
return fail(QStringLiteral("Invalid or missing product id"));
}
const QString& name = object.value(QLatin1String("Name")).toString();
if (name.isEmpty())
{
return fail(QStringLiteral("Invalid or missing name"));
}
const QString& url = getDriverUrl(object);
if (url.isNull())
{
return fail(QStringLiteral("Invalid driver URL entry"));
}
// If Pattern is missing or empty => use name (exact match).
const QString& pattern = object.value(QLatin1String("Pattern")).toString();
const QString& icon = object.value(QLatin1String("Icon")).toString();
const QString& iconWithNPA = object.value(QLatin1String("IconWithNPA")).toString();
return ReaderConfigurationInfo(vendorId, productId, name, url, pattern, icon, iconWithNPA);
}
ReaderConfigurationInfo ReaderConfigurationParser::EntryParser::fail(const QString& pLogMessage) const
{
qCWarning(card_drivers) << pLogMessage;
return ReaderConfigurationInfo();
}
QVector<ReaderConfigurationInfo> ReaderConfigurationParser::parse(const QByteArray& pData)
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(pData, &error);
if (error.error != QJsonParseError::NoError)
{
return fail(QStringLiteral("Json parsing failed. Error at offset %1: %2").arg(error.offset).arg(error.errorString()));
}
const QJsonObject& rootObject = doc.object();
if (rootObject.isEmpty())
{
return fail(QStringLiteral("Expected object at top level"));
}
if (!rootObject.contains(QStringLiteral("SupportedDevices")))
{
return fail(QStringLiteral("Root object does not contain a property named 'SupportedDevices'"));
}
// Root object OK. Look at child list.
const QJsonValue& devicesValue = rootObject.value(QStringLiteral("SupportedDevices"));
if (!devicesValue.isArray())
{
return fail(QStringLiteral("The value of 'SupportedDevices' must be an array"));
}
const QJsonArray& devicesArray = devicesValue.toArray();
QVector<ReaderConfigurationInfo> infos;
for (const auto& entry : devicesArray)
{
auto info = EntryParser(entry).parse();
if (info.isKnownReader())
{
if (hasUniqueId(info, infos))
{
infos += info;
}
else
{
const auto& json = QString::fromUtf8(QJsonDocument(entry.toObject()).toJson());
return fail(QStringLiteral("Invalid reader configuration entry: '%1' duplicate reader information").arg(json));
}
}
else
{
return fail(QStringLiteral("Invalid reader configuration entry: ") + QString::fromUtf8(QJsonDocument(entry.toObject()).toJson()));
}
}
return infos;
}
QVector<ReaderConfigurationInfo> ReaderConfigurationParser::fail(const QString& pLogMessage)
{
qCWarning(card_drivers) << pLogMessage;
return QVector<ReaderConfigurationInfo>();
}
bool ReaderConfigurationParser::hasUniqueId(const ReaderConfigurationInfo& pInfo, const QVector<ReaderConfigurationInfo>& pInfos)
{
const uint vendorId = pInfo.getVendorId();
const uint productId = pInfo.getProductId();
const QString& name = pInfo.getName();
const QString& pattern = pInfo.getPattern();
for (const ReaderConfigurationInfo& info : pInfos)
{
if (vendorId > 0 && productId > 0 && vendorId == info.getVendorId() && productId == info.getProductId())
{
return false;
}
if (name == info.getName())
{
return false;
}
if (!pattern.isEmpty() && pattern == info.getPattern())
{
return false;
}
}
return true;
}