1 /*
2 Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
3
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.
8
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.
13
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.
18 */
19
20 #include "config.h"
21 #include "InspectorServerQt.h"
22
23 #include "InspectorClientQt.h"
24 #include "InspectorController.h"
25 #include "MD5.h"
26 #include "Page.h"
27 #include "qwebpage.h"
28 #include "qwebpage_p.h"
29 #include <QFile>
30 #include <QHttpHeader>
31 #include <QHttpRequestHeader>
32 #include <QHttpResponseHeader>
33 #include <QString>
34 #include <QStringList>
35 #include <QTcpServer>
36 #include <QTcpSocket>
37 #include <QUrl>
38 #include <QWidget>
39 #include <qendian.h>
40 #include <wtf/text/CString.h>
41
42 namespace WebCore {
43
44 /*!
45 Computes the WebSocket handshake response given the two challenge numbers and key3.
46 */
generateWebSocketChallengeResponse(uint32_t number1,uint32_t number2,const unsigned char key3[8],unsigned char response[16])47 static void generateWebSocketChallengeResponse(uint32_t number1, uint32_t number2, const unsigned char key3[8], unsigned char response[16])
48 {
49 uint8_t challenge[16];
50 qToBigEndian<qint32>(number1, &challenge[0]);
51 qToBigEndian<qint32>(number2, &challenge[4]);
52 memcpy(&challenge[8], key3, 8);
53 MD5 md5;
54 md5.addBytes(challenge, sizeof(challenge));
55 Vector<uint8_t, 16> digest;
56 md5.checksum(digest);
57 memcpy(response, digest.data(), 16);
58 }
59
60 /*!
61 Parses and returns a WebSocket challenge number according to the
62 method specified in the WebSocket protocol.
63
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
68 the number of spaces.
69 */
parseWebSocketChallengeNumber(QString field)70 static quint32 parseWebSocketChallengeNumber(QString field)
71 {
72 QString nString;
73 int numSpaces = 0;
74 for (int i = 0; i < field.size(); i++) {
75 QChar c = field[i];
76 if (c == QLatin1Char(' '))
77 numSpaces++;
78 else if ((c >= QLatin1Char('0')) && (c <= QLatin1Char('9')))
79 nString.append(c);
80 }
81 quint32 num = nString.toLong();
82 quint32 result = (numSpaces ? (num / numSpaces) : num);
83 return result;
84 }
85
86 static InspectorServerQt* s_inspectorServer;
87
server()88 InspectorServerQt* InspectorServerQt::server()
89 {
90 // s_inspectorServer is deleted in unregisterClient() when the last client is unregistered.
91 if (!s_inspectorServer)
92 s_inspectorServer = new InspectorServerQt();
93
94 return s_inspectorServer;
95 }
96
InspectorServerQt()97 InspectorServerQt::InspectorServerQt()
98 : QObject()
99 , m_tcpServer(0)
100 , m_pageNumber(1)
101 {
102 }
103
~InspectorServerQt()104 InspectorServerQt::~InspectorServerQt()
105 {
106 close();
107 }
108
listen(quint16 port)109 void InspectorServerQt::listen(quint16 port)
110 {
111 if (m_tcpServer)
112 return;
113
114 m_tcpServer = new QTcpServer();
115 m_tcpServer->listen(QHostAddress::Any, port);
116 connect(m_tcpServer, SIGNAL(newConnection()), SLOT(newConnection()));
117 }
118
close()119 void InspectorServerQt::close()
120 {
121 if (m_tcpServer) {
122 m_tcpServer->close();
123 delete m_tcpServer;
124 }
125 m_tcpServer = 0;
126 }
127
inspectorClientForPage(int pageNum)128 InspectorClientQt* InspectorServerQt::inspectorClientForPage(int pageNum)
129 {
130 InspectorClientQt* client = m_inspectorClients.value(pageNum);
131 return client;
132 }
133
registerClient(InspectorClientQt * client)134 void InspectorServerQt::registerClient(InspectorClientQt* client)
135 {
136 if (!m_inspectorClients.key(client))
137 m_inspectorClients.insert(m_pageNumber++, client);
138 }
139
unregisterClient(InspectorClientQt * client)140 void InspectorServerQt::unregisterClient(InspectorClientQt* client)
141 {
142 int pageNum = m_inspectorClients.key(client, -1);
143 if (pageNum >= 0)
144 m_inspectorClients.remove(pageNum);
145 if (!m_inspectorClients.size()) {
146 // s_inspectorServer points to this.
147 s_inspectorServer = 0;
148 close();
149 deleteLater();
150 }
151 }
152
newConnection()153 void InspectorServerQt::newConnection()
154 {
155 QTcpSocket* tcpConnection = m_tcpServer->nextPendingConnection();
156 InspectorServerRequestHandlerQt* handler = new InspectorServerRequestHandlerQt(tcpConnection, this);
157 handler->setParent(this);
158 }
159
InspectorServerRequestHandlerQt(QTcpSocket * tcpConnection,InspectorServerQt * server)160 InspectorServerRequestHandlerQt::InspectorServerRequestHandlerQt(QTcpSocket* tcpConnection, InspectorServerQt* server)
161 : QObject(server)
162 , m_tcpConnection(tcpConnection)
163 , m_server(server)
164 , m_inspectorClient(0)
165 {
166 m_endOfHeaders = false;
167 m_contentLength = 0;
168
169 connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(tcpReadyRead()));
170 connect(m_tcpConnection, SIGNAL(disconnected()), SLOT(tcpConnectionDisconnected()));
171 }
172
~InspectorServerRequestHandlerQt()173 InspectorServerRequestHandlerQt::~InspectorServerRequestHandlerQt()
174 {
175 }
176
tcpReadyRead()177 void InspectorServerRequestHandlerQt::tcpReadyRead()
178 {
179 QHttpRequestHeader header;
180 bool isWebSocket = false;
181 if (!m_tcpConnection)
182 return;
183
184 if (!m_endOfHeaders) {
185 while (m_tcpConnection->bytesAvailable() && !m_endOfHeaders) {
186 QByteArray line = m_tcpConnection->readLine();
187 m_data.append(line);
188 if (line == "\r\n")
189 m_endOfHeaders = true;
190 }
191 if (m_endOfHeaders) {
192 header = QHttpRequestHeader(QString::fromLatin1(m_data));
193 if (header.isValid()) {
194 m_path = header.path();
195 m_contentType = header.contentType().toLatin1();
196 m_contentLength = header.contentLength();
197 if (header.hasKey(QLatin1String("Upgrade")) && (header.value(QLatin1String("Upgrade")) == QLatin1String("WebSocket")))
198 isWebSocket = true;
199
200 m_data.clear();
201 }
202 }
203 }
204
205 if (m_endOfHeaders) {
206 QStringList pathAndQuery = m_path.split(QLatin1Char('?'));
207 m_path = pathAndQuery[0];
208 QStringList words = m_path.split(QLatin1Char('/'));
209
210 if (isWebSocket) {
211 // switch to websocket-style WebSocketService messaging
212 if (m_tcpConnection) {
213 m_tcpConnection->disconnect(SIGNAL(readyRead()));
214 connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(webSocketReadyRead()));
215
216 QByteArray key3 = m_tcpConnection->read(8);
217
218 quint32 number1 = parseWebSocketChallengeNumber(header.value(QLatin1String("Sec-WebSocket-Key1")));
219 quint32 number2 = parseWebSocketChallengeNumber(header.value(QLatin1String("Sec-WebSocket-Key2")));
220
221 char responseData[16];
222 generateWebSocketChallengeResponse(number1, number2, (unsigned char*)key3.data(), (unsigned char*)responseData);
223 QByteArray response(responseData, sizeof(responseData));
224
225 QHttpResponseHeader responseHeader(101, QLatin1String("WebSocket Protocol Handshake"), 1, 1);
226 responseHeader.setValue(QLatin1String("Upgrade"), header.value(QLatin1String("Upgrade")));
227 responseHeader.setValue(QLatin1String("Connection"), header.value(QLatin1String("Connection")));
228 responseHeader.setValue(QLatin1String("Sec-WebSocket-Origin"), header.value(QLatin1String("Origin")));
229 responseHeader.setValue(QLatin1String("Sec-WebSocket-Location"), (QLatin1String("ws://") + header.value(QLatin1String("Host")) + m_path));
230 responseHeader.setContentLength(response.size());
231 m_tcpConnection->write(responseHeader.toString().toLatin1());
232 m_tcpConnection->write(response);
233 m_tcpConnection->flush();
234
235 if ((words.size() == 4)
236 && (words[1] == QString::fromLatin1("devtools"))
237 && (words[2] == QString::fromLatin1("page"))) {
238 int pageNum = words[3].toInt();
239
240 m_inspectorClient = m_server->inspectorClientForPage(pageNum);
241 // Attach remoteFrontendChannel to inspector, also transferring ownership.
242 if (m_inspectorClient)
243 m_inspectorClient->attachAndReplaceRemoteFrontend(new RemoteFrontendChannel(this));
244 }
245
246 }
247
248 return;
249 }
250 if (m_contentLength && (m_tcpConnection->bytesAvailable() < m_contentLength))
251 return;
252
253 QByteArray content = m_tcpConnection->read(m_contentLength);
254 m_endOfHeaders = false;
255
256 QByteArray response;
257 int code = 200;
258 QString text = QString::fromLatin1("OK");
259
260 // If no path is specified, generate an index page.
261 if (m_path.isEmpty() || (m_path == QString(QLatin1Char('/')))) {
262 QString indexHtml = QLatin1String("<html><head><title>Remote Web Inspector</title></head><body><ul>\n");
263 for (QMap<int, InspectorClientQt* >::const_iterator it = m_server->m_inspectorClients.begin();
264 it != m_server->m_inspectorClients.end();
265 ++it) {
266 indexHtml.append(QString::fromLatin1("<li><a href=\"/webkit/inspector/inspector.html?page=%1\">%2</li>\n")
267 .arg(it.key())
268 .arg(it.value()->m_inspectedWebPage->mainFrame()->url().toString()));
269 }
270 indexHtml.append(QLatin1String("</ul></body></html>"));
271 response = indexHtml.toLatin1();
272 } else {
273 QString path = QString::fromLatin1(":%1").arg(m_path);
274 QFile file(path);
275 // It seems that there should be an enum or define for these status codes somewhere in Qt or WebKit,
276 // but grep fails to turn one up.
277 // QNetwork uses the numeric values directly.
278 if (file.exists()) {
279 file.open(QIODevice::ReadOnly);
280 response = file.readAll();
281 } else {
282 code = 404;
283 text = QString::fromLatin1("Not OK");
284 }
285 }
286
287 QHttpResponseHeader responseHeader(code, text, 1, 0);
288 responseHeader.setContentLength(response.size());
289 if (!m_contentType.isEmpty())
290 responseHeader.setContentType(QString::fromLatin1(m_contentType));
291
292 QByteArray asciiHeader = responseHeader.toString().toAscii();
293 m_tcpConnection->write(asciiHeader);
294
295 m_tcpConnection->write(response);
296 m_tcpConnection->flush();
297 m_tcpConnection->close();
298
299 return;
300 }
301 }
302
tcpConnectionDisconnected()303 void InspectorServerRequestHandlerQt::tcpConnectionDisconnected()
304 {
305 if (m_inspectorClient)
306 m_inspectorClient->detachRemoteFrontend();
307 m_tcpConnection->deleteLater();
308 m_tcpConnection = 0;
309 }
310
webSocketSend(QByteArray payload)311 int InspectorServerRequestHandlerQt::webSocketSend(QByteArray payload)
312 {
313 Q_ASSERT(m_tcpConnection);
314 m_tcpConnection->putChar(0x00);
315 int nBytes = m_tcpConnection->write(payload);
316 m_tcpConnection->putChar(0xFF);
317 m_tcpConnection->flush();
318 return nBytes;
319 }
320
webSocketSend(const char * data,size_t length)321 int InspectorServerRequestHandlerQt::webSocketSend(const char* data, size_t length)
322 {
323 Q_ASSERT(m_tcpConnection);
324 m_tcpConnection->putChar(0x00);
325 int nBytes = m_tcpConnection->write(data, length);
326 m_tcpConnection->putChar(0xFF);
327 m_tcpConnection->flush();
328 return nBytes;
329 }
330
webSocketReadyRead()331 void InspectorServerRequestHandlerQt::webSocketReadyRead()
332 {
333 Q_ASSERT(m_tcpConnection);
334 if (!m_tcpConnection->bytesAvailable())
335 return;
336 QByteArray content = m_tcpConnection->read(m_tcpConnection->bytesAvailable());
337 m_data.append(content);
338 while (m_data.size() > 0) {
339 // first byte in websocket frame should be 0
340 Q_ASSERT(!m_data[0]);
341
342 // Start of WebSocket frame is indicated by 0
343 if (m_data[0]) {
344 qCritical() << "webSocketReadyRead: unknown frame type" << m_data[0];
345 m_data.clear();
346 m_tcpConnection->close();
347 return;
348 }
349
350 // End of WebSocket frame indicated by 0xff.
351 int pos = m_data.indexOf(0xff, 1);
352 if (pos < 1)
353 return;
354
355 // After above checks, length will be >= 0.
356 size_t length = pos - 1;
357 if (length <= 0)
358 return;
359
360 QByteArray payload = m_data.mid(1, length);
361
362 #if ENABLE(INSPECTOR)
363 if (m_inspectorClient) {
364 InspectorController* inspectorController = m_inspectorClient->m_inspectedWebPage->d->page->inspectorController();
365 inspectorController->dispatchMessageFromFrontend(QString::fromUtf8(payload));
366 }
367 #endif
368
369 // Remove this WebSocket message from m_data (payload, start-of-frame byte, end-of-frame byte).
370 m_data = m_data.mid(length + 2);
371 }
372 }
373
RemoteFrontendChannel(InspectorServerRequestHandlerQt * requestHandler)374 RemoteFrontendChannel::RemoteFrontendChannel(InspectorServerRequestHandlerQt* requestHandler)
375 : QObject(requestHandler)
376 , m_requestHandler(requestHandler)
377 {
378 }
379
sendMessageToFrontend(const String & message)380 bool RemoteFrontendChannel::sendMessageToFrontend(const String& message)
381 {
382 if (!m_requestHandler)
383 return false;
384 CString cstr = message.utf8();
385 return m_requestHandler->webSocketSend(cstr.data(), cstr.length());
386 }
387
388 }
389