File indexing completed on 2024-06-16 10:06:12

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
0004     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
0006     SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org>
0007     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "jobuidelegate.h"
0013 #include "kio_widgets_debug.h"
0014 #include "kiogui_export.h"
0015 #include "widgetsaskuseractionhandler.h"
0016 #include "widgetsopenorexecutefilehandler.h"
0017 #include "widgetsopenwithhandler.h"
0018 #include "widgetsuntrustedprogramhandler.h"
0019 #include <kio/jobuidelegatefactory.h>
0020 
0021 #include <KConfigGroup>
0022 #include <KJob>
0023 #include <KJobWidgets>
0024 #include <KLocalizedString>
0025 #include <KMessageBox>
0026 #include <KSharedConfig>
0027 #include <clipboardupdater_p.h>
0028 #include <ksslinfodialog.h>
0029 
0030 #ifndef KIO_ANDROID_STUB
0031 #include <QDBusInterface>
0032 #endif
0033 #include <QGuiApplication>
0034 #include <QIcon>
0035 #include <QPointer>
0036 #include <QRegularExpression>
0037 #include <QUrl>
0038 #include <QWidget>
0039 
0040 class KIO::JobUiDelegatePrivate
0041 {
0042 public:
0043     JobUiDelegatePrivate(KIO::JobUiDelegate *qq, const QList<QObject *> &ifaces)
0044     {
0045         for (auto iface : ifaces) {
0046             iface->setParent(qq);
0047             if (auto obj = qobject_cast<UntrustedProgramHandlerInterface *>(iface)) {
0048                 m_untrustedProgramHandler = obj;
0049             } else if (auto obj = qobject_cast<OpenWithHandlerInterface *>(iface)) {
0050                 m_openWithHandler = obj;
0051             } else if (auto obj = qobject_cast<OpenOrExecuteFileInterface *>(iface)) {
0052                 m_openOrExecuteFileHandler = obj;
0053             } else if (auto obj = qobject_cast<AskUserActionInterface *>(iface)) {
0054                 m_askUserActionHandler = obj;
0055             }
0056         }
0057 
0058         if (!m_untrustedProgramHandler) {
0059             m_untrustedProgramHandler = new WidgetsUntrustedProgramHandler(qq);
0060         }
0061         if (!m_openWithHandler) {
0062             m_openWithHandler = new WidgetsOpenWithHandler(qq);
0063         }
0064         if (!m_openOrExecuteFileHandler) {
0065             m_openOrExecuteFileHandler = new WidgetsOpenOrExecuteFileHandler(qq);
0066         }
0067         if (!m_askUserActionHandler) {
0068             m_askUserActionHandler = new WidgetsAskUserActionHandler(qq);
0069         }
0070     }
0071 
0072     UntrustedProgramHandlerInterface *m_untrustedProgramHandler = nullptr;
0073     OpenWithHandlerInterface *m_openWithHandler = nullptr;
0074     OpenOrExecuteFileInterface *m_openOrExecuteFileHandler = nullptr;
0075     AskUserActionInterface *m_askUserActionHandler = nullptr;
0076 };
0077 
0078 #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 98)
0079 KIO::JobUiDelegate::JobUiDelegate()
0080     : JobUiDelegate(Version::V2)
0081 {
0082 }
0083 #endif
0084 
0085 KIO::JobUiDelegate::~JobUiDelegate() = default;
0086 
0087 /*
0088   Returns the top most window associated with widget.
0089 
0090   Unlike QWidget::window(), this function does its best to find and return the
0091   main application window associated with the given widget.
0092 
0093   If widget itself is a dialog or its parent is a dialog, and that dialog has a
0094   parent widget then this function will iterate through all those widgets to
0095   find the top most window, which most of the time is the main window of the
0096   application. By contrast, QWidget::window() would simply return the first
0097   file dialog it encountered since it is the "next ancestor widget that has (or
0098   could have) a window-system frame".
0099 */
0100 static QWidget *topLevelWindow(QWidget *widget)
0101 {
0102     QWidget *w = widget;
0103     while (w && w->parentWidget()) {
0104         w = w->parentWidget();
0105     }
0106     return (w ? w->window() : nullptr);
0107 }
0108 
0109 class JobUiDelegateStatic : public QObject
0110 {
0111     Q_OBJECT
0112 public:
0113     void registerWindow(QWidget *wid)
0114     {
0115         if (!wid) {
0116             return;
0117         }
0118 
0119         QWidget *window = topLevelWindow(wid);
0120         QObject *obj = static_cast<QObject *>(window);
0121         if (!m_windowList.contains(obj)) {
0122             // We must store the window Id because by the time
0123             // the destroyed signal is emitted we can no longer
0124             // access QWidget::winId() (already destructed)
0125             WId windowId = window->winId();
0126             m_windowList.insert(obj, windowId);
0127             connect(window, &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow);
0128 #ifndef KIO_ANDROID_STUB
0129             QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5"))
0130                 .call(QDBus::NoBlock, QStringLiteral("registerWindowId"), qlonglong(windowId));
0131 #endif
0132         }
0133     }
0134 public Q_SLOTS:
0135     void slotUnregisterWindow(QObject *obj)
0136     {
0137         if (!obj) {
0138             return;
0139         }
0140 
0141         QMap<QObject *, WId>::Iterator it = m_windowList.find(obj);
0142         if (it == m_windowList.end()) {
0143             return;
0144         }
0145         WId windowId = it.value();
0146         disconnect(it.key(), &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow);
0147         m_windowList.erase(it);
0148 #ifndef KIO_ANDROID_STUB
0149         QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5"))
0150             .call(QDBus::NoBlock, QStringLiteral("unregisterWindowId"), qlonglong(windowId));
0151 #endif
0152     }
0153 
0154 private:
0155     QMap<QObject *, WId> m_windowList;
0156 };
0157 
0158 Q_GLOBAL_STATIC(JobUiDelegateStatic, s_static)
0159 
0160 #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 98)
0161 KIO::JobUiDelegate::JobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window)
0162     : JobUiDelegate(Version::V2, flags, window)
0163 {
0164     setWindow(window);
0165 }
0166 #endif
0167 
0168 void KIO::JobUiDelegate::setWindow(QWidget *window)
0169 {
0170     KDialogJobUiDelegate::setWindow(window);
0171 
0172     if (auto obj = qobject_cast<WidgetsUntrustedProgramHandler *>(d->m_openWithHandler)) {
0173         obj->setWindow(window);
0174     }
0175     if (auto obj = qobject_cast<WidgetsOpenWithHandler *>(d->m_untrustedProgramHandler)) {
0176         obj->setWindow(window);
0177     }
0178     if (auto obj = qobject_cast<WidgetsOpenOrExecuteFileHandler *>(d->m_openOrExecuteFileHandler)) {
0179         obj->setWindow(window);
0180     }
0181     if (auto obj = qobject_cast<WidgetsAskUserActionHandler *>(d->m_askUserActionHandler)) {
0182         obj->setWindow(window);
0183     }
0184 
0185     s_static()->registerWindow(window);
0186 }
0187 
0188 void KIO::JobUiDelegate::unregisterWindow(QWidget *window)
0189 {
0190     s_static()->slotUnregisterWindow(window);
0191 }
0192 
0193 KIO::RenameDialog_Result KIO::JobUiDelegate::askFileRename(KJob *job,
0194                                                            const QString &title,
0195                                                            const QUrl &src,
0196                                                            const QUrl &dest,
0197                                                            KIO::RenameDialog_Options options,
0198                                                            QString &newDest,
0199                                                            KIO::filesize_t sizeSrc,
0200                                                            KIO::filesize_t sizeDest,
0201                                                            const QDateTime &ctimeSrc,
0202                                                            const QDateTime &ctimeDest,
0203                                                            const QDateTime &mtimeSrc,
0204                                                            const QDateTime &mtimeDest)
0205 {
0206     // qDebug() << "job=" << job;
0207     // We now do it in process, so that opening the rename dialog
0208     // doesn't start uiserver for nothing if progressId=0 (e.g. F2 in konq)
0209     KIO::RenameDialog dlg(KJobWidgets::window(job), title, src, dest, options, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest);
0210     dlg.setWindowModality(Qt::WindowModal);
0211     connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976
0212     KIO::RenameDialog_Result res = static_cast<RenameDialog_Result>(dlg.exec());
0213     if (res == Result_AutoRename) {
0214         newDest = dlg.autoDestUrl().path();
0215     } else {
0216         newDest = dlg.newDestUrl().path();
0217     }
0218     return res;
0219 }
0220 
0221 KIO::SkipDialog_Result KIO::JobUiDelegate::askSkip(KJob *job, KIO::SkipDialog_Options options, const QString &error_text)
0222 {
0223     KIO::SkipDialog dlg(KJobWidgets::window(job), options, error_text);
0224     dlg.setWindowModality(Qt::WindowModal);
0225     connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976
0226     return static_cast<KIO::SkipDialog_Result>(dlg.exec());
0227 }
0228 
0229 bool KIO::JobUiDelegate::askDeleteConfirmation(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType)
0230 {
0231     QString keyName;
0232     bool ask = (confirmationType == ForceConfirmation);
0233     if (!ask) {
0234         KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
0235 
0236         // The default value for confirmations is true for delete and false
0237         // for trash. If you change this, please also update:
0238         //      dolphin/src/settings/general/confirmationssettingspage.cpp
0239         bool defaultValue = true;
0240 
0241         switch (deletionType) {
0242         case Delete:
0243             keyName = QStringLiteral("ConfirmDelete");
0244             break;
0245         case Trash:
0246             keyName = QStringLiteral("ConfirmTrash");
0247             defaultValue = false;
0248             break;
0249         case EmptyTrash:
0250             keyName = QStringLiteral("ConfirmEmptyTrash");
0251             break;
0252         }
0253 
0254         ask = kioConfig->group("Confirmations").readEntry(keyName, defaultValue);
0255     }
0256     if (ask) {
0257         QStringList prettyList;
0258         prettyList.reserve(urls.size());
0259         for (const QUrl &url : urls) {
0260             if (url.scheme() == QLatin1String("trash")) {
0261                 QString path = url.path();
0262                 // HACK (#98983): remove "0-foo". Note that it works better than
0263                 // displaying KFileItem::name(), for files under a subdir.
0264                 path.remove(QRegularExpression(QStringLiteral("^/[0-9]*-")));
0265                 prettyList.append(path);
0266             } else {
0267                 prettyList.append(url.toDisplayString(QUrl::PreferLocalFile));
0268             }
0269         }
0270 
0271         int result;
0272         QWidget *widget = window();
0273         const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal);
0274         switch (deletionType) {
0275         case Delete:
0276             if (prettyList.count() == 1) {
0277                 result = KMessageBox::warningContinueCancel(
0278                     widget,
0279                     xi18nc("@info",
0280                            "Do you really want to permanently delete this item?<nl/><filename>%1</filename><nl/><nl/><emphasis strong='true'>This action "
0281                            "cannot be undone.</emphasis>",
0282                            prettyList.first()),
0283                     i18n("Delete Permanently"),
0284                     KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
0285                     KStandardGuiItem::cancel(),
0286                     keyName,
0287                     options);
0288             } else {
0289                 result = KMessageBox::warningContinueCancelList(
0290                     widget,
0291                     xi18ncp(
0292                         "@info",
0293                         "Do you really want to permanently delete this item?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
0294                         "Do you really want to permanently delete these %1 items?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
0295                         prettyList.count()),
0296                     prettyList,
0297                     i18n("Delete Permanently"),
0298                     KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
0299                     KStandardGuiItem::cancel(),
0300                     keyName,
0301                     options);
0302             }
0303             break;
0304         case EmptyTrash:
0305             result = KMessageBox::warningContinueCancel(
0306                 widget,
0307                 xi18nc("@info",
0308                        "Do you want to permanently delete all items from the Trash?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>"),
0309                 i18n("Delete Permanently"),
0310                 KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("user-trash"))),
0311                 KStandardGuiItem::cancel(),
0312                 keyName,
0313                 options);
0314             break;
0315         case Trash:
0316         default:
0317             if (prettyList.count() == 1) {
0318                 result = KMessageBox::warningContinueCancel(
0319                     widget,
0320                     xi18nc("@info", "Do you really want to move this item to the Trash?<nl/><filename>%1</filename>", prettyList.first()),
0321                     i18n("Move to Trash"),
0322                     KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
0323                     KStandardGuiItem::cancel(),
0324                     keyName,
0325                     options);
0326             } else {
0327                 result = KMessageBox::warningContinueCancelList(
0328                     widget,
0329                     i18np("Do you really want to move this item to the Trash?", "Do you really want to move these %1 items to the Trash?", prettyList.count()),
0330                     prettyList,
0331                     i18n("Move to Trash"),
0332                     KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
0333                     KStandardGuiItem::cancel(),
0334                     keyName,
0335                     options);
0336             }
0337         }
0338         if (!keyName.isEmpty()) {
0339             // Check kmessagebox setting... erase & copy to konquerorrc.
0340             KSharedConfig::Ptr config = KSharedConfig::openConfig();
0341             KConfigGroup notificationGroup(config, "Notification Messages");
0342             if (!notificationGroup.readEntry(keyName, true)) {
0343                 notificationGroup.writeEntry(keyName, true);
0344                 notificationGroup.sync();
0345 
0346                 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
0347                 kioConfig->group("Confirmations").writeEntry(keyName, false);
0348             }
0349         }
0350         return (result == KMessageBox::Continue);
0351     }
0352     return true;
0353 }
0354 
0355 int KIO::JobUiDelegate::requestMessageBox(KIO::JobUiDelegate::MessageBoxType type,
0356                                           const QString &text,
0357                                           const QString &title,
0358                                           const QString &primaryActionText,
0359                                           const QString &secondaryActionText,
0360                                           const QString &primaryActionIconName,
0361                                           const QString &secondaryActionIconName,
0362                                           const QString &dontAskAgainName,
0363                                           const KIO::MetaData &metaData)
0364 {
0365     int result = -1;
0366 
0367     // qDebug() << type << text << "title=" << title;
0368 
0369     KConfig config(QStringLiteral("kioslaverc"));
0370     KMessageBox::setDontShowAgainConfig(&config);
0371 
0372 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 100)
0373     QT_WARNING_PUSH
0374     QT_WARNING_DISABLE_DEPRECATED
0375     KGuiItem primaryActionTextGui = KStandardGuiItem::yes();
0376     KGuiItem secondaryActionTextGui = KStandardGuiItem::no();
0377     QT_WARNING_POP
0378 
0379     if (!primaryActionText.isEmpty()) {
0380         primaryActionTextGui.setText(primaryActionText);
0381     }
0382     if (!primaryActionIconName.isNull()) {
0383         primaryActionTextGui.setIconName(primaryActionIconName);
0384     }
0385 
0386     if (!secondaryActionText.isEmpty()) {
0387         secondaryActionTextGui.setText(secondaryActionText);
0388     }
0389     if (!secondaryActionIconName.isNull()) {
0390         secondaryActionTextGui.setIconName(secondaryActionIconName);
0391     }
0392 #else
0393     KGuiItem primaryActionTextGui(primaryActionText, primaryActionIconName);
0394     KGuiItem secondaryActionTextGui(secondaryActionText, secondaryActionIconName);
0395 #endif
0396 
0397     KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal);
0398 
0399     switch (type) {
0400     case QuestionTwoActions:
0401         result = KMessageBox::questionTwoActions(window(), text, title, primaryActionTextGui, secondaryActionTextGui, dontAskAgainName, options);
0402         break;
0403     case WarningTwoActions:
0404         result = KMessageBox::warningTwoActions(window(),
0405                                                 text,
0406                                                 title,
0407                                                 primaryActionTextGui,
0408                                                 secondaryActionTextGui,
0409                                                 dontAskAgainName,
0410                                                 options | KMessageBox::Dangerous);
0411         break;
0412     case WarningTwoActionsCancel:
0413         result = KMessageBox::warningTwoActionsCancel(window(),
0414                                                       text,
0415                                                       title,
0416                                                       primaryActionTextGui,
0417                                                       secondaryActionTextGui,
0418                                                       KStandardGuiItem::cancel(),
0419                                                       dontAskAgainName,
0420                                                       options);
0421         break;
0422     case WarningContinueCancel:
0423         result = KMessageBox::warningContinueCancel(window(), text, title, primaryActionTextGui, KStandardGuiItem::cancel(), dontAskAgainName, options);
0424         break;
0425     case Information:
0426         KMessageBox::information(window(), text, title, dontAskAgainName, options);
0427         result = 1; // whatever
0428         break;
0429     case SSLMessageBox: {
0430         QPointer<KSslInfoDialog> kid(new KSslInfoDialog(window()));
0431         // ### this is boilerplate code and appears in khtml_part.cpp almost unchanged!
0432         const QStringList sl = metaData.value(QStringLiteral("ssl_peer_chain")).split(QLatin1Char('\x01'), Qt::SkipEmptyParts);
0433         QList<QSslCertificate> certChain;
0434         bool decodedOk = true;
0435         for (const QString &s : sl) {
0436             certChain.append(QSslCertificate(s.toLatin1())); // or is it toLocal8Bit or whatever?
0437             if (certChain.last().isNull()) {
0438                 decodedOk = false;
0439                 break;
0440             }
0441         }
0442 
0443         if (decodedOk) {
0444             result = 1; // whatever
0445             kid->setSslInfo(certChain,
0446                             metaData.value(QStringLiteral("ssl_peer_ip")),
0447                             text, // the URL
0448                             metaData.value(QStringLiteral("ssl_protocol_version")),
0449                             metaData.value(QStringLiteral("ssl_cipher")),
0450                             metaData.value(QStringLiteral("ssl_cipher_used_bits")).toInt(),
0451                             metaData.value(QStringLiteral("ssl_cipher_bits")).toInt(),
0452                             KSslInfoDialog::certificateErrorsFromString(metaData.value(QStringLiteral("ssl_cert_errors"))));
0453             kid->exec();
0454         } else {
0455             result = -1;
0456             KMessageBox::information(window(), i18n("The peer SSL certificate chain appears to be corrupt."), i18n("SSL"), QString(), options);
0457         }
0458         // KSslInfoDialog deletes itself (Qt::WA_DeleteOnClose).
0459         delete kid;
0460         break;
0461     }
0462     case WarningContinueCancelDetailed: {
0463         const QString details = metaData.value(QStringLiteral("privilege_conf_details"));
0464         result = KMessageBox::warningContinueCancelDetailed(window(),
0465                                                             text,
0466                                                             title,
0467                                                             KStandardGuiItem::cont(),
0468                                                             KStandardGuiItem::cancel(),
0469                                                             dontAskAgainName,
0470                                                             options | KMessageBox::Dangerous,
0471                                                             details);
0472         break;
0473     }
0474     default:
0475         qCWarning(KIO_WIDGETS) << "Unknown type" << type;
0476         result = 0;
0477         break;
0478     }
0479     KMessageBox::setDontShowAgainConfig(nullptr);
0480     return result;
0481 }
0482 
0483 KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode)
0484 {
0485     if (qobject_cast<QGuiApplication *>(qApp)) {
0486         return new KIO::ClipboardUpdater(job, mode);
0487     }
0488     return nullptr;
0489 }
0490 
0491 void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest)
0492 {
0493     if (qobject_cast<QGuiApplication *>(qApp)) {
0494         KIO::ClipboardUpdater::update(src, dest);
0495     }
0496 }
0497 
0498 KIO::JobUiDelegate::JobUiDelegate(Version version, KJobUiDelegate::Flags /*flags*/, QWidget *window, const QList<QObject *> &ifaces)
0499     : d(new JobUiDelegatePrivate(this, ifaces))
0500 {
0501     // TODO KF6: drop the version argument and replace the deprecated constructor
0502     // TODO KF6: change the API to accept QWindows rather than QWidgets (this also carries through to the Interfaces)
0503     if (window) {
0504         s_static()->registerWindow(window);
0505         setWindow(window);
0506     }
0507 
0508     Q_UNUSED(version); // only serves to disambiguate constructors
0509 }
0510 
0511 class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactoryV2
0512 {
0513 public:
0514     using KIO::JobUiDelegateFactoryV2::JobUiDelegateFactoryV2;
0515 
0516     KJobUiDelegate *createDelegate() const override
0517     {
0518         return new KIO::JobUiDelegate(KIO::JobUiDelegate::Version::V2);
0519     }
0520 
0521     KJobUiDelegate *createDelegate(KJobUiDelegate::Flags flags, QWidget *window) const override
0522     {
0523         return new KIO::JobUiDelegate(KIO::JobUiDelegate::Version::V2, flags, window);
0524     }
0525 
0526     static void registerJobUiDelegate()
0527     {
0528         static KIOWidgetJobUiDelegateFactory factory;
0529         KIO::setDefaultJobUiDelegateFactoryV2(&factory);
0530 
0531         static KIO::JobUiDelegate delegate(KIO::JobUiDelegate::Version::V2);
0532         KIO::setDefaultJobUiDelegateExtension(&delegate);
0533     }
0534 };
0535 
0536 // Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs
0537 static void registerJobUiDelegate()
0538 {
0539     // Inside the factory class so it is a friend of the delegate and can construct it.
0540     KIOWidgetJobUiDelegateFactory::registerJobUiDelegate();
0541 }
0542 
0543 Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate)
0544 
0545 #include "jobuidelegate.moc"
0546 #include "moc_jobuidelegate.cpp"