refactor: reimplement unescapeExec

adjusted patch from master branch (originally 2f0f34a44fb)

Signed-off-by: ComixHe <heyuming@deepin.org>
This commit is contained in:
ComixHe 2024-12-25 14:21:10 +08:00 committed by Wang Zichong
parent 538cfb48d4
commit 0b0c75849c
2 changed files with 256 additions and 147 deletions

View File

@ -250,8 +250,8 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con
execStr = toString(actionExec.value()); execStr = toString(actionExec.value());
if (execStr.isEmpty()) { if (execStr.isEmpty()) {
qWarning() << "exec value to string failed, try default action."; // we need this log. qWarning() << "exec value to string failed, try default action."; // we need this log.
break;
} }
break; break;
} }
@ -304,46 +304,78 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con
auto &jobManager = parent()->jobManager(); auto &jobManager = parent()->jobManager();
return jobManager.addJob( return jobManager.addJob(
m_applicationPath.path(), m_applicationPath.path(),
[this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &variantValue) -> QVariant { [this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &value) -> QVariant {
auto resourceFile = variantValue.toString(); auto rawResources = value.toString();
auto instanceRandomUUID = QUuid::createUuid().toString(QUuid::Id128); auto instanceRandomUUID = QUuid::createUuid().toString(QUuid::Id128);
auto objectPath = m_applicationPath.path() + "/" + instanceRandomUUID; auto objectPath = m_applicationPath.path() + "/" + instanceRandomUUID;
auto newCommands = commands; auto newCommands = commands;
newCommands.push_front(QString{"--SourcePath=%1"}.arg(m_desktopSource.sourcePath())); newCommands.push_front(QString{"--SourcePath=%1"}.arg(m_desktopSource.sourcePath()));
auto location = newCommands.indexOf(R"(%f)"); if (rawResources.isEmpty()) {
if (location != -1) { // due to std::move, there only remove once
newCommands.remove(location);
}
if (resourceFile.isEmpty()) {
newCommands.push_front(QString{R"(--unitName=app-DDE-%1@%2.service)"}.arg( newCommands.push_front(QString{R"(--unitName=app-DDE-%1@%2.service)"}.arg(
escapeApplicationId(this->id()), instanceRandomUUID)); // launcher should use this instanceId escapeApplicationId(this->id()), instanceRandomUUID)); // launcher should use this instanceId
QProcess process; QProcess process;
qDebug() << "run with commands:" << newCommands; qDebug() << "launcher :" << m_launcher << "run with commands:" << newCommands;
process.start(m_launcher, newCommands); process.start(m_launcher, newCommands);
process.waitForFinished(); process.waitForFinished();
if (auto code = process.exitCode(); code != 0) { if (auto code = process.exitCode(); code != 0) {
qWarning() << "Launch Application Failed"; qWarning() << "Launch Application Failed";
return QDBusError::Failed; return QDBusError::Failed;
} }
return objectPath; return objectPath;
} }
auto url = QUrl::fromUserInput(resourceFile); auto location = newCommands.end();
if (!url.isValid()) { // if url is invalid, passing to launcher directly qsizetype fieldIndex{-1};
auto scheme = url.scheme(); for (auto it = newCommands.begin(); it != newCommands.end(); ++it) {
if (!scheme.isEmpty()) { auto fieldLocation = it->indexOf(R"(%f)");
// TODO: resourceFile = processRemoteFile(resourceFile); 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 if (location == newCommands.end()) {
newCommands.insert(location, resourceFile); 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)); newCommands.push_front(QString{R"(--unitName=DDE-%1@%2.service)"}.arg(this->id(), instanceRandomUUID));
QProcess process; QProcess process;
qDebug() << "run with commands:" << newCommands; qDebug().noquote() << "launcher :" << m_launcher << "run with commands:" << newCommands;
process.start(getApplicationLauncherBinary(), newCommands); process.start(getApplicationLauncherBinary(), newCommands);
process.waitForFinished(); process.waitForFinished();
auto exitCode = process.exitCode(); auto exitCode = process.exitCode();
@ -351,6 +383,7 @@ ApplicationService::Launch(const QString &action, const QStringList &fields, con
qWarning() << "Launch Application Failed"; qWarning() << "Launch Application Failed";
return QDBusError::Failed; return QDBusError::Failed;
} }
return objectPath; return objectPath;
}, },
std::move(res)); std::move(res));
@ -957,139 +990,160 @@ std::optional<QStringList> ApplicationService::unescapeExecArgs(const QString &s
return execList; return execList;
} }
LaunchTask ApplicationService::unescapeExec(const QString &str, const QStringList &fields) noexcept LaunchTask ApplicationService::unescapeExec(const QString &str, QStringList fields) noexcept
{ {
LaunchTask task; LaunchTask task;
auto opt = unescapeExecArgs(str); auto args = unescapeExecArgs(str);
if (!opt.has_value()) { if (!args) {
qWarning() << "unescapeExecArgs failed."; qWarning() << "unescapeExecArgs failed.";
return {}; return {};
} }
auto execList = std::move(opt).value(); if (args->isEmpty()) {
if (execList.isEmpty()) {
qWarning() << "exec format is invalid."; qWarning() << "exec format is invalid.";
return {}; return {};
} }
task.LaunchBin = execList.first(); auto processUrl = [](const QString &str) {
QRegularExpression re{"%[fFuUickdDnNvm]"}; auto url = QUrl::fromUserInput(str);
auto matcher = re.match(str); if (!url.isValid()) {
if (!matcher.hasMatch()) { qDebug() << "url is invalid, pass to exec directly.";
task.command.append(std::move(execList)); return str;
task.Resources.emplace_back(QString{""}); // mapReduce should run once at least
return task;
} }
auto list = matcher.capturedTexts(); if (url.isLocalFile()) {
if (list.count() != 1) { return url.toLocalFile();
qWarning() << "invalid exec format, all filed code will be ignored.";
for (const auto &code : list) {
execList.removeOne(code);
}
task.command.append(std::move(execList));
return task;
} }
auto filesCode = list.first().back().toLatin1(); return url.toString();
auto codeStr = QString(R"(%%1)").arg(filesCode); };
auto location = execList.indexOf(codeStr);
if (location == -1) { task.LaunchBin = args->first();
qWarning() << "invalid exec format, all filed code will be ignored."; 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;
}
const auto *code = it + 1;
if (code == arg.cend()) {
qWarning() << R"(content of exec is invalid, a unterminated % is detected.)";
return {}; return {};
} }
switch (filesCode) { if (*code == percentage) {
case 'f': { // Defer to async job newArg.append(percentage);
task.command.append(std::move(execList)); it += 2;
for (const auto &field : fields) { continue;
task.Resources.emplace_back(field);
} }
} break;
case 'u': { switch (code->toLatin1()) {
execList.removeAt(location); case 'f': { // Defer to async job
if (fields.empty()) { if (exclusiveField) {
task.command.append(execList); qDebug() << R"(exclusive field is detected again, %f will be ignored.)";
break; break;
} }
if (fields.count() > 1) { 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.)"; qDebug() << R"(fields count is greater than one, %u will only take first element.)";
} }
execList.insert(location, fields.first());
task.command.append(execList); newArg.append(processUrl(fields.takeFirst()));
} break; } break;
case 'F': { case 'F': { // Defer to async job
execList.remove(location); if (exclusiveField) {
auto it = execList.begin() + location; qDebug() << R"(exclusive field is detected again, %f will be ignored.)";
for (const auto &field : fields) { break;
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);
} }
++it; exclusiveField = true;
}
task.command.append(std::move(execList)); task.Resources.emplace_back(fields.join(' '));
fields.clear();
newArg.append(R"(%F)");
} break; } break;
case 'U': { case 'U': {
execList.removeAt(location); if (exclusiveField) {
auto it = execList.begin() + location; qDebug() << R"(exclusive field is detected again, %f will be ignored.)";
for (const auto &field : fields) { break;
it = execList.insert(it, field);
++it;
} }
task.command.append(std::move(execList)); 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; } break;
case 'i': { case 'i': {
execList.removeAt(location);
auto val = m_entry->value(DesktopFileEntryKey, "Icon"); auto val = m_entry->value(DesktopFileEntryKey, "Icon");
if (!val) { if (!val) {
qDebug() << R"(Application Icons can't be found. %i will be ignored.)"; qDebug() << R"(Application Icons can't be found. %i will be ignored.)";
task.command.append(std::move(execList)); break;
return task;
} }
auto iconStr = toIconString(val.value()); auto iconStr = toIconString(val.value());
if (iconStr.isEmpty()) { if (iconStr.isEmpty()) {
qDebug() << R"(Icons Convert to string failed. %i will be ignored.)"; qDebug() << R"(Icons Convert to string failed. %i will be ignored.)";
task.command.append(std::move(execList)); break;
return task;
} }
auto it = execList.insert(location, iconStr);
execList.insert(it, "--icon"); // split at the end of loop
task.command.append(std::move(execList)); newArg.append(QString{"--icon %1"}.arg(iconStr));
} break; } break;
case 'c': { case 'c': {
execList.removeAt(location);
auto val = m_entry->value(DesktopFileEntryKey, "Name"); auto val = m_entry->value(DesktopFileEntryKey, "Name");
if (!val) { if (!val) {
qDebug() << R"(Application Name can't be found. %c will be ignored.)"; qDebug() << R"(Application Name can't be found. %c will be ignored.)";
task.command.append(std::move(execList)); break;
return task;
} }
const auto &rawValue = val.value(); const auto &rawValue = val.value();
if (!rawValue.canConvert<QStringMap>()) { if (!rawValue.canConvert<QStringMap>()) {
qDebug() << "Name's underlying type mismatch:" << "QStringMap" << rawValue.metaType().name(); qDebug() << "Name's underlying type mismatch:" << "QStringMap" << rawValue.metaType().name();
task.command.append(std::move(execList)); break;
return task;
} }
auto NameStr = toLocaleString(rawValue.value<QStringMap>(), getUserLocale()); auto NameStr = toLocaleString(rawValue.value<QStringMap>(), getUserLocale());
if (NameStr.isEmpty()) { if (NameStr.isEmpty()) {
qDebug() << R"(Name Convert to locale string failed. %c will be ignored.)"; qDebug() << R"(Name Convert to locale string failed. %c will be ignored.)";
task.command.append(std::move(execList)); break;
return task;
} }
execList.insert(location, NameStr);
task.command.append(std::move(execList)); newArg.append(NameStr);
} break; } break;
case 'k': { // ignore all desktop file location for now. case 'k': { // ignore all desktop file location for now.
execList.removeAt(location); newArg.append(m_desktopSource.sourcePath());
task.command.append(std::move(execList));
} break; } break;
case 'd': case 'd':
case 'D': case 'D':
@ -1098,11 +1152,19 @@ LaunchTask ApplicationService::unescapeExec(const QString &str, const QStringLis
case 'v': case 'v':
[[fallthrough]]; // Deprecated field codes should be removed from the command line and ignored. [[fallthrough]]; // Deprecated field codes should be removed from the command line and ignored.
case 'm': { case 'm': {
execList.removeAt(location); qDebug() << "field code" << *code << "has been deprecated.";
task.command.append(std::move(execList));
} break; } break;
default: { default: {
qDebug() << "unrecognized file code."; qDebug() << "unknown field code:" << *code << ", ignore it.";
}
}
it += 2; // skip filed code
}
auto newArgList = newArg.split(' ', Qt::SkipEmptyParts);
if (!newArgList.isEmpty()) {
task.command.append(std::move(newArgList));
} }
} }
@ -1110,9 +1172,54 @@ LaunchTask ApplicationService::unescapeExec(const QString &str, const QStringLis
task.Resources.emplace_back(QString{""}); // mapReduce should run once at least task.Resources.emplace_back(QString{""}); // mapReduce should run once at least
} }
qInfo() << "after unescape exec:" << task.LaunchBin << task.command << task.Resources;
return task; 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, QVariant ApplicationService::findEntryValue(const QString &group,
const QString &valueKey, const QString &valueKey,
EntryValueType type, EntryValueType type,

View File

@ -134,8 +134,10 @@ public:
EntryValueType type, EntryValueType type,
const QLocale &locale = getUserLocale()) const noexcept; 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<QStringList> unescapeExecArgs(const QString &str) noexcept; [[nodiscard]] static std::optional<QStringList> unescapeExecArgs(const QString &str) noexcept;
void unescapeEens(QVariantMap&) noexcept;
void autoRemoveFromDesktop() const noexcept;
public Q_SLOTS: public Q_SLOTS:
// NOTE: 'realExec' only for internal implementation // NOTE: 'realExec' only for internal implementation