| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 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 "chrome/browser/extensions/api/cast_channel/cast_channel_api.h" | |
| 6 | |
| 7 #include <limits> | |
| 8 #include <string> | |
| 9 | |
| 10 #include "base/json/json_writer.h" | |
| 11 #include "base/memory/scoped_ptr.h" | |
| 12 #include "base/strings/string_number_conversions.h" | |
| 13 #include "base/values.h" | |
| 14 #include "chrome/browser/browser_process.h" | |
| 15 #include "chrome/browser/extensions/api/cast_channel/cast_socket.h" | |
| 16 #include "chrome/browser/net/chrome_net_log.h" | |
| 17 #include "content/public/browser/browser_thread.h" | |
| 18 #include "extensions/browser/event_router.h" | |
| 19 #include "net/base/ip_endpoint.h" | |
| 20 #include "net/base/net_errors.h" | |
| 21 #include "net/base/net_util.h" | |
| 22 #include "url/gurl.h" | |
| 23 | |
| 24 // Default timeout interval for connection setup. | |
| 25 // Used if not otherwise specified at ConnectInfo::timeout. | |
| 26 const int kDefaultConnectTimeoutMillis = 5000; // 5 seconds. | |
| 27 | |
| 28 namespace extensions { | |
| 29 | |
| 30 namespace Close = cast_channel::Close; | |
| 31 namespace OnError = cast_channel::OnError; | |
| 32 namespace OnMessage = cast_channel::OnMessage; | |
| 33 namespace Open = cast_channel::Open; | |
| 34 namespace Send = cast_channel::Send; | |
| 35 using cast_channel::CastSocket; | |
| 36 using cast_channel::ChannelAuthType; | |
| 37 using cast_channel::ChannelError; | |
| 38 using cast_channel::ChannelInfo; | |
| 39 using cast_channel::ConnectInfo; | |
| 40 using cast_channel::MessageInfo; | |
| 41 using cast_channel::ReadyState; | |
| 42 using content::BrowserThread; | |
| 43 | |
| 44 namespace { | |
| 45 | |
| 46 // T is an extension dictionary (MessageInfo or ChannelInfo) | |
| 47 template <class T> | |
| 48 std::string ParamToString(const T& info) { | |
| 49 scoped_ptr<base::DictionaryValue> dict = info.ToValue(); | |
| 50 std::string out; | |
| 51 base::JSONWriter::Write(dict.get(), &out); | |
| 52 return out; | |
| 53 } | |
| 54 | |
| 55 // Fills |channel_info| from the destination and state of |socket|. | |
| 56 void FillChannelInfo(const CastSocket& socket, ChannelInfo* channel_info) { | |
| 57 DCHECK(channel_info); | |
| 58 channel_info->channel_id = socket.id(); | |
| 59 channel_info->url = socket.CastUrl(); | |
| 60 const net::IPEndPoint& ip_endpoint = socket.ip_endpoint(); | |
| 61 channel_info->connect_info.ip_address = ip_endpoint.ToStringWithoutPort(); | |
| 62 channel_info->connect_info.port = ip_endpoint.port(); | |
| 63 channel_info->connect_info.auth = socket.channel_auth(); | |
| 64 channel_info->ready_state = socket.ready_state(); | |
| 65 channel_info->error_state = socket.error_state(); | |
| 66 } | |
| 67 | |
| 68 bool IsValidConnectInfoPort(const ConnectInfo& connect_info) { | |
| 69 return connect_info.port > 0 && connect_info.port < | |
| 70 std::numeric_limits<unsigned short>::max(); | |
| 71 } | |
| 72 | |
| 73 bool IsValidConnectInfoAuth(const ConnectInfo& connect_info) { | |
| 74 return connect_info.auth == cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED || | |
| 75 connect_info.auth == cast_channel::CHANNEL_AUTH_TYPE_SSL; | |
| 76 } | |
| 77 | |
| 78 bool IsValidConnectInfoIpAddress(const ConnectInfo& connect_info) { | |
| 79 net::IPAddressNumber ip_address; | |
| 80 return net::ParseIPLiteralToNumber(connect_info.ip_address, &ip_address); | |
| 81 } | |
| 82 | |
| 83 } // namespace | |
| 84 | |
| 85 CastChannelAPI::CastChannelAPI(content::BrowserContext* context) | |
| 86 : browser_context_(context) { | |
| 87 DCHECK(browser_context_); | |
| 88 } | |
| 89 | |
| 90 // static | |
| 91 CastChannelAPI* CastChannelAPI::Get(content::BrowserContext* context) { | |
| 92 return BrowserContextKeyedAPIFactory<CastChannelAPI>::Get(context); | |
| 93 } | |
| 94 | |
| 95 static base::LazyInstance<BrowserContextKeyedAPIFactory<CastChannelAPI> > | |
| 96 g_factory = LAZY_INSTANCE_INITIALIZER; | |
| 97 | |
| 98 // static | |
| 99 BrowserContextKeyedAPIFactory<CastChannelAPI>* | |
| 100 CastChannelAPI::GetFactoryInstance() { | |
| 101 return g_factory.Pointer(); | |
| 102 } | |
| 103 | |
| 104 scoped_ptr<CastSocket> CastChannelAPI::CreateCastSocket( | |
| 105 const std::string& extension_id, const net::IPEndPoint& ip_endpoint, | |
| 106 ChannelAuthType channel_auth, const base::TimeDelta& timeout) { | |
| 107 if (socket_for_test_.get()) { | |
| 108 return socket_for_test_.Pass(); | |
| 109 } else { | |
| 110 return scoped_ptr<CastSocket>( | |
| 111 new CastSocket(extension_id, ip_endpoint, channel_auth, this, | |
| 112 g_browser_process->net_log(), | |
| 113 timeout)); | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 void CastChannelAPI::SetSocketForTest(scoped_ptr<CastSocket> socket_for_test) { | |
| 118 socket_for_test_ = socket_for_test.Pass(); | |
| 119 } | |
| 120 | |
| 121 void CastChannelAPI::OnError(const CastSocket* socket, | |
| 122 cast_channel::ChannelError error) { | |
| 123 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 124 ChannelInfo channel_info; | |
| 125 FillChannelInfo(*socket, &channel_info); | |
| 126 channel_info.error_state = error; | |
| 127 scoped_ptr<base::ListValue> results = OnError::Create(channel_info); | |
| 128 scoped_ptr<Event> event(new Event(OnError::kEventName, results.Pass())); | |
| 129 extensions::EventRouter::Get(browser_context_) | |
| 130 ->DispatchEventToExtension(socket->owner_extension_id(), event.Pass()); | |
| 131 } | |
| 132 | |
| 133 void CastChannelAPI::OnMessage(const CastSocket* socket, | |
| 134 const MessageInfo& message_info) { | |
| 135 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 136 ChannelInfo channel_info; | |
| 137 FillChannelInfo(*socket, &channel_info); | |
| 138 scoped_ptr<base::ListValue> results = | |
| 139 OnMessage::Create(channel_info, message_info); | |
| 140 VLOG(1) << "Sending message " << ParamToString(message_info) | |
| 141 << " to channel " << ParamToString(channel_info); | |
| 142 scoped_ptr<Event> event(new Event(OnMessage::kEventName, results.Pass())); | |
| 143 extensions::EventRouter::Get(browser_context_) | |
| 144 ->DispatchEventToExtension(socket->owner_extension_id(), event.Pass()); | |
| 145 } | |
| 146 | |
| 147 CastChannelAPI::~CastChannelAPI() {} | |
| 148 | |
| 149 CastChannelAsyncApiFunction::CastChannelAsyncApiFunction() | |
| 150 : manager_(NULL), error_(cast_channel::CHANNEL_ERROR_NONE) { } | |
| 151 | |
| 152 CastChannelAsyncApiFunction::~CastChannelAsyncApiFunction() { } | |
| 153 | |
| 154 bool CastChannelAsyncApiFunction::PrePrepare() { | |
| 155 manager_ = ApiResourceManager<CastSocket>::Get(browser_context()); | |
| 156 return true; | |
| 157 } | |
| 158 | |
| 159 bool CastChannelAsyncApiFunction::Respond() { | |
| 160 return error_ != cast_channel::CHANNEL_ERROR_NONE; | |
| 161 } | |
| 162 | |
| 163 CastSocket* CastChannelAsyncApiFunction::GetSocketOrCompleteWithError( | |
| 164 int channel_id) { | |
| 165 CastSocket* socket = GetSocket(channel_id); | |
| 166 if (!socket) { | |
| 167 SetResultFromError(cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID); | |
| 168 AsyncWorkCompleted(); | |
| 169 } | |
| 170 return socket; | |
| 171 } | |
| 172 | |
| 173 int CastChannelAsyncApiFunction::AddSocket(CastSocket* socket) { | |
| 174 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 175 DCHECK(socket); | |
| 176 DCHECK(manager_); | |
| 177 const int id = manager_->Add(socket); | |
| 178 socket->set_id(id); | |
| 179 return id; | |
| 180 } | |
| 181 | |
| 182 void CastChannelAsyncApiFunction::RemoveSocket(int channel_id) { | |
| 183 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 184 DCHECK(manager_); | |
| 185 manager_->Remove(extension_->id(), channel_id); | |
| 186 } | |
| 187 | |
| 188 void CastChannelAsyncApiFunction::SetResultFromSocket(int channel_id) { | |
| 189 CastSocket* socket = GetSocket(channel_id); | |
| 190 DCHECK(socket); | |
| 191 ChannelInfo channel_info; | |
| 192 FillChannelInfo(*socket, &channel_info); | |
| 193 error_ = socket->error_state(); | |
| 194 SetResultFromChannelInfo(channel_info); | |
| 195 } | |
| 196 | |
| 197 void CastChannelAsyncApiFunction::SetResultFromError(ChannelError error) { | |
| 198 ChannelInfo channel_info; | |
| 199 channel_info.channel_id = -1; | |
| 200 channel_info.url = ""; | |
| 201 channel_info.ready_state = cast_channel::READY_STATE_CLOSED; | |
| 202 channel_info.error_state = error; | |
| 203 SetResultFromChannelInfo(channel_info); | |
| 204 error_ = error; | |
| 205 } | |
| 206 | |
| 207 CastSocket* CastChannelAsyncApiFunction::GetSocket(int channel_id) { | |
| 208 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 209 DCHECK(manager_); | |
| 210 return manager_->Get(extension_->id(), channel_id); | |
| 211 } | |
| 212 | |
| 213 void CastChannelAsyncApiFunction::SetResultFromChannelInfo( | |
| 214 const ChannelInfo& channel_info) { | |
| 215 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 216 SetResult(channel_info.ToValue().release()); | |
| 217 } | |
| 218 | |
| 219 CastChannelOpenFunction::CastChannelOpenFunction() | |
| 220 : new_channel_id_(0) { } | |
| 221 | |
| 222 CastChannelOpenFunction::~CastChannelOpenFunction() { } | |
| 223 | |
| 224 // TODO(mfoltz): Remove URL parsing when clients have converted to use | |
| 225 // ConnectInfo. | |
| 226 | |
| 227 // Allowed schemes for Cast device URLs. | |
| 228 const char kCastInsecureScheme[] = "cast"; | |
| 229 const char kCastSecureScheme[] = "casts"; | |
| 230 | |
| 231 bool CastChannelOpenFunction::ParseChannelUrl(const GURL& url, | |
| 232 ConnectInfo* connect_info) { | |
| 233 DCHECK(connect_info); | |
| 234 VLOG(2) << "ParseChannelUrl"; | |
| 235 bool auth_required = false; | |
| 236 if (url.SchemeIs(kCastSecureScheme)) { | |
| 237 auth_required = true; | |
| 238 } else if (!url.SchemeIs(kCastInsecureScheme)) { | |
| 239 return false; | |
| 240 } | |
| 241 // TODO(mfoltz): Test for IPv6 addresses. Brackets or no brackets? | |
| 242 // TODO(mfoltz): Maybe enforce restriction to IPv4 private and IPv6 | |
| 243 // link-local networks | |
| 244 const std::string& path = url.path(); | |
| 245 // Shortest possible: //A:B | |
| 246 if (path.size() < 5) { | |
| 247 return false; | |
| 248 } | |
| 249 if (path.find("//") != 0) { | |
| 250 return false; | |
| 251 } | |
| 252 size_t colon = path.find_last_of(':'); | |
| 253 if (colon == std::string::npos || colon < 3 || colon > path.size() - 2) { | |
| 254 return false; | |
| 255 } | |
| 256 const std::string& ip_address_str = path.substr(2, colon - 2); | |
| 257 const std::string& port_str = path.substr(colon + 1); | |
| 258 VLOG(2) << "IP: " << ip_address_str << " Port: " << port_str; | |
| 259 int port; | |
| 260 if (!base::StringToInt(port_str, &port)) | |
| 261 return false; | |
| 262 connect_info->ip_address = ip_address_str; | |
| 263 connect_info->port = port; | |
| 264 connect_info->auth = auth_required ? | |
| 265 cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED : | |
| 266 cast_channel::CHANNEL_AUTH_TYPE_SSL; | |
| 267 return true; | |
| 268 } | |
| 269 | |
| 270 net::IPEndPoint* CastChannelOpenFunction::ParseConnectInfo( | |
| 271 const ConnectInfo& connect_info) { | |
| 272 net::IPAddressNumber ip_address; | |
| 273 CHECK(net::ParseIPLiteralToNumber(connect_info.ip_address, &ip_address)); | |
| 274 return new net::IPEndPoint(ip_address, connect_info.port); | |
| 275 } | |
| 276 | |
| 277 bool CastChannelOpenFunction::PrePrepare() { | |
| 278 api_ = CastChannelAPI::Get(browser_context()); | |
| 279 return CastChannelAsyncApiFunction::PrePrepare(); | |
| 280 } | |
| 281 | |
| 282 bool CastChannelOpenFunction::Prepare() { | |
| 283 params_ = Open::Params::Create(*args_); | |
| 284 EXTENSION_FUNCTION_VALIDATE(params_.get()); | |
| 285 // The connect_info parameter may be a string URL like cast:// or casts:// or | |
| 286 // a ConnectInfo object. | |
| 287 std::string cast_url; | |
| 288 switch (params_->connect_info->GetType()) { | |
| 289 case base::Value::TYPE_STRING: | |
| 290 CHECK(params_->connect_info->GetAsString(&cast_url)); | |
| 291 connect_info_.reset(new ConnectInfo); | |
| 292 if (!ParseChannelUrl(GURL(cast_url), connect_info_.get())) { | |
| 293 connect_info_.reset(); | |
| 294 SetError("Invalid connect_info (invalid Cast URL " + cast_url + ")"); | |
| 295 } | |
| 296 break; | |
| 297 case base::Value::TYPE_DICTIONARY: | |
| 298 connect_info_ = ConnectInfo::FromValue(*(params_->connect_info)); | |
| 299 if (!connect_info_.get()) { | |
| 300 SetError("connect_info.auth is required"); | |
| 301 } | |
| 302 break; | |
| 303 default: | |
| 304 SetError("Invalid connect_info (unknown type)"); | |
| 305 break; | |
| 306 } | |
| 307 if (!connect_info_.get()) { | |
| 308 return false; | |
| 309 } | |
| 310 if (!IsValidConnectInfoPort(*connect_info_)) { | |
| 311 SetError("Invalid connect_info (invalid port)"); | |
| 312 } else if (!IsValidConnectInfoAuth(*connect_info_)) { | |
| 313 SetError("Invalid connect_info (invalid auth)"); | |
| 314 } else if (!IsValidConnectInfoIpAddress(*connect_info_)) { | |
| 315 SetError("Invalid connect_info (invalid IP address)"); | |
| 316 } | |
| 317 if (!GetError().empty()) { | |
| 318 return false; | |
| 319 } | |
| 320 channel_auth_ = connect_info_->auth; | |
| 321 ip_endpoint_.reset(ParseConnectInfo(*connect_info_)); | |
| 322 return true; | |
| 323 } | |
| 324 | |
| 325 void CastChannelOpenFunction::AsyncWorkStart() { | |
| 326 DCHECK(api_); | |
| 327 DCHECK(ip_endpoint_.get()); | |
| 328 scoped_ptr<CastSocket> socket = api_->CreateCastSocket( | |
| 329 extension_->id(), | |
| 330 *ip_endpoint_, | |
| 331 channel_auth_, | |
| 332 base::TimeDelta::FromMilliseconds(connect_info_->timeout.get() | |
| 333 ? *connect_info_->timeout | |
| 334 : kDefaultConnectTimeoutMillis)); | |
| 335 new_channel_id_ = AddSocket(socket.release()); | |
| 336 GetSocket(new_channel_id_)->Connect( | |
| 337 base::Bind(&CastChannelOpenFunction::OnOpen, this)); | |
| 338 } | |
| 339 | |
| 340 void CastChannelOpenFunction::OnOpen(int result) { | |
| 341 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 342 VLOG(1) << "Connect finished, OnOpen invoked."; | |
| 343 SetResultFromSocket(new_channel_id_); | |
| 344 AsyncWorkCompleted(); | |
| 345 } | |
| 346 | |
| 347 CastChannelSendFunction::CastChannelSendFunction() { } | |
| 348 | |
| 349 CastChannelSendFunction::~CastChannelSendFunction() { } | |
| 350 | |
| 351 bool CastChannelSendFunction::Prepare() { | |
| 352 params_ = Send::Params::Create(*args_); | |
| 353 EXTENSION_FUNCTION_VALIDATE(params_.get()); | |
| 354 if (params_->message.namespace_.empty()) { | |
| 355 SetError("message_info.namespace_ is required"); | |
| 356 return false; | |
| 357 } | |
| 358 if (params_->message.source_id.empty()) { | |
| 359 SetError("message_info.source_id is required"); | |
| 360 return false; | |
| 361 } | |
| 362 if (params_->message.destination_id.empty()) { | |
| 363 SetError("message_info.destination_id is required"); | |
| 364 return false; | |
| 365 } | |
| 366 switch (params_->message.data->GetType()) { | |
| 367 case base::Value::TYPE_STRING: | |
| 368 case base::Value::TYPE_BINARY: | |
| 369 break; | |
| 370 default: | |
| 371 SetError("Invalid type of message_info.data"); | |
| 372 return false; | |
| 373 } | |
| 374 return true; | |
| 375 } | |
| 376 | |
| 377 void CastChannelSendFunction::AsyncWorkStart() { | |
| 378 CastSocket* socket = GetSocketOrCompleteWithError( | |
| 379 params_->channel.channel_id); | |
| 380 if (socket) | |
| 381 socket->SendMessage(params_->message, | |
| 382 base::Bind(&CastChannelSendFunction::OnSend, this)); | |
| 383 } | |
| 384 | |
| 385 void CastChannelSendFunction::OnSend(int result) { | |
| 386 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 387 if (result < 0) { | |
| 388 SetResultFromError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR); | |
| 389 } else { | |
| 390 SetResultFromSocket(params_->channel.channel_id); | |
| 391 } | |
| 392 AsyncWorkCompleted(); | |
| 393 } | |
| 394 | |
| 395 CastChannelCloseFunction::CastChannelCloseFunction() { } | |
| 396 | |
| 397 CastChannelCloseFunction::~CastChannelCloseFunction() { } | |
| 398 | |
| 399 bool CastChannelCloseFunction::Prepare() { | |
| 400 params_ = Close::Params::Create(*args_); | |
| 401 EXTENSION_FUNCTION_VALIDATE(params_.get()); | |
| 402 return true; | |
| 403 } | |
| 404 | |
| 405 void CastChannelCloseFunction::AsyncWorkStart() { | |
| 406 CastSocket* socket = GetSocketOrCompleteWithError( | |
| 407 params_->channel.channel_id); | |
| 408 if (socket) | |
| 409 socket->Close(base::Bind(&CastChannelCloseFunction::OnClose, this)); | |
| 410 } | |
| 411 | |
| 412 void CastChannelCloseFunction::OnClose(int result) { | |
| 413 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
| 414 VLOG(1) << "CastChannelCloseFunction::OnClose result = " << result; | |
| 415 if (result < 0) { | |
| 416 SetResultFromError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR); | |
| 417 } else { | |
| 418 int channel_id = params_->channel.channel_id; | |
| 419 SetResultFromSocket(channel_id); | |
| 420 RemoveSocket(channel_id); | |
| 421 } | |
| 422 AsyncWorkCompleted(); | |
| 423 } | |
| 424 | |
| 425 } // namespace extensions | |
| OLD | NEW |