summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKirill Burtsev <kirill.burtsev@qt.io>2019-08-14 18:59:23 +0200
committerKirill Burtsev <kirill.burtsev@qt.io>2019-09-05 23:32:55 +0200
commitbf3753f02402b44455038c4fa2a897d41aadf850 (patch)
treee41ec261298a4f5eff8e7b5507226301eafa3a61
parent8213ea60bacb91ddfa1594e1f515abe2cfbde621 (diff)
Allow deferring QWebEngineCertificateError handling
Introduce defer() method for halting URL load on certificate errors, and methods for rejecting and ignoring these errors subsequently in async manner. [ChangeLog][QtWebEngineWidgets][QWebEngineCertificateError] New methods for asynchronous decision on certificate error during load. Fixes: QTBUG-55110 Change-Id: Ib23eb568862ccc360208922a6a581f8e7edc4a7e Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
-rw-r--r--examples/webenginewidgets/simplebrowser/webpage.cpp42
-rw-r--r--src/webenginewidgets/api/qwebenginecertificateerror.cpp97
-rw-r--r--src/webenginewidgets/api/qwebenginecertificateerror.h19
-rw-r--r--src/webenginewidgets/api/qwebenginepage.cpp8
-rw-r--r--src/webenginewidgets/api/qwebenginepage_p.h2
-rw-r--r--src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc2
-rw-r--r--tests/auto/shared/https.pri4
-rw-r--r--tests/auto/shared/httpserver.cpp31
-rw-r--r--tests/auto/shared/httpserver.h13
-rw-r--r--tests/auto/shared/httpsserver.h81
-rw-r--r--tests/auto/shared/httpsserver.qrc6
-rw-r--r--tests/auto/shared/resources/cert.pem34
-rw-r--r--tests/auto/shared/resources/key.pem52
-rw-r--r--tests/auto/widgets/certificateerror/certificateerror.pro3
-rw-r--r--tests/auto/widgets/certificateerror/tst_certificateerror.cpp122
-rw-r--r--tests/auto/widgets/widgets.pro1
16 files changed, 472 insertions, 45 deletions
diff --git a/examples/webenginewidgets/simplebrowser/webpage.cpp b/examples/webenginewidgets/simplebrowser/webpage.cpp
index 99849c77d..2e49f651f 100644
--- a/examples/webenginewidgets/simplebrowser/webpage.cpp
+++ b/examples/webenginewidgets/simplebrowser/webpage.cpp
@@ -57,6 +57,7 @@
#include <QAuthenticator>
#include <QMessageBox>
#include <QStyle>
+#include <QTimer>
#include <QWebEngineCertificateError>
WebPage::WebPage(QWebEngineProfile *profile, QObject *parent)
@@ -74,22 +75,33 @@ WebPage::WebPage(QWebEngineProfile *profile, QObject *parent)
bool WebPage::certificateError(const QWebEngineCertificateError &error)
{
QWidget *mainWindow = view()->window();
- if (error.isOverridable()) {
- QDialog dialog(mainWindow);
- dialog.setModal(true);
- dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
- Ui::CertificateErrorDialog certificateDialog;
- certificateDialog.setupUi(&dialog);
- certificateDialog.m_iconLabel->setText(QString());
- QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, mainWindow));
- certificateDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32));
- certificateDialog.m_errorLabel->setText(error.errorDescription());
- dialog.setWindowTitle(tr("Certificate Error"));
- return dialog.exec() == QDialog::Accepted;
- }
- QMessageBox::critical(mainWindow, tr("Certificate Error"), error.errorDescription());
- return false;
+ QWebEngineCertificateError deferredError = error;
+ deferredError.defer();
+
+ QTimer::singleShot(0, mainWindow, [mainWindow, error = std::move(deferredError)] () mutable {
+ if (!error.deferred()) {
+ QMessageBox::critical(mainWindow, tr("Certificate Error"), error.errorDescription());
+ } else {
+ QDialog dialog(mainWindow);
+ dialog.setModal(true);
+ dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ Ui::CertificateErrorDialog certificateDialog;
+ certificateDialog.setupUi(&dialog);
+ certificateDialog.m_iconLabel->setText(QString());
+ QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, mainWindow));
+ certificateDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32));
+ certificateDialog.m_errorLabel->setText(error.errorDescription());
+ dialog.setWindowTitle(tr("Certificate Error"));
+
+ if (dialog.exec() == QDialog::Accepted)
+ error.ignoreCertificateError();
+ else
+ error.rejectCertificate();
+ }
+ });
+ return true;
}
void WebPage::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth)
diff --git a/src/webenginewidgets/api/qwebenginecertificateerror.cpp b/src/webenginewidgets/api/qwebenginecertificateerror.cpp
index f04b73615..e8c7a4245 100644
--- a/src/webenginewidgets/api/qwebenginecertificateerror.cpp
+++ b/src/webenginewidgets/api/qwebenginecertificateerror.cpp
@@ -39,6 +39,8 @@
#include "qwebenginecertificateerror.h"
+#include "certificate_error_controller.h"
+
QT_BEGIN_NAMESPACE
/*!
@@ -51,14 +53,37 @@ QT_BEGIN_NAMESPACE
QWebEnginePage::certificateError().
*/
-class QWebEngineCertificateErrorPrivate {
+class QWebEngineCertificateErrorPrivate : public QSharedData {
public:
QWebEngineCertificateErrorPrivate(int error, QUrl url, bool overridable, QString errorDescription);
+ ~QWebEngineCertificateErrorPrivate() {
+ if (deferred && !answered)
+ rejectCertificate();
+ }
+
+ void resolveError(bool accept) {
+ if (answered)
+ return;
+ answered = true;
+ if (overridable) {
+ if (auto ctl = controller.lock())
+ ctl->accept(accept);
+ }
+ }
+
+ void ignoreCertificateError() { resolveError(true); }
+ void rejectCertificate() { resolveError(false); }
+
QWebEngineCertificateError::Error error;
QUrl url;
bool overridable;
QString errorDescription;
+
+ bool answered = false, deferred = false;
+ QWeakPointer<CertificateErrorController> controller;
+
+ Q_DISABLE_COPY(QWebEngineCertificateErrorPrivate)
};
QWebEngineCertificateErrorPrivate::QWebEngineCertificateErrorPrivate(int error, QUrl url, bool overridable, QString errorDescription)
@@ -68,17 +93,30 @@ QWebEngineCertificateErrorPrivate::QWebEngineCertificateErrorPrivate(int error,
, errorDescription(errorDescription)
{ }
-
/*! \internal
*/
QWebEngineCertificateError::QWebEngineCertificateError(int error, QUrl url, bool overridable, QString errorDescription)
- : d_ptr(new QWebEngineCertificateErrorPrivate(error, url, overridable, errorDescription))
+ : d(new QWebEngineCertificateErrorPrivate(error, url, overridable, errorDescription))
{ }
/*! \internal
*/
+QWebEngineCertificateError::QWebEngineCertificateError(const QSharedPointer<CertificateErrorController> &controller)
+ : d(new QWebEngineCertificateErrorPrivate(controller->error(), controller->url(),
+ controller->overridable(), controller->errorString()))
+{
+ d->controller = controller;
+}
+
+QWebEngineCertificateError::QWebEngineCertificateError(const QWebEngineCertificateError &other) = default;
+
+QWebEngineCertificateError& QWebEngineCertificateError::operator=(const QWebEngineCertificateError &other) = default;
+
+/*! \internal
+*/
QWebEngineCertificateError::~QWebEngineCertificateError()
{
+
}
/*!
@@ -116,7 +154,6 @@ QWebEngineCertificateError::~QWebEngineCertificateError()
*/
bool QWebEngineCertificateError::isOverridable() const
{
- const Q_D(QWebEngineCertificateError);
return d->overridable;
}
@@ -127,7 +164,6 @@ bool QWebEngineCertificateError::isOverridable() const
*/
QUrl QWebEngineCertificateError::url() const
{
- const Q_D(QWebEngineCertificateError);
return d->url;
}
@@ -138,7 +174,6 @@ QUrl QWebEngineCertificateError::url() const
*/
QWebEngineCertificateError::Error QWebEngineCertificateError::error() const
{
- const Q_D(QWebEngineCertificateError);
return d->error;
}
@@ -149,8 +184,56 @@ QWebEngineCertificateError::Error QWebEngineCertificateError::error() const
*/
QString QWebEngineCertificateError::errorDescription() const
{
- const Q_D(QWebEngineCertificateError);
return d->errorDescription;
}
+/*!
+ Marks the certificate error for delayed handling.
+
+ This function should be called when there is a need to postpone the decision whether to ignore a
+ certificate error, for example, while waiting for user input. When called, the function pauses the
+ URL request until ignoreCertificateError() or rejectCertificate() is called.
+
+ \note It is only possible to defer overridable certificate errors.
+
+ \sa isOverridable(), deferred()
+*/
+void QWebEngineCertificateError::defer()
+{
+ if (isOverridable())
+ d->deferred = true;
+}
+
+/*!
+ Returns whether the decision for error handling was delayed and the URL load was halted.
+*/
+bool QWebEngineCertificateError::deferred() const
+{
+ return d->deferred;
+}
+
+/*!
+ Ignores the certificate error and continues the loading of the requested URL.
+*/
+void QWebEngineCertificateError::ignoreCertificateError()
+{
+ d->ignoreCertificateError();
+}
+
+/*!
+ Rejects the certificate and aborts the loading of the requested URL.
+*/
+void QWebEngineCertificateError::rejectCertificate()
+{
+ d->rejectCertificate();
+}
+
+/*!
+ Returns \c true if the error was explicitly rejected or accepted.
+*/
+bool QWebEngineCertificateError::answered() const
+{
+ return d->answered;
+}
+
QT_END_NAMESPACE
diff --git a/src/webenginewidgets/api/qwebenginecertificateerror.h b/src/webenginewidgets/api/qwebenginecertificateerror.h
index 82ac281be..f8805e8a7 100644
--- a/src/webenginewidgets/api/qwebenginecertificateerror.h
+++ b/src/webenginewidgets/api/qwebenginecertificateerror.h
@@ -42,11 +42,12 @@
#include <QtWebEngineWidgets/qtwebenginewidgetsglobal.h>
-#include <QtCore/qscopedpointer.h>
+#include <QtCore/qsharedpointer.h>
#include <QtCore/qurl.h>
QT_BEGIN_NAMESPACE
+class CertificateErrorController;
class QWebEngineCertificateErrorPrivate;
class QWEBENGINEWIDGETS_EXPORT QWebEngineCertificateError {
@@ -78,10 +79,20 @@ public:
bool isOverridable() const;
QString errorDescription() const;
+ QWebEngineCertificateError(const QWebEngineCertificateError &other);
+ QWebEngineCertificateError& operator=(const QWebEngineCertificateError &other);
+
+ void defer();
+ bool deferred() const;
+
+ void rejectCertificate();
+ void ignoreCertificateError();
+ bool answered() const;
+
private:
- Q_DISABLE_COPY(QWebEngineCertificateError)
- Q_DECLARE_PRIVATE(QWebEngineCertificateError)
- QScopedPointer<QWebEngineCertificateErrorPrivate> d_ptr;
+ friend class QWebEnginePagePrivate;
+ QWebEngineCertificateError(const QSharedPointer<CertificateErrorController> &controller);
+ QExplicitlySharedDataPointer<QWebEngineCertificateErrorPrivate> d;
};
QT_END_NAMESPACE
diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp
index c9e9177b7..b22d47916 100644
--- a/src/webenginewidgets/api/qwebenginepage.cpp
+++ b/src/webenginewidgets/api/qwebenginepage.cpp
@@ -294,6 +294,7 @@ void QWebEnginePagePrivate::loadStarted(const QUrl &provisionalUrl, bool isError
return;
isLoading = true;
+ m_certificateErrorControllers.clear();
QTimer::singleShot(0, q, &QWebEnginePage::loadStarted);
}
@@ -1715,10 +1716,11 @@ void QWebEnginePagePrivate::allowCertificateError(const QSharedPointer<Certifica
Q_Q(QWebEnginePage);
bool accepted = false;
- QWebEngineCertificateError error(controller->error(), controller->url(), controller->overridable() && !controller->strictEnforcement(), controller->errorString());
+ QWebEngineCertificateError error(controller);
accepted = q->certificateError(error);
-
- if (error.isOverridable())
+ if (error.deferred() && !error.answered())
+ m_certificateErrorControllers.append(controller);
+ else if (!error.answered() && error.isOverridable())
controller->accept(accepted);
}
diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h
index fae97b9fa..2843f69c4 100644
--- a/src/webenginewidgets/api/qwebenginepage_p.h
+++ b/src/webenginewidgets/api/qwebenginepage_p.h
@@ -210,6 +210,8 @@ public:
#if QT_CONFIG(webengine_printing_and_pdf)
QPrinter *currentPrinter;
#endif
+
+ QList<QSharedPointer<CertificateErrorController>> m_certificateErrorControllers;
};
class QContextMenuBuilder : public QtWebEngineCore::RenderViewContextMenuQt
diff --git a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc
index 64fe4c9cd..5536c0058 100644
--- a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc
+++ b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc
@@ -522,6 +522,8 @@
Return \c true to ignore the error and complete the request. Return \c false to stop loading
the request.
+ \note If the error was successfully deferred then the returned value will be ignored.
+
\sa QWebEngineCertificateError
*/
diff --git a/tests/auto/shared/https.pri b/tests/auto/shared/https.pri
new file mode 100644
index 000000000..ce4c147f7
--- /dev/null
+++ b/tests/auto/shared/https.pri
@@ -0,0 +1,4 @@
+include($$PWD/http.pri)
+
+HEADERS += $$PWD/httpsserver.h
+RESOURCES += $$PWD/httpsserver.qrc
diff --git a/tests/auto/shared/httpserver.cpp b/tests/auto/shared/httpserver.cpp
index b85af9901..e282fc8b8 100644
--- a/tests/auto/shared/httpserver.cpp
+++ b/tests/auto/shared/httpserver.cpp
@@ -31,9 +31,21 @@
Q_LOGGING_CATEGORY(gHttpServerLog, "HttpServer")
-HttpServer::HttpServer(QObject *parent) : QObject(parent)
+HttpServer::HttpServer(QObject *parent) : HttpServer(new QTcpServer, "http", parent)
{
- connect(&m_tcpServer, &QTcpServer::newConnection, this, &HttpServer::handleNewConnection);
+}
+
+HttpServer::HttpServer(QTcpServer *tcpServer, const QString &protocol, QObject *parent)
+ : QObject(parent), m_tcpServer(tcpServer)
+{
+ m_url.setHost(QStringLiteral("127.0.0.1"));
+ m_url.setScheme(protocol);
+ connect(tcpServer, &QTcpServer::newConnection, this, &HttpServer::handleNewConnection);
+}
+
+HttpServer::~HttpServer()
+{
+ delete m_tcpServer;
}
bool HttpServer::start()
@@ -41,21 +53,18 @@ bool HttpServer::start()
m_error = false;
m_expectingError = false;
- if (!m_tcpServer.listen()) {
- qCWarning(gHttpServerLog).noquote() << m_tcpServer.errorString();
+ if (!m_tcpServer->listen()) {
+ qCWarning(gHttpServerLog).noquote() << m_tcpServer->errorString();
return false;
}
- m_url.setScheme(QStringLiteral("http"));
- m_url.setHost(QStringLiteral("127.0.0.1"));
- m_url.setPort(m_tcpServer.serverPort());
-
+ m_url.setPort(m_tcpServer->serverPort());
return true;
}
bool HttpServer::stop()
{
- m_tcpServer.close();
+ m_tcpServer->close();
return m_error == m_expectingError;
}
@@ -73,12 +82,12 @@ QUrl HttpServer::url(const QString &path) const
void HttpServer::handleNewConnection()
{
- auto rr = new HttpReqRep(m_tcpServer.nextPendingConnection(), this);
+ auto rr = new HttpReqRep(m_tcpServer->nextPendingConnection(), this);
connect(rr, &HttpReqRep::requestReceived, [this, rr]() {
Q_EMIT newRequest(rr);
rr->close();
});
- connect(rr, &HttpReqRep::responseSent, [this, rr]() {
+ connect(rr, &HttpReqRep::responseSent, [rr]() {
qCInfo(gHttpServerLog).noquote() << rr->requestMethod() << rr->requestPath()
<< rr->responseStatus() << rr->responseBody().size();
});
diff --git a/tests/auto/shared/httpserver.h b/tests/auto/shared/httpserver.h
index b4649244e..57f824bb5 100644
--- a/tests/auto/shared/httpserver.h
+++ b/tests/auto/shared/httpserver.h
@@ -59,19 +59,22 @@ class HttpServer : public QObject
Q_OBJECT
public:
explicit HttpServer(QObject *parent = nullptr);
+ explicit HttpServer(QTcpServer *server, const QString &protocol, QObject *parent = nullptr);
+
+ ~HttpServer() override;
// Must be called to start listening.
//
// Returns true if a TCP port has been successfully bound.
- Q_REQUIRED_RESULT bool start();
+ Q_INVOKABLE Q_REQUIRED_RESULT bool start();
// Stops listening and performs final error checks.
- Q_REQUIRED_RESULT bool stop();
+ Q_INVOKABLE Q_REQUIRED_RESULT bool stop();
- void setExpectError(bool b);
+ Q_INVOKABLE void setExpectError(bool b);
// Full URL for given relative path
- QUrl url(const QString &path = QStringLiteral("/")) const;
+ Q_INVOKABLE QUrl url(const QString &path = QStringLiteral("/")) const;
Q_SIGNALS:
// Emitted after a HTTP request has been successfully parsed.
@@ -81,7 +84,7 @@ private Q_SLOTS:
void handleNewConnection();
private:
- QTcpServer m_tcpServer;
+ QTcpServer *m_tcpServer;
QUrl m_url;
bool m_error = false;
bool m_expectingError = false;
diff --git a/tests/auto/shared/httpsserver.h b/tests/auto/shared/httpsserver.h
new file mode 100644
index 000000000..76287a7e5
--- /dev/null
+++ b/tests/auto/shared/httpsserver.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebEngine module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef HTTPSSERVER_H
+#define HTTPSSERVER_H
+
+#include "httpreqrep.h"
+#include "httpserver.h"
+
+#include <QDebug>
+#include <QFile>
+#include <QSslKey>
+#include <QSslSocket>
+#include <QSslConfiguration>
+#include <QTcpServer>
+
+struct SslTcpServer : QTcpServer
+{
+ SslTcpServer() {
+ sslconf.setLocalCertificate(QSslCertificate::fromPath(":/resources/cert.pem").first());
+ sslconf.setPrivateKey(readKey(":/resources/key.pem"));
+ }
+
+ void incomingConnection(qintptr d) override {
+ auto socket = new QSslSocket(this);
+ socket->setSslConfiguration(sslconf);
+
+ if (!socket->setSocketDescriptor(d)) {
+ qWarning() << "Failed to setup ssl socket!";
+ delete socket;
+ return;
+ }
+
+ connect(socket, QOverload<QSslSocket::SocketError>::of(&QSslSocket::error),
+ [] (QSslSocket::SocketError e) { qWarning() << "! Socket Error:" << e; });
+ connect(socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
+ [] (const QList<QSslError> &le) { qWarning() << "! SSL Errors:\n" << le; });
+
+ addPendingConnection(socket);
+ socket->startServerEncryption();
+ }
+
+ QSslKey readKey(const QString &path) const {
+ QFile file(path);
+ file.open(QIODevice::ReadOnly);
+ return QSslKey(file.readAll(), QSsl::Rsa, QSsl::Pem);
+ }
+
+ QSslConfiguration sslconf;
+};
+
+struct HttpsServer : HttpServer
+{
+ HttpsServer(QObject *parent = nullptr) : HttpServer(new SslTcpServer, "https", parent) { }
+};
+
+#endif
diff --git a/tests/auto/shared/httpsserver.qrc b/tests/auto/shared/httpsserver.qrc
new file mode 100644
index 000000000..ec57a1983
--- /dev/null
+++ b/tests/auto/shared/httpsserver.qrc
@@ -0,0 +1,6 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>resources/cert.pem</file>
+ <file>resources/key.pem</file>
+</qresource>
+</RCC>
diff --git a/tests/auto/shared/resources/cert.pem b/tests/auto/shared/resources/cert.pem
new file mode 100644
index 000000000..518642d1d
--- /dev/null
+++ b/tests/auto/shared/resources/cert.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+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
+K+MCKRECBkh4ZvMxd3yEJAym0w==
+-----END CERTIFICATE-----
diff --git a/tests/auto/shared/resources/key.pem b/tests/auto/shared/resources/key.pem
new file mode 100644
index 000000000..bd350d7fb
--- /dev/null
+++ b/tests/auto/shared/resources/key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+zB9TRbOML1RSOJ+z+mbv7ONymoUI/Jf5
+-----END PRIVATE KEY-----
diff --git a/tests/auto/widgets/certificateerror/certificateerror.pro b/tests/auto/widgets/certificateerror/certificateerror.pro
new file mode 100644
index 000000000..73ba7515b
--- /dev/null
+++ b/tests/auto/widgets/certificateerror/certificateerror.pro
@@ -0,0 +1,3 @@
+include(../tests.pri)
+include(../../shared/https.pri)
+QT *= core-private
diff --git a/tests/auto/widgets/certificateerror/tst_certificateerror.cpp b/tests/auto/widgets/certificateerror/tst_certificateerror.cpp
new file mode 100644
index 000000000..b002dc363
--- /dev/null
+++ b/tests/auto/widgets/certificateerror/tst_certificateerror.cpp
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebEngine module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <httpsserver.h>
+#include <util.h>
+
+#include <QWebEngineCertificateError>
+#include <QWebEnginePage>
+#include <QWebEngineSettings>
+
+#include <QtTest/QtTest>
+
+class tst_CertificateError : public QObject
+{
+ Q_OBJECT
+public:
+ tst_CertificateError() { }
+
+private Q_SLOTS:
+ void handleError_data();
+ void handleError();
+};
+
+struct PageWithCertificateErrorHandler : QWebEnginePage
+{
+ PageWithCertificateErrorHandler(bool defer, bool accept, QObject *p = nullptr)
+ : QWebEnginePage(p), deferError(defer), acceptCertificate(accept) {
+ connect(this, &QWebEnginePage::loadFinished, [&] (bool result) { spyLoad(result); });
+ }
+
+ bool deferError, acceptCertificate;
+
+ CallbackSpy<bool> spyLoad;
+ QScopedPointer<QWebEngineCertificateError> error;
+
+ bool certificateError(const QWebEngineCertificateError &e) override {
+ error.reset(new QWebEngineCertificateError(e));
+ if (deferError)
+ error->defer();
+ return acceptCertificate;
+ }
+};
+
+void tst_CertificateError::handleError_data()
+{
+ QTest::addColumn<bool>("deferError");
+ QTest::addColumn<bool>("acceptCertificate");
+ QTest::addColumn<QString>("expectedContent");
+ QTest::addRow("Reject") << false << false << QString();
+ QTest::addRow("DeferReject") << true << false << QString();
+ QTest::addRow("DeferAccept") << true << true << "TEST";
+}
+
+void tst_CertificateError::handleError()
+{
+ HttpsServer server;
+ server.setExpectError(true);
+ QVERIFY(server.start());
+
+ connect(&server, &HttpsServer::newRequest, [&] (HttpReqRep *rr) {
+ rr->setResponseBody(QByteArrayLiteral("<html><body>TEST</body></html>"));
+ rr->sendResponse();
+ });
+
+ QFETCH(bool, deferError);
+ QFETCH(bool, acceptCertificate);
+ QFETCH(QString, expectedContent);
+
+ PageWithCertificateErrorHandler page(deferError, acceptCertificate);
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+
+ page.setUrl(server.url());
+ QTRY_VERIFY(page.error);
+ QVERIFY(page.error->isOverridable());
+
+ if (deferError) {
+ QVERIFY(page.error->deferred());
+ QVERIFY(!page.error->answered());
+ QVERIFY(!page.spyLoad.wasCalled());
+ QCOMPARE(toPlainTextSync(&page), QString());
+
+ if (acceptCertificate)
+ page.error->ignoreCertificateError();
+ else
+ page.error->rejectCertificate();
+
+ QVERIFY(page.error->answered());
+ page.error.reset();
+ }
+
+ bool loadResult = page.spyLoad.waitForResult();
+ QVERIFY(page.spyLoad.wasCalled());
+ QCOMPARE(loadResult, acceptCertificate);
+ QCOMPARE(toPlainTextSync(&page), expectedContent);
+}
+
+QTEST_MAIN(tst_CertificateError)
+#include <tst_certificateerror.moc>
diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro
index 92159bf83..df553df55 100644
--- a/tests/auto/widgets/widgets.pro
+++ b/tests/auto/widgets/widgets.pro
@@ -4,6 +4,7 @@ QT_FOR_CONFIG += webenginecore webenginecore-private
TEMPLATE = subdirs
SUBDIRS += \
+ certificateerror \
defaultsurfaceformat \
devtools \
faviconmanager \