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 |