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