blob: aa76c7a6e3dc8b1d2f02aaab766b93cdb5254a04 [file] [log] [blame]
// Copyright 2016 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/safe_browsing/safe_browsing_navigation_observer.h"
#include <memory>
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/page_info/page_info_ui.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/resource_type.h"
using content::WebContents;
namespace {
const char kWebContentsUserDataKey[] =
"web_contents_safe_browsing_navigation_observer";
} // namespace
namespace safe_browsing {
// SafeBrowsingNavigationObserver::NavigationEvent-----------------------------
NavigationEvent::NavigationEvent()
: source_url(),
source_main_frame_url(),
original_request_url(),
source_tab_id(SessionID::InvalidValue()),
target_tab_id(SessionID::InvalidValue()),
frame_id(-1),
last_updated(base::Time::Now()),
navigation_initiation(ReferrerChainEntry::UNDEFINED),
has_committed(false),
maybe_launched_by_external_application() {}
NavigationEvent::NavigationEvent(NavigationEvent&& nav_event)
: source_url(std::move(nav_event.source_url)),
source_main_frame_url(std::move(nav_event.source_main_frame_url)),
original_request_url(std::move(nav_event.original_request_url)),
server_redirect_urls(std::move(nav_event.server_redirect_urls)),
source_tab_id(std::move(nav_event.source_tab_id)),
target_tab_id(std::move(nav_event.target_tab_id)),
frame_id(nav_event.frame_id),
last_updated(nav_event.last_updated),
navigation_initiation(nav_event.navigation_initiation),
has_committed(nav_event.has_committed),
maybe_launched_by_external_application(
nav_event.maybe_launched_by_external_application) {}
NavigationEvent& NavigationEvent::operator=(NavigationEvent&& nav_event) {
source_url = std::move(nav_event.source_url);
source_main_frame_url = std::move(nav_event.source_main_frame_url);
original_request_url = std::move(nav_event.original_request_url);
source_tab_id = nav_event.source_tab_id;
target_tab_id = nav_event.target_tab_id;
frame_id = nav_event.frame_id;
last_updated = nav_event.last_updated;
navigation_initiation = nav_event.navigation_initiation;
has_committed = nav_event.has_committed;
maybe_launched_by_external_application =
nav_event.maybe_launched_by_external_application;
server_redirect_urls = std::move(nav_event.server_redirect_urls);
return *this;
}
NavigationEvent::~NavigationEvent() {}
// SafeBrowsingNavigationObserver --------------------------------------------
// static
void SafeBrowsingNavigationObserver::MaybeCreateForWebContents(
content::WebContents* web_contents) {
if (FromWebContents(web_contents))
return;
if (safe_browsing::SafeBrowsingNavigationObserverManager::IsEnabledAndReady(
Profile::FromBrowserContext(web_contents->GetBrowserContext()))) {
web_contents->SetUserData(
kWebContentsUserDataKey,
std::make_unique<SafeBrowsingNavigationObserver>(
web_contents, g_browser_process->safe_browsing_service()
->navigation_observer_manager()));
}
}
// static
SafeBrowsingNavigationObserver* SafeBrowsingNavigationObserver::FromWebContents(
content::WebContents* web_contents) {
return static_cast<SafeBrowsingNavigationObserver*>(
web_contents->GetUserData(kWebContentsUserDataKey));
}
SafeBrowsingNavigationObserver::SafeBrowsingNavigationObserver(
content::WebContents* contents,
const scoped_refptr<SafeBrowsingNavigationObserverManager>& manager)
: content::WebContentsObserver(contents),
manager_(manager),
has_user_gesture_(false),
last_user_gesture_timestamp_(base::Time()),
content_settings_observer_(this) {
content_settings_observer_.Add(HostContentSettingsMapFactory::GetForProfile(
Profile::FromBrowserContext(web_contents()->GetBrowserContext())));
}
SafeBrowsingNavigationObserver::~SafeBrowsingNavigationObserver() {}
void SafeBrowsingNavigationObserver::OnUserInteraction() {
last_user_gesture_timestamp_ = base::Time::Now();
has_user_gesture_ = true;
manager_->RecordUserGestureForWebContents(web_contents(),
last_user_gesture_timestamp_);
}
// Called when a navigation starts in the WebContents. |navigation_handle|
// parameter is unique to this navigation, which will appear in the following
// DidRedirectNavigation, and DidFinishNavigation too.
void SafeBrowsingNavigationObserver::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
// Treat a browser-initiated navigation as a user interaction.
if (!navigation_handle->IsRendererInitiated())
OnUserInteraction();
// Ignores navigation caused by back/forward.
if (navigation_handle->GetPageTransition() &
ui::PAGE_TRANSITION_FORWARD_BACK) {
return;
}
// Ignores reloads
if (ui::PageTransitionCoreTypeIs(navigation_handle->GetPageTransition(),
ui::PAGE_TRANSITION_RELOAD)) {
return;
}
std::unique_ptr<NavigationEvent> nav_event =
std::make_unique<NavigationEvent>();
auto it = navigation_handle_map_.find(navigation_handle);
// It is possible to see multiple DidStartNavigation(..) with the same
// navigation_handle (e.g. cross-process transfer). If that's the case,
// we need to copy the navigation_initiation field.
if (it != navigation_handle_map_.end() &&
it->second->navigation_initiation != ReferrerChainEntry::UNDEFINED) {
nav_event->navigation_initiation = it->second->navigation_initiation;
} else {
// If this is the first time we see this navigation_handle, create a new
// NavigationEvent, and decide if it is triggered by user.
if (!navigation_handle->IsRendererInitiated()) {
nav_event->navigation_initiation = ReferrerChainEntry::BROWSER_INITIATED;
} else if (has_user_gesture_ &&
!SafeBrowsingNavigationObserverManager::IsUserGestureExpired(
last_user_gesture_timestamp_)) {
nav_event->navigation_initiation =
ReferrerChainEntry::RENDERER_INITIATED_WITH_USER_GESTURE;
} else {
nav_event->navigation_initiation =
ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE;
}
if (has_user_gesture_) {
manager_->OnUserGestureConsumed(web_contents(),
last_user_gesture_timestamp_);
has_user_gesture_ = false;
}
}
// All the other fields are reconstructed based on current content of
// navigation_handle.
nav_event->frame_id = navigation_handle->GetFrameTreeNodeId();
// If there was a URL previously committed in the current RenderFrameHost,
// set it as the source url of this navigation. Otherwise, this is the
// first url going to commit in this frame. We set navigation_handle's URL as
// the source url.
int current_process_id =
navigation_handle->GetStartingSiteInstance()->GetProcess()->GetID();
content::RenderFrameHost* current_frame_host =
navigation_handle->GetWebContents()->FindFrameByFrameTreeNodeId(
nav_event->frame_id, current_process_id);
// For browser initiated navigation (e.g. from address bar or bookmark), we
// don't fill the source_url to prevent attributing navigation to the last
// committed navigation.
if (navigation_handle->IsRendererInitiated() && current_frame_host &&
current_frame_host->GetLastCommittedURL().is_valid()) {
nav_event->source_url = SafeBrowsingNavigationObserverManager::ClearURLRef(
current_frame_host->GetLastCommittedURL());
}
nav_event->original_request_url =
SafeBrowsingNavigationObserverManager::ClearURLRef(
navigation_handle->GetURL());
nav_event->source_tab_id =
SessionTabHelper::IdForTab(navigation_handle->GetWebContents());
if (navigation_handle->IsInMainFrame()) {
nav_event->source_main_frame_url = nav_event->source_url;
} else {
nav_event->source_main_frame_url =
SafeBrowsingNavigationObserverManager::ClearURLRef(
navigation_handle->GetWebContents()->GetLastCommittedURL());
}
navigation_handle_map_[navigation_handle] = std::move(nav_event);
}
void SafeBrowsingNavigationObserver::DidRedirectNavigation(
content::NavigationHandle* navigation_handle) {
// We should have already seen this navigation_handle in DidStartNavigation.
if (navigation_handle_map_.find(navigation_handle) ==
navigation_handle_map_.end()) {
return;
}
NavigationEvent* nav_event = navigation_handle_map_[navigation_handle].get();
nav_event->server_redirect_urls.push_back(
SafeBrowsingNavigationObserverManager::ClearURLRef(
navigation_handle->GetURL()));
nav_event->last_updated = base::Time::Now();
}
void SafeBrowsingNavigationObserver::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if ((navigation_handle->HasCommitted() || navigation_handle->IsDownload()) &&
!navigation_handle->GetSocketAddress().IsEmpty()) {
manager_->RecordHostToIpMapping(
navigation_handle->GetURL().host(),
navigation_handle->GetSocketAddress().host());
}
if (navigation_handle_map_.find(navigation_handle) ==
navigation_handle_map_.end()) {
return;
}
// If it is an error page, we ignore this navigation.
if (navigation_handle->IsErrorPage()) {
navigation_handle_map_.erase(navigation_handle);
return;
}
NavigationEvent* nav_event = navigation_handle_map_[navigation_handle].get();
nav_event->maybe_launched_by_external_application =
PageTransitionCoreTypeIs(navigation_handle->GetPageTransition(),
ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
nav_event->has_committed = navigation_handle->HasCommitted();
nav_event->target_tab_id =
SessionTabHelper::IdForTab(navigation_handle->GetWebContents());
nav_event->last_updated = base::Time::Now();
manager_->RecordNavigationEvent(
std::move(navigation_handle_map_[navigation_handle]));
navigation_handle_map_.erase(navigation_handle);
}
void SafeBrowsingNavigationObserver::DidGetUserInteraction(
const blink::WebInputEvent::Type type) {
OnUserInteraction();
}
void SafeBrowsingNavigationObserver::WebContentsDestroyed() {
manager_->OnWebContentDestroyed(web_contents());
web_contents()->RemoveUserData(kWebContentsUserDataKey);
// web_contents is null after this function.
}
void SafeBrowsingNavigationObserver::DidOpenRequestedURL(
content::WebContents* new_contents,
content::RenderFrameHost* source_render_frame_host,
const GURL& url,
const content::Referrer& referrer,
WindowOpenDisposition disposition,
ui::PageTransition transition,
bool started_from_context_menu,
bool renderer_initiated) {
manager_->RecordNewWebContents(
web_contents(), source_render_frame_host->GetProcess()->GetID(),
source_render_frame_host->GetRoutingID(), url, transition, new_contents,
renderer_initiated);
}
void SafeBrowsingNavigationObserver::OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const std::string& resource_identifier) {
// For all the content settings that can be changed via page info UI, we
// assume there is a user gesture associated with the content setting change.
if (web_contents() &&
primary_pattern.Matches(web_contents()->GetLastCommittedURL()) &&
PageInfoUI::ContentSettingsTypeInPageInfo(content_type)) {
OnUserInteraction();
}
}
} // namespace safe_browsing