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 }