tor-browser

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

iframe-test.js (9064B)


      1 // To use the functions below, be sure to include the following files in your
      2 // test:
      3 // - "/common/get-host-info.sub.js" to get the different origin values.
      4 // - "common.js" to have the origins easily available.
      5 // - "/common/dispatcher/dispatcher.js" for cross-origin messaging.
      6 // - "/common/utils.js" for token().
      7 
      8 function getBaseExecutorPath(origin) {
      9  return origin + '/common/dispatcher/executor.html';
     10 }
     11 
     12 function getHeadersPipe(headers) {
     13  const coop_header = headers.coop ?
     14    `|header(Cross-Origin-Opener-Policy,${encodeURIComponent(headers.coop)})` : '';
     15  const coep_header = headers.coep ?
     16    `|header(Cross-Origin-Embedder-Policy,${encodeURIComponent(headers.coep)})` : '';
     17  return coop_header + coep_header;
     18 }
     19 
     20 function getExecutorPath(uuid, origin, headers) {
     21  return getBaseExecutorPath(origin) +
     22    `?uuid=${uuid}` +
     23    `&pipe=${getHeadersPipe(headers)}`;
     24 }
     25 
     26 function evaluate(target_token, script) {
     27  const reply_token = token();
     28  send(target_token, `send('${reply_token}', ${script});`);
     29  return receive(reply_token);
     30 }
     31 
     32 // Return true if an opened iframe can access |property| on a stored
     33 // window.popup object without throwing an error.
     34 function iframeCanAccessProperty(iframe_token, property) {
     35  const reply_token = token();
     36  send(iframe_token,
     37    `try {
     38       const unused = window.popup['${property}'];
     39       send('${reply_token}', 'true')
     40     } catch (errors) {
     41       send('${reply_token}', 'false')
     42     }`);
     43  return receive(reply_token);
     44 }
     45 
     46 // Returns the script necessary to open a popup, given the method in
     47 // `popup_via`. Supported methods are 'window_open' that leverages
     48 // window.open(), 'anchor' that creates an <a> HTML element and clicks on it,
     49 // and 'form' that creates a form and submits it.
     50 function popupOpeningScript(popup_via, popup_url, popup_origin, headers,
     51    popup_token) {
     52  if (popup_via === 'window_open')
     53    return `window.popup = window.open('${popup_url}', '${popup_token}');`;
     54 
     55  if (popup_via === 'anchor') {
     56    return `
     57      const anchor = document.createElement('a');
     58      anchor.href = '${popup_url}';
     59      anchor.rel = "opener";
     60      anchor.target = '${popup_token}';
     61      anchor.innerText = "anchor";
     62      document.body.appendChild(anchor);
     63      anchor.click();
     64      `;
     65  }
     66 
     67  if (popup_via === "form") {
     68    return `
     69      const form = document.createElement("form");
     70      form.action = '${getBaseExecutorPath(popup_origin.origin)}';
     71      form.target = '${popup_token}';
     72      form.method = 'GET';
     73      const add_param = (name, value) => {
     74        const input = document.createElement("input");
     75        input.name = name;
     76        input.value = value;
     77        form.appendChild(input);
     78      };
     79      add_param("uuid", "${popup_token}");
     80      add_param("pipe", "${getHeadersPipe(headers)}");
     81      document.body.appendChild(form);
     82      form.submit();
     83      `;
     84  }
     85 
     86  assert_not_reached('Unrecognized popup opening method.');
     87 }
     88 
     89 function promise_test_parallel(promise, description) {
     90  async_test(test => {
     91    promise(test)
     92        .then(() => test.done())
     93        .catch(test.step_func(error => { throw error; }));
     94  }, description);
     95 };
     96 
     97 // Verifies that a popup with origin `popup_origin` and headers `headers` has
     98 // the expected `opener_state` after being opened from an iframe with origin
     99 // `iframe_origin`.
    100 function iframe_test(description, iframe_origin, popup_origin, headers,
    101    expected_opener_state) {
    102  for (const popup_via of ['window_open', 'anchor','form']) {
    103    promise_test_parallel(async t => {
    104      const iframe_token = token();
    105      const popup_token = token();
    106      const reply_token = token();
    107 
    108      const frame = document.createElement("iframe");
    109      const iframe_url = getExecutorPath(
    110        iframe_token,
    111        iframe_origin.origin,
    112        {});
    113 
    114      frame.src = iframe_url;
    115      document.body.append(frame);
    116 
    117      send(iframe_token, `send('${reply_token}', 'Iframe loaded');`);
    118      assert_equals(await receive(reply_token), 'Iframe loaded');
    119 
    120      const popup_url = getExecutorPath(
    121        popup_token,
    122        popup_origin.origin,
    123        headers);
    124 
    125      // We open popup and then ping it, it will respond after loading.
    126      send(iframe_token, popupOpeningScript(popup_via, popup_url, popup_origin,
    127                                            headers, popup_token));
    128      send(popup_token, `send('${reply_token}', 'Popup loaded');`);
    129      assert_equals(await receive(reply_token), 'Popup loaded');
    130 
    131      // Make sure the popup and the iframe are removed once the test has run,
    132      // keeping a clean state.
    133      add_completion_callback(() => {
    134        frame.remove();
    135        send(popup_token, `close()`);
    136      });
    137 
    138      // Give some time for things to settle across processes etc. before
    139      // proceeding with verifications.
    140      await new Promise(resolve => { t.step_timeout(resolve, 500); });
    141 
    142      // Verify that the opener is in the state we expect it to be in.
    143      switch (expected_opener_state) {
    144        case 'preserved': {
    145          assert_equals(
    146            await evaluate(popup_token, 'opener != null'), "true",
    147            'Popup has an opener?');
    148          assert_equals(
    149            await evaluate(popup_token, `name === '${popup_token}'`), "true",
    150            'Popup has a name?');
    151 
    152          // When the popup was created using window.open, we've kept a handle
    153          // and we can do extra verifications.
    154          if (popup_via === 'window_open') {
    155            assert_equals(
    156              await evaluate(iframe_token, 'popup != null'), "true",
    157              'Popup handle is non-null in iframe?');
    158            assert_equals(
    159              await evaluate(iframe_token, 'popup.closed'), "false",
    160              'Popup appears closed from iframe?');
    161            assert_equals(
    162              await iframeCanAccessProperty(iframe_token, "document"),
    163              popup_origin === iframe_origin ? "true" : "false",
    164              'Iframe has dom access to the popup?');
    165            assert_equals(
    166              await iframeCanAccessProperty(iframe_token, "frames"), "true",
    167              'Iframe has cross origin access to the popup?');
    168          }
    169          break;
    170        }
    171        case 'restricted': {
    172          assert_equals(
    173            await evaluate(popup_token, 'opener != null'), "true",
    174            'Popup has an opener?');
    175          assert_equals(
    176            await evaluate(popup_token, `name === ''`), "true",
    177            'Popup name is cleared?');
    178 
    179          // When the popup was created using window.open, we've kept a handle
    180          // and we can do extra verifications.
    181          if (popup_via === 'window_open') {
    182            assert_equals(
    183              await evaluate(iframe_token, 'popup != null'), "true",
    184              'Popup handle is non-null in iframe?');
    185            assert_equals(
    186              await evaluate(iframe_token, 'popup.closed'), "false",
    187              'Popup appears closed from iframe?');
    188            assert_equals(
    189              await iframeCanAccessProperty(iframe_token, "document"), "false",
    190              'Iframe has dom access to the popup?');
    191            assert_equals(
    192              await iframeCanAccessProperty(iframe_token, "frames"), "false",
    193              'Iframe has cross origin access to the popup?');
    194            assert_equals(
    195              await iframeCanAccessProperty(iframe_token, "closed"), "true",
    196              'Iframe has limited cross origin access to the popup?');
    197          }
    198          break;
    199        }
    200        case 'severed': {
    201          assert_equals(await evaluate(popup_token, 'opener != null'), "false",
    202                       'Popup has an opener?');
    203          assert_equals(
    204            await evaluate(popup_token, `name === ''`), "true",
    205            'Popup name is cleared?');
    206 
    207          // When the popup was created using window.open, we've kept a handle
    208          // and we can do extra verifications.
    209          if (popup_via === 'window_open') {
    210            assert_equals(
    211              await evaluate(iframe_token, 'popup != null'), "true",
    212              'Popup handle is non-null in iframe?');
    213            assert_equals(
    214              await evaluate(iframe_token, 'popup.closed'), "true",
    215              'Popup appears closed from iframe?');
    216          }
    217          break;
    218        }
    219        case 'noopener': {
    220          assert_equals(await evaluate(popup_token, 'opener != null'), "false",
    221                        'Popup has an opener?');
    222          assert_equals(
    223            await evaluate(popup_token, `name === ''`), "true",
    224            'Popup name is cleared?');
    225 
    226          // When the popup was created using window.open, we've kept a handle
    227          // and we can do extra verifications.
    228          if (popup_via === 'window_open') {
    229            assert_equals(
    230              await evaluate(iframe_token, 'popup == null'), "true",
    231              'Popup handle is null in iframe?');
    232          }
    233          break;
    234        }
    235        default:
    236          assert_not_reached('Unrecognized opener state: ' +
    237            expected_opener_state);
    238      }
    239    }, `${description} with ${popup_via}`);
    240  }
    241 }