OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager .h" | 5 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager .h" |
6 | 6 |
7 #include "base/memory/ptr_util.h" | 7 #include "base/memory/ptr_util.h" |
8 #include "base/metrics/histogram_macros.h" | |
9 #include "base/strings/stringprintf.h" | |
8 #include "base/time/time.h" | 10 #include "base/time/time.h" |
11 #include "base/timer/timer.h" | |
9 #include "chrome/browser/chrome_notification_types.h" | 12 #include "chrome/browser/chrome_notification_types.h" |
10 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h" | 13 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h" |
11 #include "chrome/browser/sessions/session_tab_helper.h" | 14 #include "chrome/browser/sessions/session_tab_helper.h" |
12 #include "chrome/browser/tab_contents/retargeting_details.h" | 15 #include "chrome/browser/tab_contents/retargeting_details.h" |
13 #include "content/public/browser/navigation_details.h" | 16 #include "content/public/browser/navigation_details.h" |
14 #include "content/public/browser/notification_service.h" | 17 #include "content/public/browser/notification_service.h" |
15 #include "content/public/browser/notification_types.h" | 18 #include "content/public/browser/notification_types.h" |
16 #include "content/public/browser/render_frame_host.h" | 19 #include "content/public/browser/render_frame_host.h" |
17 #include "content/public/browser/render_process_host.h" | 20 #include "content/public/browser/render_process_host.h" |
18 #include "content/public/browser/web_contents.h" | 21 #include "content/public/browser/web_contents.h" |
19 | 22 |
20 using content::WebContents; | 23 using content::WebContents; |
21 | 24 |
22 namespace safe_browsing { | 25 namespace safe_browsing { |
23 | 26 |
27 namespace { | |
28 | |
29 const char kAttributionResultUMAPrefix[] = "SB2.ReferrerAttributionResult"; | |
30 const char kAttributionURLChainSizeUMAPrefix[] = "SB2.ReferrerURLChainSize"; | |
31 const char kReferrerHasInvalidTabIDUMAPrefix[] = "SB2.ReferrerHasInvalidTabID"; | |
32 const char kDownloadAttributionUMASuffix[] = "DownloadAttribution"; | |
33 | |
34 // Given when an event happened and its TTL, determine if it is already expired. | |
35 // Note, if for some reason this event's timestamp is in the future, this | |
36 // event's timestamp is invalid, hence we treat it as expired. | |
37 bool IsEventExpired(const base::Time& event_time, double ttl_in_second) { | |
38 double current_time_in_second = base::Time::Now().ToDoubleT(); | |
39 double event_time_in_second = event_time.ToDoubleT(); | |
40 if (current_time_in_second <= event_time_in_second) | |
41 return true; | |
42 return current_time_in_second - event_time_in_second > ttl_in_second; | |
43 } | |
44 | |
45 void RecordURLChainSize(const std::string& attribution_type, std::size_t size) { | |
46 std::string metric_name = base::StringPrintf( | |
47 "%s.%s", kAttributionURLChainSizeUMAPrefix, attribution_type.c_str()); | |
48 UMA_HISTOGRAM_COUNTS_100(metric_name, size); | |
Nathan Parker
2016/12/05 22:21:23
Does this work? I thought most of the UMA histogr
Jialiu Lin
2016/12/06 23:10:06
You're right... this would not work. Change to lit
| |
49 } | |
50 | |
51 void RecordAttributionResult( | |
52 const std::string& attribution_type, | |
53 SafeBrowsingNavigationObserverManager::AttributionResult type) { | |
54 std::string metric_name = base::StringPrintf( | |
55 "%s.%s", kAttributionResultUMAPrefix, attribution_type.c_str()); | |
56 UMA_HISTOGRAM_ENUMERATION( | |
57 metric_name, type, | |
58 SafeBrowsingNavigationObserverManager::ATTRIBUTION_FAILURE_TYPE_MAX); | |
59 } | |
60 | |
61 void RecordInvalidTabIDEvent(const std::string& attribution_type) { | |
62 std::string metric_name = base::StringPrintf( | |
63 "%s.%s", kReferrerHasInvalidTabIDUMAPrefix, attribution_type.c_str()); | |
64 UMA_HISTOGRAM_BOOLEAN(metric_name, true); | |
65 } | |
66 | |
67 } // namespace | |
68 | |
24 // The expiration period of a user gesture. Any user gesture that happened 1.0 | 69 // The expiration period of a user gesture. Any user gesture that happened 1.0 |
25 // second ago will be considered as expired and not relevant to upcoming | 70 // second ago will be considered as expired and not relevant to upcoming |
26 // navigation events. | 71 // navigation events. |
27 static const double kUserGestureTTLInSecond = 1.0; | 72 static const double kUserGestureTTLInSecond = 1.0; |
73 static const double kNavigationFootPrintTTLInSecond = 120.0; | |
Nathan Parker
2016/12/05 22:21:24
Add a comment describing how this is intended to b
Jialiu Lin
2016/12/06 23:10:06
Done.
| |
28 | 74 |
29 // static | 75 // static |
30 bool SafeBrowsingNavigationObserverManager::IsUserGestureExpired( | 76 bool SafeBrowsingNavigationObserverManager::IsUserGestureExpired( |
31 const base::Time& timestamp) { | 77 const base::Time& timestamp) { |
32 double now = base::Time::Now().ToDoubleT(); | 78 return IsEventExpired(timestamp, kUserGestureTTLInSecond); |
33 double timestamp_in_double = timestamp.ToDoubleT(); | |
34 | |
35 if (now <= timestamp_in_double) | |
36 return true; | |
37 return (now - timestamp_in_double) > kUserGestureTTLInSecond; | |
38 } | 79 } |
39 | 80 |
40 // static | 81 // static |
41 GURL SafeBrowsingNavigationObserverManager::ClearEmptyRef(const GURL& url) { | 82 GURL SafeBrowsingNavigationObserverManager::ClearEmptyRef(const GURL& url) { |
42 if (url.has_ref() && url.ref().empty()) { | 83 if (url.has_ref() && url.ref().empty()) { |
43 url::Replacements<char> replacements; | 84 url::Replacements<char> replacements; |
44 replacements.ClearRef(); | 85 replacements.ClearRef(); |
45 return url.ReplaceComponents(replacements); | 86 return url.ReplaceComponents(replacements); |
46 } | 87 } |
47 return url; | 88 return url; |
48 } | 89 } |
49 | 90 |
50 SafeBrowsingNavigationObserverManager::SafeBrowsingNavigationObserverManager() { | 91 SafeBrowsingNavigationObserverManager::SafeBrowsingNavigationObserverManager() { |
51 registrar_.Add(this, chrome::NOTIFICATION_RETARGETING, | 92 registrar_.Add(this, chrome::NOTIFICATION_RETARGETING, |
52 content::NotificationService::AllSources()); | 93 content::NotificationService::AllSources()); |
94 | |
95 // TODO(jialiul): call ScheduleNextCleanUpAfterInterval() when this class is | |
96 // ready to be hooked into SafeBrowsingService. | |
53 } | 97 } |
54 | 98 |
55 void SafeBrowsingNavigationObserverManager::RecordNavigationEvent( | 99 void SafeBrowsingNavigationObserverManager::RecordNavigationEvent( |
56 const GURL& nav_event_key, | 100 const GURL& nav_event_key, |
57 NavigationEvent* nav_event) { | 101 NavigationEvent* nav_event) { |
58 auto insertion_result = navigation_map_.insert( | 102 auto insertion_result = navigation_map_.insert( |
59 std::make_pair(nav_event_key, std::vector<NavigationEvent>())); | 103 std::make_pair(nav_event_key, std::vector<NavigationEvent>())); |
60 | 104 |
61 insertion_result.first->second.push_back(std::move(*nav_event)); | 105 insertion_result.first->second.push_back(std::move(*nav_event)); |
62 } | 106 } |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
97 } | 141 } |
98 } | 142 } |
99 // If this is a new IP of this host, and we added to the end of the vector. | 143 // If this is a new IP of this host, and we added to the end of the vector. |
100 insert_result.first->second.push_back( | 144 insert_result.first->second.push_back( |
101 ResolvedIPAddress(base::Time::Now(), ip)); | 145 ResolvedIPAddress(base::Time::Now(), ip)); |
102 } | 146 } |
103 | 147 |
104 void SafeBrowsingNavigationObserverManager::OnWebContentDestroyed( | 148 void SafeBrowsingNavigationObserverManager::OnWebContentDestroyed( |
105 content::WebContents* web_contents) { | 149 content::WebContents* web_contents) { |
106 user_gesture_map_.erase(web_contents); | 150 user_gesture_map_.erase(web_contents); |
107 // TODO (jialiul): Will add other clean up tasks shortly. | 151 } |
152 | |
153 void SafeBrowsingNavigationObserverManager::CleanUpStaleNavigationFootPrints() { | |
154 CleanUpNavigationEvents(); | |
155 CleanUpUserGestures(); | |
156 CleanUpIpAddresses(); | |
157 ScheduleNextCleanUpAfterInterval( | |
158 base::TimeDelta::FromSecondsD(kNavigationFootPrintTTLInSecond)); | |
159 } | |
160 | |
161 void SafeBrowsingNavigationObserverManager:: | |
162 AddReferrerChainToClientDownloadRequest( | |
163 const GURL& download_url, | |
164 content::WebContents* source_contents, | |
165 ClientDownloadRequest* request) { | |
166 std::string attribution_type(kDownloadAttributionUMASuffix); | |
Nathan Parker
2016/12/05 22:21:23
const.
Or even just
const char* attribution_type
Jialiu Lin
2016/12/06 23:10:06
no longer using this variable.
| |
167 int download_tab_id = SessionTabHelper::IdForTab(source_contents); | |
168 if (download_tab_id == -1) { | |
169 RecordInvalidTabIDEvent(attribution_type); | |
170 } | |
171 std::vector<ClientDownloadRequest::ReferrerChainEntry> attribution_chain; | |
172 AttributionResult result = IdentifyReferrerChain( | |
173 download_url, download_tab_id, 2, &attribution_chain); | |
Nathan Parker
2016/12/05 22:21:23
What is the 2? Add a comment, or make it a consta
Jialiu Lin
2016/12/06 23:10:06
Added a const
| |
174 RecordURLChainSize(attribution_type, attribution_chain.size()); | |
175 RecordAttributionResult(attribution_type, result); | |
176 for (auto entry : attribution_chain) | |
177 (*request->add_referrer_chain_entry()) = entry; | |
Nathan Parker
2016/12/05 22:21:23
You shouldn't need the parens.
Jialiu Lin
2016/12/06 23:10:06
Done.
| |
108 } | 178 } |
109 | 179 |
110 SafeBrowsingNavigationObserverManager:: | 180 SafeBrowsingNavigationObserverManager:: |
111 ~SafeBrowsingNavigationObserverManager() {} | 181 ~SafeBrowsingNavigationObserverManager() {} |
112 | 182 |
113 void SafeBrowsingNavigationObserverManager::Observe( | 183 void SafeBrowsingNavigationObserverManager::Observe( |
114 int type, | 184 int type, |
115 const content::NotificationSource& source, | 185 const content::NotificationSource& source, |
116 const content::NotificationDetails& details) { | 186 const content::NotificationDetails& details) { |
117 if (type == chrome::NOTIFICATION_RETARGETING) | 187 if (type == chrome::NOTIFICATION_RETARGETING) |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
159 OnUserGestureConsumed(it->first, it->second); | 229 OnUserGestureConsumed(it->first, it->second); |
160 } else { | 230 } else { |
161 nav_event.is_user_initiated = false; | 231 nav_event.is_user_initiated = false; |
162 } | 232 } |
163 | 233 |
164 auto insertion_result = navigation_map_.insert( | 234 auto insertion_result = navigation_map_.insert( |
165 std::make_pair(target_url, std::vector<NavigationEvent>())); | 235 std::make_pair(target_url, std::vector<NavigationEvent>())); |
166 insertion_result.first->second.push_back(std::move(nav_event)); | 236 insertion_result.first->second.push_back(std::move(nav_event)); |
167 } | 237 } |
168 | 238 |
239 void SafeBrowsingNavigationObserverManager::CleanUpNavigationEvents() { | |
240 // Remove any stale NavigationEnvent, if it lasts longer than | |
Nathan Parker
2016/12/05 22:21:24
nit: NavigationEvent.
And do you mean "lasts long
Jialiu Lin
2016/12/06 23:10:06
is older than...
| |
241 // kNavigationFootPrintTTLInSecond. | |
242 for (auto it = navigation_map_.begin(); it != navigation_map_.end();) { | |
243 it->second.erase(std::remove_if(it->second.begin(), it->second.end(), | |
244 [](const NavigationEvent& nav_event) { | |
245 return IsEventExpired( | |
246 nav_event.last_updated, | |
247 kNavigationFootPrintTTLInSecond); | |
248 }), | |
249 it->second.end()); | |
250 if (it->second.size() == 0) | |
251 it = navigation_map_.erase(it); | |
Nathan Parker
2016/12/05 22:21:24
This is clever -- avoids needing a second pass.
Jialiu Lin
2016/12/06 23:10:06
Acknowledged.
| |
252 else | |
253 ++it; | |
254 } | |
255 } | |
256 | |
257 void SafeBrowsingNavigationObserverManager::CleanUpUserGestures() { | |
258 for (auto it = user_gesture_map_.begin(); it != user_gesture_map_.end();) { | |
259 if (IsEventExpired(it->second, kUserGestureTTLInSecond)) | |
260 it = user_gesture_map_.erase(it); | |
261 else | |
262 ++it; | |
263 } | |
264 } | |
265 | |
266 void SafeBrowsingNavigationObserverManager::CleanUpIpAddresses() { | |
267 for (auto it = host_to_ip_map_.begin(); it != host_to_ip_map_.end();) { | |
268 it->second.erase(std::remove_if(it->second.begin(), it->second.end(), | |
269 [](const ResolvedIPAddress& resolved_ip) { | |
270 return IsEventExpired( | |
271 resolved_ip.timestamp, | |
272 kNavigationFootPrintTTLInSecond); | |
273 }), | |
274 it->second.end()); | |
275 if (it->second.size() == 0) | |
276 it = host_to_ip_map_.erase(it); | |
277 else | |
278 ++it; | |
279 } | |
280 } | |
281 | |
282 bool SafeBrowsingNavigationObserverManager::IsCleanUpScheduled() const { | |
283 return cleanup_timer_.IsRunning(); | |
284 } | |
285 | |
286 void SafeBrowsingNavigationObserverManager::ScheduleNextCleanUpAfterInterval( | |
287 base::TimeDelta interval) { | |
288 DCHECK(interval >= base::TimeDelta()); | |
289 cleanup_timer_.Stop(); | |
290 cleanup_timer_.Start( | |
291 FROM_HERE, interval, this, | |
292 &SafeBrowsingNavigationObserverManager::CleanUpStaleNavigationFootPrints); | |
293 } | |
294 | |
295 NavigationEvent* SafeBrowsingNavigationObserverManager::FindNavigationEvent( | |
296 const GURL& target_url, | |
297 int target_tab_id) { | |
298 auto it = navigation_map_.find(target_url); | |
299 if (it == navigation_map_.end()) { | |
300 return nullptr; | |
301 } | |
302 // Since navigation events are recorded in chronological order, we traverse | |
Nathan Parker
2016/12/05 22:21:23
Is there any way someone could confuse this by add
Jialiu Lin
2016/12/06 23:10:06
creis@ and I talked about this before.
a -> b->c->
| |
303 // the vector in reverse order to get the latest match. | |
304 for (auto rit = it->second.rbegin(); rit != it->second.rend(); ++rit) { | |
305 // If tab id is not valid, we only compare url, otherwise we compare both. | |
Nathan Parker
2016/12/05 22:21:23
I'm curious: When/why would tab_id be invalid?
Jialiu Lin
2016/12/06 23:10:06
I never see this happens in real settings, but it
| |
306 if (rit->destination_url == target_url && | |
307 (target_tab_id == -1 || rit->target_tab_id == target_tab_id)) { | |
308 // If both source_url and source_main_frame_url are empty, and this | |
309 // navigation is not triggered by user, a retargeting navigation probably | |
310 // causes this navigation. In this case, we skip this navigation event and | |
311 // looks for the retargeting navigation event. | |
312 if (rit->source_url.is_empty() && rit->source_main_frame_url.is_empty() && | |
313 !rit->is_user_initiated) | |
314 continue; | |
315 else | |
316 return &*rit; | |
317 } | |
318 } | |
319 return nullptr; | |
320 } | |
321 | |
322 void SafeBrowsingNavigationObserverManager::AddToReferrerChain( | |
323 std::vector<safe_browsing::ClientDownloadRequest::ReferrerChainEntry>* | |
324 referrer_chain, | |
325 NavigationEvent* nav_event, | |
326 ClientDownloadRequest::ReferrerChainEntry::URLType type) { | |
327 ClientDownloadRequest::ReferrerChainEntry referrer_chain_entry; | |
328 referrer_chain_entry.set_url(nav_event->destination_url.spec()); | |
329 referrer_chain_entry.set_type(type); | |
330 auto ip_it = host_to_ip_map_.find(nav_event->destination_url.host()); | |
331 if (ip_it != host_to_ip_map_.end()) { | |
332 for (ResolvedIPAddress entry : ip_it->second) { | |
333 referrer_chain_entry.add_ip_address(entry.ip); | |
334 } | |
335 } | |
336 // Since we only track navigation to landing referrer, we will not log the | |
337 // referrer of the landing referrer page. | |
338 if (type != ClientDownloadRequest::ReferrerChainEntry::LANDING_REFERRER) { | |
339 referrer_chain_entry.set_referrer_url(nav_event->source_url.spec()); | |
340 referrer_chain_entry.set_referrer_main_frame_url( | |
341 nav_event->source_main_frame_url.spec()); | |
342 } | |
343 referrer_chain_entry.set_is_retargeting(nav_event->source_tab_id != | |
344 nav_event->target_tab_id); | |
345 referrer_chain_entry.set_navigation_time_msec( | |
346 nav_event->last_updated.ToJavaTime()); | |
347 referrer_chain->push_back(referrer_chain_entry); | |
348 } | |
349 | |
350 SafeBrowsingNavigationObserverManager::AttributionResult | |
351 SafeBrowsingNavigationObserverManager::IdentifyReferrerChain( | |
352 const GURL& target_url, | |
353 int target_tab_id, | |
354 int user_gesture_count_max, | |
355 std::vector<ClientDownloadRequest::ReferrerChainEntry>* referrer_chain) { | |
356 if (!target_url.is_valid()) | |
357 return INVALID_URL; | |
358 | |
359 NavigationEvent* nav_event = FindNavigationEvent(target_url, target_tab_id); | |
360 if (!nav_event) { | |
361 // We cannot find a single navigation event related to this download. | |
362 return NAVIGATION_EVENT_NOT_FOUND; | |
363 } | |
364 | |
365 AddToReferrerChain(referrer_chain, nav_event, | |
366 ClientDownloadRequest::ReferrerChainEntry::DOWNLOAD_URL); | |
367 AttributionResult result = SUCCESS; | |
368 int user_gesture_count = 0; | |
369 while (user_gesture_count < user_gesture_count_max) { | |
370 // Back trace to the next nav_event that initiated by user. | |
371 while (!nav_event->is_user_initiated) { | |
372 nav_event = | |
373 FindNavigationEvent(nav_event->source_url, nav_event->source_tab_id); | |
374 if (!nav_event) | |
375 return result; | |
376 AddToReferrerChain( | |
377 referrer_chain, nav_event, | |
378 nav_event->has_server_redirect | |
379 ? ClientDownloadRequest::ReferrerChainEntry::SERVER_REDIRECT | |
380 : ClientDownloadRequest::ReferrerChainEntry::CLIENT_REDIRECT); | |
381 } | |
382 user_gesture_count++; | |
383 // If the source_url and source_main_frame_url of current navigation event | |
384 // is empty, and is_user_initiated is true, this is a browser initiated | |
385 // navigation (e.g. trigged by typing in address bar, clicking on bookmark, | |
386 // etc). We reached the end of the referrer chain. | |
387 if (nav_event->source_url.is_empty() && | |
388 nav_event->source_main_frame_url.is_empty()) { | |
389 DCHECK(nav_event->is_user_initiated); | |
390 return result; | |
391 } | |
392 nav_event = | |
393 FindNavigationEvent(nav_event->source_url, nav_event->source_tab_id); | |
394 if (!nav_event) | |
395 return result; | |
396 AddToReferrerChain( | |
397 referrer_chain, nav_event, | |
398 user_gesture_count == 1 | |
399 ? ClientDownloadRequest::ReferrerChainEntry::LANDING_PAGE | |
400 : ClientDownloadRequest::ReferrerChainEntry::LANDING_REFERRER); | |
401 result = user_gesture_count == 1 ? SUCCESS_LANDING_PAGE | |
402 : SUCCESS_LANDING_REFERRER; | |
403 } | |
404 return result; | |
405 } | |
406 | |
169 } // namespace safe_browsing | 407 } // namespace safe_browsing |
OLD | NEW |