From 0b0c75849c42b2efc93ab1f25bb6699203d3e886 Mon Sep 17 00:00:00 2001 From: ComixHe Date: Wed, 25 Dec 2024 14:21:10 +0800 Subject: [PATCH] refactor: reimplement unescapeExec adjusted patch from master branch (originally 2f0f34a44fb) Signed-off-by: ComixHe --- src/dbus/applicationservice.cpp | 399 ++++++++++++++++++++------------ src/dbus/applicationservice.h | 4 +- 2 files changed, 256 insertions(+), 147 deletions(-) diff --git a/src/dbus/applicationservice.cpp b/src/dbus/applicationservice.cpp index f016ebf..1dd40b0 100644 --- a/src/dbus/applicationservice.cpp +++ b/src/dbus/applicationservice.cpp @@ -250,8 +250,8 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con execStr = toString(actionExec.value()); if (execStr.isEmpty()) { qWarning() << "exec value to string failed, try default action."; // we need this log. - break; } + break; } @@ -304,46 +304,78 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con auto &jobManager = parent()->jobManager(); return jobManager.addJob( m_applicationPath.path(), - [this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &variantValue) -> QVariant { - auto resourceFile = variantValue.toString(); + [this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &value) -> QVariant { + auto rawResources = value.toString(); auto instanceRandomUUID = QUuid::createUuid().toString(QUuid::Id128); auto objectPath = m_applicationPath.path() + "/" + instanceRandomUUID; auto newCommands = commands; newCommands.push_front(QString{"--SourcePath=%1"}.arg(m_desktopSource.sourcePath())); - auto location = newCommands.indexOf(R"(%f)"); - if (location != -1) { // due to std::move, there only remove once - newCommands.remove(location); - } - - if (resourceFile.isEmpty()) { + if (rawResources.isEmpty()) { newCommands.push_front(QString{R"(--unitName=app-DDE-%1@%2.service)"}.arg( escapeApplicationId(this->id()), instanceRandomUUID)); // launcher should use this instanceId + QProcess process; - qDebug() << "run with commands:" << newCommands; + qDebug() << "launcher :" << m_launcher << "run with commands:" << newCommands; process.start(m_launcher, newCommands); process.waitForFinished(); if (auto code = process.exitCode(); code != 0) { qWarning() << "Launch Application Failed"; return QDBusError::Failed; } + return objectPath; } - auto url = QUrl::fromUserInput(resourceFile); - if (!url.isValid()) { // if url is invalid, passing to launcher directly - auto scheme = url.scheme(); - if (!scheme.isEmpty()) { - // TODO: resourceFile = processRemoteFile(resourceFile); + auto location = newCommands.end(); + qsizetype fieldIndex{-1}; + for (auto it = newCommands.begin(); it != newCommands.end(); ++it) { + auto fieldLocation = it->indexOf(R"(%f)"); + if (fieldLocation != -1) { + fieldIndex = fieldLocation; + location = it; + break; + } + + fieldLocation = it->indexOf(R"(%F)"); + if (fieldLocation != -1) { + fieldIndex = fieldLocation; + location = it; + break; } } - // NOTE: resourceFile must be available in the following contexts - newCommands.insert(location, resourceFile); + if (location == newCommands.end()) { + qCritical() << R"(internal logic error, can't find %f or %F in exec command, abort.)"; + return QDBusError::Failed; + } + const auto &rawResource = rawResources.split(' ', Qt::SkipEmptyParts); + QStringList resources; + std::transform(rawResource.cbegin(), rawResource.cend(), std::back_inserter(resources), [](const QString &res) { + auto url = QUrl::fromUserInput(res); + if (url.isValid()) { + if (url.isLocalFile()) { + return url.toLocalFile(); + } + // for now, we only support local file, maybe we will support remote file in the future. + // TODO: return processRemoteFile(url); + } // if url is invalid, passing to launcher directly + + return res; + }); + auto tmpRes = resources.join(' '); + + location->replace(fieldIndex, tmpRes.size(), tmpRes); + auto newCmd = location->split(' ', Qt::SkipEmptyParts); + location = newCommands.erase(location); + for (auto &c : newCmd) { + location = newCommands.insert(location, std::move(c)); + } newCommands.push_front(QString{R"(--unitName=DDE-%1@%2.service)"}.arg(this->id(), instanceRandomUUID)); + QProcess process; - qDebug() << "run with commands:" << newCommands; + qDebug().noquote() << "launcher :" << m_launcher << "run with commands:" << newCommands; process.start(getApplicationLauncherBinary(), newCommands); process.waitForFinished(); auto exitCode = process.exitCode(); @@ -351,6 +383,7 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con qWarning() << "Launch Application Failed"; return QDBusError::Failed; } + return objectPath; }, std::move(res)); @@ -957,162 +990,236 @@ std::optional ApplicationService::unescapeExecArgs(const QString &s return execList; } -LaunchTask ApplicationService::unescapeExec(const QString &str, const QStringList &fields) noexcept +LaunchTask ApplicationService::unescapeExec(const QString &str, QStringList fields) noexcept { LaunchTask task; - auto opt = unescapeExecArgs(str); + auto args = unescapeExecArgs(str); - if (!opt.has_value()) { + if (!args) { qWarning() << "unescapeExecArgs failed."; return {}; } - auto execList = std::move(opt).value(); - if (execList.isEmpty()) { + if (args->isEmpty()) { qWarning() << "exec format is invalid."; return {}; } - task.LaunchBin = execList.first(); - QRegularExpression re{"%[fFuUickdDnNvm]"}; - auto matcher = re.match(str); - if (!matcher.hasMatch()) { - task.command.append(std::move(execList)); - task.Resources.emplace_back(QString{""}); // mapReduce should run once at least - return task; - } + auto processUrl = [](const QString &str) { + auto url = QUrl::fromUserInput(str); + if (!url.isValid()) { + qDebug() << "url is invalid, pass to exec directly."; + return str; + } - auto list = matcher.capturedTexts(); - if (list.count() != 1) { - qWarning() << "invalid exec format, all filed code will be ignored."; - for (const auto &code : list) { - execList.removeOne(code); + if (url.isLocalFile()) { + return url.toLocalFile(); } - task.command.append(std::move(execList)); - return task; - } - auto filesCode = list.first().back().toLatin1(); - auto codeStr = QString(R"(%%1)").arg(filesCode); - auto location = execList.indexOf(codeStr); - if (location == -1) { - qWarning() << "invalid exec format, all filed code will be ignored."; - return {}; - } + return url.toString(); + }; - switch (filesCode) { - case 'f': { // Defer to async job - task.command.append(std::move(execList)); - for (const auto &field : fields) { - task.Resources.emplace_back(field); - } - } break; - case 'u': { - execList.removeAt(location); - if (fields.empty()) { - task.command.append(execList); - break; - } - if (fields.count() > 1) { - qDebug() << R"(fields count is greater than one, %u will only take first element.)"; - } - execList.insert(location, fields.first()); - task.command.append(execList); - } break; - case 'F': { - execList.remove(location); - auto it = execList.begin() + location; - for (const auto &field : fields) { - auto tmp = QUrl::fromUserInput(field); - if (auto scheme = tmp.scheme(); scheme.startsWith("file") or scheme.isEmpty()) { - it = execList.insert(it, tmp.toLocalFile()); - } else { - qWarning() << "shouldn't replace %F with an URL:" << field; - it = execList.insert(it, field); + task.LaunchBin = args->first(); + const QChar percentage{'%'}; + bool exclusiveField{false}; + + for (const auto &arg : *args) { + QString newArg; + + for (const auto *it = arg.cbegin(); it != arg.cend();) { + if (*it != percentage) { + newArg.append(*(it++)); + continue; } - ++it; - } - task.command.append(std::move(execList)); - } break; - case 'U': { - execList.removeAt(location); - auto it = execList.begin() + location; - for (const auto &field : fields) { - it = execList.insert(it, field); - ++it; - } - task.command.append(std::move(execList)); - } break; - case 'i': { - execList.removeAt(location); - auto val = m_entry->value(DesktopFileEntryKey, "Icon"); - if (!val) { - qDebug() << R"(Application Icons can't be found. %i will be ignored.)"; - task.command.append(std::move(execList)); - return task; + + const auto *code = it + 1; + if (code == arg.cend()) { + qWarning() << R"(content of exec is invalid, a unterminated % is detected.)"; + return {}; + } + + if (*code == percentage) { + newArg.append(percentage); + it += 2; + continue; + } + + switch (code->toLatin1()) { + case 'f': { // Defer to async job + if (exclusiveField) { + qDebug() << R"(exclusive field is detected again, %f will be ignored.)"; + break; + } + exclusiveField = true; + + if (fields.empty()) { + qDebug() << R"(fields is empty, %f will be ignored.)"; + break; + } + + if (fields.size() > 1) { + qDebug() << R"(fields count is greater than one, %f will only take first element.)"; + } + + task.Resources.emplace_back(fields.takeFirst()); + newArg.append(R"(%f)"); + } break; + case 'u': { + if (exclusiveField) { + qDebug() << R"(exclusive field is detected again, %f will be ignored.)"; + break; + } + exclusiveField = true; + + if (fields.empty()) { + qDebug() << "fields is empty, %u will be ignored."; + break; + } + + if (fields.size() > 1) { + qDebug() << R"(fields count is greater than one, %u will only take first element.)"; + } + + newArg.append(processUrl(fields.takeFirst())); + } break; + case 'F': { // Defer to async job + if (exclusiveField) { + qDebug() << R"(exclusive field is detected again, %f will be ignored.)"; + break; + } + exclusiveField = true; + + task.Resources.emplace_back(fields.join(' ')); + fields.clear(); + newArg.append(R"(%F)"); + } break; + case 'U': { + if (exclusiveField) { + qDebug() << R"(exclusive field is detected again, %f will be ignored.)"; + break; + } + exclusiveField = true; + + QStringList urls; + std::transform(fields.cbegin(), fields.cend(), std::back_inserter(urls), processUrl); + fields.clear(); + newArg.append(urls.join(' ')); // split at the end of loop + } break; + case 'i': { + auto val = m_entry->value(DesktopFileEntryKey, "Icon"); + if (!val) { + qDebug() << R"(Application Icons can't be found. %i will be ignored.)"; + break; + } + + auto iconStr = toIconString(val.value()); + if (iconStr.isEmpty()) { + qDebug() << R"(Icons Convert to string failed. %i will be ignored.)"; + break; + } + + // split at the end of loop + newArg.append(QString{"--icon %1"}.arg(iconStr)); + } break; + case 'c': { + auto val = m_entry->value(DesktopFileEntryKey, "Name"); + if (!val) { + qDebug() << R"(Application Name can't be found. %c will be ignored.)"; + break; + } + + const auto &rawValue = val.value(); + if (!rawValue.canConvert()) { + qDebug() << "Name's underlying type mismatch:" << "QStringMap" << rawValue.metaType().name(); + break; + } + + auto NameStr = toLocaleString(rawValue.value(), getUserLocale()); + if (NameStr.isEmpty()) { + qDebug() << R"(Name Convert to locale string failed. %c will be ignored.)"; + break; + } + + newArg.append(NameStr); + } break; + case 'k': { // ignore all desktop file location for now. + newArg.append(m_desktopSource.sourcePath()); + } break; + case 'd': + case 'D': + case 'n': + case 'N': + case 'v': + [[fallthrough]]; // Deprecated field codes should be removed from the command line and ignored. + case 'm': { + qDebug() << "field code" << *code << "has been deprecated."; + } break; + default: { + qDebug() << "unknown field code:" << *code << ", ignore it."; + } + } + + it += 2; // skip filed code } - auto iconStr = toIconString(val.value()); - if (iconStr.isEmpty()) { - qDebug() << R"(Icons Convert to string failed. %i will be ignored.)"; - task.command.append(std::move(execList)); - return task; + auto newArgList = newArg.split(' ', Qt::SkipEmptyParts); + if (!newArgList.isEmpty()) { + task.command.append(std::move(newArgList)); } - auto it = execList.insert(location, iconStr); - execList.insert(it, "--icon"); - task.command.append(std::move(execList)); - } break; - case 'c': { - execList.removeAt(location); - auto val = m_entry->value(DesktopFileEntryKey, "Name"); - if (!val) { - qDebug() << R"(Application Name can't be found. %c will be ignored.)"; - task.command.append(std::move(execList)); - return task; - } - - const auto &rawValue = val.value(); - if (!rawValue.canConvert()) { - qDebug() << "Name's underlying type mismatch:" << "QStringMap" << rawValue.metaType().name(); - task.command.append(std::move(execList)); - return task; - } - - auto NameStr = toLocaleString(rawValue.value(), getUserLocale()); - if (NameStr.isEmpty()) { - qDebug() << R"(Name Convert to locale string failed. %c will be ignored.)"; - task.command.append(std::move(execList)); - return task; - } - execList.insert(location, NameStr); - task.command.append(std::move(execList)); - } break; - case 'k': { // ignore all desktop file location for now. - execList.removeAt(location); - task.command.append(std::move(execList)); - } break; - case 'd': - case 'D': - case 'n': - case 'N': - case 'v': - [[fallthrough]]; // Deprecated field codes should be removed from the command line and ignored. - case 'm': { - execList.removeAt(location); - task.command.append(std::move(execList)); - } break; - default: { - qDebug() << "unrecognized file code."; - } } if (task.Resources.isEmpty()) { task.Resources.emplace_back(QString{""}); // mapReduce should run once at least } + qInfo() << "after unescape exec:" << task.LaunchBin << task.command << task.Resources; return task; } +void ApplicationService::unescapeEens(QVariantMap &options) noexcept +{ + if (options.find("env") == options.end()) { + return; + } + QStringList result; + auto envs = options["env"]; + for (const QString &var : envs.toStringList()) { + wordexp_t p; + if (wordexp(var.toStdString().c_str(), &p, 0) == 0) { + for (size_t i = 0; i < p.we_wordc; i++) { + result << QString::fromLocal8Bit(p.we_wordv[i]); // 将结果转换为QString + } + wordfree(&p); + } else { + return; + } + } + + options.insert("env", result); +} + +void ApplicationService::autoRemoveFromDesktop() const noexcept +{ + auto dir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + if (dir.isEmpty()) { + return; + } + + QFileInfo desktopFile{QDir{dir}.filePath(m_desktopSource.desktopId() + ".desktop")}; + + if (!desktopFile.isSymbolicLink()) { + qDebug() << desktopFile.filePath() << " is not symbolicLink"; + return; + } + + QFile file{desktopFile.filePath()}; + auto success = file.remove(); + if (!success) { + qWarning() << "remove desktop file failed:" << file.errorString(); + return; + } +} + QVariant ApplicationService::findEntryValue(const QString &group, const QString &valueKey, EntryValueType type, diff --git a/src/dbus/applicationservice.h b/src/dbus/applicationservice.h index df7299d..17d5d4b 100644 --- a/src/dbus/applicationservice.h +++ b/src/dbus/applicationservice.h @@ -134,8 +134,10 @@ public: EntryValueType type, const QLocale &locale = getUserLocale()) const noexcept; - [[nodiscard]] LaunchTask unescapeExec(const QString &str, const QStringList &fields) noexcept; + [[nodiscard]] LaunchTask unescapeExec(const QString &str, QStringList fields) noexcept; [[nodiscard]] static std::optional unescapeExecArgs(const QString &str) noexcept; + void unescapeEens(QVariantMap&) noexcept; + void autoRemoveFromDesktop() const noexcept; public Q_SLOTS: // NOTE: 'realExec' only for internal implementation