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.
5 #include "chrome/browser/extensions/api/cast_channel/cast_channel_api.h"
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"
24 namespace extensions
{
26 namespace Close
= cast_channel::Close
;
27 namespace OnError
= cast_channel::OnError
;
28 namespace OnMessage
= cast_channel::OnMessage
;
29 namespace Open
= cast_channel::Open
;
30 namespace Send
= cast_channel::Send
;
31 using cast_channel::CastSocket
;
32 using cast_channel::ChannelAuthType
;
33 using cast_channel::ChannelError
;
34 using cast_channel::ChannelInfo
;
35 using cast_channel::ConnectInfo
;
36 using cast_channel::MessageInfo
;
37 using cast_channel::ReadyState
;
38 using content::BrowserThread
;
42 // T is an extension dictionary (MessageInfo or ChannelInfo)
44 std::string
ParamToString(const T
& info
) {
45 scoped_ptr
<base::DictionaryValue
> dict
= info
.ToValue();
47 base::JSONWriter::Write(dict
.get(), &out
);
51 // Fills |channel_info| from the destination and state of |socket|.
52 void FillChannelInfo(const CastSocket
& socket
, ChannelInfo
* channel_info
) {
54 channel_info
->channel_id
= socket
.id();
55 channel_info
->url
= socket
.CastUrl();
56 const net::IPEndPoint
& ip_endpoint
= socket
.ip_endpoint();
57 channel_info
->connect_info
.ip_address
= ip_endpoint
.ToStringWithoutPort();
58 channel_info
->connect_info
.port
= ip_endpoint
.port();
59 channel_info
->connect_info
.auth
= socket
.channel_auth();
60 channel_info
->ready_state
= socket
.ready_state();
61 channel_info
->error_state
= socket
.error_state();
64 bool IsValidConnectInfoPort(const ConnectInfo
& connect_info
) {
65 return connect_info
.port
> 0 && connect_info
.port
<
66 std::numeric_limits
<unsigned short>::max();
69 bool IsValidConnectInfoAuth(const ConnectInfo
& connect_info
) {
70 return connect_info
.auth
== cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED
||
71 connect_info
.auth
== cast_channel::CHANNEL_AUTH_TYPE_SSL
;
74 bool IsValidConnectInfoIpAddress(const ConnectInfo
& connect_info
) {
75 net::IPAddressNumber ip_address
;
76 return net::ParseIPLiteralToNumber(connect_info
.ip_address
, &ip_address
);
81 CastChannelAPI::CastChannelAPI(content::BrowserContext
* context
)
82 : browser_context_(context
) {
83 DCHECK(browser_context_
);
87 CastChannelAPI
* CastChannelAPI::Get(content::BrowserContext
* context
) {
88 return BrowserContextKeyedAPIFactory
<CastChannelAPI
>::Get(context
);
91 static base::LazyInstance
<BrowserContextKeyedAPIFactory
<CastChannelAPI
> >
92 g_factory
= LAZY_INSTANCE_INITIALIZER
;
95 BrowserContextKeyedAPIFactory
<CastChannelAPI
>*
96 CastChannelAPI::GetFactoryInstance() {
97 return g_factory
.Pointer();
100 scoped_ptr
<CastSocket
> CastChannelAPI::CreateCastSocket(
101 const std::string
& extension_id
, const net::IPEndPoint
& ip_endpoint
,
102 ChannelAuthType channel_auth
) {
103 if (socket_for_test_
.get()) {
104 return socket_for_test_
.Pass();
106 return scoped_ptr
<CastSocket
>(
107 new CastSocket(extension_id
, ip_endpoint
, channel_auth
, this,
108 g_browser_process
->net_log()));
112 void CastChannelAPI::SetSocketForTest(scoped_ptr
<CastSocket
> socket_for_test
) {
113 socket_for_test_
= socket_for_test
.Pass();
116 void CastChannelAPI::OnError(const CastSocket
* socket
,
117 cast_channel::ChannelError error
) {
118 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
119 ChannelInfo channel_info
;
120 FillChannelInfo(*socket
, &channel_info
);
121 channel_info
.error_state
= error
;
122 scoped_ptr
<base::ListValue
> results
= OnError::Create(channel_info
);
123 scoped_ptr
<Event
> event(new Event(OnError::kEventName
, results
.Pass()));
124 extensions::EventRouter::Get(browser_context_
)
125 ->DispatchEventToExtension(socket
->owner_extension_id(), event
.Pass());
128 void CastChannelAPI::OnMessage(const CastSocket
* socket
,
129 const MessageInfo
& message_info
) {
130 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
131 ChannelInfo channel_info
;
132 FillChannelInfo(*socket
, &channel_info
);
133 scoped_ptr
<base::ListValue
> results
=
134 OnMessage::Create(channel_info
, message_info
);
135 VLOG(1) << "Sending message " << ParamToString(message_info
)
136 << " to channel " << ParamToString(channel_info
);
137 scoped_ptr
<Event
> event(new Event(OnMessage::kEventName
, results
.Pass()));
138 extensions::EventRouter::Get(browser_context_
)
139 ->DispatchEventToExtension(socket
->owner_extension_id(), event
.Pass());
142 CastChannelAPI::~CastChannelAPI() {}
144 CastChannelAsyncApiFunction::CastChannelAsyncApiFunction()
145 : manager_(NULL
), error_(cast_channel::CHANNEL_ERROR_NONE
) { }
147 CastChannelAsyncApiFunction::~CastChannelAsyncApiFunction() { }
149 bool CastChannelAsyncApiFunction::PrePrepare() {
150 manager_
= ApiResourceManager
<CastSocket
>::Get(browser_context());
154 bool CastChannelAsyncApiFunction::Respond() {
155 return error_
!= cast_channel::CHANNEL_ERROR_NONE
;
158 CastSocket
* CastChannelAsyncApiFunction::GetSocketOrCompleteWithError(
160 CastSocket
* socket
= GetSocket(channel_id
);
162 SetResultFromError(cast_channel::CHANNEL_ERROR_INVALID_CHANNEL_ID
);
163 AsyncWorkCompleted();
168 int CastChannelAsyncApiFunction::AddSocket(CastSocket
* socket
) {
169 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
172 const int id
= manager_
->Add(socket
);
177 void CastChannelAsyncApiFunction::RemoveSocket(int channel_id
) {
178 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
180 manager_
->Remove(extension_
->id(), channel_id
);
183 void CastChannelAsyncApiFunction::SetResultFromSocket(int channel_id
) {
184 CastSocket
* socket
= GetSocket(channel_id
);
186 ChannelInfo channel_info
;
187 FillChannelInfo(*socket
, &channel_info
);
188 error_
= socket
->error_state();
189 SetResultFromChannelInfo(channel_info
);
192 void CastChannelAsyncApiFunction::SetResultFromError(ChannelError error
) {
193 ChannelInfo channel_info
;
194 channel_info
.channel_id
= -1;
195 channel_info
.url
= "";
196 channel_info
.ready_state
= cast_channel::READY_STATE_CLOSED
;
197 channel_info
.error_state
= error
;
198 SetResultFromChannelInfo(channel_info
);
202 CastSocket
* CastChannelAsyncApiFunction::GetSocket(int channel_id
) {
203 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
205 return manager_
->Get(extension_
->id(), channel_id
);
208 void CastChannelAsyncApiFunction::SetResultFromChannelInfo(
209 const ChannelInfo
& channel_info
) {
210 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
211 SetResult(channel_info
.ToValue().release());
214 CastChannelOpenFunction::CastChannelOpenFunction()
215 : new_channel_id_(0) { }
217 CastChannelOpenFunction::~CastChannelOpenFunction() { }
219 // TODO(mfoltz): Remove URL parsing when clients have converted to use
222 // Allowed schemes for Cast device URLs.
223 const char kCastInsecureScheme
[] = "cast";
224 const char kCastSecureScheme
[] = "casts";
226 bool CastChannelOpenFunction::ParseChannelUrl(const GURL
& url
,
227 ConnectInfo
* connect_info
) {
228 DCHECK(connect_info
);
229 VLOG(2) << "ParseChannelUrl";
230 bool auth_required
= false;
231 if (url
.SchemeIs(kCastSecureScheme
)) {
232 auth_required
= true;
233 } else if (!url
.SchemeIs(kCastInsecureScheme
)) {
236 // TODO(mfoltz): Test for IPv6 addresses. Brackets or no brackets?
237 // TODO(mfoltz): Maybe enforce restriction to IPv4 private and IPv6
238 // link-local networks
239 const std::string
& path
= url
.path();
240 // Shortest possible: //A:B
241 if (path
.size() < 5) {
244 if (path
.find("//") != 0) {
247 size_t colon
= path
.find_last_of(':');
248 if (colon
== std::string::npos
|| colon
< 3 || colon
> path
.size() - 2) {
251 const std::string
& ip_address_str
= path
.substr(2, colon
- 2);
252 const std::string
& port_str
= path
.substr(colon
+ 1);
253 VLOG(2) << "IP: " << ip_address_str
<< " Port: " << port_str
;
255 if (!base::StringToInt(port_str
, &port
))
257 connect_info
->ip_address
= ip_address_str
;
258 connect_info
->port
= port
;
259 connect_info
->auth
= auth_required
?
260 cast_channel::CHANNEL_AUTH_TYPE_SSL_VERIFIED
:
261 cast_channel::CHANNEL_AUTH_TYPE_SSL
;
265 net::IPEndPoint
* CastChannelOpenFunction::ParseConnectInfo(
266 const ConnectInfo
& connect_info
) {
267 net::IPAddressNumber ip_address
;
268 CHECK(net::ParseIPLiteralToNumber(connect_info
.ip_address
, &ip_address
));
269 return new net::IPEndPoint(ip_address
, connect_info
.port
);
272 bool CastChannelOpenFunction::PrePrepare() {
273 api_
= CastChannelAPI::Get(browser_context());
274 return CastChannelAsyncApiFunction::PrePrepare();
277 bool CastChannelOpenFunction::Prepare() {
278 params_
= Open::Params::Create(*args_
);
279 EXTENSION_FUNCTION_VALIDATE(params_
.get());
280 // The connect_info parameter may be a string URL like cast:// or casts:// or
281 // a ConnectInfo object.
282 std::string cast_url
;
283 switch (params_
->connect_info
->GetType()) {
284 case base::Value::TYPE_STRING
:
285 CHECK(params_
->connect_info
->GetAsString(&cast_url
));
286 connect_info_
.reset(new ConnectInfo
);
287 if (!ParseChannelUrl(GURL(cast_url
), connect_info_
.get())) {
288 connect_info_
.reset();
289 SetError("Invalid connect_info (invalid Cast URL " + cast_url
+ ")");
292 case base::Value::TYPE_DICTIONARY
:
293 connect_info_
= ConnectInfo::FromValue(*(params_
->connect_info
));
294 if (!connect_info_
.get()) {
295 SetError("connect_info.auth is required");
299 SetError("Invalid connect_info (unknown type)");
302 if (!connect_info_
.get()) {
305 if (!IsValidConnectInfoPort(*connect_info_
)) {
306 SetError("Invalid connect_info (invalid port)");
307 } else if (!IsValidConnectInfoAuth(*connect_info_
)) {
308 SetError("Invalid connect_info (invalid auth)");
309 } else if (!IsValidConnectInfoIpAddress(*connect_info_
)) {
310 SetError("Invalid connect_info (invalid IP address)");
312 if (!GetError().empty()) {
315 channel_auth_
= connect_info_
->auth
;
316 ip_endpoint_
.reset(ParseConnectInfo(*connect_info_
));
320 void CastChannelOpenFunction::AsyncWorkStart() {
322 DCHECK(ip_endpoint_
.get());
323 scoped_ptr
<CastSocket
> socket
= api_
->CreateCastSocket(
324 extension_
->id(), *ip_endpoint_
, channel_auth_
);
325 new_channel_id_
= AddSocket(socket
.release());
326 GetSocket(new_channel_id_
)->Connect(
327 base::Bind(&CastChannelOpenFunction::OnOpen
, this));
330 void CastChannelOpenFunction::OnOpen(int result
) {
331 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
332 SetResultFromSocket(new_channel_id_
);
333 AsyncWorkCompleted();
336 CastChannelSendFunction::CastChannelSendFunction() { }
338 CastChannelSendFunction::~CastChannelSendFunction() { }
340 bool CastChannelSendFunction::Prepare() {
341 params_
= Send::Params::Create(*args_
);
342 EXTENSION_FUNCTION_VALIDATE(params_
.get());
343 if (params_
->message
.namespace_
.empty()) {
344 SetError("message_info.namespace_ is required");
347 if (params_
->message
.source_id
.empty()) {
348 SetError("message_info.source_id is required");
351 if (params_
->message
.destination_id
.empty()) {
352 SetError("message_info.destination_id is required");
355 switch (params_
->message
.data
->GetType()) {
356 case base::Value::TYPE_STRING
:
357 case base::Value::TYPE_BINARY
:
360 SetError("Invalid type of message_info.data");
366 void CastChannelSendFunction::AsyncWorkStart() {
367 CastSocket
* socket
= GetSocketOrCompleteWithError(
368 params_
->channel
.channel_id
);
370 socket
->SendMessage(params_
->message
,
371 base::Bind(&CastChannelSendFunction::OnSend
, this));
374 void CastChannelSendFunction::OnSend(int result
) {
375 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
377 SetResultFromError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR
);
379 SetResultFromSocket(params_
->channel
.channel_id
);
381 AsyncWorkCompleted();
384 CastChannelCloseFunction::CastChannelCloseFunction() { }
386 CastChannelCloseFunction::~CastChannelCloseFunction() { }
388 bool CastChannelCloseFunction::Prepare() {
389 params_
= Close::Params::Create(*args_
);
390 EXTENSION_FUNCTION_VALIDATE(params_
.get());
394 void CastChannelCloseFunction::AsyncWorkStart() {
395 CastSocket
* socket
= GetSocketOrCompleteWithError(
396 params_
->channel
.channel_id
);
398 socket
->Close(base::Bind(&CastChannelCloseFunction::OnClose
, this));
401 void CastChannelCloseFunction::OnClose(int result
) {
402 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
403 VLOG(1) << "CastChannelCloseFunction::OnClose result = " << result
;
405 SetResultFromError(cast_channel::CHANNEL_ERROR_SOCKET_ERROR
);
407 int channel_id
= params_
->channel
.channel_id
;
408 SetResultFromSocket(channel_id
);
409 RemoveSocket(channel_id
);
411 AsyncWorkCompleted();
414 } // namespace extensions