File indexing completed on 2024-04-28 08:50:50

0001 /*
0002     This file is part of the KDE project.
0003 
0004     SPDX-FileCopyrightText: 2020 Stefano Crocco <stefano.crocco@alice.it>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "urlloader.h"
0010 #include "konqsettings.h"
0011 #include "konqmainwindow.h"
0012 #include "konqview.h"
0013 #include "konqurl.h"
0014 #include "konqdebug.h"
0015 
0016 #include "libkonq_utils.h"
0017 
0018 #include "interfaces/downloaderextension.h"
0019 
0020 #include <KIO/OpenUrlJob>
0021 #include <KIO/JobUiDelegate>
0022 #include <KIO/FileCopyJob>
0023 #include <KIO/MimeTypeFinderJob>
0024 #include <KIO/JobUiDelegateFactory>
0025 #include <KIO/CopyJob>
0026 #include <KIO/JobTracker>
0027 #include <KMessageBox>
0028 #include <KParts/ReadOnlyPart>
0029 #include "kf5compat.h" //For NavigationExtension
0030 #include <KParts/PartLoader>
0031 #include <KJobWidgets>
0032 #include <KProtocolManager>
0033 #include <KDesktopFile>
0034 #include <KApplicationTrader>
0035 #include <KParts/PartLoader>
0036 #include <KLocalizedString>
0037 #include <KIO/JobUiDelegateFactory>
0038 #include <KJobTrackerInterface>
0039 
0040 #include <QDebug>
0041 #include <QArgument>
0042 #include <QWebEngineProfile>
0043 #include <QMimeDatabase>
0044 #include <QWebEngineProfile>
0045 #include <QFileDialog>
0046 #include <QFileInfo>
0047 #include <QLoggingCategory>
0048 
0049 using namespace KonqInterfaces;
0050 
0051 bool UrlLoader::embedWithoutAskingToSave(const QString &mimeType)
0052 {
0053     static QStringList s_mimeTypes;
0054     if (s_mimeTypes.isEmpty()) {
0055         QStringList names{QStringLiteral("kfmclient_html"), QStringLiteral("kfmclient_dir"), QStringLiteral("kfmclient_war")};
0056         for (const QString &name : names) {
0057             KService::Ptr s = KService::serviceByStorageId(name);
0058             if (s) {
0059                 s_mimeTypes.append(s->mimeTypes());
0060             } else {
0061                 qCDebug(KONQUEROR_LOG()) << "Couldn't find service" << name;
0062             }
0063         }
0064         //The user may want to save xml files rather than embedding them
0065         //TODO: is there a better way to do this?
0066         s_mimeTypes.removeOne(QStringLiteral("application/xml"));
0067     }
0068     return s_mimeTypes.contains(mimeType);
0069 }
0070 
0071 UrlLoader::UrlLoader(KonqMainWindow *mainWindow, KonqView *view, const QUrl &url, const QString &mimeType, const KonqOpenURLRequest &req, bool trustedSource, bool dontEmbed):
0072     QObject(mainWindow),
0073     m_mainWindow(mainWindow),
0074     m_url(url),
0075     m_mimeType(mimeType),
0076     m_request(req),
0077     m_view(view),
0078     m_trustedSource(trustedSource),
0079     m_dontEmbed(dontEmbed),
0080     m_protocolAllowsReading(KProtocolManager::supportsReading(m_url) || !KProtocolInfo::isKnownProtocol(m_url)), // If the protocol is unknown, assume it allows reading
0081     m_letRequestingPartDownloadUrl(req.letPartPerformDownload)
0082 {
0083     //TODO KF6: currently there's no way to pass the suggested file name. When dropping compatibility with KF5, have a field for this
0084     //in BrowserArguments
0085     m_request.suggestedFileName = m_request.args.metaData().value(QStringLiteral("SuggestedFileName"));
0086 
0087     //TODO KF6 after implementing a better way to allow the user to display a file after saving it locally
0088     //(see comment for WebEnginePage::saveUrlToDiskAndDisplay in webenginepage.cpp), remove all references
0089     //to embedOrNothing
0090     m_embedOrNothing = m_request.args.metaData().contains(QStringLiteral("EmbedOrNothing"));
0091 
0092     getDownloaderJobFromPart();
0093     if (m_letRequestingPartDownloadUrl) {
0094         m_originalUrl = m_url;
0095     }
0096     determineStartingMimetype();
0097     m_dontPassToWebEnginePart = m_request.args.metaData().contains("DontSendToDefaultHTMLPart");
0098 
0099     //Obviously this shoud never happen
0100     if (m_dontEmbed && m_embedOrNothing) {
0101         qCDebug(KONQUEROR_LOG()) << "Conflicting requests for loading" << m_url << ": to never embed and to only embed. Nothing will be done";
0102         m_action = OpenUrlAction::DoNothing;
0103     }
0104 }
0105 
0106 UrlLoader::~UrlLoader()
0107 {
0108 }
0109 
0110 void UrlLoader::determineStartingMimetype()
0111 {
0112     QMimeDatabase db;
0113     if (!m_mimeType.isEmpty()) {
0114         if (m_letRequestingPartDownloadUrl || !db.mimeTypeForName(m_mimeType).isDefault()) {
0115             return;
0116         }
0117     }
0118 
0119     m_mimeType = m_request.args.mimeType();
0120     //In theory, this should never happen, as parts requesting to download the URL
0121     //by themselves should already have set the mimetype. However, let's check, just in case
0122     if (m_letRequestingPartDownloadUrl && m_mimeType.isEmpty()) {
0123         m_mimeType = QStringLiteral("application/octet-stream");
0124     } else if (db.mimeTypeForName(m_mimeType).isDefault()) {
0125         m_mimeType.clear();
0126     }
0127 }
0128 
0129 QString UrlLoader::mimeType() const
0130 {
0131     return m_mimeType;
0132 }
0133 
0134 bool UrlLoader::isMimeTypeKnown(const QString &mimeType)
0135 {
0136     return !mimeType.isEmpty();
0137 }
0138 
0139 void UrlLoader::setView(KonqView* view)
0140 {
0141     m_view = view;
0142 }
0143 
0144 void UrlLoader::setOldLocationBarUrl(const QString& old)
0145 {
0146     m_oldLocationBarUrl = old;
0147 }
0148 
0149 void UrlLoader::setNewTab(bool newTab)
0150 {
0151     m_request.browserArgs.setNewTab(newTab);
0152 }
0153 
0154 void UrlLoader::start()
0155 {
0156     if (m_url.isLocalFile()) {
0157         detectSettingsForLocalFiles();
0158     } else {
0159         detectSettingsForRemoteFiles();
0160     }
0161 
0162     if (hasError()) {
0163         m_mimeType = QStringLiteral("text/html");
0164     }
0165     if (isMimeTypeKnown(m_mimeType)) {
0166         KService::Ptr preferredService = KApplicationTrader::preferredService(m_mimeType);
0167         if (serviceIsKonqueror(preferredService)) {
0168             m_request.forceAutoEmbed = true;
0169         }
0170     }
0171 
0172     m_isAsync = m_protocolAllowsReading && (!isMimeTypeKnown(m_mimeType) || m_letRequestingPartDownloadUrl);
0173 }
0174 
0175 bool UrlLoader::isViewLocked() const
0176 {
0177     return m_view && m_view->isLockedLocation();
0178 }
0179 
0180 void UrlLoader::decideAction()
0181 {
0182     if (hasError()) {
0183         m_action = OpenUrlAction::Embed;
0184         return;
0185     }
0186     if (!m_embedOrNothing) {
0187         m_action = decideExecute();
0188     }
0189     switch (m_action) {
0190         case OpenUrlAction::Execute:
0191             m_ready = true;
0192             break;
0193         case OpenUrlAction::DoNothing:
0194             m_ready = true;
0195             return;
0196         default:
0197             if (!isMimeTypeKnown(m_mimeType) && !m_protocolAllowsReading && !m_embedOrNothing) {
0198                 //If the protocol doesn't allow reading and we don't have a mimetype associated with it,
0199                 //use the Open action, as we most likely won't be able to find out the mimetype. This is
0200                 //what happens, for example, for mailto URLs
0201                 m_action = OpenUrlAction::Open;
0202                 return;
0203             } else if (isViewLocked() || shouldEmbedThis() || m_embedOrNothing) {
0204                 bool success = decideEmbedOrSave();
0205                 if (success || m_embedOrNothing) {
0206                     if (m_embedOrNothing && m_action != OpenUrlAction::Embed) {
0207                         m_action = OpenUrlAction::DoNothing;
0208                     }
0209                     return;
0210                 }
0211             }
0212             decideOpenOrSave();
0213     }
0214 }
0215 
0216 void UrlLoader::abort()
0217 {
0218     if (m_openUrlJob) {
0219         m_openUrlJob->kill();
0220     }
0221     if (m_applicationLauncherJob) {
0222         m_applicationLauncherJob->kill();
0223     }
0224     deleteLater();
0225 }
0226 
0227 
0228 void UrlLoader::goOn()
0229 {
0230     if (m_isAsync && !isMimeTypeKnown(m_mimeType)) {
0231         launchMimeTypeFinderJob();
0232     } else {
0233         decideAction();
0234         m_ready = !m_letRequestingPartDownloadUrl;
0235         performAction();
0236     }
0237 }
0238 
0239 KPluginMetaData UrlLoader::findEmbeddingPart(bool forceServiceName) const
0240 {
0241     const QLatin1String webEngineName("webenginepart");
0242 
0243     //Use WebEnginePart for konq: URLs even if it's not the default html engine
0244     if (KonqUrl::hasKonqScheme(m_url)) {
0245         return findPartById(webEngineName);
0246     }
0247 
0248     KPluginMetaData part;
0249 
0250     //Check whether the view can display the mimetype, but only if the URL hasn't been explicitly
0251     //typed by the user: in this case, use the preferred service. This is needed to avoid the situation
0252     //where m_view is a Kate part, the user enters the URL of a web page and the page is opened within
0253     //the Kate part because it can handle html files.
0254     if (m_view && m_request.typedUrl.isEmpty() && m_view->supportsMimeType(m_mimeType)) {
0255         part = m_view->service();
0256     } else if (!m_request.serviceName.isEmpty()) {
0257         // If the service name has been set by the "--part" command line argument
0258         // (detected in handleCommandLine() in konqmain.cpp), then use it as is.
0259         part = findPartById(m_request.serviceName);
0260         if (!forceServiceName && !part.supportsMimeType(m_mimeType)) {
0261             part = KPluginMetaData();
0262         }
0263     }
0264 
0265     if (!part.isValid()) {
0266         part = preferredPart(m_mimeType);
0267     }
0268 
0269     /* Corner case: webenginepart can't determine mimetype (gives application/octet-stream) but
0270      * OpenUrlJob determines a mimetype supported by WebEnginePart (for example application/xml):
0271      * if the preferred part is webenginepart, we'd get an endless loop because webenginepart will
0272      * call again this. To avoid this, if the preferred service is webenginepart and m_dontPassToWebEnginePart
0273      * is true, use the second preferred service (if any); otherwise return false. This will offer the user
0274      * the option to open or save, instead.
0275      *
0276      * This can also happen if the URL was opened from a link with the "download" attribute or with a
0277      * "CONTENT-DISPOSITION: attachment" header. In these cases, WebEnginePart will always refuse to open
0278      * the URL and will ask Konqueror to download it. However, if the preferred part for the URL mimetype is
0279      * WebEnginePart itself, this would lead to an endless loop. This check avoids it
0280      */
0281     if (m_dontPassToWebEnginePart && m_part.pluginId() == webEngineName) {
0282         QVector<KPluginMetaData> parts = KParts::PartLoader::partsForMimeType(m_mimeType);
0283         auto findPart = [&webEngineName](const KPluginMetaData &md){return md.pluginId() != webEngineName;};
0284         QVector<KPluginMetaData>::const_iterator partToUse = std::find_if(parts.constBegin(), parts.constEnd(), findPart);
0285         if (partToUse != parts.constEnd()) {
0286             part = *partToUse;
0287         } else {
0288             part = KPluginMetaData();
0289         }
0290     }
0291     return part;
0292 }
0293 
0294 bool UrlLoader::decideEmbedOrSave()
0295 {
0296     m_part = findEmbeddingPart();
0297 
0298     //If we can't find a part, return false, so that the caller can use decideOpenOrSave to allow the
0299     //user the possibility of opening the file, since embedding wasn't possibile
0300     if (!m_part.isValid()) {
0301         return false;
0302     }
0303 
0304     //Ask whether to save or embed, except in the following cases:
0305     //- it's a web page: always embed
0306     //- it's a local file: always embed
0307     if (embedWithoutAskingToSave(m_mimeType) || m_url.isLocalFile() || m_embedOrNothing) {
0308         m_action = OpenUrlAction::Embed;
0309     } else {
0310         m_action = askSaveOrOpen(OpenEmbedMode::Embed).first;
0311     }
0312 
0313     if (m_action == OpenUrlAction::Embed) {
0314         m_request.serviceName = m_part.pluginId();
0315     }
0316 
0317     m_ready = m_part.isValid() || m_action != OpenUrlAction::Embed;
0318     return true;
0319 }
0320 
0321 void UrlLoader::decideOpenOrSave()
0322 {
0323     m_ready = true;
0324     QString protClass = KProtocolInfo::protocolClass(m_url.scheme());
0325     bool isLocal = m_url.isLocalFile();
0326     bool alwaysOpen = isLocal || protClass == QLatin1String(":local") || KProtocolInfo::isHelperProtocol(m_url);
0327     OpenSaveAnswer answerWithService;
0328     if (!alwaysOpen) {
0329         answerWithService = askSaveOrOpen(OpenEmbedMode::Open);
0330     } else {
0331         answerWithService = qMakePair(OpenUrlAction::Open, KApplicationTrader::preferredService(m_mimeType));
0332     }
0333 
0334     m_action = answerWithService.first;
0335 
0336     //If the URL should be opened in an external application and it should be downloaded by the requesting part,
0337     //ensure it's not downloaded in Konqueror's temporary directory but in the global temporary directory,
0338     //otherwise if the user closes Konqueror before closing the application, there could be issues because the
0339     //external application wouldn't find the file anymore.
0340     //Problem: if the application doesn't support the --tempfile switch, the file will remain even after both Konqueror
0341     //and the external application are closed and will only be removed automatically if the temporary directory is deleted
0342     //or emptied.
0343     if (m_letRequestingPartDownloadUrl && m_action == OpenUrlAction::Open && m_partDownloaderJob) {
0344         QString fileName = QFileInfo(m_partDownloaderJob->downloadPath()).fileName();
0345         m_partDownloaderJob->setDownloadPath(QDir::temp().filePath(fileName));
0346     }
0347     m_service = answerWithService.second;
0348 }
0349 
0350 
0351 bool UrlLoader::isUrlExecutable() const
0352 {
0353     if (!m_url.isLocalFile()) {
0354         return false;
0355     }
0356 
0357 //Code copied from kio/krun.cpp (KF5.109) written by:
0358 //- Torben Weis <weis@kde.org>
0359 //- David Faure <faure@kde.org>
0360 //- Michael Pyne <michael.pyne@kdemail.net>
0361 //- Harald Sitter <sitter@kde.org>
0362     QMimeDatabase db;
0363     QMimeType mimeType = db.mimeTypeForName(m_mimeType);
0364     if (!(mimeType.inherits(QStringLiteral("application/x-desktop")) ||
0365         mimeType.inherits(QStringLiteral("application/x-executable")) ||
0366         /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */
0367         mimeType.inherits(QStringLiteral("application/x-sharedlib")) ||
0368         mimeType.inherits(QStringLiteral("application/x-ms-dos-executable")) ||
0369         mimeType.inherits(QStringLiteral("application/x-shellscript")))) {
0370         return false;
0371     }
0372 
0373     return QFileInfo(m_url.path()).isExecutable();
0374 }
0375 
0376 
0377 UrlLoader::OpenUrlAction UrlLoader::decideExecute() const {
0378     //We don't want to execute files which aren't local, files which aren't executable (obviously)
0379     //and we don't want to execute when we are reloading (the file is visible in the current part,
0380     //so we know the user wanted to display it
0381     if (!isUrlExecutable() || m_request.args.reload()) {
0382         return OpenUrlAction::UnknwonAction;
0383     }
0384     bool canDisplay = !KParts::PartLoader::partsForMimeType(m_mimeType).isEmpty();
0385 
0386     KMessageBox::ButtonCode code;
0387     KGuiItem executeGuiItem(i18nc("Execute an executable file", "Execute"),
0388                             QIcon::fromTheme(QStringLiteral("system-run")));
0389     KGuiItem displayGuiItem(i18nc("Display an executable file", "Display"),
0390                             QIcon::fromTheme(QStringLiteral("document-preview")));
0391     QString dontShowAgainId(QLatin1String("AskExecuting")+m_mimeType);
0392 
0393     if (canDisplay) {
0394         code = KMessageBox::questionTwoActionsCancel(m_mainWindow,
0395                                                      xi18nc("@info The user has to decide whether to execute an executable file or display it",
0396                                                             "<filename>%1</filename> can be executed. Do you want to execute it or to display it?", m_url.path()),
0397                                                      QString(), executeGuiItem, displayGuiItem,
0398                                                      KStandardGuiItem::cancel(), dontShowAgainId, KMessageBox::Dangerous);
0399     } else {
0400         code = KMessageBox::questionTwoActions(m_mainWindow,
0401                                                xi18nc("@info The user has to decide whether to execute an executable file or not",
0402                                                       "<filename>%1</filename> can be executed. Do you want to execute it?", m_url.path()),
0403                                                QString(), executeGuiItem, KStandardGuiItem::cancel(),
0404                                                dontShowAgainId, KMessageBox::Dangerous);}
0405     switch (code) {
0406         case KMessageBox::PrimaryAction:
0407             return OpenUrlAction::Execute;
0408         case KMessageBox::Cancel:
0409             return OpenUrlAction::DoNothing;
0410         case KMessageBox::SecondaryAction:
0411             //The "No" button actually corresponds to the "Cancel" action if the file can't be displayed
0412             return canDisplay ? OpenUrlAction::UnknwonAction : OpenUrlAction::DoNothing;
0413         default: //This is here only to avoid a compiler warning
0414             return OpenUrlAction::UnknwonAction;
0415     }
0416 }
0417 
0418 void UrlLoader::performAction()
0419 {
0420     //If we still aren't ready, it means that the part wants to download the URL
0421     //by itself. Do this only when opening or embedding, however, since when
0422     //saving we first need to ask the user where to save. save() will launch the
0423     //job itself.
0424     //When the part has finished downloading the URL, the slot connected with the
0425     //job's result() signal will call again this function, after setting m_ready
0426     //to true
0427     if (!m_ready && (m_action == OpenUrlAction::Embed || m_action == OpenUrlAction::Open)) {
0428         downloadForEmbeddingOrOpening();
0429         return;
0430     }
0431     switch (m_action) {
0432         case OpenUrlAction::Embed:
0433             embed();
0434             break;
0435         case OpenUrlAction::Open:
0436             open();
0437             break;
0438         case OpenUrlAction::Execute:
0439             execute();
0440             break;
0441         case OpenUrlAction::Save:
0442             save();
0443             break;
0444         case OpenUrlAction::DoNothing:
0445         case OpenUrlAction::UnknwonAction: //This should never happen
0446             done();
0447             break;
0448     }
0449 }
0450 
0451 void UrlLoader::getDownloaderJobFromPart()
0452 {
0453     if (!m_letRequestingPartDownloadUrl) {
0454         return;
0455     }
0456     DownloaderExtension *iface = downloaderInterface();
0457     if (iface) {
0458         m_partDownloaderJob = iface->downloadJob(m_url, m_request.downloadId, this);
0459     } else {
0460         qCDebug(KONQUEROR_LOG) << "Wanting to let part download" << m_url << "but part doesn't implement the DownloaderInterface";
0461     }
0462 
0463     //If we can't get a job for whatever reason (it shouldn't happen, but let's be sure)
0464     //try to open the URL in the usual way. Maybe it'll work (most likely, it will if
0465     //there's no need to have special cookies set to access it)
0466     if (!m_partDownloaderJob) {
0467         qCDebug(KONQUEROR_LOG) << "Couldn't get DownloadJob for" << m_url << "from part" << m_part;
0468         m_letRequestingPartDownloadUrl = false;
0469         m_request.tempFile = false; //Opening a remote URL with tempFile will fail
0470     }
0471 }
0472 
0473 void UrlLoader::downloadForEmbeddingOrOpening()
0474 {
0475     //This shouldn't happen
0476     if (!m_partDownloaderJob) {
0477         done();
0478         return;
0479     }
0480     connect(m_partDownloaderJob, &DownloaderJob::downloadResult, this, &UrlLoader::jobFinished);
0481 #if QT_VERSION_MAJOR < 6
0482     //In KF5, DownloaderJob::startDownload doesn't connect to the signal
0483     connect(m_partDownloaderJob, &DownloaderJob::downloadResult, this, &UrlLoader::downloadForEmbeddingOrOpeningDone);
0484 #endif
0485     m_partDownloaderJob->startDownload(m_mainWindow, this, &UrlLoader::downloadForEmbeddingOrOpeningDone);
0486 }
0487 
0488 void UrlLoader::downloadForEmbeddingOrOpeningDone(KonqInterfaces::DownloaderJob *job, const QUrl &url)
0489 {
0490     if (job && job->error() == 0) {
0491         m_url = url;
0492         m_ready = true;
0493         m_request.tempFile = true;
0494     } else if (!job || job->error() == KIO::ERR_USER_CANCELED) {
0495         m_action = OpenUrlAction::DoNothing;
0496         m_ready = true;
0497     }
0498     checkDownloadedMimetype();
0499     performAction();
0500 }
0501 
0502 void UrlLoader::checkDownloadedMimetype()
0503 {
0504     QMimeDatabase db;
0505     QMimeType typeByContent = db.mimeTypeForFile(m_url.path(), QMimeDatabase::MatchContent);
0506     QMimeType typeByName = db.mimeTypeForFile(m_url.path(), QMimeDatabase::MatchExtension);
0507     QString type = (typeByName.inherits(typeByContent.name()) ? typeByName : typeByContent).name();
0508     if (type == m_mimeType) {
0509         return;
0510     }
0511     m_mimeType = type;
0512     //The URL is a local file now, so there are no problems in opening it with WebEnginePart
0513     m_dontPassToWebEnginePart = false;
0514     if (shouldEmbedThis()) {
0515         m_part = findEmbeddingPart(false);
0516         if (m_part.isValid()) {
0517             m_action = OpenUrlAction::Embed;
0518             return;
0519         }
0520     }
0521     m_action = OpenUrlAction::Open;
0522     if (m_service && m_service->hasMimeType(m_mimeType)) {
0523         return;
0524     }
0525     m_service = KApplicationTrader::preferredService(m_mimeType);
0526 }
0527 
0528 void UrlLoader::done(KJob *job)
0529 {
0530     //Ensure that m_mimeType and m_request.args.mimeType are equal, since it's not clear what will be used
0531     m_request.args.setMimeType(m_mimeType);
0532     if (job) {
0533         jobFinished(job);
0534     }
0535     emit finished(this);
0536     //If we reach here and m_partDownloaderJob->finished() is false, it means the job hasn't been started in the first place,
0537     //(because the user canceled the download), so kill it
0538     if (m_partDownloaderJob && !m_partDownloaderJob->finished()) {
0539         m_partDownloaderJob->kill();
0540     }
0541     deleteLater();
0542 }
0543 
0544 bool UrlLoader::serviceIsKonqueror(KService::Ptr service)
0545 {
0546     return service && (service->desktopEntryName() == QLatin1String("konqueror") || service->exec().trimmed() == QLatin1String("konqueror") || service->exec().trimmed().startsWith(QLatin1String("kfmclient")));
0547 }
0548 
0549 void UrlLoader::launchMimeTypeFinderJob()
0550 {
0551     m_mimeTypeFinderJob = new KIO::MimeTypeFinderJob(m_url, this);
0552     m_mimeTypeFinderJob->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_mainWindow));
0553     m_mimeTypeFinderJob->setSuggestedFileName(m_request.suggestedFileName);
0554     connect(m_mimeTypeFinderJob, &KIO::MimeTypeFinderJob::result, this, [this](KJob*){mimetypeDeterminedByJob();});
0555     m_mimeTypeFinderJob->start();
0556 }
0557 
0558 void UrlLoader::mimetypeDeterminedByJob()
0559 {
0560     if (m_mimeTypeFinderJob->error()) {
0561         m_jobErrorCode = m_mimeTypeFinderJob->error();
0562         m_url = Konq::makeErrorUrl(m_jobErrorCode, m_mimeTypeFinderJob->errorString(), m_url);
0563         m_mimeType = QStringLiteral("text/html");
0564         m_action = OpenUrlAction::Embed;
0565         performAction();
0566         return;
0567     }
0568     m_mimeType=m_mimeTypeFinderJob->mimeType();
0569     if (m_mimeType.isEmpty()) {
0570         //Ensure the mimetype is not empty so that goOn (called below) won't attempt to determine it again
0571         //In theory, KIO::MimeTypeFinderJob::mimeType() should never return an empty string
0572         m_mimeType = QStringLiteral("application/octet-stream");
0573     }
0574     //Only check whether the URL represents an archive when it is a local file. This can be either because
0575     //QUrl::isLocalFile returns true or because its scheme corresponds to a protocol with class :local
0576     //(for example, tar)
0577     if (m_url.isLocalFile() || KProtocolInfo::protocolClass(m_url.scheme()) == QLatin1String(":local")) {
0578         detectArchiveSettings();
0579     }
0580     goOn();
0581 }
0582 
0583 bool UrlLoader::shouldUseDefaultHttpMimeype() const
0584 {
0585     if (m_dontPassToWebEnginePart || isMimeTypeKnown(m_mimeType)) {
0586         return false;
0587     }
0588     const QVector<QString> webengineSchemes = {QStringLiteral("error"), QStringLiteral("konq")};
0589     return m_url.scheme().startsWith(QStringLiteral("http")) || webengineSchemes.contains(m_url.scheme());
0590 }
0591 
0592 DownloaderExtension* UrlLoader::downloaderInterface() const
0593 {
0594     if (!m_request.requestingPart) {
0595         return nullptr;
0596     }
0597     return DownloaderExtension::downloader(m_request.requestingPart);
0598 }
0599 
0600 void UrlLoader::detectSettingsForRemoteFiles()
0601 {
0602     if (m_url.isLocalFile()) {
0603         return;
0604     }
0605 
0606     if (m_url.scheme() == QLatin1String("error")) {
0607         m_letRequestingPartDownloadUrl = false; //error URLs can never be downloaded
0608         m_mimeType = QLatin1String("text/html");
0609         m_request.args.setMimeType(QStringLiteral("text/html"));
0610     }
0611     else if (shouldUseDefaultHttpMimeype()) {
0612         // If a part which supports html asked to download the URL, it means it's not html, so don't change its mimetype
0613         if (m_letRequestingPartDownloadUrl && m_part.supportsMimeType(QStringLiteral("text/html"))) {
0614             return;
0615         }
0616         m_mimeType = QLatin1String("text/html");
0617         m_request.args.setMimeType(QStringLiteral("text/html"));
0618     } else if (!m_trustedSource && isTextExecutable(m_mimeType)) {
0619         m_mimeType = QLatin1String("text/plain");
0620         m_request.args.setMimeType(QStringLiteral("text/plain"));
0621     }
0622 }
0623 
0624 int UrlLoader::checkAccessToLocalFile(const QString& path)
0625 {
0626     QFileInfo info(path);
0627     bool fileExists = info.exists();
0628     if (!info.isReadable()) {
0629         QFileInfo parentInfo(info.dir().path());
0630         if (parentInfo.isExecutable() && !fileExists) {
0631             return KIO::ERR_DOES_NOT_EXIST;
0632         } else {
0633             return KIO::ERR_CANNOT_OPEN_FOR_READING;
0634         }
0635     } else if (info.isDir() && !info.isExecutable()) {
0636         return KIO::ERR_CANNOT_ENTER_DIRECTORY;
0637     } else {
0638         return 0;
0639     }
0640 }
0641 
0642 void UrlLoader::detectArchiveSettings()
0643 {
0644     // Generic mechanism for redirecting to tar:/<path>/ when clicking on a tar file,
0645     // zip:/<path>/ when clicking on a zip file, etc.
0646     // The .protocol file specifies the mimetype that the kioslave handles.
0647     // Note that we don't use mimetype inheritance since we don't want to
0648     // open OpenDocument files as zip folders...
0649     const QString protocol = KProtocolManager::protocolForArchiveMimetype(m_mimeType);
0650     if (protocol.isEmpty() && !KProtocolInfo::archiveMimetypes(m_url.scheme()).isEmpty() && m_mimeType == QLatin1String("inode/directory")) {
0651         m_url.setScheme(QStringLiteral("file"));
0652     } else if (!protocol.isEmpty() && KonqFMSettings::settings()->shouldEmbed(m_mimeType)) {
0653         m_url.setScheme(protocol);
0654         //If the URL ends with /, we assume that it was the result of the user using the Up button while displaying the webarchive.
0655         //This means that we don't want to add the /index.html part, otherwise the effect will be to have the same URL but with
0656         //an increasing number of slashes before index.html
0657         if (m_mimeType == QLatin1String("application/x-webarchive") && !m_url.path().endsWith('/')) {
0658             m_url.setPath(m_url.path() + QStringLiteral("/index.html"));
0659             m_mimeType = QStringLiteral("text/html");
0660         } else {
0661             if (KProtocolManager::outputType(m_url) == KProtocolInfo::T_FILESYSTEM) {
0662                 if (!m_url.path().endsWith('/')) {
0663                     m_url.setPath(m_url.path() + '/');
0664                 }
0665                 m_mimeType = QStringLiteral("inode/directory");
0666             } else {
0667                 m_mimeType.clear();
0668             }
0669         }
0670     }
0671 }
0672 
0673 void UrlLoader::detectSettingsForLocalFiles()
0674 {
0675     if (!m_url.isLocalFile()) {
0676         return;
0677     }
0678 
0679     m_letRequestingPartDownloadUrl = false; //If the file is local, there's no need to download it
0680 
0681     m_jobErrorCode = checkAccessToLocalFile(m_url.path());
0682 
0683     if (!m_mimeType.isEmpty()) {
0684         detectArchiveSettings();
0685         // Redirect to the url in Type=Link desktop files
0686         if (m_mimeType == QLatin1String("application/x-desktop")) {
0687             KDesktopFile df(m_url.toLocalFile());
0688             if (df.hasLinkType()) {
0689                 m_url = QUrl(df.readUrl());
0690                 m_mimeType.clear(); // to be determined again
0691             }
0692         }
0693     } else {
0694         if (QFile::exists(m_url.path())) {
0695             QMimeDatabase db;
0696             m_mimeType = db.mimeTypeForFile(m_url.path()).name();
0697         } else {
0698             //Treat the nonexisting file as a directory
0699             m_mimeType = QStringLiteral("inode/directory");
0700         }
0701     }
0702 }
0703 
0704 bool UrlLoader::shouldEmbedThis() const
0705 {
0706     return !m_dontEmbed && (m_request.forceAutoEmbed || KonqFMSettings::settings()->shouldEmbed(m_mimeType));
0707 }
0708 
0709 void UrlLoader::embed()
0710 {
0711     if (m_jobErrorCode) {
0712         QUrl url = m_url;
0713         m_url = Konq::makeErrorUrl(m_jobErrorCode, m_url.scheme(), m_url);
0714         m_mimeType = QStringLiteral("text/html");
0715         m_part = findPartById(QStringLiteral("webenginepart"));
0716     }
0717     bool embedded = m_mainWindow->openView(m_mimeType, m_url, m_view, m_request, m_originalUrl);
0718     if (embedded || m_embedOrNothing) {
0719         done();
0720     } else {
0721         decideOpenOrSave();
0722         performAction();
0723     }
0724 }
0725 
0726 void UrlLoader::save()
0727 {
0728     QFileDialog dlg(m_mainWindow);
0729     dlg.setAcceptMode(QFileDialog::AcceptSave);
0730     dlg.setWindowTitle(i18n("Save As"));
0731     dlg.setOption(QFileDialog::DontConfirmOverwrite, false);
0732     QString suggestedName = !m_request.suggestedFileName.isEmpty() ? m_request.suggestedFileName : m_url.fileName();
0733     dlg.selectFile(suggestedName);
0734     dlg.setDirectory(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
0735     //NOTE: we can't use QDialog::open() because the dialog needs to be synchronous so that we can call
0736     //DownloaderJob::setDownloadPath()
0737     if (dlg.exec() == QDialog::Rejected) {
0738         done();
0739         return;
0740     }
0741     performSave(m_url, dlg.selectedUrls().value(0));
0742 }
0743 
0744 void UrlLoader::performSave(const QUrl& orig, const QUrl& dest)
0745 {
0746     KJob *job = nullptr;
0747     if (m_letRequestingPartDownloadUrl) {
0748         getDownloaderJobFromPart();
0749         if (m_partDownloaderJob) {
0750 #if QT_VERSION_MAJOR < 6
0751             //In KF5, DownloaderJob::startDownload doesn't connect to the signal
0752             connect(m_partDownloaderJob, &DownloaderJob::downloadResult, this, [this](DownloaderJob *j){done(j);});
0753 #endif
0754             m_partDownloaderJob->startDownload(dest.path(), m_mainWindow, this, [this](DownloaderJob *j){done(j);});
0755             return;
0756         }
0757     }
0758     job = KIO::file_copy(orig, dest, -1, KIO::Overwrite);
0759     if (!job) {
0760         done();
0761         return;
0762     }
0763     KJobWidgets::setWindow(job, m_mainWindow);
0764     KJobTrackerInterface *t = KIO::getJobTracker();
0765     if (t) {
0766         t->registerJob(job);
0767     }
0768     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_mainWindow));
0769     connect(job, &KJob::finished, this, [this, job](){done(job);});
0770     job->start();
0771 }
0772 
0773 void UrlLoader::open()
0774 {
0775     // Prevention against user stupidity : if the associated app for this mimetype
0776     // is konqueror/kfmclient, then we'll loop forever.
0777     if (m_service && serviceIsKonqueror(m_service) && m_mainWindow->refuseExecutingKonqueror(m_mimeType)) {
0778         return;
0779     }
0780     if (m_jobErrorCode != 0) {
0781         done();
0782         return;
0783     }
0784 
0785     KJob *job = nullptr;
0786     KIO::ApplicationLauncherJob *j = new KIO::ApplicationLauncherJob(m_service);
0787     j->setUrls({m_url});
0788     j->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_mainWindow));
0789     if (m_request.tempFile) {
0790         j->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles);
0791     }
0792     job = j;
0793     connect(job, &KJob::finished, this, [this, job](){done(job);});
0794     job->start();
0795 }
0796 
0797 void UrlLoader::execute()
0798 {
0799     m_openUrlJob = new KIO::OpenUrlJob(m_url, m_mimeType, this);
0800     m_openUrlJob->setEnableExternalBrowser(false);
0801     m_openUrlJob->setRunExecutables(true);
0802     m_openUrlJob->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_mainWindow));
0803     m_openUrlJob->setSuggestedFileName(m_request.suggestedFileName);
0804     m_openUrlJob->setDeleteTemporaryFile(m_request.tempFile);
0805     connect(m_openUrlJob, &KJob::finished, this, [this]{done(m_openUrlJob);});
0806     m_openUrlJob->start();
0807 }
0808 
0809 //Copied from KParts::BrowserRun::isTextExecutable
0810 bool UrlLoader::isTextExecutable(const QString &mimeType)
0811 {
0812     return ( mimeType == QLatin1String("application/x-desktop") || mimeType == QLatin1String("application/x-shellscript"));
0813 }
0814 
0815 UrlLoader::OpenSaveAnswer UrlLoader::askSaveOrOpen(OpenEmbedMode mode) const
0816 {
0817     BrowserOpenOrSaveQuestion dlg(m_mainWindow, m_url, m_mimeType);
0818     dlg.setSuggestedFileName(m_request.suggestedFileName);
0819     dlg.setFeatures(BrowserOpenOrSaveQuestion::ServiceSelection);
0820     BrowserOpenOrSaveQuestion::Result ans = mode == OpenEmbedMode::Open ? dlg.askOpenOrSave() : dlg.askEmbedOrSave();
0821     OpenUrlAction action;
0822     switch (ans) {
0823         case BrowserOpenOrSaveQuestion::Save:
0824             action = OpenUrlAction::Save;
0825             break;
0826         case BrowserOpenOrSaveQuestion::Open:
0827             action = OpenUrlAction::Open;
0828             break;
0829         case BrowserOpenOrSaveQuestion::Embed:
0830             action = OpenUrlAction::Embed;
0831             break;
0832         default:
0833             action = OpenUrlAction::DoNothing;
0834     }
0835     return qMakePair(action, dlg.selectedService());
0836 }
0837 
0838 QString UrlLoader::partForLocalFile(const QString& path)
0839 {
0840     QMimeDatabase db;
0841     QString mimetype = db.mimeTypeForFile(path).name();
0842 
0843     KPluginMetaData plugin = preferredPart(mimetype);
0844     return plugin.pluginId();
0845 }
0846 
0847 UrlLoader::ViewToUse UrlLoader::viewToUse() const
0848 {
0849     if (m_view && m_view->isFollowActive()) {
0850         return ViewToUse::CurrentView;
0851     }
0852 
0853     if (!m_view && !m_request.browserArgs.newTab()) {
0854         return ViewToUse::CurrentView;
0855     } else if (!m_view && m_request.browserArgs.newTab()) {
0856         return ViewToUse::NewTab;
0857     }
0858     return ViewToUse::View;
0859 }
0860 
0861 void UrlLoader::jobFinished(KJob* job)
0862 {
0863     m_jobErrorCode = job->error();
0864 }
0865 
0866 QDebug operator<<(QDebug dbg, UrlLoader::OpenUrlAction action)
0867 {
0868     QDebugStateSaver saver(dbg);
0869     dbg.resetFormat();
0870     switch (action) {
0871         case UrlLoader::OpenUrlAction::UnknwonAction:
0872             dbg << "UnknownAction";
0873             break;
0874         case UrlLoader::OpenUrlAction::DoNothing:
0875             dbg << "DoNothing";
0876             break;
0877         case UrlLoader::OpenUrlAction::Save:
0878             dbg << "Save";
0879             break;
0880         case UrlLoader::OpenUrlAction::Embed:
0881             dbg << "Embed";
0882             break;
0883         case UrlLoader::OpenUrlAction::Open:
0884             dbg << "Open";
0885             break;
0886         case UrlLoader::OpenUrlAction::Execute:
0887             dbg << "Execute";
0888             break;
0889     }
0890     return dbg;
0891 }
0892