diff options
Diffstat (limited to 'WebKit/qt/WebCoreSupport/InspectorServerQt.cpp')
-rw-r--r-- | WebKit/qt/WebCoreSupport/InspectorServerQt.cpp | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/WebKit/qt/WebCoreSupport/InspectorServerQt.cpp b/WebKit/qt/WebCoreSupport/InspectorServerQt.cpp new file mode 100644 index 0000000..fad7247 --- /dev/null +++ b/WebKit/qt/WebCoreSupport/InspectorServerQt.cpp @@ -0,0 +1,386 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "InspectorServerQt.h" + +#include "InspectorBackendDispatcher.h" +#include "InspectorClientQt.h" +#include "MD5.h" +#include "Page.h" +#include "qwebpage.h" +#include "qwebpage_p.h" +#include <QFile> +#include <QHttpHeader> +#include <QHttpRequestHeader> +#include <QHttpResponseHeader> +#include <QString> +#include <QStringList> +#include <QTcpServer> +#include <QTcpSocket> +#include <QUrl> +#include <QWidget> +#include <qendian.h> + +namespace WebCore { + +/*! + Computes the WebSocket handshake response given the two challenge numbers and key3. + */ +static void generateWebSocketChallengeResponse(uint32_t number1, uint32_t number2, const unsigned char key3[8], unsigned char response[16]) +{ + uint8_t challenge[16]; + qToBigEndian<qint32>(number1, &challenge[0]); + qToBigEndian<qint32>(number2, &challenge[4]); + memcpy(&challenge[8], key3, 8); + MD5 md5; + md5.addBytes(challenge, sizeof(challenge)); + Vector<uint8_t, 16> digest; + md5.checksum(digest); + memcpy(response, digest.data(), 16); +} + +/*! + Parses and returns a WebSocket challenge number according to the + method specified in the WebSocket protocol. + + The field contains numeric digits interspersed with spaces and + non-numeric digits. The protocol ignores the characters that are + neither digits nor spaces. The digits are concatenated and + interpreted as a long int. The result is this number divided by + the number of spaces. + */ +static quint32 parseWebSocketChallengeNumber(QString field) +{ + QString nString; + int numSpaces = 0; + for (int i = 0; i < field.size(); i++) { + QChar c = field[i]; + if (c == (QChar)' ') { + numSpaces++; + } else if ((c >= (QChar)'0') && (c <= (QChar)'9')) { + nString.append((QChar)c); + } + } + quint32 num = nString.toLong(); + quint32 result = (numSpaces ? (num / numSpaces) : num); + return result; +} + +static InspectorServerQt* s_inspectorServer; + +InspectorServerQt* InspectorServerQt::server() +{ + // s_inspectorServer is deleted in unregisterClient() when the last client is unregistered. + if (!s_inspectorServer) + s_inspectorServer = new InspectorServerQt(); + + return s_inspectorServer; +} + +InspectorServerQt::InspectorServerQt() + : QObject() + , m_tcpServer(0) + , m_pageNumber(1) +{ +} + +InspectorServerQt::~InspectorServerQt() +{ + close(); +} + +void InspectorServerQt::listen(quint16 port) +{ + if (m_tcpServer) + return; + + m_tcpServer = new QTcpServer(); + m_tcpServer->listen(QHostAddress::Any, port); + connect(m_tcpServer, SIGNAL(newConnection()), SLOT(newConnection())); +} + +void InspectorServerQt::close() +{ + if (m_tcpServer) { + m_tcpServer->close(); + delete m_tcpServer; + } + m_tcpServer = 0; +} + +InspectorClientQt* InspectorServerQt::inspectorClientForPage(int pageNum) +{ + InspectorClientQt* client = m_inspectorClients.value(pageNum); + return client; +} + +void InspectorServerQt::registerClient(InspectorClientQt* client) +{ + if (!m_inspectorClients.key(client)) + m_inspectorClients.insert(m_pageNumber++, client); +} + +void InspectorServerQt::unregisterClient(InspectorClientQt* client) +{ + int pageNum = m_inspectorClients.key(client, -1); + if (pageNum >= 0) + m_inspectorClients.remove(pageNum); + if (!m_inspectorClients.size()) { + // s_inspectorServer points to this. + s_inspectorServer = 0; + close(); + deleteLater(); + } +} + +void InspectorServerQt::newConnection() +{ + QTcpSocket* tcpConnection = m_tcpServer->nextPendingConnection(); + InspectorServerRequestHandlerQt* handler = new InspectorServerRequestHandlerQt(tcpConnection, this); + handler->setParent(this); +} + +InspectorServerRequestHandlerQt::InspectorServerRequestHandlerQt(QTcpSocket* tcpConnection, InspectorServerQt* server) + : QObject(server) + , m_tcpConnection(tcpConnection) + , m_server(server) + , m_inspectorClient(0) +{ + m_endOfHeaders = false; + m_contentLength = 0; + + connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(tcpReadyRead())); + connect(m_tcpConnection, SIGNAL(disconnected()), SLOT(tcpConnectionDisconnected())); +} + +InspectorServerRequestHandlerQt::~InspectorServerRequestHandlerQt() +{ +} + +void InspectorServerRequestHandlerQt::tcpReadyRead() +{ + QHttpRequestHeader header; + bool isWebSocket = false; + if (!m_tcpConnection) + return; + + if (!m_endOfHeaders) { + while (m_tcpConnection->bytesAvailable() && !m_endOfHeaders) { + QByteArray line = m_tcpConnection->readLine(); + m_data.append(line); + if (line == "\r\n") + m_endOfHeaders = true; + } + if (m_endOfHeaders) { + header = QHttpRequestHeader(QString::fromLatin1(m_data)); + if (header.isValid()) { + m_path = header.path(); + m_contentType = header.contentType().toLatin1(); + m_contentLength = header.contentLength(); + if (header.hasKey("Upgrade") && (header.value("Upgrade") == QLatin1String("WebSocket"))) + isWebSocket = true; + + m_data.clear(); + } + } + } + + if (m_endOfHeaders) { + QStringList pathAndQuery = m_path.split("?"); + m_path = pathAndQuery[0]; + QStringList words = m_path.split(QString::fromLatin1("/")); + + if (isWebSocket) { + // switch to websocket-style WebSocketService messaging + if (m_tcpConnection) { + m_tcpConnection->disconnect(SIGNAL(readyRead())); + connect(m_tcpConnection, SIGNAL(readyRead()), SLOT(webSocketReadyRead())); + + QByteArray key3 = m_tcpConnection->read(8); + + quint32 number1 = parseWebSocketChallengeNumber(header.value("Sec-WebSocket-Key1")); + quint32 number2 = parseWebSocketChallengeNumber(header.value("Sec-WebSocket-Key2")); + + char responseData[16]; + generateWebSocketChallengeResponse(number1, number2, (unsigned char*)key3.data(), (unsigned char*)responseData); + QByteArray response(responseData, sizeof(responseData)); + + QHttpResponseHeader responseHeader(101, "WebSocket Protocol Handshake", 1, 1); + responseHeader.setValue("Upgrade", header.value("Upgrade")); + responseHeader.setValue("Connection", header.value("Connection")); + responseHeader.setValue("Sec-WebSocket-Origin", header.value("Origin")); + responseHeader.setValue("Sec-WebSocket-Location", ("ws://" + header.value("Host") + m_path)); + responseHeader.setContentLength(response.size()); + m_tcpConnection->write(responseHeader.toString().toLatin1()); + m_tcpConnection->write(response); + m_tcpConnection->flush(); + + if ((words.size() == 4) + && (words[1] == QString::fromLatin1("devtools")) + && (words[2] == QString::fromLatin1("page"))) { + int pageNum = words[3].toInt(); + + m_inspectorClient = m_server->inspectorClientForPage(pageNum); + // Attach remoteFrontendChannel to inspector, also transferring ownership. + if (m_inspectorClient) + m_inspectorClient->attachAndReplaceRemoteFrontend(new RemoteFrontendChannel(this)); + } + + } + + return; + } + if (m_contentLength && (m_tcpConnection->bytesAvailable() < m_contentLength)) + return; + + QByteArray content = m_tcpConnection->read(m_contentLength); + m_endOfHeaders = false; + + QByteArray response; + int code = 200; + QString text = QString::fromLatin1("OK"); + + // If no path is specified, generate an index page. + if ((m_path == "") || (m_path == "/")) { + QString indexHtml = "<html><head><title>Remote Web Inspector</title></head><body><ul>\n"; + for (QMap<int, InspectorClientQt* >::const_iterator it = m_server->m_inspectorClients.begin(); + it != m_server->m_inspectorClients.end(); + ++it) { + indexHtml.append(QString("<li><a href=\"/webkit/inspector/inspector.html?page=%1\">%2</li>\n") + .arg(it.key()) + .arg(it.value()->m_inspectedWebPage->mainFrame()->url().toString())); + } + indexHtml.append("</ul></body></html>"); + response = indexHtml.toLatin1(); + } else { + QString path = QString(":%1").arg(m_path); + QFile file(path); + // It seems that there should be an enum or define for these status codes somewhere in Qt or WebKit, + // but grep fails to turn one up. + // QNetwork uses the numeric values directly. + if (file.exists()) { + file.open(QIODevice::ReadOnly); + response = file.readAll(); + } else { + code = 404; + text = QString::fromLatin1("Not OK"); + } + } + + QHttpResponseHeader responseHeader(code, text, 1, 0); + responseHeader.setContentLength(response.size()); + if (!m_contentType.isEmpty()) + responseHeader.setContentType(QString::fromLatin1(m_contentType)); + + QByteArray asciiHeader = responseHeader.toString().toAscii(); + m_tcpConnection->write(asciiHeader); + + m_tcpConnection->write(response); + m_tcpConnection->flush(); + m_tcpConnection->close(); + + return; + } +} + +void InspectorServerRequestHandlerQt::tcpConnectionDisconnected() +{ + if (m_inspectorClient) + m_inspectorClient->detachRemoteFrontend(); + m_tcpConnection->deleteLater(); + m_tcpConnection = 0; +} + +int InspectorServerRequestHandlerQt::webSocketSend(QByteArray payload) +{ + Q_ASSERT(m_tcpConnection); + m_tcpConnection->putChar(0x00); + int nBytes = m_tcpConnection->write(payload); + m_tcpConnection->putChar(0xFF); + m_tcpConnection->flush(); + return nBytes; +} + +int InspectorServerRequestHandlerQt::webSocketSend(const char* data, size_t length) +{ + Q_ASSERT(m_tcpConnection); + m_tcpConnection->putChar(0x00); + int nBytes = m_tcpConnection->write(data, length); + m_tcpConnection->putChar(0xFF); + m_tcpConnection->flush(); + return nBytes; +} + +void InspectorServerRequestHandlerQt::webSocketReadyRead() +{ + Q_ASSERT(m_tcpConnection); + if (!m_tcpConnection->bytesAvailable()) + return; + QByteArray content = m_tcpConnection->read(m_tcpConnection->bytesAvailable()); + m_data.append(content); + while (m_data.size() > 0) { + // first byte in websocket frame should be 0 + Q_ASSERT(!m_data[0]); + + // Start of WebSocket frame is indicated by 0 + if (m_data[0]) { + qCritical() << "webSocketReadyRead: unknown frame type" << m_data[0]; + m_data.clear(); + m_tcpConnection->close(); + return; + } + + // End of WebSocket frame indicated by 0xff. + int pos = m_data.indexOf(0xff, 1); + if (pos < 1) + return; + + // After above checks, length will be >= 0. + size_t length = pos - 1; + if (length <= 0) + return; + + QByteArray payload = m_data.mid(1, length); + + if (m_inspectorClient) { + InspectorController* inspectorController = m_inspectorClient->m_inspectedWebPage->d->page->inspectorController(); + inspectorController->inspectorBackendDispatcher()->dispatch(QString::fromUtf8(payload)); + } + + // Remove this WebSocket message from m_data (payload, start-of-frame byte, end-of-frame byte). + m_data = m_data.mid(length + 2); + } +} + +RemoteFrontendChannel::RemoteFrontendChannel(InspectorServerRequestHandlerQt* requestHandler) + : QObject(requestHandler) + , m_requestHandler(requestHandler) +{ +} + +bool RemoteFrontendChannel::sendMessageToFrontend(const String& message) +{ + if (!m_requestHandler) + return false; + CString cstr = message.utf8(); + return m_requestHandler->webSocketSend(cstr.data(), cstr.length()); +} + +} |