1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 #include "widgetsaskuseractionhandler.h"
9 
10 #include <KConfig>
11 #include <KConfigGroup>
12 #include <KGuiItem>
13 #include <KIO/SlaveBase>
14 #include <KJob>
15 #include <KJobWidgets>
16 #include <KLocalizedString>
17 #include <KMessageDialog>
18 #include <KSharedConfig>
19 #include <KSslInfoDialog>
20 #include <KStandardGuiItem>
21 #include <kio_widgets_debug.h>
22 
23 #include <QDialogButtonBox>
24 #include <QRegularExpression>
25 #include <QUrl>
26 
27 class KIO::WidgetsAskUserActionHandlerPrivate
28 {
29 public:
WidgetsAskUserActionHandlerPrivate(WidgetsAskUserActionHandler * qq)30     WidgetsAskUserActionHandlerPrivate(WidgetsAskUserActionHandler *qq)
31         : q(qq)
32     {
33     }
34 
35     // Creates a KSslInfoDialog or falls back to a generic Information dialog
36     void sslMessageBox(const QString &text, const KIO::MetaData &metaData, QWidget *parent);
37 
38     WidgetsAskUserActionHandler *const q;
39 };
40 
WidgetsAskUserActionHandler(QObject * parent)41 KIO::WidgetsAskUserActionHandler::WidgetsAskUserActionHandler(QObject *parent)
42     : KIO::AskUserActionInterface(parent)
43     , d(new WidgetsAskUserActionHandlerPrivate(this))
44 {
45 }
46 
~WidgetsAskUserActionHandler()47 KIO::WidgetsAskUserActionHandler::~WidgetsAskUserActionHandler()
48 {
49 }
50 
askUserRename(KJob * job,const QString & caption,const QUrl & src,const QUrl & dest,KIO::RenameDialog_Options options,KIO::filesize_t sizeSrc,KIO::filesize_t sizeDest,const QDateTime & ctimeSrc,const QDateTime & ctimeDest,const QDateTime & mtimeSrc,const QDateTime & mtimeDest)51 void KIO::WidgetsAskUserActionHandler::askUserRename(KJob *job,
52                                                      const QString &caption,
53                                                      const QUrl &src,
54                                                      const QUrl &dest,
55                                                      KIO::RenameDialog_Options options,
56                                                      KIO::filesize_t sizeSrc,
57                                                      KIO::filesize_t sizeDest,
58                                                      const QDateTime &ctimeSrc,
59                                                      const QDateTime &ctimeDest,
60                                                      const QDateTime &mtimeSrc,
61                                                      const QDateTime &mtimeDest)
62 {
63     auto *dlg = new KIO::RenameDialog(KJobWidgets::window(job), caption, src, dest, options, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest);
64 
65     dlg->setAttribute(Qt::WA_DeleteOnClose);
66     dlg->setWindowModality(Qt::WindowModal);
67 
68     connect(job, &KJob::finished, dlg, &QDialog::reject);
69     connect(dlg, &QDialog::finished, this, [this, job, dlg](const int exitCode) {
70         KIO::RenameDialog_Result result = static_cast<RenameDialog_Result>(exitCode);
71         const QUrl newUrl = result == Result_AutoRename ? dlg->autoDestUrl() : dlg->newDestUrl();
72         Q_EMIT askUserRenameResult(result, newUrl, job);
73     });
74 
75     dlg->show();
76 }
77 
askUserSkip(KJob * job,KIO::SkipDialog_Options options,const QString & errorText)78 void KIO::WidgetsAskUserActionHandler::askUserSkip(KJob *job, KIO::SkipDialog_Options options, const QString &errorText)
79 {
80     auto *dlg = new KIO::SkipDialog(KJobWidgets::window(job), options, errorText);
81     dlg->setAttribute(Qt::WA_DeleteOnClose);
82     dlg->setWindowModality(Qt::WindowModal);
83 
84     connect(job, &KJob::finished, dlg, &QDialog::reject);
85     connect(dlg, &QDialog::finished, this, [this, job](const int exitCode) {
86         Q_EMIT askUserSkipResult(static_cast<KIO::SkipDialog_Result>(exitCode), job);
87     });
88 
89     dlg->show();
90 }
91 
askUserDelete(const QList<QUrl> & urls,DeletionType deletionType,ConfirmationType confirmationType,QWidget * parent)92 void KIO::WidgetsAskUserActionHandler::askUserDelete(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType, QWidget *parent)
93 {
94     KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
95 
96     QString keyName;
97     bool ask = (confirmationType == ForceConfirmation);
98     if (!ask) {
99         // The default value for confirmations is true for delete and false
100         // for trash. If you change this, please also update:
101         //      dolphin/src/settings/general/confirmationssettingspage.cpp
102         bool defaultValue = true;
103 
104         switch (deletionType) {
105         case Delete:
106             keyName = QStringLiteral("ConfirmDelete");
107             break;
108         case Trash:
109             keyName = QStringLiteral("ConfirmTrash");
110             defaultValue = false;
111             break;
112         case EmptyTrash:
113             keyName = QStringLiteral("ConfirmEmptyTrash");
114             break;
115         }
116 
117         ask = kioConfig->group("Confirmations").readEntry(keyName, defaultValue);
118     }
119 
120     if (!ask) {
121         Q_EMIT askUserDeleteResult(true, urls, deletionType, parent);
122         return;
123     }
124 
125     QStringList prettyList;
126     prettyList.reserve(urls.size());
127     for (const QUrl &url : urls) {
128         if (url.scheme() == QLatin1String("trash")) {
129             QString path = url.path();
130             // HACK (#98983): remove "0-foo". Note that it works better than
131             // displaying KFileItem::name(), for files under a subdir.
132             path.remove(QRegularExpression(QStringLiteral("^/[0-9]+-")));
133             prettyList.append(path);
134         } else {
135             prettyList.append(url.toDisplayString(QUrl::PreferLocalFile));
136         }
137     }
138 
139     const int urlCount = prettyList.count();
140 
141     KGuiItem acceptButton;
142     QString text;
143     QString caption = i18n("Delete Permanently");
144 
145     switch (deletionType) {
146     case Delete: {
147         text = xi18ncp("@info",
148                        "Do you really want to permanently delete this %1 item?<nl/><nl/>"
149                        "<emphasis strong='true'>This action cannot be undone.</emphasis>",
150                        "Do you really want to permanently delete these %1 items?<nl/><nl/>"
151                        "<emphasis strong='true'>This action cannot be undone.</emphasis>",
152                        urlCount);
153         acceptButton = KStandardGuiItem::del();
154         break;
155     }
156     case EmptyTrash: {
157         text = xi18nc("@info",
158                       "Do you want to permanently delete all items from the Trash?<nl/><nl/>"
159                       "<emphasis strong='true'>This action cannot be undone.</emphasis>");
160         acceptButton = KGuiItem(i18nc("@action:button", "Empty Trash"), QStringLiteral("user-trash"));
161         break;
162     }
163     case Trash: {
164         if (urlCount == 1) {
165             text = xi18nc("@info",
166                           "Do you really want to move this item to the Trash?<nl/>"
167                           "<filename>%1</filename>",
168                           prettyList.at(0));
169         } else {
170             text =
171                 xi18ncp("@info", "Do you really want to move this %1 item to the Trash?", "Do you really want to move these %1 items to the Trash?", urlCount);
172         }
173         caption = i18n("Move to Trash");
174         acceptButton = KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash"));
175         break;
176     }
177     default:
178         break;
179     }
180 
181     KMessageDialog *dlg = new KMessageDialog(KMessageDialog::QuestionYesNo, text, parent);
182 
183     dlg->setAttribute(Qt::WA_DeleteOnClose);
184     dlg->setCaption(caption);
185     dlg->setIcon(QIcon{});
186     dlg->setButtons(acceptButton, KStandardGuiItem::cancel());
187     if (urlCount > 1) {
188         dlg->setListWidgetItems(prettyList);
189     }
190     dlg->setDontAskAgainText(i18nc("@option:checkbox", "Do not ask again"));
191     // If we get here, !ask must be false
192     dlg->setDontAskAgainChecked(!ask);
193 
194     connect(dlg, &QDialog::finished, this, [=](const int buttonCode) {
195         const bool isDelete = buttonCode == QDialogButtonBox::Yes;
196 
197         Q_EMIT askUserDeleteResult(isDelete, urls, deletionType, parent);
198 
199         if (isDelete) {
200             KConfigGroup cg = kioConfig->group("Confirmations");
201             cg.writeEntry(keyName, !dlg->isDontAskAgainChecked());
202             cg.sync();
203         }
204     });
205 
206     dlg->setWindowModality(Qt::WindowModal);
207     dlg->show();
208 }
209 
requestUserMessageBox(MessageDialogType type,const QString & text,const QString & caption,const QString & buttonYes,const QString & buttonNo,const QString & iconYes,const QString & iconNo,const QString & dontAskAgainName,const QString & details,const KIO::MetaData & metaData,QWidget * parent)210 void KIO::WidgetsAskUserActionHandler::requestUserMessageBox(MessageDialogType type,
211                                                              const QString &text,
212                                                              const QString &caption,
213                                                              const QString &buttonYes,
214                                                              const QString &buttonNo,
215                                                              const QString &iconYes,
216                                                              const QString &iconNo,
217                                                              const QString &dontAskAgainName,
218                                                              const QString &details,
219                                                              const KIO::MetaData &metaData,
220                                                              QWidget *parent)
221 {
222     KSharedConfigPtr reqMsgConfig = KSharedConfig::openConfig(QStringLiteral("kioslaverc"));
223     const bool ask = reqMsgConfig->group("Notification Messages").readEntry(dontAskAgainName, true);
224 
225     if (!ask) {
226         Q_EMIT messageBoxResult(type == WarningContinueCancel ? KIO::SlaveBase::Continue : KIO::SlaveBase::Yes);
227         return;
228     }
229 
230     auto acceptButton = KGuiItem(buttonYes, iconYes);
231     auto rejectButton = KGuiItem(buttonNo, iconNo);
232 
233     // It's "Do not ask again" every where except with Information
234     QString dontAskAgainText = i18nc("@option:check", "Do not ask again");
235 
236     KMessageDialog::Type dlgType;
237 
238     switch (type) {
239     case QuestionYesNo:
240         dlgType = KMessageDialog::QuestionYesNo;
241         break;
242     case QuestionYesNoCancel:
243         dlgType = KMessageDialog::QuestionYesNoCancel;
244         break;
245     case WarningYesNo:
246         dlgType = KMessageDialog::WarningYesNo;
247         break;
248     case WarningYesNoCancel:
249         dlgType = KMessageDialog::WarningYesNoCancel;
250         break;
251     case WarningContinueCancel:
252         dlgType = KMessageDialog::WarningContinueCancel;
253         break;
254     case SSLMessageBox:
255         d->sslMessageBox(text, metaData, parent);
256         return;
257     case Information:
258         dlgType = KMessageDialog::Information;
259         dontAskAgainText = i18nc("@option:check", "Do not show this message again");
260         break;
261     case Sorry:
262         dlgType = KMessageDialog::Sorry;
263         dontAskAgainText = QString{}; // No dontAskAgain checkbox
264         break;
265     case Error:
266         dlgType = KMessageDialog::Error;
267         dontAskAgainText = QString{}; // No dontAskAgain checkbox
268         break;
269     default:
270         qCWarning(KIO_WIDGETS) << "Unknown message dialog type" << type;
271         return;
272     }
273 
274     auto *dialog = new KMessageDialog(dlgType, text, parent);
275 
276     dialog->setAttribute(Qt::WA_DeleteOnClose);
277     dialog->setCaption(caption);
278     dialog->setIcon(QIcon{});
279     // If a button has empty text, KMessageDialog will replace that button
280     // with a suitable KStandardGuiItem
281     dialog->setButtons(acceptButton, rejectButton);
282     dialog->setDetails(details);
283     dialog->setDontAskAgainText(dontAskAgainText);
284     dialog->setDontAskAgainChecked(!ask);
285     dialog->setOpenExternalLinks(true); // Allow opening external links in the text labels
286 
287     connect(dialog, &QDialog::finished, this, [=](const int result) {
288         KIO::SlaveBase::ButtonCode btnCode;
289         switch (result) {
290         case QDialogButtonBox::Yes:
291             if (dlgType == KMessageDialog::WarningContinueCancel) {
292                 btnCode = KIO::SlaveBase::Continue;
293             } else {
294                 btnCode = KIO::SlaveBase::Yes;
295             }
296             break;
297         case QDialogButtonBox::No:
298             btnCode = KIO::SlaveBase::No;
299             break;
300         case QDialogButtonBox::Cancel:
301             btnCode = KIO::SlaveBase::Cancel;
302             break;
303         case QDialogButtonBox::Ok:
304             btnCode = KIO::SlaveBase::Ok;
305             break;
306         default:
307             qCWarning(KIO_WIDGETS) << "Unknown message dialog result" << result;
308             return;
309         }
310 
311         Q_EMIT messageBoxResult(btnCode);
312 
313         if (result != QDialogButtonBox::Cancel) {
314             KConfigGroup cg = reqMsgConfig->group("Notification Messages");
315             cg.writeEntry(dontAskAgainName, !dialog->isDontAskAgainChecked());
316             cg.sync();
317         }
318     });
319 
320     dialog->show();
321 }
322 
sslMessageBox(const QString & text,const KIO::MetaData & metaData,QWidget * parent)323 void KIO::WidgetsAskUserActionHandlerPrivate::sslMessageBox(const QString &text, const KIO::MetaData &metaData, QWidget *parent)
324 {
325     const QStringList sslList = metaData.value(QStringLiteral("ssl_peer_chain")).split(QLatin1Char('\x01'), Qt::SkipEmptyParts);
326 
327     QList<QSslCertificate> certChain;
328     bool decodedOk = true;
329     for (const QString &str : sslList) {
330         certChain.append(QSslCertificate(str.toUtf8()));
331         if (certChain.last().isNull()) {
332             decodedOk = false;
333             break;
334         }
335     }
336 
337     if (decodedOk) { // Use KSslInfoDialog
338         KSslInfoDialog *ksslDlg = new KSslInfoDialog(parent);
339         ksslDlg->setSslInfo(certChain,
340                             metaData.value(QStringLiteral("ssl_peer_ip")),
341                             text, // The URL
342                             metaData.value(QStringLiteral("ssl_protocol_version")),
343                             metaData.value(QStringLiteral("ssl_cipher")),
344                             metaData.value(QStringLiteral("ssl_cipher_used_bits")).toInt(),
345                             metaData.value(QStringLiteral("ssl_cipher_bits")).toInt(),
346                             KSslInfoDialog::certificateErrorsFromString(metaData.value(QStringLiteral("ssl_cert_errors"))));
347 
348         // KSslInfoDialog deletes itself by setting Qt::WA_DeleteOnClose
349 
350         QObject::connect(ksslDlg, &QDialog::finished, q, [this]() {
351             // KSslInfoDialog only has one button, QDialogButtonBox::Close
352             Q_EMIT q->messageBoxResult(KIO::SlaveBase::Cancel);
353         });
354 
355         ksslDlg->show();
356         return;
357     }
358 
359     // Fallback to a generic message box
360     auto *dialog = new KMessageDialog(KMessageDialog::Information, i18n("The peer SSL certificate chain appears to be corrupt."), parent);
361 
362     dialog->setAttribute(Qt::WA_DeleteOnClose);
363     dialog->setCaption(i18n("SSL"));
364     // KMessageDialog will set a proper icon
365     dialog->setIcon(QIcon{});
366     dialog->setButtons(KStandardGuiItem::ok());
367 
368     QObject::connect(dialog, &QDialog::finished, q, [this](const int result) {
369         Q_EMIT q->messageBoxResult(result == QDialogButtonBox::Ok ? KIO::SlaveBase::Ok : KIO::SlaveBase::Cancel);
370     });
371 
372     dialog->show();
373 }
374