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