diff options
Diffstat (limited to 'remoting/host/plugin/host_script_object.cc')
-rw-r--r-- | remoting/host/plugin/host_script_object.cc | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/remoting/host/plugin/host_script_object.cc b/remoting/host/plugin/host_script_object.cc new file mode 100644 index 0000000..9686492 --- /dev/null +++ b/remoting/host/plugin/host_script_object.cc @@ -0,0 +1,527 @@ +// Copyright (c) 2011 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/plugin/host_script_object.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "base/threading/platform_thread.h" +#include "remoting/base/auth_token_util.h" +#include "remoting/host/chromoting_host.h" +#include "remoting/host/chromoting_host_context.h" +#include "remoting/host/host_config.h" +#include "remoting/host/host_key_pair.h" +#include "remoting/host/in_memory_host_config.h" +#include "remoting/host/plugin/host_plugin_utils.h" +#include "remoting/host/register_support_host_request.h" +#include "remoting/host/support_access_verifier.h" + +namespace remoting { + +// Supported Javascript interface: +// readonly attribute string accessCode; +// readonly attribute int state; +// +// state: { +// DISCONNECTED, +// REQUESTED_ACCESS_CODE, +// RECEIVED_ACCESS_CODE, +// CONNECTED, +// AFFIRMING_CONNECTION, +// ERROR, +// } +// +// attribute Function void logDebugInfo(string); +// attribute Function void onStateChanged(); +// +// // The |auth_service_with_token| parameter should be in the format +// // "auth_service:auth_token". An example would be "oauth2:1/2a3912vd". +// void connect(string uid, string auth_service_with_token); +// void disconnect(); + +namespace { + +const char* kAttrNameAccessCode = "accessCode"; +const char* kAttrNameState = "state"; +const char* kAttrNameLogDebugInfo = "logDebugInfo"; +const char* kAttrNameOnStateChanged = "onStateChanged"; +const char* kFuncNameConnect = "connect"; +const char* kFuncNameDisconnect = "disconnect"; + +// States. +const char* kAttrNameDisconnected = "DISCONNECTED"; +const char* kAttrNameRequestedAccessCode = "REQUESTED_ACCESS_CODE"; +const char* kAttrNameReceivedAccessCode = "RECEIVED_ACCESS_CODE"; +const char* kAttrNameConnected = "CONNECTED"; +const char* kAttrNameAffirmingConnection = "AFFIRMING_CONNECTION"; +const char* kAttrNameError = "ERROR"; + +const int kMaxLoginAttempts = 5; + +} // namespace + +HostNPScriptObject::HostNPScriptObject(NPP plugin, NPObject* parent) + : plugin_(plugin), + parent_(parent), + state_(kDisconnected), + log_debug_info_func_(NULL), + on_state_changed_func_(NULL), + np_thread_id_(base::PlatformThread::CurrentId()), + failed_login_attempts_(0), + disconnected_event_(true, false) { + VLOG(2) << "HostNPScriptObject"; + host_context_.SetUITaskPostFunction(base::Bind( + &HostNPScriptObject::PostTaskToNPThread, base::Unretained(this))); +} + +HostNPScriptObject::~HostNPScriptObject() { + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + + // Disconnect synchronously. We cannot disconnect asynchronously + // here because |host_context_| needs to be stopped on the plugin + // thread, but the plugin thread may not exist after the instance + // is destroyed. + destructing_.Set(); + disconnected_event_.Reset(); + DisconnectInternal(); + disconnected_event_.Wait(); + + host_context_.Stop(); + if (log_debug_info_func_) { + g_npnetscape_funcs->releaseobject(log_debug_info_func_); + } + if (on_state_changed_func_) { + g_npnetscape_funcs->releaseobject(on_state_changed_func_); + } +} + +bool HostNPScriptObject::Init() { + VLOG(2) << "Init"; + // TODO(wez): This starts a bunch of threads, which might fail. + host_context_.Start(); + return true; +} + +bool HostNPScriptObject::HasMethod(const std::string& method_name) { + VLOG(2) << "HasMethod " << method_name; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + return (method_name == kFuncNameConnect || + method_name == kFuncNameDisconnect); +} + +bool HostNPScriptObject::InvokeDefault(const NPVariant* args, + uint32_t argCount, + NPVariant* result) { + VLOG(2) << "InvokeDefault"; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + SetException("exception during default invocation"); + return false; +} + +bool HostNPScriptObject::Invoke(const std::string& method_name, + const NPVariant* args, + uint32_t argCount, + NPVariant* result) { + VLOG(2) << "Invoke " << method_name; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + if (method_name == kFuncNameConnect) { + return Connect(args, argCount, result); + } else if (method_name == kFuncNameDisconnect) { + return Disconnect(args, argCount, result); + } else { + SetException("Invoke: unknown method " + method_name); + return false; + } +} + +bool HostNPScriptObject::HasProperty(const std::string& property_name) { + VLOG(2) << "HasProperty " << property_name; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + return (property_name == kAttrNameAccessCode || + property_name == kAttrNameState || + property_name == kAttrNameLogDebugInfo || + property_name == kAttrNameOnStateChanged || + property_name == kAttrNameDisconnected || + property_name == kAttrNameRequestedAccessCode || + property_name == kAttrNameReceivedAccessCode || + property_name == kAttrNameConnected || + property_name == kAttrNameAffirmingConnection || + property_name == kAttrNameError); +} + +bool HostNPScriptObject::GetProperty(const std::string& property_name, + NPVariant* result) { + VLOG(2) << "GetProperty " << property_name; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + if (!result) { + SetException("GetProperty: NULL result"); + return false; + } + + if (property_name == kAttrNameOnStateChanged) { + OBJECT_TO_NPVARIANT(on_state_changed_func_, *result); + return true; + } else if (property_name == kAttrNameLogDebugInfo) { + OBJECT_TO_NPVARIANT(log_debug_info_func_, *result); + return true; + } else if (property_name == kAttrNameState) { + INT32_TO_NPVARIANT(state_, *result); + return true; + } else if (property_name == kAttrNameAccessCode) { + *result = NPVariantFromString(access_code_); + return true; + } else if (property_name == kAttrNameDisconnected) { + INT32_TO_NPVARIANT(kDisconnected, *result); + return true; + } else if (property_name == kAttrNameRequestedAccessCode) { + INT32_TO_NPVARIANT(kRequestedAccessCode, *result); + return true; + } else if (property_name == kAttrNameReceivedAccessCode) { + INT32_TO_NPVARIANT(kReceivedAccessCode, *result); + return true; + } else if (property_name == kAttrNameConnected) { + INT32_TO_NPVARIANT(kConnected, *result); + return true; + } else if (property_name == kAttrNameAffirmingConnection) { + INT32_TO_NPVARIANT(kAffirmingConnection, *result); + return true; + } else if (property_name == kAttrNameError) { + INT32_TO_NPVARIANT(kError, *result); + return true; + } else { + SetException("GetProperty: unsupported property " + property_name); + return false; + } +} + +bool HostNPScriptObject::SetProperty(const std::string& property_name, + const NPVariant* value) { + VLOG(2) << "SetProperty " << property_name; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + + if (property_name == kAttrNameOnStateChanged) { + if (NPVARIANT_IS_OBJECT(*value)) { + if (on_state_changed_func_) { + g_npnetscape_funcs->releaseobject(on_state_changed_func_); + } + on_state_changed_func_ = NPVARIANT_TO_OBJECT(*value); + if (on_state_changed_func_) { + g_npnetscape_funcs->retainobject(on_state_changed_func_); + } + return true; + } else { + SetException("SetProperty: unexpected type for property " + + property_name); + } + return false; + } + + if (property_name == kAttrNameLogDebugInfo) { + if (NPVARIANT_IS_OBJECT(*value)) { + if (log_debug_info_func_) { + g_npnetscape_funcs->releaseobject(log_debug_info_func_); + } + log_debug_info_func_ = NPVARIANT_TO_OBJECT(*value); + if (log_debug_info_func_) { + g_npnetscape_funcs->retainobject(log_debug_info_func_); + } + return true; + } else { + SetException("SetProperty: unexpected type for property " + + property_name); + } + return false; + } + + return false; +} + +bool HostNPScriptObject::RemoveProperty(const std::string& property_name) { + VLOG(2) << "RemoveProperty " << property_name; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + return false; +} + +bool HostNPScriptObject::Enumerate(std::vector<std::string>* values) { + VLOG(2) << "Enumerate"; + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + const char* entries[] = { + kAttrNameAccessCode, + kAttrNameState, + kAttrNameLogDebugInfo, + kAttrNameOnStateChanged, + kFuncNameConnect, + kFuncNameDisconnect, + kAttrNameDisconnected, + kAttrNameRequestedAccessCode, + kAttrNameReceivedAccessCode, + kAttrNameConnected, + kAttrNameAffirmingConnection, + kAttrNameError + }; + for (size_t i = 0; i < arraysize(entries); ++i) { + values->push_back(entries[i]); + } + return true; +} + +void HostNPScriptObject::OnSignallingConnected(SignalStrategy* signal_strategy, + const std::string& full_jid) { +} + +void HostNPScriptObject::OnSignallingDisconnected() { +} + +void HostNPScriptObject::OnAccessDenied() { + DCHECK_EQ(MessageLoop::current(), host_context_.network_message_loop()); + + ++failed_login_attempts_; + if (failed_login_attempts_ == kMaxLoginAttempts) + DisconnectInternal(); +} + +void HostNPScriptObject::OnAuthenticatedClientsChanged(int clients_connected) { + DCHECK_NE(base::PlatformThread::CurrentId(), np_thread_id_); + OnStateChanged(clients_connected ? kConnected : kDisconnected); +} + +void HostNPScriptObject::OnShutdown() { + DCHECK_EQ(MessageLoop::current(), host_context_.main_message_loop()); + + OnStateChanged(kDisconnected); +} + +// string uid, string auth_token +bool HostNPScriptObject::Connect(const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + + LogDebugInfo("Connecting..."); + + if (arg_count != 2) { + SetException("connect: bad number of arguments"); + return false; + } + + std::string uid = StringFromNPVariant(args[0]); + if (uid.empty()) { + SetException("connect: bad uid argument"); + return false; + } + + std::string auth_service_with_token = StringFromNPVariant(args[1]); + std::string auth_token; + std::string auth_service; + ParseAuthTokenWithService(auth_service_with_token, &auth_token, + &auth_service); + if (auth_token.empty()) { + SetException("connect: auth_service_with_token argument has empty token"); + return false; + } + + ConnectInternal(uid, auth_token, auth_service); + + return true; +} + +void HostNPScriptObject::ConnectInternal( + const std::string& uid, + const std::string& auth_token, + const std::string& auth_service) { + if (MessageLoop::current() != host_context_.main_message_loop()) { + host_context_.main_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &HostNPScriptObject::ConnectInternal, + uid, auth_token, auth_service)); + return; + } + // Store the supplied user ID and token to the Host configuration. + scoped_refptr<MutableHostConfig> host_config = new InMemoryHostConfig(); + host_config->SetString(kXmppLoginConfigPath, uid); + host_config->SetString(kXmppAuthTokenConfigPath, auth_token); + host_config->SetString(kXmppAuthServiceConfigPath, auth_service); + + // Create an access verifier and fetch the host secret. + scoped_ptr<SupportAccessVerifier> access_verifier; + access_verifier.reset(new SupportAccessVerifier()); + + // Generate a key pair for the Host to use. + // TODO(wez): Move this to the worker thread. + HostKeyPair host_key_pair; + host_key_pair.Generate(); + host_key_pair.Save(host_config); + + // Request registration of the host for support. + scoped_ptr<RegisterSupportHostRequest> register_request( + new RegisterSupportHostRequest()); + if (!register_request->Init( + host_config.get(), + base::Bind(&HostNPScriptObject::OnReceivedSupportID, + base::Unretained(this), + access_verifier.get()))) { + OnStateChanged(kDisconnected); + return; + } + + // Create the Host. + scoped_refptr<ChromotingHost> host = + ChromotingHost::Create(&host_context_, host_config, + access_verifier.release()); + host->AddStatusObserver(this); + host->AddStatusObserver(register_request.get()); + host->set_it2me(true); + + // Nothing went wrong, so lets save the host, config and request. + host_ = host; + host_config_ = host_config; + register_request_.reset(register_request.release()); + + // Start the Host. + host_->Start(); + + OnStateChanged(kRequestedAccessCode); + return; +} + +bool HostNPScriptObject::Disconnect(const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + if (arg_count != 0) { + SetException("disconnect: bad number of arguments"); + return false; + } + + DisconnectInternal(); + + return true; +} + +void HostNPScriptObject::DisconnectInternal() { + if (MessageLoop::current() != host_context_.main_message_loop()) { + host_context_.main_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &HostNPScriptObject::DisconnectInternal)); + return; + } + + if (!host_) { + disconnected_event_.Signal(); + return; + } + + host_->Shutdown( + NewRunnableMethod(this, &HostNPScriptObject::OnShutdownFinished)); +} + +void HostNPScriptObject::OnShutdownFinished() { + DCHECK_EQ(MessageLoop::current(), host_context_.main_message_loop()); + + host_ = NULL; + register_request_.reset(); + host_config_ = NULL; + disconnected_event_.Signal(); +} + +void HostNPScriptObject::OnReceivedSupportID( + SupportAccessVerifier* access_verifier, + bool success, + const std::string& support_id) { + CHECK_NE(base::PlatformThread::CurrentId(), np_thread_id_); + + if (!success) { + // TODO(wez): Replace the success/fail flag with full error reporting. + DisconnectInternal(); + return; + } + + // Inform the AccessVerifier of our Support-Id, for authentication. + access_verifier->OnIT2MeHostRegistered(success, support_id); + + // Combine the Support Id with the Host Id to make the Access Code. + // TODO(wez): Locking, anyone? + access_code_ = support_id + "-" + access_verifier->host_secret(); + + // Let the caller know that life is good. + OnStateChanged(kReceivedAccessCode); +} + +void HostNPScriptObject::OnStateChanged(State state) { + if (destructing_.IsSet()) { + return; + } + + if (!host_context_.IsUIThread()) { + host_context_.PostToUIThread( + FROM_HERE, + NewRunnableMethod(this, &HostNPScriptObject::OnStateChanged, state)); + return; + } + state_ = state; + if (on_state_changed_func_) { + VLOG(2) << "Calling state changed " << state; + bool is_good = CallJSFunction(on_state_changed_func_, NULL, 0, NULL); + LOG_IF(ERROR, !is_good) << "OnStateChanged failed"; + } +} + +void HostNPScriptObject::LogDebugInfo(const std::string& message) { + if (!host_context_.IsUIThread()) { + host_context_.PostToUIThread( + FROM_HERE, + NewRunnableMethod(this, &HostNPScriptObject::LogDebugInfo, message)); + return; + } + if (log_debug_info_func_) { + NPVariant* arg = new NPVariant(); + LOG(INFO) << "Logging: " << message; + STRINGZ_TO_NPVARIANT(message.c_str(), *arg); + bool is_good = CallJSFunction(log_debug_info_func_, arg, 1, NULL); + LOG_IF(ERROR, !is_good) << "LogDebugInfo failed"; + } +} + +void HostNPScriptObject::SetException(const std::string& exception_string) { + CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); + g_npnetscape_funcs->setexception(parent_, exception_string.c_str()); + LogDebugInfo(exception_string); +} + +bool HostNPScriptObject::CallJSFunction(NPObject* func, + const NPVariant* args, + uint32_t argCount, + NPVariant* result) { + NPVariant np_result; + bool is_good = func->_class->invokeDefault(func, args, argCount, &np_result); + if (is_good) { + if (result) { + *result = np_result; + } else { + g_npnetscape_funcs->releasevariantvalue(&np_result); + } + } + return is_good; +} + +void HostNPScriptObject::PostTaskToNPThread( + const tracked_objects::Location& from_here, Task* task) { + // The NPAPI functions cannot make use of |from_here|, but this method is + // passed as a callback to ChromotingHostContext, so it needs to have the + // appropriate signature. + + // Can be called from any thread. + g_npnetscape_funcs->pluginthreadasynccall(plugin_, + &NPTaskSpringboard, + task); +} + +void HostNPScriptObject::NPTaskSpringboard(void* task) { + Task* real_task = reinterpret_cast<Task*>(task); + real_task->Run(); + delete real_task; +} + +} // namespace remoting |