<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=671389
Bug 671389 - Implement CSP sandbox directive
-->
<head>
  <meta charset="utf-8">
  <title>Tests for Bug 671389</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<script type="application/javascript">

  SimpleTest.waitForExplicitFinish();

  // Check if two sandbox flags are the same, ignoring case-sensitivity.
  // getSandboxFlags returns a list of sandbox flags (if any) or
  // null if the flag is not set.
  // This function checks if two flags are the same, i.e., they're
  // either not set or have the same flags.
  function eqFlags(a, b) {
    if (a === null && b === null) { return true; }
    if (a === null || b === null) { return false; }
    if (a.length !== b.length) {  return false; }
    var a_sorted = a.map(function(e) { return e.toLowerCase(); }).sort();
    var b_sorted = b.map(function(e) { return e.toLowerCase(); }).sort();
    for (var i in a_sorted) {
      if (a_sorted[i] !== b_sorted[i]) {
        return false;
      }
    }
    return true;
  }

  // Get the sandbox flags of document doc.
  // If the flag is not set sandboxFlagsAsString returns null,
  // this function also returns null.
  // If the flag is set it may have some flags; in this case
  // this function returns the (potentially empty) list of flags.
  function getSandboxFlags(doc) {
    var flags = doc.sandboxFlagsAsString;
    if (flags === null) { return null; }
    return flags? flags.split(" "):[];
  }

  // Constructor for a CSP sandbox flags test. The constructor
  // expectes a description 'desc' and set of options 'opts':
  //  - sandboxAttribute: [null] or string corresponding to the iframe sandbox attributes
  //  - csp: [null] or string corresponding to the CSP sandbox flags
  //  - cspReportOnly: [null] or string corresponding to the CSP report-only sandbox flags
  //  - file: [null] or string corresponding to file the server should serve
  // Above, we use [brackets] to denote default values.
  function CSPFlagsTest(desc, opts) {
    function ifundef(x, v) {
      return (x !== undefined) ? x : v;
    }

    function intersect(as, bs) { // Intersect two csp attributes:
      as = as === null ? null
                       : as.split(' ').filter(function(x) { return !!x; });
      bs = bs === null ? null
                       : bs.split(' ').filter(function(x) { return !!x; });

      if (as === null) { return bs; }
      if (bs === null) { return as; }

      var cs = [];
      as.forEach(function(a) {
        if (a && bs.includes(a))
          cs.push(a);
      });
      return cs;
    }

    this.desc     = desc || "Untitled test";
    this.attr     = ifundef(opts.sandboxAttribute, null);
    this.csp      = ifundef(opts.csp, null);
    this.cspRO    = ifundef(opts.cspReportOnly, null);
    this.file     = ifundef(opts.file, null);
    this.expected = intersect(this.attr, this.csp);
  }

  // Return function that checks that the actual flags are the same as the
  // expected flags
  CSPFlagsTest.prototype.checkFlags = function(iframe) {
    var this_ = this;
    return function() {
      try {
        var actual = getSandboxFlags(SpecialPowers.wrap(iframe).contentDocument);
        ok(eqFlags(actual, this_.expected),
           this_.desc + ' - expected: "' + this_.expected + '", got: "' + actual + '"');
      } catch (e) {
        ok(false,
           this_.desc + ' - expected: "' + this_.expected + '", failed with: "' + e + '"');
      }
      runNextTest();
     };
  };

  // Set the iframe src and sandbox attribute
  CSPFlagsTest.prototype.runTest = function () {
    var iframe = document.createElement('iframe');
    document.getElementById("content").appendChild(iframe);
    iframe.onload = this.checkFlags(iframe);

    // set sandbox attribute
    if (this.attr === null) {
      iframe.removeAttribute('sandbox');
    } else {
      iframe.sandbox = this.attr;
    }

    // set query string
    var src = 'http://mochi.test:8888/tests/dom/security/test/csp/file_testserver.sjs';

    var delim = '?';

    if (this.csp !== null) {
      src += delim + 'csp=' + escape('sandbox ' + this.csp);
      delim = '&';
    }

    if (this.cspRO !== null) {
      src += delim + 'cspRO=' + escape('sandbox ' + this.cspRO);
      delim = '&';
    }

    if (this.file !== null) {
      src += delim + 'file=' + escape(this.file);
      delim = '&';
    }

    iframe.src = src;
    iframe.width = iframe.height = 10;

  }

  testCases = [
    {
      desc: "Test 1: Header should not override attribute",
      sandboxAttribute: "",
      csp: "allow-forms aLLOw-POinter-lock alLOW-popups aLLOW-SAME-ORIGin ALLOW-SCRIPTS allow-top-navigation"
    },
    {
      desc: "Test 2: Attribute should not override header",
      sandboxAttribute: "sandbox allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation",
      csp: ""
    },
    {
      desc: "Test 3: Header and attribute intersect",
      sandboxAttribute: "allow-same-origin allow-scripts",
      csp: "allow-forms allow-same-origin allow-scripts"
    },
    {
      desc: "Test 4: CSP sandbox sets the right flags (pt 1)",
      csp: "alLOW-FORms ALLOW-pointer-lock allow-popups allow-same-origin allow-scripts ALLOW-TOP-NAVIGation"
    },
    {
      desc: "Test 5: CSP sandbox sets the right flags (pt 2)",
      csp: "allow-same-origin allow-TOP-navigation"
    },
    {
      desc: "Test 6: CSP sandbox sets the right flags (pt 3)",
      csp: "allow-FORMS ALLOW-scripts"
    },
    {
      desc: "Test 7: CSP sandbox sets the right flags (pt 4)",
      csp: ""
    },
    {
      desc: "Test 8: CSP sandbox sets the right flags (pt 5)",
      csp: null
    },
    {
      desc: "Test 9: Read-only header should not override attribute",
      sandboxAttribute: "",
      cspReportOnly: "allow-forms ALLOW-pointer-lock allow-POPUPS allow-same-origin ALLOW-scripts allow-top-NAVIGATION"
    },
    {
      desc: "Test 10: Read-only header should not override CSP header",
      csp: "allow-forms allow-scripts",
      cspReportOnly: "allow-forms aLlOw-PoInTeR-lOcK aLLow-pOPupS aLLoW-SaME-oRIgIN alLow-scripts allow-tOp-navigation"
    },
    {
      desc: "Test 11: Read-only header should not override attribute or CSP header",
      sandboxAttribute: "allow-same-origin allow-scripts",
      csp: "allow-forms allow-same-origin allow-scripts",
      cspReportOnly: "allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation"
    },
    {
      desc: "Test 12: CSP sandbox not affected by document.write()",
      csp: "allow-scripts",
      file: 'tests/dom/security/test/csp/file_iframe_sandbox_document_write.html'
    },
  ].map(function(t) { return (new CSPFlagsTest(t.desc,t)); });


  var testCaseIndex = 0;

  // Track ok messages from iframes
  var childMessages = 0;
  var totalChildMessages = 1;


  // Check to see if we ran all the tests and received all messges
  // from child iframes. If so, finish.
  function tryFinish() {
    if (testCaseIndex === testCases.length && childMessages === totalChildMessages){
      SimpleTest.finish();
    }
  }

  function runNextTest() {

    tryFinish();

    if (testCaseIndex < testCases.length) {
      testCases[testCaseIndex].runTest();
      testCaseIndex++;
    }
  }

  function receiveMessage(event) {
    ok(event.data.ok, event.data.desc);
    childMessages++;
    tryFinish();
  }

  window.addEventListener("message", receiveMessage);

  addLoadEvent(runNextTest);
</script>
<body>
  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=671389">Mozilla Bug 671389</a> - Implement CSP sandbox directive
  <p id="display"></p>
  <div id="content">
  </div>
</body>
</html>
