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