blob: 625bcb3a63ba6b68d05e333159b94688f8bdc82a [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/safe_browsing/content/browser/safe_browsing_navigation_observer.h"
#include <memory>
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "components/page_info/page_info_ui.h"
#include "components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_entry.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 "net/base/ip_endpoint.h"
#include "url/url_constants.h"
using content::WebContents;
namespace {
const char kWebContentsUserDataKey[] =
"web_contents_safe_browsing_navigation_observer";
} // namespace
namespace safe_browsing {
// SafeBrowsingNavigationObserver::NavigationEvent-----------------------------
NavigationEvent::NavigationEvent()
: source_tab_id(SessionID::InvalidValue()),
target_tab_id(SessionID::InvalidValue()),
last_updated(base::Time::Now()),
navigation_initiation(ReferrerChainEntry::UNDEFINED),
has_committed(false),
maybe_launched_by_external_application() {}
NavigationEvent::NavigationEvent(NavigationEvent&& nav_event) = default;
NavigationEvent::NavigationEvent(const NavigationEvent& nav_event) = default;
NavigationEvent& NavigationEvent::operator=(NavigationEvent&& nav_event) =
default;
NavigationEvent::~NavigationEvent() = default;
// SafeBrowsingNavigationObserver --------------------------------------------
// static
void SafeBrowsingNavigationObserver::MaybeCreateForWebContents(
content::WebContents* web_contents,
HostContentSettingsMap* host_content_settings_map,
SafeBrowsingNavigationObserverManager* observer_manager,
PrefService* prefs,
bool has_safe_browsing_service) {
if (FromWebContents(web_contents))
return;
if (safe_browsing::SafeBrowsingNavigationObserverManager::IsEnabledAndReady(
prefs, has_safe_browsing_service)) {
web_contents->SetUserData(
kWebContentsUserDataKey,
std::make_unique<SafeBrowsingNavigationObserver>(
web_contents, host_content_settings_map, observer_manager));
}
}
// static
SafeBrowsingNavigationObserver* SafeBrowsingNavigationObserver::FromWebContents(
content::WebContents* web_contents) {
return static_cast<SafeBrowsingNavigationObserver*>(
web_contents->GetUserData(kWebContentsUserDataKey));
}
SafeBrowsingNavigationObserver::SafeBrowsingNavigationObserver(
content::WebContents* contents,
HostContentSettingsMap* host_content_settings_map,
SafeBrowsingNavigationObserverManager* observer_manager)
: content::WebContentsObserver(contents),
observer_manager_(observer_manager) {
content_settings_observation_.Observe(host_content_settings_map);
}
SafeBrowsingNavigationObserver::~SafeBrowsingNavigationObserver() = default;
void SafeBrowsingNavigationObserver::OnUserInteraction() {
GetObserverManager()->RecordUserGestureForWebContents(web_contents());
}
// 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>();
SetNavigationInitiationAndRecordUserGesture(navigation_handle,
nav_event.get());
// All the other fields are reconstructed based on current content of
// navigation_handle.
SetNavigationSourceUrl(navigation_handle, nav_event.get());
nav_event->original_request_url =
SafeBrowsingNavigationObserverManager::ClearURLRef(
navigation_handle->GetURL());
nav_event->source_tab_id =
sessions::SessionTabHelper::IdForTab(navigation_handle->GetWebContents());
SetNavigationSourceMainFrameUrl(navigation_handle, nav_event.get());
SetNavigationOutermostMainFrameIds(navigation_handle, nav_event.get());
std::unique_ptr<NavigationEvent> pending_nav_event =
std::make_unique<NavigationEvent>(*nav_event);
navigation_handle_map_[navigation_handle] = std::move(nav_event);
GetObserverManager()->RecordPendingNavigationEvent(
navigation_handle, std::move(pending_nav_event));
}
void SafeBrowsingNavigationObserver::DidRedirectNavigation(
content::NavigationHandle* navigation_handle) {
// Log whether a tel scheme was observed.
base::UmaHistogramBoolean(
"SafeBrowsing.NavigationObserver.RedirectForTelScheme",
navigation_handle->GetURL().SchemeIs(url::kTelScheme));
// 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();
GetObserverManager()->AddRedirectUrlToPendingNavigationEvent(
navigation_handle, navigation_handle->GetURL());
}
void SafeBrowsingNavigationObserver::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if ((navigation_handle->HasCommitted() || navigation_handle->IsDownload()) &&
!navigation_handle->GetSocketAddress().address().empty()) {
GetObserverManager()->RecordHostToIpMapping(
navigation_handle->GetURL().GetHost(),
navigation_handle->GetSocketAddress().ToStringWithoutPort());
}
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 =
sessions::SessionTabHelper::IdForTab(navigation_handle->GetWebContents());
if (nav_event->is_outermost_main_frame && nav_event->has_committed) {
auto* rfh = navigation_handle->GetRenderFrameHost();
// We set the outermost main frame id here rather than in DidStartNavigation
// because in that function, we don't yet have a RenderFrameHost, so if
// we're the outermost main frame, we won't yet have an id.
nav_event->outermost_main_frame_id =
rfh->GetOutermostMainFrame()->GetGlobalId();
}
DCHECK(!nav_event->has_committed || nav_event->outermost_main_frame_id);
nav_event->last_updated = base::Time::Now();
GetObserverManager()->RecordNavigationEvent(
navigation_handle, std::move(navigation_handle_map_[navigation_handle]));
navigation_handle_map_.erase(navigation_handle);
}
void SafeBrowsingNavigationObserver::DidGetUserInteraction(
const blink::WebInputEvent& event) {
OnUserInteraction();
}
void SafeBrowsingNavigationObserver::WebContentsDestroyed() {
GetObserverManager()->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) {
GetObserverManager()->RecordNewWebContents(
web_contents(), source_render_frame_host, url, transition, new_contents,
renderer_initiated);
}
void SafeBrowsingNavigationObserver::OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsTypeSet content_type_set) {
// 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.MatchesAllHosts() &&
primary_pattern.Matches(web_contents()->GetLastCommittedURL()) &&
(content_type_set.ContainsAllTypes() ||
PageInfoUI::ContentSettingsTypeInPageInfo(content_type_set.GetType()))) {
OnUserInteraction();
}
}
void SafeBrowsingNavigationObserver::
SetNavigationInitiationAndRecordUserGesture(
content::NavigationHandle* navigation_handle,
NavigationEvent* nav_event) {
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, decide if it is
// triggered by user.
if (!navigation_handle->IsRendererInitiated()) {
nav_event->navigation_initiation = ReferrerChainEntry::BROWSER_INITIATED;
} else if (GetObserverManager()->HasUnexpiredUserGesture(web_contents())) {
nav_event->navigation_initiation =
ReferrerChainEntry::RENDERER_INITIATED_WITH_USER_GESTURE;
} else {
nav_event->navigation_initiation =
ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE;
}
GetObserverManager()->OnUserGestureConsumed(web_contents());
}
}
void SafeBrowsingNavigationObserver::SetNavigationSourceUrl(
content::NavigationHandle* navigation_handle,
NavigationEvent* nav_event) {
// 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())
return;
if (navigation_handle->IsInPrerenderedMainFrame()) {
// A prerendered page can only be initiated by the primary page, so we will
// grab the last committed URL from the primary main frame directly. Note
// that this cannot be obtained from the previous RFH since this is empty
// for prerenders. While this differs from the semantics for source_url
// (which is indeed related to the last committed URL in the frame into
// which we are navigating), it matches what source_url would have been had
// we been navigating normally since we will activate in the primary main
// frame.
nav_event->source_url = SafeBrowsingNavigationObserverManager::ClearURLRef(
navigation_handle->GetWebContents()
->GetPrimaryMainFrame()
->GetLastCommittedURL());
} else {
// 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.
content::RenderFrameHost* current_frame_host =
content::RenderFrameHost::FromID(
navigation_handle->GetPreviousRenderFrameHostId());
if (current_frame_host &&
current_frame_host->GetLastCommittedURL().is_valid()) {
nav_event->source_url =
SafeBrowsingNavigationObserverManager::ClearURLRef(
current_frame_host->GetLastCommittedURL());
}
}
}
void SafeBrowsingNavigationObserver::SetNavigationSourceMainFrameUrl(
content::NavigationHandle* navigation_handle,
NavigationEvent* nav_event) {
if (!navigation_handle->GetParentFrameOrOuterDocument()) {
nav_event->source_main_frame_url = nav_event->source_url;
} else {
nav_event->source_main_frame_url =
SafeBrowsingNavigationObserverManager::ClearURLRef(
navigation_handle->GetParentFrameOrOuterDocument()
->GetOutermostMainFrame()
->GetLastCommittedURL());
}
}
void SafeBrowsingNavigationObserver::SetNavigationOutermostMainFrameIds(
content::NavigationHandle* navigation_handle,
NavigationEvent* nav_event) {
auto* outer_rfh = navigation_handle->GetParentFrameOrOuterDocument();
nav_event->is_outermost_main_frame = !outer_rfh;
if (outer_rfh) {
nav_event->outermost_main_frame_id =
outer_rfh->GetOutermostMainFrame()->GetGlobalId();
}
if (navigation_handle->IsRendererInitiated()) {
auto* initiator_frame_host =
navigation_handle->GetInitiatorFrameToken().has_value()
? content::RenderFrameHost::FromFrameToken(
content::GlobalRenderFrameHostToken(
navigation_handle->GetInitiatorProcessId(),
navigation_handle->GetInitiatorFrameToken().value()))
: nullptr;
if (initiator_frame_host) {
nav_event->initiator_outermost_main_frame_id =
initiator_frame_host->GetOutermostMainFrame()->GetGlobalId();
}
}
}
SafeBrowsingNavigationObserverManager*
SafeBrowsingNavigationObserver::GetObserverManager() {
return observer_manager_;
}
void SafeBrowsingNavigationObserver::SetObserverManagerForTesting(
SafeBrowsingNavigationObserverManager* observer_manager) {
observer_manager_ = observer_manager;
}
} // namespace safe_browsing