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 | |
60 | QT_BEGIN_NAMESPACE |
61 | |
62 | /*! |
63 | \internal |
64 | */ |
65 | QWebSocketHandshakeResponse::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 | */ |
90 | QWebSocketHandshakeResponse::~QWebSocketHandshakeResponse() |
91 | { |
92 | } |
93 | |
94 | /*! |
95 | \internal |
96 | */ |
97 | bool QWebSocketHandshakeResponse::isValid() const |
98 | { |
99 | return m_isValid; |
100 | } |
101 | |
102 | /*! |
103 | \internal |
104 | */ |
105 | bool QWebSocketHandshakeResponse::canUpgrade() const |
106 | { |
107 | return m_isValid && m_canUpgrade; |
108 | } |
109 | |
110 | /*! |
111 | \internal |
112 | */ |
113 | QString QWebSocketHandshakeResponse::acceptedProtocol() const |
114 | { |
115 | return m_acceptedProtocol; |
116 | } |
117 | |
118 | /*! |
119 | \internal |
120 | */ |
121 | QString 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 | |
129 | template <class T, class Compare> |
130 | static 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 | */ |
144 | QString 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 | */ |
239 | QTextStream &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 | */ |
251 | QTextStream &operator <<(QTextStream &stream, const QWebSocketHandshakeResponse &response) |
252 | { |
253 | return response.writeToStream(textStream&: stream); |
254 | } |
255 | |
256 | /*! |
257 | \internal |
258 | */ |
259 | QWebSocketProtocol::Version QWebSocketHandshakeResponse::acceptedVersion() const |
260 | { |
261 | return m_acceptedVersion; |
262 | } |
263 | |
264 | /*! |
265 | \internal |
266 | */ |
267 | QWebSocketProtocol::CloseCode QWebSocketHandshakeResponse::error() const |
268 | { |
269 | return m_error; |
270 | } |
271 | |
272 | /*! |
273 | \internal |
274 | */ |
275 | QString QWebSocketHandshakeResponse::errorString() const |
276 | { |
277 | return m_errorString; |
278 | } |
279 | |
280 | /*! |
281 | \internal |
282 | */ |
283 | QString QWebSocketHandshakeResponse::acceptedExtension() const |
284 | { |
285 | return m_acceptedExtension; |
286 | } |
287 | |
288 | QT_END_NAMESPACE |
289 | |