tor-browser

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

cookie-test-helpers.js (8200B)


      1 'use strict';
      2 
      3 // TODO(jsbell): Once ServiceWorker is supported, add arbitrary path coverage.
      4 const kPath = location.pathname.replace(/[^/]+$/, '');
      5 
      6 // True when running in a document context as opposed to a worker context
      7 const kHasDocument = typeof document !== 'undefined';
      8 
      9 // True when running on unsecured 'http:' rather than secured 'https:'.
     10 const kIsUnsecured = location.protocol !== 'https:';
     11 
     12 const kCookieHelperCgi = 'resources/cookie_helper.py';
     13 
     14 // Approximate async equivalent to the document.cookie getter but with
     15 // important differences: optional additional getAll arguments are
     16 // forwarded, and an empty cookie jar returns undefined.
     17 //
     18 // This is intended primarily for verification against expected cookie
     19 // jar contents. It should produce more readable messages using
     20 // assert_equals in failing cases than assert_object_equals would
     21 // using parsed cookie jar contents and also allows expectations to be
     22 // written more compactly.
     23 async function getCookieString(...args) {
     24  const cookies = await cookieStore.getAll(...args);
     25  return cookies.length
     26    ? cookies.map(({name, value}) =>
     27                  (name ? (name + '=') : '') + value).join('; ')
     28    : undefined;
     29 }
     30 
     31 // Approximate async equivalent to the document.cookie getter but from
     32 // the server's point of view. Returns UTF-8 interpretation. Allows
     33 // sub-path to be specified.
     34 //
     35 // Unlike document.cookie, this returns undefined when no cookies are
     36 // present.
     37 async function getCookieStringHttp(extraPath = null) {
     38  const url =
     39        kCookieHelperCgi + ((extraPath == null) ? '' : ('/' + extraPath));
     40  const response = await fetch(url, { credentials: 'include' });
     41  const text = await response.text();
     42  assert_equals(
     43      response.ok,
     44      true,
     45      'CGI should have succeeded in getCookieStringHttp\n' + text);
     46  assert_equals(
     47      response.headers.get('content-type'),
     48      'text/plain; charset=utf-8',
     49      'CGI did not return UTF-8 text in getCookieStringHttp');
     50  if (text === '')
     51    return undefined;
     52  assert_equals(
     53      text.indexOf('cookie='),
     54      0,
     55      'CGI response did not begin with "cookie=" and was not empty: ' + text);
     56  return decodeURIComponent(text.replace(/^cookie=/, ''));
     57 }
     58 
     59 // Approximate async equivalent to the document.cookie getter but from
     60 // the server's point of view. Returns binary string
     61 // interpretation. Allows sub-path to be specified.
     62 //
     63 // Unlike document.cookie, this returns undefined when no cookies are
     64 // present.
     65 async function getCookieBinaryHttp(extraPath = null) {
     66  const url =
     67        kCookieHelperCgi +
     68        ((extraPath == null) ?
     69         '' :
     70         ('/' + extraPath)) + '?charset=iso-8859-1';
     71  const response = await fetch(url, { credentials: 'include' });
     72  const text = await response.text();
     73  assert_equals(
     74      response.ok,
     75      true,
     76      'CGI should have succeeded in getCookieBinaryHttp\n' + text);
     77  assert_equals(
     78      response.headers.get('content-type'),
     79      'text/plain; charset=iso-8859-1',
     80      'CGI did not return ISO 8859-1 text in getCookieBinaryHttp');
     81  if (text === '')
     82    return undefined;
     83  assert_equals(
     84      text.indexOf('cookie='),
     85      0,
     86      'CGI response did not begin with "cookie=" and was not empty: ' + text);
     87  return unescape(text.replace(/^cookie=/, ''));
     88 }
     89 
     90 // Approximate async equivalent to the document.cookie setter but from
     91 // the server's point of view.
     92 async function setCookieStringHttp(setCookie) {
     93  const encodedSetCookie = encodeURIComponent(setCookie);
     94  const url = kCookieHelperCgi;
     95  const headers = new Headers();
     96  headers.set(
     97      'content-type',
     98      'application/x-www-form-urlencoded; charset=utf-8');
     99  const response = await fetch(
    100      url,
    101      {
    102        credentials: 'include',
    103        method: 'POST',
    104        headers: headers,
    105        body: 'set-cookie=' + encodedSetCookie,
    106      });
    107  const text = await response.text();
    108  assert_equals(
    109      response.ok,
    110      true,
    111      'CGI should have succeeded in setCookieStringHttp set-cookie: ' +
    112        setCookie + '\n' + text);
    113  assert_equals(
    114      response.headers.get('content-type'),
    115      'text/plain; charset=utf-8',
    116      'CGI did not return UTF-8 text in setCookieStringHttp');
    117  assert_equals(
    118      text,
    119      'set-cookie=' + encodedSetCookie,
    120      'CGI did not faithfully echo the set-cookie value');
    121 }
    122 
    123 // Approximate async equivalent to the document.cookie setter but from
    124 // the server's point of view. This version sets a binary cookie rather
    125 // than a UTF-8 one.
    126 async function setCookieBinaryHttp(setCookie) {
    127  const encodedSetCookie = escape(setCookie).split('/').join('%2F');
    128  const url = kCookieHelperCgi + '?charset=iso-8859-1';
    129  const headers = new Headers();
    130  headers.set(
    131      'content-type',
    132      'application/x-www-form-urlencoded; charset=iso-8859-1');
    133  const response = await fetch(url, {
    134    credentials: 'include',
    135    method: 'POST',
    136    headers: headers,
    137    body: 'set-cookie=' + encodedSetCookie
    138  });
    139  const text = await response.text();
    140  assert_equals(
    141      response.ok,
    142      true,
    143      'CGI should have succeeded in setCookieBinaryHttp set-cookie: ' +
    144        setCookie + '\n' + text);
    145  assert_equals(
    146      response.headers.get('content-type'),
    147      'text/plain; charset=iso-8859-1',
    148      'CGI did not return Latin-1 text in setCookieBinaryHttp');
    149  assert_equals(
    150      text,
    151      'set-cookie=' + encodedSetCookie,
    152      'CGI did not faithfully echo the set-cookie value');
    153 }
    154 
    155 // Async document.cookie getter; converts '' to undefined which loses
    156 // information in the edge case where a single ''-valued anonymous
    157 // cookie is visible.
    158 async function getCookieStringDocument() {
    159  if (!kHasDocument)
    160    throw 'document.cookie not available in this context';
    161  return String(document.cookie || '') || undefined;
    162 }
    163 
    164 // Async document.cookie setter
    165 async function setCookieStringDocument(setCookie) {
    166  if (!kHasDocument)
    167    throw 'document.cookie not available in this context';
    168  document.cookie = setCookie;
    169 }
    170 
    171 // Observe the next 'change' event on the cookieStore. Typical usage:
    172 //
    173 //   const eventPromise = observeNextCookieChangeEvent();
    174 //   await /* something that modifies cookies */
    175 //   await verifyCookieChangeEvent(
    176 //     eventPromise, {changed: [{name: 'name', value: 'value'}]});
    177 //
    178 function observeNextCookieChangeEvent() {
    179  return new Promise(resolve => {
    180    cookieStore.addEventListener('change', e => resolve(e), {once: true});
    181  });
    182 }
    183 
    184 async function verifyCookieChangeEvent(eventPromise, expected, description) {
    185  description = description ? description + ': ' : '';
    186  expected = Object.assign({changed:[], deleted:[]}, expected);
    187  const event = await eventPromise;
    188  assert_equals(event.changed.length, expected.changed.length,
    189               description + 'number of changed cookies');
    190  for (let i = 0; i < event.changed.length; ++i) {
    191    assert_equals(event.changed[i].name, expected.changed[i].name,
    192                 description + 'changed cookie name');
    193    assert_equals(event.changed[i].value, expected.changed[i].value,
    194                 description + 'changed cookie value');
    195  }
    196  assert_equals(event.deleted.length, expected.deleted.length,
    197               description + 'number of deleted cookies');
    198  for (let i = 0; i < event.deleted.length; ++i) {
    199    assert_equals(event.deleted[i].name, expected.deleted[i].name,
    200                 description + 'deleted cookie name');
    201    assert_equals(event.deleted[i].value, expected.deleted[i].value,
    202                 description + 'deleted cookie value');
    203  }
    204 }
    205 
    206 // Helper function for promise_test with cookies; cookies
    207 // named in these tests are cleared before/after the test
    208 // body function is executed.
    209 async function cookie_test(func, description) {
    210 
    211  // Wipe cookies used by tests before and after the test.
    212  async function deleteAllCookies() {
    213    const cookies = await cookieStore.getAll();
    214    await Promise.all(cookies.flatMap(
    215        ({name}) =>
    216            [cookieStore.delete(name),
    217             cookieStore.delete({name, partitioned: true}),
    218    ]));
    219  }
    220 
    221  return promise_test(async t => {
    222    await deleteAllCookies();
    223    try {
    224      return await func(t);
    225    } finally {
    226      await deleteAllCookies();
    227    }
    228  }, description);
    229 }