| Index: remoting/host/host_status_service.cc
|
| diff --git a/remoting/host/host_status_service.cc b/remoting/host/host_status_service.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3141308f19587a484d7f9de2512bde4555e81a05
|
| --- /dev/null
|
| +++ b/remoting/host/host_status_service.cc
|
| @@ -0,0 +1,224 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "remoting/host/host_status_service.h"
|
| +
|
| +#include "base/json/json_reader.h"
|
| +#include "base/json/json_writer.h"
|
| +#include "base/stl_util.h"
|
| +#include "base/string_number_conversions.h"
|
| +#include "base/string_util.h"
|
| +#include "base/stringize_macros.h"
|
| +#include "base/values.h"
|
| +#include "net/base/ip_endpoint.h"
|
| +#include "net/base/net_util.h"
|
| +#include "remoting/host/websocket_connection.h"
|
| +#include "remoting/host/websocket_listener.h"
|
| +
|
| +namespace remoting {
|
| +
|
| +namespace {
|
| +
|
| +// HostStatusService uses the first port available in the following range.
|
| +const int kPortRangeMin = 12810;
|
| +const int kPortRangeMax = 12820;
|
| +
|
| +const char kChromeExtensionUrlSchemePrefix[] = "chrome-extension://";
|
| +
|
| +#if defined(NDEBUG)
|
| +const char* const kAllowedWebApplicationIds[] = {
|
| + "gbchcmhmhahfdphkhkmpfmihenigjmpp", // Chrome Remote Desktop
|
| + "kgngmbheleoaphbjbaiobfdepmghbfah", // Pre-release Chrome Remote Desktop
|
| + "odkaodonbgfohohmklejpjiejmcipmib", // Dogfood Chrome Remote Desktop
|
| + "ojoimpklfciegopdfgeenehpalipignm", // Chromoting canary
|
| +};
|
| +#endif
|
| +
|
| +// All messages we expect should be smaller than 64k.
|
| +const uint64 kMaximumIncomingMessageSize = 65536;
|
| +
|
| +} // namespace
|
| +
|
| +class HostStatusService::Connection : public WebSocketConnection::Delegate {
|
| + public:
|
| + // |service| owns Connection connection objects and must outlive them.
|
| + Connection(HostStatusService* service,
|
| + scoped_ptr<WebSocketConnection> websocket);
|
| + virtual ~Connection();
|
| +
|
| + // WebSocketConnection::Delegate interface.
|
| + virtual void OnWebSocketMessage(const std::string& message) OVERRIDE;
|
| + virtual void OnWebSocketClosed() OVERRIDE;
|
| +
|
| + private:
|
| + // Sends message with the specified |method| and |data|.
|
| + void SendMessage(const std::string& method,
|
| + scoped_ptr<base::DictionaryValue> data);
|
| +
|
| + // Closes the connection and destroys this object.
|
| + void Close();
|
| +
|
| + HostStatusService* service_;
|
| + scoped_ptr<WebSocketConnection> websocket_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Connection);
|
| +};
|
| +
|
| +HostStatusService::Connection::Connection(
|
| + HostStatusService* service,
|
| + scoped_ptr<WebSocketConnection> websocket)
|
| + : service_(service),
|
| + websocket_(websocket.Pass()) {
|
| + websocket_->Accept(this);
|
| + websocket_->set_maximum_message_size(kMaximumIncomingMessageSize);
|
| +}
|
| +
|
| +HostStatusService::Connection::~Connection() {
|
| +}
|
| +
|
| +void HostStatusService::Connection::OnWebSocketMessage(
|
| + const std::string& message) {
|
| + scoped_ptr<base::Value> json(
|
| + base::JSONReader::Read(message, base::JSON_ALLOW_TRAILING_COMMAS));
|
| +
|
| + // Verify that we've received a valid JSON dictionary and extract |method| and
|
| + // |data| fields from it.
|
| + base::DictionaryValue* message_dict = NULL;
|
| + std::string method;
|
| + base::DictionaryValue* data = NULL;
|
| + if (!json.get() ||
|
| + !json->GetAsDictionary(&message_dict) ||
|
| + !message_dict->GetString("method", &method) ||
|
| + !message_dict->GetDictionary("data", &data)) {
|
| + LOG(ERROR) << "Received invalid message: " << message;
|
| + Close();
|
| + return;
|
| + }
|
| +
|
| + if (method == "getHostStatus") {
|
| + SendMessage("hostStatus", service_->GetStatusMessage());
|
| + } else {
|
| + LOG(ERROR) << "Received message with unknown method: " << message;
|
| + scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue());
|
| + response->SetString("method", method);
|
| + SendMessage("unsupportedMethod", response.Pass());
|
| + return;
|
| + }
|
| +}
|
| +
|
| +void HostStatusService::Connection::OnWebSocketClosed() {
|
| + Close();
|
| +}
|
| +
|
| +void HostStatusService::Connection::SendMessage(
|
| + const std::string& method,
|
| + scoped_ptr<base::DictionaryValue> data) {
|
| + scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
|
| + message->SetString("method", method);
|
| + message->Set("data", data.release());
|
| +
|
| + std::string message_json;
|
| + base::JSONWriter::Write(message.get(), &message_json);
|
| + websocket_->SendText(message_json);
|
| +}
|
| +
|
| +void HostStatusService::Connection::Close() {
|
| + websocket_.reset();
|
| + service_->OnConnectionClosed(this);
|
| +}
|
| +
|
| +HostStatusService::HostStatusService()
|
| + : started_(false) {
|
| + // TODO(sergeyu): Do we need to listen on IPv6 port too?
|
| + char ip[] = {127, 0, 0, 1};
|
| + net::IPAddressNumber localhost(ip, ip + sizeof(ip));
|
| + for (int port = kPortRangeMin; port < kPortRangeMax; ++port) {
|
| + net::IPEndPoint endpoint(localhost, port);
|
| + // base::Unretained is safe because we own |websocket_listener_|.
|
| + if (websocket_listener_.Listen(
|
| + endpoint,
|
| + base::Bind(&HostStatusService::OnNewConnection,
|
| + base::Unretained(this)))) {
|
| + service_host_name_ = "localhost:" + base::UintToString(port);
|
| + LOG(INFO) << "Listening for WebSocket connections on localhost:" << port;
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +HostStatusService::~HostStatusService() {
|
| + STLDeleteElements(&connections_);
|
| +}
|
| +
|
| +void HostStatusService::SetHostIsUp(const std::string& host_id) {
|
| + started_ = true;
|
| + host_id_ = host_id;
|
| +}
|
| +void HostStatusService::SetHostIsDown() {
|
| + started_ = false;
|
| + host_id_.clear();
|
| +}
|
| +
|
| +// static
|
| +bool HostStatusService::IsAllowedOrigin(const std::string& origin) {
|
| +#ifndef NDEBUG
|
| + // Allow all chrome extensions in Debug builds.
|
| + return StartsWithASCII(origin, kChromeExtensionUrlSchemePrefix, false);
|
| +#else
|
| + // For Release builds allow only specific set of clients.
|
| + //
|
| + // TODO(sergeyu): Allow whitelisting of origins different from the ones in
|
| + // kAllowedWebApplicationIds (e.g. specify them in the host config).
|
| + std::string prefix(kChromeExtensionUrlSchemePrefix);
|
| + for (size_t i = 0; i < arraysize(kAllowedWebApplicationIds); ++i) {
|
| + if (origin == prefix + kAllowedWebApplicationIds[i]) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| +#endif
|
| +}
|
| +
|
| +void HostStatusService::OnNewConnection(
|
| + scoped_ptr<WebSocketConnection> connection) {
|
| + if (connection->request_host() != service_host_name_) {
|
| + LOG(ERROR) << "Received connection for invalid host: "
|
| + << connection->request_host()
|
| + << ". Expected " << service_host_name_;
|
| + connection->Reject();
|
| + return;
|
| + }
|
| +
|
| + if (connection->request_path() != "/remoting_host_status") {
|
| + LOG(ERROR) << "Received connection for unknown path: "
|
| + << connection->request_path();
|
| + connection->Reject();
|
| + return;
|
| + }
|
| +
|
| + if (!IsAllowedOrigin(connection->origin())) {
|
| + LOG(ERROR) << "Rejecting connection from unknown origin: "
|
| + << connection->origin();
|
| + connection->Reject();
|
| + return;
|
| + }
|
| +
|
| + // Accept connection.
|
| + connections_.insert(new Connection(this, connection.Pass()));
|
| +}
|
| +
|
| +void HostStatusService::OnConnectionClosed(Connection* connection) {
|
| + connections_.erase(connection);
|
| + delete connection;
|
| +}
|
| +
|
| +scoped_ptr<base::DictionaryValue> HostStatusService::GetStatusMessage() {
|
| + scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
|
| + result->SetString("state", started_ ? "STARTED" : "STOPPED");
|
| + result->SetString("version", STRINGIZE(VERSION));
|
| + result->SetString("hostId", host_id_);
|
| + return result.Pass();
|
| +}
|
| +
|
| +} // namespace remoting
|
|
|