| 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" | 8 #include "base/metrics/histogram_macros.h" |
| 9 #include "base/strings/stringprintf.h" | 9 #include "base/strings/stringprintf.h" |
| 10 #include "base/time/time.h" | 10 #include "base/time/time.h" |
| (...skipping 24 matching lines...) Expand all Loading... |
| 35 // Note, if for some reason this event's timestamp is in the future, this | 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. | 36 // event's timestamp is invalid, hence we treat it as expired. |
| 37 bool IsEventExpired(const base::Time& event_time, double ttl_in_second) { | 37 bool IsEventExpired(const base::Time& event_time, double ttl_in_second) { |
| 38 double current_time_in_second = base::Time::Now().ToDoubleT(); | 38 double current_time_in_second = base::Time::Now().ToDoubleT(); |
| 39 double event_time_in_second = event_time.ToDoubleT(); | 39 double event_time_in_second = event_time.ToDoubleT(); |
| 40 if (current_time_in_second <= event_time_in_second) | 40 if (current_time_in_second <= event_time_in_second) |
| 41 return true; | 41 return true; |
| 42 return current_time_in_second - event_time_in_second > ttl_in_second; | 42 return current_time_in_second - event_time_in_second > ttl_in_second; |
| 43 } | 43 } |
| 44 | 44 |
| 45 // Helper function to determine if the URL type should be LANDING_REFERRER or |
| 46 // LANDING_PAGE, and modify AttributionResult accordingly. |
| 47 ReferrerChainEntry::URLType GetURLTypeAndAdjustAttributionResult( |
| 48 bool at_user_gesture_limit, |
| 49 SafeBrowsingNavigationObserverManager::AttributionResult* out_result) { |
| 50 // Landing page of a download refers to the page user directly interacts |
| 51 // with to trigger this download (e.g. clicking on download button). Landing |
| 52 // referrer page is the one user interacts with right before navigating to |
| 53 // the landing page. |
| 54 // Since we are tracing navigations backwards, if we've reached |
| 55 // user gesture limit before this navigation event, this is a navigation |
| 56 // leading to the landing referrer page, otherwise it leads to landing page. |
| 57 if (at_user_gesture_limit) { |
| 58 *out_result = |
| 59 SafeBrowsingNavigationObserverManager::SUCCESS_LANDING_REFERRER; |
| 60 return ReferrerChainEntry::LANDING_REFERRER; |
| 61 } else { |
| 62 *out_result = SafeBrowsingNavigationObserverManager::SUCCESS_LANDING_PAGE; |
| 63 return ReferrerChainEntry::LANDING_PAGE; |
| 64 } |
| 65 } |
| 66 |
| 45 } // namespace | 67 } // namespace |
| 46 | 68 |
| 47 // 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 |
| 48 // second ago is considered as expired and not relevant to upcoming navigation | 70 // second ago is considered as expired and not relevant to upcoming navigation |
| 49 // events. | 71 // events. |
| 50 static const double kUserGestureTTLInSecond = 1.0; | 72 static const double kUserGestureTTLInSecond = 1.0; |
| 51 // The expiration period of navigation events and resolved IP addresses. Any | 73 // The expiration period of navigation events and resolved IP addresses. Any |
| 52 // navigation related records that happened 2 minutes ago are considered as | 74 // navigation related records that happened 2 minutes ago are considered as |
| 53 // expired. So we clean up these navigation footprints every 2 minutes. | 75 // expired. So we clean up these navigation footprints every 2 minutes. |
| 54 static const double kNavigationFootprintTTLInSecond = 120.0; | 76 static const double kNavigationFootprintTTLInSecond = 120.0; |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 115 void SafeBrowsingNavigationObserverManager::OnUserGestureConsumed( | 137 void SafeBrowsingNavigationObserverManager::OnUserGestureConsumed( |
| 116 content::WebContents* web_contents, | 138 content::WebContents* web_contents, |
| 117 const base::Time& timestamp) { | 139 const base::Time& timestamp) { |
| 118 auto it = user_gesture_map_.find(web_contents); | 140 auto it = user_gesture_map_.find(web_contents); |
| 119 // Remove entry from |user_gesture_map_| as a user_gesture is consumed by | 141 // Remove entry from |user_gesture_map_| as a user_gesture is consumed by |
| 120 // a navigation event. | 142 // a navigation event. |
| 121 if (it != user_gesture_map_.end() && timestamp >= it->second) | 143 if (it != user_gesture_map_.end() && timestamp >= it->second) |
| 122 user_gesture_map_.erase(it); | 144 user_gesture_map_.erase(it); |
| 123 } | 145 } |
| 124 | 146 |
| 147 bool SafeBrowsingNavigationObserverManager::HasUserGesture( |
| 148 content::WebContents* web_contents) { |
| 149 if (!web_contents) |
| 150 return false; |
| 151 auto it = user_gesture_map_.find(web_contents); |
| 152 if (it != user_gesture_map_.end() && |
| 153 !IsEventExpired(it->second, kUserGestureTTLInSecond)) { |
| 154 return true; |
| 155 } |
| 156 return false; |
| 157 } |
| 158 |
| 125 void SafeBrowsingNavigationObserverManager::RecordHostToIpMapping( | 159 void SafeBrowsingNavigationObserverManager::RecordHostToIpMapping( |
| 126 const std::string& host, | 160 const std::string& host, |
| 127 const std::string& ip) { | 161 const std::string& ip) { |
| 128 auto insert_result = host_to_ip_map_.insert( | 162 auto insert_result = host_to_ip_map_.insert( |
| 129 std::make_pair(host, std::vector<ResolvedIPAddress>())); | 163 std::make_pair(host, std::vector<ResolvedIPAddress>())); |
| 130 if (!insert_result.second) { | 164 if (!insert_result.second) { |
| 131 // host_to_ip_map already contains this key. | 165 // host_to_ip_map already contains this key. |
| 132 // If this IP is already in the vector, we update its timestamp. | 166 // If this IP is already in the vector, we update its timestamp. |
| 133 for (auto& vector_entry : insert_result.first->second) { | 167 for (auto& vector_entry : insert_result.first->second) { |
| 134 if (vector_entry.ip == host) { | 168 if (vector_entry.ip == host) { |
| (...skipping 14 matching lines...) Expand all Loading... |
| 149 | 183 |
| 150 void SafeBrowsingNavigationObserverManager::CleanUpStaleNavigationFootprints() { | 184 void SafeBrowsingNavigationObserverManager::CleanUpStaleNavigationFootprints() { |
| 151 CleanUpNavigationEvents(); | 185 CleanUpNavigationEvents(); |
| 152 CleanUpUserGestures(); | 186 CleanUpUserGestures(); |
| 153 CleanUpIpAddresses(); | 187 CleanUpIpAddresses(); |
| 154 ScheduleNextCleanUpAfterInterval( | 188 ScheduleNextCleanUpAfterInterval( |
| 155 base::TimeDelta::FromSecondsD(kNavigationFootprintTTLInSecond)); | 189 base::TimeDelta::FromSecondsD(kNavigationFootprintTTLInSecond)); |
| 156 } | 190 } |
| 157 | 191 |
| 158 SafeBrowsingNavigationObserverManager::AttributionResult | 192 SafeBrowsingNavigationObserverManager::AttributionResult |
| 159 SafeBrowsingNavigationObserverManager::IdentifyReferrerChain( | 193 SafeBrowsingNavigationObserverManager::IdentifyReferrerChainForDownload( |
| 160 const GURL& target_url, | 194 const GURL& target_url, |
| 161 int target_tab_id, | 195 int target_tab_id, |
| 162 int user_gesture_count_limit, | 196 int user_gesture_count_limit, |
| 163 std::vector<ReferrerChainEntry>* out_referrer_chain) { | 197 std::vector<ReferrerChainEntry>* out_referrer_chain) { |
| 164 if (!target_url.is_valid()) | 198 if (!target_url.is_valid()) |
| 165 return INVALID_URL; | 199 return INVALID_URL; |
| 166 | 200 |
| 167 NavigationEvent* nav_event = FindNavigationEvent( | 201 NavigationEvent* nav_event = FindNavigationEvent( |
| 168 target_url, | 202 target_url, |
| 169 GURL(), | 203 GURL(), |
| 170 target_tab_id); | 204 target_tab_id); |
| 171 if (!nav_event) { | 205 if (!nav_event) { |
| 172 // We cannot find a single navigation event related to this download. | 206 // We cannot find a single navigation event related to this download. |
| 173 return NAVIGATION_EVENT_NOT_FOUND; | 207 return NAVIGATION_EVENT_NOT_FOUND; |
| 174 } | 208 } |
| 209 AttributionResult result = SUCCESS; |
| 210 AddToReferrerChain(out_referrer_chain, nav_event, |
| 211 ReferrerChainEntry::DOWNLOAD_URL); |
| 212 int user_gesture_count = 0; |
| 213 GetRemainingReferrerChain( |
| 214 nav_event, |
| 215 user_gesture_count, |
| 216 user_gesture_count_limit, |
| 217 out_referrer_chain, |
| 218 &result); |
| 219 return result; |
| 220 } |
| 221 |
| 222 SafeBrowsingNavigationObserverManager::AttributionResult |
| 223 SafeBrowsingNavigationObserverManager::IdentifyReferrerChainForPPAPIDownload( |
| 224 const GURL& initiating_frame_url, |
| 225 int tab_id, |
| 226 bool has_user_gesture, |
| 227 int user_gesture_count_limit, |
| 228 std::vector<ReferrerChainEntry>* out_referrer_chain) { |
| 229 if (!initiating_frame_url.is_valid()) |
| 230 return INVALID_URL; |
| 231 |
| 232 NavigationEvent* nav_event = |
| 233 FindNavigationEvent(initiating_frame_url, GURL(), tab_id); |
| 234 if (!nav_event) { |
| 235 // We cannot find a single navigation event related to this download. |
| 236 return NAVIGATION_EVENT_NOT_FOUND; |
| 237 } |
| 175 | 238 |
| 176 AddToReferrerChain(out_referrer_chain, nav_event, | |
| 177 ReferrerChainEntry::DOWNLOAD_URL); | |
| 178 AttributionResult result = SUCCESS; | 239 AttributionResult result = SUCCESS; |
| 240 |
| 179 int user_gesture_count = 0; | 241 int user_gesture_count = 0; |
| 180 while (user_gesture_count < user_gesture_count_limit) { | 242 // If this initiating_frame has user gesture, we consider this as the landing |
| 181 // Back trace to the next nav_event that was initiated by the user. | 243 // page of the PPAPI download. |
| 182 while (!nav_event->is_user_initiated) { | 244 if (has_user_gesture) { |
| 183 nav_event = | 245 user_gesture_count = 1; |
| 184 FindNavigationEvent(nav_event->source_url, | 246 AddToReferrerChain(out_referrer_chain, nav_event, |
| 185 nav_event->source_main_frame_url, | 247 GetURLTypeAndAdjustAttributionResult( |
| 186 nav_event->source_tab_id); | 248 user_gesture_count == user_gesture_count_limit, |
| 187 if (!nav_event) | 249 &result)); |
| 188 return result; | 250 } else { |
| 189 AddToReferrerChain(out_referrer_chain, nav_event, | 251 AddToReferrerChain(out_referrer_chain, nav_event, |
| 190 nav_event->has_server_redirect | 252 nav_event->has_server_redirect |
| 191 ? ReferrerChainEntry::SERVER_REDIRECT | 253 ? ReferrerChainEntry::SERVER_REDIRECT |
| 192 : ReferrerChainEntry::CLIENT_REDIRECT); | 254 : ReferrerChainEntry::CLIENT_REDIRECT); |
| 193 } | 255 } |
| 194 | 256 |
| 195 user_gesture_count++; | 257 GetRemainingReferrerChain( |
| 196 | 258 nav_event, |
| 197 // If the source_url and source_main_frame_url of current navigation event | 259 user_gesture_count, |
| 198 // are empty, and is_user_initiated is true, this is a browser initiated | 260 user_gesture_count_limit, |
| 199 // navigation (e.g. trigged by typing in address bar, clicking on bookmark, | 261 out_referrer_chain, |
| 200 // etc). We reached the end of the referrer chain. | 262 &result); |
| 201 if (nav_event->source_url.is_empty() && | |
| 202 nav_event->source_main_frame_url.is_empty()) { | |
| 203 DCHECK(nav_event->is_user_initiated); | |
| 204 return result; | |
| 205 } | |
| 206 | |
| 207 nav_event = | |
| 208 FindNavigationEvent(nav_event->source_url, | |
| 209 nav_event->source_main_frame_url, | |
| 210 nav_event->source_tab_id); | |
| 211 if (!nav_event) | |
| 212 return result; | |
| 213 | |
| 214 // Landing page of a download refers to the page user directly interacts | |
| 215 // with to trigger this download (e.g. clicking on download button). Landing | |
| 216 // referrer page is the one user interacts with right before navigating to | |
| 217 // the landing page. | |
| 218 // Since we are tracing navigations backwards, if we've encountered 1 user | |
| 219 // gesture before this navigation event, this is a navigation leading to the | |
| 220 // landing page. If we've encountered 2 user gestures, it leads to landing | |
| 221 // referrer page. | |
| 222 if (user_gesture_count == 1) { | |
| 223 AddToReferrerChain(out_referrer_chain, nav_event, | |
| 224 ReferrerChainEntry::LANDING_PAGE); | |
| 225 result = SUCCESS_LANDING_PAGE; | |
| 226 } else if (user_gesture_count == 2) { | |
| 227 AddToReferrerChain(out_referrer_chain, nav_event, | |
| 228 ReferrerChainEntry::LANDING_REFERRER); | |
| 229 result = SUCCESS_LANDING_REFERRER; | |
| 230 } else { | |
| 231 NOTREACHED(); | |
| 232 } | |
| 233 } | |
| 234 return result; | 263 return result; |
| 235 } | 264 } |
| 236 | 265 |
| 237 SafeBrowsingNavigationObserverManager:: | 266 SafeBrowsingNavigationObserverManager:: |
| 238 ~SafeBrowsingNavigationObserverManager() {} | 267 ~SafeBrowsingNavigationObserverManager() {} |
| 239 | 268 |
| 240 void SafeBrowsingNavigationObserverManager::Observe( | 269 void SafeBrowsingNavigationObserverManager::Observe( |
| 241 int type, | 270 int type, |
| 242 const content::NotificationSource& source, | 271 const content::NotificationSource& source, |
| 243 const content::NotificationDetails& details) { | 272 const content::NotificationDetails& details) { |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 351 | 380 |
| 352 NavigationEvent* SafeBrowsingNavigationObserverManager::FindNavigationEvent( | 381 NavigationEvent* SafeBrowsingNavigationObserverManager::FindNavigationEvent( |
| 353 const GURL& target_url, | 382 const GURL& target_url, |
| 354 const GURL& target_main_frame_url, | 383 const GURL& target_main_frame_url, |
| 355 int target_tab_id) { | 384 int target_tab_id) { |
| 356 if (target_url.is_empty() && target_main_frame_url.is_empty()) | 385 if (target_url.is_empty() && target_main_frame_url.is_empty()) |
| 357 return nullptr; | 386 return nullptr; |
| 358 | 387 |
| 359 // If target_url is empty, we should back trace navigation based on its | 388 // If target_url is empty, we should back trace navigation based on its |
| 360 // main frame URL instead. | 389 // main frame URL instead. |
| 361 const GURL& search_url = | 390 GURL search_url = |
| 362 target_url.is_empty() ? target_main_frame_url : target_url; | 391 target_url.is_empty() ? target_main_frame_url : target_url; |
| 392 auto it = navigation_map_.find(search_url); |
| 393 if (it == navigation_map_.end()) |
| 394 return nullptr; |
| 363 | 395 |
| 364 auto it = navigation_map_.find(search_url); | |
| 365 if (it == navigation_map_.end()) { | |
| 366 return nullptr; | |
| 367 } | |
| 368 // Since navigation events are recorded in chronological order, we traverse | 396 // Since navigation events are recorded in chronological order, we traverse |
| 369 // the vector in reverse order to get the latest match. | 397 // the vector in reverse order to get the latest match. |
| 370 for (auto rit = it->second.rbegin(); rit != it->second.rend(); ++rit) { | 398 for (auto rit = it->second.rbegin(); rit != it->second.rend(); ++rit) { |
| 371 // If tab id is not valid, we only compare url, otherwise we compare both. | 399 // If tab id is not valid, we only compare url, otherwise we compare both. |
| 400 if (rit->destination_url == search_url) |
| 372 if (rit->destination_url == search_url && | 401 if (rit->destination_url == search_url && |
| 373 (target_tab_id == -1 || rit->target_tab_id == target_tab_id)) { | 402 (target_tab_id == -1 || rit->target_tab_id == target_tab_id)) { |
| 374 // If both source_url and source_main_frame_url are empty, and this | 403 // If both source_url and source_main_frame_url are empty, and this |
| 375 // navigation is not triggered by user, a retargeting navigation probably | 404 // navigation is not triggered by user, a retargeting navigation probably |
| 376 // causes this navigation. In this case, we skip this navigation event and | 405 // causes this navigation. In this case, we skip this navigation event and |
| 377 // looks for the retargeting navigation event. | 406 // looks for the retargeting navigation event. |
| 378 if (rit->source_url.is_empty() && rit->source_main_frame_url.is_empty() && | 407 if (rit->source_url.is_empty() && rit->source_main_frame_url.is_empty() && |
| 379 !rit->is_user_initiated) { | 408 !rit->is_user_initiated) { |
| 380 continue; | 409 // If there is a server redirection immediately after retargeting, we |
| 410 // need to adjust our search url to the original request. |
| 411 if (rit->has_server_redirect){ |
| 412 NavigationEvent* retargeting_nav_event = |
| 413 FindNavigationEvent(rit->original_request_url, |
| 414 GURL(), |
| 415 rit->target_tab_id); |
| 416 // Adjust retargeting navigation event's attributes. |
| 417 retargeting_nav_event->has_server_redirect = true; |
| 418 retargeting_nav_event->destination_url = search_url; |
| 419 return retargeting_nav_event; |
| 420 } else { |
| 421 continue; |
| 422 } |
| 381 } else { | 423 } else { |
| 382 return &*rit; | 424 return &*rit; |
| 383 } | 425 } |
| 384 } | 426 } |
| 385 } | 427 } |
| 386 return nullptr; | 428 return nullptr; |
| 387 } | 429 } |
| 388 | 430 |
| 389 void SafeBrowsingNavigationObserverManager::AddToReferrerChain( | 431 void SafeBrowsingNavigationObserverManager::AddToReferrerChain( |
| 390 std::vector<ReferrerChainEntry>* referrer_chain, | 432 std::vector<ReferrerChainEntry>* referrer_chain, |
| (...skipping 15 matching lines...) Expand all Loading... |
| 406 referrer_chain_entry.set_referrer_main_frame_url( | 448 referrer_chain_entry.set_referrer_main_frame_url( |
| 407 nav_event->source_main_frame_url.spec()); | 449 nav_event->source_main_frame_url.spec()); |
| 408 } | 450 } |
| 409 referrer_chain_entry.set_is_retargeting(nav_event->source_tab_id != | 451 referrer_chain_entry.set_is_retargeting(nav_event->source_tab_id != |
| 410 nav_event->target_tab_id); | 452 nav_event->target_tab_id); |
| 411 referrer_chain_entry.set_navigation_time_msec( | 453 referrer_chain_entry.set_navigation_time_msec( |
| 412 nav_event->last_updated.ToJavaTime()); | 454 nav_event->last_updated.ToJavaTime()); |
| 413 referrer_chain->push_back(std::move(referrer_chain_entry)); | 455 referrer_chain->push_back(std::move(referrer_chain_entry)); |
| 414 } | 456 } |
| 415 | 457 |
| 458 void SafeBrowsingNavigationObserverManager::GetRemainingReferrerChain( |
| 459 NavigationEvent* last_nav_event_traced, |
| 460 int current_user_gesture_count, |
| 461 int user_gesture_count_limit, |
| 462 std::vector<ReferrerChainEntry>* out_referrer_chain, |
| 463 SafeBrowsingNavigationObserverManager::AttributionResult* out_result) { |
| 464 |
| 465 while (current_user_gesture_count < user_gesture_count_limit) { |
| 466 // Back trace to the next nav_event that was initiated by the user. |
| 467 while (!last_nav_event_traced->is_user_initiated) { |
| 468 last_nav_event_traced = |
| 469 FindNavigationEvent(last_nav_event_traced->source_url, |
| 470 last_nav_event_traced->source_main_frame_url, |
| 471 last_nav_event_traced->source_tab_id); |
| 472 if (!last_nav_event_traced) |
| 473 return; |
| 474 AddToReferrerChain(out_referrer_chain, last_nav_event_traced, |
| 475 last_nav_event_traced->has_server_redirect |
| 476 ? ReferrerChainEntry::SERVER_REDIRECT |
| 477 : ReferrerChainEntry::CLIENT_REDIRECT); |
| 478 } |
| 479 |
| 480 current_user_gesture_count++; |
| 481 |
| 482 |
| 483 // If the source_url and source_main_frame_url of current navigation event |
| 484 // are empty, and is_user_initiated is true, this is a browser initiated |
| 485 // navigation (e.g. trigged by typing in address bar, clicking on bookmark, |
| 486 // etc). We reached the end of the referrer chain. |
| 487 if (last_nav_event_traced->source_url.is_empty() && |
| 488 last_nav_event_traced->source_main_frame_url.is_empty()) { |
| 489 DCHECK(last_nav_event_traced->is_user_initiated); |
| 490 return; |
| 491 } |
| 492 |
| 493 last_nav_event_traced = |
| 494 FindNavigationEvent(last_nav_event_traced->source_url, |
| 495 last_nav_event_traced->source_main_frame_url, |
| 496 last_nav_event_traced->source_tab_id); |
| 497 if (!last_nav_event_traced) |
| 498 return; |
| 499 |
| 500 AddToReferrerChain(out_referrer_chain, last_nav_event_traced, |
| 501 GetURLTypeAndAdjustAttributionResult( |
| 502 current_user_gesture_count == |
| 503 user_gesture_count_limit, |
| 504 out_result)); |
| 505 } |
| 506 } |
| 507 |
| 416 } // namespace safe_browsing | 508 } // namespace safe_browsing |
| OLD | NEW |