diff options
author | lambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-03 19:12:01 +0000 |
---|---|---|
committer | lambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-03 19:12:01 +0000 |
commit | 3f118f750972f1232033243312819b2acee9abab (patch) | |
tree | 847a432d52ac7122723e6e4ec6a7f391db88d56d /remoting | |
parent | 73ff7fe36a24d6366706dfae48dfacfe77a7ddd8 (diff) | |
download | chromium_src-3f118f750972f1232033243312819b2acee9abab.zip chromium_src-3f118f750972f1232033243312819b2acee9abab.tar.gz chromium_src-3f118f750972f1232033243312819b2acee9abab.tar.bz2 |
Native Messaging host process for Chromoting Me2Me.
This adds a GYP target to build a Native Messaging host
executable for all platforms that currently support Me2Me
hosts.
BUG=173509
TEST=manual
Review URL: https://chromiumcodereview.appspot.com/12852005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@198164 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/host/setup/daemon_controller_linux.cc | 11 | ||||
-rw-r--r-- | remoting/host/setup/native_messaging_host.cc | 427 | ||||
-rw-r--r-- | remoting/host/setup/native_messaging_reader.cc | 164 | ||||
-rw-r--r-- | remoting/host/setup/native_messaging_reader.h | 76 | ||||
-rw-r--r-- | remoting/host/setup/native_messaging_writer.cc | 103 | ||||
-rw-r--r-- | remoting/host/setup/native_messaging_writer.h | 37 | ||||
-rw-r--r-- | remoting/remoting.gyp | 21 |
7 files changed, 837 insertions, 2 deletions
diff --git a/remoting/host/setup/daemon_controller_linux.cc b/remoting/host/setup/daemon_controller_linux.cc index 216572f..a777346 100644 --- a/remoting/host/setup/daemon_controller_linux.cc +++ b/remoting/host/setup/daemon_controller_linux.cc @@ -117,8 +117,15 @@ static bool RunHostScriptWithTimeout( command_line.AppendArg(args[i]); } base::ProcessHandle process_handle; - if (!base::LaunchProcess(command_line, base::LaunchOptions(), - &process_handle)) { + + // Redirect the child's stdout to the parent's stderr. In the case where this + // parent process is a Native Messaging host, its stdout is used to send + // messages to the web-app. + base::FileHandleMappingVector fds_to_remap; + fds_to_remap.push_back(std::pair<int, int>(STDERR_FILENO, STDOUT_FILENO)); + base::LaunchOptions options; + options.fds_to_remap = &fds_to_remap; + if (!base::LaunchProcess(command_line, options, &process_handle)) { return false; } diff --git a/remoting/host/setup/native_messaging_host.cc b/remoting/host/setup/native_messaging_host.cc new file mode 100644 index 0000000..b665f83 --- /dev/null +++ b/remoting/host/setup/native_messaging_host.cc @@ -0,0 +1,427 @@ +// Copyright 2013 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 <string> + +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/platform_file.h" +#include "base/run_loop.h" +#include "base/strings/stringize_macros.h" +#include "base/thread_task_runner_handle.h" +#include "base/values.h" +#include "net/base/net_util.h" +#include "remoting/base/rsa_key_pair.h" +#include "remoting/host/logging.h" +#include "remoting/host/pin_hash.h" +#include "remoting/host/setup/daemon_controller.h" +#include "remoting/host/setup/native_messaging_reader.h" +#include "remoting/host/setup/native_messaging_writer.h" + +#if defined(OS_POSIX) +#include <unistd.h> +#endif + +namespace { + +// Helper to extract the "config" part of a message as a DictionaryValue. +// Returns NULL on failure, and logs an error message. +scoped_ptr<base::DictionaryValue> ConfigDictionaryFromMessage( + const base::DictionaryValue& message) { + scoped_ptr<base::DictionaryValue> config_dict; + std::string config_str; + if (!message.GetString("config", &config_str)) { + LOG(ERROR) << "'config' not found."; + return config_dict.Pass(); + } + + // TODO(lambroslambrou): Fix the webapp to embed the config dictionary + // directly into the request, rather than as a serialized JSON string. + scoped_ptr<base::Value> config( + base::JSONReader::Read(config_str, base::JSON_ALLOW_TRAILING_COMMAS)); + if (!config || !config->IsType(base::Value::TYPE_DICTIONARY)) { + LOG(ERROR) << "Bad config parameter."; + return config_dict.Pass(); + } + config_dict.reset(reinterpret_cast<base::DictionaryValue*>(config.release())); + return config_dict.Pass(); +} + +} // namespace + +namespace remoting { + +// Implementation of the NativeMessaging host process. +class NativeMessagingHost { + public: + NativeMessagingHost( + base::PlatformFile input, + base::PlatformFile output, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + const base::Closure& quit_closure); + ~NativeMessagingHost(); + + // Starts reading and processing messages. + void Start(); + + // Posts |quit_closure| to |caller_task_runner|. This gets called whenever an + // error is encountered during reading and processing messages. + void Shutdown(); + + private: + // Processes a message received from the client app. + void ProcessMessage(scoped_ptr<base::Value> message); + + // These "Process.." methods handle specific request types. The |response| + // dictionary is pre-filled by ProcessMessage() with the parts of the + // response already known ("id" and "type" fields). + bool ProcessHello(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessGetHostName(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessGetPinHash(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessGenerateKeyPair(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessUpdateDaemonConfig(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessGetDaemonConfig(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessGetUsageStatsConsent(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessStartDaemon(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessStopDaemon(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + bool ProcessGetDaemonState(const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response); + + // Sends a response back to the client app. This can be called on either the + // main message loop or the DaemonController's internal thread, so it + // PostTask()s to the main thread if necessary. + void SendResponse(scoped_ptr<base::DictionaryValue> response); + + // These Send... methods get called on the DaemonController's internal thread + // These methods fill in the |response| dictionary from the other parameters, + // and pass it to SendResponse(). + void SendUpdateConfigResponse(scoped_ptr<base::DictionaryValue> response, + DaemonController::AsyncResult result); + void SendConfigResponse(scoped_ptr<base::DictionaryValue> response, + scoped_ptr<base::DictionaryValue> config); + void SendUsageStatsConsentResponse( + scoped_ptr<base::DictionaryValue> response, + bool supported, + bool allowed, + bool set_by_policy); + void SendAsyncResult(scoped_ptr<base::DictionaryValue> response, + DaemonController::AsyncResult result); + + // Callbacks may be invoked by e.g. DaemonController during destruction, + // which use |weak_ptr_|, so it's important that it be the last member to be + // destroyed. + base::WeakPtr<NativeMessagingHost> weak_ptr_; + + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; + base::Closure quit_closure_; + + NativeMessagingReader native_messaging_reader_; + NativeMessagingWriter native_messaging_writer_; + + // The DaemonController may post tasks to this object during destruction (but + // not afterwards), so it needs to be destroyed before other members of this + // class (except for |weak_factory_|). + scoped_ptr<remoting::DaemonController> daemon_controller_; + + base::WeakPtrFactory<NativeMessagingHost> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NativeMessagingHost); +}; + +NativeMessagingHost::NativeMessagingHost( + base::PlatformFile input, + base::PlatformFile output, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + const base::Closure& quit_closure) + : caller_task_runner_(caller_task_runner), + quit_closure_(quit_closure), + native_messaging_reader_(input), + native_messaging_writer_(output), + daemon_controller_(DaemonController::Create()), + weak_factory_(this) { + weak_ptr_ = weak_factory_.GetWeakPtr(); +} + +NativeMessagingHost::~NativeMessagingHost() { +} + +void NativeMessagingHost::Start() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + native_messaging_reader_.Start( + base::Bind(&NativeMessagingHost::ProcessMessage, weak_ptr_), + base::Bind(&NativeMessagingHost::Shutdown, weak_ptr_)); +} + +void NativeMessagingHost::Shutdown() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + if (!quit_closure_.is_null()) { + caller_task_runner_->PostTask(FROM_HERE, quit_closure_); + quit_closure_.Reset(); + } +} + +void NativeMessagingHost::ProcessMessage(scoped_ptr<base::Value> message) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + const base::DictionaryValue* message_dict; + if (!message->GetAsDictionary(&message_dict)) { + LOG(ERROR) << "Expected DictionaryValue"; + Shutdown(); + } + + scoped_ptr<base::DictionaryValue> response_dict(new base::DictionaryValue()); + + // If the client supplies an ID, it will expect it in the response. This + // might be a string or a number, so cope with both. + const base::Value* id; + if (message_dict->Get("id", &id)) + response_dict->Set("id", id->DeepCopy()); + + std::string type; + if (!message_dict->GetString("type", &type)) { + LOG(ERROR) << "'type' not found"; + Shutdown(); + return; + } + + response_dict->SetString("type", type + "Response"); + + bool success = false; + if (type == "hello") { + success = ProcessHello(*message_dict, response_dict.Pass()); + } else if (type == "getHostName") { + success = ProcessGetHostName(*message_dict, response_dict.Pass()); + } else if (type == "getPinHash") { + success = ProcessGetPinHash(*message_dict, response_dict.Pass()); + } else if (type == "generateKeyPair") { + success = ProcessGenerateKeyPair(*message_dict, response_dict.Pass()); + } else if (type == "updateDaemonConfig") { + success = ProcessUpdateDaemonConfig(*message_dict, response_dict.Pass()); + } else if (type == "getDaemonConfig") { + success = ProcessGetDaemonConfig(*message_dict, response_dict.Pass()); + } else if (type == "getUsageStatsConsent") { + success = ProcessGetUsageStatsConsent(*message_dict, response_dict.Pass()); + } else if (type == "startDaemon") { + success = ProcessStartDaemon(*message_dict, response_dict.Pass()); + } else if (type == "stopDaemon") { + success = ProcessStopDaemon(*message_dict, response_dict.Pass()); + } else if (type == "getDaemonState") { + success = ProcessGetDaemonState(*message_dict, response_dict.Pass()); + } else { + LOG(ERROR) << "Unsupported request type: " << type; + } + + if (!success) + Shutdown(); +} + +bool NativeMessagingHost::ProcessHello( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + response->SetString("version", STRINGIZE(VERSION)); + SendResponse(response.Pass()); + return true; +} + +bool NativeMessagingHost::ProcessGetHostName( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + response->SetString("hostname", net::GetHostName()); + SendResponse(response.Pass()); + return true; +} + +bool NativeMessagingHost::ProcessGetPinHash( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + std::string host_id; + if (!message.GetString("hostId", &host_id)) { + LOG(ERROR) << "'hostId' not found: " << message; + return false; + } + std::string pin; + if (!message.GetString("pin", &pin)) { + LOG(ERROR) << "'pin' not found: " << message; + return false; + } + response->SetString("hash", remoting::MakeHostPinHash(host_id, pin)); + SendResponse(response.Pass()); + return true; +} + +bool NativeMessagingHost::ProcessGenerateKeyPair( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + scoped_refptr<RsaKeyPair> key_pair = RsaKeyPair::Generate(); + response->SetString("private_key", key_pair->ToString()); + response->SetString("public_key", key_pair->GetPublicKey()); + SendResponse(response.Pass()); + return true; +} + +bool NativeMessagingHost::ProcessUpdateDaemonConfig( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + scoped_ptr<base::DictionaryValue> config_dict = + ConfigDictionaryFromMessage(message); + if (!config_dict) + return false; + + // base::Unretained() is safe because this object owns |daemon_controller_| + // which owns the thread that will run the callback. + daemon_controller_->UpdateConfig( + config_dict.Pass(), + base::Bind(&NativeMessagingHost::SendAsyncResult, base::Unretained(this), + base::Passed(&response))); + return true; +} + +bool NativeMessagingHost::ProcessGetDaemonConfig( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + daemon_controller_->GetConfig(base::Bind( + &NativeMessagingHost::SendConfigResponse, + base::Unretained(this), base::Passed(&response))); + return true; +} + +bool NativeMessagingHost::ProcessGetUsageStatsConsent( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + daemon_controller_->GetUsageStatsConsent(base::Bind( + &NativeMessagingHost::SendUsageStatsConsentResponse, + base::Unretained(this), base::Passed(&response))); + return true; +} + +bool NativeMessagingHost::ProcessStartDaemon( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + bool consent; + if (!message.GetBoolean("consent", &consent)) { + LOG(ERROR) << "'consent' not found."; + return false; + } + + scoped_ptr<base::DictionaryValue> config_dict = + ConfigDictionaryFromMessage(message); + if (!config_dict) + return false; + + daemon_controller_->SetConfigAndStart( + config_dict.Pass(), consent, + base::Bind(&NativeMessagingHost::SendAsyncResult, base::Unretained(this), + base::Passed(&response))); + return true; +} + +bool NativeMessagingHost::ProcessStopDaemon( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + daemon_controller_->Stop(base::Bind( + &NativeMessagingHost::SendAsyncResult, base::Unretained(this), + base::Passed(&response))); + return true; +} + +bool NativeMessagingHost::ProcessGetDaemonState( + const base::DictionaryValue& message, + scoped_ptr<base::DictionaryValue> response) { + // TODO(lambroslambrou): Send the state as a string instead of an integer, + // and update the web-app accordingly. + DaemonController::State state = daemon_controller_->GetState(); + response->SetInteger("state", state); + SendResponse(response.Pass()); + return true; +} + +void NativeMessagingHost::SendResponse( + scoped_ptr<base::DictionaryValue> response) { + if (!caller_task_runner_->BelongsToCurrentThread()) { + caller_task_runner_->PostTask(FROM_HERE, base::Bind( + &NativeMessagingHost::SendResponse, weak_ptr_, + base::Passed(&response))); + return; + } + + if (!native_messaging_writer_.WriteMessage(*response)) + Shutdown(); +} + +void NativeMessagingHost::SendConfigResponse( + scoped_ptr<base::DictionaryValue> response, + scoped_ptr<base::DictionaryValue> config) { + // TODO(lambroslambrou): Fix the web-app to accept the config dictionary + // directly embedded in the response, rather than as serialized JSON. See + // http://crbug.com/232135. + std::string config_json; + base::JSONWriter::Write(config.get(), &config_json); + response->SetString("config", config_json); + SendResponse(response.Pass()); +} + +void NativeMessagingHost::SendUsageStatsConsentResponse( + scoped_ptr<base::DictionaryValue> response, + bool supported, + bool allowed, + bool set_by_policy) { + response->SetBoolean("supported", supported); + response->SetBoolean("allowed", allowed); + response->SetBoolean("set_by_policy", set_by_policy); + SendResponse(response.Pass()); +} + +void NativeMessagingHost::SendAsyncResult( + scoped_ptr<base::DictionaryValue> response, + DaemonController::AsyncResult result) { + // TODO(lambroslambrou): Send the result as a string instead of an integer, + // and update the web-app accordingly. See http://crbug.com/232135. + response->SetInteger("result", result); + SendResponse(response.Pass()); +} + +} // namespace remoting + +int main(int argc, char** argv) { + // This object instance is required by Chrome code (such as MessageLoop). + base::AtExitManager exit_manager; + + CommandLine::Init(argc, argv); + remoting::InitHostLogging(); + +#if defined(OS_WIN) + base::PlatformFile read_file = GetStdHandle(STD_INPUT_HANDLE); + base::PlatformFile write_file = GetStdHandle(STD_OUTPUT_HANDLE); +#elif defined(OS_POSIX) + base::PlatformFile read_file = STDIN_FILENO; + base::PlatformFile write_file = STDOUT_FILENO; +#else +#error Not implemented. +#endif + + base::MessageLoop message_loop(base::MessageLoop::TYPE_IO); + base::RunLoop run_loop; + remoting::NativeMessagingHost host(read_file, write_file, + message_loop.message_loop_proxy(), + run_loop.QuitClosure()); + host.Start(); + run_loop.Run(); + return 0; +} diff --git a/remoting/host/setup/native_messaging_reader.cc b/remoting/host/setup/native_messaging_reader.cc new file mode 100644 index 0000000..e98ac52 --- /dev/null +++ b/remoting/host/setup/native_messaging_reader.cc @@ -0,0 +1,164 @@ +// Copyright 2013 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/setup/native_messaging_reader.h" + +#include <string> + +#include "base/bind.h" +#include "base/json/json_reader.h" +#include "base/location.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/values.h" +#include "net/base/file_stream.h" + +namespace { + +// uint32 is specified in the protocol as the type for the message header. +typedef uint32 MessageLengthType; + +const int kMessageHeaderSize = sizeof(MessageLengthType); + +// Limit the size of received messages, to avoid excessive memory-allocation in +// this process, and potential overflow issues when casting to a signed 32-bit +// int. +const MessageLengthType kMaximumMessageSize = 1024 * 1024; + +} // namespace + +namespace remoting { + +class NativeMessagingReader::Core { + public: + Core(base::PlatformFile handle, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SequencedTaskRunner> read_task_runner, + base::WeakPtr<NativeMessagingReader> reader_); + ~Core(); + + // Reads a message from the Native Messaging client and passes it to + // |message_callback_| on the originating thread. Called on the reader thread. + void ReadMessage(); + + private: + // Notify the reader's EOF callback when an error occurs or EOF is reached. + void NotifyEof(); + + net::FileStream read_stream_; + + base::WeakPtr<NativeMessagingReader> reader_; + + // Used to post the caller-supplied reader callbacks on the caller thread. + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; + + // Used to DCHECK that the reader code executes on the correct thread. + scoped_refptr<base::SequencedTaskRunner> read_task_runner_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +NativeMessagingReader::Core::Core( + base::PlatformFile handle, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SequencedTaskRunner> read_task_runner, + base::WeakPtr<NativeMessagingReader> reader) + : read_stream_(handle, base::PLATFORM_FILE_READ, NULL), + reader_(reader), + caller_task_runner_(caller_task_runner), + read_task_runner_(read_task_runner) { +} + +NativeMessagingReader::Core::~Core() { +} + +void NativeMessagingReader::Core::ReadMessage() { + DCHECK(read_task_runner_->RunsTasksOnCurrentThread()); + + // Keep reading messages until the stream is closed or an error occurs. + while (true) { + MessageLengthType message_length; + int read_result = read_stream_.ReadUntilComplete( + reinterpret_cast<char*>(&message_length), kMessageHeaderSize); + if (read_result != kMessageHeaderSize) { + LOG(ERROR) << "Failed to read message header, read returned " + << read_result; + NotifyEof(); + return; + } + + if (message_length > kMaximumMessageSize) { + LOG(ERROR) << "Message size too large: " << message_length; + NotifyEof(); + return; + } + + std::string message_json(message_length, '\0'); + read_result = read_stream_.ReadUntilComplete( + string_as_array(&message_json), message_length); + if (read_result != static_cast<int>(message_length)) { + LOG(ERROR) << "Failed to read message body, read returned " + << read_result; + NotifyEof(); + return; + } + + scoped_ptr<base::Value> message(base::JSONReader::Read(message_json)); + if (!message) { + LOG(ERROR) << "Failed to parse JSON message: " << message; + NotifyEof(); + return; + } + + // Notify callback of new message. + caller_task_runner_->PostTask(FROM_HERE, base::Bind( + &NativeMessagingReader::InvokeMessageCallback, reader_, + base::Passed(&message))); + } +} + +void NativeMessagingReader::Core::NotifyEof() { + DCHECK(read_task_runner_->RunsTasksOnCurrentThread()); + caller_task_runner_->PostTask(FROM_HERE, base::Bind( + &NativeMessagingReader::InvokeEofCallback, reader_)); +} + +NativeMessagingReader::NativeMessagingReader(base::PlatformFile handle) + : reader_thread_("Reader"), + weak_factory_(this) { + reader_thread_.Start(); + read_task_runner_ = reader_thread_.message_loop_proxy(); + core_.reset(new Core(handle, base::ThreadTaskRunnerHandle::Get(), + read_task_runner_, weak_factory_.GetWeakPtr())); +} + +NativeMessagingReader::~NativeMessagingReader() { + read_task_runner_->DeleteSoon(FROM_HERE, core_.release()); +} + +void NativeMessagingReader::Start(MessageCallback message_callback, + base::Closure eof_callback) { + message_callback_ = message_callback; + eof_callback_ = eof_callback; + + // base::Unretained is safe since |core_| is only deleted via the + // DeleteSoon task which is posted from this class's dtor. + read_task_runner_->PostTask(FROM_HERE, base::Bind( + &NativeMessagingReader::Core::ReadMessage, + base::Unretained(core_.get()))); +} + +void NativeMessagingReader::InvokeMessageCallback( + scoped_ptr<base::Value> message) { + message_callback_.Run(message.Pass()); +} + +void NativeMessagingReader::InvokeEofCallback() { + eof_callback_.Run(); +} + +} // namespace remoting diff --git a/remoting/host/setup/native_messaging_reader.h b/remoting/host/setup/native_messaging_reader.h new file mode 100644 index 0000000..b9c81c3 --- /dev/null +++ b/remoting/host/setup/native_messaging_reader.h @@ -0,0 +1,76 @@ +// Copyright 2013 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. + +#ifndef REMOTING_HOST_SETUP_NATIVE_MESSAGING_READER_H_ +#define REMOTING_HOST_SETUP_NATIVE_MESSAGING_READER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "base/threading/thread.h" + +namespace base { +class SequencedTaskRunner; +class Value; +} // namespace base + +namespace net { +class FileStream; +} // namespace net + +namespace remoting { + +// This class is used for reading messages from the Native Messaging client +// webapp. +class NativeMessagingReader { + public: + typedef base::Callback<void(scoped_ptr<base::Value>)> MessageCallback; + + explicit NativeMessagingReader(base::PlatformFile handle); + ~NativeMessagingReader(); + + // Begin reading messages from the Native Messaging client webapp, calling + // |message_callback| for each received message, or |eof_callback| if + // EOF or error is encountered. This method is asynchronous - the callbacks + // will be run on the same thread via PostTask. The caller should be prepared + // for these callbacks to be invoked right up until this object is destroyed. + void Start(MessageCallback message_callback, base::Closure eof_callback); + + private: + class Core; + friend class Core; + + // Wrappers posted to by the read thread to trigger the message and EOF + // callbacks on the caller thread, and have them safely dropped if the reader + // has been deleted before they are processed. + void InvokeMessageCallback(scoped_ptr<base::Value> message); + void InvokeEofCallback(); + + // Holds the information that the read thread needs to access, such as the + // FileStream, and the TaskRunner used for posting notifications back to this + // class. + scoped_ptr<Core> core_; + + // Caller-supplied message and end-of-file callbacks. + MessageCallback message_callback_; + base::Closure eof_callback_; + + // Separate thread used to read from the stream without blocking the main + // thread. net::FileStream's async API cannot be used here because, on + // Windows, it requires the file handle to have been opened for overlapped IO. + base::Thread reader_thread_; + scoped_refptr<base::SequencedTaskRunner> read_task_runner_; + + // Allows the reader to be deleted safely even when tasks may be pending on + // it. + base::WeakPtrFactory<NativeMessagingReader> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NativeMessagingReader); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SETUP_NATIVE_MESSAGING_READER_H_ diff --git a/remoting/host/setup/native_messaging_writer.cc b/remoting/host/setup/native_messaging_writer.cc new file mode 100644 index 0000000..15d6cd7 --- /dev/null +++ b/remoting/host/setup/native_messaging_writer.cc @@ -0,0 +1,103 @@ +// Copyright 2013 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/setup/native_messaging_writer.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/json/json_writer.h" + +namespace { + +// 4-byte type used for the message header. +typedef uint32 MessageLengthType; + +// Defined as an int, for passing to APIs that take an int, to avoid +// signed/unsigned warnings about implicit cast. +const int kMessageHeaderSize = sizeof(MessageLengthType); + +// Limit the size of sent messages, since Chrome will not accept messages +// larger than 1MB, and this helps deal with the problem of integer overflow +// when passing sizes to net::FileStream APIs that take |int| parameters. +// This is defined as size_t (unsigned type) so it can be compared with the +// result of std::string::length() without compiler warnings. +const size_t kMaximumMessageSize = 1024 * 1024; + +// Performs the same task as FileStream::WriteSync(), but ensures that exactly +// |buffer_length| bytes are written. Unlike WriteSync(), a partial write may +// only occur as a result of end-of-file or fatal error. Returns the number of +// bytes written (buffer_length) or an error-code <= 0. +// +// TODO(lambroslambrou): Add this method to net::FileStream, with unit-tests. +// See http://crbug.com/232202. +int WriteUntilComplete(net::FileStream* out, + const char* buffer, int buffer_length) { + int written = 0; + while (written < buffer_length) { + int result = out->WriteSync(buffer + written, buffer_length - written); + if (result <= 0) { + return result; + } + DCHECK_LE(result, buffer_length - written); + written += result; + } + return written; +} + +} // namespace + +namespace remoting { + +NativeMessagingWriter::NativeMessagingWriter(base::PlatformFile handle) + : write_stream_(handle, base::PLATFORM_FILE_WRITE, NULL), + fail_(false) { +} + +NativeMessagingWriter::~NativeMessagingWriter() { +} + +bool NativeMessagingWriter::WriteMessage(const base::Value& message) { + if (fail_) { + LOG(ERROR) << "Stream marked as corrupt."; + return false; + } + + std::string message_json; + base::JSONWriter::Write(&message, &message_json); + + CHECK_LE(message_json.length(), kMaximumMessageSize); + + // Cast from size_t to the proper header type. The check above ensures this + // won't overflow. + MessageLengthType message_length = + static_cast<MessageLengthType>(message_json.length()); + + int result = WriteUntilComplete( + &write_stream_, reinterpret_cast<char*>(&message_length), + kMessageHeaderSize); + if (result != kMessageHeaderSize) { + LOG(ERROR) << "Failed to send message header, write returned " << result; + fail_ = true; + return false; + } + + // The length check above ensures that the cast won't overflow a signed + // 32-bit int. + int message_length_as_int = message_length; + + // CHECK needed since data() is undefined on an empty std::string. + CHECK(!message_json.empty()); + result = WriteUntilComplete(&write_stream_, message_json.data(), + message_length_as_int); + if (result != message_length_as_int) { + LOG(ERROR) << "Failed to send message body, write returned " << result; + fail_ = true; + return false; + } + + return true; +} + +} // namespace remoting diff --git a/remoting/host/setup/native_messaging_writer.h b/remoting/host/setup/native_messaging_writer.h new file mode 100644 index 0000000..72e5345 --- /dev/null +++ b/remoting/host/setup/native_messaging_writer.h @@ -0,0 +1,37 @@ +// Copyright 2013 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. + +#ifndef REMOTING_HOST_SETUP_NATIVE_MESSAGING_WRITER_H_ +#define REMOTING_HOST_SETUP_NATIVE_MESSAGING_WRITER_H_ + +#include "base/platform_file.h" +#include "net/base/file_stream.h" + +namespace base { +class Value; +} // namespace base + +namespace remoting { + +// This class is used for sending messages to the Native Messaging client +// webapp. +class NativeMessagingWriter { + public: + explicit NativeMessagingWriter(base::PlatformFile handle); + ~NativeMessagingWriter(); + + // Sends a message to the Native Messaging client, returning true if + // successful. + bool WriteMessage(const base::Value& message); + + private: + net::FileStream write_stream_; + bool fail_; + + DISALLOW_COPY_AND_ASSIGN(NativeMessagingWriter); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_SETUP_NATIVE_MESSAGING_WRITER_H_ diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index b9fa5b7..791f1e6 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -711,6 +711,27 @@ ], }, # end of target 'remoting_host_plugin' + { + 'target_name': 'remoting_native_messaging_host', + 'type': 'executable', + 'variables': { 'enable_wexit_time_destructors': 1, }, + 'dependencies': [ + '../base/base.gyp:base', + 'remoting_host', + 'remoting_host_logging', + 'remoting_host_setup_base', + ], + 'defines': [ + 'VERSION=<(version_full)', + ], + 'sources': [ + 'host/setup/native_messaging_host.cc', + 'host/setup/native_messaging_reader.cc', + 'host/setup/native_messaging_reader.h', + 'host/setup/native_messaging_writer.cc', + 'host/setup/native_messaging_writer.h', + ], + }, # end of target 'remoting_native_messaging_host' ], # end of 'targets' }], # 'enable_remoting_host==1' |