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 "qwebsockethandshakerequest_p.h"
41#include "qwebsocketprotocol.h"
42#include "qwebsocketprotocol_p.h"
43
44#include <QtCore/QString>
45#include <QtCore/QMap>
46#include <QtCore/QTextStream>
47#include <QtCore/QUrl>
48#include <QtCore/QList>
49#include <QtCore/QStringList>
50#include <functional> //for std::greater
51
52QT_BEGIN_NAMESPACE
53
54/*!
55 \brief Constructs a new QWebSocketHandshakeRequest given \a port and \a isSecure
56 \internal
57 */
58QWebSocketHandshakeRequest::QWebSocketHandshakeRequest(int port, bool isSecure) :
59 m_port(port),
60 m_isSecure(isSecure),
61 m_isValid(false),
62 m_headers(),
63 m_versions(),
64 m_key(),
65 m_origin(),
66 m_protocols(),
67 m_extensions(),
68 m_requestUrl()
69{
70}
71
72/*!
73 \internal
74 */
75QWebSocketHandshakeRequest::~QWebSocketHandshakeRequest()
76{
77}
78
79/*!
80 \brief Clears the request
81 \internal
82 */
83void QWebSocketHandshakeRequest::clear()
84{
85 m_isValid = false;
86 m_headers.clear();
87 m_versions.clear();
88 m_key.clear();
89 m_origin.clear();
90 m_protocols.clear();
91 m_extensions.clear();
92 m_requestUrl.clear();
93}
94
95/*!
96 \internal
97 */
98int QWebSocketHandshakeRequest::port() const
99{
100 return m_requestUrl.port(defaultPort: m_port);
101}
102
103/*!
104 \internal
105 */
106bool QWebSocketHandshakeRequest::isSecure() const
107{
108 return m_isSecure;
109}
110
111/*!
112 \internal
113 */
114bool QWebSocketHandshakeRequest::isValid() const
115{
116 return m_isValid;
117}
118
119/*!
120 \internal
121 */
122QMap<QString, QString> QWebSocketHandshakeRequest::headers() const
123{
124 return m_headers;
125}
126
127/*!
128 \internal
129 */
130QList<QWebSocketProtocol::Version> QWebSocketHandshakeRequest::versions() const
131{
132 return m_versions;
133}
134
135/*!
136 \internal
137 */
138QString QWebSocketHandshakeRequest::resourceName() const
139{
140 return m_requestUrl.path();
141}
142
143/*!
144 \internal
145 */
146QString QWebSocketHandshakeRequest::key() const
147{
148 return m_key;
149}
150
151/*!
152 \internal
153 */
154QString QWebSocketHandshakeRequest::host() const
155{
156 return m_requestUrl.host();
157}
158
159/*!
160 \internal
161 */
162QString QWebSocketHandshakeRequest::origin() const
163{
164 return m_origin;
165}
166
167/*!
168 \internal
169 */
170QList<QString> QWebSocketHandshakeRequest::protocols() const
171{
172 return m_protocols;
173}
174
175/*!
176 \internal
177 */
178QList<QString> QWebSocketHandshakeRequest::extensions() const
179{
180 return m_extensions;
181}
182
183/*!
184 \internal
185 */
186QUrl QWebSocketHandshakeRequest::requestUrl() const
187{
188 return m_requestUrl;
189}
190
191/*!
192 Reads a line of text from the given textstream (terminated by CR/LF).
193 If an empty line was detected, an empty string is returned.
194 When an error occurs, a null string is returned.
195 \internal
196 */
197static QString readLine(QTextStream &stream, int maxHeaderLineLength)
198{
199 QString line;
200 char c;
201 while (!stream.atEnd()) {
202 stream >> c;
203 if (stream.status() != QTextStream::Ok)
204 return QString();
205 if (c == char('\r')) {
206 //eat the \n character
207 stream >> c;
208 line.append(QStringLiteral(""));
209 break;
210 } else {
211 line.append(c: QChar::fromLatin1(c));
212 if (line.length() > maxHeaderLineLength)
213 return QString();
214 }
215 }
216 return line;
217}
218
219/*!
220 \internal
221 */
222void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int maxHeaderLineLength,
223 int maxHeaders)
224{
225 clear();
226 if (Q_UNLIKELY(textStream.status() != QTextStream::Ok))
227 return;
228 const QString requestLine = readLine(stream&: textStream, maxHeaderLineLength);
229 if (requestLine.isNull()) {
230 clear();
231 return;
232 }
233 const QStringList tokens = requestLine.split(sep: ' ', behavior: Qt::SkipEmptyParts);
234 if (Q_UNLIKELY(tokens.length() < 3)) {
235 clear();
236 return;
237 }
238 const QString verb(tokens.at(i: 0));
239 const QString resourceName(tokens.at(i: 1));
240 const QString httpProtocol(tokens.at(i: 2));
241 bool conversionOk = false;
242 const float httpVersion = httpProtocol.midRef(position: 5).toFloat(ok: &conversionOk);
243
244 if (Q_UNLIKELY(!conversionOk)) {
245 clear();
246 return;
247 }
248 QString headerLine = readLine(stream&: textStream, maxHeaderLineLength);
249 if (headerLine.isNull()) {
250 clear();
251 return;
252 }
253 m_headers.clear();
254 // TODO: this should really use the existing code from QHttpNetworkReplyPrivate::parseHeader
255 auto lastHeader = m_headers.end();
256 while (!headerLine.isEmpty()) {
257 if (headerLine.startsWith(c: QLatin1Char(' ')) || headerLine.startsWith(c: QLatin1Char('\t'))) {
258 // continuation line -- add this to the last header field
259 if (Q_UNLIKELY(lastHeader == m_headers.end())) {
260 clear();
261 return;
262 }
263 lastHeader.value().append(c: QLatin1Char(' '));
264 lastHeader.value().append(s: headerLine.trimmed());
265 } else {
266 int colonPos = headerLine.indexOf(c: QLatin1Char(':'));
267 if (Q_UNLIKELY(colonPos <= 0)) {
268 clear();
269 return;
270 }
271 lastHeader = m_headers.insert(akey: headerLine.left(n: colonPos).trimmed().toLower(),
272 avalue: headerLine.mid(position: colonPos + 1).trimmed());
273 }
274 if (m_headers.size() > maxHeaders) {
275 clear();
276 return;
277 }
278 headerLine = readLine(stream&: textStream, maxHeaderLineLength);
279 if (headerLine.isNull()) {
280 clear();
281 return;
282 }
283 }
284
285 m_requestUrl = QUrl::fromEncoded(url: resourceName.toLatin1());
286 QString host = m_headers.value(QStringLiteral("host"), adefaultValue: QString());
287 if (m_requestUrl.isRelative()) {
288 // see http://tools.ietf.org/html/rfc6455#page-17
289 // No. 4 item in "The requirements for this handshake"
290 m_requestUrl.setAuthority(authority: host);
291 if (!m_requestUrl.userName().isNull()) { // If the username is null, the password must be too.
292 m_isValid = false;
293 clear();
294 return;
295 }
296 }
297 if (m_requestUrl.scheme().isEmpty()) {
298 const QString scheme = isSecure() ? QStringLiteral("wss") : QStringLiteral("ws");
299 m_requestUrl.setScheme(scheme);
300 }
301
302 const QStringList versionLines = m_headers.values(QStringLiteral("sec-websocket-version"));
303 for (QStringList::const_iterator v = versionLines.begin(); v != versionLines.end(); ++v) {
304 const QStringList versions = (*v).split(QStringLiteral(","), behavior: Qt::SkipEmptyParts);
305 for (QStringList::const_iterator i = versions.begin(); i != versions.end(); ++i) {
306 bool ok = false;
307 (void)(*i).toUInt(ok: &ok);
308 if (!ok) {
309 clear();
310 return;
311 }
312 const QWebSocketProtocol::Version ver =
313 QWebSocketProtocol::versionFromString(versionString: (*i).trimmed());
314 m_versions << ver;
315 }
316 }
317 //sort in descending order
318 std::sort(first: m_versions.begin(), last: m_versions.end(), comp: std::greater<QWebSocketProtocol::Version>());
319 m_key = m_headers.value(QStringLiteral("sec-websocket-key"), adefaultValue: QString());
320 //must contain "Upgrade", case-insensitive
321 const QString upgrade = m_headers.value(QStringLiteral("upgrade"), adefaultValue: QString());
322 //must be equal to "websocket", case-insensitive
323 const QString connection = m_headers.value(QStringLiteral("connection"), adefaultValue: QString());
324 const QStringList connectionLine = connection.split(QStringLiteral(","), behavior: Qt::SkipEmptyParts);
325 QStringList connectionValues;
326 for (QStringList::const_iterator c = connectionLine.begin(); c != connectionLine.end(); ++c)
327 connectionValues << (*c).trimmed();
328
329 //optional headers
330 m_origin = m_headers.value(QStringLiteral("origin"), adefaultValue: QString());
331 const QStringList protocolLines = m_headers.values(QStringLiteral("sec-websocket-protocol"));
332 for (const QString& pl : protocolLines) {
333 const QStringList protocols = pl.split(QStringLiteral(","), behavior: Qt::SkipEmptyParts);
334 for (const QString& p : protocols)
335 m_protocols << p.trimmed();
336 }
337
338 const QStringList extensionLines = m_headers.values(QStringLiteral("sec-websocket-extensions"));
339 for (const QString& el : extensionLines) {
340 const QStringList extensions = el.split(QStringLiteral(","), behavior: Qt::SkipEmptyParts);
341 for (const QString& e : extensions)
342 m_extensions << e.trimmed();
343 }
344
345 //TODO: authentication field
346
347 m_isValid = !(m_requestUrl.host().isEmpty() ||
348 resourceName.isEmpty() ||
349 m_versions.isEmpty() ||
350 m_key.isEmpty() ||
351 (verb != QStringLiteral("GET")) ||
352 (!conversionOk || (httpVersion < 1.1f)) ||
353 (upgrade.toLower() != QStringLiteral("websocket")) ||
354 (!connectionValues.contains(QStringLiteral("upgrade"), cs: Qt::CaseInsensitive)));
355 if (Q_UNLIKELY(!m_isValid))
356 clear();
357}
358
359QT_END_NAMESPACE
360

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