tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

dangling-markup-mitigation-data-url.tentative.sub.html (8781B)


      1 <!DOCTYPE html>
      2 <script src="/resources/testharness.js"></script>
      3 <script src="/resources/testharnessreport.js"></script>
      4 <body>
      5 <script>
      6  function readableURL(url) {
      7    return url.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
      8  }
      9 
     10  // For each of the following tests, we'll inject a frame containing the HTML we'd like to poke at
     11  // as a `srcdoc` attribute. Because we're injecting markup via `srcdoc`, we need to entity-escape
     12  // the content we'd like to treat as "raw" (e.g. `\n` => `&#10;`, `<` => `&lt;`), and
     13  // double-escape the "escaped" content.
     14  var rawBrace = "&lt;";
     15  var escapedBrace = "&amp;lt;";
     16  var doubleEscapedBrace = "&amp;amp;lt;";
     17  var rawNewline = "&#10;";
     18  var escapedNewline = "&amp;#10;";
     19  // doubleEscapedNewline is used inside a data URI, and so must have its '#' escaped.
     20  var doubleEscapedNewline = "&amp;amp;%2310;";
     21 
     22  function appendFrameAndGetElement(test, frame) {
     23    return new Promise((resolve, reject) => {
     24      frame.onload = test.step_func(_ => {
     25        frame.onload = null;
     26        resolve(frame.contentDocument.querySelector('#dangling'));
     27      });
     28      document.body.appendChild(frame);
     29    });
     30  }
     31 
     32  function assert_img_loaded(test, frame) {
     33    appendFrameAndGetElement(test, frame)
     34      .then(test.step_func_done(img => {
     35        assert_equals(img.naturalHeight, 1, "Height");
     36        frame.remove();
     37      }));
     38  }
     39 
     40  function assert_img_not_loaded(test, frame) {
     41    appendFrameAndGetElement(test, frame)
     42      .then(test.step_func_done(img => {
     43        assert_equals(img.naturalHeight, 0, "Height");
     44        assert_equals(img.naturalWidth, 0, "Width");
     45      }));
     46  }
     47 
     48  function assert_nested_img_not_loaded(test, frame) {
     49    window.addEventListener('message', test.step_func(e => {
     50      if (e.source != frame.contentWindow)
     51        return;
     52 
     53      assert_equals(e.data, 'error');
     54      test.done();
     55    }));
     56    appendFrameAndGetElement(test, frame);
     57  }
     58 
     59  function assert_nested_img_loaded(test, frame) {
     60    window.addEventListener('message', test.step_func(e => {
     61      if (e.source != frame.contentWindow)
     62        return;
     63 
     64      assert_equals(e.data, 'loaded');
     65      test.done();
     66    }));
     67    appendFrameAndGetElement(test, frame);
     68  }
     69 
     70  function createFrame(markup) {
     71    var i = document.createElement('iframe');
     72    i.srcdoc = `${markup}sekrit`;
     73    return i;
     74  }
     75 
     76  // Subresource requests:
     77  [
     78    // Data URLs don't themselves trigger blocking:
     79    `<img id="dangling" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
     80    `<img id="dangling" src="data:image/png;base64,${rawNewline}iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
     81    `<img id="dangling" src="data:image/png;base64,i${rawNewline}VBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
     82 
     83    // Data URLs with visual structure don't trigger blocking
     84    `<img id="dangling" src="data:image/svg+xml;utf8,
     85      <svg width='1' height='1' xmlns='http://www.w3.org/2000/svg'>
     86        <rect width='100%' height='100%' fill='rebeccapurple'/>
     87        <rect x='10%' y='10%' width='80%' height='80%' fill='lightgreen'/>
     88      </svg>">`
     89  ].forEach(markup => {
     90    async_test(t => {
     91      var i = createFrame(`${markup} <element attr="" another=''>`);
     92      assert_img_loaded(t, i);
     93    }, readableURL(markup));
     94  });
     95 
     96  // Nested subresource requests:
     97  //
     98  // The following tests load a given HTML string into `<iframe srcdoc="...">`, so we'll
     99  // end up with a frame with an ID of `dangling` inside the srcdoc frame. That frame's
    100  // `src` is a `data:` URL that resolves to an HTML document containing an `<img>`. The
    101  // error/load handlers on that image are piped back up to the top-level document to
    102  // determine whether the tests' expectations were met. *phew*
    103 
    104  // Allowed:
    105  [
    106     // Just a newline:
    107     `<iframe id="dangling"
    108        src="data:text/html,
    109            <img
    110              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    111              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    112              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png'>
    113        ">
    114     </iframe>`,
    115 
    116     // Just a brace:
    117     `<iframe id="dangling"
    118        src="data:text/html,
    119            <img
    120              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    121              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    122              src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${rawBrace}'>
    123        ">
    124     </iframe>`,
    125 
    126     // Newline and escaped brace.
    127     `<iframe id="dangling"
    128        src="data:text/html,
    129            <img
    130              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    131              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    132              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${doubleEscapedBrace}'>
    133        ">
    134     </iframe>`,
    135 
    136     // Brace and escaped newline:
    137     `<iframe id="dangling"
    138        src="data:text/html,
    139            <img
    140              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    141              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    142              src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${doubleEscapedNewline}${rawBrace}'>
    143        ">
    144     </iframe>`,
    145  ].forEach(markup => {
    146    async_test(t => {
    147      var i = createFrame(`
    148        <script>
    149          // Repeat the message so that the parent can track this frame as the source.
    150          window.onmessage = e => window.parent.postMessage(e.data, '*');
    151        </scr`+`ipt>
    152        ${markup}
    153      `);
    154      assert_nested_img_loaded(t, i);
    155    }, readableURL(markup));
    156  });
    157 
    158  // Nested requests that should fail:
    159  [
    160     // Newline and brace:
    161     `<iframe id="dangling"
    162        src="data:text/html,
    163            <img
    164              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    165              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    166              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
    167        ">
    168     </iframe>`,
    169 
    170     // Leading whitespace:
    171     `<iframe id="dangling"
    172        src="     data:text/html,
    173            <img
    174              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    175              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    176              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
    177        ">
    178     </iframe>`,
    179 
    180     // Leading newline:
    181     `<iframe id="dangling"
    182        src="\ndata:text/html,
    183            <img
    184              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    185              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    186              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
    187        ">
    188     </iframe>`,
    189     `<iframe id="dangling"
    190        src="${rawNewline}data:text/html,
    191            <img
    192              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    193              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    194              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
    195        ">
    196     </iframe>`,
    197 
    198     // Leading tab:
    199     `<iframe id="dangling"
    200        src="\tdata:text/html,
    201            <img
    202              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    203              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    204              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
    205        ">
    206     </iframe>`,
    207 
    208     // Leading carrige return:
    209     `<iframe id="dangling"
    210        src="\rdata:text/html,
    211            <img
    212              onload='window.parent.postMessage(&quot;loaded&quot;, &quot;*&quot;);'
    213              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
    214              src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
    215        ">
    216     </iframe>`,
    217  ].forEach(markup => {
    218    async_test(t => {
    219      var i = createFrame(`
    220        <script>
    221          // Repeat the message so that the parent can track this frame as the source.
    222          window.onmessage = e => window.parent.postMessage(e.data, '*');
    223        </scr`+`ipt>
    224        ${markup}
    225      `);
    226      assert_nested_img_not_loaded(t, i);
    227    }, readableURL(markup));
    228  });
    229 </script>