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 "qwebsockethandshakeresponse_p.h"
41#include "qwebsockethandshakerequest_p.h"
42#include "qwebsocketprotocol.h"
43#include "qwebsocketprotocol_p.h"
44
45#include <QtCore/QString>
46#include <QtCore/QTextStream>
47#include <QtCore/QByteArray>
48#include <QtCore/QStringList>
49#include <QtCore/QDateTime>
50#include <QtCore/QLocale>
51#include <QtCore/QCryptographicHash>
52#include <QtCore/QSet>
53#include <QtCore/QList>
54#include <QtCore/QStringBuilder> //for more efficient string concatenation
55
56#include <algorithm>
57#include <functional> //for std::greater
58#include <iterator>
59
60QT_BEGIN_NAMESPACE
61
62/*!
63 \internal
64 */
65QWebSocketHandshakeResponse::QWebSocketHandshakeResponse(
66 const QWebSocketHandshakeRequest &request,
67 const QString &serverName,
68 bool isOriginAllowed,
69 const QList<QWebSocketProtocol::Version> &supportedVersions,
70 const QList<QString> &supportedProtocols,
71 const QList<QString> &supportedExtensions) :
72 m_isValid(false),
73 m_canUpgrade(false),
74 m_response(),
75 m_acceptedProtocol(),
76 m_acceptedExtension(),
77 m_acceptedVersion(QWebSocketProtocol::VersionUnknown),
78 m_error(QWebSocketProtocol::CloseCodeNormal),
79 m_errorString()
80{
81 m_response = getHandshakeResponse(request, serverName,
82 isOriginAllowed, supportedVersions,
83 supportedProtocols, supportedExtensions);
84 m_isValid = true;
85}
86
87/*!
88 \internal
89 */
90QWebSocketHandshakeResponse::~QWebSocketHandshakeResponse()
91{
92}
93
94/*!
95 \internal
96 */
97bool QWebSocketHandshakeResponse::isValid() const
98{
99 return m_isValid;
100}
101
102/*!
103 \internal
104 */
105bool QWebSocketHandshakeResponse::canUpgrade() const
106{
107 return m_isValid && m_canUpgrade;
108}
109
110/*!
111 \internal
112 */
113QString QWebSocketHandshakeResponse::acceptedProtocol() const
114{
115 return m_acceptedProtocol;
116}
117
118/*!
119 \internal
120 */
121QString QWebSocketHandshakeResponse::calculateAcceptKey(const QString &key) const
122{
123 //the UID comes from RFC6455
124 const QString tmpKey = key % QStringLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
125 const QByteArray hash = QCryptographicHash::hash(data: tmpKey.toLatin1(), method: QCryptographicHash::Sha1);
126 return QString::fromLatin1(str: hash.toBase64());
127}
128
129template <class T, class Compare>
130static QList<T> listIntersection(QList<T> list1, QList<T> list2, Compare comp)
131{
132 QList<T> result;
133 std::sort(list1.begin(), list1.end(), comp);
134 std::sort(list2.begin(), list2.end(), comp);
135 std::set_intersection(list1.cbegin(), list1.cend(),
136 list2.cbegin(), list2.cend(),
137 std::back_inserter(result), comp);
138 return result;
139}
140
141/*!
142 \internal
143 */
144QString QWebSocketHandshakeResponse::getHandshakeResponse(
145 const QWebSocketHandshakeRequest &request,
146 const QString &serverName,
147 bool isOriginAllowed,
148 const QList<QWebSocketProtocol::Version> &supportedVersions,
149 const QList<QString> &supportedProtocols,
150 const QList<QString> &supportedExtensions)
151{
152 QStringList response;
153 m_canUpgrade = false;
154
155 if (!isOriginAllowed) {
156 if (!m_canUpgrade) {
157 m_error = QWebSocketProtocol::CloseCodePolicyViolated;
158 m_errorString = tr(s: "Access forbidden.");
159 response << QStringLiteral("HTTP/1.1 403 Access Forbidden");
160 }
161 } else {
162 if (request.isValid()) {
163 const QString acceptKey = calculateAcceptKey(key: request.key());
164 const QList<QString> matchingProtocols =
165 listIntersection(list1: supportedProtocols, list2: request.protocols(),
166 comp: std::less<QString>());
167 //TODO: extensions must be kept in the order in which they arrive
168 //cannot use set.intersect() to get the supported extensions
169 const QList<QString> matchingExtensions =
170 listIntersection(list1: supportedExtensions, list2: request.extensions(),
171 comp: std::less<QString>());
172 const QList<QWebSocketProtocol::Version> matchingVersions =
173 listIntersection(list1: supportedVersions, list2: request.versions(),
174 comp: std::greater<QWebSocketProtocol::Version>()); //sort in descending order
175
176 if (Q_UNLIKELY(matchingVersions.isEmpty())) {
177 m_error = QWebSocketProtocol::CloseCodeProtocolError;
178 m_errorString = tr(s: "Unsupported version requested.");
179 m_canUpgrade = false;
180 } else {
181 response << QStringLiteral("HTTP/1.1 101 Switching Protocols") <<
182 QStringLiteral("Upgrade: websocket") <<
183 QStringLiteral("Connection: Upgrade") <<
184 QStringLiteral("Sec-WebSocket-Accept: ") % acceptKey;
185 if (!matchingProtocols.isEmpty()) {
186 m_acceptedProtocol = matchingProtocols.first();
187 response << QStringLiteral("Sec-WebSocket-Protocol: ") % m_acceptedProtocol;
188 }
189 if (!matchingExtensions.isEmpty()) {
190 m_acceptedExtension = matchingExtensions.first();
191 response << QStringLiteral("Sec-WebSocket-Extensions: ") % m_acceptedExtension;
192 }
193 QString origin = request.origin().trimmed();
194 if (origin.contains(QStringLiteral("\r\n")) ||
195 serverName.contains(QStringLiteral("\r\n"))) {
196 m_error = QWebSocketProtocol::CloseCodeAbnormalDisconnection;
197 m_errorString = tr(s: "One of the headers contains a newline. " \
198 "Possible attack detected.");
199 m_canUpgrade = false;
200 } else {
201 if (origin.isEmpty())
202 origin = QStringLiteral("*");
203 QDateTime datetime = QDateTime::currentDateTimeUtc();
204 if (!serverName.isEmpty())
205 response << QStringLiteral("Server: ") % serverName;
206 response << QStringLiteral("Access-Control-Allow-Credentials: false") <<
207 QStringLiteral("Access-Control-Allow-Methods: GET") <<
208 QStringLiteral("Access-Control-Allow-Headers: content-type") <<
209 QStringLiteral("Access-Control-Allow-Origin: ") % origin <<
210 QStringLiteral("Date: ") % QLocale::c()
211 .toString(dateTime: datetime, QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT'"));
212
213
214 m_acceptedVersion = QWebSocketProtocol::currentVersion();
215 m_canUpgrade = true;
216 }
217 }
218 } else {
219 m_error = QWebSocketProtocol::CloseCodeProtocolError;
220 m_errorString = tr(s: "Bad handshake request received.");
221 m_canUpgrade = false;
222 }
223 if (Q_UNLIKELY(!m_canUpgrade)) {
224 response << QStringLiteral("HTTP/1.1 400 Bad Request");
225 QStringList versions;
226 for (QWebSocketProtocol::Version version : supportedVersions)
227 versions << QString::number(static_cast<int>(version));
228 response << QStringLiteral("Sec-WebSocket-Version: ")
229 % versions.join(QStringLiteral(", "));
230 }
231 }
232 response << QStringLiteral("\r\n"); //append empty line at end of header
233 return response.join(QStringLiteral("\r\n"));
234}
235
236/*!
237 \internal
238 */
239QTextStream &QWebSocketHandshakeResponse::writeToStream(QTextStream &textStream) const
240{
241 if (Q_LIKELY(!m_response.isEmpty()))
242 textStream << m_response.toLatin1().constData();
243 else
244 textStream.setStatus(QTextStream::WriteFailed);
245 return textStream;
246}
247
248/*!
249 \internal
250 */
251QTextStream &operator <<(QTextStream &stream, const QWebSocketHandshakeResponse &response)
252{
253 return response.writeToStream(textStream&: stream);
254}
255
256/*!
257 \internal
258 */
259QWebSocketProtocol::Version QWebSocketHandshakeResponse::acceptedVersion() const
260{
261 return m_acceptedVersion;
262}
263
264/*!
265 \internal
266 */
267QWebSocketProtocol::CloseCode QWebSocketHandshakeResponse::error() const
268{
269 return m_error;
270}
271
272/*!
273 \internal
274 */
275QString QWebSocketHandshakeResponse::errorString() const
276{
277 return m_errorString;
278}
279
280/*!
281 \internal
282 */
283QString QWebSocketHandshakeResponse::acceptedExtension() const
284{
285 return m_acceptedExtension;
286}
287
288QT_END_NAMESPACE
289

source code of qtwebsockets/src/websockets/qwebsockethandshakeresponse.cpp