OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "remoting/host/host_script_object.h" |
| 6 |
| 7 #include "base/bind.h" |
| 8 #include "base/message_loop.h" |
| 9 #include "base/task.h" |
| 10 #include "base/threading/platform_thread.h" |
| 11 #include "remoting/base/auth_token_util.h" |
| 12 #include "remoting/host/chromoting_host.h" |
| 13 #include "remoting/host/chromoting_host_context.h" |
| 14 #include "remoting/host/host_config.h" |
| 15 #include "remoting/host/host_key_pair.h" |
| 16 #include "remoting/host/host_plugin_utils.h" |
| 17 #include "remoting/host/in_memory_host_config.h" |
| 18 #include "remoting/host/register_support_host_request.h" |
| 19 #include "remoting/host/support_access_verifier.h" |
| 20 |
| 21 namespace remoting { |
| 22 |
| 23 // Supported Javascript interface: |
| 24 // readonly attribute string accessCode; |
| 25 // readonly attribute int state; |
| 26 // |
| 27 // state: { |
| 28 // DISCONNECTED, |
| 29 // REQUESTED_ACCESS_CODE, |
| 30 // RECEIVED_ACCESS_CODE, |
| 31 // CONNECTED, |
| 32 // AFFIRMING_CONNECTION, |
| 33 // ERROR, |
| 34 // } |
| 35 // |
| 36 // attribute Function void logDebugInfo(string); |
| 37 // attribute Function void onStateChanged(); |
| 38 // |
| 39 // // The |auth_service_with_token| parameter should be in the format |
| 40 // // "auth_service:auth_token". An example would be "oauth2:1/2a3912vd". |
| 41 // void connect(string uid, string auth_service_with_token); |
| 42 // void disconnect(); |
| 43 |
| 44 namespace { |
| 45 |
| 46 const char* kAttrNameAccessCode = "accessCode"; |
| 47 const char* kAttrNameState = "state"; |
| 48 const char* kAttrNameLogDebugInfo = "logDebugInfo"; |
| 49 const char* kAttrNameOnStateChanged = "onStateChanged"; |
| 50 const char* kFuncNameConnect = "connect"; |
| 51 const char* kFuncNameDisconnect = "disconnect"; |
| 52 |
| 53 // States. |
| 54 const char* kAttrNameDisconnected = "DISCONNECTED"; |
| 55 const char* kAttrNameRequestedAccessCode = "REQUESTED_ACCESS_CODE"; |
| 56 const char* kAttrNameReceivedAccessCode = "RECEIVED_ACCESS_CODE"; |
| 57 const char* kAttrNameConnected = "CONNECTED"; |
| 58 const char* kAttrNameAffirmingConnection = "AFFIRMING_CONNECTION"; |
| 59 const char* kAttrNameError = "ERROR"; |
| 60 |
| 61 const int kMaxLoginAttempts = 5; |
| 62 |
| 63 } // namespace |
| 64 |
| 65 HostNPScriptObject::HostNPScriptObject(NPP plugin, NPObject* parent) |
| 66 : plugin_(plugin), |
| 67 parent_(parent), |
| 68 state_(kDisconnected), |
| 69 log_debug_info_func_(NULL), |
| 70 on_state_changed_func_(NULL), |
| 71 np_thread_id_(base::PlatformThread::CurrentId()), |
| 72 failed_login_attempts_(0), |
| 73 disconnected_event_(true, false) { |
| 74 VLOG(2) << "HostNPScriptObject"; |
| 75 host_context_.SetUITaskPostFunction(base::Bind( |
| 76 &HostNPScriptObject::PostTaskToNPThread, base::Unretained(this))); |
| 77 } |
| 78 |
| 79 HostNPScriptObject::~HostNPScriptObject() { |
| 80 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 81 |
| 82 // Disconnect synchronously. We cannot disconnect asynchronously |
| 83 // here because |host_context_| needs to be stopped on the plugin |
| 84 // thread, but the plugin thread may not exist after the instance |
| 85 // is destroyed. |
| 86 destructing_.Set(); |
| 87 disconnected_event_.Reset(); |
| 88 DisconnectInternal(); |
| 89 disconnected_event_.Wait(); |
| 90 |
| 91 host_context_.Stop(); |
| 92 if (log_debug_info_func_) { |
| 93 g_npnetscape_funcs->releaseobject(log_debug_info_func_); |
| 94 } |
| 95 if (on_state_changed_func_) { |
| 96 g_npnetscape_funcs->releaseobject(on_state_changed_func_); |
| 97 } |
| 98 } |
| 99 |
| 100 bool HostNPScriptObject::Init() { |
| 101 VLOG(2) << "Init"; |
| 102 // TODO(wez): This starts a bunch of threads, which might fail. |
| 103 host_context_.Start(); |
| 104 return true; |
| 105 } |
| 106 |
| 107 bool HostNPScriptObject::HasMethod(const std::string& method_name) { |
| 108 VLOG(2) << "HasMethod " << method_name; |
| 109 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 110 return (method_name == kFuncNameConnect || |
| 111 method_name == kFuncNameDisconnect); |
| 112 } |
| 113 |
| 114 bool HostNPScriptObject::InvokeDefault(const NPVariant* args, |
| 115 uint32_t argCount, |
| 116 NPVariant* result) { |
| 117 VLOG(2) << "InvokeDefault"; |
| 118 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 119 SetException("exception during default invocation"); |
| 120 return false; |
| 121 } |
| 122 |
| 123 bool HostNPScriptObject::Invoke(const std::string& method_name, |
| 124 const NPVariant* args, |
| 125 uint32_t argCount, |
| 126 NPVariant* result) { |
| 127 VLOG(2) << "Invoke " << method_name; |
| 128 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 129 if (method_name == kFuncNameConnect) { |
| 130 return Connect(args, argCount, result); |
| 131 } else if (method_name == kFuncNameDisconnect) { |
| 132 return Disconnect(args, argCount, result); |
| 133 } else { |
| 134 SetException("Invoke: unknown method " + method_name); |
| 135 return false; |
| 136 } |
| 137 } |
| 138 |
| 139 bool HostNPScriptObject::HasProperty(const std::string& property_name) { |
| 140 VLOG(2) << "HasProperty " << property_name; |
| 141 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 142 return (property_name == kAttrNameAccessCode || |
| 143 property_name == kAttrNameState || |
| 144 property_name == kAttrNameLogDebugInfo || |
| 145 property_name == kAttrNameOnStateChanged || |
| 146 property_name == kAttrNameDisconnected || |
| 147 property_name == kAttrNameRequestedAccessCode || |
| 148 property_name == kAttrNameReceivedAccessCode || |
| 149 property_name == kAttrNameConnected || |
| 150 property_name == kAttrNameAffirmingConnection || |
| 151 property_name == kAttrNameError); |
| 152 } |
| 153 |
| 154 bool HostNPScriptObject::GetProperty(const std::string& property_name, |
| 155 NPVariant* result) { |
| 156 VLOG(2) << "GetProperty " << property_name; |
| 157 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 158 if (!result) { |
| 159 SetException("GetProperty: NULL result"); |
| 160 return false; |
| 161 } |
| 162 |
| 163 if (property_name == kAttrNameOnStateChanged) { |
| 164 OBJECT_TO_NPVARIANT(on_state_changed_func_, *result); |
| 165 return true; |
| 166 } else if (property_name == kAttrNameLogDebugInfo) { |
| 167 OBJECT_TO_NPVARIANT(log_debug_info_func_, *result); |
| 168 return true; |
| 169 } else if (property_name == kAttrNameState) { |
| 170 INT32_TO_NPVARIANT(state_, *result); |
| 171 return true; |
| 172 } else if (property_name == kAttrNameAccessCode) { |
| 173 *result = NPVariantFromString(access_code_); |
| 174 return true; |
| 175 } else if (property_name == kAttrNameDisconnected) { |
| 176 INT32_TO_NPVARIANT(kDisconnected, *result); |
| 177 return true; |
| 178 } else if (property_name == kAttrNameRequestedAccessCode) { |
| 179 INT32_TO_NPVARIANT(kRequestedAccessCode, *result); |
| 180 return true; |
| 181 } else if (property_name == kAttrNameReceivedAccessCode) { |
| 182 INT32_TO_NPVARIANT(kReceivedAccessCode, *result); |
| 183 return true; |
| 184 } else if (property_name == kAttrNameConnected) { |
| 185 INT32_TO_NPVARIANT(kConnected, *result); |
| 186 return true; |
| 187 } else if (property_name == kAttrNameAffirmingConnection) { |
| 188 INT32_TO_NPVARIANT(kAffirmingConnection, *result); |
| 189 return true; |
| 190 } else if (property_name == kAttrNameError) { |
| 191 INT32_TO_NPVARIANT(kError, *result); |
| 192 return true; |
| 193 } else { |
| 194 SetException("GetProperty: unsupported property " + property_name); |
| 195 return false; |
| 196 } |
| 197 } |
| 198 |
| 199 bool HostNPScriptObject::SetProperty(const std::string& property_name, |
| 200 const NPVariant* value) { |
| 201 VLOG(2) << "SetProperty " << property_name; |
| 202 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 203 |
| 204 if (property_name == kAttrNameOnStateChanged) { |
| 205 if (NPVARIANT_IS_OBJECT(*value)) { |
| 206 if (on_state_changed_func_) { |
| 207 g_npnetscape_funcs->releaseobject(on_state_changed_func_); |
| 208 } |
| 209 on_state_changed_func_ = NPVARIANT_TO_OBJECT(*value); |
| 210 if (on_state_changed_func_) { |
| 211 g_npnetscape_funcs->retainobject(on_state_changed_func_); |
| 212 } |
| 213 return true; |
| 214 } else { |
| 215 SetException("SetProperty: unexpected type for property " + |
| 216 property_name); |
| 217 } |
| 218 return false; |
| 219 } |
| 220 |
| 221 if (property_name == kAttrNameLogDebugInfo) { |
| 222 if (NPVARIANT_IS_OBJECT(*value)) { |
| 223 if (log_debug_info_func_) { |
| 224 g_npnetscape_funcs->releaseobject(log_debug_info_func_); |
| 225 } |
| 226 log_debug_info_func_ = NPVARIANT_TO_OBJECT(*value); |
| 227 if (log_debug_info_func_) { |
| 228 g_npnetscape_funcs->retainobject(log_debug_info_func_); |
| 229 } |
| 230 return true; |
| 231 } else { |
| 232 SetException("SetProperty: unexpected type for property " + |
| 233 property_name); |
| 234 } |
| 235 return false; |
| 236 } |
| 237 |
| 238 return false; |
| 239 } |
| 240 |
| 241 bool HostNPScriptObject::RemoveProperty(const std::string& property_name) { |
| 242 VLOG(2) << "RemoveProperty " << property_name; |
| 243 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 244 return false; |
| 245 } |
| 246 |
| 247 bool HostNPScriptObject::Enumerate(std::vector<std::string>* values) { |
| 248 VLOG(2) << "Enumerate"; |
| 249 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 250 const char* entries[] = { |
| 251 kAttrNameAccessCode, |
| 252 kAttrNameState, |
| 253 kAttrNameLogDebugInfo, |
| 254 kAttrNameOnStateChanged, |
| 255 kFuncNameConnect, |
| 256 kFuncNameDisconnect, |
| 257 kAttrNameDisconnected, |
| 258 kAttrNameRequestedAccessCode, |
| 259 kAttrNameReceivedAccessCode, |
| 260 kAttrNameConnected, |
| 261 kAttrNameAffirmingConnection, |
| 262 kAttrNameError |
| 263 }; |
| 264 for (size_t i = 0; i < arraysize(entries); ++i) { |
| 265 values->push_back(entries[i]); |
| 266 } |
| 267 return true; |
| 268 } |
| 269 |
| 270 void HostNPScriptObject::OnSignallingConnected(SignalStrategy* signal_strategy, |
| 271 const std::string& full_jid) { |
| 272 OnStateChanged(kConnected); |
| 273 } |
| 274 |
| 275 void HostNPScriptObject::OnSignallingDisconnected() { |
| 276 } |
| 277 |
| 278 void HostNPScriptObject::OnAccessDenied() { |
| 279 DCHECK_EQ(MessageLoop::current(), host_context_.network_message_loop()); |
| 280 |
| 281 ++failed_login_attempts_; |
| 282 if (failed_login_attempts_ == kMaxLoginAttempts) |
| 283 DisconnectInternal(); |
| 284 } |
| 285 |
| 286 void HostNPScriptObject::OnShutdown() { |
| 287 DCHECK_EQ(MessageLoop::current(), host_context_.main_message_loop()); |
| 288 |
| 289 OnStateChanged(kDisconnected); |
| 290 } |
| 291 |
| 292 // string uid, string auth_token |
| 293 bool HostNPScriptObject::Connect(const NPVariant* args, |
| 294 uint32_t arg_count, |
| 295 NPVariant* result) { |
| 296 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 297 |
| 298 LogDebugInfo("Connecting..."); |
| 299 |
| 300 if (arg_count != 2) { |
| 301 SetException("connect: bad number of arguments"); |
| 302 return false; |
| 303 } |
| 304 |
| 305 std::string uid = StringFromNPVariant(args[0]); |
| 306 if (uid.empty()) { |
| 307 SetException("connect: bad uid argument"); |
| 308 return false; |
| 309 } |
| 310 |
| 311 std::string auth_service_with_token = StringFromNPVariant(args[1]); |
| 312 std::string auth_token; |
| 313 std::string auth_service; |
| 314 ParseAuthTokenWithService(auth_service_with_token, &auth_token, |
| 315 &auth_service); |
| 316 if (auth_token.empty()) { |
| 317 SetException("connect: auth_service_with_token argument has empty token"); |
| 318 return false; |
| 319 } |
| 320 |
| 321 ConnectInternal(uid, auth_token, auth_service); |
| 322 |
| 323 return true; |
| 324 } |
| 325 |
| 326 void HostNPScriptObject::ConnectInternal( |
| 327 const std::string& uid, |
| 328 const std::string& auth_token, |
| 329 const std::string& auth_service) { |
| 330 if (MessageLoop::current() != host_context_.main_message_loop()) { |
| 331 host_context_.main_message_loop()->PostTask( |
| 332 FROM_HERE, |
| 333 NewRunnableMethod(this, &HostNPScriptObject::ConnectInternal, |
| 334 uid, auth_token, auth_service)); |
| 335 return; |
| 336 } |
| 337 // Store the supplied user ID and token to the Host configuration. |
| 338 scoped_refptr<MutableHostConfig> host_config = new InMemoryHostConfig(); |
| 339 host_config->SetString(kXmppLoginConfigPath, uid); |
| 340 host_config->SetString(kXmppAuthTokenConfigPath, auth_token); |
| 341 host_config->SetString(kXmppAuthServiceConfigPath, auth_service); |
| 342 |
| 343 // Create an access verifier and fetch the host secret. |
| 344 scoped_ptr<SupportAccessVerifier> access_verifier; |
| 345 access_verifier.reset(new SupportAccessVerifier()); |
| 346 |
| 347 // Generate a key pair for the Host to use. |
| 348 // TODO(wez): Move this to the worker thread. |
| 349 HostKeyPair host_key_pair; |
| 350 host_key_pair.Generate(); |
| 351 host_key_pair.Save(host_config); |
| 352 |
| 353 // Request registration of the host for support. |
| 354 scoped_ptr<RegisterSupportHostRequest> register_request( |
| 355 new RegisterSupportHostRequest()); |
| 356 if (!register_request->Init( |
| 357 host_config.get(), |
| 358 base::Bind(&HostNPScriptObject::OnReceivedSupportID, |
| 359 base::Unretained(this), |
| 360 access_verifier.get()))) { |
| 361 OnStateChanged(kDisconnected); |
| 362 return; |
| 363 } |
| 364 |
| 365 // Create the Host. |
| 366 scoped_refptr<ChromotingHost> host = |
| 367 ChromotingHost::Create(&host_context_, host_config, |
| 368 access_verifier.release()); |
| 369 host->AddStatusObserver(this); |
| 370 host->AddStatusObserver(register_request.get()); |
| 371 host->set_it2me(true); |
| 372 |
| 373 // Nothing went wrong, so lets save the host, config and request. |
| 374 host_ = host; |
| 375 host_config_ = host_config; |
| 376 register_request_.reset(register_request.release()); |
| 377 |
| 378 // Start the Host. |
| 379 host_->Start(); |
| 380 |
| 381 OnStateChanged(kRequestedAccessCode); |
| 382 return; |
| 383 } |
| 384 |
| 385 bool HostNPScriptObject::Disconnect(const NPVariant* args, |
| 386 uint32_t arg_count, |
| 387 NPVariant* result) { |
| 388 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 389 if (arg_count != 0) { |
| 390 SetException("disconnect: bad number of arguments"); |
| 391 return false; |
| 392 } |
| 393 |
| 394 DisconnectInternal(); |
| 395 |
| 396 return true; |
| 397 } |
| 398 |
| 399 void HostNPScriptObject::DisconnectInternal() { |
| 400 if (MessageLoop::current() != host_context_.main_message_loop()) { |
| 401 host_context_.main_message_loop()->PostTask( |
| 402 FROM_HERE, |
| 403 NewRunnableMethod(this, &HostNPScriptObject::DisconnectInternal)); |
| 404 return; |
| 405 } |
| 406 |
| 407 if (!host_) { |
| 408 disconnected_event_.Signal(); |
| 409 return; |
| 410 } |
| 411 |
| 412 host_->Shutdown( |
| 413 NewRunnableMethod(this, &HostNPScriptObject::OnShutdownFinished)); |
| 414 } |
| 415 |
| 416 void HostNPScriptObject::OnShutdownFinished() { |
| 417 DCHECK_EQ(MessageLoop::current(), host_context_.main_message_loop()); |
| 418 |
| 419 host_ = NULL; |
| 420 register_request_.reset(); |
| 421 host_config_ = NULL; |
| 422 disconnected_event_.Signal(); |
| 423 } |
| 424 |
| 425 void HostNPScriptObject::OnReceivedSupportID( |
| 426 SupportAccessVerifier* access_verifier, |
| 427 bool success, |
| 428 const std::string& support_id) { |
| 429 CHECK_NE(base::PlatformThread::CurrentId(), np_thread_id_); |
| 430 |
| 431 if (!success) { |
| 432 // TODO(wez): Replace the success/fail flag with full error reporting. |
| 433 DisconnectInternal(); |
| 434 return; |
| 435 } |
| 436 |
| 437 // Inform the AccessVerifier of our Support-Id, for authentication. |
| 438 access_verifier->OnIT2MeHostRegistered(success, support_id); |
| 439 |
| 440 // Combine the Support Id with the Host Id to make the Access Code. |
| 441 // TODO(wez): Locking, anyone? |
| 442 access_code_ = support_id + "-" + access_verifier->host_secret(); |
| 443 |
| 444 // Let the caller know that life is good. |
| 445 OnStateChanged(kReceivedAccessCode); |
| 446 } |
| 447 |
| 448 void HostNPScriptObject::OnStateChanged(State state) { |
| 449 if (destructing_.IsSet()) { |
| 450 return; |
| 451 } |
| 452 |
| 453 if (!host_context_.IsUIThread()) { |
| 454 host_context_.PostToUIThread( |
| 455 FROM_HERE, |
| 456 NewRunnableMethod(this, &HostNPScriptObject::OnStateChanged, state)); |
| 457 return; |
| 458 } |
| 459 state_ = state; |
| 460 if (on_state_changed_func_) { |
| 461 VLOG(2) << "Calling state changed " << state; |
| 462 bool is_good = CallJSFunction(on_state_changed_func_, NULL, 0, NULL); |
| 463 LOG_IF(ERROR, !is_good) << "OnStateChanged failed"; |
| 464 } |
| 465 } |
| 466 |
| 467 void HostNPScriptObject::LogDebugInfo(const std::string& message) { |
| 468 if (!host_context_.IsUIThread()) { |
| 469 host_context_.PostToUIThread( |
| 470 FROM_HERE, |
| 471 NewRunnableMethod(this, &HostNPScriptObject::LogDebugInfo, message)); |
| 472 return; |
| 473 } |
| 474 if (log_debug_info_func_) { |
| 475 NPVariant* arg = new NPVariant(); |
| 476 LOG(INFO) << "Logging: " << message; |
| 477 STRINGZ_TO_NPVARIANT(message.c_str(), *arg); |
| 478 bool is_good = CallJSFunction(log_debug_info_func_, arg, 1, NULL); |
| 479 LOG_IF(ERROR, !is_good) << "LogDebugInfo failed"; |
| 480 } |
| 481 } |
| 482 |
| 483 void HostNPScriptObject::SetException(const std::string& exception_string) { |
| 484 CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); |
| 485 g_npnetscape_funcs->setexception(parent_, exception_string.c_str()); |
| 486 LogDebugInfo(exception_string); |
| 487 } |
| 488 |
| 489 bool HostNPScriptObject::CallJSFunction(NPObject* func, |
| 490 const NPVariant* args, |
| 491 uint32_t argCount, |
| 492 NPVariant* result) { |
| 493 NPVariant np_result; |
| 494 bool is_good = func->_class->invokeDefault(func, args, argCount, &np_result); |
| 495 if (is_good) { |
| 496 if (result) { |
| 497 *result = np_result; |
| 498 } else { |
| 499 g_npnetscape_funcs->releasevariantvalue(&np_result); |
| 500 } |
| 501 } |
| 502 return is_good; |
| 503 } |
| 504 |
| 505 void HostNPScriptObject::PostTaskToNPThread( |
| 506 const tracked_objects::Location& from_here, Task* task) { |
| 507 // The NPAPI functions cannot make use of |from_here|, but this method is |
| 508 // passed as a callback to ChromotingHostContext, so it needs to have the |
| 509 // appropriate signature. |
| 510 |
| 511 // Can be called from any thread. |
| 512 g_npnetscape_funcs->pluginthreadasynccall(plugin_, |
| 513 &NPTaskSpringboard, |
| 514 task); |
| 515 } |
| 516 |
| 517 void HostNPScriptObject::NPTaskSpringboard(void* task) { |
| 518 Task* real_task = reinterpret_cast<Task*>(task); |
| 519 real_task->Run(); |
| 520 delete real_task; |
| 521 } |
| 522 |
| 523 } // namespace remoting |
OLD | NEW |