2 Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 #include "InspectorServerQt.h"
23 #include "InspectorClientQt.h"
24 #include "InspectorController.h"
28 #include "qwebpage_p.h"
30 #include <QHttpHeader>
31 #include <QHttpRequestHeader>
32 #include <QHttpResponseHeader>
34 #include <QStringList>
40 #include <wtf/text/CString.h>
45 Computes the WebSocket handshake response given the two challenge numbers and key3.
47 static void generateWebSocketChallengeResponse(uint32_t number1, uint32_t number2, const unsigned char key3[8], unsigned char response[16])
49 uint8_t challenge[16];
50 qToBigEndian<qint32>(number1, &challenge[0]);
51 qToBigEndian<qint32>(number2, &challenge[4]);
52 memcpy(&challenge[8], key3, 8);
54 md5.addBytes(challenge, sizeof(challenge));
55 Vector<uint8_t, 16> digest;
57 memcpy(response, digest.data(), 16);
61 Parses and returns a WebSocket challenge number according to the
62 method specified in the WebSocket protocol.
64 The field contains numeric digits interspersed with spaces and
65 non-numeric digits. The protocol ignores the characters that are
66 neither digits nor spaces. The digits are concatenated and
67 interpreted as a long int. The result is this number divided by
70 static quint32 parseWebSocketChallengeNumber(QString field)
74 for (int i = 0; i < field.size(); i++) {
76 if (c == (QChar)' ') {
78 } else if ((c >= (QChar)'0') && (c <= (QChar)'9')) {
79 nString.append((QChar)c);
82 quint32 num = nString.toLong();
83 quint32 result = (numSpaces ? (num / numSpaces) : num);
87 static InspectorServerQt* s_inspectorServer;
89 InspectorServerQt* InspectorServerQt::server()
91 // s_inspectorServer is deleted in unregisterClient() when the last client is unregistered.
92 if (!s_inspectorServer)
93 s_inspectorServer = new InspectorServerQt();
95 return s_inspectorServer;
98 InspectorServerQt::InspectorServerQt()
105 InspectorServerQt::~InspectorServerQt()
110 void InspectorServerQt::listen(quint16 port)
115 m_tcpServer = new QTcpServer();
116 m_tcpServer->listen(QHostAddress::Any, port);
117 connect(m_tcpServer, SIGNAL(newConnection()), SLOT(newConnection()));
120 void InspectorServerQt::close()
123 m_tcpServer->close();
129 InspectorClientQt* InspectorServerQt::inspectorClientForPage(int pageNum)
131 InspectorClientQt* client = m_inspectorClients.value(pageNum);
135 void InspectorServerQt::registerClient(InspectorClientQt* client)
137 if (!m_inspectorClients.key(client))
138 m_inspectorClients.insert(m_pageNumber++, client);
141 void InspectorServerQt::unregisterClient(InspectorClientQt* client)
143 int pageNum = m_inspectorClients.key(client, -1);
145 m_inspectorClients.remove(pageNum);
146 if (!m_inspectorClients.size()) {
147 // s_inspectorServer points to this.
148 s_inspectorServer = 0;
154 void InspectorServerQt::newConnection()
156 QTcpSocket* tcpConnection = m_tcpServer->nextPendingConnection();
157 InspectorServerRequestHandlerQt* handler = new InspectorServerRequestHandlerQt(tcpConnection, this);
158 handler->setParent(this);
161 InspectorServerRequestHandlerQt::InspectorServerRequestHandlerQt(QTcpSocket* tcpConnection, InspectorServerQt* server)
163 , m_tcpConnection(tcpConnection)
165 , m_inspectorClient(0)
167 m_endOfHeaders = false;
170 connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(tcpReadyRead()));
171 connect(m_tcpConnection, SIGNAL(disconnected()), SLOT(tcpConnectionDisconnected()));
174 InspectorServerRequestHandlerQt::~InspectorServerRequestHandlerQt()
178 void InspectorServerRequestHandlerQt::tcpReadyRead()
180 QHttpRequestHeader header;
181 bool isWebSocket = false;
182 if (!m_tcpConnection)
185 if (!m_endOfHeaders) {
186 while (m_tcpConnection->bytesAvailable() && !m_endOfHeaders) {
187 QByteArray line = m_tcpConnection->readLine();
190 m_endOfHeaders = true;
192 if (m_endOfHeaders) {
193 header = QHttpRequestHeader(QString::fromLatin1(m_data));
194 if (header.isValid()) {
195 m_path = header.path();
196 m_contentType = header.contentType().toLatin1();
197 m_contentLength = header.contentLength();
198 if (header.hasKey("Upgrade") && (header.value("Upgrade") == QLatin1String("WebSocket")))
206 if (m_endOfHeaders) {
207 QStringList pathAndQuery = m_path.split("?");
208 m_path = pathAndQuery[0];
209 QStringList words = m_path.split(QString::fromLatin1("/"));
212 // switch to websocket-style WebSocketService messaging
213 if (m_tcpConnection) {
214 m_tcpConnection->disconnect(SIGNAL(readyRead()));
215 connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(webSocketReadyRead()));
217 QByteArray key3 = m_tcpConnection->read(8);
219 quint32 number1 = parseWebSocketChallengeNumber(header.value("Sec-WebSocket-Key1"));
220 quint32 number2 = parseWebSocketChallengeNumber(header.value("Sec-WebSocket-Key2"));
222 char responseData[16];
223 generateWebSocketChallengeResponse(number1, number2, (unsigned char*)key3.data(), (unsigned char*)responseData);
224 QByteArray response(responseData, sizeof(responseData));
226 QHttpResponseHeader responseHeader(101, "WebSocket Protocol Handshake", 1, 1);
227 responseHeader.setValue("Upgrade", header.value("Upgrade"));
228 responseHeader.setValue("Connection", header.value("Connection"));
229 responseHeader.setValue("Sec-WebSocket-Origin", header.value("Origin"));
230 responseHeader.setValue("Sec-WebSocket-Location", ("ws://" + header.value("Host") + m_path));
231 responseHeader.setContentLength(response.size());
232 m_tcpConnection->write(responseHeader.toString().toLatin1());
233 m_tcpConnection->write(response);
234 m_tcpConnection->flush();
236 if ((words.size() == 4)
237 && (words[1] == QString::fromLatin1("devtools"))
238 && (words[2] == QString::fromLatin1("page"))) {
239 int pageNum = words[3].toInt();
241 m_inspectorClient = m_server->inspectorClientForPage(pageNum);
242 // Attach remoteFrontendChannel to inspector, also transferring ownership.
243 if (m_inspectorClient)
244 m_inspectorClient->attachAndReplaceRemoteFrontend(new RemoteFrontendChannel(this));
251 if (m_contentLength && (m_tcpConnection->bytesAvailable() < m_contentLength))
254 QByteArray content = m_tcpConnection->read(m_contentLength);
255 m_endOfHeaders = false;
259 QString text = QString::fromLatin1("OK");
261 // If no path is specified, generate an index page.
262 if ((m_path == "") || (m_path == "/")) {
263 QString indexHtml = "<html><head><title>Remote Web Inspector</title></head><body><ul>\n";
264 for (QMap<int, InspectorClientQt* >::const_iterator it = m_server->m_inspectorClients.begin();
265 it != m_server->m_inspectorClients.end();
267 indexHtml.append(QString("<li><a href=\"/webkit/inspector/inspector.html?page=%1\">%2</li>\n")
269 .arg(it.value()->m_inspectedWebPage->mainFrame()->url().toString()));
271 indexHtml.append("</ul></body></html>");
272 response = indexHtml.toLatin1();
274 QString path = QString(":%1").arg(m_path);
276 // It seems that there should be an enum or define for these status codes somewhere in Qt or WebKit,
277 // but grep fails to turn one up.
278 // QNetwork uses the numeric values directly.
280 file.open(QIODevice::ReadOnly);
281 response = file.readAll();
284 text = QString::fromLatin1("Not OK");
288 QHttpResponseHeader responseHeader(code, text, 1, 0);
289 responseHeader.setContentLength(response.size());
290 if (!m_contentType.isEmpty())
291 responseHeader.setContentType(QString::fromLatin1(m_contentType));
293 QByteArray asciiHeader = responseHeader.toString().toAscii();
294 m_tcpConnection->write(asciiHeader);
296 m_tcpConnection->write(response);
297 m_tcpConnection->flush();
298 m_tcpConnection->close();
304 void InspectorServerRequestHandlerQt::tcpConnectionDisconnected()
306 if (m_inspectorClient)
307 m_inspectorClient->detachRemoteFrontend();
308 m_tcpConnection->deleteLater();
312 int InspectorServerRequestHandlerQt::webSocketSend(QByteArray payload)
314 Q_ASSERT(m_tcpConnection);
315 m_tcpConnection->putChar(0x00);
316 int nBytes = m_tcpConnection->write(payload);
317 m_tcpConnection->putChar(0xFF);
318 m_tcpConnection->flush();
322 int InspectorServerRequestHandlerQt::webSocketSend(const char* data, size_t length)
324 Q_ASSERT(m_tcpConnection);
325 m_tcpConnection->putChar(0x00);
326 int nBytes = m_tcpConnection->write(data, length);
327 m_tcpConnection->putChar(0xFF);
328 m_tcpConnection->flush();
332 void InspectorServerRequestHandlerQt::webSocketReadyRead()
334 Q_ASSERT(m_tcpConnection);
335 if (!m_tcpConnection->bytesAvailable())
337 QByteArray content = m_tcpConnection->read(m_tcpConnection->bytesAvailable());
338 m_data.append(content);
339 while (m_data.size() > 0) {
340 // first byte in websocket frame should be 0
341 Q_ASSERT(!m_data[0]);
343 // Start of WebSocket frame is indicated by 0
345 qCritical() << "webSocketReadyRead: unknown frame type" << m_data[0];
347 m_tcpConnection->close();
351 // End of WebSocket frame indicated by 0xff.
352 int pos = m_data.indexOf(0xff, 1);
356 // After above checks, length will be >= 0.
357 size_t length = pos - 1;
361 QByteArray payload = m_data.mid(1, length);
363 #if ENABLE(INSPECTOR)
364 if (m_inspectorClient) {
365 InspectorController* inspectorController = m_inspectorClient->m_inspectedWebPage->d->page->inspectorController();
366 inspectorController->dispatchMessageFromFrontend(QString::fromUtf8(payload));
370 // Remove this WebSocket message from m_data (payload, start-of-frame byte, end-of-frame byte).
371 m_data = m_data.mid(length + 2);
375 RemoteFrontendChannel::RemoteFrontendChannel(InspectorServerRequestHandlerQt* requestHandler)
376 : QObject(requestHandler)
377 , m_requestHandler(requestHandler)
381 bool RemoteFrontendChannel::sendMessageToFrontend(const String& message)
383 if (!m_requestHandler)
385 CString cstr = message.utf8();
386 return m_requestHandler->webSocketSend(cstr.data(), cstr.length());