File indexing completed on 2024-11-24 04:46:24
0001 /* 0002 SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org> 0003 SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 #include <kitinerary_version.h> 0007 0008 #include <KItinerary/ExtractorEngine> 0009 #include <KItinerary/ExtractorResult> 0010 #include <KItinerary/HttpResponse> 0011 0012 #include <QCommandLineParser> 0013 #include <QCoreApplication> 0014 #include <QDateTime> 0015 #include <QFile> 0016 #include <QJsonDocument> 0017 #include <QJsonObject> 0018 #include <QNetworkAccessManager> 0019 #include <QNetworkReply> 0020 0021 #include <iostream> 0022 0023 using namespace KItinerary; 0024 0025 static void harPostRequest(const QNetworkRequest &req, const QByteArray &postData, QJsonObject &harEntry) 0026 { 0027 QJsonArray headers; 0028 const auto rawHeaders = req.rawHeaderList(); 0029 for (const auto &h : rawHeaders) { 0030 headers.push_back(QJsonObject({ 0031 {QLatin1StringView("name"), QString::fromUtf8(h)}, 0032 {QLatin1StringView("value"), QString::fromUtf8(req.rawHeader(h))}, 0033 })); 0034 } 0035 0036 harEntry.insert( 0037 QLatin1StringView("request"), 0038 QJsonObject({ 0039 {QLatin1StringView("method"), QLatin1String("POST")}, 0040 {QLatin1StringView("url"), req.url().toString()}, 0041 {QLatin1StringView("headers"), headers}, 0042 {QLatin1StringView("postData"), 0043 QJsonObject({ 0044 {QLatin1StringView("text"), QString::fromUtf8(postData)}, 0045 })}, 0046 })); 0047 } 0048 0049 static void harResponse(const HttpResponse &response, QJsonObject &harEntry) 0050 { 0051 harEntry.insert( 0052 QLatin1StringView("response"), 0053 QJsonObject( 0054 {{QLatin1StringView("content"), 0055 QJsonObject({ 0056 {QLatin1StringView("text"), 0057 QString::fromUtf8(response.content().toBase64())}, 0058 {QLatin1StringView("encoding"), QLatin1String("base64")}, 0059 })}})); 0060 } 0061 0062 int main(int argc, char **argv) 0063 { 0064 QCoreApplication::setApplicationName(QStringLiteral("online-ticket-dump")); 0065 QCoreApplication::setApplicationVersion(QStringLiteral(KITINERARY_VERSION_STRING)); 0066 QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); 0067 QCoreApplication::setOrganizationName(QStringLiteral("KDE")); 0068 QCoreApplication app(argc, argv); 0069 0070 QCommandLineParser parser; 0071 parser.setApplicationDescription(QStringLiteral("Dump online ticket data.")); 0072 parser.addHelpOption(); 0073 parser.addVersionOption(); 0074 QCommandLineOption nameOpt(QStringLiteral("name"), QStringLiteral("Passenger last name."), QStringLiteral("name")); 0075 parser.addOption(nameOpt); 0076 QCommandLineOption refOpt(QStringLiteral("ref"), QStringLiteral("Ticket reference number."), QStringLiteral("ref")); 0077 parser.addOption(refOpt); 0078 QCommandLineOption kwidOpt(QStringLiteral("kwid"), QStringLiteral("DB kwid UUID."), QStringLiteral("ref")); 0079 parser.addOption(kwidOpt); 0080 QCommandLineOption sourceOpt(QStringLiteral("source"), QStringLiteral("Ticket provider (db or sncf)."), QStringLiteral("provider")); 0081 parser.addOption(sourceOpt); 0082 QCommandLineOption harOpt(QStringLiteral("har"), QStringLiteral("File to write HTTP communication to."), QStringLiteral("file")); 0083 parser.addOption(harOpt); 0084 parser.process(app); 0085 0086 if (!parser.isSet(nameOpt) || !parser.isSet(refOpt) || !parser.isSet(sourceOpt)) { 0087 parser.showHelp(1); 0088 } 0089 0090 QNetworkAccessManager nam; 0091 nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); 0092 QNetworkReply *reply = nullptr; 0093 0094 QJsonObject harEntry{ 0095 {QLatin1StringView("startDateTime"), 0096 QDateTime::currentDateTime().toString(Qt::ISODateWithMs)}}; 0097 if (parser.value(sourceOpt) == QLatin1StringView("db")) { 0098 QNetworkRequest req( 0099 QUrl(QStringLiteral("https://fahrkarten.bahn.de/mobile/dbc/xs.go?"))); 0100 req.setHeader(QNetworkRequest::ContentTypeHeader, 0101 QByteArray("application/x-www-form-urlencoded")); 0102 QByteArray postData( 0103 "<rqorderdetails version=\"1.0\"><rqheader v=\"23080000\" os=\"KCI\" " 0104 "app=\"KCI-Webservice\"/><rqorder on=\"" + 0105 parser.value(refOpt).toUtf8() + 0106 (parser.isSet(kwidOpt) 0107 ? QByteArray("\" kwid=\"" + parser.value(kwidOpt).toUtf8()) 0108 : QByteArray()) + 0109 "\"/><authname tln=\"" + parser.value(nameOpt).toUtf8() + 0110 "\"/></rqorderdetails>"); 0111 reply = nam.post(req, postData); 0112 harPostRequest(req, postData, harEntry); 0113 } else if (parser.value(sourceOpt) == QLatin1StringView("sncf")) { 0114 // based on https://www.sncf-connect.com/app/trips/search and stripped to 0115 // the bare minimum that works 0116 QNetworkRequest req(QUrl(QStringLiteral( 0117 "https://www.sncf-connect.com/bff/api/v1/trips/trips-by-criteria"))); 0118 req.setHeader(QNetworkRequest::ContentTypeHeader, 0119 QByteArray("application/json")); 0120 req.setHeader(QNetworkRequest::UserAgentHeader, 0121 QByteArray("Mozilla/5.0 (X11; Linux x86_64; rv:109.0) " 0122 "Gecko/20100101 Firefox/111.0")); 0123 req.setRawHeader("Accept", "application/json, text/plain, */*"); 0124 req.setRawHeader("x-bff-key", "ah1MPO-izehIHD-QZZ9y88n-kku876"); 0125 QByteArray postData("{\"reference\":\"" + parser.value(refOpt).toUtf8() + 0126 "\",\"name\":\"" + parser.value(nameOpt).toUtf8() + 0127 "\"}"); 0128 reply = nam.post(req, postData); 0129 harPostRequest(req, postData, harEntry); 0130 } 0131 0132 if (!reply) { 0133 parser.showHelp(1); 0134 } 0135 0136 QObject::connect(reply, &QNetworkReply::finished, &app, [&app, &parser, &harOpt, &harEntry, reply]() { 0137 reply->deleteLater(); 0138 const auto response = HttpResponse::fromNetworkReply(reply); 0139 ExtractorEngine engine; 0140 engine.setContent(response, u"internal/http-response"); 0141 qDebug().noquote() << QJsonDocument(engine.extract()).toJson(); 0142 0143 if (const auto harPath = parser.value(harOpt); !harPath.isEmpty()) { 0144 QFile f(harPath); 0145 if (!f.open(QFile::WriteOnly)) { 0146 qWarning() << f.errorString(); 0147 } 0148 0149 harResponse(response, harEntry); 0150 QJsonObject har({ 0151 {QLatin1StringView("log"), 0152 QJsonObject( 0153 {{QLatin1StringView("entries"), QJsonArray({harEntry})}})}, 0154 }); 0155 f.write(QJsonDocument(har).toJson()); 0156 } 0157 0158 app.quit(); 0159 }); 0160 0161 return app.exec(); 0162 }