webenginepage.cpp 39.1 KB
Newer Older
1
2
3
4
/*
 * This file is part of the KDE project.
 *
 * Copyright (C) 2008 Dirk Mueller <mueller@kde.org>
5
 * Copyright (C) 2008 - 2010 Urs Wolfer <uwolfer @ kde.org>
6
 * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org>
7
 *
8
9
10
11
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
12
 *
13
14
15
16
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 * details.
17
 *
18
19
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20
21
22
 *
 */

23
#include "webenginepage.h"
24

25
#include "webenginepart.h"
26
#include "webengineview.h"
27
#include "settings/webenginesettings.h"
28
#include "webenginepartdownloadmanager.h"
29
#include "webenginewallet.h"
30
31
#include <webenginepart_debug.h>

Christophe Giboudeaux's avatar
Christophe Giboudeaux committed
32
#include <QWebEngineCertificateError>
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
33
34
#include <QWebEngineSettings>
#include <QWebEngineProfile>
35
#include <KDialogJobUiDelegate>
36

37
38
39
40
41
42
43
#include <KMessageBox>
#include <KLocalizedString>
#include <KShell>
#include <KAuthorized>
#include <KStringHandler>
#include <KUrlAuthorized>
#include <KSharedConfig>
44
#include <KIO/AuthInfo>
45
#include <KIO/Job>
46
#include <KIO/AccessManager>
47
#include <KIO/Scheduler>
48
#include <KIO/CommandLauncherJob>
49
#include <KParts/HtmlExtension>
50
51
#include <KUserTimestamp>
#include <KPasswdServerClient>
52
#include <KParts/BrowserInterface>
53

54
55
#include <QStandardPaths>
#include <QDesktopWidget>
56
#include <QFileDialog>
Laurent Montel's avatar
Laurent Montel committed
57

Dawit Alemayehu's avatar
Dawit Alemayehu committed
58
#include <QFile>
59
#include <QAuthenticator>
Dawit Alemayehu's avatar
Dawit Alemayehu committed
60
61
#include <QApplication>
#include <QNetworkReply>
62
63
#include <QWebEngineHistory>
#include <QWebEngineHistoryItem>
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
64
#include <QWebEngineDownloadItem>
65
66
#include <QUrlQuery>
#include <KConfigGroup>
67
#include <KToggleFullScreenAction>
68
//#include <QWebSecurityOrigin>
69
#include "utils.h"
70

71

72
WebEnginePage::WebEnginePage(WebEnginePart *part, QWidget *parent)
73
        : QWebEnginePage(parent),
74
75
         m_kioErrorCode(0),
         m_ignoreError(false),
76
         m_part(part),
77
         m_passwdServerClient(new KPasswdServerClient),
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
78
         m_wallet(nullptr)
79
{
80
    if (view())
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
81
        WebEngineSettings::self()->computeFontSizes(view()->logicalDpiY());
82

83
    //setForwardUnsupportedContent(true);
Dawit Alemayehu's avatar
Dawit Alemayehu committed
84

Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
85
    connect(this, &QWebEnginePage::geometryChangeRequested,
86
            this, &WebEnginePage::slotGeometryChangeRequested);
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
87
88
89
//    connect(this, SIGNAL(unsupportedContent(QNetworkReply*)),
//            this, SLOT(slotUnsupportedContent(QNetworkReply*)));
    connect(this, &QWebEnginePage::featurePermissionRequested,
90
            this, &WebEnginePage::slotFeaturePermissionRequested);
91
    connect(this, &QWebEnginePage::loadFinished,
92
            this, &WebEnginePage::slotLoadFinished);
93
94
    connect(this, &QWebEnginePage::authenticationRequired,
            this, &WebEnginePage::slotAuthenticationRequired);
95
    connect(this, &QWebEnginePage::fullScreenRequested, this, &WebEnginePage::changeFullScreenMode);
96
    if(!this->profile()->httpUserAgent().contains(QLatin1String("Konqueror")))
97
98
99
    {
        this->profile()->setHttpUserAgent(this->profile()->httpUserAgent() + " Konqueror (WebEnginePart)");
    }
100

101
    WebEnginePartDownloadManager::instance()->addPage(this);
102

103
    m_wallet = new WebEngineWallet(this, parent ? parent->window()->winId() : 0);
104
105
}

106
WebEnginePage::~WebEnginePage()
107
{
108
    //qCDebug(WEBENGINEPART_LOG) << this;
109
110
}

111
const WebSslInfo& WebEnginePage::sslInfo() const
112
{
113
    return m_sslInfo;
114
115
}

116
void WebEnginePage::setSslInfo (const WebSslInfo& info)
117
{
118
    m_sslInfo = info;
119
120
}

121
122
123
static void checkForDownloadManager(QWidget* widget, QString& cmd)
{
    cmd.clear();
124
    KConfigGroup cfg (KSharedConfig::openConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals), "HTML Settings");
125
126
127
128
    const QString fileName (cfg.readPathEntry("DownloadManager", QString()));
    if (fileName.isEmpty())
        return;

129
    const QString exeName = QStandardPaths::findExecutable(fileName);
130
131
132
133
134
135
136
137
138
139
140
141
    if (exeName.isEmpty()) {
        KMessageBox::detailedSorry(widget,
                                   i18n("The download manager (%1) could not be found in your installation.", fileName),
                                   i18n("Try to reinstall it and make sure that it is available in $PATH. \n\nThe integration will be disabled."));
        cfg.writePathEntry("DownloadManager", QString());
        cfg.sync();
        return;
    }

    cmd = exeName;
}

142
void WebEnginePage::download(const QUrl& url, const QString& mimetype, bool newWindow)
143
144
145
{
    // Integration with a download manager...
    if (!url.isLocalFile()) {
146
147
148
        QString managerExe;
        checkForDownloadManager(view(), managerExe);
        if (!managerExe.isEmpty()) {
149
            //qCDebug(WEBENGINEPART_LOG) << "Calling command" << cmd;
150
151
152
            KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(managerExe, {url.toString()});
            job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, view()));
            job->start();
153
            return;
154
155
        }
    }
156
157
    KParts::BrowserArguments bArgs;
    bArgs.setForcesNewWindow(newWindow);
158
159
160
    KParts::OpenUrlArguments urlArgs;
    urlArgs.setMimeType(mimetype);
    emit part()->browserExtension()->openUrlRequest(url, urlArgs, bArgs);
161
162
}

163
164
165
166
167
168
169
170
171
172
void WebEnginePage::requestOpenFileAsTemporary(const QUrl& url, const QString &mimeType, bool newWindow)
{
    KParts::BrowserArguments bArgs;
    bArgs.setForcesNewWindow(newWindow);
    KParts::OpenUrlArguments oArgs;
    oArgs.setMimeType(mimeType);
    oArgs.metaData().insert("konq-temp-file", "1");
    emit part()->browserExtension()->openUrlRequest(url, oArgs, bArgs);
}

173
QWebEnginePage *WebEnginePage::createWindow(WebWindowType type)
174
{
175
    //qCDebug(WEBENGINEPART_LOG) << "window type:" << type;
176
    // Crete an instance of NewWindowPage class to capture all the
177
178
    // information we need to create a new window. See documentation of
    // the class for more information...
179
    NewWindowPage* page = new NewWindowPage(type, part());
180
    return page;
181
182
}

183
184
185
186
187
188
// Returns true if the scheme and domain of the two urls match...
static bool domainSchemeMatch(const QUrl& u1, const QUrl& u2)
{
    if (u1.scheme() != u2.scheme())
        return false;

Laurent Montel's avatar
Laurent Montel committed
189
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
190
    QStringList u1List = u1.host().split(QL1C('.'), QString::SkipEmptyParts);
Laurent Montel's avatar
Laurent Montel committed
191
192
193
194
#else
    QStringList u1List = u1.host().split(QL1C('.'), Qt::SkipEmptyParts);
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
195
    QStringList u2List = u2.host().split(QL1C('.'), QString::SkipEmptyParts);
Laurent Montel's avatar
Laurent Montel committed
196
197
198
#else
    QStringList u2List = u2.host().split(QL1C('.'), Qt::SkipEmptyParts);
#endif
199
200
201
202
203
204
205
206
207
208
209
210
211

    if (qMin(u1List.count(), u2List.count()) < 2)
        return false;  // better safe than sorry...

    while (u1List.count() > 2)
        u1List.removeFirst();

    while (u2List.count() > 2)
        u2List.removeFirst();

    return (u1List == u2List);
}

212
bool WebEnginePage::acceptNavigationRequest(const QUrl& url, NavigationType type, bool isMainFrame)
213
{
214
215
    if (m_urlLoadedByPart != url) {
        m_urlLoadedByPart = QUrl();
216
217
218
219
220
221
222
223
        
        //Don't open local files using WebEnginePart except if configured to do so by the user. For example
        //for example, this ensures that the "Home" link in the introduction page is opened in Dolphin part 
        //(or whichever part the user has chosen to open directories instead of WebEnginePart
        if (url.isLocalFile()) {
            emit m_part->browserExtension()->openUrlRequest(url);
            return false;
        }
224
    }
225
//     qCDebug(WEBENGINEPART_LOG) << url << "type=" << type;
226
    QUrl reqUrl(url);
227
228
229

    // Handle "mailto:" url here...
    if (handleMailToUrl(reqUrl, type))
230
        return false;
231
232
233

    const bool isTypedUrl = property("NavigationTypeUrlEntered").toBool();

234
235
236
    /*
      NOTE: We use a dynamic QObject property called "NavigationTypeUrlEntered"
      to distinguish between requests generated by user entering a url vs those
237
      that were generated programmatically through javascript (AJAX requests).
238
    */
239
    if (isMainFrame && isTypedUrl)
240
241
      setProperty("NavigationTypeUrlEntered", QVariant());

242
243
244
245
    // inPage requests are those generarted within the current page through
    // link clicks, javascript queries, and button clicks (form submission).
    bool inPageRequest = true;
    switch (type) {
246
        case QWebEnginePage::NavigationTypeFormSubmitted:
247
248
            if (!checkFormData(url))
               return false;
249
250
251
252
            if (m_wallet) {
                m_wallet->saveFormsInPage(this);
            }

253
            break;
254
255
#if 0
        case QWebEnginePage::NavigationTypeFormResubmitted:
256
257
            if (!checkFormData(request))
                return false;
258
259
260
261
262
263
264
265
266
267
268
            if (KMessageBox::warningContinueCancel(view(),
                            i18n("<qt><p>To display the requested web page again, "
                                  "the browser needs to resend information you have "
                                  "previously submitted.</p>"
                                  "<p>If you were shopping online and made a purchase, "
                                  "click the Cancel button to prevent a duplicate purchase."
                                  "Otherwise, click the Continue button to display the web"
                                  "page again.</p>"),
                            i18n("Resubmit Information")) == KMessageBox::Cancel) {
                return false;
            }
269
            break;
270
271
#endif
        case QWebEnginePage::NavigationTypeBackForward:
272
273
274
            // If history navigation is locked, ignore all such requests...
            if (property("HistoryNavigationLocked").toBool()) {
                setProperty("HistoryNavigationLocked", QVariant());
275
                qCDebug(WEBENGINEPART_LOG) << "Rejected history navigation because 'HistoryNavigationLocked' property is set!";
276
277
                return false;
            }
278
            //qCDebug(WEBENGINEPART_LOG) << "Navigating to item (" << history()->currentItemIndex()
279
            //         << "of" << history()->count() << "):" << history()->currentItem().url();
280
281
            inPageRequest = false;
            break;
282
283
        case QWebEnginePage::NavigationTypeReload:
//            setRequestMetaData(QL1S("cache"), QL1S("reload"));
284
            inPageRequest = false;
285
            break;
286
        case QWebEnginePage::NavigationTypeOther: // triggered by javascript
287
            qCDebug(WEBENGINEPART_LOG) << "Triggered by javascript";
288
            inPageRequest = !isTypedUrl;
289
290
291
            break;
        default:
            break;
292
    }
293

294
295
296
    if (inPageRequest) {
        // if (!checkLinkSecurity(request, type))
        //      return false;
297

298
299
        //  if (m_sslInfo.isValid())
        //      setRequestMetaData(QL1S("ssl_was_in_use"), QL1S("TRUE"));
300
    }
301

302

303
    // Honor the enabling/disabling of plugins per host.
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
304
    settings()->setAttribute(QWebEngineSettings::PluginsEnabled, WebEngineSettings::self()->isPluginsEnabled(reqUrl.host()));
305

306
    return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame);
307
}
308

309
#if 0
310
311
312
313
314
315
316
317
318
319
static int errorCodeFromReply(QNetworkReply* reply)
{
    // First check if there is a KIO error code sent back and use that,
    // if not attempt to convert QNetworkReply's NetworkError to KIO::Error.
    QVariant attr = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::KioError));
    if (attr.isValid() && attr.type() == QVariant::Int)
        return attr.toInt();

    switch (reply->error()) {
        case QNetworkReply::ConnectionRefusedError:
Laurent Montel's avatar
Laurent Montel committed
320
            return KIO::ERR_CANNOT_CONNECT;
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
        case QNetworkReply::HostNotFoundError:
            return KIO::ERR_UNKNOWN_HOST;
        case QNetworkReply::TimeoutError:
            return KIO::ERR_SERVER_TIMEOUT;
        case QNetworkReply::OperationCanceledError:
            return KIO::ERR_USER_CANCELED;
        case QNetworkReply::ProxyNotFoundError:
            return KIO::ERR_UNKNOWN_PROXY_HOST;
        case QNetworkReply::ContentAccessDenied:
            return KIO::ERR_ACCESS_DENIED;
        case QNetworkReply::ContentOperationNotPermittedError:
            return KIO::ERR_WRITE_ACCESS_DENIED;
        case QNetworkReply::ContentNotFoundError:
            return KIO::ERR_NO_CONTENT;
        case QNetworkReply::AuthenticationRequiredError:
Laurent Montel's avatar
Laurent Montel committed
336
            return KIO::ERR_CANNOT_AUTHENTICATE;
337
338
339
340
341
342
343
344
345
346
347
348
349
        case QNetworkReply::ProtocolUnknownError:
            return KIO::ERR_UNSUPPORTED_PROTOCOL;
        case QNetworkReply::ProtocolInvalidOperationError:
            return KIO::ERR_UNSUPPORTED_ACTION;
        case QNetworkReply::UnknownNetworkError:
            return KIO::ERR_UNKNOWN;
        case QNetworkReply::NoError:
        default:
            break;
    }

    return 0;
}
350
#endif
351

352
353
bool WebEnginePage::certificateError(const QWebEngineCertificateError& ce)
{
354
    if (ce.isOverridable()) {
355
356
357
358
359
360
361
362
363
364
        QString translatedDesc = i18n(ce.errorDescription().toUtf8());
        QString text = i18n("<p>The server failed the authenticity check (%1). The error is:</p><p><tt>%2</tt></p>Do you want to ignore this error?",
                            ce.url().host(), translatedDesc);
        KMessageBox::ButtonCode ans = KMessageBox::questionYesNo(view(), text, i18n("Authentication error"));
        return ans == KMessageBox::Yes;
    } else {
        return false;
    }
}

365
WebEnginePart* WebEnginePage::part() const
366
{
367
    return m_part.data();
368
369
}

370
void WebEnginePage::setPart(WebEnginePart* part)
371
{
Dawit Alemayehu's avatar
Dawit Alemayehu committed
372
    m_part = part;
373
374
}

375
void WebEnginePage::slotLoadFinished(bool ok)
Urs Wolfer's avatar
Urs Wolfer committed
376
{
377
    QUrl requestUrl = url();
378
    requestUrl.setUserInfo(QString());
379
#if 0
380
    const bool shouldResetSslInfo = (m_sslInfo.isValid() && !domainSchemeMatch(requestUrl, m_sslInfo.url()));
381
382
383
    QWebFrame* frame = qobject_cast<QWebFrame *>(reply->request().originatingObject());
    if (!frame)
        return;
384
385
386
387
388
    const bool isMainFrameRequest = (frame == mainFrame());
#else
    // PORTING_TODO
    const bool isMainFrameRequest = true;
#endif
389

390
#if 0
391
    // Only deal with non-redirect responses...
392
    const QVariant redirectVar = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
393
394

    if (isMainFrameRequest && redirectVar.isValid()) {
395
        m_sslInfo.restoreFrom(reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)),
396
                              reply->url(), shouldResetSslInfo);
397
398
399
400
        return;
    }

    const int errCode = errorCodeFromReply(reply);
401
    qCDebug(WEBENGINEPART_LOG) << frame << "is main frame request?" << isMainFrameRequest << requestUrl;
402
403
404
405
406
407
408
409
410
411
412
#endif

    if (ok) {
        if (isMainFrameRequest) {
#if 0
            m_sslInfo.restoreFrom(reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)),
                    reply->url(), shouldResetSslInfo);
#endif
            setPageJScriptPolicy(url());
        }
    } else {
413
    // Handle any error...
414
#if 0
415
416
    switch (errCode) {
        case 0:
417
        case KIO::ERR_NO_CONTENT:
418
419
420
            break;
        case KIO::ERR_ABORTED:
        case KIO::ERR_USER_CANCELED: // Do nothing if request is cancelled/aborted
421
            //qCDebug(WEBENGINEPART_LOG) << "User aborted request!";
422
            m_ignoreError = true;
423
424
425
426
427
            emit loadAborted(QUrl());
            return;
        // Handle the user clicking on a link that refers to a directory
        // Since KIO cannot automatically convert a GET request to a LISTDIR one.
        case KIO::ERR_IS_DIRECTORY:
428
            m_ignoreError = true;
429
430
431
432
433
434
435
            emit loadAborted(reply->url());
            return;
        default:
            // Make sure the saveFrameStateRequested signal is emitted so
            // the page can restored properly.
            if (isMainFrameRequest)
                emit saveFrameStateRequested(frame, 0);
436

437
            m_ignoreError = (reply->attribute(QNetworkRequest::User).toInt() == QNetworkReply::ContentAccessDenied);
438
            m_kioErrorCode = errCode;
439
            break;
440
#endif
441
    }
442

443
    if (isMainFrameRequest) {
444
        const WebEnginePageSecurity security = (m_sslInfo.isValid() ? PageEncrypted : PageUnencrypted);
Dawit Alemayehu's avatar
Dawit Alemayehu committed
445
        emit m_part->browserExtension()->setPageSecurity(security);
446
447
448
    }
}

449
void WebEnginePage::slotUnsupportedContent(QNetworkReply* reply)
450
{
451
#if 0
452
    //qCDebug(WEBENGINEPART_LOG) << reply->url();
453
454
455
456
    QString mimeType;
    KIO::MetaData metaData;

    KIO::AccessManager::putReplyOnHold(reply);
457
458
459
460
461
    QString downloadCmd;
    checkForDownloadManager(view(), downloadCmd);
    if (!downloadCmd.isEmpty()) {
        reply->setProperty("DownloadManagerExe", downloadCmd);
    }
462

463
    if (QWePage::handleReply(reply, &mimeType, &metaData)) {
464
        reply->deleteLater();
Dawit Alemayehu's avatar
Dawit Alemayehu committed
465
466
467
468
        if (qobject_cast<NewWindowPage*>(this) && isBlankUrl(m_part->url())) {
            m_part->closeUrl();
            if (m_part->arguments().metaData().contains(QL1S("new-window"))) {
                m_part->widget()->topLevelWidget()->close();
469
            } else {
Dawit Alemayehu's avatar
Dawit Alemayehu committed
470
                delete m_part;
471
            }
472
        }
473
474
475
        return;
    }

476
    //qCDebug(WEBENGINEPART_LOG) << "mimetype=" << mimeType << "metadata:" << metaData;
477
478
479
480
481

    if (reply->request().originatingObject() == this->mainFrame()) {
        KParts::OpenUrlArguments args;
        args.setMimeType(mimeType);
        args.metaData() = metaData;
Dawit Alemayehu's avatar
Dawit Alemayehu committed
482
        emit m_part->browserExtension()->openUrlRequest(reply->url(), args, KParts::BrowserArguments());
483
484
        return;
    }
485
#endif
486
    reply->deleteLater();
487

488
}
489
void WebEnginePage::slotFeaturePermissionRequested(const QUrl& url, QWebEnginePage::Feature feature)
490
{
491
492
493
494
495
    //url.path() is always / (meaning that permissions should be granted site-wide and not per page)
    QUrl thisUrl(this->url());
    thisUrl.setPath("/");
    if (url == thisUrl) {
        part()->slotShowFeaturePermissionBar(url, feature);
496
497
498
        return;
    }
    switch(feature) {
499
    case QWebEnginePage::Notifications:
500
        // FIXME: We should have a setting to tell if this is enabled, but so far it is always enabled.
501
        setFeaturePermission(url, feature, QWebEnginePage::PermissionGrantedByUser);
502
        break;
503
    case QWebEnginePage::Geolocation:
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
504
        if (KMessageBox::warningContinueCancel(nullptr, i18n("This site is attempting to "
505
506
507
508
509
510
                                                       "access information about your "
                                                       "physical location.\n"
                                                       "Do you want to allow it access?"),
                                            i18n("Network Transmission"),
                                            KGuiItem(i18n("Allow access")),
                                            KStandardGuiItem::cancel(),
511
                                            QStringLiteral("WarnGeolocation")) == KMessageBox::Cancel) {
512
            setFeaturePermission(url, feature, QWebEnginePage::PermissionDeniedByUser);
513
        } else {
514
            setFeaturePermission(url, feature, QWebEnginePage::PermissionGrantedByUser);
515
516
517
        }
        break;
    default:
518
        setFeaturePermission(url, feature, QWebEnginePage::PermissionUnknown);
519
520
521
522
        break;
    }
}

523
void WebEnginePage::slotGeometryChangeRequested(const QRect & rect)
524
{
525
    const QString host = url().host();
526
527
528

    // NOTE: If a new window was created from another window which is in
    // maximized mode and its width and/or height were not specified at the
529
    // time of its creation, which is always the case in QWebEnginePage::createWindow,
530
531
    // then any move operation will seem not to work. That is because the new
    // window will be in maximized mode where moving it will not be possible...
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
532
    if (WebEngineSettings::self()->windowMovePolicy(host) == KParts::HtmlSettingsInterface::JSWindowMoveAllow &&
533
        (view()->x() != rect.x() || view()->y() != rect.y()))
Dawit Alemayehu's avatar
Dawit Alemayehu committed
534
        emit m_part->browserExtension()->moveTopLevelWidget(rect.x(), rect.y());
535
536
537
538
539
540
541

    const int height = rect.height();
    const int width = rect.width();

    // parts of following code are based on kjs_window.cpp
    // Security check: within desktop limits and bigger than 100x100 (per spec)
    if (width < 100 || height < 100) {
542
        qCWarning(WEBENGINEPART_LOG) << "Window resize refused, window would be too small (" << width << "," << height << ")";
543
544
545
        return;
    }

546
    QRect sg = QApplication::desktop()->screenGeometry(view());
547
548

    if (width > sg.width() || height > sg.height()) {
549
        qCWarning(WEBENGINEPART_LOG) << "Window resize refused, window would be too big (" << width << "," << height << ")";
550
551
552
        return;
    }

Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
553
    if (WebEngineSettings::self()->windowResizePolicy(host) == KParts::HtmlSettingsInterface::JSWindowResizeAllow) {
554
        //qCDebug(WEBENGINEPART_LOG) << "resizing to " << width << "x" << height;
Dawit Alemayehu's avatar
Dawit Alemayehu committed
555
        emit m_part->browserExtension()->resizeTopLevelWidget(width, height);
556
557
558
559
560
561
562
563
564
565
566
567
    }

    // If the window is out of the desktop, move it up/left
    // (maybe we should use workarea instead of sg, otherwise the window ends up below kicker)
    const int right = view()->x() + view()->frameGeometry().width();
    const int bottom = view()->y() + view()->frameGeometry().height();
    int moveByX = 0, moveByY = 0;
    if (right > sg.right())
        moveByX = - right + sg.right(); // always <0
    if (bottom > sg.bottom())
        moveByY = - bottom + sg.bottom(); // always <0

Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
568
    if ((moveByX || moveByY) && WebEngineSettings::self()->windowMovePolicy(host) == KParts::HtmlSettingsInterface::JSWindowMoveAllow)
Dawit Alemayehu's avatar
Dawit Alemayehu committed
569
        emit m_part->browserExtension()->moveTopLevelWidget(view()->x() + moveByX, view()->y() + moveByY);
570
571
}

572
bool WebEnginePage::checkLinkSecurity(const QNetworkRequest &req, NavigationType type) const
573
574
{
    // Check whether the request is authorized or not...
575
    if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), url(), req.url())) {
576

577
        //qCDebug(WEBENGINEPART_LOG) << "*** Failed security check: base-url=" << mainFrame()->url() << ", dest-url=" << req.url();
578
579
580
        QString buttonText, title, message;

        int response = KMessageBox::Cancel;
Dániel Grósz's avatar
Dániel Grósz committed
581
        QUrl linkUrl (req.url());
582

583
        if (type == QWebEnginePage::NavigationTypeLinkClicked) {
584
585
586
            message = i18n("<qt>This untrusted page links to<br/><b>%1</b>."
                           "<br/>Do you want to follow the link?</qt>", linkUrl.url());
            title = i18n("Security Warning");
587
            buttonText = i18nc("follow link despite of security warning", "Follow");
588
589
590
        } else {
            title = i18n("Security Alert");
            message = i18n("<qt>Access by untrusted page to<br/><b>%1</b><br/> denied.</qt>",
591
                           linkUrl.toDisplayString().toHtmlEscaped());
592
593
594
        }

        if (buttonText.isEmpty()) {
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
595
            KMessageBox::error( nullptr, message, title);
596
597
        } else {
            // Dangerous flag makes the Cancel button the default
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
598
            response = KMessageBox::warningContinueCancel(nullptr, message, title,
599
600
601
602
603
604
605
606
607
608
609
610
                                                          KGuiItem(buttonText),
                                                          KStandardGuiItem::cancel(),
                                                          QString(), // no don't ask again info
                                                          KMessageBox::Notify | KMessageBox::Dangerous);
        }

        return (response == KMessageBox::Continue);
    }

    return true;
}

611
bool WebEnginePage::checkFormData(const QUrl &url) const
612
{
613
    const QString scheme (url.scheme());
614

615
    if (m_sslInfo.isValid() &&
616
        !scheme.compare(QL1S("https")) && !scheme.compare(QL1S("mailto")) &&
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
617
        (KMessageBox::warningContinueCancel(nullptr,
618
619
620
621
622
623
                                           i18n("Warning: This is a secure form "
                                                "but it is attempting to send "
                                                "your data back unencrypted.\n"
                                                "A third party may be able to "
                                                "intercept and view this "
                                                "information.\nAre you sure you "
624
                                                "want to send the data unencrypted?"),
625
626
627
628
629
630
631
                                           i18n("Network Transmission"),
                                           KGuiItem(i18n("&Send Unencrypted")))  == KMessageBox::Cancel)) {

        return false;
    }


632
    if (scheme.compare(QL1S("mailto")) == 0 &&
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
633
        (KMessageBox::warningContinueCancel(nullptr, i18n("This site is attempting to "
634
635
636
637
638
                                                    "submit form data via email.\n"
                                                    "Do you want to continue?"),
                                            i18n("Network Transmission"),
                                            KGuiItem(i18n("&Send Email")),
                                            KStandardGuiItem::cancel(),
639
                                            QStringLiteral("WarnTriedEmailSubmit")) == KMessageBox::Cancel)) {
640
641
642
643
644
645
        return false;
    }

    return true;
}

646
647
648
649
650
651
652
653
654
655
656
// Sanitizes the "mailto:" url, e.g. strips out any "attach" parameters.
static QUrl sanitizeMailToUrl(const QUrl &url, QStringList& files) {
    QUrl sanitizedUrl;

    // NOTE: This is necessary to ensure we can properly use QUrl's query
    // related APIs to process 'mailto:' urls of form 'mailto:foo@bar.com'.
    if (url.hasQuery())
      sanitizedUrl = url;
    else
      sanitizedUrl = QUrl(url.scheme() + QL1S(":?") + url.path());

657
658
    QUrlQuery query(sanitizedUrl);
    const QList<QPair<QString, QString> > items (query.queryItems());
659

660
661
    QUrlQuery sanitizedQuery;
    for(auto queryItem : items) {
662
        if (queryItem.first.contains(QL1C('@')) && queryItem.second.isEmpty()) {
663
            // ### DF: this hack breaks mailto:faure@kde.org, kmail doesn't expect mailto:?to=faure@kde.org
664
            queryItem.second = queryItem.first;
665
            queryItem.first = QStringLiteral("to");
666
667
668
669
        } else if (QString::compare(queryItem.first, QL1S("attach"), Qt::CaseInsensitive) == 0) {
            files << queryItem.second;
            continue;
        }
670
        sanitizedQuery.addQueryItem(queryItem.first, queryItem.second);
671
672
    }

673
    sanitizedUrl.setQuery(sanitizedQuery);
674
675
676
    return sanitizedUrl;
}

677
bool WebEnginePage::handleMailToUrl (const QUrl &url, NavigationType type) const
678
{
679
    if (url.scheme() == QL1S("mailto")) {
680
681
682
683
        QStringList files;
        QUrl mailtoUrl (sanitizeMailToUrl(url, files));

        switch (type) {
684
            case QWebEnginePage::NavigationTypeLinkClicked:
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
685
                if (!files.isEmpty() && KMessageBox::warningContinueCancelList(nullptr,
686
                                                                               i18n("<qt>Do you want to allow this site to attach "
Pino Toscano's avatar
Pino Toscano committed
687
                                                                                    "the following files to the email message?</qt>"),
688
                                                                               files, i18n("Email Attachment Confirmation"),
689
                                                                               KGuiItem(i18n("&Allow attachments")),
690
                                                                               KGuiItem(i18n("&Ignore attachments")), QL1S("WarnEmailAttachment")) == KMessageBox::Continue) {
691

692
                   // Re-add the attachments...
693
                    QStringListIterator filesIt (files);
694
                    QUrlQuery query(mailtoUrl);
695
                    while (filesIt.hasNext()) {
696
                        query.addQueryItem(QL1S("attach"), filesIt.next());
697
                    }
698
                    mailtoUrl.setQuery(query);
699
700
                }
                break;
701
702
            case QWebEnginePage::NavigationTypeFormSubmitted:
            //case QWebEnginePage::NavigationTypeFormResubmitted:
703
                if (!files.isEmpty()) {
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
704
                    KMessageBox::information(nullptr, i18n("This site attempted to attach a file from your "
705
706
                                                     "computer in the form submission. The attachment "
                                                     "was removed for your protection."),
707
                                             i18n("Attachment Removed"), QStringLiteral("InfoTriedAttach"));
708
709
710
711
712
713
                }
                break;
            default:
                 break;
        }

714
        //qCDebug(WEBENGINEPART_LOG) << "Emitting openUrlRequest with " << mailtoUrl;
Dawit Alemayehu's avatar
Dawit Alemayehu committed
715
        emit m_part->browserExtension()->openUrlRequest(mailtoUrl);
716
        return true;
717
    }
718
719
720
721

    return false;
}

722
void WebEnginePage::setPageJScriptPolicy(const QUrl &url)
723
724
{
    const QString hostname (url.host());
725
    settings()->setAttribute(QWebEngineSettings::JavascriptEnabled,
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
726
                             WebEngineSettings::self()->isJavaScriptEnabled(hostname));
727

Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
728
    const KParts::HtmlSettingsInterface::JSWindowOpenPolicy policy = WebEngineSettings::self()->windowOpenPolicy(hostname);
729
    settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows,
730
731
                             (policy != KParts::HtmlSettingsInterface::JSWindowOpenDeny &&
                              policy != KParts::HtmlSettingsInterface::JSWindowOpenSmart));
732
}
733

734
735
736
737
738
739
740
741
742
void WebEnginePage::slotAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth)
{
    KIO::AuthInfo info;
    info.url = requestUrl;
    info.username = auth->user();
    info.realmValue = auth->realm();
    // If no realm metadata, then make sure path matching is turned on.
    info.verifyPath = info.realmValue.isEmpty();

Pino Toscano's avatar
Pino Toscano committed
743
    const QString errorMsg = QString();
744
745
746
747
748
749
750
751
752
753
    const int ret = m_passwdServerClient->queryAuthInfo(&info, errorMsg, view()->window()->winId(), KUserTimestamp::userTimestamp());
    if (ret == KJob::NoError) {
        auth->setUser(info.username);
        auth->setPassword(info.password);
    } else {
        // Set authenticator null if dialog is cancelled
        // or if we couldn't communicate with kpasswdserver
        *auth = QAuthenticator();
    }
}
754

755
756
757
758
759
760
761
762
763
764
765
766
void WebEnginePage::changeFullScreenMode(QWebEngineFullScreenRequest req)
{
        KParts::BrowserInterface *iface = part()->browserExtension()->browserInterface();
        if (iface) {
            req.accept();
            iface->callMethod("toggleCompleteFullScreen", req.toggleOn());
        } else {
            req.reject();
        }
}


Stefano Crocco's avatar
Stefano Crocco committed
767
768
769
770
771
772
void WebEnginePage::setStatusBarText(const QString& text)
{
    if (m_part) {
        emit m_part->setStatusBarText(text);
    }
}
773
774
775

/************************************* Begin NewWindowPage ******************************************/

776
NewWindowPage::NewWindowPage(WebWindowType type, WebEnginePart* part, QWidget* parent)
777
              :WebEnginePage(part, parent) , m_type(type) , m_createNewWindow(true)
778
779
780
{
    Q_ASSERT_X (part, "NewWindowPage", "Must specify a valid KPart");

781
    // FIXME: are these 3 signals actually defined or used?
782
783
784
785
786
787
    connect(this, SIGNAL(menuBarVisibilityChangeRequested(bool)),
            this, SLOT(slotMenuBarVisibilityChangeRequested(bool)));
    connect(this, SIGNAL(toolBarVisibilityChangeRequested(bool)),
            this, SLOT(slotToolBarVisibilityChangeRequested(bool)));
    connect(this, SIGNAL(statusBarVisibilityChangeRequested(bool)),
            this, SLOT(slotStatusBarVisibilityChangeRequested(bool)));
788
    connect(this, &QWebEnginePage::loadFinished, this, &NewWindowPage::slotLoadFinished);
789
790
791
    if (m_type == WebBrowserBackgroundTab) {
        m_windowArgs.setLowerWindow(true);
    }
792
793
794
795
}

NewWindowPage::~NewWindowPage()
{
796
797
}

798
static KParts::BrowserArguments browserArgs(WebEnginePage::WebWindowType type)
799
800
801
{
    KParts::BrowserArguments bargs;
    switch (type) {
802
803
        case WebEnginePage::WebDialog:
        case WebEnginePage::WebBrowserWindow:
804
805
            bargs.setForcesNewWindow(true);
            break;
806
807
        case WebEnginePage::WebBrowserTab:
        case WebEnginePage::WebBrowserBackgroundTab:
808
809
810
811
812
            // let konq decide, based on user configuration
            //bargs.setNewTab(true);
            break;
    }
    return bargs;
813
}
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
814
815

bool NewWindowPage::acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame)
816
{
817
    //qCDebug(WEBENGINEPART_LOG) << "url:" << url << ", type:" << type << ", isMainFrame:" << isMainFrame << "m_createNewWindow=" << m_createNewWindow;
818
    if (m_createNewWindow) {
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
819
        const QUrl reqUrl (url);
820

821
        const bool actionRequestedByUser = type != QWebEnginePage::NavigationTypeOther;
822
823
        const bool actionRequestsNewTab = m_type == QWebEnginePage::WebBrowserBackgroundTab ||
                                          m_type == QWebEnginePage::WebBrowserTab;
824

825
        if (actionRequestedByUser && !actionRequestsNewTab) {
826
827
828
            if (!part() && !isMainFrame) {
                return false;
            }
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
829
            const KParts::HtmlSettingsInterface::JSWindowOpenPolicy policy = WebEngineSettings::self()->windowOpenPolicy(reqUrl.host());
830
            switch (policy) {
831
            case KParts::HtmlSettingsInterface::JSWindowOpenDeny:
832
                // TODO: Implement support for dealing with blocked pop up windows.
833
834
                this->deleteLater();
                return false;
835
            case KParts::HtmlSettingsInterface::JSWindowOpenAsk: {
836
                const QString message = (reqUrl.isEmpty() ?
837
                                          i18n("This site is requesting to open a new popup window.\n"
838
839
840
                                               "Do you want to allow this?") :
                                          i18n("<qt>This site is requesting to open a popup window to"
                                               "<p>%1</p><br/>Do you want to allow this?</qt>",
841
                                               KStringHandler::rsqueeze(reqUrl.toDisplayString().toHtmlEscaped(), 100)));
842
843
844
                if (KMessageBox::questionYesNo(view(), message,
                                               i18n("Javascript Popup Confirmation"),
                                               KGuiItem(i18n("Allow")),
845
846
                                               KGuiItem(i18n("Do Not Allow"))) == KMessageBox::No) {
                    // TODO: Implement support for dealing with blocked pop up windows.
847
848
849
                    this->deleteLater();
                    return false;
                }
850
               break;
851
852
853
854
855
856
            }
            default:
                break;
            }
        }

857
        // Browser args...
858
        KParts::BrowserArguments bargs = browserArgs(m_type);
859

860
861
        // OpenUrl args...
        KParts::OpenUrlArguments uargs;
862
        uargs.setMimeType(QL1S("text/html"));
863
        uargs.setActionRequestedByUser(actionRequestedByUser);
864

865
866
        // Window args...
        KParts::WindowArgs wargs (m_windowArgs);
867

Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
868
        KParts::ReadOnlyPart* newWindowPart =nullptr;
869
        emit part()->browserExtension()->createNewWindow(QUrl(), uargs, bargs, wargs, &newWindowPart);
870
        qCDebug(WEBENGINEPART_LOG) << "Created new window" << newWindowPart;
871

872
        if (!newWindowPart) {
873
            return false;
874
875
876
877
878
        } else if (newWindowPart->widget()->topLevelWidget() != part()->widget()->topLevelWidget()) {
            KParts::OpenUrlArguments args;
            args.metaData().insert(QL1S("new-window"), QL1S("true"));
            newWindowPart->setArguments(args);
        }
879

880
        // Get the webview...
Sune Stolborg Vuorela's avatar
Sune Stolborg Vuorela committed
881
        WebEnginePart* webenginePart = qobject_cast<WebEnginePart*>(newWindowPart);
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
882
        WebEngineView* webView = webenginePart ? qobject_cast<WebEngineView*>(webenginePart->view()) : nullptr;
883