/* 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 #include #include #include #include #include #include #include #include #include #include #include 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(number1, &challenge[0]); qToBigEndian(number2, &challenge[4]); memcpy(&challenge[8], key3, 8); MD5 md5; md5.addBytes(challenge, sizeof(challenge)); Vector 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 = "Remote Web Inspector"); 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 ENABLE(INSPECTOR) if (m_inspectorClient) { InspectorController* inspectorController = m_inspectorClient->m_inspectedWebPage->d->page->inspectorController(); inspectorController->inspectorBackendDispatcher()->dispatch(QString::fromUtf8(payload)); } #endif // 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()); } }