// Copyright 2015 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 "components/security_interstitials/core/ssl_error_ui.h"

#include "base/i18n/time_formatting.h"
#include "components/security_interstitials/core/common_string_util.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "components/ssl_errors/error_classification.h"
#include "components/ssl_errors/error_info.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"

namespace security_interstitials {
namespace {

// Path to the relevant help center page. Used if |support_url_| is invalid.
const char kHelpPath[] = "answer/6098869";

bool IsMasked(int options, SSLErrorUI::SSLErrorOptionsMask mask) {
  return ((options & mask) != 0);
}

}  // namespace

SSLErrorUI::SSLErrorUI(const GURL& request_url,
                       int cert_error,
                       const net::SSLInfo& ssl_info,
                       int display_options,
                       const base::Time& time_triggered,
                       const GURL& support_url,
                       ControllerClient* controller)
    : request_url_(request_url),
      cert_error_(cert_error),
      ssl_info_(ssl_info),
      time_triggered_(time_triggered),
      support_url_(support_url),
      requested_strict_enforcement_(
          IsMasked(display_options, STRICT_ENFORCEMENT)),
      soft_override_enabled_(IsMasked(display_options, SOFT_OVERRIDE_ENABLED)),
      hard_override_enabled_(
          !IsMasked(display_options, HARD_OVERRIDE_DISABLED)),
      controller_(controller),
      user_made_decision_(false) {
  controller_->metrics_helper()->RecordUserDecision(MetricsHelper::SHOW);
  controller_->metrics_helper()->RecordUserInteraction(
      MetricsHelper::TOTAL_VISITS);
  ssl_errors::RecordUMAStatistics(soft_override_enabled_, time_triggered_,
                                  request_url, cert_error_, *ssl_info_.cert);
}

SSLErrorUI::~SSLErrorUI() {
  // If the page is closing without an explicit decision, record it as not
  // proceeding.
  if (!user_made_decision_) {
    controller_->metrics_helper()->RecordUserDecision(
        MetricsHelper::DONT_PROCEED);
  }
  controller_->metrics_helper()->RecordShutdownMetrics();
}

void SSLErrorUI::PopulateStringsForHTML(base::DictionaryValue* load_time_data) {
  DCHECK(load_time_data);

  // Shared with other errors.
  common_string_util::PopulateSSLLayoutStrings(cert_error_, load_time_data);
  common_string_util::PopulateSSLDebuggingStrings(ssl_info_, time_triggered_,
                                                  load_time_data);

  // Shared values for both the overridable and non-overridable versions.
  load_time_data->SetBoolean("bad_clock", false);
  load_time_data->SetBoolean("hide_primary_button", false);
  load_time_data->SetString("tabTitle",
                            l10n_util::GetStringUTF16(IDS_SSL_V2_TITLE));
  load_time_data->SetString("heading",
                            l10n_util::GetStringUTF16(IDS_SSL_V2_HEADING));
  load_time_data->SetString(
      "primaryParagraph",
      l10n_util::GetStringFUTF16(
          IDS_SSL_V2_PRIMARY_PARAGRAPH,
          common_string_util::GetFormattedHostName(request_url_)));
  load_time_data->SetString(
      "recurrentErrorParagraph",
      l10n_util::GetStringUTF16(IDS_SSL_V2_RECURRENT_ERROR_PARAGRAPH));
  load_time_data->SetBoolean("show_recurrent_error_paragraph",
                             controller_->HasSeenRecurrentError());

  if (soft_override_enabled_)
    PopulateOverridableStrings(load_time_data);
  else
    PopulateNonOverridableStrings(load_time_data);
}

const net::SSLInfo& SSLErrorUI::ssl_info() const {
  return ssl_info_;
}

const base::Time& SSLErrorUI::time_triggered() const {
  return time_triggered_;
}

ControllerClient* SSLErrorUI::controller() const {
  return controller_;
}

int SSLErrorUI::cert_error() const {
  return cert_error_;
}

void SSLErrorUI::PopulateOverridableStrings(
    base::DictionaryValue* load_time_data) {
  DCHECK(soft_override_enabled_);

  base::string16 url(common_string_util::GetFormattedHostName(request_url_));
  ssl_errors::ErrorInfo error_info = ssl_errors::ErrorInfo::CreateError(
      ssl_errors::ErrorInfo::NetErrorToErrorType(cert_error_),
      ssl_info_.cert.get(), request_url_);

  load_time_data->SetBoolean("overridable", true);
  load_time_data->SetBoolean("hide_primary_button", false);
  load_time_data->SetString("explanationParagraph", error_info.details());
  load_time_data->SetString(
      "primaryButtonText",
      l10n_util::GetStringUTF16(IDS_SSL_OVERRIDABLE_SAFETY_BUTTON));
  load_time_data->SetString(
      "finalParagraph",
      l10n_util::GetStringFUTF16(IDS_SSL_OVERRIDABLE_PROCEED_PARAGRAPH, url));
}

void SSLErrorUI::PopulateNonOverridableStrings(
    base::DictionaryValue* load_time_data) {
  DCHECK(!soft_override_enabled_);

  base::string16 url(common_string_util::GetFormattedHostName(request_url_));
  ssl_errors::ErrorInfo::ErrorType type =
      ssl_errors::ErrorInfo::NetErrorToErrorType(cert_error_);

  load_time_data->SetBoolean("overridable", false);
  load_time_data->SetBoolean("hide_primary_button", false);
  load_time_data->SetString(
      "explanationParagraph",
      l10n_util::GetStringFUTF16(IDS_SSL_NONOVERRIDABLE_MORE, url));
  load_time_data->SetString("primaryButtonText",
                            l10n_util::GetStringUTF16(IDS_SSL_RELOAD));

  // Customize the help link depending on the specific error type.
  // Only mark as HSTS if none of the more specific error types apply,
  // and use INVALID as a fallback if no other string is appropriate.
  load_time_data->SetInteger("errorType", type);
  int help_string = IDS_SSL_NONOVERRIDABLE_INVALID;
  switch (type) {
    case ssl_errors::ErrorInfo::CERT_REVOKED:
      help_string = IDS_SSL_NONOVERRIDABLE_REVOKED;
      break;
    case ssl_errors::ErrorInfo::CERT_PINNED_KEY_MISSING:
      help_string = IDS_SSL_NONOVERRIDABLE_PINNED;
      break;
    case ssl_errors::ErrorInfo::CERT_INVALID:
      help_string = IDS_SSL_NONOVERRIDABLE_INVALID;
      break;
    default:
      if (requested_strict_enforcement_)
        help_string = IDS_SSL_NONOVERRIDABLE_HSTS;
  }
  load_time_data->SetString("finalParagraph",
                            l10n_util::GetStringFUTF16(help_string, url));
}

void SSLErrorUI::HandleCommand(SecurityInterstitialCommand command) {
  switch (command) {
    case CMD_DONT_PROCEED: {
      controller_->metrics_helper()->RecordUserDecision(
          MetricsHelper::DONT_PROCEED);
      user_made_decision_ = true;
      controller_->GoBack();
      break;
    }
    case CMD_PROCEED: {
      if (hard_override_enabled_) {
        controller_->metrics_helper()->RecordUserDecision(
            MetricsHelper::PROCEED);
        controller_->Proceed();
        user_made_decision_ = true;
      }
      break;
    }
    case CMD_DO_REPORT: {
      controller_->SetReportingPreference(true);
      break;
    }
    case CMD_DONT_REPORT: {
      controller_->SetReportingPreference(false);
      break;
    }
    case CMD_SHOW_MORE_SECTION: {
      controller_->metrics_helper()->RecordUserInteraction(
          security_interstitials::MetricsHelper::SHOW_ADVANCED);
      break;
    }
    case CMD_OPEN_HELP_CENTER: {
      controller_->metrics_helper()->RecordUserInteraction(
          security_interstitials::MetricsHelper::SHOW_LEARN_MORE);

      // Add cert error code as a ref to support URL, this is used to expand the
      // right section if the user is redirected to chrome://connection-help.
      GURL::Replacements replacements;
      // This has to be stored in a separate variable, otherwise asan throws a
      // use-after-scope error
      std::string cert_error_string = std::to_string(cert_error_);
      replacements.SetRefStr(cert_error_string);
      // If |support_url_| is invalid, use the default help center url.
      controller_->OpenUrlInNewForegroundTab(
          (support_url_.is_valid()
               ? support_url_
               : controller_->GetBaseHelpCenterUrl().Resolve(kHelpPath))
              .ReplaceComponents(replacements));
      break;
    }
    case CMD_RELOAD: {
      controller_->metrics_helper()->RecordUserInteraction(
          security_interstitials::MetricsHelper::RELOAD);
      controller_->Reload();
      break;
    }
    case CMD_OPEN_REPORTING_PRIVACY: {
      controller_->OpenExtendedReportingPrivacyPolicy(true);
      break;
    }
    case CMD_OPEN_WHITEPAPER: {
      controller_->OpenExtendedReportingWhitepaper(true);
      break;
    }
    case CMD_OPEN_DATE_SETTINGS:
    case CMD_OPEN_DIAGNOSTIC:
    case CMD_OPEN_LOGIN:
    case CMD_REPORT_PHISHING_ERROR: {
      // Not supported by the SSL error page.
      NOTREACHED() << "Unsupported command: " << command;
      break;
    }
    case CMD_ERROR:
    case CMD_TEXT_FOUND:
    case CMD_TEXT_NOT_FOUND: {
      // Commands are for testing.
      break;
    }
  }
}

}  // security_interstitials
