Chromium Code Reviews| 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 |