diff --git a/src/widgets/krun.h b/src/widgets/krun.h
--- a/src/widgets/krun.h
+++ b/src/widgets/krun.h
@@ -506,12 +506,6 @@
*/
virtual void slotStatResult(KJob *);
- /**
- * This slot is called when 'chmod' job has finished.
- * It is only used when user asks to make a file executable for him.
- */
- void slotChmodFinished(KJob *);
-
protected:
/**
* All following protected methods are used by subclasses of KRun!
diff --git a/src/widgets/krun.cpp b/src/widgets/krun.cpp
--- a/src/widgets/krun.cpp
+++ b/src/widgets/krun.cpp
@@ -167,6 +167,136 @@
}
}
+// Simple QDialog that resizes the given text edit after being shown to more
+// or less fit the enclosed text.
+class SecureMessageDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr)
+ {
+ }
+
+ void setTextEdit(QPlainTextEdit *textEdit)
+ {
+ m_textEdit = textEdit;
+ }
+
+protected:
+ void showEvent(QShowEvent *e) override
+ {
+ // Now that we're shown, use our width to calculate a good
+ // bounding box for the text, and resize m_textEdit appropriately.
+ QDialog::showEvent(e);
+
+ if (!m_textEdit) {
+ return;
+ }
+
+ QSize fudge(20, 24); // About what it sounds like :-/
+
+ // Form rect with a lot of height for bounding. Use no more than
+ // 5 lines.
+ QRect curRect(m_textEdit->rect());
+ QFontMetrics metrics(fontMetrics());
+ curRect.setHeight(5 * metrics.lineSpacing());
+ curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok?
+
+ QString text(m_textEdit->toPlainText());
+ curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text);
+
+ // Scroll bars interfere. If we don't think there's enough room, enable
+ // the vertical scrollbar however.
+ m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ if (curRect.height() < m_textEdit->height()) { // then we've got room
+ m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_textEdit->setMaximumHeight(curRect.height() + fudge.height());
+ }
+
+ m_textEdit->setMinimumSize(curRect.size() + fudge);
+ m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
+ updateGeometry();
+ }
+
+private:
+ QPlainTextEdit *m_textEdit;
+};
+
+// Shows confirmation dialog whether an untrusted program should be run
+// or not, since it may be potentially dangerous.
+static int showUntrustedProgramWarning(const QString &programName, QWidget *window)
+{
+ SecureMessageDialog *baseDialog = new SecureMessageDialog(window);
+ baseDialog->setWindowTitle(i18nc("Warning about executing unknown program", "Warning"));
+
+ QVBoxLayout *topLayout = new QVBoxLayout;
+ baseDialog->setLayout(topLayout);
+
+ // Dialog will have explanatory text with a disabled lineedit with the
+ // Exec= to make it visually distinct.
+ QWidget *baseWidget = new QWidget(baseDialog);
+ QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget);
+
+ QLabel *iconLabel = new QLabel(baseWidget);
+ QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge));
+ mainLayout->addWidget(iconLabel);
+ iconLabel->setPixmap(warningIcon);
+
+ QVBoxLayout *contentLayout = new QVBoxLayout;
+ QString warningMessage = i18nc("program name follows in a line edit below",
+ "This will start the program:");
+
+ QLabel *message = new QLabel(warningMessage, baseWidget);
+ contentLayout->addWidget(message);
+
+ QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
+ textEdit->setPlainText(programName);
+ textEdit->setReadOnly(true);
+ contentLayout->addWidget(textEdit);
+
+ QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel"));
+ contentLayout->addWidget(footerLabel);
+ contentLayout->addStretch(0); // Don't allow the text edit to expand
+
+ mainLayout->addLayout(contentLayout);
+
+ topLayout->addWidget(baseWidget);
+ baseDialog->setTextEdit(textEdit);
+
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(baseDialog);
+ buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::cont());
+ buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true);
+ buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
+ QObject::connect(buttonBox, &QDialogButtonBox::accepted, baseDialog, &QDialog::accept);
+ QObject::connect(buttonBox, &QDialogButtonBox::rejected, baseDialog, &QDialog::reject);
+ topLayout->addWidget(buttonBox);
+
+ // Constrain maximum size. Minimum size set in
+ // the dialog's show event.
+ QSize screenSize = QApplication::desktop()->screen()->size();
+ baseDialog->resize(screenSize.width() / 4, 50);
+ baseDialog->setMaximumHeight(screenSize.height() / 3);
+ baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
+
+ return baseDialog->exec();
+}
+
+// Helper function that attempts to set execute bit for given file.
+static bool setExecuteBit(const QString &fileName)
+{
+ QFile desktopFile(fileName);
+
+ // corresponds to owner on unix, which will have to do since if the user
+ // isn't the owner we can't change perms anyways.
+ if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) {
+ qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << desktopFile.errorString();
+ return false;
+ }
+
+ return true;
+}
+
#ifndef KIOWIDGETS_NO_DEPRECATED
bool KRun::runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile, bool runExecutables, const QString &suggestedFileName, const QByteArray &asn)
{
@@ -182,6 +312,7 @@
// This is called by foundMimeType, since it knows the mimetype of the URL
bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn)
{
+ const QMimeDatabase db;
const bool runExecutables = flags.testFlag(KRun::RunExecutables);
const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles);
bool noRun = false;
@@ -194,26 +325,52 @@
if (u.isLocalFile() && runExecutables) {
return KDesktopFileActions::runWithStartup(u, true, asn);
}
- } else if (isExecutableFile(u, _mimetype)) {
+ } else if (isExecutable(_mimetype)) {
+ // Check whether file is executable script
+ const QMimeType mime = db.mimeTypeForName(_mimetype);
+ bool isTextFile = mime.inherits(QStringLiteral("text/plain"));
+ // Only run local files
if (u.isLocalFile() && runExecutables) {
if (KAuthorized::authorize(QStringLiteral("shell_access"))) {
- return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(),
- window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command
- // ## TODO implement deleting the file if tempFile==true
+
+ bool canRun = true;
+ bool isFileExecutable = isExecutableFile(u, _mimetype);
+
+ // For executables that aren't scripts and without execute bit,
+ // show prompt asking user if he wants to run the program.
+ if (!isFileExecutable && !isTextFile) {
+ canRun = false;
+ int result = showUntrustedProgramWarning(u.fileName(), window);
+ if (result == QDialog::Accepted) {
+ if (!setExecuteBit(u.toLocalFile())) {
+ KMessageBox::sorry(
+ window,
+ i18n("Unable to make file %1 executable!", u.toLocalFile())
+ );
+ } else {
+ canRun = true;
+ }
+ }
+ } else if (!isFileExecutable && isTextFile) {
+ // Don't try to run scripts without execute bit, instead
+ // open them with default application
+ canRun = false;
+ }
+
+ if (canRun) {
+ return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(),
+ window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command
+ // ## TODO implement deleting the file if tempFile==true
+ }
+
} else {
+ // Show no permission warning
noAuth = true;
}
- } else if (_mimetype == QLatin1String("application/x-executable")) {
- noRun = true;
- }
- } else if (isExecutable(_mimetype)) {
- if (!runExecutables) {
+ } else if (!isTextFile) {
+ // Show warning for executables that aren't scripts
noRun = true;
}
-
- if (!KAuthorized::authorize(QStringLiteral("shell_access"))) {
- noAuth = true;
- }
}
if (noRun) {
@@ -515,65 +672,10 @@
return urls;
}
-// Simple QDialog that resizes the given text edit after being shown to more
-// or less fit the enclosed text.
-class SecureMessageDialog : public QDialog
-{
- Q_OBJECT
-public:
- SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr)
- {
- }
-
- void setTextEdit(QPlainTextEdit *textEdit)
- {
- m_textEdit = textEdit;
- }
-
-protected:
- void showEvent(QShowEvent *e) override
- {
- // Now that we're shown, use our width to calculate a good
- // bounding box for the text, and resize m_textEdit appropriately.
- QDialog::showEvent(e);
-
- if (!m_textEdit) {
- return;
- }
-
- QSize fudge(20, 24); // About what it sounds like :-/
-
- // Form rect with a lot of height for bounding. Use no more than
- // 5 lines.
- QRect curRect(m_textEdit->rect());
- QFontMetrics metrics(fontMetrics());
- curRect.setHeight(5 * metrics.lineSpacing());
- curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok?
-
- QString text(m_textEdit->toPlainText());
- curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text);
-
- // Scroll bars interfere. If we don't think there's enough room, enable
- // the vertical scrollbar however.
- m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- if (curRect.height() < m_textEdit->height()) { // then we've got room
- m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- m_textEdit->setMaximumHeight(curRect.height() + fudge.height());
- }
-
- m_textEdit->setMinimumSize(curRect.size() + fudge);
- m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
- updateGeometry();
- }
-
-private:
- QPlainTextEdit *m_textEdit;
-};
-
// Helper function to make the given .desktop file executable by ensuring
// that a #!/usr/bin/env xdg-open line is added if necessary and the file has
// the +x bit set for the user. Returns false if either fails.
-static bool makeFileExecutable(const QString &fileName)
+static bool makeServiceFileExecutable(const QString &fileName)
{
// Open the file and read the first two characters, check if it's
// #!. If not, create a new file, prepend appropriate lines, and copy
@@ -632,15 +734,7 @@
}
} // Add header
- // corresponds to owner on unix, which will have to do since if the user
- // isn't the owner we can't change perms anyways.
- if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) {
- qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << desktopFile.errorString();
- return false;
- }
-
- // whew
- return true;
+ return setExecuteBit(fileName);
}
// Helper function to make a .desktop file executable if prompted by the user.
@@ -654,67 +748,14 @@
return false; // Don't circumvent the Kiosk
}
- SecureMessageDialog *baseDialog = new SecureMessageDialog(window);
- baseDialog->setWindowTitle(i18nc("Warning about executing unknown .desktop file", "Warning"));
-
- QVBoxLayout *topLayout = new QVBoxLayout;
- baseDialog->setLayout(topLayout);
-
- // Dialog will have explanatory text with a disabled lineedit with the
- // Exec= to make it visually distinct.
- QWidget *baseWidget = new QWidget(baseDialog);
- QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget);
-
- QLabel *iconLabel = new QLabel(baseWidget);
- QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge));
- mainLayout->addWidget(iconLabel);
- iconLabel->setPixmap(warningIcon);
-
- QVBoxLayout *contentLayout = new QVBoxLayout;
- QString warningMessage = i18nc("program name follows in a line edit below",
- "This will start the program:");
-
- QLabel *message = new QLabel(warningMessage, baseWidget);
- contentLayout->addWidget(message);
-
// We can use KStandardDirs::findExe to resolve relative pathnames
// but that gets rid of the command line arguments.
QString program = QFileInfo(service.exec()).canonicalFilePath();
if (program.isEmpty()) { // e.g. due to command line arguments
program = service.exec();
}
- QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
- textEdit->setPlainText(program);
- textEdit->setReadOnly(true);
- contentLayout->addWidget(textEdit);
-
- QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel"));
- contentLayout->addWidget(footerLabel);
- contentLayout->addStretch(0); // Don't allow the text edit to expand
-
- mainLayout->addLayout(contentLayout);
-
- topLayout->addWidget(baseWidget);
- baseDialog->setTextEdit(textEdit);
-
- QDialogButtonBox *buttonBox = new QDialogButtonBox(baseDialog);
- buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::cont());
- buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true);
- buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
- QObject::connect(buttonBox, &QDialogButtonBox::accepted, baseDialog, &QDialog::accept);
- QObject::connect(buttonBox, &QDialogButtonBox::rejected, baseDialog, &QDialog::reject);
- topLayout->addWidget(buttonBox);
-
- // Constrain maximum size. Minimum size set in
- // the dialog's show event.
- QSize screenSize = QApplication::desktop()->screen()->size();
- baseDialog->resize(screenSize.width() / 4, 50);
- baseDialog->setMaximumHeight(screenSize.height() / 3);
- baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
-
- int result = baseDialog->exec();
+ int result = showUntrustedProgramWarning(program, window);
if (result != QDialog::Accepted) {
return false;
}
@@ -724,7 +765,7 @@
// .desktop file is. Now add a header to it if it doesn't already have one
// and add the +x bit.
- if (!::makeFileExecutable(service.entryPath())) {
+ if (!::makeServiceFileExecutable(service.entryPath())) {
QString serviceName = service.name();
if (serviceName.isEmpty()) {
serviceName = service.genericName();
@@ -1302,26 +1343,6 @@
}
}
-void KRun::slotChmodFinished(KJob *job)
-{
- d->m_job = nullptr;
- const int errCode = job->error();
- if (errCode) {
- // ERR_NO_CONTENT is not an error, but an indication no further
- // actions needs to be taken.
- if (errCode != KIO::ERR_NO_CONTENT) {
- qCWarning(KIO_WIDGETS) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
- handleError(job);
-
- d->m_bFault = true;
- }
- } else {
- //If job was succesfull, then restart KRun for the same file.
- KRun *rerun = new KRun(d->m_strURL, d->m_window, d->m_bProgressInfo, d->m_asn);
- }
- setFinished(true);
-}
-
void KRun::mimeTypeDetermined(const QString &mimeType)
{
// foundMimeType reimplementations might show a dialog box;
@@ -1386,31 +1407,6 @@
KRun::RunFlags runFlags;
if (d->m_runExecutables) {
runFlags |= KRun::RunExecutables;
- //If file is executable but doesn't have +x permission, ask user if he wants to set +x
- if (!isExecutableFile(d->m_strURL, type) && isExecutable(type)) {
- KMessageBox::ButtonCode makeExecutable = KMessageBox::questionYesNo(
- d->m_window,
- i18n("File %1 is not marked as executable. Do you wish to run it?
"
- "Keep in mind that if you don't know the source of this file, it may be unsafe to run it!",
- d->m_strURL.toLocalFile()),
- i18n("Run executable file"),
- KGuiItem(i18n("Make executable and run")),
- KGuiItem(i18n("Open"))
- );
- //If the answer was Yes, start KIO::chmod job
- if (makeExecutable == KMessageBox::ButtonCode::Yes) {
- KFileItem binaryFile(d->m_strURL);
- mode_t newPermissions = binaryFile.permissions() | S_IXUSR;
- KIO::Job* chmodJob = KIO::chmod(d->m_strURL, newPermissions);
- connect(chmodJob, &KJob::result,
- this, &KRun::slotChmodFinished);
- chmodJob->start();
- d->m_job = chmodJob;
- //Stop further execution, it will later be restarted or abandoned after the job finishes
- return;
- }
- //If the answer was No, then execute normally
- }
}
if (!KRun::runUrl(d->m_strURL, type, d->m_window, runFlags, d->m_suggestedFileName, d->m_asn)) {