#include "eetv4response.h" #include #include 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; }