Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(133)

Side by Side Diff: chrome/browser/media/router/device_description_service.cc

Issue 2701633002: [Media Router] Add DialMediaSinkService and DeviceDescriptionService (Closed)
Patch Set: Add DialMediaSinkCacheService and unit test Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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/media/router/device_description_service.h"
6
7 #include "base/strings/string_util.h"
8 #include "chrome/browser/extensions/api/dial/device_description_fetcher.h"
9 #include "chrome/browser/extensions/api/dial/dial_device_data.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "net/base/ip_address.h"
13 #include "third_party/libxml/chromium/libxml_utils.h"
14 #include "url/gurl.h"
15
16 using base::Time;
17 using base::TimeDelta;
18 using content::BrowserThread;
19
20 namespace {
21
22 // How long to cache a device description.
23 // TODO: Use value from chrome.dial when http://crbug.com/165289 is fixed.
24 const int kDeviceDescriptionCacheTimeMillis = 30 * 60 * 1000;
25
26 // Replaces "<element_name>content</element_name>" with
27 // "<element_name>***</element_name>"
28 bool Scrub(const std::string& element_name, std::string& xml_text) {
29 size_t pos = xml_text.find("<" + element_name + ">");
30 size_t end_pos = xml_text.find("</" + element_name + ">");
31 size_t start_pos = pos + element_name.length() + 2;
32
33 if (pos == std::string::npos || end_pos == std::string::npos)
34 return false;
35
36 xml_text.replace(start_pos, end_pos - start_pos, "***");
37 return true;
38 }
39
40 // Removes unique identifiers from the device description.
41 // |xml_text|: xmlDoc The device description.
42 // Returns false if <UDN> or <serialNumber> field does not exist in |xml_text|.
43 bool ScrubDeviceDescription(std::string& xml_text) {
44 return Scrub("UDN", xml_text) && Scrub("serialNumber", xml_text);
45 }
46
47 // Validates whether a URL is valid for fetching device descriptions.
48 // |descriptionUrl|: Device description URL.
49 // Returns true if descriptionUrl is valid.
50 static bool IsValidDeviceDescriptionUrl(const GURL& device_description_url) {
51 if (!device_description_url.SchemeIs(url::kHttpScheme))
52 return false;
53
54 net::IPAddress address;
55 if (!net::ParseURLHostnameToAddress(device_description_url.host(), &address))
56 return false;
57
58 return address.IsReserved();
59 }
60
61 static bool IsValidAppUrl(const GURL& app_url, const std::string& ip_address) {
62 return app_url.SchemeIs(url::kHttpScheme) && app_url.host() == ip_address;
63 }
64
65 static std::string Validate(
66 const media_router::DialDeviceDescription& description) {
67 if (description.device_label.empty()) {
68 return "Missing deviceLabel";
69 }
70 if (description.unique_id.empty()) {
71 return "Missing uniqueId";
72 }
73 if (description.friendly_name.empty()) {
74 return "Missing friendlyName";
75 }
76 if (description.ip_address.empty()) {
77 return "Missing ipAddress";
78 }
79 if (!description.app_url.is_valid()) {
80 return "Missing appUrl";
81 }
82 if (description.fetch_time_millis.is_null()) {
83 return "Missing fetchTimeMillis";
84 }
85 if (description.fetch_time_millis.is_null()) {
86 return "Missing expireTimeMillis";
87 }
88 if (IsValidAppUrl(description.app_url, description.ip_address)) {
89 return "Invalid appUrl";
90 }
91 return "";
92 }
93
94 static std::string ToString(
95 const media_router::DialDeviceDescription& description) {
96 std::stringstream ss;
97 ss << "DialDeviceDescription [unique_id]: " << description.unique_id
98 << " [device_label]: " << description.device_label
99 << " [friendly_name]: " << description.friendly_name
100 << " [ip_address]: " << description.ip_address
101 << " [app_url]: " << description.app_url
102 << " [fetch_time_millis]: " << description.fetch_time_millis
103 << " [expire_time_millis]: " << description.expire_time_millis
104 << " [device_type]: " << description.device_type
105 << " [model_name]: " << description.model_name
106 << " [config_id]: " << *description.config_id;
107
108 return ss.str();
109 }
110
111 } // namespace
112
113 namespace media_router {
114
115 DialDeviceDescription::DialDeviceDescription() = default;
116 DialDeviceDescription::~DialDeviceDescription() = default;
117 DialDeviceDescription::DialDeviceDescription(
118 const DialDeviceDescription& other) = default;
119
120 DeviceDescriptionService::DeviceDescriptionService(Observer* observer)
121 : observer_(observer) {
122 DCHECK(observer_);
123 }
124
125 DeviceDescriptionService::~DeviceDescriptionService() = default;
126
127 bool DeviceDescriptionService::GetDeviceDescription(
128 const DialDeviceData& device,
129 net::URLRequestContextGetter* request_context,
130 DialDeviceDescription* out_description) {
131 DCHECK_CURRENTLY_ON(BrowserThread::IO);
132
133 if (CheckAndUpdateCache(device, out_description))
134 return true;
135
136 FetchDeviceDescription(device, request_context);
137 return false;
138 }
139
140 bool DeviceDescriptionService::MayStopDeviceDescriptionFetching(
141 const std::string& device_label) {
142 return device_description_fetcher_map_.erase(device_label) > 0;
143 }
144
145 void DeviceDescriptionService::CheckAccess(
146 const GURL& device_description_url,
147 net::URLRequestContextGetter* request_context,
148 const CheckAccessCallback& check_access_callback) {
149 const auto& it = pending_access_requests_.find(device_description_url);
150 if (it != pending_access_requests_.end())
151 return;
152
153 auto device_description_fetcher =
154 base::MakeUnique<extensions::api::dial::DeviceDescriptionFetcher>(
155 device_description_url, request_context,
156 base::BindOnce(&DeviceDescriptionService::OnCheckAccessSucceeded,
157 base::Unretained(this), check_access_callback),
158 base::BindOnce(&DeviceDescriptionService::OnCheckAccessError,
159 base::Unretained(this), check_access_callback));
160
161 device_description_fetcher->Start();
162 pending_access_requests_.insert(std::make_pair(
163 device_description_url, std::move(device_description_fetcher)));
164 }
165
166 void DeviceDescriptionService::OnCheckAccessSucceeded(
167 const CheckAccessCallback& check_access_cb,
168 const DialDeviceDescriptionData& description_data) {
169 check_access_cb.Run(true);
170 }
171
172 void DeviceDescriptionService::OnCheckAccessError(
173 const CheckAccessCallback& check_access_cb,
174 const std::string& error_message) {
175 check_access_cb.Run(false);
176 }
177
178 void DeviceDescriptionService::FetchDeviceDescription(
179 const DialDeviceData& dial_device,
180 net::URLRequestContextGetter* request_context) {
181 DCHECK_CURRENTLY_ON(BrowserThread::IO);
182
183 if (!IsValidDeviceDescriptionUrl(dial_device.device_description_url())) {
184 std::string error_message = "Invalid device description URL: " +
185 dial_device.device_description_url().spec();
186 OnDeviceDescriptionFetchError(dial_device, error_message);
187 return;
188 }
189
190 const auto& it = device_description_fetcher_map_.find(dial_device.label());
191 if (it != device_description_fetcher_map_.end()) {
192 if (it->second->device_description_url() ==
193 dial_device.device_description_url()) {
194 DVLOG(2) << "Pending request in progress [URL]: "
195 << dial_device.device_description_url();
196 return;
197 }
198 // Destroy fetcher and cancel HTTP request.
199 device_description_fetcher_map_.erase(it);
200 }
201
202 auto device_description_fetcher =
203 base::MakeUnique<extensions::api::dial::DeviceDescriptionFetcher>(
204 dial_device.device_description_url(), request_context,
205 base::BindOnce(
206 &DeviceDescriptionService::OnDeviceDescriptionAvailable,
207 base::Unretained(this), dial_device),
208 base::BindOnce(
209 &DeviceDescriptionService::OnDeviceDescriptionFetchError,
210 base::Unretained(this), dial_device));
211
212 device_description_fetcher->Start();
213 device_description_fetcher_map_.insert(std::make_pair(
214 dial_device.label(), std::move(device_description_fetcher)));
215 }
216
217 bool DeviceDescriptionService::CheckAndUpdateCache(
218 const DialDeviceData& dial_device,
219 DialDeviceDescription* out_description) {
220 const auto& it = description_map_.find(dial_device.label());
221 if (it == description_map_.end())
222 return false;
223
224 // If the entry's configId does not match, or it has expired, remove it.
225 if (it->second.config_id != dial_device.config_id() ||
226 base::Time::Now() >= it->second.expire_time_millis) {
227 DVLOG(2) << "Removing invalid entry " << it->second.device_label;
228 description_map_.erase(it);
229 return false;
230 }
231 // Entry is valid.
232 (*out_description) = it->second;
233 return true;
234 }
235
236 bool DeviceDescriptionService::ProcessDeviceDescription(
237 const DialDeviceData& dial_device,
238 const std::string& xmlText,
239 const GURL& app_url,
240 DialDeviceDescription* description) {
241 if (!ParseDeviceDescription(dial_device, xmlText, app_url, description)) {
242 DVLOG(2) << "Invalid device description";
243 return false;
244 }
245 description->device_description_url = dial_device.device_description_url();
246
247 DVLOG(2) << "Got device description for " << description->device_label;
248 DVLOG(2) << "... device description was: " << ToString(*description);
249
250 // Stick it in the cache if the description has a config (version) id.
251 // NOTE: We could cache descriptions without a config id and rely on the
252 // timeout to eventually update the cache.
253 if (description->config_id) {
254 DVLOG(2) << "Caching device description for " << description->device_label;
255 description_map_.insert(std::make_pair(dial_device.label(), *description));
256 }
257 return true;
258 }
259
260 bool DeviceDescriptionService::ParseDeviceDescription(
261 const DialDeviceData& dial_device,
262 const std::string& xml_text,
263 const GURL& app_url,
264 DialDeviceDescription* description) {
265 XmlReader xml_reader;
266 if (!xml_reader.Load(xml_text))
267 return false;
268
269 description->fetch_time_millis = base::Time::Now();
270 description->expire_time_millis =
271 description->fetch_time_millis +
272 base::TimeDelta::FromMilliseconds(kDeviceDescriptionCacheTimeMillis);
273 description->device_label = dial_device.label();
274 description->config_id = dial_device.config_id();
275 description->ip_address = dial_device.device_description_url().host();
276 description->app_url = app_url;
277
278 while (xml_reader.Read()) {
279 xml_reader.SkipToElement();
280 std::string node_name(xml_reader.NodeName());
281
282 if (node_name == "deviceType") {
283 if (!xml_reader.ReadElementContent(&description->device_type))
284 return false;
285 } else if (node_name == "modelName") {
286 if (!xml_reader.ReadElementContent(&description->model_name))
287 return false;
288 } else if (node_name == "friendlyName") {
289 if (!xml_reader.ReadElementContent(&description->friendly_name))
290 return false;
291 } else if (node_name == "UDN") {
292 if (!xml_reader.ReadElementContent(&description->unique_id))
293 return false;
294 }
295 }
296
297 // If friendly name does not exist, fall back to use model name + last 4
298 // digits of UUID as friendly name.
299 if (description->friendly_name.empty() && !description->model_name.empty()) {
300 description->friendly_name = description->model_name;
301 if (!description->unique_id.empty()) {
302 description->friendly_name +=
303 '[' +
304 description->unique_id.substr(description->unique_id.length() - 4) +
305 ']';
306 }
307 DVLOG(2)
308 << "Fixed device description: created friendlyName from modelName.";
309 }
310
311 std::string xml_logging = xml_text;
312 if (ScrubDeviceDescription(xml_logging))
313 DVLOG(2) << "Device description: " << xml_logging;
314
315 std::string error = Validate(*description);
316 if (!error.empty()) {
317 DLOG(WARNING) << "Device description failed to validate: " << error;
318 return false;
319 }
320
321 return true;
322 }
323
324 void DeviceDescriptionService::OnDeviceDescriptionAvailable(
325 const extensions::api::dial::DialDeviceData& dial_device,
326 const extensions::api::dial::DialDeviceDescriptionData& description_data) {
327 DCHECK_CURRENTLY_ON(BrowserThread::IO);
328
329 DialDeviceDescription description;
330 if (ProcessDeviceDescription(dial_device, description_data.device_description,
331 description_data.app_url, &description)) {
332 observer_->OnDeviceDescriptionAvailable(dial_device.label(), description);
333 } else {
334 observer_->OnDeviceDescriptionFetchError(dial_device.label(),
335 "Failed to process fetch result");
336 }
337
338 device_description_fetcher_map_.erase(dial_device.label());
339 }
340
341 void DeviceDescriptionService::OnDeviceDescriptionFetchError(
342 const extensions::api::dial::DialDeviceData& dial_device,
343 const std::string& error_message) {
344 DCHECK_CURRENTLY_ON(BrowserThread::IO);
345
346 DVLOG(2) << "OnDeviceDescriptionFetchError [label]: " << dial_device.label();
347
348 observer_->OnDeviceDescriptionFetchError(dial_device.label(), error_message);
349 device_description_fetcher_map_.erase(dial_device.label());
350 }
351
352 } // namespace media_router
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698