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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/media/router/device_description_service.cc
diff --git a/chrome/browser/media/router/device_description_service.cc b/chrome/browser/media/router/device_description_service.cc
new file mode 100644
index 0000000000000000000000000000000000000000..672e8d9634e9de6e40a50f3636de8f04560440d0
--- /dev/null
+++ b/chrome/browser/media/router/device_description_service.cc
@@ -0,0 +1,352 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/router/device_description_service.h"
+
+#include "base/strings/string_util.h"
+#include "chrome/browser/extensions/api/dial/device_description_fetcher.h"
+#include "chrome/browser/extensions/api/dial/dial_device_data.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/ip_address.h"
+#include "third_party/libxml/chromium/libxml_utils.h"
+#include "url/gurl.h"
+
+using base::Time;
+using base::TimeDelta;
+using content::BrowserThread;
+
+namespace {
+
+// How long to cache a device description.
+// TODO: Use value from chrome.dial when http://crbug.com/165289 is fixed.
+const int kDeviceDescriptionCacheTimeMillis = 30 * 60 * 1000;
+
+// Replaces "<element_name>content</element_name>" with
+// "<element_name>***</element_name>"
+bool Scrub(const std::string& element_name, std::string& xml_text) {
+ size_t pos = xml_text.find("<" + element_name + ">");
+ size_t end_pos = xml_text.find("</" + element_name + ">");
+ size_t start_pos = pos + element_name.length() + 2;
+
+ if (pos == std::string::npos || end_pos == std::string::npos)
+ return false;
+
+ xml_text.replace(start_pos, end_pos - start_pos, "***");
+ return true;
+}
+
+// Removes unique identifiers from the device description.
+// |xml_text|: xmlDoc The device description.
+// Returns false if <UDN> or <serialNumber> field does not exist in |xml_text|.
+bool ScrubDeviceDescription(std::string& xml_text) {
+ return Scrub("UDN", xml_text) && Scrub("serialNumber", xml_text);
+}
+
+// Validates whether a URL is valid for fetching device descriptions.
+// |descriptionUrl|: Device description URL.
+// Returns true if descriptionUrl is valid.
+static bool IsValidDeviceDescriptionUrl(const GURL& device_description_url) {
+ if (!device_description_url.SchemeIs(url::kHttpScheme))
+ return false;
+
+ net::IPAddress address;
+ if (!net::ParseURLHostnameToAddress(device_description_url.host(), &address))
+ return false;
+
+ return address.IsReserved();
+}
+
+static bool IsValidAppUrl(const GURL& app_url, const std::string& ip_address) {
+ return app_url.SchemeIs(url::kHttpScheme) && app_url.host() == ip_address;
+}
+
+static std::string Validate(
+ const media_router::DialDeviceDescription& description) {
+ if (description.device_label.empty()) {
+ return "Missing deviceLabel";
+ }
+ if (description.unique_id.empty()) {
+ return "Missing uniqueId";
+ }
+ if (description.friendly_name.empty()) {
+ return "Missing friendlyName";
+ }
+ if (description.ip_address.empty()) {
+ return "Missing ipAddress";
+ }
+ if (!description.app_url.is_valid()) {
+ return "Missing appUrl";
+ }
+ if (description.fetch_time_millis.is_null()) {
+ return "Missing fetchTimeMillis";
+ }
+ if (description.fetch_time_millis.is_null()) {
+ return "Missing expireTimeMillis";
+ }
+ if (IsValidAppUrl(description.app_url, description.ip_address)) {
+ return "Invalid appUrl";
+ }
+ return "";
+}
+
+static std::string ToString(
+ const media_router::DialDeviceDescription& description) {
+ std::stringstream ss;
+ ss << "DialDeviceDescription [unique_id]: " << description.unique_id
+ << " [device_label]: " << description.device_label
+ << " [friendly_name]: " << description.friendly_name
+ << " [ip_address]: " << description.ip_address
+ << " [app_url]: " << description.app_url
+ << " [fetch_time_millis]: " << description.fetch_time_millis
+ << " [expire_time_millis]: " << description.expire_time_millis
+ << " [device_type]: " << description.device_type
+ << " [model_name]: " << description.model_name
+ << " [config_id]: " << *description.config_id;
+
+ return ss.str();
+}
+
+} // namespace
+
+namespace media_router {
+
+DialDeviceDescription::DialDeviceDescription() = default;
+DialDeviceDescription::~DialDeviceDescription() = default;
+DialDeviceDescription::DialDeviceDescription(
+ const DialDeviceDescription& other) = default;
+
+DeviceDescriptionService::DeviceDescriptionService(Observer* observer)
+ : observer_(observer) {
+ DCHECK(observer_);
+}
+
+DeviceDescriptionService::~DeviceDescriptionService() = default;
+
+bool DeviceDescriptionService::GetDeviceDescription(
+ const DialDeviceData& device,
+ net::URLRequestContextGetter* request_context,
+ DialDeviceDescription* out_description) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ if (CheckAndUpdateCache(device, out_description))
+ return true;
+
+ FetchDeviceDescription(device, request_context);
+ return false;
+}
+
+bool DeviceDescriptionService::MayStopDeviceDescriptionFetching(
+ const std::string& device_label) {
+ return device_description_fetcher_map_.erase(device_label) > 0;
+}
+
+void DeviceDescriptionService::CheckAccess(
+ const GURL& device_description_url,
+ net::URLRequestContextGetter* request_context,
+ const CheckAccessCallback& check_access_callback) {
+ const auto& it = pending_access_requests_.find(device_description_url);
+ if (it != pending_access_requests_.end())
+ return;
+
+ auto device_description_fetcher =
+ base::MakeUnique<extensions::api::dial::DeviceDescriptionFetcher>(
+ device_description_url, request_context,
+ base::BindOnce(&DeviceDescriptionService::OnCheckAccessSucceeded,
+ base::Unretained(this), check_access_callback),
+ base::BindOnce(&DeviceDescriptionService::OnCheckAccessError,
+ base::Unretained(this), check_access_callback));
+
+ device_description_fetcher->Start();
+ pending_access_requests_.insert(std::make_pair(
+ device_description_url, std::move(device_description_fetcher)));
+}
+
+void DeviceDescriptionService::OnCheckAccessSucceeded(
+ const CheckAccessCallback& check_access_cb,
+ const DialDeviceDescriptionData& description_data) {
+ check_access_cb.Run(true);
+}
+
+void DeviceDescriptionService::OnCheckAccessError(
+ const CheckAccessCallback& check_access_cb,
+ const std::string& error_message) {
+ check_access_cb.Run(false);
+}
+
+void DeviceDescriptionService::FetchDeviceDescription(
+ const DialDeviceData& dial_device,
+ net::URLRequestContextGetter* request_context) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ if (!IsValidDeviceDescriptionUrl(dial_device.device_description_url())) {
+ std::string error_message = "Invalid device description URL: " +
+ dial_device.device_description_url().spec();
+ OnDeviceDescriptionFetchError(dial_device, error_message);
+ return;
+ }
+
+ const auto& it = device_description_fetcher_map_.find(dial_device.label());
+ if (it != device_description_fetcher_map_.end()) {
+ if (it->second->device_description_url() ==
+ dial_device.device_description_url()) {
+ DVLOG(2) << "Pending request in progress [URL]: "
+ << dial_device.device_description_url();
+ return;
+ }
+ // Destroy fetcher and cancel HTTP request.
+ device_description_fetcher_map_.erase(it);
+ }
+
+ auto device_description_fetcher =
+ base::MakeUnique<extensions::api::dial::DeviceDescriptionFetcher>(
+ dial_device.device_description_url(), request_context,
+ base::BindOnce(
+ &DeviceDescriptionService::OnDeviceDescriptionAvailable,
+ base::Unretained(this), dial_device),
+ base::BindOnce(
+ &DeviceDescriptionService::OnDeviceDescriptionFetchError,
+ base::Unretained(this), dial_device));
+
+ device_description_fetcher->Start();
+ device_description_fetcher_map_.insert(std::make_pair(
+ dial_device.label(), std::move(device_description_fetcher)));
+}
+
+bool DeviceDescriptionService::CheckAndUpdateCache(
+ const DialDeviceData& dial_device,
+ DialDeviceDescription* out_description) {
+ const auto& it = description_map_.find(dial_device.label());
+ if (it == description_map_.end())
+ return false;
+
+ // If the entry's configId does not match, or it has expired, remove it.
+ if (it->second.config_id != dial_device.config_id() ||
+ base::Time::Now() >= it->second.expire_time_millis) {
+ DVLOG(2) << "Removing invalid entry " << it->second.device_label;
+ description_map_.erase(it);
+ return false;
+ }
+ // Entry is valid.
+ (*out_description) = it->second;
+ return true;
+}
+
+bool DeviceDescriptionService::ProcessDeviceDescription(
+ const DialDeviceData& dial_device,
+ const std::string& xmlText,
+ const GURL& app_url,
+ DialDeviceDescription* description) {
+ if (!ParseDeviceDescription(dial_device, xmlText, app_url, description)) {
+ DVLOG(2) << "Invalid device description";
+ return false;
+ }
+ description->device_description_url = dial_device.device_description_url();
+
+ DVLOG(2) << "Got device description for " << description->device_label;
+ DVLOG(2) << "... device description was: " << ToString(*description);
+
+ // Stick it in the cache if the description has a config (version) id.
+ // NOTE: We could cache descriptions without a config id and rely on the
+ // timeout to eventually update the cache.
+ if (description->config_id) {
+ DVLOG(2) << "Caching device description for " << description->device_label;
+ description_map_.insert(std::make_pair(dial_device.label(), *description));
+ }
+ return true;
+}
+
+bool DeviceDescriptionService::ParseDeviceDescription(
+ const DialDeviceData& dial_device,
+ const std::string& xml_text,
+ const GURL& app_url,
+ DialDeviceDescription* description) {
+ XmlReader xml_reader;
+ if (!xml_reader.Load(xml_text))
+ return false;
+
+ description->fetch_time_millis = base::Time::Now();
+ description->expire_time_millis =
+ description->fetch_time_millis +
+ base::TimeDelta::FromMilliseconds(kDeviceDescriptionCacheTimeMillis);
+ description->device_label = dial_device.label();
+ description->config_id = dial_device.config_id();
+ description->ip_address = dial_device.device_description_url().host();
+ description->app_url = app_url;
+
+ while (xml_reader.Read()) {
+ xml_reader.SkipToElement();
+ std::string node_name(xml_reader.NodeName());
+
+ if (node_name == "deviceType") {
+ if (!xml_reader.ReadElementContent(&description->device_type))
+ return false;
+ } else if (node_name == "modelName") {
+ if (!xml_reader.ReadElementContent(&description->model_name))
+ return false;
+ } else if (node_name == "friendlyName") {
+ if (!xml_reader.ReadElementContent(&description->friendly_name))
+ return false;
+ } else if (node_name == "UDN") {
+ if (!xml_reader.ReadElementContent(&description->unique_id))
+ return false;
+ }
+ }
+
+ // If friendly name does not exist, fall back to use model name + last 4
+ // digits of UUID as friendly name.
+ if (description->friendly_name.empty() && !description->model_name.empty()) {
+ description->friendly_name = description->model_name;
+ if (!description->unique_id.empty()) {
+ description->friendly_name +=
+ '[' +
+ description->unique_id.substr(description->unique_id.length() - 4) +
+ ']';
+ }
+ DVLOG(2)
+ << "Fixed device description: created friendlyName from modelName.";
+ }
+
+ std::string xml_logging = xml_text;
+ if (ScrubDeviceDescription(xml_logging))
+ DVLOG(2) << "Device description: " << xml_logging;
+
+ std::string error = Validate(*description);
+ if (!error.empty()) {
+ DLOG(WARNING) << "Device description failed to validate: " << error;
+ return false;
+ }
+
+ return true;
+}
+
+void DeviceDescriptionService::OnDeviceDescriptionAvailable(
+ const extensions::api::dial::DialDeviceData& dial_device,
+ const extensions::api::dial::DialDeviceDescriptionData& description_data) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ DialDeviceDescription description;
+ if (ProcessDeviceDescription(dial_device, description_data.device_description,
+ description_data.app_url, &description)) {
+ observer_->OnDeviceDescriptionAvailable(dial_device.label(), description);
+ } else {
+ observer_->OnDeviceDescriptionFetchError(dial_device.label(),
+ "Failed to process fetch result");
+ }
+
+ device_description_fetcher_map_.erase(dial_device.label());
+}
+
+void DeviceDescriptionService::OnDeviceDescriptionFetchError(
+ const extensions::api::dial::DialDeviceData& dial_device,
+ const std::string& error_message) {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ DVLOG(2) << "OnDeviceDescriptionFetchError [label]: " << dial_device.label();
+
+ observer_->OnDeviceDescriptionFetchError(dial_device.label(), error_message);
+ device_description_fetcher_map_.erase(dial_device.label());
+}
+
+} // namespace media_router

Powered by Google App Engine
This is Rietveld 408576698