 Chromium Code Reviews
 Chromium Code Reviews Issue 11362267:
  Add status service for remoting host.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src
    
  
    Issue 11362267:
  Add status service for remoting host.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src| 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..37e83bfd5acb21169f0d3c74def999ca36a8a042 | 
| --- /dev/null | 
| +++ b/remoting/host/host_status_service.cc | 
| @@ -0,0 +1,197 @@ | 
| +// 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; | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
Does this range need a firewall rule on Windows?
 
Sergey Ulanov
2012/11/16 00:52:10
We are connecting from localhost to localhost. Are
 | 
| +const int kPortRangeMax = 12820; | 
| + | 
| +const char kChromeExtensionProtocolPrefix[] = "chrome-extension://"; | 
| 
Wez
2012/11/15 23:34:59
You should really change this to kChromeExtensionU
 
Sergey Ulanov
2012/11/16 00:52:10
I considered this, but I don't think using GURL fo
 | 
| + | 
| +#ifdef NDEBUG | 
| 
Wez
2012/11/15 23:34:59
nit: #if !defined(NDEBUG)
 
Sergey Ulanov
2012/11/16 00:52:10
Changed to "#if defined(NDEBUG)",
 | 
| +const char* kAllowedWebApplicationIds[] = { | 
| + "gbchcmhmhahfdphkhkmpfmihenigjmpp", // Chrome Remote Desktop | 
| + "kgngmbheleoaphbjbaiobfdepmghbfah", // Pre-release Chrome Remote Desktop | 
| + "odkaodonbgfohohmklejpjiejmcipmib", // Dogfood Chrome Remote Desktop | 
| + "ojoimpklfciegopdfgeenehpalipignm", // Chromoting canary | 
| +}; | 
| +#endif | 
| + | 
| +} // namespace | 
| + | 
| +class HostStatusService::Connection : public WebsocketConnection::Delegate { | 
| + public: | 
| + Connection(HostStatusService* service, | 
| + scoped_ptr<WebsocketConnection> websocket) | 
| + : service_(service), | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
nit: Add a comment explaining that |service| must
 
Sergey Ulanov
2012/11/16 00:52:10
Done, but I don't think its really necessary becau
 | 
| + websocket_(websocket.Pass()) { | 
| + websocket_->Accept(this); | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
I believe this constructor is too complex to defin
 
Sergey Ulanov
2012/11/16 00:52:10
Done.
 | 
| + } | 
| + virtual ~Connection() { | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
nit: it is not required by the style guide but it
 
Wez
2012/11/15 23:34:59
I'd prefer it be left in-line, and on a single lin
 
Sergey Ulanov
2012/11/16 00:52:10
Done.
 
Sergey Ulanov
2012/11/16 00:52:10
I've already moved it, so I'll leave it there.
 | 
| + } | 
| + | 
| + // WebsocketConnection::Delegate interface. | 
| + virtual void OnWebsocketMessage(const std::string& message) OVERRIDE; | 
| + virtual void OnWebsocketClosed() OVERRIDE; | 
| + | 
| + private: | 
| + void SendHostStatusMessage(); | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
nit: Add comments explaining what these methods ar
 
Sergey Ulanov
2012/11/16 00:52:10
Done.
 | 
| + void SendMessage(const std::string& method, | 
| + scoped_ptr<base::DictionaryValue> data); | 
| + | 
| + // Closes this connection. Destroys the object, so should be used with care. | 
| 
Wez
2012/11/15 23:34:59
nit: Reword e.g "Closes the connection and destroy
 
Sergey Ulanov
2012/11/16 00:52:10
Done.
 | 
| + void CloseOnError(); | 
| 
Wez
2012/11/15 23:34:59
nit: CloseOnError sounds like it tells the thing t
 
Sergey Ulanov
2012/11/16 00:52:10
It means "close connection because there was a pro
 | 
| + | 
| + HostStatusService* service_; | 
| + scoped_ptr<WebsocketConnection> websocket_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(Connection); | 
| +}; | 
| + | 
| +void HostStatusService::Connection::OnWebsocketMessage( | 
| + const std::string& message) { | 
| + scoped_ptr<base::Value> json( | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
nit: Can anything bad happen if a malicious sender
 
Sergey Ulanov
2012/11/16 00:52:10
That's a good point. I think it's better to limit
 | 
| + base::JSONReader::Read(message, base::JSON_ALLOW_TRAILING_COMMAS)); | 
| + | 
| + base::DictionaryValue* message_dict = NULL; | 
| 
Wez
2012/11/15 23:34:59
nit: Add a comment before this block explaining wh
 
Sergey Ulanov
2012/11/16 00:52:10
Done.
 | 
| + std::string method; | 
| + base::DictionaryValue* data = NULL; | 
| 
Wez
2012/11/15 23:34:59
|data| doesn't seem to be used?
 
Sergey Ulanov
2012/11/16 00:52:10
We may need it in the future when we add some new
 | 
| + if (!json.get() || | 
| + !json->GetAsDictionary(&message_dict) || | 
| + !message_dict->GetString("method", &method) || | 
| + !message_dict->GetDictionary("data", &data)) { | 
| + LOG(ERROR) << "Received invalid message: " << message; | 
| + CloseOnError(); | 
| + return; | 
| + } | 
| + | 
| + if (method == "getHostStatus") { | 
| + SendHostStatusMessage(); | 
| + } else { | 
| + LOG(ERROR) << "Received message with unknown method: " << message; | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
Call CloseOnError() here?
 
Sergey Ulanov
2012/11/16 00:52:10
Actually I think it's better to keep it connected,
 | 
| + return; | 
| + } | 
| +} | 
| + | 
| +void HostStatusService::Connection::OnWebsocketClosed() { | 
| + service_->OnConnectionClosed(this); | 
| 
Wez
2012/11/15 23:34:59
You reset |websocket_| in CloseOnError() but not h
 
Sergey Ulanov
2012/11/16 00:52:10
Changed this to Close().
 | 
| +} | 
| + | 
| +void HostStatusService::Connection::SendHostStatusMessage() { | 
| + SendMessage("hostStatus", service_->GetStatusMessage()); | 
| +} | 
| + | 
| +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::CloseOnError() { | 
| + websocket_.reset(); | 
| + service_->OnConnectionClosed(this); | 
| +} | 
| + | 
| +HostStatusService::HostStatusService() | 
| + : started_(false) { | 
| + char ip[] = {127, 0, 0, 1}; | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
Will it still work in presence of IPv6?
 
Sergey Ulanov
2012/11/16 00:52:10
Yes. On Linux localhost normally resolves to IPv4
 | 
| + net::IPAddressNumber localhost(ip, ip + sizeof(ip)); | 
| + for (int port = kPortRangeMin; port < kPortRangeMax; ++port) { | 
| + net::IPEndPoint endpoint(localhost, port); | 
| + if (websocket_listener_.Listen( | 
| + endpoint, base::Bind(&HostStatusService::OnNewConnection, | 
| 
Wez
2012/11/15 23:34:59
nit: Moving endpoint to the previous line would ma
 
Sergey Ulanov
2012/11/16 00:52:10
No, it doesn't, because that would mean that the s
 | 
| + base::Unretained(this)))) { | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
How is it guaranteed that |this| will not be delet
 
Sergey Ulanov
2012/11/16 00:52:10
We own websocket_listener_ which is guaranteed to
 | 
| + host_name_ = "localhost:" + base::UintToString(port); | 
| + LOG(INFO) << "Listening for WebSocket connections on localhost:" << port; | 
| + break; | 
| + } | 
| + } | 
| +} | 
| + | 
| +HostStatusService::~HostStatusService() { | 
| + STLDeleteElements(&connections_); | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
nit: Should |websocket_| be reset first?
 
Wez
2012/11/15 23:34:59
It's held in a scoped_ptr<> in Connection, so that
 
Sergey Ulanov
2012/11/16 00:52:10
It doesn't matter. websocket_listener_ is not conn
 | 
| +} | 
| + | 
| +void HostStatusService::SetState(bool started, const std::string& host_id) { | 
| + started_ = started; | 
| + host_id_ = host_id; | 
| +} | 
| + | 
| +bool HostStatusService::IsAllowedOrigin(const std::string& origin) { | 
| 
Wez
2012/11/15 23:34:59
Use GURL to break down the URL first, and then deb
 
Sergey Ulanov
2012/11/16 00:52:10
As explained above I think it would be overkill to
 | 
| +#ifndef NDEBUG | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
Why is the difference? If it is just a sanity chec
 
Sergey Ulanov
2012/11/16 00:52:10
We want to limit which webapps can connect to the
 | 
| + // Allow all chrome extensions in Debug builds. | 
| + return StartsWithASCII(origin, kChromeExtensionProtocolPrefix, false); | 
| +#else | 
| + std::string prefix(kChromeExtensionProtocolPrefix); | 
| + for (int i = 0; i < arraysize(kAllowedWebApplicationIds)) { | 
| + if (origin == prefix + kAllowedWebApplicationIds[i]) { | 
| + return true; | 
| + } | 
| + } | 
| + return false; | 
| +#endif | 
| +} | 
| + | 
| +void HostStatusService::OnNewConnection( | 
| + scoped_ptr<WebsocketConnection> connection) { | 
| + bool accept = true; | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
nit: It is better to set |accept| to false by defa
 
Wez
2012/11/15 23:34:59
I think this would be more readable as a series of
 
Sergey Ulanov
2012/11/16 00:52:10
Done.
 
Sergey Ulanov
2012/11/16 00:52:10
Done.
 | 
| + | 
| + if (connection->request_host() != host_name_) { | 
| + LOG(ERROR) << "Received connection for invalid host: " | 
| + << connection->request_host() | 
| + << ". Expected " << host_name_; | 
| + accept = false; | 
| + } else if (connection->request_path() != "/remoting_host_status") { | 
| + LOG(ERROR) << "Received connection for unknown path: " | 
| + << connection->request_path(); | 
| + accept = false; | 
| + } else if (!IsAllowedOrigin(connection->origin())) { | 
| 
alexeypa (please no reviews)
2012/11/15 19:45:56
Can connection->origin() be trusted? Can the conne
 
Wez
2012/11/15 23:34:59
The origin protects from XSS; it's assumed the req
 
Sergey Ulanov
2012/11/16 00:52:10
A web site cannot spoof it, but any locally instal
 | 
| + LOG(ERROR) << "Rejecting connection from unknown origin: " | 
| + << connection->origin(); | 
| + accept = false; | 
| + } | 
| + | 
| + if (accept) { | 
| + connections_.insert(new Connection(this, connection.Pass())); | 
| + } else { | 
| + connection->Reject(); | 
| + } | 
| +} | 
| + | 
| +void HostStatusService::OnConnectionClosed(Connection* connection) { | 
| + connections_.erase(connection); | 
| 
Wez
2012/11/15 23:34:59
This doesn't seem to delete Connection?
 
Sergey Ulanov
2012/11/16 00:52:10
Good catch. Fixed.
 | 
| +} | 
| + | 
| +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 |