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 }