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 *************************************************/