From 4e539a5948d6673d61c3044ac5c9b03f4a0009dc Mon Sep 17 00:00:00 2001 From: sidharthify Date: Sun, 10 May 2026 14:51:25 +0530 Subject: [PATCH] RunTaskDialog: validate executable before accepting Running a non-existent program or file through "Run New Task" would silently succeed because the command is passed to `/bin/sh -c`, which always launches successfully regardless of whether the target exists. Add an `accept()` override in RunTaskDialog that validates the executable before the dialog closes. Signed-off-by: sidharthify --- src/runtaskdialog.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++- src/runtaskdialog.h | 5 +++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/runtaskdialog.cpp b/src/runtaskdialog.cpp index 95b937a..6b368cb 100644 --- a/src/runtaskdialog.cpp +++ b/src/runtaskdialog.cpp @@ -24,8 +24,11 @@ #include #include #include +#include #include #include +#include +#include RunTaskDialog::RunTaskDialog(QWidget *parent) : QDialog(parent), ui(new Ui::RunTaskDialog) { @@ -37,11 +40,23 @@ RunTaskDialog::RunTaskDialog(QWidget *parent) : QDialog(parent), ui(new Ui::RunT this->m_okButton = this->ui->buttonBox->button(QDialogButtonBox::Ok); - connect(this->ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + this->m_errorLabel = new QLabel(this); + this->m_errorLabel->setWordWrap(true); + this->m_errorLabel->setVisible(false); + + auto *layout = qobject_cast(this->layout()); + if (layout) + { + const int buttonBoxIndex = layout->indexOf(this->ui->buttonBox); + layout->insertWidget(buttonBoxIndex, this->m_errorLabel); + } + + connect(this->ui->buttonBox, &QDialogButtonBox::accepted, this, &RunTaskDialog::accept); connect(this->ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(this->ui->browseButton, &QPushButton::clicked, this, &RunTaskDialog::browseForCommand); connect(this->ui->commandCombo->lineEdit(), &QLineEdit::textChanged, this, [this]() { + this->m_errorLabel->setVisible(false); this->updateOkButton(); }); @@ -79,6 +94,81 @@ void RunTaskDialog::updateOkButton() this->m_okButton->setEnabled(!this->Command().isEmpty()); } +void RunTaskDialog::accept() +{ + const QString command = this->Command(); + if (command.isEmpty()) + return; + + const QString exe = RunTaskDialog::resolveExecutable(command); + if (exe.isEmpty()) + { + // clamped to avoid blowing up the dialog + QString program = command.split(' ', Qt::SkipEmptyParts).value(0); + constexpr int maxLen = 40; + if (program.size() > maxLen) + program = program.left(maxLen) + QStringLiteral("\u2026"); // '…' + this->setError(tr("Program or file \u201c%1\u201d does not exist or could not be found.").arg(program)); + return; + } + + QDialog::accept(); +} + +void RunTaskDialog::setError(const QString &message) +{ + if (!this->m_errorLabel) + return; + this->m_errorLabel->setText(QStringLiteral("\u26a0 ") + message); + if (!this->m_errorLabel->isVisible()) + { + this->m_errorLabel->setVisible(true); + const QSize hint = this->sizeHint(); + if (hint.height() > this->height()) + this->resize(this->width(), hint.height()); + } +} + +QString RunTaskDialog::resolveExecutable(const QString &command) +{ + if (command.trimmed().isEmpty()) + return {}; + + // Grab the first token + QString token = command.trimmed(); + + // Handle single-quoted token + if (token.startsWith('\'')) + { + const int end = token.indexOf('\'', 1); + token = (end > 0) ? token.mid(1, end - 1) : token.mid(1); + } + // Handle double-quoted token + else if (token.startsWith('"')) + { + const int end = token.indexOf('"', 1); + token = (end > 0) ? token.mid(1, end - 1) : token.mid(1); + } + else + { + // Take everything up to the first whitespace + token = token.split(' ', Qt::SkipEmptyParts).value(0); + } + + if (token.isEmpty()) + return {}; + + // Check if the token contains a path separator + if (token.contains('/') || token.startsWith('.')) + { + const QFileInfo fi(token); + return fi.exists() ? fi.absoluteFilePath() : QString{}; + } + + // Else look it up on PATH + return QStandardPaths::findExecutable(token); +} + QString RunTaskDialog::shellQuote(const QString &text) { QString escaped = text; diff --git a/src/runtaskdialog.h b/src/runtaskdialog.h index 72b130c..f259360 100644 --- a/src/runtaskdialog.h +++ b/src/runtaskdialog.h @@ -24,6 +24,7 @@ QT_BEGIN_NAMESPACE namespace Ui { class RunTaskDialog; } class QPushButton; +class QLabel; QT_END_NAMESPACE class RunTaskDialog : public QDialog @@ -37,12 +38,16 @@ class RunTaskDialog : public QDialog QString Command() const; private: + void accept() override; void browseForCommand(); void updateOkButton(); + void setError(const QString &message); static QString shellQuote(const QString &text); + static QString resolveExecutable(const QString &command); Ui::RunTaskDialog *ui { nullptr }; QPushButton *m_okButton { nullptr }; + QLabel *m_errorLabel { nullptr }; }; #endif // RUNTASKDIALOG_H