Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package qt6-websockets for openSUSE:Factory 
checked in at 2023-10-13 23:14:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/qt6-websockets (Old)
 and      /work/SRC/openSUSE:Factory/.qt6-websockets.new.20540 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "qt6-websockets"

Fri Oct 13 23:14:46 2023 rev:17 rq:1116964 version:6.6.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/qt6-websockets/qt6-websockets.changes    
2023-10-02 20:08:29.527175457 +0200
+++ /work/SRC/openSUSE:Factory/.qt6-websockets.new.20540/qt6-websockets.changes 
2023-10-13 23:15:49.761535999 +0200
@@ -1,0 +2,6 @@
+Tue Oct 10 09:40:04 UTC 2023 - Christophe Marin <christo...@krop.fr>
+
+- Update to 6.6.0
+  * https://www.qt.io/blog/qt-6.6-released
+
+-------------------------------------------------------------------

Old:
----
  qtwebsockets-everywhere-src-6.5.3.tar.xz

New:
----
  qtwebsockets-everywhere-src-6.6.0.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ qt6-websockets.spec ++++++
--- /var/tmp/diff_new_pack.BVN5x0/_old  2023-10-13 23:15:50.557564871 +0200
+++ /var/tmp/diff_new_pack.BVN5x0/_new  2023-10-13 23:15:50.561565016 +0200
@@ -16,8 +16,8 @@
 #
 
 
-%define real_version 6.5.3
-%define short_version 6.5
+%define real_version 6.6.0
+%define short_version 6.6
 %define tar_name qtwebsockets-everywhere-src
 %define tar_suffix %{nil}
 #
@@ -27,7 +27,7 @@
 %endif
 #
 Name:           qt6-websockets%{?pkg_suffix}
-Version:        6.5.3
+Version:        6.6.0
 Release:        0
 Summary:        Qt 6 WebSockets library
 License:        LGPL-3.0-only OR (GPL-2.0-only OR GPL-3.0-or-later)

++++++ qtwebsockets-everywhere-src-6.5.3.tar.xz -> 
qtwebsockets-everywhere-src-6.6.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtwebsockets-everywhere-src-6.5.3/.cmake.conf 
new/qtwebsockets-everywhere-src-6.6.0/.cmake.conf
--- old/qtwebsockets-everywhere-src-6.5.3/.cmake.conf   2023-09-24 
11:45:46.000000000 +0200
+++ new/qtwebsockets-everywhere-src-6.6.0/.cmake.conf   2023-10-03 
20:26:29.000000000 +0200
@@ -1,3 +1,3 @@
-set(QT_REPO_MODULE_VERSION "6.5.3")
+set(QT_REPO_MODULE_VERSION "6.6.0")
 set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1")
 set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_AS_CONST=1")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtwebsockets-everywhere-src-6.5.3/.tag 
new/qtwebsockets-everywhere-src-6.6.0/.tag
--- old/qtwebsockets-everywhere-src-6.5.3/.tag  2023-09-24 11:45:46.000000000 
+0200
+++ new/qtwebsockets-everywhere-src-6.6.0/.tag  2023-10-03 20:26:29.000000000 
+0200
@@ -1 +1 @@
-625524eb7e7518a61c9cfa2ba2eb1cd2673d5cf3
+090fb14fa3011d2590ac28dfc37f2c6e2afe09b1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtwebsockets-everywhere-src-6.5.3/coin/axivion/ci_config_linux.json 
new/qtwebsockets-everywhere-src-6.6.0/coin/axivion/ci_config_linux.json
--- old/qtwebsockets-everywhere-src-6.5.3/coin/axivion/ci_config_linux.json     
1970-01-01 01:00:00.000000000 +0100
+++ new/qtwebsockets-everywhere-src-6.6.0/coin/axivion/ci_config_linux.json     
2023-10-03 20:26:29.000000000 +0200
@@ -0,0 +1,60 @@
+{
+    "Project": {
+        "Git": {
+            "_active": true,
+            "sourceserver_gitdir": 
"/data/axivion/databases/$(env:TESTED_MODULE_COIN).git"
+        },
+        "BuildSystemIntegration": {
+            "child_order": [
+                "GCCSetup",
+                "CMake",
+                "LinkLibraries"
+            ]
+        },
+        "CMake": {
+            "_active": true,
+            "_copy_from": "CMakeIntegration",
+            "build_environment": {},
+            "build_options": "-j4",
+            "generate_options": "--fresh",
+            "generator": "Ninja"
+        },
+        "GCCSetup": {
+            "_active": true,
+            "_copy_from": "Command",
+            "build_command": "gccsetup --cc gcc --cxx g++ --config 
../../../axivion/"
+        },
+        "LinkLibraries": {
+            "_active": true,
+            "_copy_from": "AxivionLinker",
+            "input_files": [
+                "build/lib/lib*.so*.ir",
+                "build/qml/*/lib*.so*.ir"
+            ],
+            "ir": "build/$(env:TESTED_MODULE_COIN).ir"
+        },
+        "Project-GlobalOptions": {
+            "directory": "../work/qt/$(env:TESTED_MODULE_COIN)",
+            "ir": "build/$(env:TESTED_MODULE_COIN).ir",
+            "name": "qt_$(env:TESTED_MODULE_COIN)_dev_$(env:TARGET_OS_COIN)"
+        }
+    },
+    "Results": {
+        "Dashboard": {
+            "dashboard_url": "https://axivion-srv.ci.qt.io/axivion/";
+        },
+        "Database": {
+            "ci_mode": {
+                "directory": "/data/axivion/databases"
+            }
+        }
+    },
+    "_Format": "1.0",
+    "_Version": "trunk-9e0ef9c5818",
+    "_VersionNum": [
+        7,
+        6,
+        9999,
+        11489
+    ]
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtwebsockets-everywhere-src-6.5.3/dependencies.yaml 
new/qtwebsockets-everywhere-src-6.6.0/dependencies.yaml
--- old/qtwebsockets-everywhere-src-6.5.3/dependencies.yaml     2023-09-24 
11:45:46.000000000 +0200
+++ new/qtwebsockets-everywhere-src-6.6.0/dependencies.yaml     2023-10-03 
20:26:29.000000000 +0200
@@ -1,7 +1,7 @@
 dependencies:
   ../qtbase:
-    ref: 372eaedc5b8c771c46acc4c96e91bbade4ca3624
+    ref: 33f5e985e480283bb0ca9dea5f82643e825ba87c
     required: true
   ../qtdeclarative:
-    ref: e00c258fa5a4e122636d441967dea035865fac5d
+    ref: e559d5cf2b66c4a973f83f173d57676a21d287ef
     required: false
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket.cpp 
new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket.cpp
--- old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket.cpp 
2023-09-24 11:45:46.000000000 +0200
+++ new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket.cpp 
2023-10-03 20:26:29.000000000 +0200
@@ -98,6 +98,27 @@
 
 \sa QAuthenticator, QNetworkProxy
 */
+
+/*!
+    \fn void QWebSocket::authenticationRequired(QAuthenticator *authenticator)
+    \since 6.6
+
+    This signal is emitted when the server requires authentication.
+    The \a authenticator object must then be filled in with the required 
details
+    to allow authentication and continue the connection.
+
+    If you know that the server may require authentication, you can set the
+    username and password on the initial QUrl, using QUrl::setUserName and
+    QUrl::setPassword. QWebSocket will still try to connect \e{once} without
+    using the provided credentials.
+
+    \note It is not possible to use a QueuedConnection to connect to
+    this signal, as the connection will fail if the authenticator has
+    not been filled in with new information when the signal returns.
+
+    \sa QAuthenticator
+*/
+
 /*!
     \fn void QWebSocket::stateChanged(QAbstractSocket::SocketState state);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket.h 
new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket.h
--- old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket.h   
2023-09-24 11:45:46.000000000 +0200
+++ new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket.h   
2023-10-03 20:26:29.000000000 +0200
@@ -17,8 +17,11 @@
 #include "QtWebSockets/qwebsockets_global.h"
 #include "QtWebSockets/qwebsocketprotocol.h"
 
+#include <QtCore/qobject.h>
+
 QT_BEGIN_NAMESPACE
 
+class QAuthenticator;
 class QTcpSocket;
 class QWebSocketPrivate;
 class QMaskGenerator;
@@ -118,6 +121,7 @@
 #ifndef QT_NO_NETWORKPROXY
     void proxyAuthenticationRequired(const QNetworkProxy &proxy, 
QAuthenticator *pAuthenticator);
 #endif
+    void authenticationRequired(QAuthenticator *authenticator);
     void readChannelFinished();
     void textFrameReceived(const QString &frame, bool isLastFrame);
     void binaryFrameReceived(const QByteArray &frame, bool isLastFrame);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket_p.cpp 
new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket_p.cpp
--- old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket_p.cpp       
2023-09-24 11:45:46.000000000 +0200
+++ new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket_p.cpp       
2023-10-03 20:26:29.000000000 +0200
@@ -28,13 +28,17 @@
 #endif
 
 #include <QtNetwork/private/qhttpheaderparser_p.h>
+#include <QtNetwork/private/qauthenticator_p.h>
 
 #include <QtCore/QDebug>
 
 #include <limits>
+#include <memory>
 
 QT_BEGIN_NAMESPACE
 
+using namespace Qt::StringLiterals;
+
 namespace {
 
 constexpr quint64 MAX_OUTGOING_FRAME_SIZE_IN_BYTES = 
std::numeric_limits<int>::max() - 1;
@@ -963,6 +967,17 @@
     return written;
 }
 
+static QString msgUnsupportedAuthenticateChallenges(qsizetype count)
+{
+    // Keep the error on a single line so it can easily be searched for:
+    //: 'WWW-Authenticate' is the HTTP header.
+    return count == 1
+        ? QWebSocket::tr("QWebSocketPrivate::processHandshake: "
+                         "Unsupported WWW-Authenticate challenge encountered.")
+        : QWebSocket::tr("QWebSocketPrivate::processHandshake: "
+                         "Unsupported WWW-Authenticate challenges 
encountered.");
+}
+
 //called on the client for a server handshake response
 /*!
     \internal
@@ -1040,7 +1055,8 @@
                                 QByteArrayLiteral("sec-websocket-version")));
     bool ok = false;
     QString errorDescription;
-    if (Q_LIKELY(parser.getStatusCode() == 101)) {
+    switch (parser.getStatusCode()) {
+    case 101: {
         //HTTP/x.y 101 Switching Protocols
         //TODO: do not check the httpStatusText right now
         ok = (acceptKey.size() > 0
@@ -1064,7 +1080,9 @@
             errorDescription = QWebSocket::tr(
                 "Invalid parameter encountered during protocol upgrade: 
%1").arg(upgradeParms);
         }
-    } else if (parser.getStatusCode() == 400) {
+        break;
+    }
+    case 400: {
         //HTTP/1.1 400 Bad Request
         if (!version.isEmpty()) {
             const QStringList versions = version.split(QStringLiteral(", "), 
Qt::SkipEmptyParts);
@@ -1083,17 +1101,72 @@
             errorDescription =
                 QWebSocket::tr("QWebSocketPrivate::processHandshake: Unknown 
error condition encountered. Aborting connection.");
         }
-    } else {
+        break;
+    }
+    case 401: {
+        // HTTP/1.1 401 UNAUTHORIZED
+        if (m_authenticator.isNull())
+            m_authenticator.detach();
+        auto *priv = QAuthenticatorPrivate::getPrivate(m_authenticator);
+        const QList<QByteArray> challenges = 
parser.headerFieldValues("WWW-Authenticate");
+        const bool isSupported = std::any_of(challenges.begin(), 
challenges.end(),
+                                             
QAuthenticatorPrivate::isMethodSupported);
+        if (isSupported)
+            priv->parseHttpResponse(parser.headers(), /*isProxy=*/false, 
m_request.url().host());
+        if (!isSupported || priv->method == QAuthenticatorPrivate::None) {
+            errorDescription = 
msgUnsupportedAuthenticateChallenges(challenges.size());
+            break;
+        }
+
+        const QUrl url = m_request.url();
+        const bool hasCredentials = !url.userName().isEmpty() || 
!url.password().isEmpty();
+        if (hasCredentials) {
+            m_authenticator.setUser(url.userName());
+            m_authenticator.setPassword(url.password());
+            // Unset username and password so we don't try it again
+            QUrl copy = url;
+            copy.setUserName({});
+            copy.setPassword({});
+            m_request.setUrl(copy);
+        }
+        if (priv->phase == QAuthenticatorPrivate::Done) { // No user/pass from 
URL:
+            emit q->authenticationRequired(&m_authenticator);
+            if (priv->phase == QAuthenticatorPrivate::Done) {
+                // user/pass was not updated:
+                errorDescription = QWebSocket::tr(
+                        "QWebSocket::processHandshake: Host requires 
authentication");
+                break;
+            }
+        }
+        m_needsResendWithCredentials = true;
+        if (parser.firstHeaderField("Connection").compare("close", 
Qt::CaseInsensitive) == 0)
+            m_needsReconnect = true;
+        else
+            m_bytesToSkipBeforeNewResponse = 
parser.firstHeaderField("Content-Length").toInt();
+        break;
+    }
+    default: {
         errorDescription =
             QWebSocket::tr("QWebSocketPrivate::processHandshake: Unhandled 
http status code: %1 (%2).")
                     .arg(parser.getStatusCode()).arg(parser.getReasonPhrase());
     }
+    }
 
     if (ok) {
         // handshake succeeded
         setProtocol(protocol);
         setSocketState(QAbstractSocket::ConnectedState);
         Q_EMIT q->connected();
+    } else if (m_needsResendWithCredentials) {
+        if (m_needsReconnect && m_pSocket->state() != 
QAbstractSocket::UnconnectedState) {
+            // Disconnect here, then in processStateChanged() we reconnect when
+            // we are unconnected.
+            m_pSocket->disconnectFromHost();
+        } else {
+            // I'm cheating, this is how a handshake starts:
+            processStateChanged(QAbstractSocket::ConnectedState);
+        }
+        return;
     } else {
         // handshake failed
         setErrorString(errorDescription);
@@ -1132,6 +1205,27 @@
             }
             const QStringList subProtocols = requestedSubProtocols();
 
+            // Perform authorization if needed:
+            if (m_needsResendWithCredentials) {
+                m_needsResendWithCredentials = false;
+                // Based on QHttpNetworkRequest::uri:
+                auto uri = [](QUrl url) -> QByteArray {
+                    QUrl::FormattingOptions format(QUrl::RemoveFragment | 
QUrl::RemoveUserInfo
+                                                   | QUrl::FullyEncoded);
+                    if (url.path().isEmpty())
+                        url.setPath(QStringLiteral("/"));
+                    else
+                        format |= QUrl::NormalizePathSegments;
+                    return url.toEncoded(format);
+                };
+                auto *priv = 
QAuthenticatorPrivate::getPrivate(m_authenticator);
+                Q_ASSERT(priv);
+                QByteArray response = priv->calculateResponse("GET", 
uri(m_request.url()),
+                                                              
m_request.url().host());
+                if (!response.isEmpty())
+                    headers << qMakePair(u"Authorization"_s, 
QString::fromLatin1(response));
+            }
+
             const auto format = QUrl::RemoveScheme | QUrl::RemoveUserInfo
                                 | QUrl::RemovePath | QUrl::RemoveQuery
                                 | QUrl::RemoveFragment;
@@ -1158,7 +1252,28 @@
         break;
 
     case QAbstractSocket::UnconnectedState:
-        if (webSocketState != QAbstractSocket::UnconnectedState) {
+        if (m_needsReconnect) {
+            // Need to reinvoke the lambda queued because the underlying socket
+            // isn't done cleaning up yet...
+            auto reconnect = [this]() {
+                m_needsReconnect = false;
+                const QUrl url = m_request.url();
+#if QT_CONFIG(ssl)
+                const bool isEncrypted = url.scheme().compare(u"wss", 
Qt::CaseInsensitive) == 0;
+                if (isEncrypted) {
+                    // This has to work because we did it earlier; this is 
just us
+                    // reconnecting!
+                    auto *sslSocket = qobject_cast<QSslSocket *>(m_pSocket);
+                    Q_ASSERT(sslSocket);
+                    sslSocket->connectToHostEncrypted(url.host(), 
quint16(url.port(443)));
+                } else
+#endif
+                {
+                    m_pSocket->connectToHost(url.host(), 
quint16(url.port(80)));
+                }
+            };
+            QMetaObject::invokeMethod(q, reconnect, Qt::QueuedConnection);
+        } else if (webSocketState != QAbstractSocket::UnconnectedState) {
             setSocketState(QAbstractSocket::UnconnectedState);
             Q_EMIT q->disconnected();
         }
@@ -1189,7 +1304,9 @@
     if (!m_pSocket) // disconnected with data still in-bound
         return;
     if (state() == QAbstractSocket::ConnectingState) {
-        if (!m_pSocket->canReadLine())
+        if (m_bytesToSkipBeforeNewResponse > 0)
+            m_bytesToSkipBeforeNewResponse -= 
m_pSocket->skip(m_bytesToSkipBeforeNewResponse);
+        if (m_bytesToSkipBeforeNewResponse > 0 || !m_pSocket->canReadLine())
             return;
         processHandshake(m_pSocket);
        // That may have changed state(), recheck in the next 'if' below.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket_p.h 
new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket_p.h
--- old/qtwebsockets-everywhere-src-6.5.3/src/websockets/qwebsocket_p.h 
2023-09-24 11:45:46.000000000 +0200
+++ new/qtwebsockets-everywhere-src-6.6.0/src/websockets/qwebsocket_p.h 
2023-10-03 20:26:29.000000000 +0200
@@ -20,6 +20,7 @@
 #ifndef QT_NO_NETWORKPROXY
 #include <QtNetwork/QNetworkProxy>
 #endif
+#include <QtNetwork/QAuthenticator>
 #ifndef QT_NO_SSL
 #include <QtNetwork/QSslConfiguration>
 #include <QtNetwork/QSslError>
@@ -211,12 +212,21 @@
     QAbstractSocket::PauseModes m_pauseMode;
     qint64 m_readBufferSize;
 
+    // For WWW-Authenticate handling
+    QAuthenticator m_authenticator;
+    qint64 m_bytesToSkipBeforeNewResponse = 0;
+
     QByteArray m_key;  //identification key used in handshake requests
 
     bool m_mustMask;   //a server must not mask the frames it sends
 
     bool m_isClosingHandshakeSent;
     bool m_isClosingHandshakeReceived;
+
+    // For WWW-Authenticate handling
+    bool m_needsResendWithCredentials = false;
+    bool m_needsReconnect = false;
+
     QWebSocketProtocol::CloseCode m_closeCode;
     QString m_closeReason;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtwebsockets-everywhere-src-6.5.3/tests/auto/websockets/qwebsocket/CMakeLists.txt
 
new/qtwebsockets-everywhere-src-6.6.0/tests/auto/websockets/qwebsocket/CMakeLists.txt
--- 
old/qtwebsockets-everywhere-src-6.5.3/tests/auto/websockets/qwebsocket/CMakeLists.txt
       2023-09-24 11:45:46.000000000 +0200
+++ 
new/qtwebsockets-everywhere-src-6.6.0/tests/auto/websockets/qwebsocket/CMakeLists.txt
       2023-10-03 20:26:29.000000000 +0200
@@ -14,5 +14,18 @@
         Qt::WebSockets
 )
 
+set(qwebsocketshared_resource_files
+    "../shared/localhost.cert"
+    "../shared/localhost.key"
+)
+qt_internal_add_resource(tst_qwebsocket "qwebsocketshared"
+    PREFIX
+        "/"
+    BASE
+        "../shared"
+    FILES
+        ${qwebsocketshared_resource_files}
+)
+
 #### Keys ignored in scope 1:.:.:qwebsocket.pro:<TRUE>:
 # TEMPLATE = "app"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtwebsockets-everywhere-src-6.5.3/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp
 
new/qtwebsockets-everywhere-src-6.6.0/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp
--- 
old/qtwebsockets-everywhere-src-6.5.3/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp
   2023-09-24 11:45:46.000000000 +0200
+++ 
new/qtwebsockets-everywhere-src-6.6.0/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp
   2023-10-03 20:26:29.000000000 +0200
@@ -10,6 +10,16 @@
 #include <QtWebSockets/qwebsocketprotocol.h>
 
 #include <QtNetwork/qtcpserver.h>
+#include <QtNetwork/qauthenticator.h>
+#include <QtNetwork/qtcpsocket.h>
+
+#if QT_CONFIG(ssl)
+#include <QtNetwork/qsslserver.h>
+#include <QtNetwork/qsslcertificate.h>
+#include <QtNetwork/qsslkey.h>
+#endif
+
+#include <utility>
 
 QT_USE_NAMESPACE
 
@@ -143,6 +153,8 @@
 #ifndef QT_NO_NETWORKPROXY
     void tst_setProxy();
 #endif
+    void authenticationRequired_data();
+    void authenticationRequired();
     void overlongCloseReason();
     void incomingMessageTooLong();
     void incomingFrameTooLong();
@@ -921,6 +933,308 @@
 }
 #endif // QT_NO_NETWORKPROXY
 
+class AuthServer : public QTcpServer
+{
+    Q_OBJECT
+public:
+    AuthServer()
+    {
+        connect(this, &QTcpServer::pendingConnectionAvailable, this, 
&AuthServer::handleConnection);
+    }
+
+    void incomingConnection(qintptr sockfd) override
+    {
+        if (withEncryption) {
+#if QT_CONFIG(ssl)
+            auto *sslSocket = new QSslSocket(this);
+            connect(sslSocket, &QSslSocket::encrypted, this,
+                [this, sslSocket]() {
+                    addPendingConnection(sslSocket);
+                });
+            sslSocket->setSslConfiguration(configuration);
+            sslSocket->setSocketDescriptor(sockfd);
+            sslSocket->startServerEncryption();
+#else
+            QFAIL("withEncryption should not be 'true' if we don't have TLS");
+#endif
+        } else {
+            QTcpSocket *socket = new QTcpSocket(this);
+            socket->setSocketDescriptor(sockfd);
+            addPendingConnection(socket);
+        }
+    }
+
+    void handleConnection()
+    {
+        QTcpSocket *serverSocket = nextPendingConnection();
+        connect(serverSocket, &QTcpSocket::readyRead, this, 
&AuthServer::handleReadyRead);
+    }
+
+    void handleReadyRead()
+    {
+        auto *serverSocket = qobject_cast<QTcpSocket *>(sender());
+        incomingData.append(serverSocket->readAll());
+        if (finished) {
+            qWarning() << "Unexpected trailing data..." << incomingData;
+            return;
+        }
+        if (!incomingData.contains("\r\n\r\n")) {
+            qDebug("Not all of the data arrived at once, waiting for more...");
+            return;
+        }
+        // Move incomingData into local variable and reset it since we 
received it all:
+        const QByteArray fullHeader = std::exchange(incomingData, {});
+
+        QLatin1StringView authView = getHeaderValue("Authorization"_L1, 
fullHeader);
+        if (authView.isEmpty())
+            return writeAuthRequired(serverSocket);
+        qsizetype sep = authView.indexOf(' ');
+        if (sep == -1)
+            return writeAuthRequired(serverSocket);
+        QLatin1StringView authenticateMethod = authView.first(sep);
+        QLatin1StringView authenticateAttempt = authView.sliced(sep + 1);
+        if (authenticateMethod != "Basic" || authenticateAttempt != 
expectedBasicPayload())
+            return writeAuthRequired(serverSocket);
+
+        QLatin1StringView keyView = getHeaderValue("Sec-WebSocket-Key"_L1, 
fullHeader);
+        QVERIFY(!keyView.isEmpty());
+
+        const QByteArray accept =
+                QByteArrayView(keyView) % 
QByteArrayLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+        auto generatedKey = QCryptographicHash::hash(accept, 
QCryptographicHash::Sha1).toBase64();
+        serverSocket->write("HTTP/1.1 101 Switching Protocols\r\n"
+                            "Upgrade: websocket\r\n"
+                            "Connection: Upgrade\r\n"
+                            "Sec-WebSocket-Accept: " % generatedKey % "\r\n"
+                            "\r\n");
+        finished = true;
+    }
+
+    void writeAuthRequired(QTcpSocket *socket) const
+    {
+        QByteArray payload = "HTTP/1.1 401 UNAUTHORIZED\r\n"
+            "WWW-Authenticate: Basic realm=shadow\r\n";
+        if (withConnectionClose)
+            payload.append("Connection: Close\r\n");
+        else if (withContentLength)
+            payload.append("Content-Length: " % 
QByteArray::number(body.size()) % "\r\n");
+        payload.append("\r\n");
+
+        if (withBody)
+            payload.append(body);
+
+        socket->write(payload);
+        if (withConnectionClose)
+            socket->disconnectFromHost();
+    }
+
+    static QLatin1StringView getHeaderValue(const QLatin1StringView keyHeader,
+                                            const QByteArrayView fullHeader)
+    {
+        const auto fullHeaderView = QLatin1StringView(fullHeader);
+        const qsizetype headerStart = fullHeaderView.indexOf(keyHeader, 0, 
Qt::CaseInsensitive);
+        if (headerStart == -1)
+            return {};
+        qsizetype valueStart = headerStart + keyHeader.size();
+        Q_ASSERT(fullHeaderView.size() > valueStart);
+        Q_ASSERT(fullHeaderView[valueStart] == ':');
+        ++valueStart;
+        const qsizetype valueEnd = 
fullHeaderView.indexOf(QLatin1StringView("\r\n"), valueStart);
+        if (valueEnd == -1)
+            return {};
+        return fullHeaderView.sliced(valueStart, valueEnd - 
valueStart).trimmed();
+    }
+
+    static QByteArray expectedBasicPayload()
+    {
+        return QByteArray(user % ':' % password).toBase64();
+    }
+
+    static constexpr QByteArrayView user = "user";
+    static constexpr QByteArrayView password = "password";
+    static constexpr QUtf8StringView body = "Authorization required";
+
+    bool withBody = false;
+    bool withContentLength = true;
+    bool withConnectionClose = false;
+    bool withEncryption = false;
+#if QT_CONFIG(ssl)
+    QSslConfiguration configuration;
+#endif
+
+private:
+    QByteArray incomingData;
+    bool finished = false;
+};
+
+struct ServerScenario {
+    QByteArrayView label;
+    bool withContentLength = false;
+    bool withBody = false;
+    bool withConnectionClose = false;
+    bool withEncryption = false;
+};
+struct Credentials { QString username, password; };
+struct ClientScenario {
+    QByteArrayView label;
+    Credentials urlCredentials;
+    QVector<Credentials> callbackCredentials;
+    bool expectSuccess = true;
+};
+
+void tst_QWebSocket::authenticationRequired_data()
+{
+    const QString correctUser = 
QString::fromUtf8(AuthServer::user.toByteArray());
+    const QString correctPassword = 
QString::fromUtf8(AuthServer::password.toByteArray());
+
+    QTest::addColumn<ServerScenario>("serverScenario");
+    QTest::addColumn<ClientScenario>("clientScenario");
+
+    // Need to test multiple server scenarios:
+    // 1. Normal server (connection: keep-alive, Content-Length)
+    // 2. Older server (connection: close, Content-Length)
+    // 3. Even older server (connection: close, no Content-Length)
+    // 4. Strange server (connection: close, no Content-Length, no body)
+    // 5. Quiet server (connection: keep-alive, no Content-Length, no body)
+    ServerScenario serverScenarios[] = {
+        { "normal-server", true, true, false, false },
+        { "connection-close", true, true, true, false },
+        { "connection-close-no-content-length", false, true, true, false },
+        { "connection-close-no-content-length-no-body", false, false, true, 
false },
+        { "keep-alive-no-content-length-no-body", false, false, false, false },
+    };
+
+    // And some client scenarios
+    // 1. User/pass supplied in url
+    // 2. User/pass supplied in callback
+    // 3. _Wrong_ user/pass supplied in URL, correct in callback
+    // 4. _Wrong_ user/pass supplied in URL, _wrong_ supplied in callback
+    // 5. No user/pass supplied in URL, nothing supplied in callback
+    // 5. No user/pass supplied in URL, wrong, then correct, supplied in 
callback
+    ClientScenario clientScenarios[]{
+        { "url-ok", {correctUser, correctPassword}, {} },
+        { "callback-ok", {}, { {correctUser, correctPassword } } },
+        { "url-wrong-callback-ok", {u"admin"_s, u"admin"_s}, { {correctUser, 
correctPassword} } },
+        { "url-wrong-callback-wrong", {u"admin"_s, u"admin"_s}, { {u"test"_s, 
u"test"_s} }, false },
+        { "no-creds", {{}, {}}, {}, false },
+        { "url-wrong-callback-2-ok", {u"admin"_s, u"admin"_s}, { {u"test"_s, 
u"test"_s}, {correctUser , correctPassword} } },
+    };
+
+    for (auto &server : serverScenarios) {
+        for (auto &client : clientScenarios) {
+            QTest::addRow("Server:%s,Client:%s", server.label.data(), 
client.label.data())
+                    << server << client;
+        }
+    }
+#if QT_CONFIG(ssl)
+    if (!QSslSocket::supportsSsl()) {
+        qDebug("Skipping the SslServer part of this test because proper TLS is 
not supported.");
+        return;
+    }
+    // And double that, but now with TLS
+    for (auto &server : serverScenarios) {
+        server.withEncryption = true;
+        for (auto &client : clientScenarios) {
+            QTest::addRow("SslServer:%s,Client:%s", server.label.data(), 
client.label.data())
+                    << server << client;
+        }
+    }
+#endif
+}
+
+void tst_QWebSocket::authenticationRequired()
+{
+    QFETCH(const ServerScenario, serverScenario);
+    QFETCH(const ClientScenario, clientScenario);
+
+    int credentialIndex = 0;
+    auto handleAuthenticationRequired = [&clientScenario,
+                                         &credentialIndex](QAuthenticator 
*authenticator) {
+        if (credentialIndex == clientScenario.callbackCredentials.size()) {
+            if (clientScenario.expectSuccess)
+                QFAIL("Ran out of credentials to try, but failed to 
authorize!");
+            if (clientScenario.callbackCredentials.isEmpty())
+                return;
+            // If we don't expect to succeed, retry the last returned 
credentials.
+            // QAuthenticator should notice there is no change in user/pass and
+            // ignore it, leading to authentication failure.
+            --credentialIndex;
+        }
+        // Verify that realm parsing works:
+        QCOMPARE_EQ(authenticator->realm(), u"shadow"_s);
+
+        Credentials credentials = 
clientScenario.callbackCredentials[credentialIndex++];
+        authenticator->setUser(credentials.username);
+        authenticator->setPassword(credentials.password);
+    };
+
+    AuthServer server;
+    server.withBody = serverScenario.withBody;
+    server.withContentLength = serverScenario.withContentLength;
+    server.withConnectionClose = serverScenario.withConnectionClose;
+    server.withEncryption = serverScenario.withEncryption;
+#if QT_CONFIG(ssl)
+    if (serverScenario.withEncryption) {
+        QSslConfiguration config = QSslConfiguration::defaultConfiguration();
+        QList<QSslCertificate> certificates = 
QSslCertificate::fromPath(u":/localhost.cert"_s);
+        QVERIFY(!certificates.isEmpty());
+        config.setLocalCertificateChain(certificates);
+        QFile keyFile(u":/localhost.key"_s);
+        QVERIFY(keyFile.open(QIODevice::ReadOnly));
+        config.setPrivateKey(QSslKey(keyFile.readAll(), QSsl::Rsa));
+        server.configuration = config;
+    }
+#endif
+
+    QVERIFY(server.listen());
+    QUrl url = QUrl(u"ws://127.0.0.1"_s);
+    if (serverScenario.withEncryption)
+        url.setScheme(u"wss"_s);
+    url.setPort(server.serverPort());
+    url.setUserName(clientScenario.urlCredentials.username);
+    url.setPassword(clientScenario.urlCredentials.password);
+
+    QWebSocket socket;
+    QSignalSpy connectedSpy(&socket, &QWebSocket::connected);
+    QSignalSpy errorSpy(&socket, &QWebSocket::errorOccurred);
+    QSignalSpy stateChangedSpy(&socket, &QWebSocket::stateChanged);
+    connect(&socket, &QWebSocket::authenticationRequired, &socket, 
handleAuthenticationRequired);
+#if QT_CONFIG(ssl)
+    if (serverScenario.withEncryption) {
+        auto config = socket.sslConfiguration();
+        config.setPeerVerifyMode(QSslSocket::VerifyNone);
+        socket.setSslConfiguration(config);
+        QObject::connect(&socket, &QWebSocket::sslErrors, &socket,
+                qOverload<>(&QWebSocket::ignoreSslErrors));
+    }
+#endif
+    socket.open(url);
+
+    if (clientScenario.expectSuccess) {
+        // Wait for connected!
+        QTRY_COMPARE_EQ(connectedSpy.size(), 1);
+        QCOMPARE_EQ(errorSpy.size(), 0);
+        // connecting->connected
+        const int ExpectedStateChanges = 2;
+        QTRY_COMPARE_EQ(stateChangedSpy.size(), ExpectedStateChanges);
+        auto firstState = 
stateChangedSpy.at(0).front().value<QAbstractSocket::SocketState>();
+        QCOMPARE_EQ(firstState, QAbstractSocket::ConnectingState);
+        auto secondState = 
stateChangedSpy.at(1).front().value<QAbstractSocket::SocketState>();
+        QCOMPARE_EQ(secondState, QAbstractSocket::ConnectedState);
+    } else {
+        // Wait for error!
+        QTRY_COMPARE_EQ(errorSpy.size(), 1);
+        QCOMPARE_EQ(connectedSpy.size(), 0);
+        // connecting->unconnected
+        const int ExpectedStateChanges = 2;
+        QTRY_COMPARE_EQ(stateChangedSpy.size(), ExpectedStateChanges);
+        auto firstState = 
stateChangedSpy.at(0).front().value<QAbstractSocket::SocketState>();
+        QCOMPARE_EQ(firstState, QAbstractSocket::ConnectingState);
+        auto secondState = 
stateChangedSpy.at(1).front().value<QAbstractSocket::SocketState>();
+        QCOMPARE_EQ(secondState, QAbstractSocket::UnconnectedState);
+    }
+}
+
 void tst_QWebSocket::overlongCloseReason()
 {
     EchoServer echoServer;

Reply via email to