Added QT_BEGIN_NAMESPACE and QT_END_NAMESPACE to files
[contrib/qtwebsockets.git] / src / handshakeresponse_p.cpp
1 #include "handshakeresponse_p.h"
2 #include "handshakerequest_p.h"
3 #include <QString>
4 #include <QTextStream>
5 #include <QByteArray>
6 #include <QStringList>
7 #include <QDateTime>
8 #include <QCryptographicHash>
9 #include <QSet>
10 #include <QList>
11
12 QT_BEGIN_NAMESPACE
13
14 /*!
15         \internal
16  */
17 HandshakeResponse::HandshakeResponse(const HandshakeRequest &request,
18                                                                          const QString &serverName,
19                                                                          bool isOriginAllowed,
20                                                                          const QList<QWebSocketProtocol::Version> &supportedVersions,
21                                                                          const QList<QString> &supportedProtocols,
22                                                                          const QList<QString> &supportedExtensions) :
23         m_isValid(false),
24         m_canUpgrade(false),
25         m_response(),
26         m_acceptedProtocol(),
27         m_acceptedExtension(),
28         m_acceptedVersion(QWebSocketProtocol::V_Unknow)
29 {
30         m_response = getHandshakeResponse(request, serverName, isOriginAllowed, supportedVersions, supportedProtocols, supportedExtensions);
31         m_isValid = true;
32 }
33
34 /*!
35         \internal
36  */
37 HandshakeResponse::~HandshakeResponse()
38 {
39 }
40
41 /*!
42         \internal
43  */
44 bool HandshakeResponse::isValid() const
45 {
46         return m_isValid;
47 }
48
49 /*!
50         \internal
51  */
52 bool HandshakeResponse::canUpgrade() const
53 {
54         return m_isValid && m_canUpgrade;
55 }
56
57 /*!
58         \internal
59  */
60 QString HandshakeResponse::getAcceptedProtocol() const
61 {
62         return m_acceptedProtocol;
63 }
64
65 /*!
66         \internal
67  */
68 QString HandshakeResponse::calculateAcceptKey(const QString &key) const
69 {
70         QString tmpKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";  //the UID comes from RFC6455
71         QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1);
72         return QString(hash.toBase64());
73 }
74
75 /*!
76         \internal
77  */
78 QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request,
79                                                                                                 const QString &serverName,
80                                                                                                 bool isOriginAllowed,
81                                                                                                 const QList<QWebSocketProtocol::Version> &supportedVersions,
82                                                                                                 const QList<QString> &supportedProtocols,
83                                                                                                 const QList<QString> &supportedExtensions)
84 {
85         QStringList response;
86         m_canUpgrade = false;
87
88         if (!isOriginAllowed)
89         {
90                 if (!m_canUpgrade)
91                 {
92                         response << "HTTP/1.1 403 Access Forbidden";
93                 }
94         }
95         else
96         {
97                 if (request.isValid())
98                 {
99                         QString acceptKey = calculateAcceptKey(request.getKey());
100                         QList<QString> matchingProtocols = supportedProtocols.toSet().intersect(request.getProtocols().toSet()).toList();
101                         QList<QString> matchingExtensions = supportedExtensions.toSet().intersect(request.getExtensions().toSet()).toList();
102                         QList<QWebSocketProtocol::Version> matchingVersions = request.getVersions().toSet().intersect(supportedVersions.toSet()).toList();
103                         qStableSort(matchingVersions.begin(), matchingVersions.end(), qGreater<QWebSocketProtocol::Version>()); //sort in descending order
104
105                         if (matchingVersions.isEmpty())
106                         {
107                                 m_canUpgrade = false;
108                         }
109                         else
110                         {
111                                 response << "HTTP/1.1 101 Switching Protocols" <<
112                                                         "Upgrade: websocket" <<
113                                                         "Connection: Upgrade" <<
114                                                         "Sec-WebSocket-Accept: " + acceptKey;
115                                 if (!matchingProtocols.isEmpty())
116                                 {
117                                         m_acceptedProtocol = matchingProtocols.first();
118                                         response << "Sec-WebSocket-Protocol: " + m_acceptedProtocol;
119                                 }
120                                 if (!matchingExtensions.isEmpty())
121                                 {
122                                         m_acceptedExtension = matchingExtensions.first();
123                                         response << "Sec-WebSocket-Extensions: " + m_acceptedExtension;
124                                 }
125                                 QString origin = request.getOrigin().trimmed();
126                                 if (origin.isEmpty())
127                                 {
128                                         origin = "*";
129                                 }
130                                 //TODO: header values should be configurable; i.e. Server, Allow-Credentials, Allow-Headers
131                                 response << "Server: " + serverName <<
132                                                         "Access-Control-Allow-Credentials: false"               <<      //do not allow credentialed request (containing cookies)
133                                                         "Access-Control-Allow-Methods: GET"                             <<      //only GET is allowed during handshaking
134                                                         "Access-Control-Allow-Headers: content-type"    <<      //this is OK to be fixed; only the content-type header is allowed, no other headers are accepted
135                                                         "Access-Control-Allow-Origin: " + origin                <<
136                                                         "Date: " + QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'");
137
138                                 m_acceptedVersion = QWebSocketProtocol::currentVersion();
139                                 m_canUpgrade = true;
140                         }
141                 }
142                 else
143                 {
144                         m_canUpgrade = false;
145                 }
146                 if (!m_canUpgrade)
147                 {
148                         response << "HTTP/1.1 400 Bad Request";
149                         QStringList versions;
150                         Q_FOREACH(QWebSocketProtocol::Version version, supportedVersions)
151                         {
152                                 versions << QString::number(static_cast<int>(version));
153                         }
154                         response << "Sec-WebSocket-Version: " + versions.join(", ");
155                 }
156         }
157         response << "\r\n";     //append empty line at end of header
158         return response.join("\r\n");
159 }
160
161 /*!
162         \internal
163  */
164 QTextStream &HandshakeResponse::writeToStream(QTextStream &textStream) const
165 {
166         if (!m_response.isEmpty())
167         {
168                 textStream << m_response.toLatin1().constData();
169         }
170         else
171         {
172                 textStream.setStatus(QTextStream::WriteFailed);
173         }
174         return textStream;
175 }
176
177 /*!
178         \internal
179  */
180 QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response)
181 {
182         return response.writeToStream(stream);
183 }
184
185 /*!
186         \internal
187  */
188 QWebSocketProtocol::Version HandshakeResponse::getAcceptedVersion() const
189 {
190         return m_acceptedVersion;
191 }
192
193 /*!
194         \internal
195  */
196 QString HandshakeResponse::getAcceptedExtension() const
197 {
198         return m_acceptedExtension;
199 }
200
201 QT_END_NAMESPACE