File indexing completed on 2024-05-12 05:51:06

0001 /*
0002     SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "dapclient_debug.h"
0007 
0008 #include <QJsonArray>
0009 #include <QLocale>
0010 #include <QProcess>
0011 #include <QString>
0012 #include <random>
0013 
0014 #include "settings.h"
0015 
0016 namespace dap
0017 {
0018 #include <json_utils.h>
0019 }
0020 
0021 namespace dap
0022 {
0023 namespace settings
0024 {
0025 static const QString RUN = QStringLiteral("run");
0026 static const QString CONFIGURATIONS = QStringLiteral("configurations");
0027 static const QString REQUEST = QStringLiteral("request");
0028 static const QString COMMAND = QStringLiteral("command");
0029 static const QString COMMAND_ARGS = QStringLiteral("commandArgs");
0030 static const QString PORT = QStringLiteral("port");
0031 static const QString HOST = QStringLiteral("host");
0032 static const QString REDIRECT_STDERR = QStringLiteral("redirectStderr");
0033 static const QString REDIRECT_STDOUT = QStringLiteral("redirectStdout");
0034 
0035 std::random_device rd;
0036 std::default_random_engine rng(rd());
0037 std::uniform_int_distribution<> randomPort(40000, 65535);
0038 
0039 bool checkSection(const QJsonObject &data, const QString &key)
0040 {
0041     if (!data.contains(key)) {
0042         qCWarning(DAPCLIENT) << "required section '" << key << "' not found";
0043         return false;
0044     }
0045     if (!data[key].isObject()) {
0046         qCWarning(DAPCLIENT) << "section '" << key << "' is not an object";
0047         return false;
0048     }
0049     return true;
0050 }
0051 
0052 bool checkArray(const QJsonObject &data, const QString &key)
0053 {
0054     return data.contains(key) && data[key].isArray();
0055 }
0056 
0057 std::optional<QJsonObject> expandConfiguration(const QJsonObject &adapterSettings, const QJsonObject &configuration, bool resolvePort)
0058 {
0059     auto out = json::merge(adapterSettings[RUN].toObject(), configuration);
0060 
0061     // check request
0062     if (!checkSection(out, REQUEST)) {
0063         return std::nullopt;
0064     }
0065 
0066     const bool withProcess = checkArray(out, COMMAND);
0067     const bool withSocket = out.contains(PORT) && out[PORT].isDouble();
0068 
0069     if (!withProcess && !withSocket) {
0070         qCWarning(DAPCLIENT) << "'run' requires 'command: string[]' or 'port: number'";
0071         return std::nullopt;
0072     }
0073 
0074     // check command
0075     if (withProcess && checkArray(out, COMMAND_ARGS)) {
0076         auto command = out[COMMAND].toArray();
0077         for (const auto &item : out[COMMAND_ARGS].toArray()) {
0078             command << item;
0079         }
0080         out[COMMAND] = command;
0081         out.remove(COMMAND_ARGS);
0082     }
0083 
0084     // check port
0085     if (withSocket) {
0086         int port = out[PORT].toInt(-1);
0087         if ((port == 0) && resolvePort) {
0088             port = randomPort(rng);
0089             out[PORT] = port;
0090         }
0091         if (port < 0) {
0092             qCWarning(DAPCLIENT) << "'port' must be a positive integer or 0";
0093             return std::nullopt;
0094         }
0095     }
0096 
0097     return out;
0098 }
0099 
0100 std::optional<QJsonObject> expandConfigurations(const QJsonObject &adapterSettings, bool resolvePort)
0101 {
0102     if (!checkSection(adapterSettings, RUN)) {
0103         return std::nullopt;
0104     }
0105     if (!checkSection(adapterSettings, CONFIGURATIONS)) {
0106         return std::nullopt;
0107     }
0108 
0109     const auto &confs = adapterSettings[CONFIGURATIONS].toObject();
0110 
0111     QJsonObject out;
0112 
0113     for (auto it = confs.constBegin(); it != confs.constEnd(); ++it) {
0114         const auto profile = expandConfiguration(adapterSettings, it.value().toObject(), resolvePort);
0115         if (profile) {
0116             out[it.key()] = *profile;
0117         }
0118     }
0119 
0120     return out;
0121 }
0122 
0123 std::optional<QJsonObject> findConfiguration(const QJsonObject &adapterSettings, const QString &configurationKey, bool resolvePort)
0124 {
0125     if (!checkSection(adapterSettings, RUN)) {
0126         return std::nullopt;
0127     }
0128     if (!checkSection(adapterSettings, CONFIGURATIONS)) {
0129         return std::nullopt;
0130     }
0131 
0132     const auto &confs = adapterSettings[CONFIGURATIONS].toObject();
0133 
0134     if (!checkSection(confs, configurationKey)) {
0135         return std::nullopt;
0136     }
0137 
0138     return expandConfiguration(adapterSettings, confs[configurationKey].toObject(), resolvePort);
0139 }
0140 
0141 QHash<QString, QJsonValue> findReferences(const QJsonObject &configuration)
0142 {
0143     QHash<QString, QJsonValue> variables;
0144 
0145     if (configuration.contains(PORT)) {
0146         variables[QStringLiteral("#run.port")] = QString::number(configuration[PORT].toInt(-1));
0147     }
0148     if (configuration.contains(HOST)) {
0149         variables[QStringLiteral("#run.host")] = configuration[HOST].toString();
0150     }
0151 
0152     return variables;
0153 }
0154 
0155 std::optional<QJsonObject> resolveClientPort(const QJsonObject &configuration)
0156 {
0157     int port = configuration[PORT].toInt(-1);
0158 
0159     if (port == 0) {
0160         QJsonObject out(configuration);
0161         out[PORT] = randomPort(rng);
0162         return out;
0163     }
0164     return std::nullopt;
0165 }
0166 
0167 std::optional<QStringList> toStringList(const QJsonObject &configuration, const QString &key)
0168 {
0169     const auto &field = configuration[key];
0170     if (field.isNull() || field.isUndefined() || !field.isArray()) {
0171         return std::nullopt;
0172     }
0173     const auto &array = field.toArray();
0174 
0175     QStringList parts;
0176 
0177     for (const auto &value : array) {
0178         if (!value.isString()) {
0179             return std::nullopt;
0180         }
0181         parts << value.toString();
0182     }
0183 
0184     return parts;
0185 }
0186 
0187 std::optional<QHash<QString, QString>> toStringHash(const QJsonObject &configuration, const QString &key)
0188 {
0189     const auto &field = configuration[key];
0190     if (field.isNull() || field.isUndefined() || !field.isObject()) {
0191         return std::nullopt;
0192     }
0193     const auto &object = field.toObject();
0194     if (object.isEmpty()) {
0195         return QHash<QString, QString>();
0196     }
0197 
0198     QHash<QString, QString> map;
0199 
0200     for (auto it = object.begin(); it != object.end(); ++it) {
0201         if (!it.value().isString()) {
0202             return std::nullopt;
0203         }
0204         map[it.key()] = it.value().toString();
0205     }
0206 
0207     return map;
0208 }
0209 
0210 /*
0211  * Command
0212  */
0213 bool Command::isValid() const
0214 {
0215     return !command.isEmpty();
0216 }
0217 
0218 void Command::start(QProcess &process) const
0219 {
0220     if (environment) {
0221         QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0222         for (auto it = environment->begin(); it != environment->end(); ++it) {
0223             env.insert(it.key(), it.value());
0224         }
0225         process.setProcessEnvironment(env);
0226     }
0227     //    qDebug() << process.environment();
0228     //    qDebug() << process.program();
0229     process.start(command, arguments);
0230 }
0231 
0232 Command::Command(const QJsonObject &configuration)
0233     : environment(toStringHash(configuration, QStringLiteral("environment")))
0234 {
0235     auto cmdParts = toStringList(configuration, COMMAND);
0236 
0237     if (cmdParts && !cmdParts->isEmpty()) {
0238         command = cmdParts->at(0);
0239         cmdParts->removeFirst();
0240         if (!cmdParts->isEmpty()) {
0241             arguments = *cmdParts;
0242         }
0243     }
0244 }
0245 
0246 /*
0247  * Connection
0248  */
0249 bool Connection::isValid() const
0250 {
0251     return (port > 0) && !host.isEmpty();
0252 }
0253 
0254 Connection::Connection()
0255     : port(-1)
0256     , host(QStringLiteral("127.0.0.1"))
0257 {
0258 }
0259 
0260 Connection::Connection(const QJsonObject &configuration)
0261     : port(configuration[PORT].toInt(-1))
0262     , host(QStringLiteral("127.0.0.1"))
0263 {
0264 }
0265 
0266 /*
0267  * BusSettings
0268  */
0269 bool BusSettings::isValid() const
0270 {
0271     return hasCommand() || hasConnection();
0272 }
0273 
0274 bool BusSettings::hasCommand() const
0275 {
0276     return command && command->isValid();
0277 }
0278 
0279 bool BusSettings::hasConnection() const
0280 {
0281     return connection && connection->isValid();
0282 }
0283 
0284 BusSettings::BusSettings(const QJsonObject &configuration)
0285     : command(Command(configuration))
0286     , connection(Connection(configuration))
0287 {
0288 }
0289 
0290 /*
0291  * ProtocolSettings
0292  */
0293 ProtocolSettings::ProtocolSettings()
0294     : linesStartAt1(true)
0295     , columnsStartAt1(true)
0296     , pathFormatURI(false)
0297     , redirectStderr(false)
0298     , redirectStdout(false)
0299     , supportsSourceRequest(true)
0300     , locale(QLocale::system().name())
0301 {
0302 }
0303 
0304 ProtocolSettings::ProtocolSettings(const QJsonObject &configuration)
0305     : linesStartAt1(true)
0306     , columnsStartAt1(true)
0307     , pathFormatURI(false)
0308     , redirectStderr(configuration[REDIRECT_STDERR].toBool(false))
0309     , redirectStdout(configuration[REDIRECT_STDOUT].toBool(false))
0310     , supportsSourceRequest(configuration[QStringLiteral("supportsSourceRequest")].toBool(true))
0311     , launchRequest(configuration[REQUEST].toObject())
0312     , locale(QLocale::system().name())
0313 {
0314 }
0315 
0316 /*
0317  * ClientSettings
0318  */
0319 ClientSettings::ClientSettings(const QJsonObject &configuration)
0320     : busSettings(configuration)
0321     , protocolSettings(configuration)
0322 {
0323 }
0324 
0325 std::optional<ClientSettings> ClientSettings::extractFromAdapter(const QJsonObject &adapterSettings, const QString &configurationKey)
0326 {
0327     const auto configuration = findConfiguration(adapterSettings, configurationKey);
0328     if (!configuration)
0329         return std::nullopt;
0330 
0331     return ClientSettings(*configuration);
0332 }
0333 
0334 } // settings
0335 } // dap