Files
tubestation/security/manager/ssl/nsSecureBrowserUIImpl.cpp

408 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsSecureBrowserUIImpl.h"
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
#include "nsIChannel.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsISecurityEventSink.h"
#include "nsITransportSecurityInfo.h"
#include "nsIWebProgress.h"
using namespace mozilla;
LazyLogModule gSecureBrowserUILog("nsSecureBrowserUI");
nsSecureBrowserUIImpl::nsSecureBrowserUIImpl()
: mOldState(0)
, mState(0)
{
MOZ_ASSERT(NS_IsMainThread());
}
NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl,
nsISecureBrowserUI,
nsIWebProgressListener,
nsISupportsWeakReference)
NS_IMETHODIMP
nsSecureBrowserUIImpl::Init(mozIDOMWindowProxy* aWindow)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aWindow);
auto* piwindow = nsPIDOMWindowOuter::From(aWindow);
nsIDocShell* docShell = piwindow->GetDocShell();
// The Docshell will own the SecureBrowserUI object
if (!docShell) {
return NS_ERROR_FAILURE;
}
docShell->SetSecurityUI(this);
// hook up to the webprogress notifications.
nsCOMPtr<nsIWebProgress> wp(do_GetInterface(docShell));
if (!wp) {
return NS_ERROR_FAILURE;
}
// Save this so we can compare it to the web progress in OnLocationChange.
nsresult rv;
mWebProgress = do_GetWeakReference(wp, &rv);
if (NS_FAILED(rv)) {
return rv;
}
return wp->AddProgressListener(this, nsIWebProgress::NOTIFY_LOCATION);
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::GetOldState(uint32_t* aOldState)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aOldState);
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("GetOldState %p", this));
// Only sync our state with the docshell in GetState().
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" mOldState: %x", mOldState));
*aOldState = mOldState;
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::GetState(uint32_t* aState)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aState);
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("GetState %p", this));
// With respect to mixed content and tracking protection, we won't know when
// the state of our document (or a subdocument) has changed, so we ask the
// docShell.
CheckForBlockedContent();
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" mState: %x", mState));
*aState = mState;
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::GetContentBlockingLogJSON(nsAString& aContentBlockingLogJSON)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("GetContentBlockingLogJSON %p", this));
aContentBlockingLogJSON.Truncate();
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
if (docShell) {
nsIDocument* doc = docShell->GetDocument();
if (doc) {
aContentBlockingLogJSON = doc->GetContentBlockingLog()->Stringify();
}
}
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" ContentBlockingLogJSON: %s", NS_ConvertUTF16toUTF8(aContentBlockingLogJSON).get()));
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::GetSecInfo(nsITransportSecurityInfo** result)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(result);
*result = mTopLevelSecurityInfo;
NS_IF_ADDREF(*result);
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::SetDocShell(nsIDocShell* aDocShell)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aDocShell);
nsresult rv;
mDocShell = do_GetWeakReference(aDocShell, &rv);
return rv;
}
// Ask the docShell if we've blocked or loaded any mixed or tracking content.
void
nsSecureBrowserUIImpl::CheckForBlockedContent()
{
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
if (!docShell) {
return;
}
// For content docShells, the mixed content security state is set on the root
// docShell.
if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(docShell));
nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
Unused << docShellTreeItem->GetSameTypeRootTreeItem(
getter_AddRefs(sameTypeRoot));
MOZ_ASSERT(
sameTypeRoot,
"No document shell root tree item from document shell tree item!");
docShell = do_QueryInterface(sameTypeRoot);
if (!docShell) {
return;
}
}
mOldState = mState;
// Has mixed content been loaded or blocked in nsMixedContentBlocker?
// This only applies to secure documents.
if (mState & STATE_IS_SECURE) {
if (docShell->GetHasMixedActiveContentLoaded()) {
mState |= STATE_IS_BROKEN | STATE_LOADED_MIXED_ACTIVE_CONTENT;
mState &= ~STATE_IS_SECURE;
mState &= ~STATE_SECURE_HIGH;
}
if (docShell->GetHasMixedDisplayContentLoaded()) {
mState |= STATE_IS_BROKEN | STATE_LOADED_MIXED_DISPLAY_CONTENT;
mState &= ~STATE_IS_SECURE;
mState &= ~STATE_SECURE_HIGH;
}
if (docShell->GetHasMixedActiveContentBlocked()) {
mState |= STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
}
if (docShell->GetHasMixedDisplayContentBlocked()) {
mState |= STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
}
}
// Has tracking content been blocked or loaded?
if (docShell->GetHasTrackingContentBlocked()) {
mState |= STATE_BLOCKED_TRACKING_CONTENT;
}
if (docShell->GetHasSlowTrackingContentBlocked()) {
mState |= STATE_BLOCKED_SLOW_TRACKING_CONTENT;
}
if (docShell->GetHasTrackingContentLoaded()) {
mState |= STATE_LOADED_TRACKING_CONTENT;
}
if (docShell->GetHasCookiesBlockedByPermission()) {
mState |= STATE_COOKIES_BLOCKED_BY_PERMISSION;
}
if (docShell->GetHasCookiesBlockedDueToTrackers()) {
mState |= STATE_COOKIES_BLOCKED_TRACKER;
}
if (docShell->GetHasForeignCookiesBeenBlocked()) {
mState |= STATE_COOKIES_BLOCKED_FOREIGN;
}
if (docShell->GetHasAllCookiesBeenBlocked()) {
mState |= STATE_COOKIES_BLOCKED_ALL;
}
}
// Helper function to get the securityInfo from a channel as a
// nsITransportSecurityInfo. The out parameter will be set to null if there is
// no securityInfo set.
static void
GetSecurityInfoFromChannel(nsIChannel* channel,
nsITransportSecurityInfo** securityInfoOut)
{
MOZ_ASSERT(channel);
MOZ_ASSERT(securityInfoOut);
NS_ENSURE_TRUE_VOID(channel);
NS_ENSURE_TRUE_VOID(securityInfoOut);
*securityInfoOut = nullptr;
nsCOMPtr<nsISupports> securityInfoSupports;
nsresult rv = channel->GetSecurityInfo(getter_AddRefs(securityInfoSupports));
// GetSecurityInfo may return an error, but it's not necessarily fatal - the
// underlying channel may simply not have a securityInfo.
if (NS_FAILED(rv)) {
return;
}
nsCOMPtr<nsITransportSecurityInfo> securityInfo(
do_QueryInterface(securityInfoSupports));
securityInfo.forget(securityInfoOut);
}
nsresult
nsSecureBrowserUIImpl::UpdateStateAndSecurityInfo(nsIChannel* channel,
nsIURI* uri)
{
MOZ_ASSERT(channel);
MOZ_ASSERT(uri);
NS_ENSURE_ARG(channel);
NS_ENSURE_ARG(uri);
mState = STATE_IS_INSECURE;
mTopLevelSecurityInfo = nullptr;
nsCOMPtr<nsITransportSecurityInfo> securityInfo;
GetSecurityInfoFromChannel(channel, getter_AddRefs(securityInfo));
if (securityInfo) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" we have a security info %p", securityInfo.get()));
nsresult rv = securityInfo->GetSecurityState(&mState);
if (NS_FAILED(rv)) {
return rv;
}
// If the security state is STATE_IS_INSECURE, the TLS handshake never
// completed. Don't set any further state.
if (mState == STATE_IS_INSECURE) {
return NS_OK;
}
mTopLevelSecurityInfo = securityInfo;
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" set mTopLevelSecurityInfo"));
bool isEV;
rv = mTopLevelSecurityInfo->GetIsExtendedValidation(&isEV);
if (NS_FAILED(rv)) {
return rv;
}
if (isEV) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" is EV"));
mState |= STATE_IDENTITY_EV_TOPLEVEL;
}
}
return NS_OK;
}
// We receive this notification for the nsIWebProgress we added ourselves to
// (i.e. the window we were passed in Init, which should be the top-level
// window or whatever corresponds to an <iframe mozbrowser> element). In some
// cases, we also receive it from nsIWebProgress instances that are children of
// that nsIWebProgress. We ignore notifications from children because they don't
// change the top-level state (if children load mixed or tracking content, the
// docShell will know and will tell us in GetState when we call
// CheckForBlockedContent).
// When we receive a notification from the top-level nsIWebProgress, we extract
// any relevant security information and set our state accordingly. We then call
// OnSecurityChange to notify any downstream listeners of the security state.
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
nsIURI* aLocation,
uint32_t aFlags)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aWebProgress);
NS_ENSURE_ARG(aLocation);
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
("%p OnLocationChange: %p %p %s %x", this, aWebProgress, aRequest,
aLocation->GetSpecOrDefault().get(), aFlags));
// Filter out events from children. See comment at the top of this function.
// It would be nice if the attribute isTopLevel worked for this, but that
// filters out events for <iframe mozbrowser> elements, which means they don't
// get OnSecurityChange events from this implementation. Instead, we check to
// see if the web progress we were handed here is the same one as we were
// initialized with.
nsCOMPtr<nsIWebProgress> originalWebProgress = do_QueryReferent(mWebProgress);
if (aWebProgress != originalWebProgress) {
return NS_OK;
}
// Clear any state that varies by location.
if (!(aFlags & LOCATION_CHANGE_SAME_DOCUMENT)) {
mOldState = 0;
mState = 0;
mTopLevelSecurityInfo = nullptr;
}
// NB: aRequest may be null. It may also not be QI-able to nsIChannel.
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
if (channel) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" we have a channel %p", channel.get()));
nsresult rv = UpdateStateAndSecurityInfo(channel, aLocation);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsISecurityEventSink> eventSink;
NS_QueryNotificationCallbacks(channel, eventSink);
if (NS_WARN_IF(!eventSink)) {
return NS_ERROR_INVALID_ARG;
}
mozilla::dom::ContentBlockingLog* contentBlockingLog = nullptr;
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
if (docShell) {
nsIDocument* doc = docShell->GetDocument();
if (doc) {
contentBlockingLog = doc->GetContentBlockingLog();
}
}
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" calling OnSecurityChange %p %x\n", aRequest, mState));
Unused << eventSink->OnSecurityChange(aRequest, mOldState, mState,
contentBlockingLog);
}
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress*,
nsIRequest*,
uint32_t,
nsresult)
{
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress*,
nsIRequest*,
int32_t,
int32_t,
int32_t,
int32_t)
{
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnStatusChange(nsIWebProgress*,
nsIRequest*,
nsresult,
const char16_t*)
{
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}
nsresult
nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress*, nsIRequest*, uint32_t,
uint32_t, const nsAString&)
{
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}