Index: remoting/host/host_script_object.cc |
diff --git a/remoting/host/host_script_object.cc b/remoting/host/host_script_object.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..dab16d1a0a5698387904fce1d78f70f8d2412939 |
--- /dev/null |
+++ b/remoting/host/host_script_object.cc |
@@ -0,0 +1,523 @@ |
+// 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/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/host_plugin_utils.h" |
+#include "remoting/host/in_memory_host_config.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) { |
+ OnStateChanged(kConnected); |
+} |
+ |
+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::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 |