Changeset View
Standalone View
src/widgets/krun.cpp
Show First 20 Lines • Show All 131 Lines • ▼ Show 20 Line(s) | 131 | QObject::connect(processRunner, &KProcessRunner::error, receiver, [widget](const QString &errorString) { | |||
---|---|---|---|---|---|
132 | QEventLoopLocker locker; | 132 | QEventLoopLocker locker; | ||
133 | KMessageBox::sorry(widget, errorString); | 133 | KMessageBox::sorry(widget, errorString); | ||
134 | }); | 134 | }); | ||
135 | return processRunner->pid(); | 135 | return processRunner->pid(); | ||
136 | } | 136 | } | ||
137 | 137 | | |||
138 | // --------------------------------------------------------------------------- | 138 | // --------------------------------------------------------------------------- | ||
139 | 139 | | |||
140 | // Helper function that returns whether a file has the execute bit set or not. | ||||
141 | static bool hasExecuteBit(const QString &fileName) | ||||
142 | { | ||||
143 | QFileInfo file(fileName); | ||||
144 | return file.isExecutable(); | ||||
145 | } | ||||
146 | | ||||
140 | bool KRun::isExecutableFile(const QUrl &url, const QString &mimetype) | 147 | bool KRun::isExecutableFile(const QUrl &url, const QString &mimetype) | ||
141 | { | 148 | { | ||
142 | if (!url.isLocalFile()) { | 149 | if (!url.isLocalFile()) { | ||
143 | return false; | 150 | return false; | ||
144 | } | 151 | } | ||
145 | 152 | | |||
146 | QMimeDatabase db; | 153 | if (!isExecutable(mimetype)) { | ||
dfaure: I wonder why this doesn't use isExecutable(mimetype)... | |||||
Hmm, because isExecutable(mimetype) also returns true for desktop files. So this would change behaviour for users of this API. (see why we try to limit public API...). Most users found in https://lxr.kde.org/ident?_i=isExecutableFile would actually see this as a bugfix (they warn about, or forbid, local execution), but So... I was just wondering (and hinting at an investigation, not requesting a change), but I have my answer now.. I suggest to revert your last change, and maybe add a comment about why we can't use isExecutable. (Or we could extract a common function...) dfaure: Hmm, because isExecutable(mimetype) also returns true for desktop files.
So this would change… | |||||
I understand, it was a bit careless from me not to check this beforehand. Reverted and added a note. mdlubakowski: I understand, it was a bit careless from me not to check this beforehand. Reverted and added a… | |||||
147 | QMimeType mimeType = db.mimeTypeForName(mimetype); | | |||
148 | if (!mimeType.inherits(QStringLiteral("application/x-executable")) | | |||
149 | #ifdef Q_OS_WIN | | |||
150 | && !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable")) | | |||
151 | #endif | | |||
152 | && !mimeType.inherits(QStringLiteral("application/x-executable-script")) | | |||
153 | && !mimeType.inherits(QStringLiteral("application/x-sharedlib"))) { | | |||
154 | return false; | 154 | return false; | ||
155 | } | 155 | } | ||
156 | 156 | | |||
157 | QFileInfo file(url.toLocalFile()); | 157 | if (!hasExecuteBit(url.toLocalFile())) { | ||
158 | if (!file.isExecutable()) { | | |||
159 | return false; | 158 | return false; | ||
160 | } | 159 | } | ||
161 | 160 | | |||
162 | return true; | 161 | return true; | ||
163 | } | 162 | } | ||
164 | 163 | | |||
165 | void KRun::handleInitError(int kioErrorCode, const QString &errorMsg) | 164 | void KRun::handleInitError(int kioErrorCode, const QString &errorMsg) | ||
please make this function as file static, so there is no need to expose it as API of the KRun class pino: please make this function as file static, so there is no need to expose it as API of the KRun… | |||||
166 | { | 165 | { | ||
167 | Q_UNUSED(kioErrorCode); | 166 | Q_UNUSED(kioErrorCode); | ||
168 | d->m_showingDialog = true; | 167 | d->m_showingDialog = true; | ||
dfaure: This could now be simplified to `return file.isExecutable();` | |||||
169 | KMessageBox::error(d->m_window, errorMsg); | 168 | KMessageBox::error(d->m_window, errorMsg); | ||
170 | d->m_showingDialog = false; | 169 | d->m_showingDialog = false; | ||
171 | } | 170 | } | ||
172 | 171 | | |||
173 | void KRun::handleError(KJob *job) | 172 | void KRun::handleError(KJob *job) | ||
174 | { | 173 | { | ||
175 | Q_ASSERT(job); | 174 | Q_ASSERT(job); | ||
176 | if (job) { | 175 | if (job) { | ||
177 | d->m_showingDialog = true; | 176 | d->m_showingDialog = true; | ||
178 | job->uiDelegate()->showErrorMessage(); | 177 | job->uiDelegate()->showErrorMessage(); | ||
179 | d->m_showingDialog = false; | 178 | d->m_showingDialog = false; | ||
180 | } | 179 | } | ||
181 | } | 180 | } | ||
182 | 181 | | |||
182 | // Simple QDialog that resizes the given text edit after being shown to more | ||||
183 | // or less fit the enclosed text. | ||||
184 | class SecureMessageDialog : public QDialog | ||||
185 | { | ||||
186 | Q_OBJECT | ||||
187 | public: | ||||
188 | SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr) | ||||
189 | { | ||||
190 | } | ||||
191 | | ||||
192 | void setTextEdit(QPlainTextEdit *textEdit) | ||||
193 | { | ||||
194 | m_textEdit = textEdit; | ||||
195 | } | ||||
196 | | ||||
197 | protected: | ||||
198 | void showEvent(QShowEvent *e) override | ||||
_pre-existing) this code will also be triggered when switching virtual desktops, and we don't want a resize then. So this should test for e->spontaneous() and return early if true. dfaure: _pre-existing) this code will also be triggered when switching virtual desktops, and we don't… | |||||
199 | { | ||||
200 | if (e->spontaneous()) { | ||||
201 | return; | ||||
202 | } | ||||
203 | | ||||
204 | // Now that we're shown, use our width to calculate a good | ||||
205 | // bounding box for the text, and resize m_textEdit appropriately. | ||||
206 | QDialog::showEvent(e); | ||||
207 | | ||||
208 | if (!m_textEdit) { | ||||
209 | return; | ||||
210 | } | ||||
211 | | ||||
212 | QSize fudge(20, 24); // About what it sounds like :-/ | ||||
213 | | ||||
214 | // Form rect with a lot of height for bounding. Use no more than | ||||
215 | // 5 lines. | ||||
216 | QRect curRect(m_textEdit->rect()); | ||||
217 | QFontMetrics metrics(fontMetrics()); | ||||
218 | curRect.setHeight(5 * metrics.lineSpacing()); | ||||
219 | curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok? | ||||
220 | | ||||
221 | QString text(m_textEdit->toPlainText()); | ||||
222 | curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text); | ||||
223 | | ||||
224 | // Scroll bars interfere. If we don't think there's enough room, enable | ||||
225 | // the vertical scrollbar however. | ||||
226 | m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | ||||
227 | if (curRect.height() < m_textEdit->height()) { // then we've got room | ||||
228 | m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | ||||
229 | m_textEdit->setMaximumHeight(curRect.height() + fudge.height()); | ||||
230 | } | ||||
dfaure: Is this line necessary? (I wouldn't think so) | |||||
231 | | ||||
232 | m_textEdit->setMinimumSize(curRect.size() + fudge); | ||||
233 | m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); | ||||
234 | } | ||||
235 | | ||||
236 | private: | ||||
237 | QPlainTextEdit *m_textEdit; | ||||
238 | }; | ||||
239 | | ||||
240 | // Shows confirmation dialog whether an untrusted program should be run | ||||
241 | // or not, since it may be potentially dangerous. | ||||
242 | static int showUntrustedProgramWarning(const QString &programName, QWidget *window) | ||||
243 | { | ||||
244 | SecureMessageDialog *baseDialog = new SecureMessageDialog(window); | ||||
245 | baseDialog->setWindowTitle(i18nc("Warning about executing unknown program", "Warning")); | ||||
246 | | ||||
247 | QVBoxLayout *topLayout = new QVBoxLayout; | ||||
248 | baseDialog->setLayout(topLayout); | ||||
249 | | ||||
250 | // Dialog will have explanatory text with a disabled lineedit with the | ||||
251 | // Exec= to make it visually distinct. | ||||
252 | QWidget *baseWidget = new QWidget(baseDialog); | ||||
253 | QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget); | ||||
254 | | ||||
255 | QLabel *iconLabel = new QLabel(baseWidget); | ||||
256 | QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge)); | ||||
257 | mainLayout->addWidget(iconLabel); | ||||
258 | iconLabel->setPixmap(warningIcon); | ||||
259 | | ||||
260 | QVBoxLayout *contentLayout = new QVBoxLayout; | ||||
261 | QString warningMessage = i18nc("program name follows in a line edit below", | ||||
262 | "This will start the program:"); | ||||
263 | | ||||
264 | QLabel *message = new QLabel(warningMessage, baseWidget); | ||||
265 | contentLayout->addWidget(message); | ||||
266 | | ||||
267 | QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget); | ||||
268 | textEdit->setPlainText(programName); | ||||
269 | textEdit->setReadOnly(true); | ||||
270 | contentLayout->addWidget(textEdit); | ||||
271 | | ||||
272 | QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel")); | ||||
273 | contentLayout->addWidget(footerLabel); | ||||
274 | contentLayout->addStretch(0); // Don't allow the text edit to expand | ||||
275 | | ||||
276 | mainLayout->addLayout(contentLayout); | ||||
277 | | ||||
278 | topLayout->addWidget(baseWidget); | ||||
279 | baseDialog->setTextEdit(textEdit); | ||||
280 | | ||||
281 | QDialogButtonBox *buttonBox = new QDialogButtonBox(baseDialog); | ||||
282 | buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); | ||||
283 | KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::cont()); | ||||
284 | buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); | ||||
285 | buttonBox->button(QDialogButtonBox::Cancel)->setFocus(); | ||||
286 | QObject::connect(buttonBox, &QDialogButtonBox::accepted, baseDialog, &QDialog::accept); | ||||
287 | QObject::connect(buttonBox, &QDialogButtonBox::rejected, baseDialog, &QDialog::reject); | ||||
288 | topLayout->addWidget(buttonBox); | ||||
289 | | ||||
290 | // Constrain maximum size. Minimum size set in | ||||
291 | // the dialog's show event. | ||||
292 | QSize screenSize = QApplication::desktop()->screen()->size(); | ||||
293 | baseDialog->resize(screenSize.width() / 4, 50); | ||||
294 | baseDialog->setMaximumHeight(screenSize.height() / 3); | ||||
295 | baseDialog->setMaximumWidth(screenSize.width() / 10 * 8); | ||||
296 | | ||||
297 | return baseDialog->exec(); | ||||
298 | } | ||||
299 | | ||||
300 | // Helper function that attempts to set execute bit for given file. | ||||
dfaure: rename to `file` now that it's used for both | |||||
301 | static bool setExecuteBit(const QString &fileName, QString &errorString) | ||||
maybe this function could return the error string on failure as out parameter, so ... pino: maybe this function could return the error string on failure as out parameter, so ... | |||||
302 | { | ||||
303 | QFile file(fileName); | ||||
304 | | ||||
305 | // corresponds to owner on unix, which will have to do since if the user | ||||
306 | // isn't the owner we can't change perms anyways. | ||||
307 | if (!file.setPermissions(QFile::ExeUser | file.permissions())) { | ||||
308 | errorString = file.errorString(); | ||||
309 | qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << errorString; | ||||
310 | return false; | ||||
311 | } | ||||
312 | | ||||
313 | return true; | ||||
314 | } | ||||
315 | | ||||
183 | #ifndef KIOWIDGETS_NO_DEPRECATED | 316 | #ifndef KIOWIDGETS_NO_DEPRECATED | ||
184 | bool KRun::runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile, bool runExecutables, const QString &suggestedFileName, const QByteArray &asn) | 317 | bool KRun::runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile, bool runExecutables, const QString &suggestedFileName, const QByteArray &asn) | ||
185 | { | 318 | { | ||
186 | RunFlags flags = tempFile ? KRun::DeleteTemporaryFiles : RunFlags(); | 319 | RunFlags flags = tempFile ? KRun::DeleteTemporaryFiles : RunFlags(); | ||
187 | if (runExecutables) { | 320 | if (runExecutables) { | ||
188 | flags |= KRun::RunExecutables; | 321 | flags |= KRun::RunExecutables; | ||
189 | } | 322 | } | ||
190 | 323 | | |||
191 | return runUrl(url, mimetype, window, flags, suggestedFileName, asn); | 324 | return runUrl(url, mimetype, window, flags, suggestedFileName, asn); | ||
192 | } | 325 | } | ||
193 | #endif | 326 | #endif | ||
194 | 327 | | |||
195 | // This is called by foundMimeType, since it knows the mimetype of the URL | 328 | // This is called by foundMimeType, since it knows the mimetype of the URL | ||
196 | bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) | 329 | bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) | ||
197 | { | 330 | { | ||
331 | const QMimeDatabase db; | ||||
198 | const bool runExecutables = flags.testFlag(KRun::RunExecutables); | 332 | const bool runExecutables = flags.testFlag(KRun::RunExecutables); | ||
199 | const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles); | 333 | const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles); | ||
200 | bool noRun = false; | 334 | bool noRun = false; | ||
201 | bool noAuth = false; | 335 | bool noAuth = false; | ||
202 | if (_mimetype == QLatin1String("inode/directory-locked")) { | 336 | if (_mimetype == QLatin1String("inode/directory-locked")) { | ||
203 | KMessageBox::error(window, | 337 | KMessageBox::error(window, | ||
204 | i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>", u.toDisplayString().toHtmlEscaped())); | 338 | i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>", u.toDisplayString().toHtmlEscaped())); | ||
205 | return false; | 339 | return false; | ||
206 | } else if (_mimetype == QLatin1String("application/x-desktop")) { | 340 | } else if (_mimetype == QLatin1String("application/x-desktop")) { | ||
207 | if (u.isLocalFile() && runExecutables) { | 341 | if (u.isLocalFile() && runExecutables) { | ||
208 | return KDesktopFileActions::runWithStartup(u, true, asn); | 342 | return KDesktopFileActions::runWithStartup(u, true, asn); | ||
209 | } | 343 | } | ||
210 | } else if (isExecutableFile(u, _mimetype)) { | 344 | } else if (isExecutable(_mimetype)) { | ||
345 | // Check whether file is executable script | ||||
346 | const QMimeType mime = db.mimeTypeForName(_mimetype); | ||||
347 | bool isTextFile = mime.inherits(QStringLiteral("text/plain")); | ||||
348 | // Only run local files | ||||
211 | if (u.isLocalFile() && runExecutables) { | 349 | if (u.isLocalFile() && runExecutables) { | ||
212 | if (KAuthorized::authorize(QStringLiteral("shell_access"))) { | 350 | if (KAuthorized::authorize(QStringLiteral("shell_access"))) { | ||
351 | | ||||
352 | bool canRun = true; | ||||
353 | bool isFileExecutable = hasExecuteBit(u.toLocalFile()); | ||||
At this point checking the mimetype again is redundant, it was done on line 328. I guess we need a simple QFileInfo::isExecutable call or wrapper. dfaure: At this point checking the mimetype again is redundant, it was done on line 328. I guess we… | |||||
354 | | ||||
355 | // For executables that aren't scripts and without execute bit, | ||||
356 | // show prompt asking user if he wants to run the program. | ||||
357 | if (!isFileExecutable && !isTextFile) { | ||||
358 | canRun = false; | ||||
359 | int result = showUntrustedProgramWarning(u.fileName(), window); | ||||
360 | if (result == QDialog::Accepted) { | ||||
361 | QString errorString; | ||||
362 | if (!setExecuteBit(u.toLocalFile(), errorString)) { | ||||
363 | KMessageBox::sorry( | ||||
364 | window, | ||||
... here this dialog can show the error to the user (also, please drop the exclamation mark from the message) pino: ... here this dialog can show the error to the user
(also, please drop the exclamation mark… | |||||
365 | i18n("<qt>Unable to make file <b>%1</b> executable.\n%2.</qt>", | ||||
366 | u.toLocalFile(), errorString) | ||||
367 | ); | ||||
368 | } else { | ||||
369 | canRun = true; | ||||
370 | } | ||||
371 | } | ||||
372 | } else if (!isFileExecutable && isTextFile) { | ||||
373 | // Don't try to run scripts without execute bit, instead | ||||
374 | // open them with default application | ||||
375 | canRun = false; | ||||
376 | } | ||||
377 | | ||||
378 | if (canRun) { | ||||
213 | return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), | 379 | return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), | ||
214 | window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command | 380 | window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command | ||
215 | // ## TODO implement deleting the file if tempFile==true | 381 | // ## TODO implement deleting the file if tempFile==true | ||
382 | } | ||||
383 | | ||||
216 | } else { | 384 | } else { | ||
385 | // Show no permission warning | ||||
217 | noAuth = true; | 386 | noAuth = true; | ||
218 | } | 387 | } | ||
219 | } else if (_mimetype == QLatin1String("application/x-executable")) { | 388 | } else if (!isTextFile) { | ||
220 | noRun = true; | 389 | // Show warning for executables that aren't scripts | ||
221 | } | | |||
222 | } else if (isExecutable(_mimetype)) { | | |||
223 | if (!runExecutables) { | | |||
224 | noRun = true; | 390 | noRun = true; | ||
225 | } | 391 | } | ||
226 | | ||||
227 | if (!KAuthorized::authorize(QStringLiteral("shell_access"))) { | | |||
228 | noAuth = true; | | |||
229 | } | | |||
230 | } | 392 | } | ||
231 | 393 | | |||
232 | if (noRun) { | 394 | if (noRun) { | ||
233 | KMessageBox::sorry(window, | 395 | KMessageBox::sorry(window, | ||
234 | i18n("<qt>The file <b>%1</b> is an executable program. " | 396 | i18n("<qt>The file <b>%1</b> is an executable program. " | ||
235 | "For safety it will not be started.</qt>", u.toDisplayString().toHtmlEscaped())); | 397 | "For safety it will not be started.</qt>", u.toDisplayString().toHtmlEscaped())); | ||
236 | return false; | 398 | return false; | ||
237 | } | 399 | } | ||
▲ Show 20 Lines • Show All 286 Lines • ▼ Show 20 Line(s) | 681 | if (job->exec()) { // ## nasty nested event loop! | |||
524 | } | 686 | } | ||
525 | } | 687 | } | ||
526 | } | 688 | } | ||
527 | } | 689 | } | ||
528 | } | 690 | } | ||
529 | return urls; | 691 | return urls; | ||
530 | } | 692 | } | ||
531 | 693 | | |||
532 | // Simple QDialog that resizes the given text edit after being shown to more | | |||
533 | // or less fit the enclosed text. | | |||
534 | class SecureMessageDialog : public QDialog | | |||
535 | { | | |||
536 | Q_OBJECT | | |||
537 | public: | | |||
538 | SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr) | | |||
539 | { | | |||
540 | } | | |||
541 | | ||||
542 | void setTextEdit(QPlainTextEdit *textEdit) | | |||
543 | { | | |||
544 | m_textEdit = textEdit; | | |||
545 | } | | |||
546 | | ||||
547 | protected: | | |||
548 | void showEvent(QShowEvent *e) override | | |||
549 | { | | |||
550 | // Now that we're shown, use our width to calculate a good | | |||
551 | // bounding box for the text, and resize m_textEdit appropriately. | | |||
552 | QDialog::showEvent(e); | | |||
553 | | ||||
554 | if (!m_textEdit) { | | |||
555 | return; | | |||
556 | } | | |||
557 | | ||||
558 | QSize fudge(20, 24); // About what it sounds like :-/ | | |||
559 | | ||||
560 | // Form rect with a lot of height for bounding. Use no more than | | |||
561 | // 5 lines. | | |||
562 | QRect curRect(m_textEdit->rect()); | | |||
563 | QFontMetrics metrics(fontMetrics()); | | |||
564 | curRect.setHeight(5 * metrics.lineSpacing()); | | |||
565 | curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok? | | |||
566 | | ||||
567 | QString text(m_textEdit->toPlainText()); | | |||
568 | curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text); | | |||
569 | | ||||
570 | // Scroll bars interfere. If we don't think there's enough room, enable | | |||
571 | // the vertical scrollbar however. | | |||
572 | m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | | |||
573 | if (curRect.height() < m_textEdit->height()) { // then we've got room | | |||
574 | m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | | |||
575 | m_textEdit->setMaximumHeight(curRect.height() + fudge.height()); | | |||
576 | } | | |||
577 | | ||||
578 | m_textEdit->setMinimumSize(curRect.size() + fudge); | | |||
579 | m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); | | |||
580 | updateGeometry(); | | |||
581 | } | | |||
582 | | ||||
583 | private: | | |||
584 | QPlainTextEdit *m_textEdit; | | |||
585 | }; | | |||
586 | | ||||
587 | // Helper function to make the given .desktop file executable by ensuring | 694 | // Helper function to make the given .desktop file executable by ensuring | ||
588 | // that a #!/usr/bin/env xdg-open line is added if necessary and the file has | 695 | // that a #!/usr/bin/env xdg-open line is added if necessary and the file has | ||
589 | // the +x bit set for the user. Returns false if either fails. | 696 | // the +x bit set for the user. Returns false if either fails. | ||
590 | static bool makeFileExecutable(const QString &fileName) | 697 | static bool makeServiceFileExecutable(const QString &fileName, QString &errorString) | ||
591 | { | 698 | { | ||
592 | // Open the file and read the first two characters, check if it's | 699 | // Open the file and read the first two characters, check if it's | ||
593 | // #!. If not, create a new file, prepend appropriate lines, and copy | 700 | // #!. If not, create a new file, prepend appropriate lines, and copy | ||
594 | // over. | 701 | // over. | ||
595 | QFile desktopFile(fileName); | 702 | QFile desktopFile(fileName); | ||
596 | if (!desktopFile.open(QFile::ReadOnly)) { | 703 | if (!desktopFile.open(QFile::ReadOnly)) { | ||
597 | qCWarning(KIO_WIDGETS) << "Error opening service" << fileName << desktopFile.errorString(); | 704 | errorString = desktopFile.errorString(); | ||
705 | qCWarning(KIO_WIDGETS) << "Error opening service" << fileName << errorString; | ||||
598 | return false; | 706 | return false; | ||
599 | } | 707 | } | ||
600 | 708 | | |||
601 | QByteArray header = desktopFile.peek(2); // First two chars of file | 709 | QByteArray header = desktopFile.peek(2); // First two chars of file | ||
602 | if (header.size() == 0) { | 710 | if (header.size() == 0) { | ||
603 | qCWarning(KIO_WIDGETS) << "Error inspecting service" << fileName << desktopFile.errorString(); | 711 | errorString = desktopFile.errorString(); | ||
712 | qCWarning(KIO_WIDGETS) << "Error inspecting service" << fileName << errorString; | ||||
604 | return false; // Some kind of error | 713 | return false; // Some kind of error | ||
605 | } | 714 | } | ||
606 | 715 | | |||
607 | if (header != "#!") { | 716 | if (header != "#!") { | ||
608 | // Add header | 717 | // Add header | ||
609 | QSaveFile saveFile; | 718 | QSaveFile saveFile; | ||
610 | saveFile.setFileName(fileName); | 719 | saveFile.setFileName(fileName); | ||
611 | if (!saveFile.open(QIODevice::WriteOnly)) { | 720 | if (!saveFile.open(QIODevice::WriteOnly)) { | ||
612 | qCWarning(KIO_WIDGETS) << "Unable to open replacement file for" << fileName << saveFile.errorString(); | 721 | errorString = saveFile.errorString(); | ||
722 | qCWarning(KIO_WIDGETS) << "Unable to open replacement file for" << fileName << errorString; | ||||
613 | return false; | 723 | return false; | ||
614 | } | 724 | } | ||
615 | 725 | | |||
616 | QByteArray shebang("#!/usr/bin/env xdg-open\n"); | 726 | QByteArray shebang("#!/usr/bin/env xdg-open\n"); | ||
617 | if (saveFile.write(shebang) != shebang.size()) { | 727 | if (saveFile.write(shebang) != shebang.size()) { | ||
618 | qCWarning(KIO_WIDGETS) << "Error occurred adding header for" << fileName << saveFile.errorString(); | 728 | errorString = saveFile.errorString(); | ||
729 | qCWarning(KIO_WIDGETS) << "Error occurred adding header for" << fileName << errorString; | ||||
619 | saveFile.cancelWriting(); | 730 | saveFile.cancelWriting(); | ||
620 | return false; | 731 | return false; | ||
621 | } | 732 | } | ||
622 | 733 | | |||
623 | // Now copy the one into the other and then close and reopen desktopFile | 734 | // Now copy the one into the other and then close and reopen desktopFile | ||
624 | QByteArray desktopData(desktopFile.readAll()); | 735 | QByteArray desktopData(desktopFile.readAll()); | ||
625 | if (desktopData.isEmpty()) { | 736 | if (desktopData.isEmpty()) { | ||
626 | qCWarning(KIO_WIDGETS) << "Unable to read service" << fileName << desktopFile.errorString(); | 737 | errorString = desktopFile.errorString(); | ||
738 | qCWarning(KIO_WIDGETS) << "Unable to read service" << fileName << errorString; | ||||
627 | saveFile.cancelWriting(); | 739 | saveFile.cancelWriting(); | ||
628 | return false; | 740 | return false; | ||
629 | } | 741 | } | ||
630 | 742 | | |||
631 | if (saveFile.write(desktopData) != desktopData.size()) { | 743 | if (saveFile.write(desktopData) != desktopData.size()) { | ||
632 | qCWarning(KIO_WIDGETS) << "Error copying service" << fileName << saveFile.errorString(); | 744 | errorString = saveFile.errorString(); | ||
745 | qCWarning(KIO_WIDGETS) << "Error copying service" << fileName << errorString; | ||||
633 | saveFile.cancelWriting(); | 746 | saveFile.cancelWriting(); | ||
634 | return false; | 747 | return false; | ||
635 | } | 748 | } | ||
636 | 749 | | |||
637 | desktopFile.close(); | 750 | desktopFile.close(); | ||
638 | if (!saveFile.commit()) { // Figures.... | 751 | if (!saveFile.commit()) { // Figures.... | ||
639 | qCWarning(KIO_WIDGETS) << "Error committing changes to service" << fileName << saveFile.errorString(); | 752 | errorString = saveFile.errorString(); | ||
753 | qCWarning(KIO_WIDGETS) << "Error committing changes to service" << fileName << errorString; | ||||
640 | return false; | 754 | return false; | ||
641 | } | 755 | } | ||
642 | 756 | | |||
643 | if (!desktopFile.open(QFile::ReadOnly)) { | 757 | if (!desktopFile.open(QFile::ReadOnly)) { | ||
644 | qCWarning(KIO_WIDGETS) << "Error re-opening service" << fileName << desktopFile.errorString(); | 758 | errorString = desktopFile.errorString(); | ||
759 | qCWarning(KIO_WIDGETS) << "Error re-opening service" << fileName << errorString; | ||||
645 | return false; | 760 | return false; | ||
646 | } | 761 | } | ||
647 | } // Add header | 762 | } // Add header | ||
648 | 763 | | |||
649 | // corresponds to owner on unix, which will have to do since if the user | 764 | return setExecuteBit(fileName, errorString); | ||
650 | // isn't the owner we can't change perms anyways. | | |||
651 | if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) { | | |||
652 | qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << desktopFile.errorString(); | | |||
653 | return false; | | |||
654 | } | | |||
655 | | ||||
656 | // whew | | |||
657 | return true; | | |||
658 | } | 765 | } | ||
659 | 766 | | |||
660 | // Helper function to make a .desktop file executable if prompted by the user. | 767 | // Helper function to make a .desktop file executable if prompted by the user. | ||
661 | // returns true if KRun::run() should continue with execution, false if user declined | 768 | // returns true if KRun::run() should continue with execution, false if user declined | ||
662 | // to make the file executable or we failed to make it executable. | 769 | // to make the file executable or we failed to make it executable. | ||
663 | static bool makeServiceExecutable(const KService &service, QWidget *window) | 770 | static bool makeServiceExecutable(const KService &service, QWidget *window) | ||
664 | { | 771 | { | ||
665 | if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { | 772 | if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { | ||
666 | qCWarning(KIO_WIDGETS) << "No authorization to execute " << service.entryPath(); | 773 | qCWarning(KIO_WIDGETS) << "No authorization to execute " << service.entryPath(); | ||
667 | KMessageBox::sorry(window, i18n("You are not authorized to execute this service.")); | 774 | KMessageBox::sorry(window, i18n("You are not authorized to execute this service.")); | ||
668 | return false; // Don't circumvent the Kiosk | 775 | return false; // Don't circumvent the Kiosk | ||
669 | } | 776 | } | ||
670 | 777 | | |||
671 | SecureMessageDialog *baseDialog = new SecureMessageDialog(window); | | |||
672 | baseDialog->setWindowTitle(i18nc("Warning about executing unknown .desktop file", "Warning")); | | |||
673 | | ||||
674 | QVBoxLayout *topLayout = new QVBoxLayout; | | |||
675 | baseDialog->setLayout(topLayout); | | |||
676 | | ||||
677 | // Dialog will have explanatory text with a disabled lineedit with the | | |||
678 | // Exec= to make it visually distinct. | | |||
679 | QWidget *baseWidget = new QWidget(baseDialog); | | |||
680 | QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget); | | |||
681 | | ||||
682 | QLabel *iconLabel = new QLabel(baseWidget); | | |||
683 | QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge)); | | |||
684 | mainLayout->addWidget(iconLabel); | | |||
685 | iconLabel->setPixmap(warningIcon); | | |||
686 | | ||||
687 | QVBoxLayout *contentLayout = new QVBoxLayout; | | |||
688 | QString warningMessage = i18nc("program name follows in a line edit below", | | |||
689 | "This will start the program:"); | | |||
690 | | ||||
691 | QLabel *message = new QLabel(warningMessage, baseWidget); | | |||
692 | contentLayout->addWidget(message); | | |||
693 | | ||||
694 | // We can use KStandardDirs::findExe to resolve relative pathnames | 778 | // We can use KStandardDirs::findExe to resolve relative pathnames | ||
695 | // but that gets rid of the command line arguments. | 779 | // but that gets rid of the command line arguments. | ||
696 | QString program = QFileInfo(service.exec()).canonicalFilePath(); | 780 | QString program = QFileInfo(service.exec()).canonicalFilePath(); | ||
697 | if (program.isEmpty()) { // e.g. due to command line arguments | 781 | if (program.isEmpty()) { // e.g. due to command line arguments | ||
698 | program = service.exec(); | 782 | program = service.exec(); | ||
699 | } | 783 | } | ||
700 | 784 | | |||
701 | QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget); | 785 | int result = showUntrustedProgramWarning(program, window); | ||
702 | textEdit->setPlainText(program); | | |||
703 | textEdit->setReadOnly(true); | | |||
704 | contentLayout->addWidget(textEdit); | | |||
705 | | ||||
706 | QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel")); | | |||
707 | contentLayout->addWidget(footerLabel); | | |||
708 | contentLayout->addStretch(0); // Don't allow the text edit to expand | | |||
709 | | ||||
710 | mainLayout->addLayout(contentLayout); | | |||
711 | | ||||
712 | topLayout->addWidget(baseWidget); | | |||
713 | baseDialog->setTextEdit(textEdit); | | |||
714 | | ||||
715 | QDialogButtonBox *buttonBox = new QDialogButtonBox(baseDialog); | | |||
716 | buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); | | |||
717 | KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::cont()); | | |||
718 | buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); | | |||
719 | buttonBox->button(QDialogButtonBox::Cancel)->setFocus(); | | |||
720 | QObject::connect(buttonBox, &QDialogButtonBox::accepted, baseDialog, &QDialog::accept); | | |||
721 | QObject::connect(buttonBox, &QDialogButtonBox::rejected, baseDialog, &QDialog::reject); | | |||
722 | topLayout->addWidget(buttonBox); | | |||
723 | | ||||
724 | // Constrain maximum size. Minimum size set in | | |||
725 | // the dialog's show event. | | |||
726 | QSize screenSize = QApplication::desktop()->screen()->size(); | | |||
727 | baseDialog->resize(screenSize.width() / 4, 50); | | |||
728 | baseDialog->setMaximumHeight(screenSize.height() / 3); | | |||
729 | baseDialog->setMaximumWidth(screenSize.width() / 10 * 8); | | |||
730 | | ||||
731 | int result = baseDialog->exec(); | | |||
732 | if (result != QDialog::Accepted) { | 786 | if (result != QDialog::Accepted) { | ||
733 | return false; | 787 | return false; | ||
734 | } | 788 | } | ||
735 | 789 | | |||
736 | // Assume that service is an absolute path since we're being called (relative paths | 790 | // Assume that service is an absolute path since we're being called (relative paths | ||
737 | // would have been allowed unless Kiosk said no, therefore we already know where the | 791 | // would have been allowed unless Kiosk said no, therefore we already know where the | ||
738 | // .desktop file is. Now add a header to it if it doesn't already have one | 792 | // .desktop file is. Now add a header to it if it doesn't already have one | ||
739 | // and add the +x bit. | 793 | // and add the +x bit. | ||
740 | 794 | | |||
741 | if (!::makeFileExecutable(service.entryPath())) { | 795 | QString errorString; | ||
796 | if (!::makeServiceFileExecutable(service.entryPath(), errorString)) { | ||||
742 | QString serviceName = service.name(); | 797 | QString serviceName = service.name(); | ||
743 | if (serviceName.isEmpty()) { | 798 | if (serviceName.isEmpty()) { | ||
744 | serviceName = service.genericName(); | 799 | serviceName = service.genericName(); | ||
745 | } | 800 | } | ||
746 | 801 | | |||
747 | KMessageBox::sorry( | 802 | KMessageBox::sorry( | ||
748 | window, | 803 | window, | ||
749 | i18n("Unable to make the service %1 executable, aborting execution", serviceName) | 804 | i18n("Unable to make the service %1 executable, aborting execution.\n%2.", | ||
805 | serviceName, errorString) | ||||
750 | ); | 806 | ); | ||
751 | 807 | | |||
752 | return false; | 808 | return false; | ||
753 | } | 809 | } | ||
754 | 810 | | |||
755 | return true; | 811 | return true; | ||
756 | } | 812 | } | ||
757 | 813 | | |||
▲ Show 20 Lines • Show All 356 Lines • ▼ Show 20 Line(s) | |||||
1114 | { | 1170 | { | ||
1115 | if (m_strURL == QUrl(QStringLiteral("remote:/x-wizard_service.desktop"))) { | 1171 | if (m_strURL == QUrl(QStringLiteral("remote:/x-wizard_service.desktop"))) { | ||
1116 | return false; | 1172 | return false; | ||
1117 | } | 1173 | } | ||
1118 | const QMimeDatabase db; | 1174 | const QMimeDatabase db; | ||
1119 | const QMimeType mime = db.mimeTypeForUrl(m_strURL); | 1175 | const QMimeType mime = db.mimeTypeForUrl(m_strURL); | ||
1120 | 1176 | | |||
1121 | const bool isFileExecutable = (isExecutableFile(m_strURL, mime.name()) || | 1177 | const bool isFileExecutable = (isExecutableFile(m_strURL, mime.name()) || | ||
1122 | mime.inherits(QStringLiteral("application/x-desktop"))); | 1178 | mime.inherits(QStringLiteral("application/x-desktop"))); | ||
dfaure: This can be removed now that isExecutableFile returns true for desktop files. | |||||
isExecutableFile will return false if the file doesn't have +x bit, which will result in prompt not being show, so I dont think this should be removed. mdlubakowski: isExecutableFile will return false if the file doesn't have +x bit, which will result in prompt… | |||||
Ah. Hmm. I see. This code is confusing. But other than the naming, it makes sense, this code is actually about the edit-or-execute prompt for +x scripts and (any) desktop files. dfaure: Ah. Hmm. I see. This code is confusing.
isFileExecutable will then be true for a non-+x desktop… | |||||
1123 | const bool isTextFile = mime.inherits(QStringLiteral("text/plain")); | 1179 | const bool isTextFile = mime.inherits(QStringLiteral("text/plain")); | ||
1124 | 1180 | | |||
1125 | if (isFileExecutable && isTextFile) { | 1181 | if (isFileExecutable && isTextFile) { | ||
1126 | KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); | 1182 | KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); | ||
1127 | const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk"); | 1183 | const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk"); | ||
1128 | 1184 | | |||
1129 | if (value == QLatin1String("alwaysAsk")) { | 1185 | if (value == QLatin1String("alwaysAsk")) { | ||
1130 | return true; | 1186 | return true; | ||
▲ Show 20 Lines • Show All 247 Lines • ▼ Show 20 Line(s) | 1433 | if (!mime.isValid()) { | |||
1378 | qCWarning(KIO_WIDGETS) << "Unknown mimetype " << type; | 1434 | qCWarning(KIO_WIDGETS) << "Unknown mimetype " << type; | ||
1379 | } else if (mime.inherits(QStringLiteral("application/x-desktop")) && !d->m_localPath.isEmpty()) { | 1435 | } else if (mime.inherits(QStringLiteral("application/x-desktop")) && !d->m_localPath.isEmpty()) { | ||
1380 | d->m_strURL = QUrl::fromLocalFile(d->m_localPath); | 1436 | d->m_strURL = QUrl::fromLocalFile(d->m_localPath); | ||
1381 | } | 1437 | } | ||
1382 | 1438 | | |||
1383 | KRun::RunFlags runFlags; | 1439 | KRun::RunFlags runFlags; | ||
1384 | if (d->m_runExecutables) { | 1440 | if (d->m_runExecutables) { | ||
1385 | runFlags |= KRun::RunExecutables; | 1441 | runFlags |= KRun::RunExecutables; | ||
1386 | } | 1442 | } | ||
dfaure: is *an* executable
(otherwise this sentence is very confusing) | |||||
mdlubakowski: This line is now removed | |||||
1387 | if (!KRun::runUrl(d->m_strURL, type, d->m_window, runFlags, d->m_suggestedFileName, d->m_asn)) { | 1443 | if (!KRun::runUrl(d->m_strURL, type, d->m_window, runFlags, d->m_suggestedFileName, d->m_asn)) { | ||
What happens for desktop files? Won't this go into this code, before going into the existing code to make desktop files executable? dfaure: What happens for desktop files? Won't this go into this code, before going into the existing… | |||||
Moved all the code to KRun::runUrl instead, so it doesnt intercept anything mdlubakowski: Moved all the code to KRun::runUrl instead, so it doesnt intercept anything | |||||
pino: extra empty line | |||||
1388 | d->m_bFault = true; | 1444 | d->m_bFault = true; | ||
1389 | } | 1445 | } | ||
1390 | setFinished(true); | 1446 | setFinished(true); | ||
1391 | } | 1447 | } | ||
1392 | 1448 | | |||
1393 | void KRun::killJob() | 1449 | void KRun::killJob() | ||
1394 | { | 1450 | { | ||
1395 | if (d->m_job) { | 1451 | if (d->m_job) { | ||
1396 | //qDebug() << this << "m_job=" << d->m_job; | 1452 | //qDebug() << this << "m_job=" << d->m_job; | ||
1397 | d->m_job->kill(); | 1453 | d->m_job->kill(); | ||
1398 | d->m_job = nullptr; | 1454 | d->m_job = nullptr; | ||
1399 | } | 1455 | } | ||
1400 | } | 1456 | } | ||
1401 | 1457 | | |||
Only local files can be executed anyway, so you could use file.setPermissions(QFile::ExeUser | file.permissions()) like makeFileExecutable does for desktop files. Actually maybe you could reuse more of that code? dfaure: Only local files can be executed anyway, so you could use file.setPermissions(QFile::ExeUser |… | |||||
1402 | void KRun::abort() | 1458 | void KRun::abort() | ||
1403 | { | 1459 | { | ||
1404 | if (d->m_bFinished) { | 1460 | if (d->m_bFinished) { | ||
1405 | return; | 1461 | return; | ||
1406 | } | 1462 | } | ||
1407 | //qDebug() << this << "m_showingDialog=" << d->m_showingDialog; | 1463 | //qDebug() << this << "m_showingDialog=" << d->m_showingDialog; | ||
1408 | killJob(); | 1464 | killJob(); | ||
1409 | // If we're showing an error message box, the rest will be done | 1465 | // If we're showing an error message box, the rest will be done | ||
▲ Show 20 Lines • Show All 85 Lines • ▼ Show 20 Line(s) | |||||
1495 | 1551 | | |||
1496 | QString KRun::suggestedFileName() const | 1552 | QString KRun::suggestedFileName() const | ||
1497 | { | 1553 | { | ||
1498 | return d->m_suggestedFileName; | 1554 | return d->m_suggestedFileName; | ||
1499 | } | 1555 | } | ||
1500 | 1556 | | |||
1501 | bool KRun::isExecutable(const QString &serviceType) | 1557 | bool KRun::isExecutable(const QString &serviceType) | ||
1502 | { | 1558 | { | ||
1503 | return (serviceType == QLatin1String("application/x-desktop") || | 1559 | QMimeDatabase db; | ||
1504 | serviceType == QLatin1String("application/x-executable") || | 1560 | QMimeType mimeType = db.mimeTypeForName(serviceType); | ||
1561 | return (mimeType.inherits(QLatin1String("application/x-desktop")) || | ||||
1562 | mimeType.inherits(QLatin1String("application/x-executable")) || | ||||
1505 | /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */ | 1563 | /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */ | ||
1506 | serviceType == QLatin1String("application/x-sharedlib") || | 1564 | mimeType.inherits(QLatin1String("application/x-sharedlib")) || | ||
1507 | serviceType == QLatin1String("application/x-ms-dos-executable") || | 1565 | mimeType.inherits(QLatin1String("application/x-ms-dos-executable")) || | ||
1508 | serviceType == QLatin1String("application/x-shellscript")); | 1566 | mimeType.inherits(QLatin1String("application/x-shellscript"))); | ||
1509 | } | 1567 | } | ||
1510 | 1568 | | |||
1511 | void KRun::setUrl(const QUrl &url) | 1569 | void KRun::setUrl(const QUrl &url) | ||
1512 | { | 1570 | { | ||
1513 | d->m_strURL = url; | 1571 | d->m_strURL = url; | ||
1514 | } | 1572 | } | ||
1515 | 1573 | | |||
1516 | QUrl KRun::url() const | 1574 | QUrl KRun::url() const | ||
▲ Show 20 Lines • Show All 168 Lines • Show Last 20 Lines |
I wonder why this doesn't use isExecutable(mimetype)...