Home | History | Annotate | Download | only in WebCoreSupport
      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  */
     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  */
     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 
     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 
     97 InspectorServerQt::InspectorServerQt()
     98     : QObject()
     99     , m_tcpServer(0)
    100     , m_pageNumber(1)
    101 {
    102 }
    103 
    104 InspectorServerQt::~InspectorServerQt()
    105 {
    106     close();
    107 }
    108 
    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 
    119 void InspectorServerQt::close()
    120 {
    121     if (m_tcpServer) {
    122         m_tcpServer->close();
    123         delete m_tcpServer;
    124     }
    125     m_tcpServer = 0;
    126 }
    127 
    128 InspectorClientQt* InspectorServerQt::inspectorClientForPage(int pageNum)
    129 {
    130     InspectorClientQt* client = m_inspectorClients.value(pageNum);
    131     return client;
    132 }
    133 
    134 void InspectorServerQt::registerClient(InspectorClientQt* client)
    135 {
    136     if (!m_inspectorClients.key(client))
    137         m_inspectorClients.insert(m_pageNumber++, client);
    138 }
    139 
    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 
    153 void InspectorServerQt::newConnection()
    154 {
    155     QTcpSocket* tcpConnection = m_tcpServer->nextPendingConnection();
    156     InspectorServerRequestHandlerQt* handler = new InspectorServerRequestHandlerQt(tcpConnection, this);
    157     handler->setParent(this);
    158 }
    159 
    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 
    173 InspectorServerRequestHandlerQt::~InspectorServerRequestHandlerQt()
    174 {
    175 }
    176 
    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 
    303 void InspectorServerRequestHandlerQt::tcpConnectionDisconnected()
    304 {
    305     if (m_inspectorClient)
    306         m_inspectorClient->detachRemoteFrontend();
    307     m_tcpConnection->deleteLater();
    308     m_tcpConnection = 0;
    309 }
    310 
    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 
    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 
    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 
    374 RemoteFrontendChannel::RemoteFrontendChannel(InspectorServerRequestHandlerQt* requestHandler)
    375     : QObject(requestHandler)
    376     , m_requestHandler(requestHandler)
    377 {
    378 }
    379 
    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