tor-browser

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

common.js (7857B)


      1 const executor_path = '/common/dispatcher/executor.html?pipe=';
      2 const remote_executor_path = '/common/dispatcher/remote-executor.html?pipe=';
      3 const executor_worker_path = '/common/dispatcher/executor-worker.js?pipe=';
      4 const remote_executor_worker_path = '/common/dispatcher/remote-executor-worker.js?pipe=';
      5 const executor_service_worker_path = '/common/dispatcher/executor-service-worker.js?pipe=';
      6 
      7 // COEP
      8 const coep_none =
      9    '|header(Cross-Origin-Embedder-Policy,none)';
     10 const coep_credentialless =
     11    '|header(Cross-Origin-Embedder-Policy,credentialless)';
     12 
     13 // DIP
     14 const dip_none =
     15    '|header(Document-Isolation-Policy,none)';
     16 const dip_credentialless =
     17    '|header(Document-Isolation-Policy,isolate-and-credentialless)';
     18 const dip_require_corp =
     19    '|header(Document-Isolation-Policy,isolate-and-require-corp)';
     20 
     21 // DIP-Report-Only
     22 const dip_report_only_credentialless =
     23    '|header(Document-Isolation-Policy-Report-Only,isolate-and-credentialless)';
     24 
     25 // CORP
     26 const corp_cross_origin =
     27    '|header(Cross-Origin-Resource-Policy,cross-origin)';
     28 
     29 const cookie_same_site_none = ';SameSite=None;Secure';
     30 
     31 // Test using the modern async/await primitives are easier to read/write.
     32 // However they run sequentially, contrary to async_test. This is the parallel
     33 // version, to avoid timing out.
     34 let promise_test_parallel = (promise, description) => {
     35  async_test(test => {
     36    promise(test)
     37      .then(() => test.done())
     38      .catch(test.step_func(error => { throw error; }));
     39  }, description);
     40 };
     41 
     42 // Add a cookie |cookie_key|=|cookie_value| on an |origin|.
     43 // Note: cookies visibility depends on the path of the document. Those are set
     44 // from a document from: /html/cross-origin-embedder-policy/credentialless/. So
     45 // the cookie is visible to every path underneath.
     46 const setCookie = async (origin, cookie_key, cookie_value) => {
     47  const popup_token = token();
     48  const popup_url = origin + executor_path + `&uuid=${popup_token}`;
     49  const popup = window.open(popup_url);
     50 
     51  const reply_token = token();
     52  send(popup_token, `
     53    document.cookie = "${cookie_key}=${cookie_value}";
     54    send("${reply_token}", "done");
     55  `);
     56  assert_equals(await receive(reply_token), "done");
     57  popup.close();
     58 }
     59 
     60 let parseCookies = function(headers_json) {
     61  if (!headers_json["cookie"])
     62    return {};
     63 
     64  return headers_json["cookie"]
     65    .split(';')
     66    .map(v => v.split('='))
     67    .reduce((acc, v) => {
     68      acc[v[0].trim()] = v[1].trim();
     69      return acc;
     70    }, {});
     71 }
     72 
     73 // Open a new window with a given |origin|, loaded with DIP:credentialless. The
     74 // new document will execute any scripts sent toward the token it returns.
     75 const newCredentiallessWindow = (origin) => {
     76  const main_document_token = token();
     77  const url = origin + executor_path + dip_credentialless +
     78    `&uuid=${main_document_token}`;
     79  const context = window.open(url);
     80  add_completion_callback(() => w.close());
     81  return main_document_token;
     82 };
     83 
     84 // Create a new iframe, loaded with DIP:credentialless.
     85 // The new document will execute any scripts sent toward the token it returns.
     86 const newCredentiallessIframe = (parent_token, child_origin) => {
     87  const sub_document_token = token();
     88  const iframe_url = child_origin + executor_path + dip_credentialless +
     89    `&uuid=${sub_document_token}`;
     90  send(parent_token, `
     91    let iframe = document.createElement("iframe");
     92    iframe.src = "${iframe_url}";
     93    document.body.appendChild(iframe);
     94  `)
     95  return sub_document_token;
     96 };
     97 
     98 // The following functions create remote execution contexts with the matching
     99 // origins and headers. The first return value is the uuid that can be used
    100 // to instantiate a RemoteContext object. The second return value is the URL of
    101 // the context that was created.
    102 async function createIframeContext(t, origin, header) {
    103  const uuid = token();
    104  const frame_url = origin + remote_executor_path + header + '&uuid=' + uuid;
    105  const frame = await with_iframe(frame_url);
    106  t.add_cleanup(() => frame.remove());
    107  return [uuid, frame_url];
    108 }
    109 
    110 async function createDedicatedWorkerContext(t, origin, header) {
    111  const iframe_uuid = token();
    112  const frame_url = origin + remote_executor_path + header + '&uuid=' + iframe_uuid;
    113  const frame = await with_iframe(frame_url);
    114  t.add_cleanup(() => frame.remove());
    115 
    116  const uuid = token();
    117  const worker_url = origin + remote_executor_worker_path + '&uuid=' + uuid;
    118  const ctx = new RemoteContext(iframe_uuid);
    119  await ctx.execute_script(
    120    (url) => {
    121      const worker = new Worker(url);
    122    }, [worker_url]);
    123  return [uuid, worker_url];
    124 }
    125 
    126 async function createSharedWorkerContext(t, origin, header) {
    127  const uuid = token();
    128  const worker_url = origin + remote_executor_worker_path + header + '&uuid=' + uuid;
    129  const worker = new SharedWorker(worker_url);
    130  worker.addEventListener('error', t.unreached_func('Worker.onerror'));
    131  return [uuid, worker_url];
    132 }
    133 
    134 async function createIframeWithSWContext(t, origin, header) {
    135  // Register a service worker with no headers.
    136  const uuid = token();
    137  const frame_url = origin + remote_executor_path + header + '&uuid=' + uuid;
    138  const service_worker_url = origin + executor_service_worker_path;
    139  const reg = await service_worker_unregister_and_register(
    140    t, service_worker_url, frame_url);
    141  const worker = reg.installing || reg.waiting || reg.active;
    142  worker.addEventListener('error', t.unreached_func('Worker.onerror'));
    143 
    144  const frame = await with_iframe(frame_url);
    145  t.add_cleanup(() => {
    146    reg.unregister();
    147    frame.remove();
    148  });
    149  return [uuid, frame_url];
    150 }
    151 
    152 // A common interface for building the 4 type of execution contexts. Outputs the
    153 // token needed to create the RemoteContext.
    154 async function getTokenFromEnvironment(t,  environment, headers) {
    155  switch(environment) {
    156    case "document":
    157      const iframe_context = await createIframeContext(t, window.origin, headers);
    158      return iframe_context[0];
    159    case "dedicated_worker":
    160      const dedicated_worker_context = await createDedicatedWorkerContext(t, window.origin, headers);
    161      return dedicated_worker_context[0];
    162    case "shared_worker":
    163      const shared_worker_context = await createSharedWorkerContext(t, window.origin, headers);
    164      return shared_worker_context[0];
    165    case "service_worker":
    166      const sw_context = await createIframeWithSWContext(t, window.origin, headers);
    167      return sw_context[0];
    168  }
    169 }
    170 
    171 // A common interface for building the 4 type of execution contexts:
    172 // It outputs: [
    173 //   - The token to communicate with the environment.
    174 //   - A promise resolved when the environment encounters an error.
    175 // ]
    176 const environments = {
    177  document: headers => {
    178    const tok = token();
    179    const url = window.origin + executor_path + headers + `&uuid=${tok}`;
    180    const context = window.open(url);
    181    add_completion_callback(() => context.close());
    182    return [tok, new Promise(resolve => {})];
    183  },
    184 
    185  dedicated_worker: headers => {
    186    const tok = token();
    187    const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
    188    const context = new Worker(url);
    189    return [tok, new Promise(resolve => context.onerror = resolve)];
    190  },
    191 
    192  shared_worker: headers => {
    193    const tok = token();
    194    const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
    195    const context = new SharedWorker(url);
    196    return [tok, new Promise(resolve => context.onerror = resolve)];
    197  },
    198 
    199  service_worker: headers => {
    200    const tok = token();
    201    const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
    202    const scope = url; // Generate a one-time scope for service worker.
    203    const error = new Promise(resolve => {
    204      navigator.serviceWorker.register(url, {scope: scope})
    205        .then(registration => {
    206          add_completion_callback(() => registration.unregister());
    207        }, /* catch */ resolve);
    208    });
    209    return [tok, error];
    210  },
    211 };