File indexing completed on 2024-04-14 04:37:47
0001 /* 0002 SPDX-FileCopyrightText: 2008 Dirk Mueller <mueller@kde.org> 0003 SPDX-FileCopyrightText: 2008-2010 Urs Wolfer <uwolfer@kde.org> 0004 SPDX-FileCopyrightText: 2009 Dawit Alemayehu <adawit@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.1-or-later 0007 */ 0008 0009 #include "webpage.h" 0010 0011 #include "kwebkitpart_debug.h" 0012 #include "kwebkitpart.h" 0013 #include "webview.h" 0014 #include "networkaccessmanager.h" 0015 #include "settings/webkitsettings.h" 0016 #include "webpluginfactory.h" 0017 0018 #include <KDialogJobUiDelegate> 0019 #include <KIO/CommandLauncherJob> 0020 #include <KIconLoader> 0021 #include <KMessageBox> 0022 #include <KShell> 0023 #include <KProtocolInfo> 0024 #include <KStringHandler> 0025 #include <KUrlAuthorized> 0026 #include <KSharedConfig> 0027 #include <KConfigGroup> 0028 #include <KLocalizedString> 0029 0030 #include <KIO/Job> 0031 #include <KParts/HtmlExtension> 0032 0033 #include <QApplication> 0034 #include <QNetworkReply> 0035 #include <QWebSecurityOrigin> 0036 #include <QDesktopWidget> 0037 #include <QMimeType> 0038 #include <QMimeDatabase> 0039 #include <QLocale> 0040 #include <QFileDialog> 0041 #include <QUrlQuery> 0042 0043 #define QL1S(x) QLatin1String(x) 0044 #define QL1C(x) QLatin1Char(x) 0045 0046 static bool isBlankUrl(const QUrl& url) 0047 { 0048 return (url.isEmpty() || url.url() == QL1S("about:blank")); 0049 } 0050 0051 WebPage::WebPage(KWebKitPart *part, QWidget *parent) 0052 :KWebPage(parent, (KWebPage::KPartsIntegration|KWebPage::KWalletIntegration)), 0053 m_kioErrorCode(0), 0054 m_ignoreError(false), 0055 m_noJSOpenWindowCheck(false), 0056 m_part(part) 0057 { 0058 // FIXME: Need a better way to handle request filtering than to inherit 0059 // KIO::Integration::AccessManager... 0060 KDEPrivate::MyNetworkAccessManager *manager = new KDEPrivate::MyNetworkAccessManager(this); 0061 manager->setEmitReadyReadOnMetaDataChange(true); 0062 manager->setCache(nullptr); 0063 QWidget* window = parent ? parent->window() : nullptr; 0064 if (window) { 0065 manager->setWindow(window); 0066 } 0067 setNetworkAccessManager(manager); 0068 0069 setPluginFactory(new WebPluginFactory(part, this)); 0070 0071 setSessionMetaData(QL1S("ssl_activate_warnings"), QL1S("TRUE")); 0072 0073 // Set font sizes accordingly... 0074 if (view()) 0075 WebKitSettings::self()->computeFontSizes(view()->logicalDpiY()); 0076 0077 setForwardUnsupportedContent(true); 0078 0079 // Add all KDE's local protocols to QWebSecurityOrigin 0080 Q_FOREACH (const QString& protocol, KProtocolInfo::protocols()) { 0081 // file is already a known local scheme and about must not be added 0082 // to this list since there is about:blank. 0083 if (protocol == QL1S("about") || protocol == QL1S("file")) 0084 continue; 0085 0086 if (KProtocolInfo::protocolClass(protocol) != QL1S(":local")) 0087 continue; 0088 0089 QWebSecurityOrigin::addLocalScheme(protocol); 0090 } 0091 0092 connect(this, SIGNAL(geometryChangeRequested(QRect)), 0093 this, SLOT(slotGeometryChangeRequested(QRect))); 0094 connect(this, SIGNAL(downloadRequested(QNetworkRequest)), 0095 this, SLOT(downloadRequest(QNetworkRequest))); 0096 connect(this, SIGNAL(unsupportedContent(QNetworkReply*)), 0097 this, SLOT(slotUnsupportedContent(QNetworkReply*))); 0098 connect(this, SIGNAL(featurePermissionRequested(QWebFrame*,QWebPage::Feature)), 0099 this, SLOT(slotFeaturePermissionRequested(QWebFrame*,QWebPage::Feature))); 0100 connect(networkAccessManager(), SIGNAL(finished(QNetworkReply*)), 0101 this, SLOT(slotRequestFinished(QNetworkReply*))); 0102 } 0103 0104 WebPage::~WebPage() 0105 { 0106 //qCDebug(KWEBKITPART_LOG) << this; 0107 } 0108 0109 const WebSslInfo& WebPage::sslInfo() const 0110 { 0111 return m_sslInfo; 0112 } 0113 0114 void WebPage::setSslInfo (const WebSslInfo& info) 0115 { 0116 m_sslInfo = info; 0117 } 0118 0119 static void checkForDownloadManager(QWidget* widget, QString& cmd) 0120 { 0121 cmd.clear(); 0122 KConfigGroup cfg (KSharedConfig::openConfig("konquerorrc", KConfig::NoGlobals), "HTML Settings"); 0123 const QString fileName (cfg.readPathEntry("DownloadManager", QString())); 0124 if (fileName.isEmpty()) 0125 return; 0126 0127 const QString exeName = QStandardPaths::findExecutable(fileName); 0128 if (exeName.isEmpty()) { 0129 KMessageBox::detailedSorry(widget, 0130 i18n("The download manager (%1) could not be found in your installation.", fileName), 0131 i18n("Try to reinstall it and make sure that it is available in $PATH. \n\nThe integration will be disabled.")); 0132 cfg.writePathEntry("DownloadManager", QString()); 0133 cfg.sync(); 0134 return; 0135 } 0136 0137 cmd = exeName; 0138 } 0139 0140 void WebPage::downloadRequest(const QNetworkRequest &request) 0141 { 0142 const QUrl url(request.url()); 0143 0144 // Integration with a download manager... 0145 if (!url.isLocalFile()) { 0146 QString managerExe; 0147 checkForDownloadManager(view(), managerExe); 0148 if (!managerExe.isEmpty()) { 0149 //qCDebug(KWEBKITPART_LOG) << "Calling command" << cmd; 0150 KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(managerExe, {url.toString()}); 0151 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, view())); 0152 job->start(); 0153 return; 0154 } 0155 } 0156 0157 KWebPage::downloadRequest(request); 0158 } 0159 0160 static QString warningIconData() 0161 { 0162 QString data; 0163 QFile f (KIconLoader::global()->iconPath("dialog-warning", -KIconLoader::SizeHuge)); 0164 0165 if (f.open(QIODevice::ReadOnly)) { 0166 QByteArray fileData = f.readAll(); 0167 QMimeDatabase db; 0168 QMimeType mime = db.mimeTypeForFileNameAndData(f.fileName(), fileData); 0169 data += QL1S("data:"); 0170 data += mime.name(); 0171 data += QL1S(";base64,"); 0172 data += fileData.toBase64(); 0173 f.close(); 0174 } 0175 0176 return data; 0177 } 0178 0179 QString WebPage::errorPage(int code, const QString& text, const QUrl& reqUrl) const 0180 { 0181 QString errorName, techName, description; 0182 QStringList causes, solutions; 0183 0184 QByteArray raw = KIO::rawErrorDetail( code, text, &reqUrl ); 0185 QDataStream stream(raw); 0186 0187 stream >> errorName >> techName >> description >> causes >> solutions; 0188 0189 QFile file (QStandardPaths::locate(QStandardPaths::GenericDataLocation, QL1S("kwebkitpart/error.html"))); 0190 if ( !file.open( QIODevice::ReadOnly ) ) 0191 return i18n("<html><body><h3>Unable to display error message</h3>" 0192 "<p>The error template file <em>error.html</em> could not be " 0193 "found.</p></body></html>"); 0194 0195 QString html( QL1S(file.readAll()) ); 0196 0197 html.replace( QL1S( "TITLE" ), i18n( "Error: %1", errorName ) ); 0198 html.replace( QL1S( "DIRECTION" ), QApplication::isRightToLeft() ? "rtl" : "ltr" ); 0199 html.replace( QL1S( "ICON_PATH" ), warningIconData()); 0200 0201 QString doc (QL1S( "<h1>" )); 0202 doc += i18n( "The requested operation could not be completed" ); 0203 doc += QL1S( "</h1><h2>" ); 0204 doc += errorName; 0205 doc += QL1S( "</h2>" ); 0206 0207 if ( !techName.isNull() ) { 0208 doc += QL1S( "<h2>" ); 0209 doc += i18n( "Technical Reason: %1", techName ); 0210 doc += QL1S( "</h2>" ); 0211 } 0212 0213 doc += QL1S( "<h3>" ); 0214 doc += i18n( "Details of the Request:" ); 0215 doc += QL1S( "</h3><ul><li>" ); 0216 // escape URL twice: once for i18n, and once for HTML. 0217 doc += i18n( "URL: %1", reqUrl.toDisplayString().toHtmlEscaped().toHtmlEscaped() ); 0218 doc += QL1S( "</li><li>" ); 0219 0220 const QString protocol (reqUrl.scheme()); 0221 if ( !protocol.isNull() ) { 0222 // escape protocol twice: once for i18n, and once for HTML. 0223 doc += i18n( "Protocol: %1", protocol.toHtmlEscaped().toHtmlEscaped() ); 0224 doc += QL1S( "</li><li>" ); 0225 } 0226 0227 doc += i18n( "Date and Time: %1", 0228 QLocale().toString(QDateTime::currentDateTime(), QLocale::LongFormat) ); 0229 doc += QL1S( "</li><li>" ); 0230 // escape text twice: once for i18n, and once for HTML. 0231 doc += i18n( "Additional Information: %1", text.toHtmlEscaped().toHtmlEscaped() ); 0232 doc += QL1S( "</li></ul><h3>" ); 0233 doc += i18n( "Description:" ); 0234 doc += QL1S( "</h3><p>" ); 0235 doc += description.toHtmlEscaped(); 0236 doc += QL1S( "</p>" ); 0237 0238 if ( causes.count() ) { 0239 doc += QL1S( "<h3>" ); 0240 doc += i18n( "Possible Causes:" ); 0241 doc += QL1S( "</h3><ul><li>" ); 0242 doc += causes.join( "</li><li>" ); 0243 doc += QL1S( "</li></ul>" ); 0244 } 0245 0246 if ( solutions.count() ) { 0247 doc += QL1S( "<h3>" ); 0248 doc += i18n( "Possible Solutions:" ); 0249 doc += QL1S( "</h3><ul><li>" ); 0250 doc += solutions.join( "</li><li>" ); 0251 doc += QL1S( "</li></ul>" ); 0252 } 0253 0254 html.replace( QL1S("TEXT"), doc ); 0255 0256 return html; 0257 } 0258 0259 bool WebPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) 0260 { 0261 switch (extension) { 0262 case QWebPage::ErrorPageExtension: { 0263 if (!m_ignoreError) { 0264 const QWebPage::ErrorPageExtensionOption *extOption = static_cast<const QWebPage::ErrorPageExtensionOption*>(option); 0265 QWebPage::ErrorPageExtensionReturn *extOutput = static_cast<QWebPage::ErrorPageExtensionReturn*>(output); 0266 if (extOutput && extOption && extOption->domain != QWebPage::WebKit) { 0267 extOutput->content = errorPage(m_kioErrorCode, extOption->errorString, extOption->url).toUtf8(); 0268 extOutput->baseUrl = extOption->url; 0269 return true; 0270 } 0271 } 0272 break; 0273 } 0274 case QWebPage::ChooseMultipleFilesExtension: { 0275 const QWebPage::ChooseMultipleFilesExtensionOption* extOption = static_cast<const QWebPage::ChooseMultipleFilesExtensionOption*> (option); 0276 QWebPage::ChooseMultipleFilesExtensionReturn *extOutput = static_cast<QWebPage::ChooseMultipleFilesExtensionReturn*>(output); 0277 if (extOutput && extOption && currentFrame() == extOption->parentFrame) { 0278 if (extOption->suggestedFileNames.isEmpty()) 0279 extOutput->fileNames = QFileDialog::getOpenFileNames(view(), 0280 i18n("Choose files to upload"), 0281 QString(), QString()); 0282 else 0283 extOutput->fileNames = QFileDialog::getOpenFileNames(view(), 0284 i18n("Choose files to upload"), 0285 extOption->suggestedFileNames.first(), 0286 QString()); 0287 return true; 0288 } 0289 break; 0290 } 0291 default: 0292 break; 0293 } 0294 0295 return KWebPage::extension(extension, option, output); 0296 } 0297 0298 bool WebPage::supportsExtension(Extension extension) const 0299 { 0300 //qCDebug(KWEBKITPART_LOG) << extension << m_ignoreError; 0301 switch (extension) { 0302 case QWebPage::ErrorPageExtension: 0303 return (!m_ignoreError); 0304 case QWebPage::ChooseMultipleFilesExtension: 0305 return true; 0306 default: 0307 break; 0308 } 0309 0310 return KWebPage::supportsExtension(extension); 0311 } 0312 0313 QWebPage *WebPage::createWindow(WebWindowType type) 0314 { 0315 // qCDebug(KWEBKITPART_LOG) << "window type:" << type; 0316 // Crete an instance of NewWindowPage class to capture all the 0317 // information we need to create a new window. See documentation of 0318 // the class for more information... 0319 NewWindowPage* page = new NewWindowPage(type, part(), m_noJSOpenWindowCheck); 0320 m_noJSOpenWindowCheck = false; 0321 return page; 0322 } 0323 0324 // Returns true if the scheme and domain of the two urls match... 0325 static bool domainSchemeMatch(const QUrl& u1, const QUrl& u2) 0326 { 0327 if (u1.scheme() != u2.scheme()) 0328 return false; 0329 0330 QStringList u1List = u1.host().split(QL1C('.'), QString::SkipEmptyParts); 0331 QStringList u2List = u2.host().split(QL1C('.'), QString::SkipEmptyParts); 0332 0333 if (qMin(u1List.count(), u2List.count()) < 2) 0334 return false; // better safe than sorry... 0335 0336 while (u1List.count() > 2) 0337 u1List.removeFirst(); 0338 0339 while (u2List.count() > 2) 0340 u2List.removeFirst(); 0341 0342 return (u1List == u2List); 0343 } 0344 0345 static void resetPluginsLoadedOnDemandFor(QWebPluginFactory* _factory) 0346 { 0347 WebPluginFactory* factory = qobject_cast<WebPluginFactory*>(_factory); 0348 if (factory) { 0349 factory->resetPluginOnDemandList(); 0350 } 0351 } 0352 0353 bool WebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type) 0354 { 0355 QUrl reqUrl (request.url()); 0356 0357 // Handle "mailto:" url here... 0358 if (handleMailToUrl(reqUrl, type)) 0359 return false; 0360 0361 const bool isMainFrameRequest = (frame == mainFrame()); 0362 const bool isTypedUrl = property("NavigationTypeUrlEntered").toBool(); 0363 0364 /* 0365 NOTE: We use a dynamic QObject property called "NavigationTypeUrlEntered" 0366 to distinguish between requests generated by user entering a url vs those 0367 that were generated programatically through javascript (AJAX requests). 0368 */ 0369 if (isMainFrameRequest && isTypedUrl) 0370 setProperty("NavigationTypeUrlEntered", QVariant()); 0371 0372 if (frame) { 0373 // inPage requests are those generarted within the current page through 0374 // link clicks, javascript queries, and button clicks (form submission). 0375 bool inPageRequest = true; 0376 switch (type) { 0377 case QWebPage::NavigationTypeFormSubmitted: 0378 if (!checkFormData(request)) 0379 return false; 0380 break; 0381 case QWebPage::NavigationTypeFormResubmitted: 0382 if (!checkFormData(request)) 0383 return false; 0384 if (KMessageBox::warningContinueCancel(view(), 0385 i18n("<qt><p>To display the requested web page again, " 0386 "the browser needs to resend information you have " 0387 "previously submitted.</p>" 0388 "<p>If you were shopping online and made a purchase, " 0389 "click the Cancel button to prevent a duplicate purchase." 0390 "Otherwise, click the Continue button to display the web" 0391 "page again.</p>"), 0392 i18n("Resubmit Information")) == KMessageBox::Cancel) { 0393 return false; 0394 } 0395 break; 0396 case QWebPage::NavigationTypeBackOrForward: 0397 // If history navigation is locked, ignore all such requests... 0398 if (property("HistoryNavigationLocked").toBool()) { 0399 setProperty("HistoryNavigationLocked", QVariant()); 0400 qCDebug(KWEBKITPART_LOG) << "Rejected history navigation because 'HistoryNavigationLocked' property is set!"; 0401 return false; 0402 } 0403 //qCDebug(KWEBKITPART_LOG) << "Navigating to item (" << history()->currentItemIndex() 0404 // << "of" << history()->count() << "):" << history()->currentItem().url(); 0405 inPageRequest = false; 0406 if (!isBlankUrl(reqUrl)) { 0407 resetPluginsLoadedOnDemandFor(pluginFactory()); 0408 } 0409 break; 0410 case QWebPage::NavigationTypeReload: 0411 setRequestMetaData(QL1S("cache"), QL1S("reload")); 0412 inPageRequest = false; 0413 if (!isBlankUrl(reqUrl)) { 0414 resetPluginsLoadedOnDemandFor(pluginFactory()); 0415 } 0416 break; 0417 case QWebPage::NavigationTypeOther: 0418 inPageRequest = !isTypedUrl; 0419 if (isTypedUrl && !isBlankUrl(reqUrl)) { 0420 resetPluginsLoadedOnDemandFor(pluginFactory()); 0421 } 0422 break; 0423 default: 0424 break; 0425 } 0426 0427 if (inPageRequest) { 0428 if (!checkLinkSecurity(request, type)) 0429 return false; 0430 0431 if (m_sslInfo.isValid()) 0432 setRequestMetaData(QL1S("ssl_was_in_use"), QL1S("TRUE")); 0433 } 0434 0435 // Set the "main_frame_request" meta-data to aid SSL verification in KIO. 0436 setRequestMetaData(QL1S("main_frame_request"), (isMainFrameRequest ? QL1S("TRUE") : QL1S("FALSE"))); 0437 0438 // Insert the request into the queue... 0439 reqUrl.setUserInfo(QString()); 0440 m_requestQueue << reqUrl; 0441 } else { 0442 // If request came from javascript, set m_noJSOpenWindowCheck to true. 0443 m_noJSOpenWindowCheck = (!isTypedUrl && type != QWebPage::NavigationTypeOther); 0444 } 0445 0446 // Honor the enabling/disabling of plugins per host. 0447 settings()->setAttribute(QWebSettings::PluginsEnabled, WebKitSettings::self()->isPluginsEnabled(reqUrl.host())); 0448 return KWebPage::acceptNavigationRequest(frame, request, type); 0449 } 0450 0451 QString WebPage::userAgentForUrl(const QUrl& url) const 0452 { 0453 QString userAgent = KWebPage::userAgentForUrl(url); 0454 0455 // Remove the useless "U" if it is present. 0456 const int index = userAgent.indexOf(QL1S(" U;"), -1, Qt::CaseInsensitive); 0457 if (index > -1) 0458 userAgent.remove(index, 3); 0459 0460 return userAgent.trimmed(); 0461 } 0462 0463 static int errorCodeFromReply(QNetworkReply* reply) 0464 { 0465 // First check if there is a KIO error code sent back and use that, 0466 // if not attempt to convert QNetworkReply's NetworkError to KIO::Error. 0467 QVariant attr = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::KioError)); 0468 if (attr.isValid() && attr.type() == QVariant::Int) 0469 return attr.toInt(); 0470 0471 switch (reply->error()) { 0472 case QNetworkReply::ConnectionRefusedError: 0473 return KIO::ERR_COULD_NOT_CONNECT; 0474 case QNetworkReply::HostNotFoundError: 0475 return KIO::ERR_UNKNOWN_HOST; 0476 case QNetworkReply::TimeoutError: 0477 return KIO::ERR_SERVER_TIMEOUT; 0478 case QNetworkReply::OperationCanceledError: 0479 return KIO::ERR_USER_CANCELED; 0480 case QNetworkReply::ProxyNotFoundError: 0481 return KIO::ERR_UNKNOWN_PROXY_HOST; 0482 case QNetworkReply::ContentAccessDenied: 0483 return KIO::ERR_ACCESS_DENIED; 0484 case QNetworkReply::ContentOperationNotPermittedError: 0485 return KIO::ERR_WRITE_ACCESS_DENIED; 0486 case QNetworkReply::ContentNotFoundError: 0487 return KIO::ERR_NO_CONTENT; 0488 case QNetworkReply::AuthenticationRequiredError: 0489 return KIO::ERR_COULD_NOT_AUTHENTICATE; 0490 case QNetworkReply::ProtocolUnknownError: 0491 return KIO::ERR_UNSUPPORTED_PROTOCOL; 0492 case QNetworkReply::ProtocolInvalidOperationError: 0493 return KIO::ERR_UNSUPPORTED_ACTION; 0494 case QNetworkReply::UnknownNetworkError: 0495 return KIO::ERR_UNKNOWN; 0496 case QNetworkReply::NoError: 0497 default: 0498 break; 0499 } 0500 0501 return 0; 0502 } 0503 0504 KWebKitPart* WebPage::part() const 0505 { 0506 return m_part.data(); 0507 } 0508 0509 void WebPage::setPart(KWebKitPart* part) 0510 { 0511 m_part = part; 0512 } 0513 0514 void WebPage::slotRequestFinished(QNetworkReply *reply) 0515 { 0516 Q_ASSERT(reply); 0517 0518 QUrl requestUrl (reply->request().url()); 0519 requestUrl.setUserInfo(QString()); 0520 0521 // Disregards requests that are not in the request queue... 0522 if (!m_requestQueue.removeOne(requestUrl)) 0523 return; 0524 0525 QWebFrame* frame = qobject_cast<QWebFrame *>(reply->request().originatingObject()); 0526 if (!frame) 0527 return; 0528 0529 const bool shouldResetSslInfo = (m_sslInfo.isValid() && !domainSchemeMatch(requestUrl, m_sslInfo.url())); 0530 // Only deal with non-redirect responses... 0531 const QVariant redirectVar = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); 0532 const bool isMainFrameRequest = (frame == mainFrame()); 0533 0534 if (isMainFrameRequest && redirectVar.isValid()) { 0535 m_sslInfo.restoreFrom(reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)), 0536 reply->url(), shouldResetSslInfo); 0537 return; 0538 } 0539 0540 const int errCode = errorCodeFromReply(reply); 0541 qCDebug(KWEBKITPART_LOG) << frame << "is main frame request?" << isMainFrameRequest << requestUrl; 0542 // Handle any error... 0543 switch (errCode) { 0544 case 0: 0545 case KIO::ERR_NO_CONTENT: 0546 if (isMainFrameRequest) { 0547 m_sslInfo.restoreFrom(reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)), 0548 reply->url(), shouldResetSslInfo); 0549 setPageJScriptPolicy(reply->url()); 0550 } 0551 break; 0552 case KIO::ERR_ABORTED: 0553 case KIO::ERR_USER_CANCELED: // Do nothing if request is cancelled/aborted 0554 //qCDebug(KWEBKITPART_LOG) << "User aborted request!"; 0555 m_ignoreError = true; 0556 emit loadAborted(QUrl()); 0557 return; 0558 // Handle the user clicking on a link that refers to a directory 0559 // Since KIO cannot automatically convert a GET request to a LISTDIR one. 0560 case KIO::ERR_IS_DIRECTORY: 0561 m_ignoreError = true; 0562 emit loadAborted(reply->url()); 0563 return; 0564 default: 0565 // Make sure the saveFrameStateRequested signal is emitted so 0566 // the page can restored properly. 0567 if (isMainFrameRequest) 0568 emit saveFrameStateRequested(frame, nullptr); 0569 0570 m_ignoreError = (reply->attribute(QNetworkRequest::User).toInt() == QNetworkReply::ContentAccessDenied); 0571 m_kioErrorCode = errCode; 0572 break; 0573 } 0574 0575 if (isMainFrameRequest) { 0576 const WebPageSecurity security = (m_sslInfo.isValid() ? PageEncrypted : PageUnencrypted); 0577 emit m_part->browserExtension()->setPageSecurity(security); 0578 } 0579 } 0580 0581 void WebPage::slotUnsupportedContent(QNetworkReply* reply) 0582 { 0583 //qCDebug(KWEBKITPART_LOG) << reply->url(); 0584 QString mimeType; 0585 KIO::MetaData metaData; 0586 0587 KIO::AccessManager::putReplyOnHold(reply); 0588 QString downloadCmd; 0589 checkForDownloadManager(view(), downloadCmd); 0590 if (!downloadCmd.isEmpty()) { 0591 reply->setProperty("DownloadManagerExe", downloadCmd); 0592 } 0593 0594 if (KWebPage::handleReply(reply, &mimeType, &metaData)) { 0595 reply->deleteLater(); 0596 if (qobject_cast<NewWindowPage*>(this) && isBlankUrl(m_part->url())) { 0597 m_part->closeUrl(); 0598 if (m_part->arguments().metaData().contains(QL1S("new-window"))) { 0599 m_part->widget()->topLevelWidget()->close(); 0600 } else { 0601 delete m_part; 0602 } 0603 } 0604 return; 0605 } 0606 0607 //qCDebug(KWEBKITPART_LOG) << "mimetype=" << mimeType << "metadata:" << metaData; 0608 0609 if (reply->request().originatingObject() == this->mainFrame()) { 0610 KParts::OpenUrlArguments args; 0611 args.setMimeType(mimeType); 0612 args.metaData() = metaData; 0613 emit m_part->browserExtension()->openUrlRequest(reply->url(), args, KParts::BrowserArguments()); 0614 return; 0615 } 0616 reply->deleteLater(); 0617 } 0618 0619 void WebPage::slotFeaturePermissionRequested(QWebFrame* frame, QWebPage::Feature feature) 0620 { 0621 if (frame == mainFrame()) { 0622 part()->slotShowFeaturePermissionBar(feature); 0623 return; 0624 } 0625 switch(feature) { 0626 case QWebPage::Notifications: 0627 // FIXME: We should have a setting to tell if this is enabled, but so far it is always enabled. 0628 setFeaturePermission(frame, feature, QWebPage::PermissionGrantedByUser); 0629 break; 0630 case QWebPage::Geolocation: 0631 if (KMessageBox::warningContinueCancel(nullptr, i18n("This site is attempting to " 0632 "access information about your " 0633 "physical location.\n" 0634 "Do you want to allow it access?"), 0635 i18n("Network Transmission"), 0636 KGuiItem(i18n("Allow access")), 0637 KStandardGuiItem::cancel(), 0638 "WarnGeolocation") == KMessageBox::Cancel) { 0639 setFeaturePermission(frame, feature, QWebPage::PermissionDeniedByUser); 0640 } else { 0641 setFeaturePermission(frame, feature, QWebPage::PermissionGrantedByUser); 0642 } 0643 break; 0644 default: 0645 setFeaturePermission(frame, feature, QWebPage::PermissionUnknown); 0646 break; 0647 } 0648 } 0649 0650 void WebPage::slotGeometryChangeRequested(const QRect & rect) 0651 { 0652 const QString host = mainFrame()->url().host(); 0653 0654 // NOTE: If a new window was created from another window which is in 0655 // maximized mode and its width and/or height were not specified at the 0656 // time of its creation, which is always the case in QWebPage::createWindow, 0657 // then any move operation will seem not to work. That is because the new 0658 // window will be in maximized mode where moving it will not be possible... 0659 if (WebKitSettings::self()->windowMovePolicy(host) == KParts::HtmlSettingsInterface::JSWindowMoveAllow && 0660 (view()->x() != rect.x() || view()->y() != rect.y())) 0661 emit m_part->browserExtension()->moveTopLevelWidget(rect.x(), rect.y()); 0662 0663 const int height = rect.height(); 0664 const int width = rect.width(); 0665 0666 // parts of following code are based on kjs_window.cpp 0667 // Security check: within desktop limits and bigger than 100x100 (per spec) 0668 if (width < 100 || height < 100) { 0669 qCWarning(KWEBKITPART_LOG) << "Window resize refused, window would be too small (" << width << "," << height << ")"; 0670 return; 0671 } 0672 0673 QRect sg = QApplication::desktop()->screenGeometry(view()); 0674 0675 if (width > sg.width() || height > sg.height()) { 0676 qCWarning(KWEBKITPART_LOG) << "Window resize refused, window would be too big (" << width << "," << height << ")"; 0677 return; 0678 } 0679 0680 if (WebKitSettings::self()->windowResizePolicy(host) == KParts::HtmlSettingsInterface::JSWindowResizeAllow) { 0681 //qCDebug(KWEBKITPART_LOG) << "resizing to " << width << "x" << height; 0682 emit m_part->browserExtension()->resizeTopLevelWidget(width, height); 0683 } 0684 0685 // If the window is out of the desktop, move it up/left 0686 // (maybe we should use workarea instead of sg, otherwise the window ends up below kicker) 0687 const int right = view()->x() + view()->frameGeometry().width(); 0688 const int bottom = view()->y() + view()->frameGeometry().height(); 0689 int moveByX = 0, moveByY = 0; 0690 if (right > sg.right()) 0691 moveByX = - right + sg.right(); // always <0 0692 if (bottom > sg.bottom()) 0693 moveByY = - bottom + sg.bottom(); // always <0 0694 0695 if ((moveByX || moveByY) && WebKitSettings::self()->windowMovePolicy(host) == KParts::HtmlSettingsInterface::JSWindowMoveAllow) 0696 emit m_part->browserExtension()->moveTopLevelWidget(view()->x() + moveByX, view()->y() + moveByY); 0697 } 0698 0699 bool WebPage::checkLinkSecurity(const QNetworkRequest &req, NavigationType type) const 0700 { 0701 // Check whether the request is authorized or not... 0702 if (!KUrlAuthorized::authorizeUrlAction("redirect", mainFrame()->url(), req.url())) { 0703 0704 //qCDebug(KWEBKITPART_LOG) << "*** Failed security check: base-url=" << mainFrame()->url() << ", dest-url=" << req.url(); 0705 QString buttonText, title, message; 0706 0707 int response = KMessageBox::Cancel; 0708 QUrl linkUrl (req.url()); 0709 0710 if (type == QWebPage::NavigationTypeLinkClicked) { 0711 message = i18n("<qt>This untrusted page links to<br/><b>%1</b>." 0712 "<br/>Do you want to follow the link?</qt>", linkUrl.url()); 0713 title = i18n("Security Warning"); 0714 buttonText = i18nc("follow link despite of security warning", "Follow"); 0715 } else { 0716 title = i18n("Security Alert"); 0717 message = i18n("<qt>Access by untrusted page to<br/><b>%1</b><br/> denied.</qt>", 0718 linkUrl.toDisplayString().toHtmlEscaped()); 0719 } 0720 0721 if (buttonText.isEmpty()) { 0722 KMessageBox::error( nullptr, message, title); 0723 } else { 0724 // Dangerous flag makes the Cancel button the default 0725 response = KMessageBox::warningContinueCancel(nullptr, message, title, 0726 KGuiItem(buttonText), 0727 KStandardGuiItem::cancel(), 0728 QString(), // no don't ask again info 0729 KMessageBox::Notify | KMessageBox::Dangerous); 0730 } 0731 0732 return (response == KMessageBox::Continue); 0733 } 0734 0735 return true; 0736 } 0737 0738 bool WebPage::checkFormData(const QNetworkRequest &req) const 0739 { 0740 const QString scheme (req.url().scheme()); 0741 0742 if (m_sslInfo.isValid() && 0743 !scheme.compare(QL1S("https")) && !scheme.compare(QL1S("mailto")) && 0744 (KMessageBox::warningContinueCancel(nullptr, 0745 i18n("Warning: This is a secure form " 0746 "but it is attempting to send " 0747 "your data back unencrypted.\n" 0748 "A third party may be able to " 0749 "intercept and view this " 0750 "information.\nAre you sure you " 0751 "want to send the data unencrypted?"), 0752 i18n("Network Transmission"), 0753 KGuiItem(i18n("&Send Unencrypted"))) == KMessageBox::Cancel)) { 0754 0755 return false; 0756 } 0757 0758 0759 if (scheme.compare(QL1S("mailto")) == 0 && 0760 (KMessageBox::warningContinueCancel(nullptr, i18n("This site is attempting to " 0761 "submit form data via email.\n" 0762 "Do you want to continue?"), 0763 i18n("Network Transmission"), 0764 KGuiItem(i18n("&Send Email")), 0765 KStandardGuiItem::cancel(), 0766 "WarnTriedEmailSubmit") == KMessageBox::Cancel)) { 0767 return false; 0768 } 0769 0770 return true; 0771 } 0772 0773 // Sanitizes the "mailto:" url, e.g. strips out any "attach" parameters. 0774 static QUrl sanitizeMailToUrl(const QUrl &url, QStringList& files) { 0775 QUrl sanitizedUrl; 0776 0777 // NOTE: This is necessary to ensure we can properly use QUrl's query 0778 // related APIs to process 'mailto:' urls of form 'mailto:foo@bar.com'. 0779 if (url.hasQuery()) 0780 sanitizedUrl = url; 0781 else 0782 sanitizedUrl = QUrl(url.scheme() + QL1S(":?") + url.path()); 0783 0784 QListIterator<QPair<QString, QString> > it (QUrlQuery(sanitizedUrl).queryItems()); 0785 QUrlQuery newQuery; 0786 0787 while (it.hasNext()) { 0788 QPair<QString, QString> queryItem = it.next(); 0789 if (queryItem.first.contains(QL1C('@')) && queryItem.second.isEmpty()) { 0790 // ### DF: this hack breaks mailto:faure@kde.org, kmail doesn't expect mailto:?to=faure@kde.org 0791 queryItem.second = queryItem.first; 0792 queryItem.first = "to"; 0793 } else if (QString::compare(queryItem.first, QL1S("attach"), Qt::CaseInsensitive) == 0) { 0794 files << queryItem.second; 0795 continue; 0796 } 0797 newQuery.addQueryItem(queryItem.first, queryItem.second); 0798 } 0799 0800 sanitizedUrl.setQuery(newQuery); // replace the query component 0801 return sanitizedUrl; 0802 } 0803 0804 bool WebPage::handleMailToUrl (const QUrl &url, NavigationType type) const 0805 { 0806 if (QString::compare(url.scheme(), QL1S("mailto"), Qt::CaseInsensitive) == 0) { 0807 QStringList files; 0808 QUrl mailtoUrl (sanitizeMailToUrl(url, files)); 0809 0810 switch (type) { 0811 case QWebPage::NavigationTypeLinkClicked: 0812 if (!files.isEmpty() && KMessageBox::warningContinueCancelList(nullptr, 0813 i18n("<qt>Do you want to allow this site to attach " 0814 "the following files to the email message?</qt>"), 0815 files, i18n("Email Attachment Confirmation"), 0816 KGuiItem(i18n("&Allow attachments")), 0817 KGuiItem(i18n("&Ignore attachments")), QL1S("WarnEmailAttachment")) == KMessageBox::Continue) { 0818 0819 // Re-add the attachments... 0820 QUrlQuery newQuery(mailtoUrl); 0821 QStringListIterator filesIt (files); 0822 while (filesIt.hasNext()) { 0823 newQuery.addQueryItem(QL1S("attach"), filesIt.next()); 0824 } 0825 mailtoUrl.setQuery(newQuery); 0826 } 0827 break; 0828 case QWebPage::NavigationTypeFormSubmitted: 0829 case QWebPage::NavigationTypeFormResubmitted: 0830 if (!files.isEmpty()) { 0831 KMessageBox::information(nullptr, i18n("This site attempted to attach a file from your " 0832 "computer in the form submission. The attachment " 0833 "was removed for your protection."), 0834 i18n("Attachment Removed"), "InfoTriedAttach"); 0835 } 0836 break; 0837 default: 0838 break; 0839 } 0840 0841 //qCDebug(KWEBKITPART_LOG) << "Emitting openUrlRequest with " << mailtoUrl; 0842 emit m_part->browserExtension()->openUrlRequest(mailtoUrl); 0843 return true; 0844 } 0845 0846 return false; 0847 } 0848 0849 void WebPage::setPageJScriptPolicy(const QUrl &url) 0850 { 0851 const QString hostname (url.host()); 0852 settings()->setAttribute(QWebSettings::JavascriptEnabled, 0853 WebKitSettings::self()->isJavaScriptEnabled(hostname)); 0854 0855 const KParts::HtmlSettingsInterface::JSWindowOpenPolicy policy = WebKitSettings::self()->windowOpenPolicy(hostname); 0856 settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, 0857 (policy != KParts::HtmlSettingsInterface::JSWindowOpenDeny && 0858 policy != KParts::HtmlSettingsInterface::JSWindowOpenSmart)); 0859 } 0860 0861 0862 0863 0864 0865 /************************************* Begin NewWindowPage ******************************************/ 0866 0867 NewWindowPage::NewWindowPage(WebWindowType type, KWebKitPart* part, bool disableJSOpenwindowCheck, QWidget* parent) 0868 :WebPage(part, parent) , m_type(type) , m_createNewWindow(true) 0869 , m_disableJSOpenwindowCheck(disableJSOpenwindowCheck) 0870 { 0871 Q_ASSERT_X (part, "NewWindowPage", "Must specify a valid KPart"); 0872 0873 connect(this, SIGNAL(menuBarVisibilityChangeRequested(bool)), 0874 this, SLOT(slotMenuBarVisibilityChangeRequested(bool))); 0875 connect(this, SIGNAL(toolBarVisibilityChangeRequested(bool)), 0876 this, SLOT(slotToolBarVisibilityChangeRequested(bool))); 0877 connect(this, SIGNAL(statusBarVisibilityChangeRequested(bool)), 0878 this, SLOT(slotStatusBarVisibilityChangeRequested(bool))); 0879 connect(mainFrame(), SIGNAL(loadFinished(bool)), this, SLOT(slotLoadFinished(bool))); 0880 } 0881 0882 NewWindowPage::~NewWindowPage() 0883 { 0884 //qCDebug(KWEBKITPART_LOG) << this; 0885 } 0886 0887 bool NewWindowPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type) 0888 { 0889 // qCDebug(KWEBKITPART_LOG) << "url:" << request.url() << ",type:" << type << ",frame:" << frame; 0890 if (m_createNewWindow) { 0891 const QUrl reqUrl (request.url()); 0892 0893 if (!m_disableJSOpenwindowCheck) { 0894 const KParts::HtmlSettingsInterface::JSWindowOpenPolicy policy = WebKitSettings::self()->windowOpenPolicy(reqUrl.host()); 0895 switch (policy) { 0896 case KParts::HtmlSettingsInterface::JSWindowOpenDeny: 0897 // TODO: Implement support for dealing with blocked pop up windows. 0898 this->deleteLater(); 0899 return false; 0900 case KParts::HtmlSettingsInterface::JSWindowOpenAsk: { 0901 const QString message = (reqUrl.isEmpty() ? 0902 i18n("This site is requesting to open a new popup window.\n" 0903 "Do you want to allow this?") : 0904 i18n("<qt>This site is requesting to open a popup window to" 0905 "<p>%1</p><br/>Do you want to allow this?</qt>", 0906 KStringHandler::rsqueeze(reqUrl.toDisplayString().toHtmlEscaped(), 100))); 0907 if (KMessageBox::questionYesNo(view(), message, 0908 i18n("Javascript Popup Confirmation"), 0909 KGuiItem(i18n("Allow")), 0910 KGuiItem(i18n("Do Not Allow"))) == KMessageBox::No) { 0911 // TODO: Implement support for dealing with blocked pop up windows. 0912 this->deleteLater(); 0913 return false; 0914 } 0915 break; 0916 } 0917 default: 0918 break; 0919 } 0920 } 0921 0922 if (!part() && frame != mainFrame() && type != QWebPage::NavigationTypeOther) 0923 return false; 0924 0925 // Browser args... 0926 KParts::BrowserArguments bargs; 0927 bargs.frameName = mainFrame()->frameName(); 0928 if (m_type == WebModalDialog) 0929 bargs.setForcesNewWindow(true); 0930 0931 // OpenUrl args... 0932 KParts::OpenUrlArguments uargs; 0933 uargs.setMimeType(QL1S("text/html")); 0934 uargs.setActionRequestedByUser(m_disableJSOpenwindowCheck); 0935 0936 // Window args... 0937 KParts::WindowArgs wargs (m_windowArgs); 0938 0939 KParts::ReadOnlyPart* newWindowPart =nullptr; 0940 part()->browserExtension()->createNewWindow(QUrl(), uargs, bargs, wargs, &newWindowPart); 0941 qCDebug(KWEBKITPART_LOG) << "Created new window" << newWindowPart; 0942 0943 if (!newWindowPart) { 0944 return false; 0945 } else if (newWindowPart->widget()->topLevelWidget() != part()->widget()->topLevelWidget()) { 0946 KParts::OpenUrlArguments args; 0947 args.metaData().insert(QL1S("new-window"), QL1S("true")); 0948 newWindowPart->setArguments(args); 0949 } 0950 0951 // Get the webview... 0952 KWebKitPart* webkitPart = qobject_cast<KWebKitPart*>(newWindowPart); 0953 WebView* webView = webkitPart ? qobject_cast<WebView*>(webkitPart->view()) : nullptr; 0954 0955 // If the newly created window is NOT a webkitpart... 0956 if (!webView) { 0957 newWindowPart->openUrl(reqUrl); 0958 this->deleteLater(); 0959 return false; 0960 } 0961 // Reparent this page to the new webview to prevent memory leaks. 0962 setParent(webView); 0963 // Replace the webpage of the new webview with this one. Nice trick... 0964 webView->setPage(this); 0965 // Set the new part as the one this page will use going forward. 0966 setPart(webkitPart); 0967 // Connect all the signals from this page to the slots in the new part. 0968 webkitPart->connectWebPageSignals(this); 0969 //Set the create new window flag to false... 0970 m_createNewWindow = false; 0971 } 0972 0973 return WebPage::acceptNavigationRequest(frame, request, type); 0974 } 0975 0976 void NewWindowPage::slotGeometryChangeRequested(const QRect & rect) 0977 { 0978 if (!rect.isValid()) 0979 return; 0980 0981 if (!m_createNewWindow) { 0982 WebPage::slotGeometryChangeRequested(rect); 0983 return; 0984 } 0985 0986 m_windowArgs.setX(rect.x()); 0987 m_windowArgs.setY(rect.y()); 0988 m_windowArgs.setWidth(qMax(rect.width(), 100)); 0989 m_windowArgs.setHeight(qMax(rect.height(), 100)); 0990 } 0991 0992 void NewWindowPage::slotMenuBarVisibilityChangeRequested(bool visible) 0993 { 0994 //qCDebug(KWEBKITPART_LOG) << visible; 0995 m_windowArgs.setMenuBarVisible(visible); 0996 } 0997 0998 void NewWindowPage::slotStatusBarVisibilityChangeRequested(bool visible) 0999 { 1000 //qCDebug(KWEBKITPART_LOG) << visible; 1001 m_windowArgs.setStatusBarVisible(visible); 1002 } 1003 1004 void NewWindowPage::slotToolBarVisibilityChangeRequested(bool visible) 1005 { 1006 //qCDebug(KWEBKITPART_LOG) << visible; 1007 m_windowArgs.setToolBarsVisible(visible); 1008 } 1009 1010 void NewWindowPage::slotLoadFinished(bool ok) 1011 { 1012 Q_UNUSED(ok) 1013 //qCDebug(KWEBKITPART_LOG) << ok; 1014 if (!m_createNewWindow) 1015 return; 1016 1017 // Browser args... 1018 KParts::BrowserArguments bargs; 1019 bargs.frameName = mainFrame()->frameName(); 1020 if (m_type == WebModalDialog) 1021 bargs.setForcesNewWindow(true); 1022 1023 // OpenUrl args... 1024 KParts::OpenUrlArguments uargs; 1025 uargs.setMimeType(QL1S("text/html")); 1026 uargs.setActionRequestedByUser(m_disableJSOpenwindowCheck); 1027 1028 // Window args... 1029 KParts::WindowArgs wargs (m_windowArgs); 1030 1031 KParts::ReadOnlyPart* newWindowPart =nullptr; 1032 part()->browserExtension()->createNewWindow(QUrl(), uargs, bargs, wargs, &newWindowPart); 1033 1034 qCDebug(KWEBKITPART_LOG) << "Created new window" << newWindowPart; 1035 1036 // Get the webview... 1037 KWebKitPart* webkitPart = newWindowPart ? qobject_cast<KWebKitPart*>(newWindowPart) : nullptr; 1038 WebView* webView = webkitPart ? qobject_cast<WebView*>(webkitPart->view()) : nullptr; 1039 1040 if (webView) { 1041 // if a new window is created, set a new window meta-data flag. 1042 if (newWindowPart->widget()->topLevelWidget() != part()->widget()->topLevelWidget()) { 1043 KParts::OpenUrlArguments args; 1044 args.metaData().insert(QL1S("new-window"), QL1S("true")); 1045 newWindowPart->setArguments(args); 1046 } 1047 // Reparent this page to the new webview to prevent memory leaks. 1048 setParent(webView); 1049 // Replace the webpage of the new webview with this one. Nice trick... 1050 webView->setPage(this); 1051 // Set the new part as the one this page will use going forward. 1052 setPart(webkitPart); 1053 // Connect all the signals from this page to the slots in the new part. 1054 webkitPart->connectWebPageSignals(this); 1055 } 1056 1057 //Set the create new window flag to false... 1058 m_createNewWindow = false; 1059 } 1060 1061 /****************************** End NewWindowPage *************************************************/