Revision control

Copy as Markdown

// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:network-protocol
#include "qwebsockethandshakerequest_p.h"
#include "qwebsocketprotocol.h"
#include "qwebsocketprotocol_p.h"
#include <QtCore/QString>
#include <QtCore/QMap>
#include <QtCore/QTextStream>
#include <QtCore/QUrl>
#include <QtCore/QList>
#include <QtCore/QStringList>
#include <functional> //for std::greater
QT_BEGIN_NAMESPACE
/*!
\brief Constructs a new QWebSocketHandshakeRequest given \a port and \a isSecure
\internal
*/
QWebSocketHandshakeRequest::QWebSocketHandshakeRequest(int port, bool isSecure) :
m_port(port),
m_isSecure(isSecure),
m_isValid(false),
m_parser(),
m_versions(),
m_key(),
m_origin(),
m_protocols(),
m_extensions(),
m_requestUrl()
{
}
/*!
\internal
*/
QWebSocketHandshakeRequest::~QWebSocketHandshakeRequest()
{
}
/*!
\brief Clears the request
\internal
*/
void QWebSocketHandshakeRequest::clear()
{
m_isValid = false;
m_parser.clear();
m_versions.clear();
m_key.clear();
m_origin.clear();
m_protocols.clear();
m_extensions.clear();
m_requestUrl.clear();
}
/*!
\internal
*/
int QWebSocketHandshakeRequest::port() const
{
return m_requestUrl.port(m_port);
}
/*!
\internal
*/
bool QWebSocketHandshakeRequest::isSecure() const
{
return m_isSecure;
}
/*!
\internal
*/
bool QWebSocketHandshakeRequest::isValid() const
{
return m_isValid;
}
/*!
\internal
*/
QHttpHeaders QWebSocketHandshakeRequest::headers() const
{
return m_parser.headers();
}
/*!
\internal
*/
bool QWebSocketHandshakeRequest::hasHeader(const QByteArray &name) const
{
return !m_parser.firstHeaderField(name).isEmpty();
}
/*!
\internal
*/
QList<QWebSocketProtocol::Version> QWebSocketHandshakeRequest::versions() const
{
return m_versions;
}
/*!
\internal
*/
QString QWebSocketHandshakeRequest::resourceName() const
{
return m_requestUrl.path();
}
/*!
\internal
*/
QString QWebSocketHandshakeRequest::key() const
{
return m_key;
}
/*!
\internal
*/
QString QWebSocketHandshakeRequest::host() const
{
return m_requestUrl.host();
}
/*!
\internal
*/
QString QWebSocketHandshakeRequest::origin() const
{
return m_origin;
}
/*!
\internal
*/
QList<QString> QWebSocketHandshakeRequest::protocols() const
{
return m_protocols;
}
/*!
\internal
*/
QList<QString> QWebSocketHandshakeRequest::extensions() const
{
return m_extensions;
}
/*!
\internal
*/
QUrl QWebSocketHandshakeRequest::requestUrl() const
{
return m_requestUrl;
}
/*!
Reads a line of text from the given QByteArrayView (terminated by CR/LF),
and removes the read bytes from the QByteArrayView.
If an empty line was detected, an empty string is returned.
When an error occurs, a null string is returned.
\internal
*/
static QByteArrayView readLine(QByteArrayView &header, int maxHeaderLineLength)
{
qsizetype length = qMin(maxHeaderLineLength, header.size());
QByteArrayView line = header.first(length);
qsizetype end = line.indexOf('\n');
if (end != -1) {
line = line.first(end);
if (line.endsWith('\r'))
line.chop(1);
header = header.sliced(end + 1);
return line;
}
return QByteArrayView();
}
/*!
\internal
*/
static void appendCommmaSeparatedLineToList(QStringList &list, QByteArrayView line)
{
for (auto &c : QLatin1String(line).tokenize(QLatin1String(","), Qt::SkipEmptyParts))
list << c.trimmed().toString();
}
/*!
\internal
*/
void QWebSocketHandshakeRequest::readHandshake(QByteArrayView header, int maxHeaderLineLength)
{
clear();
m_parser.setMaxHeaderFieldSize(maxHeaderLineLength);
QString requestLine = QString::fromLatin1(readLine(header, maxHeaderLineLength));
if (requestLine.isNull()) {
clear();
return;
}
const QStringList tokens = requestLine.split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (Q_UNLIKELY(tokens.size() < 3)) {
clear();
return;
}
const QString verb(tokens.at(0));
const QString resourceName(tokens.at(1));
const QString httpProtocol(tokens.at(2));
bool conversionOk = false;
const float httpVersion = QStringView(httpProtocol).mid(5).toFloat(&conversionOk);
if (Q_UNLIKELY(!conversionOk)) {
clear();
return;
}
bool parsed = m_parser.parseHeaders(header);
if (!parsed) {
clear();
return;
}
m_requestUrl = QUrl::fromEncoded(resourceName.toLatin1());
QString host = QString::fromLatin1(m_parser.firstHeaderField("host"));
if (m_requestUrl.isRelative()) {
// No. 4 item in "The requirements for this handshake"
m_requestUrl.setAuthority(host);
if (!m_requestUrl.userName().isNull()) { // If the username is null, the password must be too.
m_isValid = false;
clear();
return;
}
}
if (m_requestUrl.scheme().isEmpty()) {
const QString scheme = isSecure() ? QStringLiteral("wss") : QStringLiteral("ws");
m_requestUrl.setScheme(scheme);
}
const QList<QByteArray> versionLines = m_parser.headerFieldValues("sec-websocket-version");
for (auto v = versionLines.begin(); v != versionLines.end(); ++v) {
for (const auto &version :
QString::fromLatin1(*v).tokenize(QStringLiteral(","), Qt::SkipEmptyParts)) {
bool ok = false;
(void)version.toUInt(&ok);
if (!ok) {
clear();
return;
}
const QWebSocketProtocol::Version ver =
QWebSocketProtocol::versionFromString(version.trimmed());
m_versions << ver;
}
}
//sort in descending order
std::sort(m_versions.begin(), m_versions.end(), std::greater<QWebSocketProtocol::Version>());
m_key = QString::fromLatin1(m_parser.firstHeaderField("sec-websocket-key"));
const QByteArray upgrade = m_parser.firstHeaderField("upgrade");
QStringList connectionValues;
appendCommmaSeparatedLineToList(connectionValues, m_parser.firstHeaderField("connection"));
//optional headers
m_origin = QString::fromLatin1(m_parser.firstHeaderField("origin"));
const QList<QByteArray> protocolLines = m_parser.headerFieldValues("sec-websocket-protocol");
for (const QByteArray &pl : protocolLines)
appendCommmaSeparatedLineToList(m_protocols, pl);
const QList<QByteArray> extensionLines = m_parser.headerFieldValues("sec-websocket-extensions");
for (const QByteArray &el : extensionLines)
appendCommmaSeparatedLineToList(m_extensions, el);
//TODO: authentication field
m_isValid = !(m_requestUrl.host().isEmpty() || resourceName.isEmpty() || m_versions.isEmpty()
|| m_key.isEmpty() || verb != QStringLiteral("GET") || !conversionOk
|| httpVersion < 1.1f || upgrade.compare("websocket", Qt::CaseInsensitive) != 0
|| !connectionValues.contains(QStringLiteral("upgrade"), Qt::CaseInsensitive));
if (Q_UNLIKELY(!m_isValid))
clear();
}
QT_END_NAMESPACE