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
42namespace WebCore {
43
44/*!
45    Computes the WebSocket handshake response given the two challenge numbers and key3.
46 */
47static 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 */
70static 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
86static InspectorServerQt* s_inspectorServer;
87
88InspectorServerQt* 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
97InspectorServerQt::InspectorServerQt()
98    : QObject()
99    , m_tcpServer(0)
100    , m_pageNumber(1)
101{
102}
103
104InspectorServerQt::~InspectorServerQt()
105{
106    close();
107}
108
109void 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
119void InspectorServerQt::close()
120{
121    if (m_tcpServer) {
122        m_tcpServer->close();
123        delete m_tcpServer;
124    }
125    m_tcpServer = 0;
126}
127
128InspectorClientQt* InspectorServerQt::inspectorClientForPage(int pageNum)
129{
130    InspectorClientQt* client = m_inspectorClients.value(pageNum);
131    return client;
132}
133
134void InspectorServerQt::registerClient(InspectorClientQt* client)
135{
136    if (!m_inspectorClients.key(client))
137        m_inspectorClients.insert(m_pageNumber++, client);
138}
139
140void 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
153void InspectorServerQt::newConnection()
154{
155    QTcpSocket* tcpConnection = m_tcpServer->nextPendingConnection();
156    InspectorServerRequestHandlerQt* handler = new InspectorServerRequestHandlerQt(tcpConnection, this);
157    handler->setParent(this);
158}
159
160InspectorServerRequestHandlerQt::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
173InspectorServerRequestHandlerQt::~InspectorServerRequestHandlerQt()
174{
175}
176
177void 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
303void InspectorServerRequestHandlerQt::tcpConnectionDisconnected()
304{
305    if (m_inspectorClient)
306        m_inspectorClient->detachRemoteFrontend();
307    m_tcpConnection->deleteLater();
308    m_tcpConnection = 0;
309}
310
311int 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
321int 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
331void 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
374RemoteFrontendChannel::RemoteFrontendChannel(InspectorServerRequestHandlerQt* requestHandler)
375    : QObject(requestHandler)
376    , m_requestHandler(requestHandler)
377{
378}
379
380bool 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