Fix QWebSocketServer for clients preferring lowercase http headers.
[contrib/qtwebsockets.git] / src / websockets / qwebsockethandshakerequest.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>.
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtWebSockets module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL21$
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 Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/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 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** In addition, as a special exception, Digia gives you certain additional
27 ** rights. These rights are described in the Digia Qt LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** $QT_END_LICENSE$
31 **
32 ****************************************************************************/
33
34 #include "qwebsockethandshakerequest_p.h"
35 #include "qwebsocketprotocol.h"
36 #include "qwebsocketprotocol_p.h"
37
38 #include <QtCore/QString>
39 #include <QtCore/QMap>
40 #include <QtCore/QTextStream>
41 #include <QtCore/QUrl>
42 #include <QtCore/QList>
43 #include <QtCore/QStringList>
44 #include <functional>   //for std::greater
45
46 QT_BEGIN_NAMESPACE
47
48 /*!
49     \brief Constructs a new QWebSocketHandshakeRequest given \a port and \a isSecure
50     \internal
51  */
52 QWebSocketHandshakeRequest::QWebSocketHandshakeRequest(int port, bool isSecure) :
53     m_port(port),
54     m_isSecure(isSecure),
55     m_isValid(false),
56     m_headers(),
57     m_versions(),
58     m_key(),
59     m_origin(),
60     m_protocols(),
61     m_extensions(),
62     m_requestUrl()
63 {
64 }
65
66 /*!
67     \internal
68  */
69 QWebSocketHandshakeRequest::~QWebSocketHandshakeRequest()
70 {
71 }
72
73 /*!
74     \brief Clears the request
75     \internal
76  */
77 void QWebSocketHandshakeRequest::clear()
78 {
79     m_isValid = false;
80     m_headers.clear();
81     m_versions.clear();
82     m_key.clear();
83     m_origin.clear();
84     m_protocols.clear();
85     m_extensions.clear();
86     m_requestUrl.clear();
87 }
88
89 /*!
90     \internal
91  */
92 int QWebSocketHandshakeRequest::port() const
93 {
94     return m_requestUrl.port(m_port);
95 }
96
97 /*!
98     \internal
99  */
100 bool QWebSocketHandshakeRequest::isSecure() const
101 {
102     return m_isSecure;
103 }
104
105 /*!
106     \internal
107  */
108 bool QWebSocketHandshakeRequest::isValid() const
109 {
110     return m_isValid;
111 }
112
113 /*!
114     \internal
115  */
116 QMap<QString, QString> QWebSocketHandshakeRequest::headers() const
117 {
118     return m_headers;
119 }
120
121 /*!
122     \internal
123  */
124 QList<QWebSocketProtocol::Version> QWebSocketHandshakeRequest::versions() const
125 {
126     return m_versions;
127 }
128
129 /*!
130     \internal
131  */
132 QString QWebSocketHandshakeRequest::resourceName() const
133 {
134     return m_requestUrl.path();
135 }
136
137 /*!
138     \internal
139  */
140 QString QWebSocketHandshakeRequest::key() const
141 {
142     return m_key;
143 }
144
145 /*!
146     \internal
147  */
148 QString QWebSocketHandshakeRequest::host() const
149 {
150     return m_requestUrl.host();
151 }
152
153 /*!
154     \internal
155  */
156 QString QWebSocketHandshakeRequest::origin() const
157 {
158     return m_origin;
159 }
160
161 /*!
162     \internal
163  */
164 QList<QString> QWebSocketHandshakeRequest::protocols() const
165 {
166     return m_protocols;
167 }
168
169 /*!
170     \internal
171  */
172 QList<QString> QWebSocketHandshakeRequest::extensions() const
173 {
174     return m_extensions;
175 }
176
177 /*!
178     \internal
179  */
180 QUrl QWebSocketHandshakeRequest::requestUrl() const
181 {
182     return m_requestUrl;
183 }
184
185 /*!
186     \internal
187  */
188 void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream)
189 {
190     m_isValid = false;
191     clear();
192     if (Q_UNLIKELY(textStream.status() != QTextStream::Ok))
193         return;
194     const QString requestLine = textStream.readLine();
195     const QStringList tokens = requestLine.split(' ', QString::SkipEmptyParts);
196     if (Q_UNLIKELY(tokens.length() < 3)) {
197         m_isValid = false;
198         clear();
199         return;
200     }
201     const QString verb(tokens.at(0));
202     const QString resourceName(tokens.at(1));
203     const QString httpProtocol(tokens.at(2));
204     bool conversionOk = false;
205     const float httpVersion = httpProtocol.midRef(5).toFloat(&conversionOk);
206
207     if (Q_UNLIKELY(!conversionOk)) {
208         clear();
209         m_isValid = false;
210         return;
211     }
212     QString headerLine = textStream.readLine();
213     m_headers.clear();
214     while (!headerLine.isEmpty()) {
215         const QStringList headerField = headerLine.split(QStringLiteral(": "),
216                                                          QString::SkipEmptyParts);
217         if (Q_UNLIKELY(headerField.length() < 2)) {
218             clear();
219             return;
220         }
221         m_headers.insertMulti(headerField.at(0).toLower(), headerField.at(1));
222         headerLine = textStream.readLine();
223     }
224
225     const QString host = m_headers.value(QStringLiteral("host"), QString());
226     m_requestUrl = QUrl::fromEncoded(resourceName.toLatin1());
227     if (m_requestUrl.isRelative())
228         m_requestUrl.setHost(host);
229     if (m_requestUrl.scheme().isEmpty()) {
230         const QString scheme =  isSecure() ? QStringLiteral("wss") : QStringLiteral("ws");
231         m_requestUrl.setScheme(scheme);
232     }
233
234     const QStringList versionLines = m_headers.values(QStringLiteral("sec-websocket-version"));
235     for (QStringList::const_iterator v = versionLines.begin(); v != versionLines.end(); ++v) {
236         const QStringList versions = (*v).split(QStringLiteral(","), QString::SkipEmptyParts);
237         for (QStringList::const_iterator i = versions.begin(); i != versions.end(); ++i) {
238             bool ok = false;
239             (void)(*i).toUInt(&ok);
240             if (!ok) {
241                 clear();
242                 return;
243             }
244             const QWebSocketProtocol::Version ver =
245                     QWebSocketProtocol::versionFromString((*i).trimmed());
246             m_versions << ver;
247         }
248     }
249     //sort in descending order
250     std::sort(m_versions.begin(), m_versions.end(), std::greater<QWebSocketProtocol::Version>());
251     m_key = m_headers.value(QStringLiteral("sec-websocket-key"), QString());
252     //must contain "Upgrade", case-insensitive
253     const QString upgrade = m_headers.value(QStringLiteral("upgrade"), QString());
254     //must be equal to "websocket", case-insensitive
255     const QString connection = m_headers.value(QStringLiteral("connection"), QString());
256     const QStringList connectionLine = connection.split(QStringLiteral(","),
257                                                         QString::SkipEmptyParts);
258     QStringList connectionValues;
259     for (QStringList::const_iterator c = connectionLine.begin(); c != connectionLine.end(); ++c)
260         connectionValues << (*c).trimmed();
261
262     //optional headers
263     m_origin = m_headers.value(QStringLiteral("sec-websocket-origin"), QString());
264     const QStringList protocolLines = m_headers.values(QStringLiteral("sec-websocket-protocol"));
265     for (QStringList::const_iterator pl = protocolLines.begin(); pl != protocolLines.end(); ++pl) {
266         QStringList protocols = (*pl).split(QStringLiteral(","), QString::SkipEmptyParts);
267         for (QStringList::const_iterator p = protocols.begin(); p != protocols.end(); ++p)
268             m_protocols << (*p).trimmed();
269     }
270     const QStringList extensionLines = m_headers.values(QStringLiteral("sec-websocket-extensions"));
271     for (QStringList::const_iterator el = extensionLines.begin();
272          el != extensionLines.end(); ++el) {
273         QStringList extensions = (*el).split(QStringLiteral(","), QString::SkipEmptyParts);
274         for (QStringList::const_iterator e = extensions.begin(); e != extensions.end(); ++e)
275             m_extensions << (*e).trimmed();
276     }
277
278     //TODO: authentication field
279
280     m_isValid = !(host.isEmpty() ||
281                   resourceName.isEmpty() ||
282                   m_versions.isEmpty() ||
283                   m_key.isEmpty() ||
284                   (verb != QStringLiteral("GET")) ||
285                   (!conversionOk || (httpVersion < 1.1f)) ||
286                   (upgrade.toLower() != QStringLiteral("websocket")) ||
287                   (!connectionValues.contains(QStringLiteral("upgrade"), Qt::CaseInsensitive)));
288     if (Q_UNLIKELY(!m_isValid))
289         clear();
290 }
291
292 QT_END_NAMESPACE