Chromium Code Reviews| OLD | NEW |
|---|---|
| (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/discovery/dial/device_description_service. h" | |
| 6 | |
| 7 #include "base/strings/string_util.h" | |
| 8 #include "chrome/browser/media/router/discovery/dial/device_description_fetcher. h" | |
| 9 #include "chrome/browser/media/router/discovery/dial/safe_dial_device_descriptio n_parser.h" | |
| 10 #include "net/base/ip_address.h" | |
| 11 #include "url/gurl.h" | |
| 12 | |
| 13 namespace { | |
| 14 | |
| 15 // How long to cache a device description. | |
| 16 // TODO: Use value from chrome.dial when http://crbug.com/165289 is fixed. | |
|
mark a. foltz
2017/04/12 00:17:17
I'm going to close this as WontFix.
zhaobin
2017/04/18 06:58:26
Done.
| |
| 17 const int kDeviceDescriptionCacheTimeMillis = 30 * 60 * 1000; | |
| 18 | |
| 19 // Replaces "<element_name>content</element_name>" with | |
| 20 // "<element_name>***</element_name>" | |
| 21 void Scrub(const std::string& element_name, std::string& xml_text) { | |
|
imcheng
2017/04/12 19:33:50
nit: |xml_text| should be passed in as a pointer.
zhaobin
2017/04/18 06:58:25
Done.
| |
| 22 size_t pos = xml_text.find("<" + element_name + ">"); | |
| 23 size_t end_pos = xml_text.find("</" + element_name + ">"); | |
| 24 size_t start_pos = pos + element_name.length() + 2; | |
|
mark a. foltz
2017/04/12 00:17:17
What happens if pos == std::string::npos?
zhaobin
2017/04/18 06:58:26
Done.
| |
| 25 | |
| 26 if (pos != std::string::npos && end_pos > start_pos) | |
| 27 xml_text.replace(start_pos, end_pos - start_pos, "***"); | |
| 28 } | |
| 29 | |
| 30 // Removes unique identifiers from the device description. | |
| 31 // |xml_text|: xmlDoc The device description. | |
| 32 // Returns false if <UDN> or <serialNumber> field does not exist in |xml_text|. | |
|
mark a. foltz
2017/04/12 00:17:17
This needs to be updated
zhaobin
2017/04/18 06:58:25
Done.
| |
| 33 std::string ScrubDeviceDescriptionXml(const std::string& xml_text) { | |
| 34 std::string scrubbed_xml(xml_text); | |
| 35 Scrub("UDN", scrubbed_xml); | |
| 36 Scrub("serialNumber", scrubbed_xml); | |
| 37 return scrubbed_xml; | |
| 38 } | |
| 39 | |
| 40 // Validates whether a URL is valid for fetching device descriptions. | |
| 41 // |descriptionUrl|: Device description URL. | |
| 42 // Returns true if descriptionUrl is valid. | |
| 43 static bool IsValidDeviceDescriptionUrl(const GURL& device_description_url) { | |
|
imcheng
2017/04/12 19:33:50
Functions in anonymous namespace do not need to be
zhaobin
2017/04/18 06:58:25
Done.
| |
| 44 if (!device_description_url.SchemeIs(url::kHttpScheme)) | |
| 45 return false; | |
| 46 | |
| 47 net::IPAddress address; | |
| 48 if (!net::ParseURLHostnameToAddress(device_description_url.host(), &address)) | |
|
mark a. foltz
2017/04/12 00:17:17
This check could go earlier in DialService::ParseR
zhaobin
2017/04/18 06:58:26
Done.
| |
| 49 return false; | |
| 50 | |
| 51 return address.IsReserved(); | |
| 52 } | |
| 53 | |
| 54 static bool IsValidAppUrl(const GURL& app_url, const std::string& ip_address) { | |
| 55 return app_url.SchemeIs(url::kHttpScheme) && app_url.host() == ip_address; | |
| 56 } | |
| 57 | |
| 58 // Checks mandatory fields. | |
|
mark a. foltz
2017/04/12 00:17:17
Please document meaning of return value.
zhaobin
2017/04/18 06:58:25
Done.
| |
| 59 static std::string Validate( | |
|
imcheng
2017/04/12 19:33:50
Maybe ValidateParsedDeviceDescription to be more c
zhaobin
2017/04/18 06:58:25
Done.
| |
| 60 const std::string& expected_ip_address, | |
| 61 const media_router::ParsedDialDeviceDescription& description_data) { | |
| 62 if (description_data.unique_id.empty()) { | |
| 63 return "Missing uniqueId"; | |
|
imcheng
2017/04/12 19:33:50
IMO, enum is slightly preferable to strings for in
zhaobin
2017/04/18 06:58:25
Done.
| |
| 64 } | |
| 65 if (description_data.friendly_name.empty()) { | |
| 66 return "Missing friendlyName"; | |
| 67 } | |
| 68 if (!description_data.app_url.is_valid()) { | |
| 69 return "Missing appUrl"; | |
| 70 } | |
| 71 if (!IsValidAppUrl(description_data.app_url, expected_ip_address)) { | |
| 72 return "Invalid appUrl"; | |
| 73 } | |
| 74 return ""; | |
| 75 } | |
| 76 | |
| 77 static std::string ToString( | |
|
mark a. foltz
2017/04/12 00:17:17
If this is only use with D*LOG statements, use #if
imcheng
2017/04/12 19:33:50
Naming suggestion: CachedDeviceDescriptionToString
zhaobin
2017/04/18 06:58:25
Done.
zhaobin
2017/04/18 06:58:26
Done.
| |
| 78 const media_router::CachedParsedDialDeviceDescription& cached_data) { | |
| 79 std::stringstream ss; | |
| 80 ss << "CachedDialDeviceDescription [unique_id]: " | |
| 81 << cached_data.description_data.unique_id | |
| 82 << " [friendly_name]: " << cached_data.description_data.friendly_name | |
| 83 << " [model_name]: " << cached_data.description_data.model_name | |
| 84 << " [device_type]: " << cached_data.description_data.device_type | |
| 85 << " [app_url]: " << cached_data.description_data.app_url | |
| 86 << " [fetch_time_millis]: " << cached_data.fetch_time_millis | |
| 87 << " [expire_time_millis]: " << cached_data.expire_time_millis | |
| 88 << " [config_id]: "; | |
|
mark a. foltz
2017/04/12 00:17:17
Nit: Maybe only include this if config_id is set.
zhaobin
2017/04/18 06:58:26
Done.
| |
| 89 | |
| 90 if (cached_data.config_id) | |
| 91 ss << cached_data.config_id.value(); | |
| 92 | |
| 93 return ss.str(); | |
| 94 } | |
| 95 | |
| 96 } // namespace | |
| 97 | |
| 98 namespace media_router { | |
| 99 | |
| 100 DeviceDescriptionService::DeviceDescriptionService( | |
| 101 DeviceDescriptionParseSuccessCallback success_cb, | |
| 102 DeviceDescriptionParseErrorCallback error_cb) | |
| 103 : success_cb_(success_cb), error_cb_(error_cb) {} | |
| 104 | |
| 105 DeviceDescriptionService::~DeviceDescriptionService() = default; | |
| 106 | |
| 107 void DeviceDescriptionService::GetDeviceDescriptions( | |
| 108 const std::vector<DialDeviceData>& devices, | |
|
imcheng
2017/04/12 19:33:50
Is the idea that this gets called with the full de
zhaobin
2017/04/18 06:58:26
Done.
| |
| 109 net::URLRequestContextGetter* request_context) { | |
| 110 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 111 | |
| 112 std::map<std::string, std::unique_ptr<DeviceDescriptionFetcher>> | |
| 113 existing_fetcher_map; | |
| 114 for (const auto& device_data : devices) { | |
| 115 // Keep fetcher if it is for |device_data.label()| and has the same Url. | |
|
mark a. foltz
2017/04/12 00:17:17
nit: URL
zhaobin
2017/04/18 06:58:25
Done.
| |
| 116 const auto& it = device_description_fetcher_map_.find(device_data.label()); | |
| 117 if (it != device_description_fetcher_map_.end() && | |
| 118 it->second->device_description_url() == | |
| 119 device_data.device_description_url()) { | |
| 120 existing_fetcher_map.insert( | |
| 121 std::make_pair(device_data.label(), std::move(it->second))); | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 // Remove all out dated fetcheres. | |
|
mark a. foltz
2017/04/12 00:17:17
typo in fetchers
zhaobin
2017/04/18 06:58:25
Done.
| |
| 126 device_description_fetcher_map_ = std::move(existing_fetcher_map); | |
|
mark a. foltz
2017/04/12 00:17:17
Will this delete all fetchers whose labels are not
zhaobin
2017/04/18 06:58:26
Seems to be safe. net::URLFetcher says "You may ca
| |
| 127 | |
| 128 for (const auto& device_data : devices) { | |
| 129 CachedParsedDialDeviceDescription description_data; | |
| 130 if (CheckAndUpdateCache(device_data, &description_data)) { | |
| 131 // Get device description from cache. | |
| 132 success_cb_.Run(device_data, description_data.description_data); | |
| 133 continue; | |
| 134 } | |
| 135 | |
| 136 FetchDeviceDescription(device_data, request_context); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 void DeviceDescriptionService::FetchDeviceDescription( | |
| 141 const DialDeviceData& device_data, | |
| 142 net::URLRequestContextGetter* request_context) { | |
| 143 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 144 | |
| 145 if (!IsValidDeviceDescriptionUrl(device_data.device_description_url())) { | |
| 146 std::string error_message = "Invalid device description URL: " + | |
| 147 device_data.device_description_url().spec(); | |
| 148 OnDeviceDescriptionFetchError(device_data, error_message); | |
| 149 return; | |
| 150 } | |
| 151 | |
| 152 auto device_description_fetcher = base::MakeUnique<DeviceDescriptionFetcher>( | |
| 153 device_data.device_description_url(), request_context, | |
| 154 base::BindOnce( | |
| 155 &DeviceDescriptionService::OnDeviceDescriptionFetchComplete, | |
| 156 base::Unretained(this), device_data), | |
| 157 base::BindOnce(&DeviceDescriptionService::OnDeviceDescriptionFetchError, | |
| 158 base::Unretained(this), device_data)); | |
| 159 | |
| 160 device_description_fetcher->Start(); | |
| 161 device_description_fetcher_map_.insert(std::make_pair( | |
| 162 device_data.label(), std::move(device_description_fetcher))); | |
| 163 } | |
| 164 | |
| 165 bool DeviceDescriptionService::CheckAndUpdateCache( | |
| 166 const DialDeviceData& device_data, | |
| 167 CachedParsedDialDeviceDescription* out_description) { | |
| 168 const auto& it = description_map_.find(device_data.label()); | |
| 169 if (it == description_map_.end()) | |
| 170 return false; | |
| 171 | |
| 172 // If the entry's configId does not match, or it has expired, remove it. | |
|
mark a. foltz
2017/04/12 00:17:17
nit: config_id
zhaobin
2017/04/18 06:58:25
Done.
| |
| 173 if (it->second.config_id != device_data.config_id() || | |
| 174 base::Time::Now() >= it->second.expire_time_millis) { | |
|
imcheng
2017/04/12 19:33:50
General comment: for tests you might find it usefu
zhaobin
2017/04/18 06:58:25
Done.
| |
| 175 DVLOG(2) << "Removing invalid entry " << it->first; | |
| 176 description_map_.erase(it); | |
|
mark a. foltz
2017/04/12 00:17:17
It looks like there will be some expired cached en
zhaobin
2017/04/18 06:58:26
On 2017/04/12 00:17:17, mark a. foltz wrote:
> It
| |
| 177 return false; | |
| 178 } | |
| 179 | |
| 180 // Entry is valid. | |
| 181 (*out_description) = it->second; | |
|
mark a. foltz
2017/04/12 00:17:17
It it necessary to make a copy here, or could Chec
zhaobin
2017/04/18 06:58:26
Done.
| |
| 182 return true; | |
| 183 } | |
| 184 | |
| 185 void DeviceDescriptionService::OnParsedDeviceDescription( | |
| 186 const DialDeviceData& device_data, | |
| 187 const GURL& app_url, | |
| 188 chrome::mojom::DialDeviceDescriptionPtr parsed_device_description) { | |
| 189 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 190 | |
| 191 if (!parsed_device_description) { | |
| 192 error_cb_.Run(device_data.label(), | |
| 193 "Failed to parse device description xml"); | |
| 194 return; | |
| 195 } | |
| 196 | |
| 197 ParsedDialDeviceDescription description_data; | |
| 198 description_data.unique_id = parsed_device_description->unique_id; | |
| 199 description_data.friendly_name = parsed_device_description->friendly_name; | |
| 200 description_data.model_name = parsed_device_description->model_name; | |
| 201 description_data.device_type = parsed_device_description->device_type; | |
| 202 description_data.app_url = app_url; | |
| 203 | |
| 204 std::string error = | |
| 205 Validate(device_data.device_description_url().host(), description_data); | |
| 206 | |
| 207 if (error.empty()) { | |
|
mark a. foltz
2017/04/12 00:17:17
Nit: I would reverse the two cases of the if() and
zhaobin
2017/04/18 06:58:26
Done.
| |
| 208 CachedParsedDialDeviceDescription cached_description_data; | |
| 209 cached_description_data.fetch_time_millis = base::Time::Now(); | |
| 210 cached_description_data.expire_time_millis = | |
| 211 cached_description_data.fetch_time_millis + | |
| 212 base::TimeDelta::FromMilliseconds(kDeviceDescriptionCacheTimeMillis); | |
| 213 cached_description_data.config_id = device_data.config_id(); | |
| 214 cached_description_data.description_data = description_data; | |
| 215 | |
| 216 DVLOG(2) << "Got device description for " << device_data.label(); | |
|
mark a. foltz
2017/04/12 00:17:17
Nit: These can be combined into one statement.
zhaobin
2017/04/18 06:58:26
Done.
| |
| 217 DVLOG(2) << "... device description was: " | |
| 218 << ToString(cached_description_data); | |
| 219 | |
| 220 // Stick it in the cache if the description has a config (version) id. | |
| 221 // NOTE: We could cache descriptions without a config id and rely on the | |
| 222 // timeout to eventually update the cache. | |
|
mark a. foltz
2017/04/12 00:17:17
This would be fine with me. The only piece of dat
zhaobin
2017/04/18 06:58:26
Acknowledged.
| |
| 223 if (cached_description_data.config_id) { | |
| 224 DVLOG(2) << "Caching device description for " << device_data.label(); | |
| 225 description_map_.insert( | |
|
mark a. foltz
2017/04/12 00:17:17
Can we set a maximum size on the number of cached
zhaobin
2017/04/18 06:58:25
Done.
| |
| 226 std::make_pair(device_data.label(), cached_description_data)); | |
| 227 } | |
| 228 | |
| 229 success_cb_.Run(device_data, description_data); | |
| 230 } else { | |
| 231 DLOG(WARNING) << "Device description failed to validate: " << error; | |
| 232 error_cb_.Run(device_data.label(), "Failed to process fetch result"); | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 void DeviceDescriptionService::OnDeviceDescriptionFetchComplete( | |
| 237 const DialDeviceData& device_data, | |
| 238 const DialDeviceDescriptionData& description_data) { | |
| 239 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 240 | |
| 241 scoped_refptr<SafeDialDeviceDescriptionParser> parser( | |
|
mark a. foltz
2017/04/12 00:17:17
What keeps the parser alive after calling Start()?
zhaobin
2017/04/18 06:58:26
It is bound insdie parser->start() (https://codere
| |
| 242 new SafeDialDeviceDescriptionParser()); | |
|
mark a. foltz
2017/04/12 00:17:17
Could this potentially spawn multiple utility proc
zhaobin
2017/04/18 06:58:25
Done.
| |
| 243 | |
| 244 parser->Start(description_data.device_description, | |
| 245 base::Bind(&DeviceDescriptionService::OnParsedDeviceDescription, | |
| 246 base::Unretained(this), device_data, | |
|
mark a. foltz
2017/04/12 00:17:17
What prevents the callback from being run after |t
zhaobin
2017/04/18 06:58:26
(I think) |this| is owned by DialMediaSinkService,
| |
| 247 description_data.app_url)); | |
| 248 | |
| 249 std::string xml_logging = | |
|
imcheng
2017/04/12 19:33:50
Similar to above, we want to guard this with #ifnd
zhaobin
2017/04/18 06:58:25
Done.
| |
| 250 ScrubDeviceDescriptionXml(description_data.device_description); | |
| 251 DVLOG(2) << "Device description: " << xml_logging; | |
| 252 | |
| 253 device_description_fetcher_map_.erase(device_data.label()); | |
| 254 } | |
| 255 | |
| 256 void DeviceDescriptionService::OnDeviceDescriptionFetchError( | |
| 257 const DialDeviceData& device_data, | |
| 258 const std::string& error_message) { | |
| 259 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 260 | |
| 261 DVLOG(2) << "OnDeviceDescriptionFetchError [label]: " << device_data.label(); | |
| 262 | |
| 263 error_cb_.Run(device_data.label(), error_message); | |
| 264 device_description_fetcher_map_.erase(device_data.label()); | |
| 265 } | |
| 266 | |
| 267 CachedParsedDialDeviceDescription::CachedParsedDialDeviceDescription() = | |
| 268 default; | |
| 269 CachedParsedDialDeviceDescription::CachedParsedDialDeviceDescription( | |
| 270 const CachedParsedDialDeviceDescription& other) = default; | |
| 271 CachedParsedDialDeviceDescription::~CachedParsedDialDeviceDescription() = | |
| 272 default; | |
| 273 | |
| 274 } // namespace media_router | |
| OLD | NEW |