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 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | /*! |
55 | \brief Constructs a new QWebSocketHandshakeRequest given \a port and \a isSecure |
56 | \internal |
57 | */ |
58 | QWebSocketHandshakeRequest::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 | */ |
75 | QWebSocketHandshakeRequest::~QWebSocketHandshakeRequest() |
76 | { |
77 | } |
78 | |
79 | /*! |
80 | \brief Clears the request |
81 | \internal |
82 | */ |
83 | void 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 | */ |
98 | int QWebSocketHandshakeRequest::port() const |
99 | { |
100 | return m_requestUrl.port(defaultPort: m_port); |
101 | } |
102 | |
103 | /*! |
104 | \internal |
105 | */ |
106 | bool QWebSocketHandshakeRequest::isSecure() const |
107 | { |
108 | return m_isSecure; |
109 | } |
110 | |
111 | /*! |
112 | \internal |
113 | */ |
114 | bool QWebSocketHandshakeRequest::isValid() const |
115 | { |
116 | return m_isValid; |
117 | } |
118 | |
119 | /*! |
120 | \internal |
121 | */ |
122 | QMap<QString, QString> QWebSocketHandshakeRequest::headers() const |
123 | { |
124 | return m_headers; |
125 | } |
126 | |
127 | /*! |
128 | \internal |
129 | */ |
130 | QList<QWebSocketProtocol::Version> QWebSocketHandshakeRequest::versions() const |
131 | { |
132 | return m_versions; |
133 | } |
134 | |
135 | /*! |
136 | \internal |
137 | */ |
138 | QString QWebSocketHandshakeRequest::resourceName() const |
139 | { |
140 | return m_requestUrl.path(); |
141 | } |
142 | |
143 | /*! |
144 | \internal |
145 | */ |
146 | QString QWebSocketHandshakeRequest::key() const |
147 | { |
148 | return m_key; |
149 | } |
150 | |
151 | /*! |
152 | \internal |
153 | */ |
154 | QString QWebSocketHandshakeRequest::host() const |
155 | { |
156 | return m_requestUrl.host(); |
157 | } |
158 | |
159 | /*! |
160 | \internal |
161 | */ |
162 | QString QWebSocketHandshakeRequest::origin() const |
163 | { |
164 | return m_origin; |
165 | } |
166 | |
167 | /*! |
168 | \internal |
169 | */ |
170 | QList<QString> QWebSocketHandshakeRequest::protocols() const |
171 | { |
172 | return m_protocols; |
173 | } |
174 | |
175 | /*! |
176 | \internal |
177 | */ |
178 | QList<QString> QWebSocketHandshakeRequest::extensions() const |
179 | { |
180 | return m_extensions; |
181 | } |
182 | |
183 | /*! |
184 | \internal |
185 | */ |
186 | QUrl 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 | */ |
197 | static QString readLine(QTextStream &stream, int ) |
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 | */ |
222 | void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream, int , |
223 | int ) |
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 = 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 = 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 | |
359 | QT_END_NAMESPACE |
360 | |