| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtWebSockets module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or (at your option) the GNU General |
| 28 | ** Public license version 3 or any later version approved by the KDE Free |
| 29 | ** Qt Foundation. The licenses are as published by the Free Software |
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| 31 | ** included in the packaging of this file. Please review the following |
| 32 | ** information to ensure the GNU General Public License requirements will |
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
| 35 | ** |
| 36 | ** $QT_END_LICENSE$ |
| 37 | ** |
| 38 | ****************************************************************************/ |
| 39 | |
| 40 | #include "qwebsocket.h" |
| 41 | #include "qwebsocket_p.h" |
| 42 | #include "qwebsocketprotocol_p.h" |
| 43 | #include "qwebsockethandshakerequest_p.h" |
| 44 | #include "qwebsockethandshakeresponse_p.h" |
| 45 | #include "qdefaultmaskgenerator_p.h" |
| 46 | |
| 47 | #include <QtCore/QUrl> |
| 48 | #include <QtNetwork/QAuthenticator> |
| 49 | #include <QtNetwork/QTcpSocket> |
| 50 | #include <QtCore/QByteArray> |
| 51 | #include <QtCore/QtEndian> |
| 52 | #include <QtCore/QCryptographicHash> |
| 53 | #include <QtCore/QRegularExpression> |
| 54 | #include <QtCore/QStringList> |
| 55 | #include <QtNetwork/QHostAddress> |
| 56 | #include <QtCore/QStringBuilder> //for more efficient string concatenation |
| 57 | #ifndef QT_NONETWORKPROXY |
| 58 | #include <QtNetwork/QNetworkProxy> |
| 59 | #endif |
| 60 | #ifndef QT_NO_SSL |
| 61 | #include <QtNetwork/QSslConfiguration> |
| 62 | #include <QtNetwork/QSslError> |
| 63 | #include <QtNetwork/QSslPreSharedKeyAuthenticator> |
| 64 | #endif |
| 65 | |
| 66 | #include <QtCore/QDebug> |
| 67 | |
| 68 | #include <limits> |
| 69 | |
| 70 | QT_BEGIN_NAMESPACE |
| 71 | |
| 72 | const quint64 MAX_OUTGOING_FRAME_SIZE_IN_BYTES = std::numeric_limits<int>::max() - 1; |
| 73 | const quint64 DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //default size of a frame when sending a message |
| 74 | |
| 75 | QWebSocketConfiguration::QWebSocketConfiguration() : |
| 76 | #ifndef QT_NO_SSL |
| 77 | m_ignoredSslErrors(), |
| 78 | m_ignoreSslErrors(false), |
| 79 | #endif |
| 80 | #ifndef QT_NO_NETWORKPROXY |
| 81 | m_proxy(QNetworkProxy::DefaultProxy), |
| 82 | #endif |
| 83 | m_pSocket(nullptr) |
| 84 | { |
| 85 | } |
| 86 | |
| 87 | /*! |
| 88 | \internal |
| 89 | */ |
| 90 | QWebSocketPrivate::QWebSocketPrivate(const QString &origin, QWebSocketProtocol::Version version) : |
| 91 | QObjectPrivate(), |
| 92 | m_pSocket(nullptr), |
| 93 | m_errorString(), |
| 94 | m_version(version), |
| 95 | m_resourceName(), |
| 96 | m_request(), |
| 97 | m_origin(origin), |
| 98 | m_protocol(), |
| 99 | m_extension(), |
| 100 | m_socketState(QAbstractSocket::UnconnectedState), |
| 101 | m_pauseMode(QAbstractSocket::PauseNever), |
| 102 | m_readBufferSize(0), |
| 103 | m_key(), |
| 104 | m_mustMask(true), |
| 105 | m_isClosingHandshakeSent(false), |
| 106 | m_isClosingHandshakeReceived(false), |
| 107 | m_closeCode(QWebSocketProtocol::CloseCodeNormal), |
| 108 | m_closeReason(), |
| 109 | m_pingTimer(), |
| 110 | m_configuration(), |
| 111 | m_pMaskGenerator(&m_defaultMaskGenerator), |
| 112 | m_defaultMaskGenerator(), |
| 113 | m_handshakeState(NothingDoneState), |
| 114 | m_outgoingFrameSize(DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES) |
| 115 | { |
| 116 | m_pingTimer.start(); |
| 117 | } |
| 118 | |
| 119 | /*! |
| 120 | \internal |
| 121 | */ |
| 122 | QWebSocketPrivate::QWebSocketPrivate(QTcpSocket *pTcpSocket, QWebSocketProtocol::Version version) : |
| 123 | QObjectPrivate(), |
| 124 | m_pSocket(pTcpSocket), |
| 125 | m_errorString(pTcpSocket->errorString()), |
| 126 | m_version(version), |
| 127 | m_resourceName(), |
| 128 | m_request(), |
| 129 | m_origin(), |
| 130 | m_protocol(), |
| 131 | m_extension(), |
| 132 | m_socketState(pTcpSocket->state()), |
| 133 | m_pauseMode(pTcpSocket->pauseMode()), |
| 134 | m_readBufferSize(pTcpSocket->readBufferSize()), |
| 135 | m_key(), |
| 136 | m_mustMask(true), |
| 137 | m_isClosingHandshakeSent(false), |
| 138 | m_isClosingHandshakeReceived(false), |
| 139 | m_closeCode(QWebSocketProtocol::CloseCodeNormal), |
| 140 | m_closeReason(), |
| 141 | m_pingTimer(), |
| 142 | m_configuration(), |
| 143 | m_pMaskGenerator(&m_defaultMaskGenerator), |
| 144 | m_defaultMaskGenerator(), |
| 145 | m_handshakeState(NothingDoneState), |
| 146 | m_outgoingFrameSize(DEFAULT_OUTGOING_FRAME_SIZE_IN_BYTES) |
| 147 | { |
| 148 | m_pingTimer.start(); |
| 149 | } |
| 150 | |
| 151 | /*! |
| 152 | \internal |
| 153 | */ |
| 154 | void QWebSocketPrivate::init() |
| 155 | { |
| 156 | Q_ASSERT(q_ptr); |
| 157 | Q_ASSERT(m_pMaskGenerator); |
| 158 | |
| 159 | m_dataProcessor->setParent(q_ptr); |
| 160 | m_pMaskGenerator->seed(); |
| 161 | |
| 162 | if (m_pSocket) { |
| 163 | makeConnections(pTcpSocket: m_pSocket); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | /*! |
| 168 | \internal |
| 169 | */ |
| 170 | QWebSocketPrivate::~QWebSocketPrivate() |
| 171 | { |
| 172 | } |
| 173 | |
| 174 | /*! |
| 175 | \internal |
| 176 | */ |
| 177 | void QWebSocketPrivate::closeGoingAway() |
| 178 | { |
| 179 | if (!m_pSocket) |
| 180 | return; |
| 181 | if (state() == QAbstractSocket::ConnectedState) |
| 182 | close(closeCode: QWebSocketProtocol::CloseCodeGoingAway, reason: QWebSocket::tr(s: "Connection closed" )); |
| 183 | releaseConnections(pTcpSocket: m_pSocket); |
| 184 | } |
| 185 | |
| 186 | /*! |
| 187 | \internal |
| 188 | */ |
| 189 | void QWebSocketPrivate::abort() |
| 190 | { |
| 191 | if (m_pSocket) |
| 192 | m_pSocket->abort(); |
| 193 | } |
| 194 | |
| 195 | /*! |
| 196 | \internal |
| 197 | */ |
| 198 | QAbstractSocket::SocketError QWebSocketPrivate::error() const |
| 199 | { |
| 200 | QAbstractSocket::SocketError err = QAbstractSocket::UnknownSocketError; |
| 201 | if (Q_LIKELY(m_pSocket)) |
| 202 | err = m_pSocket->error(); |
| 203 | return err; |
| 204 | } |
| 205 | |
| 206 | /*! |
| 207 | \internal |
| 208 | */ |
| 209 | QString QWebSocketPrivate::errorString() const |
| 210 | { |
| 211 | QString errMsg; |
| 212 | if (!m_errorString.isEmpty()) |
| 213 | errMsg = m_errorString; |
| 214 | else if (m_pSocket) |
| 215 | errMsg = m_pSocket->errorString(); |
| 216 | return errMsg; |
| 217 | } |
| 218 | |
| 219 | /*! |
| 220 | \internal |
| 221 | */ |
| 222 | bool QWebSocketPrivate::flush() |
| 223 | { |
| 224 | bool result = true; |
| 225 | if (Q_LIKELY(m_pSocket)) |
| 226 | result = m_pSocket->flush(); |
| 227 | return result; |
| 228 | } |
| 229 | |
| 230 | #ifndef Q_OS_WASM |
| 231 | |
| 232 | /*! |
| 233 | \internal |
| 234 | */ |
| 235 | qint64 QWebSocketPrivate::sendTextMessage(const QString &message) |
| 236 | { |
| 237 | return doWriteFrames(data: message.toUtf8(), isBinary: false); |
| 238 | } |
| 239 | |
| 240 | /*! |
| 241 | \internal |
| 242 | */ |
| 243 | qint64 QWebSocketPrivate::sendBinaryMessage(const QByteArray &data) |
| 244 | { |
| 245 | return doWriteFrames(data, isBinary: true); |
| 246 | } |
| 247 | |
| 248 | #endif |
| 249 | |
| 250 | #ifndef QT_NO_SSL |
| 251 | /*! |
| 252 | \internal |
| 253 | */ |
| 254 | void QWebSocketPrivate::setSslConfiguration(const QSslConfiguration &sslConfiguration) |
| 255 | { |
| 256 | m_configuration.m_sslConfiguration = sslConfiguration; |
| 257 | } |
| 258 | |
| 259 | /*! |
| 260 | \internal |
| 261 | */ |
| 262 | QSslConfiguration QWebSocketPrivate::sslConfiguration() const |
| 263 | { |
| 264 | return m_configuration.m_sslConfiguration; |
| 265 | } |
| 266 | |
| 267 | /*! |
| 268 | \internal |
| 269 | */ |
| 270 | void QWebSocketPrivate::ignoreSslErrors(const QList<QSslError> &errors) |
| 271 | { |
| 272 | m_configuration.m_ignoredSslErrors = errors; |
| 273 | if (Q_LIKELY(m_pSocket)) { |
| 274 | QSslSocket *pSslSocket = qobject_cast<QSslSocket *>(object: m_pSocket); |
| 275 | if (Q_LIKELY(pSslSocket)) |
| 276 | pSslSocket->ignoreSslErrors(errors); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | /*! |
| 281 | * \internal |
| 282 | */ |
| 283 | void QWebSocketPrivate::ignoreSslErrors() |
| 284 | { |
| 285 | m_configuration.m_ignoreSslErrors = true; |
| 286 | if (Q_LIKELY(m_pSocket)) { |
| 287 | QSslSocket *pSslSocket = qobject_cast<QSslSocket *>(object: m_pSocket); |
| 288 | if (Q_LIKELY(pSslSocket)) |
| 289 | pSslSocket->ignoreSslErrors(); |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | /*! |
| 294 | * \internal |
| 295 | */ |
| 296 | void QWebSocketPrivate::_q_updateSslConfiguration() |
| 297 | { |
| 298 | if (QSslSocket *sslSock = qobject_cast<QSslSocket *>(object: m_pSocket)) |
| 299 | m_configuration.m_sslConfiguration = sslSock->sslConfiguration(); |
| 300 | } |
| 301 | |
| 302 | #endif |
| 303 | |
| 304 | /*! |
| 305 | Called from QWebSocketServer |
| 306 | \internal |
| 307 | */ |
| 308 | QWebSocket *QWebSocketPrivate::upgradeFrom(QTcpSocket *pTcpSocket, |
| 309 | const QWebSocketHandshakeRequest &request, |
| 310 | const QWebSocketHandshakeResponse &response, |
| 311 | QObject *parent) |
| 312 | { |
| 313 | QWebSocket *pWebSocket = new QWebSocket(pTcpSocket, response.acceptedVersion(), parent); |
| 314 | if (Q_LIKELY(pWebSocket)) { |
| 315 | QNetworkRequest netRequest(request.requestUrl()); |
| 316 | const auto = request.headers(); |
| 317 | for (auto it = headers.begin(), end = headers.end(); it != end; ++it) |
| 318 | netRequest.setRawHeader(headerName: it.key().toLatin1(), value: it.value().toLatin1()); |
| 319 | #ifndef QT_NO_SSL |
| 320 | if (QSslSocket *sslSock = qobject_cast<QSslSocket *>(object: pTcpSocket)) |
| 321 | pWebSocket->setSslConfiguration(sslSock->sslConfiguration()); |
| 322 | #endif |
| 323 | pWebSocket->d_func()->setExtension(response.acceptedExtension()); |
| 324 | pWebSocket->d_func()->setOrigin(request.origin()); |
| 325 | pWebSocket->d_func()->setRequest(netRequest); |
| 326 | pWebSocket->d_func()->setProtocol(response.acceptedProtocol()); |
| 327 | pWebSocket->d_func()->setResourceName(request.requestUrl().toString(options: QUrl::RemoveUserInfo)); |
| 328 | //a server should not send masked frames |
| 329 | pWebSocket->d_func()->enableMasking(enable: false); |
| 330 | } |
| 331 | |
| 332 | return pWebSocket; |
| 333 | } |
| 334 | |
| 335 | #ifndef Q_OS_WASM |
| 336 | |
| 337 | /*! |
| 338 | \internal |
| 339 | */ |
| 340 | void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString reason) |
| 341 | { |
| 342 | if (Q_UNLIKELY(!m_pSocket)) |
| 343 | return; |
| 344 | if (!m_isClosingHandshakeSent) { |
| 345 | Q_Q(QWebSocket); |
| 346 | m_closeCode = closeCode; |
| 347 | // 125 is the maximum length of a control frame, and 2 bytes are used for the close code: |
| 348 | const QByteArray reasonUtf8 = reason.toUtf8().left(len: 123); |
| 349 | m_closeReason = QString::fromUtf8(str: reasonUtf8); |
| 350 | const quint16 code = qToBigEndian<quint16>(source: closeCode); |
| 351 | QByteArray payload; |
| 352 | payload.append(s: static_cast<const char *>(static_cast<const void *>(&code)), len: 2); |
| 353 | if (!reasonUtf8.isEmpty()) |
| 354 | payload.append(a: reasonUtf8); |
| 355 | quint32 maskingKey = 0; |
| 356 | if (m_mustMask) { |
| 357 | maskingKey = generateMaskingKey(); |
| 358 | QWebSocketProtocol::mask(payload: payload.data(), size: quint64(payload.size()), maskingKey); |
| 359 | } |
| 360 | QByteArray frame = getFrameHeader(opCode: QWebSocketProtocol::OpCodeClose, |
| 361 | payloadLength: quint64(payload.size()), maskingKey, lastFrame: true); |
| 362 | |
| 363 | Q_ASSERT(payload.length() <= 125); |
| 364 | frame.append(a: payload); |
| 365 | m_pSocket->write(data: frame); |
| 366 | m_pSocket->flush(); |
| 367 | |
| 368 | m_isClosingHandshakeSent = true; |
| 369 | |
| 370 | Q_EMIT q->aboutToClose(); |
| 371 | } |
| 372 | m_pSocket->close(); |
| 373 | } |
| 374 | |
| 375 | /*! |
| 376 | \internal |
| 377 | */ |
| 378 | void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask) |
| 379 | { |
| 380 | //just delete the old socket for the moment; |
| 381 | //later, we can add more 'intelligent' handling by looking at the URL |
| 382 | |
| 383 | Q_Q(QWebSocket); |
| 384 | QUrl url = request.url(); |
| 385 | if (!url.isValid() || url.toString().contains(QStringLiteral("\r\n" ))) { |
| 386 | setErrorString(QWebSocket::tr(s: "Invalid URL." )); |
| 387 | Q_EMIT q->error(error: QAbstractSocket::ConnectionRefusedError); |
| 388 | return; |
| 389 | } |
| 390 | if (m_pSocket) { |
| 391 | releaseConnections(pTcpSocket: m_pSocket); |
| 392 | m_pSocket->deleteLater(); |
| 393 | m_pSocket = nullptr; |
| 394 | } |
| 395 | //if (m_url != url) |
| 396 | if (Q_LIKELY(!m_pSocket)) { |
| 397 | m_dataProcessor->clear(); |
| 398 | m_isClosingHandshakeReceived = false; |
| 399 | m_isClosingHandshakeSent = false; |
| 400 | |
| 401 | setRequest(request); |
| 402 | QString resourceName = url.path(options: QUrl::FullyEncoded); |
| 403 | // Check for encoded \r\n |
| 404 | if (resourceName.contains(QStringLiteral("%0D%0A" ))) { |
| 405 | setRequest(QNetworkRequest()); //clear request |
| 406 | setErrorString(QWebSocket::tr(s: "Invalid resource name." )); |
| 407 | Q_EMIT q->error(error: QAbstractSocket::ConnectionRefusedError); |
| 408 | return; |
| 409 | } |
| 410 | if (!url.query().isEmpty()) { |
| 411 | if (!resourceName.endsWith(c: QChar::fromLatin1(c: '?'))) { |
| 412 | resourceName.append(c: QChar::fromLatin1(c: '?')); |
| 413 | } |
| 414 | resourceName.append(s: url.query(QUrl::FullyEncoded)); |
| 415 | } |
| 416 | if (resourceName.isEmpty()) |
| 417 | resourceName = QStringLiteral("/" ); |
| 418 | setResourceName(resourceName); |
| 419 | enableMasking(enable: mask); |
| 420 | |
| 421 | #ifndef QT_NO_SSL |
| 422 | if (url.scheme() == QStringLiteral("wss" )) { |
| 423 | if (!QSslSocket::supportsSsl()) { |
| 424 | const QString message = |
| 425 | QWebSocket::tr(s: "SSL Sockets are not supported on this platform." ); |
| 426 | setErrorString(message); |
| 427 | Q_EMIT q->error(error: QAbstractSocket::UnsupportedSocketOperationError); |
| 428 | } else { |
| 429 | QSslSocket *sslSocket = new QSslSocket(q); |
| 430 | m_pSocket = sslSocket; |
| 431 | if (Q_LIKELY(m_pSocket)) { |
| 432 | QObject::connect(sender: sslSocket, signal: &QSslSocket::connected, slot: [sslSocket](){ |
| 433 | sslSocket->setSocketOption(option: QAbstractSocket::LowDelayOption, value: 1); |
| 434 | sslSocket->setSocketOption(option: QAbstractSocket::KeepAliveOption, value: 1); |
| 435 | }); |
| 436 | m_pSocket->setReadBufferSize(m_readBufferSize); |
| 437 | m_pSocket->setPauseMode(m_pauseMode); |
| 438 | |
| 439 | makeConnections(pTcpSocket: m_pSocket); |
| 440 | setSocketState(QAbstractSocket::ConnectingState); |
| 441 | |
| 442 | sslSocket->setSslConfiguration(m_configuration.m_sslConfiguration); |
| 443 | if (Q_UNLIKELY(m_configuration.m_ignoreSslErrors)) |
| 444 | sslSocket->ignoreSslErrors(); |
| 445 | else |
| 446 | sslSocket->ignoreSslErrors(errors: m_configuration.m_ignoredSslErrors); |
| 447 | #ifndef QT_NO_NETWORKPROXY |
| 448 | sslSocket->setProxy(m_configuration.m_proxy); |
| 449 | m_pSocket->setProtocolTag(QStringLiteral("https" )); |
| 450 | #endif |
| 451 | sslSocket->connectToHostEncrypted(hostName: url.host(), port: quint16(url.port(defaultPort: 443))); |
| 452 | } else { |
| 453 | const QString message = QWebSocket::tr(s: "Out of memory." ); |
| 454 | setErrorString(message); |
| 455 | Q_EMIT q->error(error: QAbstractSocket::SocketResourceError); |
| 456 | } |
| 457 | } |
| 458 | } else |
| 459 | #endif |
| 460 | if (url.scheme() == QStringLiteral("ws" )) { |
| 461 | m_pSocket = new QTcpSocket(q); |
| 462 | if (Q_LIKELY(m_pSocket)) { |
| 463 | QObject::connect(sender: m_pSocket, signal: &QTcpSocket::connected, slot: [this](){ |
| 464 | m_pSocket->setSocketOption(option: QAbstractSocket::LowDelayOption, value: 1); |
| 465 | m_pSocket->setSocketOption(option: QAbstractSocket::KeepAliveOption, value: 1); |
| 466 | }); |
| 467 | m_pSocket->setReadBufferSize(m_readBufferSize); |
| 468 | m_pSocket->setPauseMode(m_pauseMode); |
| 469 | |
| 470 | makeConnections(pTcpSocket: m_pSocket); |
| 471 | setSocketState(QAbstractSocket::ConnectingState); |
| 472 | #ifndef QT_NO_NETWORKPROXY |
| 473 | m_pSocket->setProxy(m_configuration.m_proxy); |
| 474 | m_pSocket->setProtocolTag(QStringLiteral("http" )); |
| 475 | #endif |
| 476 | m_pSocket->connectToHost(hostName: url.host(), port: quint16(url.port(defaultPort: 80))); |
| 477 | } else { |
| 478 | const QString message = QWebSocket::tr(s: "Out of memory." ); |
| 479 | setErrorString(message); |
| 480 | Q_EMIT q->error(error: QAbstractSocket::SocketResourceError); |
| 481 | } |
| 482 | } else { |
| 483 | const QString message = |
| 484 | QWebSocket::tr(s: "Unsupported WebSocket scheme: %1" ).arg(a: url.scheme()); |
| 485 | setErrorString(message); |
| 486 | Q_EMIT q->error(error: QAbstractSocket::UnsupportedSocketOperationError); |
| 487 | } |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | #endif |
| 492 | |
| 493 | /*! |
| 494 | \internal |
| 495 | */ |
| 496 | void QWebSocketPrivate::ping(const QByteArray &payload) |
| 497 | { |
| 498 | QByteArray payloadTruncated = payload.left(len: 125); |
| 499 | m_pingTimer.restart(); |
| 500 | quint32 maskingKey = 0; |
| 501 | if (m_mustMask) |
| 502 | maskingKey = generateMaskingKey(); |
| 503 | QByteArray pingFrame = getFrameHeader(opCode: QWebSocketProtocol::OpCodePing, |
| 504 | payloadLength: quint64(payloadTruncated.size()), |
| 505 | maskingKey, lastFrame: true); |
| 506 | if (m_mustMask) |
| 507 | QWebSocketProtocol::mask(payload: &payloadTruncated, maskingKey); |
| 508 | pingFrame.append(a: payloadTruncated); |
| 509 | qint64 ret = writeFrame(frame: pingFrame); |
| 510 | Q_UNUSED(ret); |
| 511 | } |
| 512 | |
| 513 | /*! |
| 514 | \internal |
| 515 | Sets the version to use for the WebSocket protocol; |
| 516 | this must be set before the socket is opened. |
| 517 | */ |
| 518 | void QWebSocketPrivate::setVersion(QWebSocketProtocol::Version version) |
| 519 | { |
| 520 | if (m_version != version) |
| 521 | m_version = version; |
| 522 | } |
| 523 | |
| 524 | /*! |
| 525 | \internal |
| 526 | Sets the resource name of the connection; must be set before the socket is openend |
| 527 | */ |
| 528 | void QWebSocketPrivate::setResourceName(const QString &resourceName) |
| 529 | { |
| 530 | if (m_resourceName != resourceName) |
| 531 | m_resourceName = resourceName; |
| 532 | } |
| 533 | |
| 534 | /*! |
| 535 | \internal |
| 536 | */ |
| 537 | void QWebSocketPrivate::setRequest(const QNetworkRequest &request) |
| 538 | { |
| 539 | if (m_request != request) |
| 540 | m_request = request; |
| 541 | } |
| 542 | |
| 543 | /*! |
| 544 | \internal |
| 545 | */ |
| 546 | void QWebSocketPrivate::setOrigin(const QString &origin) |
| 547 | { |
| 548 | if (m_origin != origin) |
| 549 | m_origin = origin; |
| 550 | } |
| 551 | |
| 552 | /*! |
| 553 | \internal |
| 554 | */ |
| 555 | void QWebSocketPrivate::setProtocol(const QString &protocol) |
| 556 | { |
| 557 | if (m_protocol != protocol) |
| 558 | m_protocol = protocol; |
| 559 | } |
| 560 | |
| 561 | /*! |
| 562 | \internal |
| 563 | */ |
| 564 | void QWebSocketPrivate::setExtension(const QString &extension) |
| 565 | { |
| 566 | if (m_extension != extension) |
| 567 | m_extension = extension; |
| 568 | } |
| 569 | |
| 570 | /*! |
| 571 | \internal |
| 572 | */ |
| 573 | void QWebSocketPrivate::enableMasking(bool enable) |
| 574 | { |
| 575 | if (m_mustMask != enable) |
| 576 | m_mustMask = enable; |
| 577 | } |
| 578 | |
| 579 | /*! |
| 580 | * \internal |
| 581 | */ |
| 582 | void QWebSocketPrivate::makeConnections(QTcpSocket *pTcpSocket) |
| 583 | { |
| 584 | Q_ASSERT(pTcpSocket); |
| 585 | Q_Q(QWebSocket); |
| 586 | |
| 587 | if (Q_LIKELY(pTcpSocket)) { |
| 588 | //pass through signals |
| 589 | QObject::connect(sender: pTcpSocket, signal: &QAbstractSocket::errorOccurred, |
| 590 | receiver: q, slot: QOverload<QAbstractSocket::SocketError>::of(ptr: &QWebSocket::error)); |
| 591 | #ifndef QT_NO_NETWORKPROXY |
| 592 | QObject::connect(sender: pTcpSocket, signal: &QAbstractSocket::proxyAuthenticationRequired, receiver: q, |
| 593 | slot: &QWebSocket::proxyAuthenticationRequired); |
| 594 | #endif // QT_NO_NETWORKPROXY |
| 595 | QObject::connect(sender: pTcpSocket, signal: &QAbstractSocket::readChannelFinished, receiver: q, |
| 596 | slot: &QWebSocket::readChannelFinished); |
| 597 | QObject::connect(sender: pTcpSocket, signal: &QAbstractSocket::aboutToClose, receiver: q, slot: &QWebSocket::aboutToClose); |
| 598 | |
| 599 | QObjectPrivate::connect(sender: pTcpSocket, signal: &QObject::destroyed, |
| 600 | receiverPrivate: this, slot: &QWebSocketPrivate::socketDestroyed); |
| 601 | |
| 602 | //catch signals |
| 603 | QObjectPrivate::connect(sender: pTcpSocket, signal: &QAbstractSocket::stateChanged, receiverPrivate: this, |
| 604 | slot: &QWebSocketPrivate::processStateChanged); |
| 605 | //!!!important to use a QueuedConnection here; |
| 606 | //with QTcpSocket there is no problem, but with QSslSocket the processing hangs |
| 607 | QObjectPrivate::connect(sender: pTcpSocket, signal: &QAbstractSocket::readyRead, receiverPrivate: this, |
| 608 | slot: &QWebSocketPrivate::processData, type: Qt::QueuedConnection); |
| 609 | #ifndef QT_NO_SSL |
| 610 | const QSslSocket * const sslSocket = qobject_cast<const QSslSocket *>(object: pTcpSocket); |
| 611 | if (sslSocket) { |
| 612 | QObject::connect(sender: sslSocket, signal: &QSslSocket::preSharedKeyAuthenticationRequired, receiver: q, |
| 613 | slot: &QWebSocket::preSharedKeyAuthenticationRequired); |
| 614 | QObject::connect(sender: sslSocket, signal: &QSslSocket::encryptedBytesWritten, receiver: q, |
| 615 | slot: &QWebSocket::bytesWritten); |
| 616 | QObjectPrivate::connect(sender: sslSocket, |
| 617 | signal: QOverload<const QList<QSslError>&>::of(ptr: &QSslSocket::sslErrors), |
| 618 | receiverPrivate: this, slot: &QWebSocketPrivate::_q_updateSslConfiguration); |
| 619 | QObject::connect(sender: sslSocket, |
| 620 | signal: QOverload<const QList<QSslError>&>::of(ptr: &QSslSocket::sslErrors), |
| 621 | receiver: q, slot: &QWebSocket::sslErrors); |
| 622 | QObjectPrivate::connect(sender: sslSocket, signal: &QSslSocket::encrypted, |
| 623 | receiverPrivate: this, slot: &QWebSocketPrivate::_q_updateSslConfiguration); |
| 624 | } else |
| 625 | #endif // QT_NO_SSL |
| 626 | { |
| 627 | QObject::connect(sender: pTcpSocket, signal: &QAbstractSocket::bytesWritten, receiver: q, |
| 628 | slot: &QWebSocket::bytesWritten); |
| 629 | } |
| 630 | } |
| 631 | |
| 632 | QObject::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::textFrameReceived, receiver: q, |
| 633 | slot: &QWebSocket::textFrameReceived); |
| 634 | QObject::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::binaryFrameReceived, receiver: q, |
| 635 | slot: &QWebSocket::binaryFrameReceived); |
| 636 | QObject::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::binaryMessageReceived, receiver: q, |
| 637 | slot: &QWebSocket::binaryMessageReceived); |
| 638 | QObject::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::textMessageReceived, receiver: q, |
| 639 | slot: &QWebSocket::textMessageReceived); |
| 640 | QObjectPrivate::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::errorEncountered, receiverPrivate: this, |
| 641 | slot: &QWebSocketPrivate::close); |
| 642 | QObjectPrivate::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::pingReceived, receiverPrivate: this, |
| 643 | slot: &QWebSocketPrivate::processPing); |
| 644 | QObjectPrivate::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::pongReceived, receiverPrivate: this, |
| 645 | slot: &QWebSocketPrivate::processPong); |
| 646 | QObjectPrivate::connect(sender: m_dataProcessor, signal: &QWebSocketDataProcessor::closeReceived, receiverPrivate: this, |
| 647 | slot: &QWebSocketPrivate::processClose); |
| 648 | |
| 649 | //fire readyread, in case we already have data inside the tcpSocket |
| 650 | if (pTcpSocket->bytesAvailable()) |
| 651 | Q_EMIT pTcpSocket->readyRead(); |
| 652 | } |
| 653 | |
| 654 | /*! |
| 655 | * \internal |
| 656 | */ |
| 657 | void QWebSocketPrivate::releaseConnections(const QTcpSocket *pTcpSocket) |
| 658 | { |
| 659 | if (Q_LIKELY(pTcpSocket)) |
| 660 | pTcpSocket->disconnect(); |
| 661 | m_dataProcessor->disconnect(); |
| 662 | } |
| 663 | |
| 664 | /*! |
| 665 | \internal |
| 666 | */ |
| 667 | QWebSocketProtocol::Version QWebSocketPrivate::version() const |
| 668 | { |
| 669 | return m_version; |
| 670 | } |
| 671 | |
| 672 | /*! |
| 673 | \internal |
| 674 | */ |
| 675 | QString QWebSocketPrivate::resourceName() const |
| 676 | { |
| 677 | return m_resourceName; |
| 678 | } |
| 679 | |
| 680 | /*! |
| 681 | \internal |
| 682 | */ |
| 683 | QNetworkRequest QWebSocketPrivate::request() const |
| 684 | { |
| 685 | return m_request; |
| 686 | } |
| 687 | |
| 688 | /*! |
| 689 | \internal |
| 690 | */ |
| 691 | QString QWebSocketPrivate::origin() const |
| 692 | { |
| 693 | return m_origin; |
| 694 | } |
| 695 | |
| 696 | /*! |
| 697 | \internal |
| 698 | */ |
| 699 | QString QWebSocketPrivate::protocol() const |
| 700 | { |
| 701 | return m_protocol; |
| 702 | } |
| 703 | |
| 704 | /*! |
| 705 | \internal |
| 706 | */ |
| 707 | QString QWebSocketPrivate::extension() const |
| 708 | { |
| 709 | return m_extension; |
| 710 | } |
| 711 | |
| 712 | /*! |
| 713 | * \internal |
| 714 | */ |
| 715 | QWebSocketProtocol::CloseCode QWebSocketPrivate::closeCode() const |
| 716 | { |
| 717 | return m_closeCode; |
| 718 | } |
| 719 | |
| 720 | /*! |
| 721 | * \internal |
| 722 | */ |
| 723 | QString QWebSocketPrivate::closeReason() const |
| 724 | { |
| 725 | return m_closeReason; |
| 726 | } |
| 727 | |
| 728 | /*! |
| 729 | * \internal |
| 730 | */ |
| 731 | QByteArray QWebSocketPrivate::(QWebSocketProtocol::OpCode opCode, |
| 732 | quint64 payloadLength, quint32 maskingKey, |
| 733 | bool lastFrame) |
| 734 | { |
| 735 | Q_Q(QWebSocket); |
| 736 | QByteArray ; |
| 737 | bool ok = payloadLength <= 0x7FFFFFFFFFFFFFFFULL; |
| 738 | |
| 739 | if (Q_LIKELY(ok)) { |
| 740 | //FIN, RSV1-3, opcode (RSV-1, RSV-2 and RSV-3 are zero) |
| 741 | quint8 byte = static_cast<quint8>((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00)); |
| 742 | header.append(c: static_cast<char>(byte)); |
| 743 | |
| 744 | byte = 0x00; |
| 745 | if (maskingKey != 0) |
| 746 | byte |= 0x80; |
| 747 | if (payloadLength <= 125) { |
| 748 | byte |= static_cast<quint8>(payloadLength); |
| 749 | header.append(c: static_cast<char>(byte)); |
| 750 | } else if (payloadLength <= 0xFFFFU) { |
| 751 | byte |= 126; |
| 752 | header.append(c: static_cast<char>(byte)); |
| 753 | quint16 swapped = qToBigEndian<quint16>(source: static_cast<quint16>(payloadLength)); |
| 754 | header.append(s: static_cast<const char *>(static_cast<const void *>(&swapped)), len: 2); |
| 755 | } else if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL) { |
| 756 | byte |= 127; |
| 757 | header.append(c: static_cast<char>(byte)); |
| 758 | quint64 swapped = qToBigEndian<quint64>(source: payloadLength); |
| 759 | header.append(s: static_cast<const char *>(static_cast<const void *>(&swapped)), len: 8); |
| 760 | } |
| 761 | |
| 762 | if (maskingKey != 0) { |
| 763 | const quint32 mask = qToBigEndian<quint32>(source: maskingKey); |
| 764 | header.append(s: static_cast<const char *>(static_cast<const void *>(&mask)), |
| 765 | len: sizeof(quint32)); |
| 766 | } |
| 767 | } else { |
| 768 | setErrorString(QStringLiteral("WebSocket::getHeader: payload too big!" )); |
| 769 | Q_EMIT q->error(error: QAbstractSocket::DatagramTooLargeError); |
| 770 | } |
| 771 | |
| 772 | return header; |
| 773 | } |
| 774 | |
| 775 | /*! |
| 776 | * \internal |
| 777 | */ |
| 778 | qint64 QWebSocketPrivate::doWriteFrames(const QByteArray &data, bool isBinary) |
| 779 | { |
| 780 | qint64 payloadWritten = 0; |
| 781 | if (Q_UNLIKELY(!m_pSocket) || (state() != QAbstractSocket::ConnectedState)) |
| 782 | return payloadWritten; |
| 783 | |
| 784 | Q_Q(QWebSocket); |
| 785 | const QWebSocketProtocol::OpCode firstOpCode = isBinary ? |
| 786 | QWebSocketProtocol::OpCodeBinary : QWebSocketProtocol::OpCodeText; |
| 787 | |
| 788 | int numFrames = data.size() / int(outgoingFrameSize()); |
| 789 | QByteArray tmpData(data); |
| 790 | tmpData.detach(); |
| 791 | char *payload = tmpData.data(); |
| 792 | quint64 sizeLeft = quint64(data.size()) % outgoingFrameSize(); |
| 793 | if (Q_LIKELY(sizeLeft)) |
| 794 | ++numFrames; |
| 795 | |
| 796 | //catch the case where the payload is zero bytes; |
| 797 | //in this case, we still need to send a frame |
| 798 | if (Q_UNLIKELY(numFrames == 0)) |
| 799 | numFrames = 1; |
| 800 | quint64 currentPosition = 0; |
| 801 | quint64 bytesLeft = quint64(data.size()); |
| 802 | |
| 803 | for (int i = 0; i < numFrames; ++i) { |
| 804 | quint32 maskingKey = 0; |
| 805 | if (m_mustMask) |
| 806 | maskingKey = generateMaskingKey(); |
| 807 | |
| 808 | const bool isLastFrame = (i == (numFrames - 1)); |
| 809 | const bool isFirstFrame = (i == 0); |
| 810 | |
| 811 | const quint64 size = qMin(a: bytesLeft, b: outgoingFrameSize()); |
| 812 | const QWebSocketProtocol::OpCode opcode = isFirstFrame ? firstOpCode |
| 813 | : QWebSocketProtocol::OpCodeContinue; |
| 814 | |
| 815 | //write header |
| 816 | m_pSocket->write(data: getFrameHeader(opCode: opcode, payloadLength: size, maskingKey, lastFrame: isLastFrame)); |
| 817 | |
| 818 | //write payload |
| 819 | if (Q_LIKELY(size > 0)) { |
| 820 | char *currentData = payload + currentPosition; |
| 821 | if (m_mustMask) |
| 822 | QWebSocketProtocol::mask(payload: currentData, size, maskingKey); |
| 823 | qint64 written = m_pSocket->write(data: currentData, len: static_cast<qint64>(size)); |
| 824 | if (Q_LIKELY(written > 0)) { |
| 825 | payloadWritten += written; |
| 826 | } else { |
| 827 | m_pSocket->flush(); |
| 828 | setErrorString(QWebSocket::tr(s: "Error writing bytes to socket: %1." ) |
| 829 | .arg(a: m_pSocket->errorString())); |
| 830 | Q_EMIT q->error(error: QAbstractSocket::NetworkError); |
| 831 | break; |
| 832 | } |
| 833 | } |
| 834 | currentPosition += size; |
| 835 | bytesLeft -= size; |
| 836 | } |
| 837 | if (Q_UNLIKELY(payloadWritten != data.size())) { |
| 838 | setErrorString(QWebSocket::tr(s: "Bytes written %1 != %2." ) |
| 839 | .arg(a: payloadWritten).arg(a: data.size())); |
| 840 | Q_EMIT q->error(error: QAbstractSocket::NetworkError); |
| 841 | } |
| 842 | return payloadWritten; |
| 843 | } |
| 844 | |
| 845 | /*! |
| 846 | \internal |
| 847 | */ |
| 848 | quint32 QWebSocketPrivate::generateMaskingKey() const |
| 849 | { |
| 850 | return m_pMaskGenerator->nextMask(); |
| 851 | } |
| 852 | |
| 853 | /*! |
| 854 | \internal |
| 855 | */ |
| 856 | QByteArray QWebSocketPrivate::generateKey() const |
| 857 | { |
| 858 | QByteArray key; |
| 859 | |
| 860 | for (int i = 0; i < 4; ++i) { |
| 861 | const quint32 tmp = m_pMaskGenerator->nextMask(); |
| 862 | key.append(s: static_cast<const char *>(static_cast<const void *>(&tmp)), len: sizeof(quint32)); |
| 863 | } |
| 864 | |
| 865 | return key.toBase64(); |
| 866 | } |
| 867 | |
| 868 | |
| 869 | /*! |
| 870 | \internal |
| 871 | */ |
| 872 | QString QWebSocketPrivate::calculateAcceptKey(const QByteArray &key) const |
| 873 | { |
| 874 | const QByteArray tmpKey = key + QByteArrayLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ); |
| 875 | const QByteArray hash = QCryptographicHash::hash(data: tmpKey, method: QCryptographicHash::Sha1).toBase64(); |
| 876 | return QString::fromLatin1(str: hash); |
| 877 | } |
| 878 | |
| 879 | /*! |
| 880 | \internal |
| 881 | */ |
| 882 | qint64 QWebSocketPrivate::writeFrames(const QList<QByteArray> &frames) |
| 883 | { |
| 884 | qint64 written = 0; |
| 885 | if (Q_LIKELY(m_pSocket)) { |
| 886 | QList<QByteArray>::const_iterator it; |
| 887 | for (it = frames.cbegin(); it < frames.cend(); ++it) |
| 888 | written += writeFrame(frame: *it); |
| 889 | } |
| 890 | return written; |
| 891 | } |
| 892 | |
| 893 | /*! |
| 894 | \internal |
| 895 | */ |
| 896 | qint64 QWebSocketPrivate::writeFrame(const QByteArray &frame) |
| 897 | { |
| 898 | qint64 written = 0; |
| 899 | if (Q_LIKELY(m_pSocket)) |
| 900 | written = m_pSocket->write(data: frame); |
| 901 | return written; |
| 902 | } |
| 903 | |
| 904 | /*! |
| 905 | \internal |
| 906 | */ |
| 907 | static QString readLine(QTcpSocket *pSocket) |
| 908 | { |
| 909 | Q_ASSERT(pSocket); |
| 910 | QString line; |
| 911 | char c; |
| 912 | while (pSocket->getChar(c: &c)) { |
| 913 | if (c == char('\r')) { |
| 914 | pSocket->getChar(c: &c); |
| 915 | break; |
| 916 | } else { |
| 917 | line.append(c: QChar::fromLatin1(c)); |
| 918 | } |
| 919 | } |
| 920 | return line; |
| 921 | } |
| 922 | |
| 923 | // this function is a copy of QHttpNetworkReplyPrivate::parseStatus |
| 924 | static bool parseStatusLine(const QByteArray &status, int *majorVersion, int *minorVersion, |
| 925 | int *statusCode, QString *reasonPhrase) |
| 926 | { |
| 927 | // from RFC 2616: |
| 928 | // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF |
| 929 | // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT |
| 930 | // that makes: 'HTTP/n.n xxx Message' |
| 931 | // byte count: 0123456789012 |
| 932 | |
| 933 | static const int minLength = 11; |
| 934 | static const int dotPos = 6; |
| 935 | static const int spacePos = 8; |
| 936 | static const char httpMagic[] = "HTTP/" ; |
| 937 | |
| 938 | if (status.length() < minLength |
| 939 | || !status.startsWith(c: httpMagic) |
| 940 | || status.at(i: dotPos) != '.' |
| 941 | || status.at(i: spacePos) != ' ') { |
| 942 | // I don't know how to parse this status line |
| 943 | return false; |
| 944 | } |
| 945 | |
| 946 | // optimize for the valid case: defer checking until the end |
| 947 | *majorVersion = status.at(i: dotPos - 1) - '0'; |
| 948 | *minorVersion = status.at(i: dotPos + 1) - '0'; |
| 949 | |
| 950 | int i = spacePos; |
| 951 | int j = status.indexOf(c: ' ', from: i + 1); // j == -1 || at(j) == ' ' so j+1 == 0 && j+1 <= length() |
| 952 | const QByteArray code = status.mid(index: i + 1, len: j - i - 1); |
| 953 | |
| 954 | bool ok; |
| 955 | *statusCode = code.toInt(ok: &ok); |
| 956 | *reasonPhrase = QString::fromLatin1(str: status.constData() + j + 1); |
| 957 | |
| 958 | return ok && uint(*majorVersion) <= 9 && uint(* minorVersion) <= 9; |
| 959 | } |
| 960 | |
| 961 | |
| 962 | //called on the client for a server handshake response |
| 963 | /*! |
| 964 | \internal |
| 965 | */ |
| 966 | void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) |
| 967 | { |
| 968 | Q_Q(QWebSocket); |
| 969 | if (Q_UNLIKELY(!pSocket)) |
| 970 | return; |
| 971 | // Reset handshake on a new connection. |
| 972 | if (m_handshakeState == AllDoneState) |
| 973 | m_handshakeState = NothingDoneState; |
| 974 | |
| 975 | QString errorDescription; |
| 976 | |
| 977 | switch (m_handshakeState) { |
| 978 | case NothingDoneState: |
| 979 | m_headers.clear(); |
| 980 | m_handshakeState = ReadingStatusState; |
| 981 | Q_FALLTHROUGH(); |
| 982 | case ReadingStatusState: |
| 983 | if (!pSocket->canReadLine()) |
| 984 | return; |
| 985 | m_statusLine = pSocket->readLine().trimmed(); |
| 986 | if (Q_UNLIKELY(!parseStatusLine(m_statusLine, &m_httpMajorVersion, &m_httpMinorVersion, &m_httpStatusCode, &m_httpStatusMessage))) { |
| 987 | errorDescription = QWebSocket::tr(s: "Invalid statusline in response: %1." ).arg(a: QString::fromLatin1(str: m_statusLine)); |
| 988 | break; |
| 989 | } |
| 990 | m_handshakeState = ReadingHeaderState; |
| 991 | Q_FALLTHROUGH(); |
| 992 | case ReadingHeaderState: { |
| 993 | // TODO: this should really use the existing code from QHttpNetworkReplyPrivate::parseHeader |
| 994 | auto = m_headers.end(); |
| 995 | while (pSocket->canReadLine()) { |
| 996 | QString = readLine(pSocket); |
| 997 | |
| 998 | if (headerLine.isEmpty()) { |
| 999 | // end of headers |
| 1000 | m_handshakeState = ParsingHeaderState; |
| 1001 | break; |
| 1002 | } else if (headerLine.startsWith(c: QLatin1Char(' ')) || headerLine.startsWith(c: QLatin1Char('\t'))) { |
| 1003 | // continuation line -- add this to the last header field |
| 1004 | if (Q_UNLIKELY(lastHeader == m_headers.end())) { |
| 1005 | errorDescription = QWebSocket::tr(s: "Malformed header in response: %1." ).arg(a: headerLine); |
| 1006 | break; |
| 1007 | } |
| 1008 | lastHeader.value().append(c: QLatin1Char(' ')); |
| 1009 | lastHeader.value().append(s: headerLine.trimmed()); |
| 1010 | } else { |
| 1011 | int colonPos = headerLine.indexOf(c: QLatin1Char(':')); |
| 1012 | if (Q_UNLIKELY(colonPos <= 0)) { |
| 1013 | errorDescription = QWebSocket::tr(s: "Malformed header in response: %1." ).arg(a: headerLine); |
| 1014 | break; |
| 1015 | } |
| 1016 | lastHeader = m_headers.insert(akey: headerLine.left(n: colonPos).trimmed().toLower(), |
| 1017 | avalue: headerLine.mid(position: colonPos + 1).trimmed()); |
| 1018 | } |
| 1019 | } |
| 1020 | |
| 1021 | if (m_handshakeState != ParsingHeaderState) { |
| 1022 | if (pSocket->state() != QAbstractSocket::ConnectedState) { |
| 1023 | errorDescription = QWebSocket::tr(s: "QWebSocketPrivate::processHandshake: Connection closed while reading header." ); |
| 1024 | break; |
| 1025 | } |
| 1026 | return; |
| 1027 | } |
| 1028 | Q_FALLTHROUGH(); |
| 1029 | } |
| 1030 | case ParsingHeaderState: { |
| 1031 | const QString acceptKey = m_headers.value(QStringLiteral("sec-websocket-accept" ), adefaultValue: QString()); |
| 1032 | const QString upgrade = m_headers.value(QStringLiteral("upgrade" ), adefaultValue: QString()); |
| 1033 | const QString connection = m_headers.value(QStringLiteral("connection" ), adefaultValue: QString()); |
| 1034 | // unused for the moment |
| 1035 | // const QString extensions = m_headers.value(QStringLiteral("sec-websocket-extensions"), |
| 1036 | // QString()); |
| 1037 | // const QString protocol = m_headers.value(QStringLiteral("sec-websocket-protocol"), |
| 1038 | // QString()); |
| 1039 | const QString version = m_headers.value(QStringLiteral("sec-websocket-version" ), adefaultValue: QString()); |
| 1040 | |
| 1041 | bool ok = false; |
| 1042 | if (Q_LIKELY(m_httpStatusCode == 101)) { |
| 1043 | //HTTP/x.y 101 Switching Protocols |
| 1044 | //TODO: do not check the httpStatusText right now |
| 1045 | ok = !(acceptKey.isEmpty() || |
| 1046 | (m_httpMajorVersion < 1 || m_httpMinorVersion < 1) || |
| 1047 | (upgrade.toLower() != QStringLiteral("websocket" )) || |
| 1048 | (connection.toLower() != QStringLiteral("upgrade" ))); |
| 1049 | if (ok) { |
| 1050 | const QString accept = calculateAcceptKey(key: m_key); |
| 1051 | ok = (accept == acceptKey); |
| 1052 | if (!ok) |
| 1053 | errorDescription = |
| 1054 | QWebSocket::tr(s: "Accept-Key received from server %1 does not match the client key %2." ) |
| 1055 | .arg(a1: acceptKey, a2: accept); |
| 1056 | } else { |
| 1057 | errorDescription = |
| 1058 | QWebSocket::tr(s: "QWebSocketPrivate::processHandshake: Invalid statusline in response: %1." ) |
| 1059 | .arg(a: QString::fromLatin1(str: m_statusLine)); |
| 1060 | } |
| 1061 | } else if (m_httpStatusCode == 400) { |
| 1062 | //HTTP/1.1 400 Bad Request |
| 1063 | if (!version.isEmpty()) { |
| 1064 | const QStringList versions = version.split(QStringLiteral(", " ), behavior: Qt::SkipEmptyParts); |
| 1065 | if (!versions.contains(str: QString::number(QWebSocketProtocol::currentVersion()))) { |
| 1066 | //if needed to switch protocol version, then we are finished here |
| 1067 | //because we cannot handle other protocols than the RFC one (v13) |
| 1068 | errorDescription = |
| 1069 | QWebSocket::tr(s: "Handshake: Server requests a version that we don't support: %1." ) |
| 1070 | .arg(a: versions.join(QStringLiteral(", " ))); |
| 1071 | } else { |
| 1072 | //we tried v13, but something different went wrong |
| 1073 | errorDescription = |
| 1074 | QWebSocket::tr(s: "QWebSocketPrivate::processHandshake: Unknown error condition encountered. Aborting connection." ); |
| 1075 | } |
| 1076 | } else { |
| 1077 | errorDescription = |
| 1078 | QWebSocket::tr(s: "QWebSocketPrivate::processHandshake: Unknown error condition encountered. Aborting connection." ); |
| 1079 | } |
| 1080 | } else { |
| 1081 | errorDescription = |
| 1082 | QWebSocket::tr(s: "QWebSocketPrivate::processHandshake: Unhandled http status code: %1 (%2)." ) |
| 1083 | .arg(a: m_httpStatusCode).arg(a: m_httpStatusMessage); |
| 1084 | } |
| 1085 | if (ok) |
| 1086 | m_handshakeState = AllDoneState; |
| 1087 | break; |
| 1088 | } |
| 1089 | case AllDoneState: |
| 1090 | Q_UNREACHABLE(); |
| 1091 | break; |
| 1092 | } |
| 1093 | |
| 1094 | if (m_handshakeState == AllDoneState) { |
| 1095 | // handshake succeeded |
| 1096 | setSocketState(QAbstractSocket::ConnectedState); |
| 1097 | Q_EMIT q->connected(); |
| 1098 | } else { |
| 1099 | // handshake failed |
| 1100 | m_handshakeState = AllDoneState; |
| 1101 | setErrorString(errorDescription); |
| 1102 | Q_EMIT q->error(error: QAbstractSocket::ConnectionRefusedError); |
| 1103 | } |
| 1104 | } |
| 1105 | |
| 1106 | /*! |
| 1107 | \internal |
| 1108 | */ |
| 1109 | void QWebSocketPrivate::processStateChanged(QAbstractSocket::SocketState socketState) |
| 1110 | { |
| 1111 | Q_ASSERT(m_pSocket); |
| 1112 | Q_Q(QWebSocket); |
| 1113 | QAbstractSocket::SocketState webSocketState = this->state(); |
| 1114 | |
| 1115 | switch (socketState) { |
| 1116 | case QAbstractSocket::ConnectedState: |
| 1117 | #ifndef QT_NO_SSL |
| 1118 | if (QSslSocket *sslSock = qobject_cast<QSslSocket *>(object: m_pSocket)) |
| 1119 | m_configuration.m_sslConfiguration = sslSock->sslConfiguration(); |
| 1120 | #endif |
| 1121 | if (webSocketState == QAbstractSocket::ConnectingState) { |
| 1122 | m_key = generateKey(); |
| 1123 | |
| 1124 | QList<QPair<QString, QString> > ; |
| 1125 | const auto = m_request.rawHeaderList(); |
| 1126 | for (const QByteArray &key : headerList) |
| 1127 | headers << qMakePair(x: QString::fromLatin1(str: key), |
| 1128 | y: QString::fromLatin1(str: m_request.rawHeader(headerName: key))); |
| 1129 | |
| 1130 | const auto format = QUrl::RemoveScheme | QUrl::RemoveUserInfo |
| 1131 | | QUrl::RemovePath | QUrl::RemoveQuery |
| 1132 | | QUrl::RemoveFragment; |
| 1133 | const QString host = m_request.url().toString(options: format).mid(position: 2); |
| 1134 | const QString handshake = createHandShakeRequest(resourceName: m_resourceName, |
| 1135 | host, |
| 1136 | origin: origin(), |
| 1137 | extensions: QString(), |
| 1138 | protocols: QString(), |
| 1139 | key: m_key, |
| 1140 | headers); |
| 1141 | if (handshake.isEmpty()) { |
| 1142 | m_pSocket->abort(); |
| 1143 | Q_EMIT q->error(error: QAbstractSocket::ConnectionRefusedError); |
| 1144 | return; |
| 1145 | } |
| 1146 | m_pSocket->write(data: handshake.toLatin1()); |
| 1147 | } |
| 1148 | break; |
| 1149 | |
| 1150 | case QAbstractSocket::ClosingState: |
| 1151 | if (webSocketState == QAbstractSocket::ConnectedState) |
| 1152 | setSocketState(QAbstractSocket::ClosingState); |
| 1153 | break; |
| 1154 | |
| 1155 | case QAbstractSocket::UnconnectedState: |
| 1156 | if (webSocketState != QAbstractSocket::UnconnectedState) { |
| 1157 | setSocketState(QAbstractSocket::UnconnectedState); |
| 1158 | Q_EMIT q->disconnected(); |
| 1159 | } |
| 1160 | break; |
| 1161 | |
| 1162 | case QAbstractSocket::HostLookupState: |
| 1163 | case QAbstractSocket::ConnectingState: |
| 1164 | case QAbstractSocket::BoundState: |
| 1165 | case QAbstractSocket::ListeningState: |
| 1166 | //do nothing |
| 1167 | //to make C++ compiler happy; |
| 1168 | break; |
| 1169 | } |
| 1170 | } |
| 1171 | |
| 1172 | void QWebSocketPrivate::socketDestroyed(QObject *socket) |
| 1173 | { |
| 1174 | Q_ASSERT(m_pSocket); |
| 1175 | if (m_pSocket == socket) |
| 1176 | m_pSocket = nullptr; |
| 1177 | } |
| 1178 | |
| 1179 | /*! |
| 1180 | \internal |
| 1181 | */ |
| 1182 | void QWebSocketPrivate::processData() |
| 1183 | { |
| 1184 | if (!m_pSocket) // disconnected with data still in-bound |
| 1185 | return; |
| 1186 | while (m_pSocket->bytesAvailable()) { |
| 1187 | if (state() == QAbstractSocket::ConnectingState) { |
| 1188 | if (!m_pSocket->canReadLine()) |
| 1189 | return; |
| 1190 | processHandshake(pSocket: m_pSocket); |
| 1191 | } else if (!m_dataProcessor->process(pIoDevice: m_pSocket)) { |
| 1192 | return; |
| 1193 | } |
| 1194 | } |
| 1195 | } |
| 1196 | |
| 1197 | /*! |
| 1198 | \internal |
| 1199 | */ |
| 1200 | void QWebSocketPrivate::processPing(const QByteArray &data) |
| 1201 | { |
| 1202 | Q_ASSERT(m_pSocket); |
| 1203 | quint32 maskingKey = 0; |
| 1204 | if (m_mustMask) |
| 1205 | maskingKey = generateMaskingKey(); |
| 1206 | m_pSocket->write(data: getFrameHeader(opCode: QWebSocketProtocol::OpCodePong, |
| 1207 | payloadLength: unsigned(data.size()), |
| 1208 | maskingKey, |
| 1209 | lastFrame: true)); |
| 1210 | if (data.size() > 0) { |
| 1211 | QByteArray maskedData = data; |
| 1212 | if (m_mustMask) |
| 1213 | QWebSocketProtocol::mask(payload: &maskedData, maskingKey); |
| 1214 | m_pSocket->write(data: maskedData); |
| 1215 | } |
| 1216 | } |
| 1217 | |
| 1218 | /*! |
| 1219 | \internal |
| 1220 | */ |
| 1221 | void QWebSocketPrivate::processPong(const QByteArray &data) |
| 1222 | { |
| 1223 | Q_Q(QWebSocket); |
| 1224 | Q_EMIT q->pong(elapsedTime: static_cast<quint64>(m_pingTimer.elapsed()), payload: data); |
| 1225 | } |
| 1226 | |
| 1227 | /*! |
| 1228 | \internal |
| 1229 | */ |
| 1230 | void QWebSocketPrivate::processClose(QWebSocketProtocol::CloseCode closeCode, QString closeReason) |
| 1231 | { |
| 1232 | m_isClosingHandshakeReceived = true; |
| 1233 | close(closeCode, reason: closeReason); |
| 1234 | } |
| 1235 | |
| 1236 | /*! |
| 1237 | \internal |
| 1238 | */ |
| 1239 | QString QWebSocketPrivate::createHandShakeRequest(QString resourceName, |
| 1240 | QString host, |
| 1241 | QString origin, |
| 1242 | QString extensions, |
| 1243 | QString protocols, |
| 1244 | QByteArray key, |
| 1245 | const QList<QPair<QString, QString> > &) |
| 1246 | { |
| 1247 | QStringList handshakeRequest; |
| 1248 | if (resourceName.contains(QStringLiteral("\r\n" ))) { |
| 1249 | setErrorString(QWebSocket::tr(s: "The resource name contains newlines. " \ |
| 1250 | "Possible attack detected." )); |
| 1251 | return QString(); |
| 1252 | } |
| 1253 | if (host.contains(QStringLiteral("\r\n" ))) { |
| 1254 | setErrorString(QWebSocket::tr(s: "The hostname contains newlines. " \ |
| 1255 | "Possible attack detected." )); |
| 1256 | return QString(); |
| 1257 | } |
| 1258 | if (origin.contains(QStringLiteral("\r\n" ))) { |
| 1259 | setErrorString(QWebSocket::tr(s: "The origin contains newlines. " \ |
| 1260 | "Possible attack detected." )); |
| 1261 | return QString(); |
| 1262 | } |
| 1263 | if (extensions.contains(QStringLiteral("\r\n" ))) { |
| 1264 | setErrorString(QWebSocket::tr(s: "The extensions attribute contains newlines. " \ |
| 1265 | "Possible attack detected." )); |
| 1266 | return QString(); |
| 1267 | } |
| 1268 | if (protocols.contains(QStringLiteral("\r\n" ))) { |
| 1269 | setErrorString(QWebSocket::tr(s: "The protocols attribute contains newlines. " \ |
| 1270 | "Possible attack detected." )); |
| 1271 | return QString(); |
| 1272 | } |
| 1273 | |
| 1274 | handshakeRequest << QStringLiteral("GET " ) % resourceName % QStringLiteral(" HTTP/1.1" ) << |
| 1275 | QStringLiteral("Host: " ) % host << |
| 1276 | QStringLiteral("Upgrade: websocket" ) << |
| 1277 | QStringLiteral("Connection: Upgrade" ) << |
| 1278 | QStringLiteral("Sec-WebSocket-Key: " ) % QString::fromLatin1(str: key); |
| 1279 | if (!origin.isEmpty()) |
| 1280 | handshakeRequest << QStringLiteral("Origin: " ) % origin; |
| 1281 | handshakeRequest << QStringLiteral("Sec-WebSocket-Version: " ) |
| 1282 | % QString::number(QWebSocketProtocol::currentVersion()); |
| 1283 | if (extensions.length() > 0) |
| 1284 | handshakeRequest << QStringLiteral("Sec-WebSocket-Extensions: " ) % extensions; |
| 1285 | if (protocols.length() > 0) |
| 1286 | handshakeRequest << QStringLiteral("Sec-WebSocket-Protocol: " ) % protocols; |
| 1287 | |
| 1288 | for (const auto & : headers) |
| 1289 | handshakeRequest << header.first % QStringLiteral(": " ) % header.second; |
| 1290 | |
| 1291 | handshakeRequest << QStringLiteral("\r\n" ); |
| 1292 | |
| 1293 | return handshakeRequest.join(QStringLiteral("\r\n" )); |
| 1294 | } |
| 1295 | |
| 1296 | /*! |
| 1297 | \internal |
| 1298 | */ |
| 1299 | QAbstractSocket::SocketState QWebSocketPrivate::state() const |
| 1300 | { |
| 1301 | return m_socketState; |
| 1302 | } |
| 1303 | |
| 1304 | /*! |
| 1305 | \internal |
| 1306 | */ |
| 1307 | void QWebSocketPrivate::setSocketState(QAbstractSocket::SocketState state) |
| 1308 | { |
| 1309 | Q_Q(QWebSocket); |
| 1310 | if (m_socketState != state) { |
| 1311 | m_socketState = state; |
| 1312 | Q_EMIT q->stateChanged(state: m_socketState); |
| 1313 | } |
| 1314 | } |
| 1315 | |
| 1316 | /*! |
| 1317 | \internal |
| 1318 | */ |
| 1319 | void QWebSocketPrivate::setMaxAllowedIncomingFrameSize(quint64 maxAllowedIncomingFrameSize) |
| 1320 | { |
| 1321 | m_dataProcessor->setMaxAllowedFrameSize(maxAllowedIncomingFrameSize); |
| 1322 | } |
| 1323 | |
| 1324 | /*! |
| 1325 | \internal |
| 1326 | */ |
| 1327 | quint64 QWebSocketPrivate::maxAllowedIncomingFrameSize() const |
| 1328 | { |
| 1329 | return m_dataProcessor->maxAllowedFrameSize(); |
| 1330 | } |
| 1331 | |
| 1332 | /*! |
| 1333 | \internal |
| 1334 | */ |
| 1335 | void QWebSocketPrivate::setMaxAllowedIncomingMessageSize(quint64 maxAllowedIncomingMessageSize) |
| 1336 | { |
| 1337 | m_dataProcessor->setMaxAllowedMessageSize(maxAllowedIncomingMessageSize); |
| 1338 | } |
| 1339 | |
| 1340 | /*! |
| 1341 | \internal |
| 1342 | */ |
| 1343 | quint64 QWebSocketPrivate::maxAllowedIncomingMessageSize() const |
| 1344 | { |
| 1345 | return m_dataProcessor->maxAllowedMessageSize(); |
| 1346 | } |
| 1347 | |
| 1348 | /*! |
| 1349 | \internal |
| 1350 | */ |
| 1351 | quint64 QWebSocketPrivate::maxIncomingMessageSize() |
| 1352 | { |
| 1353 | return QWebSocketDataProcessor::maxMessageSize(); |
| 1354 | } |
| 1355 | |
| 1356 | /*! |
| 1357 | \internal |
| 1358 | */ |
| 1359 | quint64 QWebSocketPrivate::maxIncomingFrameSize() |
| 1360 | { |
| 1361 | return QWebSocketDataProcessor::maxFrameSize(); |
| 1362 | } |
| 1363 | |
| 1364 | /*! |
| 1365 | \internal |
| 1366 | */ |
| 1367 | void QWebSocketPrivate::setOutgoingFrameSize(quint64 outgoingFrameSize) |
| 1368 | { |
| 1369 | if (outgoingFrameSize <= maxOutgoingFrameSize()) |
| 1370 | m_outgoingFrameSize = outgoingFrameSize; |
| 1371 | } |
| 1372 | |
| 1373 | /*! |
| 1374 | \internal |
| 1375 | */ |
| 1376 | quint64 QWebSocketPrivate::outgoingFrameSize() const |
| 1377 | { |
| 1378 | return m_outgoingFrameSize; |
| 1379 | } |
| 1380 | |
| 1381 | /*! |
| 1382 | \internal |
| 1383 | */ |
| 1384 | quint64 QWebSocketPrivate::maxOutgoingFrameSize() |
| 1385 | { |
| 1386 | return MAX_OUTGOING_FRAME_SIZE_IN_BYTES; |
| 1387 | } |
| 1388 | |
| 1389 | |
| 1390 | /*! |
| 1391 | \internal |
| 1392 | */ |
| 1393 | void QWebSocketPrivate::setErrorString(const QString &errorString) |
| 1394 | { |
| 1395 | if (m_errorString != errorString) |
| 1396 | m_errorString = errorString; |
| 1397 | } |
| 1398 | |
| 1399 | /*! |
| 1400 | \internal |
| 1401 | */ |
| 1402 | QHostAddress QWebSocketPrivate::localAddress() const |
| 1403 | { |
| 1404 | QHostAddress address; |
| 1405 | if (Q_LIKELY(m_pSocket)) |
| 1406 | address = m_pSocket->localAddress(); |
| 1407 | return address; |
| 1408 | } |
| 1409 | |
| 1410 | /*! |
| 1411 | \internal |
| 1412 | */ |
| 1413 | quint16 QWebSocketPrivate::localPort() const |
| 1414 | { |
| 1415 | quint16 port = 0; |
| 1416 | if (Q_LIKELY(m_pSocket)) |
| 1417 | port = m_pSocket->localPort(); |
| 1418 | return port; |
| 1419 | } |
| 1420 | |
| 1421 | /*! |
| 1422 | \internal |
| 1423 | */ |
| 1424 | QAbstractSocket::PauseModes QWebSocketPrivate::pauseMode() const |
| 1425 | { |
| 1426 | return m_pauseMode; |
| 1427 | } |
| 1428 | |
| 1429 | /*! |
| 1430 | \internal |
| 1431 | */ |
| 1432 | QHostAddress QWebSocketPrivate::peerAddress() const |
| 1433 | { |
| 1434 | QHostAddress address; |
| 1435 | if (Q_LIKELY(m_pSocket)) |
| 1436 | address = m_pSocket->peerAddress(); |
| 1437 | return address; |
| 1438 | } |
| 1439 | |
| 1440 | /*! |
| 1441 | \internal |
| 1442 | */ |
| 1443 | QString QWebSocketPrivate::peerName() const |
| 1444 | { |
| 1445 | QString name; |
| 1446 | if (Q_LIKELY(m_pSocket)) |
| 1447 | name = m_pSocket->peerName(); |
| 1448 | return name; |
| 1449 | } |
| 1450 | |
| 1451 | /*! |
| 1452 | \internal |
| 1453 | */ |
| 1454 | quint16 QWebSocketPrivate::peerPort() const |
| 1455 | { |
| 1456 | quint16 port = 0; |
| 1457 | if (Q_LIKELY(m_pSocket)) |
| 1458 | port = m_pSocket->peerPort(); |
| 1459 | return port; |
| 1460 | } |
| 1461 | |
| 1462 | #ifndef QT_NO_NETWORKPROXY |
| 1463 | /*! |
| 1464 | \internal |
| 1465 | */ |
| 1466 | QNetworkProxy QWebSocketPrivate::proxy() const |
| 1467 | { |
| 1468 | return m_configuration.m_proxy; |
| 1469 | } |
| 1470 | |
| 1471 | /*! |
| 1472 | \internal |
| 1473 | */ |
| 1474 | void QWebSocketPrivate::setProxy(const QNetworkProxy &networkProxy) |
| 1475 | { |
| 1476 | if (m_configuration.m_proxy != networkProxy) |
| 1477 | m_configuration.m_proxy = networkProxy; |
| 1478 | } |
| 1479 | #endif //QT_NO_NETWORKPROXY |
| 1480 | |
| 1481 | /*! |
| 1482 | \internal |
| 1483 | */ |
| 1484 | void QWebSocketPrivate::setMaskGenerator(const QMaskGenerator *maskGenerator) |
| 1485 | { |
| 1486 | if (!maskGenerator) |
| 1487 | m_pMaskGenerator = &m_defaultMaskGenerator; |
| 1488 | else if (maskGenerator != m_pMaskGenerator) |
| 1489 | m_pMaskGenerator = const_cast<QMaskGenerator *>(maskGenerator); |
| 1490 | } |
| 1491 | |
| 1492 | /*! |
| 1493 | \internal |
| 1494 | */ |
| 1495 | const QMaskGenerator *QWebSocketPrivate::maskGenerator() const |
| 1496 | { |
| 1497 | Q_ASSERT(m_pMaskGenerator); |
| 1498 | return m_pMaskGenerator; |
| 1499 | } |
| 1500 | |
| 1501 | /*! |
| 1502 | \internal |
| 1503 | */ |
| 1504 | qint64 QWebSocketPrivate::readBufferSize() const |
| 1505 | { |
| 1506 | return m_readBufferSize; |
| 1507 | } |
| 1508 | |
| 1509 | /*! |
| 1510 | \internal |
| 1511 | */ |
| 1512 | void QWebSocketPrivate::resume() |
| 1513 | { |
| 1514 | if (Q_LIKELY(m_pSocket)) |
| 1515 | m_pSocket->resume(); |
| 1516 | } |
| 1517 | |
| 1518 | /*! |
| 1519 | \internal |
| 1520 | */ |
| 1521 | void QWebSocketPrivate::setPauseMode(QAbstractSocket::PauseModes pauseMode) |
| 1522 | { |
| 1523 | m_pauseMode = pauseMode; |
| 1524 | if (Q_LIKELY(m_pSocket)) |
| 1525 | m_pSocket->setPauseMode(m_pauseMode); |
| 1526 | } |
| 1527 | |
| 1528 | /*! |
| 1529 | \internal |
| 1530 | */ |
| 1531 | void QWebSocketPrivate::setReadBufferSize(qint64 size) |
| 1532 | { |
| 1533 | m_readBufferSize = size; |
| 1534 | if (Q_LIKELY(m_pSocket)) |
| 1535 | m_pSocket->setReadBufferSize(m_readBufferSize); |
| 1536 | } |
| 1537 | |
| 1538 | #ifndef Q_OS_WASM |
| 1539 | /*! |
| 1540 | \internal |
| 1541 | */ |
| 1542 | bool QWebSocketPrivate::isValid() const |
| 1543 | { |
| 1544 | return (m_pSocket && m_pSocket->isValid() && |
| 1545 | (m_socketState == QAbstractSocket::ConnectedState)); |
| 1546 | } |
| 1547 | #endif |
| 1548 | |
| 1549 | QT_END_NAMESPACE |
| 1550 | |