311 lines
10 KiB
C++
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;
|
|
}
|