1/*
2 * Copyright (C) 2011 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28
29#if ENABLE(INSPECTOR_SERVER)
30
31#include "WebInspectorServer.h"
32
33#include "HTTPHeaderNames.h"
34#include "HTTPRequest.h"
35#include "WebInspectorProxy.h"
36#include "WebSocketServerConnection.h"
37
38using namespace WebCore;
39
40namespace WebKit {
41
42static unsigned pageIdFromRequestPath(const String& path)
43{
44    size_t start = path.reverseFind('/');
45    String numberString = path.substring(start + 1, path.length() - start - 1);
46
47    bool ok = false;
48    unsigned number = numberString.toUIntStrict(&ok);
49    if (!ok)
50        return 0;
51    return number;
52}
53
54WebInspectorServer& WebInspectorServer::shared()
55{
56    static WebInspectorServer& server = *new WebInspectorServer;
57    return server;
58}
59
60WebInspectorServer::WebInspectorServer()
61    : WebSocketServer(this)
62    , m_nextAvailablePageId(1)
63{
64}
65
66WebInspectorServer::~WebInspectorServer()
67{
68    // Close any remaining open connections.
69    HashMap<unsigned, WebSocketServerConnection*>::iterator end = m_connectionMap.end();
70    for (HashMap<unsigned, WebSocketServerConnection*>::iterator it = m_connectionMap.begin(); it != end; ++it) {
71        WebSocketServerConnection* connection = it->value;
72        WebInspectorProxy* client = m_clientMap.get(connection->identifier());
73        closeConnection(client, connection);
74    }
75}
76
77int WebInspectorServer::registerPage(WebInspectorProxy* client)
78{
79#ifndef ASSERT_DISABLED
80    ClientMap::iterator end = m_clientMap.end();
81    for (ClientMap::iterator it = m_clientMap.begin(); it != end; ++it)
82        ASSERT(it->value != client);
83#endif
84
85    int pageId = m_nextAvailablePageId++;
86    m_clientMap.set(pageId, client);
87    return pageId;
88}
89
90void WebInspectorServer::unregisterPage(int pageId)
91{
92    m_clientMap.remove(pageId);
93    WebSocketServerConnection* connection = m_connectionMap.get(pageId);
94    if (connection)
95        closeConnection(0, connection);
96}
97
98String WebInspectorServer::inspectorUrlForPageID(int)
99{
100    return String();
101}
102
103void WebInspectorServer::sendMessageOverConnection(unsigned pageIdForConnection, const String& message)
104{
105    WebSocketServerConnection* connection = m_connectionMap.get(pageIdForConnection);
106    if (connection)
107        connection->sendWebSocketMessage(message);
108}
109
110void WebInspectorServer::didReceiveUnrecognizedHTTPRequest(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
111{
112    // request->url() contains only the path extracted from the HTTP request line
113    // and URL is poor at parsing incomplete URLs, so extract the interesting parts manually.
114    String path = request->url();
115    size_t pathEnd = path.find('?');
116    if (pathEnd == notFound)
117        pathEnd = path.find('#');
118    if (pathEnd != notFound)
119        path.truncate(pathEnd);
120
121    // Ask for the complete payload in memory for the sake of simplicity. A more efficient way would be
122    // to ask for header data and then let the platform abstraction write the payload straight on the connection.
123    Vector<char> body;
124    String contentType;
125    bool found = platformResourceForPath(path, body, contentType);
126
127    HTTPHeaderMap headerFields;
128    headerFields.set(HTTPHeaderName::Connection, "close");
129    headerFields.set(HTTPHeaderName::ContentLength, String::number(body.size()));
130    if (found)
131        headerFields.set(HTTPHeaderName::ContentType, contentType);
132
133    // Send when ready and close immediately afterwards.
134    connection->sendHTTPResponseHeader(found ? 200 : 404, found ? "OK" : "Not Found", headerFields);
135    connection->sendRawData(body.data(), body.size());
136    connection->shutdownAfterSendOrNow();
137}
138
139bool WebInspectorServer::didReceiveWebSocketUpgradeHTTPRequest(WebSocketServerConnection*, PassRefPtr<HTTPRequest> request)
140{
141    String path = request->url();
142
143    // NOTE: Keep this in sync with WebCore/inspector/front-end/inspector.js.
144    DEPRECATED_DEFINE_STATIC_LOCAL(const String, inspectorWebSocketConnectionPathPrefix, (ASCIILiteral("/devtools/page/")));
145
146    // Unknown path requested.
147    if (!path.startsWith(inspectorWebSocketConnectionPathPrefix))
148        return false;
149
150    int pageId = pageIdFromRequestPath(path);
151    // Invalid page id.
152    if (!pageId)
153        return false;
154
155    // There is no client for that page id.
156    WebInspectorProxy* client = m_clientMap.get(pageId);
157    if (!client)
158        return false;
159
160    return true;
161}
162
163void WebInspectorServer::didEstablishWebSocketConnection(WebSocketServerConnection* connection, PassRefPtr<HTTPRequest> request)
164{
165    String path = request->url();
166    unsigned pageId = pageIdFromRequestPath(path);
167    ASSERT(pageId);
168
169    // Ignore connections to a page that already have a remote inspector connected.
170    if (m_connectionMap.contains(pageId)) {
171        LOG_ERROR("A remote inspector connection already exist for page ID %d. Ignoring.", pageId);
172        connection->shutdownNow();
173        return;
174    }
175
176    // Map the pageId to the connection in case we need to close the connection locally.
177    connection->setIdentifier(pageId);
178    m_connectionMap.set(pageId, connection);
179
180    WebInspectorProxy* client = m_clientMap.get(pageId);
181    client->remoteFrontendConnected();
182}
183
184void WebInspectorServer::didReceiveWebSocketMessage(WebSocketServerConnection* connection, const String& message)
185{
186    // Dispatch incoming remote message locally.
187    unsigned pageId = connection->identifier();
188    ASSERT(pageId);
189    WebInspectorProxy* client = m_clientMap.get(pageId);
190    client->dispatchMessageFromRemoteFrontend(message);
191}
192
193void WebInspectorServer::didCloseWebSocketConnection(WebSocketServerConnection* connection)
194{
195    // Connection has already shut down.
196    unsigned pageId = connection->identifier();
197    if (!pageId)
198        return;
199
200    // The socket closing means the remote side has caused the close.
201    WebInspectorProxy* client = m_clientMap.get(pageId);
202    closeConnection(client, connection);
203}
204
205void WebInspectorServer::closeConnection(WebInspectorProxy* client, WebSocketServerConnection* connection)
206{
207    // Local side cleanup.
208    if (client)
209        client->remoteFrontendDisconnected();
210
211    // Remote side cleanup.
212    m_connectionMap.remove(connection->identifier());
213    connection->setIdentifier(0);
214    connection->shutdownNow();
215}
216
217}
218
219#endif // ENABLE(INSPECTOR_SERVER)
220