/* -*- 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 "nsSecureBrowserUI.h"

#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/Document.h"
#include "nsContentUtils.h"
#include "nsIChannel.h"
#include "nsDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsGlobalWindow.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsITransportSecurityInfo.h"
#include "nsIWebProgress.h"
#include "nsNetUtil.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/Element.h"
#include "nsIBrowser.h"

using namespace mozilla;
using namespace mozilla::dom;

LazyLogModule gSecureBrowserUILog("nsSecureBrowserUI");

nsSecureBrowserUI::nsSecureBrowserUI(CanonicalBrowsingContext* aBrowsingContext)
    : mState(0) {
  MOZ_ASSERT(NS_IsMainThread());

  // The BrowsingContext will own the SecureBrowserUI object, we keep a weak
  // ref.
  mBrowsingContextId = aBrowsingContext->Id();
}

NS_IMPL_ISUPPORTS(nsSecureBrowserUI, nsISecureBrowserUI,
                  nsISupportsWeakReference)

NS_IMETHODIMP
nsSecureBrowserUI::GetState(uint32_t* aState) {
  MOZ_ASSERT(NS_IsMainThread());
  NS_ENSURE_ARG(aState);

  MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
          ("GetState %p mState: %x", this, mState));
  *aState = mState;
  return NS_OK;
}

void nsSecureBrowserUI::RecomputeSecurityFlags() {
  // Our BrowsingContext either has a new WindowGlobalParent, or the
  // existing one has mutated its security state.
  // Recompute our security state and fire notifications to listeners

  RefPtr<WindowGlobalParent> win = GetCurrentWindow();
  mState = nsIWebProgressListener::STATE_IS_INSECURE;

  // Only https is considered secure (it is possible to have e.g. an http URI
  // with a channel that has a securityInfo that indicates the connection is
  // secure - e.g. h2/alt-svc or by visiting an http URI over an https proxy).
  nsCOMPtr<nsITransportSecurityInfo> securityInfo;
  if (win && win->GetIsSecure()) {
    securityInfo = win->GetSecurityInfo();
    if (securityInfo) {
      MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
              ("  we have a security info %p", securityInfo.get()));

      nsresult rv = securityInfo->GetSecurityState(&mState);

      // If the security state is STATE_IS_INSECURE, the TLS handshake never
      // completed. Don't set any further state.
      if (NS_SUCCEEDED(rv) &&
          mState != nsIWebProgressListener::STATE_IS_INSECURE) {
        MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
                ("  set mTopLevelSecurityInfo"));
        bool isEV;
        rv = securityInfo->GetIsExtendedValidation(&isEV);
        if (NS_SUCCEEDED(rv) && isEV) {
          MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("  is EV"));
          mState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
        }
      }
    }
  }

  // Add upgraded-state flags when request has been
  // upgraded with HTTPS-Only Mode
  if (win) {
    // Check if top-level load has been upgraded
    uint32_t httpsOnlyStatus = win->HttpsOnlyStatus();
    if (!(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UNINITIALIZED) &&
        !(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT)) {
      mState |= nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED;
    }
    if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST) {
      if (win->GetDocumentURI()->SchemeIs("https")) {
        mState |= nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST;
      } else {
        mState |= nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED;
      }
    }
    // Add the secruity flags from the window
    mState |= win->GetSecurityFlags();
  }

  // If we have loaded mixed content and this is a secure page,
  // then clear secure flags and add broken instead.
  static const uint32_t kLoadedMixedContentFlags =
      nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
      nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
  if (win && win->GetIsSecure() && (mState & kLoadedMixedContentFlags)) {
    // reset state security flag
    mState = mState >> 4 << 4;
    // set state security flag to broken, since there is mixed content
    mState |= nsIWebProgressListener::STATE_IS_BROKEN;
  }

  RefPtr<CanonicalBrowsingContext> ctx =
      CanonicalBrowsingContext::Get(mBrowsingContextId);
  if (!ctx) {
    return;
  }

  if (ctx->GetDocShell()) {
    nsDocShell* nativeDocShell = nsDocShell::Cast(ctx->GetDocShell());
    nativeDocShell->nsDocLoader::OnSecurityChange(nullptr, mState);
  } else if (ctx->GetWebProgress()) {
    ctx->GetWebProgress()->OnSecurityChange(nullptr, nullptr, mState);
  }
}

NS_IMETHODIMP
nsSecureBrowserUI::GetIsSecureContext(bool* aIsSecureContext) {
  MOZ_ASSERT(NS_IsMainThread());
  NS_ENSURE_ARG(aIsSecureContext);

  if (WindowGlobalParent* parent = GetCurrentWindow()) {
    *aIsSecureContext = parent->GetIsSecureContext();
  } else {
    *aIsSecureContext = false;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSecureBrowserUI::GetSecInfo(nsITransportSecurityInfo** result) {
  MOZ_ASSERT(NS_IsMainThread());
  NS_ENSURE_ARG_POINTER(result);

  if (WindowGlobalParent* parent = GetCurrentWindow()) {
    *result = parent->GetSecurityInfo();
  }
  NS_IF_ADDREF(*result);

  return NS_OK;
}

WindowGlobalParent* nsSecureBrowserUI::GetCurrentWindow() {
  RefPtr<CanonicalBrowsingContext> ctx =
      CanonicalBrowsingContext::Get(mBrowsingContextId);
  if (!ctx) {
    return nullptr;
  }
  return ctx->GetCurrentWindowGlobal();
}
