Files
EetCpp/libEet/eetv4response.cpp
T

311 lines
10 KiB
C++

#include "eetv4response.h"
#include <QRegularExpression>
#include <QXmlStreamReader>
namespace
{
constexpr auto SoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
constexpr auto EetNamespace = "http://fs.gov.cz/eet/schema/v4";
void setError(QString *error, const QString &message)
{
if (error != nullptr)
{
*error = message;
}
}
bool isElement(const QXmlStreamReader &reader, const char *name, const char *namespaceUri)
{
return reader.isStartElement() &&
reader.name() == QLatin1String(name) &&
reader.namespaceUri() == QLatin1String(namespaceUri);
}
bool parseBoolean(const QString &value, bool *result)
{
if (value == QLatin1String("true") || value == QLatin1String("1"))
{
*result = true;
return true;
}
if (value == QLatin1String("false") || value == QLatin1String("0"))
{
*result = false;
return true;
}
return false;
}
bool parseDateTime(const QString &value, QDateTime *result)
{
const QDateTime parsed = QDateTime::fromString(value, Qt::ISODate);
if (!parsed.isValid() || value.size() < 20 ||
!(value.endsWith(QLatin1Char('Z')) || value.at(value.size() - 6) == QLatin1Char('+') ||
value.at(value.size() - 6) == QLatin1Char('-')))
{
return false;
}
*result = parsed;
return true;
}
bool parseUuid(const QString &value)
{
static const QRegularExpression expression(
QStringLiteral("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-"
"[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"));
return expression.match(value).hasMatch();
}
bool parsePok(const QString &value)
{
static const QRegularExpression expression(
QStringLiteral("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-"
"[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}-[0-9a-fA-F]{2}$"));
return expression.match(value).hasMatch();
}
bool parseResponseBody(QXmlStreamReader &reader, EetV4Response *response, QString *error)
{
bool hasHeader = false;
bool hasResult = false;
while (reader.readNextStartElement())
{
if (isElement(reader, "Hlavicka", EetNamespace))
{
if (hasHeader)
{
setError(error, QStringLiteral("EET response contains duplicate Hlavicka."));
return false;
}
hasHeader = true;
const auto attributes = reader.attributes();
const QString uuid = attributes.value(QStringLiteral("uuid_zpravy")).toString();
if (!uuid.isEmpty() && !parseUuid(uuid))
{
setError(error, QStringLiteral("EET response contains an invalid message UUID."));
return false;
}
response->messageUuid = uuid;
const QString receivedAt = attributes.value(QStringLiteral("dat_prij")).toString();
const QString rejectedAt = attributes.value(QStringLiteral("dat_odmit")).toString();
if (!receivedAt.isEmpty() && !parseDateTime(receivedAt, &response->receivedAt))
{
setError(error, QStringLiteral("EET response contains an invalid receipt timestamp."));
return false;
}
if (!rejectedAt.isEmpty() && !parseDateTime(rejectedAt, &response->rejectedAt))
{
setError(error, QStringLiteral("EET response contains an invalid rejection timestamp."));
return false;
}
reader.skipCurrentElement();
}
else if (isElement(reader, "Potvrzeni", EetNamespace))
{
if (hasResult)
{
setError(error, QStringLiteral("EET response contains multiple results."));
return false;
}
hasResult = true;
const auto attributes = reader.attributes();
const QString pok = attributes.value(QStringLiteral("pok")).toString();
if (!parsePok(pok))
{
setError(error, QStringLiteral("EET response contains an invalid POK."));
return false;
}
response->pok = pok;
response->status = EetV4ResponseStatus::Success;
const QString test = attributes.value(QStringLiteral("test")).toString();
if (!test.isEmpty() && !parseBoolean(test, &response->test))
{
setError(error, QStringLiteral("EET response contains an invalid test flag."));
return false;
}
reader.skipCurrentElement();
}
else if (isElement(reader, "Chyba", EetNamespace))
{
if (hasResult)
{
setError(error, QStringLiteral("EET response contains multiple results."));
return false;
}
hasResult = true;
const auto attributes = reader.attributes();
bool ok = false;
const int code = attributes.value(QStringLiteral("kod")).toInt(&ok);
if (!ok || code < -999 || code > 999)
{
setError(error, QStringLiteral("EET response contains an invalid error code."));
return false;
}
response->errorCode = code;
response->message = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
response->status = EetV4ResponseStatus::Error;
const QString test = attributes.value(QStringLiteral("test")).toString();
if (!test.isEmpty() && !parseBoolean(test, &response->test))
{
setError(error, QStringLiteral("EET response contains an invalid test flag."));
return false;
}
}
else if (isElement(reader, "Varovani", EetNamespace))
{
bool ok = false;
const int code = reader.attributes().value(QStringLiteral("kod_varov")).toInt(&ok);
if (!ok || code < 1 || code > 999)
{
setError(error, QStringLiteral("EET response contains an invalid warning code."));
return false;
}
response->warningCodes.append(code);
reader.skipCurrentElement();
}
else
{
reader.skipCurrentElement();
}
}
if (!hasHeader || !hasResult)
{
setError(error, QStringLiteral("EET response is missing Hlavicka or a result."));
return false;
}
return true;
}
bool parseFault(QXmlStreamReader &reader, EetV4Response *response, QString *error)
{
QString code;
QString reason;
QString detail;
while (reader.readNextStartElement())
{
if (reader.name() == QLatin1String("faultcode") ||
reader.name() == QLatin1String("Value"))
{
code = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
}
else if (reader.name() == QLatin1String("faultstring") ||
reader.name() == QLatin1String("Text"))
{
reason = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
}
else if (reader.name() == QLatin1String("Detail"))
{
detail = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
}
else
{
reader.skipCurrentElement();
}
}
response->status = EetV4ResponseStatus::SoapFault;
response->message = reason.isEmpty() ? detail : reason;
if (!code.isEmpty())
{
response->message.prepend(code + QStringLiteral(": "));
}
if (response->message.isEmpty())
{
setError(error, QStringLiteral("SOAP Fault does not contain a reason."));
return false;
}
return true;
}
}
bool EetV4ResponseParser::parseSoap(const QByteArray &soap,
EetV4Response *response,
QString *error)
{
if (response == nullptr)
{
setError(error, QStringLiteral("A response output object is required."));
return false;
}
*response = {};
QXmlStreamReader reader(soap);
bool inBody = false;
bool hasPayload = false;
while (!reader.atEnd())
{
reader.readNext();
if (!reader.isStartElement())
{
continue;
}
if (reader.name() == QLatin1String("Envelope"))
{
if (reader.namespaceUri() != QLatin1String(SoapNamespace))
{
setError(error, QStringLiteral("SOAP Envelope has an invalid namespace."));
return false;
}
}
else if (isElement(reader, "Body", SoapNamespace))
{
if (inBody)
{
setError(error, QStringLiteral("SOAP response contains duplicate Body."));
return false;
}
inBody = true;
while (reader.readNextStartElement())
{
if (hasPayload)
{
setError(error, QStringLiteral("SOAP Body contains multiple payloads."));
return false;
}
hasPayload = true;
if (isElement(reader, "Odpoved", EetNamespace))
{
if (!parseResponseBody(reader, response, error))
{
return false;
}
}
else if (reader.name() == QLatin1String("Fault") &&
reader.namespaceUri() == QLatin1String(SoapNamespace))
{
if (!parseFault(reader, response, error))
{
return false;
}
}
else
{
setError(error, QStringLiteral("SOAP Body contains an unknown payload."));
return false;
}
}
}
else if (reader.name() == QLatin1String("Header") &&
reader.namespaceUri() == QLatin1String(SoapNamespace))
{
reader.skipCurrentElement();
}
else
{
reader.skipCurrentElement();
}
}
if (reader.hasError())
{
setError(error, reader.errorString());
return false;
}
if (!inBody || !hasPayload)
{
setError(error, QStringLiteral("SOAP response does not contain a payload."));
return false;
}
return true;
}