// Copyright (c) 2012 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.

/**
 * This UI allows a user to query and update the browser's list of per-domain
 * security policies. These policies include:
 * - HSTS: HTTPS Strict Transport Security. A way for sites to elect to always
 *   use HTTPS. See http://dev.chromium.org/sts
 * - Expect-CT. A way for sites to elect to always require valid Certificate
 *   Transparency information to be present. See
 *   https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-01
 */

var DomainSecurityPolicyView = (function() {
  'use strict';

  // We inherit from DivView.
  var superClass = DivView;

  /**
   * @constructor
   */
  function DomainSecurityPolicyView() {
    assertFirstConstructorCall(DomainSecurityPolicyView);

    // Call superclass's constructor.
    superClass.call(this, DomainSecurityPolicyView.MAIN_BOX_ID);

    this.deleteInput_ = $(DomainSecurityPolicyView.DELETE_INPUT_ID);
    this.addStsInput_ = $(DomainSecurityPolicyView.ADD_HSTS_INPUT_ID);
    this.addStsCheck_ = $(DomainSecurityPolicyView.ADD_STS_CHECK_ID);
    this.queryStsInput_ = $(DomainSecurityPolicyView.QUERY_HSTS_INPUT_ID);
    this.queryStsOutputDiv_ =
        $(DomainSecurityPolicyView.QUERY_HSTS_OUTPUT_DIV_ID);
    this.addExpectCTInput_ = $(DomainSecurityPolicyView.ADD_EXPECT_CT_INPUT_ID);
    this.addExpectCTReportUriInput_ =
        $(DomainSecurityPolicyView.ADD_EXPECT_CT_REPORT_URI_INPUT_ID);
    this.addExpectCTEnforceCheck_ =
        $(DomainSecurityPolicyView.ADD_EXPECT_CT_ENFORCE_CHECK_ID);
    this.queryExpectCTInput_ =
        $(DomainSecurityPolicyView.QUERY_EXPECT_CT_INPUT_ID);
    this.queryExpectCTOutputDiv_ =
        $(DomainSecurityPolicyView.QUERY_EXPECT_CT_OUTPUT_DIV_ID);
    this.testExpectCTReportInput_ =
        $(DomainSecurityPolicyView.TEST_REPORT_EXPECT_CT_INPUT_ID);
    this.testExpectCTOutputDiv_ =
        $(DomainSecurityPolicyView.TEST_REPORT_EXPECT_CT_OUTPUT_DIV_ID);

    var form = $(DomainSecurityPolicyView.DELETE_FORM_ID);
    form.addEventListener('submit', this.onSubmitDelete_.bind(this), false);

    form = $(DomainSecurityPolicyView.ADD_HSTS_FORM_ID);
    form.addEventListener('submit', this.onSubmitHSTSAdd_.bind(this), false);

    form = $(DomainSecurityPolicyView.QUERY_HSTS_FORM_ID);
    form.addEventListener('submit', this.onSubmitHSTSQuery_.bind(this), false);

    form = $(DomainSecurityPolicyView.ADD_EXPECT_CT_FORM_ID);
    form.addEventListener(
        'submit', this.onSubmitExpectCTAdd_.bind(this), false);

    form = $(DomainSecurityPolicyView.QUERY_EXPECT_CT_FORM_ID);
    form.addEventListener(
        'submit', this.onSubmitExpectCTQuery_.bind(this), false);

    form = $(DomainSecurityPolicyView.TEST_REPORT_EXPECT_CT_FORM_ID);
    form.addEventListener(
        'submit', this.onSubmitExpectCTTestReport_.bind(this), false);

    g_browser.addHSTSObserver(this);
    g_browser.addExpectCTObserver(this);
  }

  DomainSecurityPolicyView.TAB_ID = 'tab-handle-domain-security-policy';
  DomainSecurityPolicyView.TAB_NAME = 'Domain Security Policy';
  // This tab was originally limited to HSTS. Even though it now encompasses
  // domain security policy more broadly, keep the hash as "#hsts" to preserve
  // links/documentation that directs users to chrome://net-internals#hsts.
  DomainSecurityPolicyView.TAB_HASH = '#hsts';

  // IDs for special HTML elements in domain_security_policy_view.html
  DomainSecurityPolicyView.MAIN_BOX_ID =
      'domain-security-policy-view-tab-content';
  DomainSecurityPolicyView.DELETE_INPUT_ID =
      'domain-security-policy-view-delete-input';
  DomainSecurityPolicyView.DELETE_FORM_ID =
      'domain-security-policy-view-delete-form';
  DomainSecurityPolicyView.DELETE_SUBMIT_ID =
      'domain-security-policy-view-delete-submit';
  // HSTS form elements
  DomainSecurityPolicyView.ADD_HSTS_INPUT_ID = 'hsts-view-add-input';
  DomainSecurityPolicyView.ADD_STS_CHECK_ID = 'hsts-view-check-sts-input';
  DomainSecurityPolicyView.ADD_PINS_ID = 'hsts-view-add-pins';
  DomainSecurityPolicyView.ADD_HSTS_FORM_ID = 'hsts-view-add-form';
  DomainSecurityPolicyView.ADD_HSTS_SUBMIT_ID = 'hsts-view-add-submit';
  DomainSecurityPolicyView.QUERY_HSTS_INPUT_ID = 'hsts-view-query-input';
  DomainSecurityPolicyView.QUERY_HSTS_OUTPUT_DIV_ID = 'hsts-view-query-output';
  DomainSecurityPolicyView.QUERY_HSTS_FORM_ID = 'hsts-view-query-form';
  DomainSecurityPolicyView.QUERY_HSTS_SUBMIT_ID = 'hsts-view-query-submit';
  // Expect-CT form elements
  DomainSecurityPolicyView.ADD_EXPECT_CT_INPUT_ID = 'expect-ct-view-add-input';
  DomainSecurityPolicyView.ADD_EXPECT_CT_REPORT_URI_INPUT_ID =
      'expect-ct-view-add-report-uri-input';
  DomainSecurityPolicyView.ADD_EXPECT_CT_ENFORCE_CHECK_ID =
      'expect-ct-view-check-enforce-input';
  DomainSecurityPolicyView.ADD_EXPECT_CT_FORM_ID = 'expect-ct-view-add-form';
  DomainSecurityPolicyView.ADD_EXPECT_CT_SUBMIT_ID =
      'expect-ct-view-add-submit';
  DomainSecurityPolicyView.QUERY_EXPECT_CT_INPUT_ID =
      'expect-ct-view-query-input';
  DomainSecurityPolicyView.QUERY_EXPECT_CT_FORM_ID =
      'expect-ct-view-query-form';
  DomainSecurityPolicyView.QUERY_EXPECT_CT_SUBMIT_ID =
      'expect-ct-view-query-submit';
  DomainSecurityPolicyView.QUERY_EXPECT_CT_OUTPUT_DIV_ID =
      'expect-ct-view-query-output';
  DomainSecurityPolicyView.TEST_REPORT_EXPECT_CT_INPUT_ID =
      'expect-ct-view-test-report-uri';
  DomainSecurityPolicyView.TEST_REPORT_EXPECT_CT_FORM_ID =
      'expect-ct-view-test-report-form';
  DomainSecurityPolicyView.TEST_REPORT_EXPECT_CT_SUBMIT_ID =
      'expect-ct-view-test-report-submit';
  DomainSecurityPolicyView.TEST_REPORT_EXPECT_CT_OUTPUT_DIV_ID =
      'expect-ct-view-test-report-output';

  cr.addSingletonGetter(DomainSecurityPolicyView);

  DomainSecurityPolicyView.prototype = {
    // Inherit the superclass's methods.
    __proto__: superClass.prototype,

    onSubmitHSTSAdd_: function(event) {
      g_browser.sendHSTSAdd(this.addStsInput_.value, this.addStsCheck_.checked);
      g_browser.sendHSTSQuery(this.addStsInput_.value);
      this.queryStsInput_.value = this.addStsInput_.value;
      this.addStsCheck_.checked = false;
      this.addStsInput_.value = '';
      event.preventDefault();
    },

    onSubmitDelete_: function(event) {
      g_browser.sendDomainSecurityPolicyDelete(this.deleteInput_.value);
      this.deleteInput_.value = '';
      event.preventDefault();
    },

    onSubmitHSTSQuery_: function(event) {
      g_browser.sendHSTSQuery(this.queryStsInput_.value);
      event.preventDefault();
    },

    onHSTSQueryResult: function(result) {
      if (result.error != undefined) {
        this.queryStsOutputDiv_.innerHTML = '';
        var s = addNode(this.queryStsOutputDiv_, 'span');
        s.textContent = result.error;
        s.style.color = '#e00';
        yellowFade(this.queryStsOutputDiv_);
        return;
      }

      if (result.result == false) {
        this.queryStsOutputDiv_.innerHTML = '<b>Not found</b>';
        yellowFade(this.queryStsOutputDiv_);
        return;
      }

      this.queryStsOutputDiv_.innerHTML = '';

      var s = addNode(this.queryStsOutputDiv_, 'span');
      s.innerHTML = '<b>Found:</b><br/>';

      var keys = [
        'static_sts_domain',
        'static_upgrade_mode',
        'static_sts_include_subdomains',
        'static_sts_observed',
        'static_pkp_domain',
        'static_pkp_include_subdomains',
        'static_pkp_observed',
        'static_spki_hashes',
        'dynamic_sts_domain',
        'dynamic_upgrade_mode',
        'dynamic_sts_include_subdomains',
        'dynamic_sts_observed',
        'dynamic_sts_expiry',
        // TODO(crbug.com/779166): Remove these:
        'dynamic_pkp_domain',
        'dynamic_pkp_include_subdomains',
        'dynamic_pkp_observed',
        'dynamic_pkp_expiry',
        'dynamic_spki_hashes',
      ];

      var kStaticHashKeys =
          ['public_key_hashes', 'preloaded_spki_hashes', 'static_spki_hashes'];

      var staticHashes = [];
      for (var i = 0; i < kStaticHashKeys.length; ++i) {
        var staticHashValue = result[kStaticHashKeys[i]];
        if (staticHashValue != undefined && staticHashValue != '')
          staticHashes.push(staticHashValue);
      }

      for (var i = 0; i < keys.length; ++i) {
        var key = keys[i];
        var value = result[key];
        addTextNode(this.queryStsOutputDiv_, ' ' + key + ': ');

        // If there are no static_hashes, do not make it seem like there is a
        // static PKP policy in place.
        if (staticHashes.length == 0 && key.startsWith('static_pkp_')) {
          addNode(this.queryStsOutputDiv_, 'br');
          continue;
        }

        if (key === 'static_spki_hashes') {
          addNodeWithText(
              this.queryStsOutputDiv_, 'tt', staticHashes.join(','));
        } else if (key.indexOf('_upgrade_mode') >= 0) {
          addNodeWithText(this.queryStsOutputDiv_, 'tt', modeToString(value));
        } else {
          addNodeWithText(
              this.queryStsOutputDiv_, 'tt', value == undefined ? '' : value);
        }
        addNode(this.queryStsOutputDiv_, 'br');
      }

      yellowFade(this.queryStsOutputDiv_);
    },

    onSubmitExpectCTAdd_: function(event) {
      g_browser.sendExpectCTAdd(
          this.addExpectCTInput_.value, this.addExpectCTReportUriInput_.value,
          this.addExpectCTEnforceCheck_.checked);
      g_browser.sendExpectCTQuery(this.addExpectCTInput_.value);
      this.queryExpectCTInput_.value = this.addExpectCTInput_.value;
      this.addExpectCTInput_.value = '';
      this.addExpectCTReportUriInput_.value = '';
      this.addExpectCTEnforceCheck_.checked = false;
      event.preventDefault();
    },

    onSubmitExpectCTQuery_: function(event) {
      g_browser.sendExpectCTQuery(this.queryExpectCTInput_.value);
      event.preventDefault();
    },

    onExpectCTQueryResult: function(result) {
      if (result.error != undefined) {
        this.queryExpectCTOutputDiv_.innerHTML = '';
        var s = addNode(this.queryExpectCTOutputDiv_, 'span');
        s.textContent = result.error;
        s.style.color = '#e00';
        yellowFade(this.queryExpectCTOutputDiv_);
        return;
      }

      if (result.result == false) {
        this.queryExpectCTOutputDiv_.innerHTML = '<b>Not found</b>';
        yellowFade(this.queryExpectCTOutputDiv_);
        return;
      }

      this.queryExpectCTOutputDiv_.innerHTML = '';

      var s = addNode(this.queryExpectCTOutputDiv_, 'span');
      s.innerHTML = '<b>Found:</b><br/>';

      var keys = [
        'dynamic_expect_ct_domain',
        'dynamic_expect_ct_observed',
        'dynamic_expect_ct_expiry',
        'dynamic_expect_ct_enforce',
        'dynamic_expect_ct_report_uri',
      ];

      for (var i in keys) {
        var key = keys[i];
        var value = result[key];
        addTextNode(this.queryExpectCTOutputDiv_, ' ' + key + ': ');
        addNodeWithText(
            this.queryExpectCTOutputDiv_, 'tt',
            value == undefined ? '' : value);
        addNode(this.queryExpectCTOutputDiv_, 'br');
      }

      yellowFade(this.queryExpectCTOutputDiv_);
    },

    onSubmitExpectCTTestReport_: function(event) {
      g_browser.sendExpectCTTestReport(this.testExpectCTReportInput_.value);
      event.preventDefault();
    },

    onExpectCTTestReportResult: function(result) {
      if (result == 'success') {
        addTextNode(this.testExpectCTOutputDiv_, 'Test report succeeded');
      } else {
        addTextNode(this.testExpectCTOutputDiv_, 'Test report failed');
      }
      yellowFade(this.testExpectCTOutputDiv_);
    },

  };

  function modeToString(m) {
    // These numbers must match those in
    // TransportSecurityState::STSState::UpgradeMode.
    if (m == 0) {
      return 'FORCE_HTTPS';
    } else if (m == 1) {
      return 'DEFAULT';
    } else {
      return 'UNKNOWN';
    }
  }

  function yellowFade(element) {
    element.style.transitionProperty = 'background-color';
    element.style.transitionDuration = '0';
    element.style.backgroundColor = '#fffccf';
    setTimeout(function() {
      element.style.transitionDuration = '1000ms';
      element.style.backgroundColor = '#fff';
    }, 0);
  }

  return DomainSecurityPolicyView;
})();
