File indexing completed on 2024-05-12 04:42:32

0001 /*
0002     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "abstractbackend.h"
0008 #include "logging.h"
0009 
0010 #include <KPublicTransport/JourneyReply>
0011 #include <KPublicTransport/JourneyRequest>
0012 #include <KPublicTransport/Location>
0013 
0014 #include <QDebug>
0015 #include <QDir>
0016 #include <QJsonDocument>
0017 #include <QNetworkReply>
0018 
0019 using namespace KPublicTransport;
0020 
0021 AbstractBackend::AbstractBackend() = default;
0022 AbstractBackend::~AbstractBackend() = default;
0023 
0024 QString AbstractBackend::backendId() const
0025 {
0026     return m_backendId;
0027 }
0028 
0029 void AbstractBackend::setBackendId(const QString& id)
0030 {
0031     m_backendId = id;
0032 }
0033 
0034 QTimeZone AbstractBackend::timeZone() const
0035 {
0036     return m_timeZone;
0037 }
0038 
0039 void AbstractBackend::setTimeZone(const QTimeZone &tz)
0040 {
0041     m_timeZone = tz;
0042 }
0043 
0044 QStringList AbstractBackend::supportedLanguages() const
0045 {
0046     return m_supportedLanguages;
0047 }
0048 
0049 void AbstractBackend::setSupportedLanguages(const QStringList &langs)
0050 {
0051     m_supportedLanguages = langs;
0052 }
0053 
0054 QString AbstractBackend::preferredLanguage() const
0055 {
0056     const auto localeLangs = QLocale().uiLanguages();
0057     for (const auto &l : localeLangs) {
0058         if (m_supportedLanguages.contains(l)) {
0059             return l;
0060         }
0061         if (l.size() > 2 && l[2] == QLatin1Char('-') && m_supportedLanguages.contains(QStringView(l).left(2))) {
0062             return l.left(2);
0063         }
0064     }
0065 
0066     if (!m_supportedLanguages.empty()) {
0067         return m_supportedLanguages.at(0);
0068     }
0069     return QStringLiteral("en");
0070 }
0071 
0072 void AbstractBackend::init()
0073 {
0074 }
0075 
0076 AbstractBackend::Capabilities AbstractBackend::capabilities() const
0077 {
0078     return NoCapability;
0079 }
0080 
0081 bool AbstractBackend::needsLocationQuery(const Location &loc, QueryType type) const
0082 {
0083     Q_UNUSED(loc);
0084     Q_UNUSED(type);
0085     return false;
0086 }
0087 
0088 bool AbstractBackend::queryStopover(const StopoverRequest &request, StopoverReply *reply, QNetworkAccessManager *nam) const
0089 {
0090     Q_UNUSED(request);
0091     Q_UNUSED(reply);
0092     Q_UNUSED(nam);
0093     return false;
0094 }
0095 
0096 bool AbstractBackend::queryJourney(const JourneyRequest &request, JourneyReply *reply, QNetworkAccessManager *nam) const
0097 {
0098     Q_UNUSED(request);
0099     Q_UNUSED(reply);
0100     Q_UNUSED(nam);
0101     return false;
0102 }
0103 
0104 bool AbstractBackend::queryLocation(const LocationRequest &request, LocationReply *reply, QNetworkAccessManager *nam) const
0105 {
0106     Q_UNUSED(request);
0107     Q_UNUSED(reply);
0108     Q_UNUSED(nam);
0109     return false;
0110 }
0111 
0112 void AbstractBackend::addAttributions(Reply *reply, std::vector<Attribution> &&attributions)
0113 {
0114     reply->addAttributions(std::move(attributions));
0115 }
0116 
0117 Attribution AbstractBackend::attribution() const
0118 {
0119     return m_attribution;
0120 }
0121 
0122 void AbstractBackend::setAttribution(const Attribution &attr)
0123 {
0124     m_attribution = attr;
0125 }
0126 
0127 bool AbstractBackend::isLoggingEnabled() const
0128 {
0129     return qEnvironmentVariableIsSet("KPUBLICTRANSPORT_LOG_DIR");
0130 }
0131 
0132 QString AbstractBackend::logDir() const
0133 {
0134     const QString dir = qEnvironmentVariable("KPUBLICTRANSPORT_LOG_DIR") + QLatin1Char('/') + m_backendId + QLatin1Char('/');
0135     QDir().mkpath(dir);
0136     return dir;
0137 }
0138 
0139 void AbstractBackend::logRequest(const char *typeName, const QJsonObject &requestData, const QNetworkRequest &netRequest, const QByteArray &postData) const
0140 {
0141     const QString baseFile = logDir() + QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMddThhmmss.zzz")) + QLatin1Char('-') + QLatin1String(typeName);
0142 
0143     if (!postData.isEmpty()) {
0144         QFile dataFile(baseFile + QLatin1String("-3-post-data"));
0145         if (!dataFile.open(QFile::WriteOnly)) {
0146             qCWarning(Log) << "could not open" << dataFile.fileName() << dataFile.errorString();
0147             return;
0148         }
0149         dataFile.write(postData);
0150     }
0151 
0152     QFile httpFile(baseFile + QLatin1String("-2-http-request.txt"));
0153 
0154     qCWarning(Log) << "Logging requests to: " << httpFile.fileName();
0155 
0156     if (!httpFile.open(QFile::WriteOnly)) {
0157         qCWarning(Log) << "could not open" << httpFile.fileName() << httpFile.error();
0158         return;
0159     }
0160     httpFile.write(netRequest.url().toString().toUtf8());
0161     httpFile.write("\n");
0162     const auto headers = netRequest.rawHeaderList();
0163     for (const auto &header : headers) {
0164         httpFile.write(header);
0165         httpFile.write(": ");
0166         httpFile.write(netRequest.rawHeader(header));
0167         httpFile.write("\n");
0168     }
0169 
0170     QFile reqFile(baseFile + QLatin1String("-1-request.json"));
0171     if (!reqFile.open(QFile::WriteOnly)) {
0172         qCWarning(Log) << "could not open" << reqFile.fileName() << reqFile.error();
0173         return;
0174     }
0175     reqFile.write(QJsonDocument(requestData).toJson());
0176 }
0177 
0178 void AbstractBackend::logReply(const char *typeName, QNetworkReply *netReply, const QByteArray &data) const
0179 {
0180     const QString baseFile = logDir() + QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMddThhmmss.zzz")) + QLatin1Char('-') + QLatin1String(typeName);
0181 
0182     if (!data.isEmpty()) {
0183         const auto contentType = netReply->header(QNetworkRequest::ContentTypeHeader).toString();
0184         QString ext;
0185         if (contentType == QLatin1String("application/json") || data.startsWith("{")) {
0186             ext = QStringLiteral(".json");
0187         } else if (contentType == QLatin1String("application/xml") || data.startsWith("<")) {
0188             ext = QStringLiteral(".xml");
0189         } else if (data.startsWith("\x1f\x8b")) {
0190             ext = QStringLiteral(".gz");
0191         }
0192 
0193         QFile dataFile(baseFile + QLatin1String("-5-reply-data") + ext);
0194         if (!dataFile.open(QFile::WriteOnly)) {
0195             qCWarning(Log) << "could not open" << dataFile.fileName() << dataFile.errorString();
0196             return;
0197         }
0198         dataFile.write(data);
0199     }
0200 
0201     QFile httpFile(baseFile + QLatin1String("-4-http-reply.txt"));
0202     if (!httpFile.open(QFile::WriteOnly)) {
0203         qCWarning(Log) << "could not open" << httpFile.fileName() << httpFile.error();
0204         return;
0205     }
0206     httpFile.write(netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString().toUtf8());
0207     httpFile.write(" ");
0208     httpFile.write(netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().toUtf8());
0209     httpFile.write("\n");
0210     const auto &headers = netReply->rawHeaderPairs();
0211     for (const auto &header : headers) {
0212         httpFile.write(header.first);
0213         httpFile.write(": ");
0214         httpFile.write(header.second);
0215         httpFile.write("\n");
0216     }
0217 }
0218 
0219 bool AbstractBackend::queryVehicleLayout(const VehicleLayoutRequest &request, VehicleLayoutReply *reply, QNetworkAccessManager *nam) const
0220 {
0221     Q_UNUSED(request);
0222     Q_UNUSED(reply);
0223     Q_UNUSED(nam);
0224     return false;
0225 }
0226 
0227 void AbstractBackend::setCustomCaCertificate(const QString &caCert)
0228 {
0229     QFile f(QLatin1String(":/org.kde.kpublictransport/network-certificates/") + caCert);
0230     if (!f.open(QFile::ReadOnly)) {
0231         qCWarning(Log) << f.fileName() << f.errorString();
0232         return;
0233     }
0234     m_customCaCerts = QSslCertificate::fromDevice(&f, QSsl::Pem);
0235 }
0236 
0237 void AbstractBackend::setPkcs12(const QString &pkcs12Name)
0238 {
0239     QFile f(QLatin1String(":/org.kde.kpublictransport/network-certificates/") + pkcs12Name);
0240     if (!f.open(QFile::ReadOnly)) {
0241         qCWarning(Log) << f.fileName() << f.errorString();
0242         return;
0243     }
0244     const auto r = QSslCertificate::importPkcs12(&f, &m_privateKey, &m_clientCert, &m_customCaCerts, "");
0245     if (!r) {
0246         qCWarning(Log) << "Failed to load PKCS#12 bundle" << f.fileName();
0247     }
0248 }
0249 
0250 void AbstractBackend::applySslConfiguration(QNetworkRequest &request) const
0251 {
0252     auto sslConfig = request.sslConfiguration();
0253     if (!m_customCaCerts.empty()) {
0254         sslConfig.setCaCertificates(m_customCaCerts);
0255     }
0256     if (!m_clientCert.isNull()) {
0257         sslConfig.setLocalCertificate(m_clientCert);
0258     }
0259     if (!m_privateKey.isNull()) {
0260         sslConfig.setPrivateKey(m_privateKey);
0261     }
0262     request.setSslConfiguration(std::move(sslConfig));
0263 }