summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/cookie-store/resources/cookie-test-helpers.js
blob: 178947ad6eca72967aa9bbc3a13172a0402bc17d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
'use strict';

// TODO(jsbell): Once ServiceWorker is supported, add arbitrary path coverage.
const kPath = location.pathname.replace(/[^/]+$/, '');

// True when running in a document context as opposed to a worker context
const kHasDocument = typeof document !== 'undefined';

// True when running on unsecured 'http:' rather than secured 'https:'.
const kIsUnsecured = location.protocol !== 'https:';

const kCookieHelperCgi = 'resources/cookie_helper.py';

// Approximate async equivalent to the document.cookie getter but with
// important differences: optional additional getAll arguments are
// forwarded, and an empty cookie jar returns undefined.
//
// This is intended primarily for verification against expected cookie
// jar contents. It should produce more readable messages using
// assert_equals in failing cases than assert_object_equals would
// using parsed cookie jar contents and also allows expectations to be
// written more compactly.
async function getCookieString(...args) {
  const cookies = await cookieStore.getAll(...args);
  return cookies.length
    ? cookies.map(({name, value}) =>
                  (name ? (name + '=') : '') + value).join('; ')
    : undefined;
}

// Approximate async equivalent to the document.cookie getter but from
// the server's point of view. Returns UTF-8 interpretation. Allows
// sub-path to be specified.
//
// Unlike document.cookie, this returns undefined when no cookies are
// present.
async function getCookieStringHttp(extraPath = null) {
  const url =
        kCookieHelperCgi + ((extraPath == null) ? '' : ('/' + extraPath));
  const response = await fetch(url, { credentials: 'include' });
  const text = await response.text();
  assert_equals(
      response.ok,
      true,
      'CGI should have succeeded in getCookieStringHttp\n' + text);
  assert_equals(
      response.headers.get('content-type'),
      'text/plain; charset=utf-8',
      'CGI did not return UTF-8 text in getCookieStringHttp');
  if (text === '')
    return undefined;
  assert_equals(
      text.indexOf('cookie='),
      0,
      'CGI response did not begin with "cookie=" and was not empty: ' + text);
  return decodeURIComponent(text.replace(/^cookie=/, ''));
}

// Approximate async equivalent to the document.cookie getter but from
// the server's point of view. Returns binary string
// interpretation. Allows sub-path to be specified.
//
// Unlike document.cookie, this returns undefined when no cookies are
// present.
async function getCookieBinaryHttp(extraPath = null) {
  const url =
        kCookieHelperCgi +
        ((extraPath == null) ?
         '' :
         ('/' + extraPath)) + '?charset=iso-8859-1';
  const response = await fetch(url, { credentials: 'include' });
  const text = await response.text();
  assert_equals(
      response.ok,
      true,
      'CGI should have succeeded in getCookieBinaryHttp\n' + text);
  assert_equals(
      response.headers.get('content-type'),
      'text/plain; charset=iso-8859-1',
      'CGI did not return ISO 8859-1 text in getCookieBinaryHttp');
  if (text === '')
    return undefined;
  assert_equals(
      text.indexOf('cookie='),
      0,
      'CGI response did not begin with "cookie=" and was not empty: ' + text);
  return unescape(text.replace(/^cookie=/, ''));
}

// Approximate async equivalent to the document.cookie setter but from
// the server's point of view.
async function setCookieStringHttp(setCookie) {
  const encodedSetCookie = encodeURIComponent(setCookie);
  const url = kCookieHelperCgi;
  const headers = new Headers();
  headers.set(
      'content-type',
      'application/x-www-form-urlencoded; charset=utf-8');
  const response = await fetch(
      url,
      {
        credentials: 'include',
        method: 'POST',
        headers: headers,
        body: 'set-cookie=' + encodedSetCookie,
      });
  const text = await response.text();
  assert_equals(
      response.ok,
      true,
      'CGI should have succeeded in setCookieStringHttp set-cookie: ' +
        setCookie + '\n' + text);
  assert_equals(
      response.headers.get('content-type'),
      'text/plain; charset=utf-8',
      'CGI did not return UTF-8 text in setCookieStringHttp');
  assert_equals(
      text,
      'set-cookie=' + encodedSetCookie,
      'CGI did not faithfully echo the set-cookie value');
}

// Approximate async equivalent to the document.cookie setter but from
// the server's point of view. This version sets a binary cookie rather
// than a UTF-8 one.
async function setCookieBinaryHttp(setCookie) {
  const encodedSetCookie = escape(setCookie).split('/').join('%2F');
  const url = kCookieHelperCgi + '?charset=iso-8859-1';
  const headers = new Headers();
  headers.set(
      'content-type',
      'application/x-www-form-urlencoded; charset=iso-8859-1');
  const response = await fetch(url, {
    credentials: 'include',
    method: 'POST',
    headers: headers,
    body: 'set-cookie=' + encodedSetCookie
  });
  const text = await response.text();
  assert_equals(
      response.ok,
      true,
      'CGI should have succeeded in setCookieBinaryHttp set-cookie: ' +
        setCookie + '\n' + text);
  assert_equals(
      response.headers.get('content-type'),
      'text/plain; charset=iso-8859-1',
      'CGI did not return Latin-1 text in setCookieBinaryHttp');
  assert_equals(
      text,
      'set-cookie=' + encodedSetCookie,
      'CGI did not faithfully echo the set-cookie value');
}

// Async document.cookie getter; converts '' to undefined which loses
// information in the edge case where a single ''-valued anonymous
// cookie is visible.
async function getCookieStringDocument() {
  if (!kHasDocument)
    throw 'document.cookie not available in this context';
  return String(document.cookie || '') || undefined;
}

// Async document.cookie setter
async function setCookieStringDocument(setCookie) {
  if (!kHasDocument)
    throw 'document.cookie not available in this context';
  document.cookie = setCookie;
}

// Observe the next 'change' event on the cookieStore. Typical usage:
//
//   const eventPromise = observeNextCookieChangeEvent();
//   await /* something that modifies cookies */
//   await verifyCookieChangeEvent(
//     eventPromise, {changed: [{name: 'name', value: 'value'}]});
//
function observeNextCookieChangeEvent() {
  return new Promise(resolve => {
    cookieStore.addEventListener('change', e => resolve(e), {once: true});
  });
}

async function verifyCookieChangeEvent(eventPromise, expected, description) {
  description = description ? description + ': ' : '';
  expected = Object.assign({changed:[], deleted:[]}, expected);
  const event = await eventPromise;
  assert_equals(event.changed.length, expected.changed.length,
               description + 'number of changed cookies');
  for (let i = 0; i < event.changed.length; ++i) {
    assert_equals(event.changed[i].name, expected.changed[i].name,
                 description + 'changed cookie name');
    assert_equals(event.changed[i].value, expected.changed[i].value,
                 description + 'changed cookie value');
  }
  assert_equals(event.deleted.length, expected.deleted.length,
               description + 'number of deleted cookies');
  for (let i = 0; i < event.deleted.length; ++i) {
    assert_equals(event.deleted[i].name, expected.deleted[i].name,
                 description + 'deleted cookie name');
    assert_equals(event.deleted[i].value, expected.deleted[i].value,
                 description + 'deleted cookie value');
  }
}

// Helper function for promise_test with cookies; cookies
// named in these tests are cleared before/after the test
// body function is executed.
async function cookie_test(func, description) {

  // Wipe cookies used by tests before and after the test.
  async function deleteAllCookies() {
    (await cookieStore.getAll()).forEach(({name, value}) => {
      cookieStore.delete(name);
    });
  }

  return promise_test(async t => {
    await deleteAllCookies();
    try {
      return await func(t);
    } finally {
      await deleteAllCookies();
    }
  }, description);
}