File indexing completed on 2024-11-10 04:31:14
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