Made accessor methods const; made serverName parameter const in QWebSocket
[contrib/qtwebsockets.git] / src / handshakeresponse_p.cpp
1 /*
2 QWebSockets implements the WebSocket protocol as defined in RFC 6455.
3 Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com)
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20 #include "handshakeresponse_p.h"
21 #include "handshakerequest_p.h"
22 #include <QString>
23 #include <QTextStream>
24 #include <QByteArray>
25 #include <QStringList>
26 #include <QDateTime>
27 #include <QCryptographicHash>
28 #include <QSet>
29 #include <QList>
30 #include <QStringBuilder>   //for more efficient string concatenation
31
32 QT_BEGIN_NAMESPACE
33
34 /*!
35     \internal
36  */
37 HandshakeResponse::HandshakeResponse(const HandshakeRequest &request,
38                                      const QString &serverName,
39                                      bool isOriginAllowed,
40                                      const QList<QWebSocketProtocol::Version> &supportedVersions,
41                                      const QList<QString> &supportedProtocols,
42                                      const QList<QString> &supportedExtensions) :
43     m_isValid(false),
44     m_canUpgrade(false),
45     m_response(),
46     m_acceptedProtocol(),
47     m_acceptedExtension(),
48     m_acceptedVersion(QWebSocketProtocol::V_Unknow)
49 {
50     m_response = getHandshakeResponse(request, serverName, isOriginAllowed, supportedVersions, supportedProtocols, supportedExtensions);
51     m_isValid = true;
52 }
53
54 /*!
55     \internal
56  */
57 HandshakeResponse::~HandshakeResponse()
58 {
59 }
60
61 /*!
62     \internal
63  */
64 bool HandshakeResponse::isValid() const
65 {
66     return m_isValid;
67 }
68
69 /*!
70     \internal
71  */
72 bool HandshakeResponse::canUpgrade() const
73 {
74     return m_isValid && m_canUpgrade;
75 }
76
77 /*!
78     \internal
79  */
80 QString HandshakeResponse::getAcceptedProtocol() const
81 {
82     return m_acceptedProtocol;
83 }
84
85 /*!
86     \internal
87  */
88 QString HandshakeResponse::calculateAcceptKey(const QString &key) const
89 {
90     QString tmpKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";      //the UID comes from RFC6455
91     QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1);
92     return QString(hash.toBase64());
93 }
94
95 /*!
96     \internal
97  */
98 QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request,
99                                                 const QString &serverName,
100                                                 bool isOriginAllowed,
101                                                 const QList<QWebSocketProtocol::Version> &supportedVersions,
102                                                 const QList<QString> &supportedProtocols,
103                                                 const QList<QString> &supportedExtensions)
104 {
105     QStringList response;
106     m_canUpgrade = false;
107
108     if (!isOriginAllowed)
109     {
110         if (!m_canUpgrade)
111         {
112             response << "HTTP/1.1 403 Access Forbidden";
113         }
114     }
115     else
116     {
117         if (request.isValid())
118         {
119             QString acceptKey = calculateAcceptKey(request.getKey());
120             QList<QString> matchingProtocols = supportedProtocols.toSet().intersect(request.getProtocols().toSet()).toList();
121             QList<QString> matchingExtensions = supportedExtensions.toSet().intersect(request.getExtensions().toSet()).toList();
122             QList<QWebSocketProtocol::Version> matchingVersions = request.getVersions().toSet().intersect(supportedVersions.toSet()).toList();
123             qStableSort(matchingVersions.begin(), matchingVersions.end(), qGreater<QWebSocketProtocol::Version>());     //sort in descending order
124
125             if (matchingVersions.isEmpty())
126             {
127                 m_canUpgrade = false;
128             }
129             else
130             {
131                 response << "HTTP/1.1 101 Switching Protocols" <<
132                             "Upgrade: websocket" <<
133                             "Connection: Upgrade" <<
134                             "Sec-WebSocket-Accept: " % acceptKey;
135                 if (!matchingProtocols.isEmpty())
136                 {
137                     m_acceptedProtocol = matchingProtocols.first();
138                     response << "Sec-WebSocket-Protocol: " % m_acceptedProtocol;
139                 }
140                 if (!matchingExtensions.isEmpty())
141                 {
142                     m_acceptedExtension = matchingExtensions.first();
143                     response << "Sec-WebSocket-Extensions: " % m_acceptedExtension;
144                 }
145                 QString origin = request.getOrigin().trimmed();
146                 if (origin.isEmpty())
147                 {
148                     origin = "*";
149                 }
150                 //TODO: header values should be configurable; i.e. Server, Allow-Credentials, Allow-Headers
151                 response << "Server: " + serverName <<
152                             "Access-Control-Allow-Credentials: false"           <<      //do not allow credentialed request (containing cookies)
153                             "Access-Control-Allow-Methods: GET"                         <<      //only GET is allowed during handshaking
154                             "Access-Control-Allow-Headers: content-type"        <<      //this is OK to be fixed; only the content-type header is allowed, no other headers are accepted
155                             "Access-Control-Allow-Origin: " % origin            <<
156                             "Date: " % QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'");
157
158                 m_acceptedVersion = QWebSocketProtocol::currentVersion();
159                 m_canUpgrade = true;
160             }
161         }
162         else
163         {
164             m_canUpgrade = false;
165         }
166         if (!m_canUpgrade)
167         {
168             response << "HTTP/1.1 400 Bad Request";
169             QStringList versions;
170             Q_FOREACH(QWebSocketProtocol::Version version, supportedVersions)
171             {
172                 versions << QString::number(static_cast<int>(version));
173             }
174             response << "Sec-WebSocket-Version: " % versions.join(", ");
175         }
176     }
177     response << "\r\n"; //append empty line at end of header
178     return response.join("\r\n");
179 }
180
181 /*!
182     \internal
183  */
184 QTextStream &HandshakeResponse::writeToStream(QTextStream &textStream) const
185 {
186     if (!m_response.isEmpty())
187     {
188         textStream << m_response.toLatin1().constData();
189     }
190     else
191     {
192         textStream.setStatus(QTextStream::WriteFailed);
193     }
194     return textStream;
195 }
196
197 /*!
198     \internal
199  */
200 QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response)
201 {
202     return response.writeToStream(stream);
203 }
204
205 /*!
206     \internal
207  */
208 QWebSocketProtocol::Version HandshakeResponse::getAcceptedVersion() const
209 {
210     return m_acceptedVersion;
211 }
212
213 /*!
214     \internal
215  */
216 QString HandshakeResponse::getAcceptedExtension() const
217 {
218     return m_acceptedExtension;
219 }
220
221 QT_END_NAMESPACE