This is an automated email from the ASF dual-hosted git repository. glynnbird pushed a commit to branch fetch in repository https://gitbox.apache.org/repos/asf/couchdb-nano.git
commit b91594c0c303caaab464240c7ff11f88e11bbbdd Author: Glynn Bird <[email protected]> AuthorDate: Wed Dec 14 11:55:26 2022 +0000 remove tough-cookie and its patch --- lib/cookie.js | 130 ++++++++++++++++ lib/cookiejar.js | 45 ------ lib/nano.js | 11 +- package-lock.json | 100 ------------ package.json | 1 - test/cookie.test.js | 433 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 569 insertions(+), 151 deletions(-) diff --git a/lib/cookie.js b/lib/cookie.js new file mode 100644 index 0000000..2d11c2e --- /dev/null +++ b/lib/cookie.js @@ -0,0 +1,130 @@ +const { URL } = require('url') + +// a simple cookie jar +class CookieJar { + // create new empty cookie jar + constructor () { + this.jar = [] + } + + // remove expired cookies + clean () { + const now = new Date().getTime() + for (let i = 0; i < this.jar.length; i++) { + const c = this.jar[i] + if (c.ts < now) { + this.jar.splice(i, 1) + i-- + } + } + } + + // add a cookie to the jar + add (cookie, url) { + // see if we have this cookie already + const oldCookieIndex = this.findByName(url, cookie.name) + + // if we do, update it + if (oldCookieIndex >= 0) { + // update existing cookie + this.jar[oldCookieIndex].value = cookie.value + this.jar[oldCookieIndex].expires = cookie.expires + this.jar[oldCookieIndex].ts = new Date(cookie.expires).getTime() + } else { + // otherwise, just add it + this.jar.push(cookie) + } + } + + // locate a cookie by name & url + findByName (url, name) { + this.clean() + const now = new Date().getTime() + const parsedURL = new URL(url) + for (let i = 0; i < this.jar.length; i++) { + const c = this.jar[i] + if (c.origin === parsedURL.origin && + c.name === name && + c.ts >= now) { + return i + } + } + return -1 + } + + // get a list of cookies to send for a supplied URL + getCookieString (url) { + let i + // clean up deceased cookies + this.clean() + + // find cookies that match the url + const now = new Date().getTime() + const parsedURL = new URL(url) + const retval = [] + for (i = 0; i < this.jar.length; i++) { + const c = this.jar[i] + // if match domain name, protocol and timestamp + if ((c.origin === parsedURL.origin || + (c.domain && parsedURL.hostname.endsWith(c.domain))) && + c.protocol === parsedURL.protocol && + c.ts >= now) { + // if cookie has httponly flag and this is not http, ignore + if (c.httponly && parsedURL.protocol !== 'http:') { + continue + } + + // if cookie has a path and it doesn't match incoming url, ignore + if (c.path && !parsedURL.pathname.startsWith(c.path)) { + continue + } + + // if cookie has a secure flag and the transport isn't secure, ignore + if (c.secure && parsedURL.protocol !== 'https:') { + continue + } + + // add to list of returned cookies + retval.push(c.value) + } + } + // if we've got cookies to return + if (retval.length > 0) { + // join them with semi-colons + return retval.join('; ') + } else { + // otherwise a blank string + return '' + } + } + + // parse a 'set-cookie' header of the form: + // AuthSession=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY; Version=1; Expires=Tue, 13-Dec-2022 13:54:19 GMT; Max-Age=60; Path=/; HttpOnly + parse (h, url) { + const parsedURL = new URL(url) + + // split components by ; and remove whitespace + const bits = h.split(';').map(s => s.trim()) + + // extract the cookie's value from the start of the string + const cookieValue = bits.shift() + + // start a cookie object + const cookie = { + name: cookieValue.split('=')[0], // the first part of the value + origin: parsedURL.origin, + pathname: parsedURL.pathname, + protocol: parsedURL.protocol + } + bits.forEach((e) => { + const lr = e.split('=') + cookie[lr[0].toLowerCase()] = lr[1] || true + }) + // calculate expiry timestamp + cookie.ts = new Date(cookie.expires).getTime() + cookie.value = cookieValue + this.add(cookie, url) + } +} + +module.exports = CookieJar diff --git a/lib/cookiejar.js b/lib/cookiejar.js deleted file mode 100644 index 270a669..0000000 --- a/lib/cookiejar.js +++ /dev/null @@ -1,45 +0,0 @@ -const tough = require('tough-cookie') -const cookieJar = new tough.CookieJar() - -// this is a monkey-patch of toughcookie's cookiejar, as it doesn't handle -// the refreshing of cookies from CouchDB properly -// see https://github.com/salesforce/tough-cookie/issues/154 -cookieJar.cloudantPatch = true -// Replace the store's updateCookie function with one that applies a patch to newCookie -const originalUpdateCookieFn = cookieJar.store.updateCookie -cookieJar.store.updateCookie = function (oldCookie, newCookie, cb) { - // Add current time as an update timestamp to the newCookie - newCookie.cloudantPatchUpdateTime = new Date() - // Replace the cookie's expiryTime function with one that uses cloudantPatchUpdateTime - // in place of creation time to check the expiry. - const originalExpiryTimeFn = newCookie.expiryTime - newCookie.expiryTime = function (now) { - // The original expiryTime check is relative to a time in this order: - // 1. supplied now argument - // 2. this.creation (original cookie creation time) - // 3. current time - // This patch replaces 2 with an expiry check relative to the cloudantPatchUpdateTime if set instead of - // the creation time by passing it as the now argument. - return originalExpiryTimeFn.call( - newCookie, - newCookie.cloudantPatchUpdateTime || now - ) - } - // Finally delegate back to the original update function or the fallback put (which is set by Cookie - // when an update function is not present on the store). Since we always set an update function for our - // patch we need to also provide that fallback. - if (originalUpdateCookieFn) { - originalUpdateCookieFn.call( - cookieJar.store, - oldCookie, - newCookie, - cb - ) - } else { - cookieJar.store.putCookie(newCookie, cb) - } -} -module.exports = { - tough, - cookieJar -} diff --git a/lib/nano.js b/lib/nano.js index 103b919..cc4f149 100644 --- a/lib/nano.js +++ b/lib/nano.js @@ -16,7 +16,7 @@ const stream = require('stream') const Readable = stream.Readable const undici = require('undici') const ChangesReader = require('./changesreader.js') -const { tough, cookieJar } = require('./cookiejar.js') +const CookieJar = require('./cookie.js') const MultiPartFactory = require('./multipart.js') const pkg = require('../package.json') @@ -83,6 +83,9 @@ module.exports = exports = function dbScope (cfg) { cfg.agent = new undici.Agent(cfg.agentOptions) } + // create cookieJar for this Nano + cfg.cookieJar = new CookieJar() + function maybeExtractDatabaseComponent () { if (!parseUrl) { return @@ -126,8 +129,7 @@ module.exports = exports = function dbScope (cfg) { if (response.headers) { const h = response.headers.get('set-cookie') if (h) { - const cookie = tough.Cookie.parse(h) - cookieJar.setCookieSync(cookie, req.url) + cfg.cookieJar.parse(h, req.url) } } @@ -155,7 +157,6 @@ module.exports = exports = function dbScope (cfg) { delete responseHeaders['content-length'] if (statusCode >= 200 && statusCode < 400) { - // collect response const ct = response.headers.get('content-type') let retval = '' @@ -420,7 +421,7 @@ module.exports = exports = function dbScope (cfg) { } // add any cookies for this domain - const cookie = cookieJar.getCookieStringSync(uri) + const cookie = cfg.cookieJar.getCookieString(uri) if (cookie) { fetchOptions.headers.cookie = cookie } diff --git a/package-lock.json b/package-lock.json index db8229c..0978f1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "11.0.0", "license": "Apache-2.0", "dependencies": { - "tough-cookie": "^4.1.2", "undici": "^5.14.0" }, "devDependencies": {}, @@ -28,29 +27,6 @@ "node": ">=10.16.0" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -59,20 +35,6 @@ "node": ">=10.0.0" } }, - "node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/undici": { "version": "5.14.0", "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz", @@ -83,23 +45,6 @@ "engines": { "node": ">=12.18" } - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } } }, "dependencies": { @@ -111,42 +56,11 @@ "streamsearch": "^1.1.0" } }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, "undici": { "version": "5.14.0", "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz", @@ -154,20 +68,6 @@ "requires": { "busboy": "^1.6.0" } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } } } } diff --git a/package.json b/package.json index 8f3fd8f..9013ae0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "database" ], "dependencies": { - "tough-cookie": "^4.1.2", "undici": "^5.14.0" }, "devDependencies": { diff --git a/test/cookie.test.js b/test/cookie.test.js new file mode 100644 index 0000000..b28368d --- /dev/null +++ b/test/cookie.test.js @@ -0,0 +1,433 @@ +// Licensed under the Apache License, Version 2.0 (the 'License'); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +const test = require('node:test') +const assert = require('node:assert/strict') + +const CookieJar = require('../lib/cookie.js') + +test('should parse cookies correctly', () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 60 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v = `${n}=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY` + const sc = `${v}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const url = 'http://mydomain.com/_session' + cj.parse(sc, url) + assert.equal(cj.jar.length, 1) + const cookie = { + name: 'AuthSession', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: v + } + assert.deepEqual(cj.jar[0], cookie) +}) + +test('should handle multiple cookies', () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 60 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v1 = 'YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY' + const v2 = 'YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY' + const v3 = 'YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY' + const sc1 = `${n}1=${v1}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const sc2 = `${n}2=${v2}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const sc3 = `${n}3=${v3}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const url = 'http://mydomain.com/_session' + cj.parse(sc1, url) + cj.parse(sc2, url) + cj.parse(sc3, url) + assert.equal(cj.jar.length, 3) + let cookie = { + name: 'AuthSession1', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: `AuthSession1=${v1}` + } + assert.deepEqual(cj.jar[0], cookie) + cookie = { + name: 'AuthSession2', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: `AuthSession2=${v2}` + } + assert.deepEqual(cj.jar[1], cookie) + cookie = { + name: 'AuthSession3', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: `AuthSession3=${v3}` + } + assert.deepEqual(cj.jar[2], cookie) +}) + +test('should handle multiple domains', () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 60 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v1 = 'gzQ0Y6TuB66MczYWRtaW46NjM5ODvkZ7axEJq6Fz0gOdhKY' + const v2 = 'YWRtaWzQ0Y6T46NjM5ODguB66MczvkZ7axEJq6Fz0gOdhKY' + const v3 = '46NjM5ODgYWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY' + const v4 = 'Y6TuB66MczvkZ7axY6TuBxzvkZ7ax46NjM5ODgYWRtaW46NjM5ODgzQ0EJq6Fz0gOdhKY' + const sc1 = `${n}1=${v1}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const sc2 = `${n}2=${v2}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const sc3 = `${n}3=${v3}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const sc4 = `${n}4=${v4}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + const url1 = 'http://mydomain1.com/_session' + const url2 = 'http://mydomain2.com/_session' + const url3 = 'http://mydomain3.com/_session' + cj.parse(sc1, url1) + cj.parse(sc2, url2) + cj.parse(sc3, url3) + cj.parse(sc4, url3) + assert.equal(cj.jar.length, 4) + let cookie = { + name: 'AuthSession1', + origin: 'http://mydomain1.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: `AuthSession1=${v1}` + } + assert.deepEqual(cj.jar[0], cookie) + assert.deepEqual(cj.getCookieString(url1), cookie.value) + cookie = { + name: 'AuthSession2', + origin: 'http://mydomain2.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: `AuthSession2=${v2}` + } + assert.deepEqual(cj.jar[1], cookie) + assert.deepEqual(cj.getCookieString(url2), cookie.value) + const cookie1 = { + name: 'AuthSession3', + origin: 'http://mydomain3.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: `AuthSession3=${v3}` + } + assert.deepEqual(cj.jar[2], cookie1) + const cookie2 = { + name: 'AuthSession4', + origin: 'http://mydomain3.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: `AuthSession4=${v4}` + } + assert.deepEqual(cj.jar[3], cookie2) + // multiple cookies - 2 cookies for url3 + assert.equal(cj.getCookieString(url3), `${cookie1.value}; ${cookie2.value}`) + + // check we don't get a cookie for a subdomain + assert.equal(cj.getCookieString('http://sub.mydomain3.com'), '') +}) + +const sleep = async (ms) => { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms) + }) +} + +test('should expire cookies correctly', async () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 5 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v = `${n}=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY` + const sc = `${v}; Version=1; Expires=${expiryStr}; Max-Age=5; Path=/; HttpOnly` + const url = 'http://mydomain.com/_session' + cj.parse(sc, url) + assert.equal(cj.jar.length, 1) + assert.notEqual(cj.getCookieString(url).length, 0) + await sleep(5000) + assert.equal(cj.getCookieString(url).length, 0) + assert.equal(cj.getCookieString(url).length, 0) +}) + +test('should respect path', () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 60 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v1 = `${n}1=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY` + const sc1 = `${v1}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/my/path; HttpOnly` + const v2 = `${n}2=YczvkZ7axEJq6Fz0gOdhKYWRtaW46NjM5ODgzQ0Y6TuB66M` + const sc2 = `${v2}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly` + + const url = 'http://mydomain.com/_session' + cj.parse(sc1, url) + cj.parse(sc2, url) + assert.equal(cj.jar.length, 2) + const cookie1 = { + name: 'AuthSession1', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/my/path', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: v1 + } + assert.deepEqual(cj.jar[0], cookie1) + const cookie2 = { + name: 'AuthSession2', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr).getTime(), + value: v2 + } + assert.deepEqual(cj.jar[1], cookie2) + + // one cookies for path=/ + let cs = cj.getCookieString('http://mydomain.com/') + assert.equal(cs, `${cookie2.value}`) + // two cookies for path=/my/path + cs = cj.getCookieString('http://mydomain.com/my/path') + assert.equal(cs, `${cookie1.value}; ${cookie2.value}`) + // two cookies for path=/my/path/extra + cs = cj.getCookieString('http://mydomain.com/my/path/extra') + assert.equal(cs, `${cookie1.value}; ${cookie2.value}`) + // zero cookies for different domain + cs = cj.getCookieString('http://myotherdomain.com/my/path/extra') + assert.equal(cs, '') +}) + +test('should renew cookies', () => { + const cj = new CookieJar() + const n = 'AuthSession' + const expiry1 = new Date().getTime() + 1000 * 60 + const expiryStr1 = new Date(expiry1).toGMTString() + + const v1 = `${n}=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY` + const sc1 = `${v1}; Version=1; Expires=${expiryStr1}; Max-Age=60; Path=/; HttpOnly` + + const expiry2 = new Date().getTime() + 1000 * 120 + const expiryStr2 = new Date(expiry2).toGMTString() + const v2 = `${n}=gOdhKYWRtaW46NjM5ODgzQ0Y6TuB66MYczvkZ7axEJq6Fz0` + const sc2 = `${v2}; Version=1; Expires=${expiryStr2}; Max-Age=60; Path=/; HttpOnly` + + const url = 'http://mydomain.com/_session' + + // parse first cookie string + cj.parse(sc1, url) + assert.equal(cj.jar.length, 1) + const cookie1 = { + name: 'AuthSession', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr1, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr1).getTime(), + value: v1 + } + assert.deepEqual(cj.jar[0], cookie1) + + // then refresh the cookie + cj.parse(sc2, url) + const cookie2 = { + name: 'AuthSession', + origin: 'http://mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr2, + 'max-age': '60', + path: '/', + httponly: true, + ts: new Date(expiryStr2).getTime(), + value: v2 + } + + // ensure it updates the same cookie + assert.equal(cj.jar.length, 1) + assert.deepEqual(cj.jar[0], cookie2) +}) + +test('should send cookies to authorised subdomains', () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 60 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v = `${n}=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY` + const sc = `${v}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly; Domain=.mydomain.com` + const url = 'http://test.mydomain.com/_session' + cj.parse(sc, url) + assert.equal(cj.jar.length, 1) + const cookie = { + name: 'AuthSession', + origin: 'http://test.mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + domain: '.mydomain.com', + ts: new Date(expiryStr).getTime(), + value: v + } + assert.deepEqual(cj.jar[0], cookie) + + // check we get a cookie for the same domain + let cs = cj.getCookieString('http://test.mydomain.com/my/path/extra') + assert.equal(cs, `${cookie.value}`) + + // check we get a cookie for the different domain + cs = cj.getCookieString('http://different.mydomain.com/my/path/extra') + assert.equal(cs, `${cookie.value}`) + cs = cj.getCookieString('http://sub.different.mydomain.com/my/path/extra') + assert.equal(cs, `${cookie.value}`) + + // check we get no cookies for the different domain + cs = cj.getCookieString('http://mydomain1.com/my/path/extra') + assert.equal(cs, '') +}) + +test('should not send http-only cookies to https', () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 60 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v = `${n}=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY` + const sc = `${v}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; HttpOnly; Domain=.mydomain.com` + const url = 'http://test.mydomain.com/_session' + cj.parse(sc, url) + assert.equal(cj.jar.length, 1) + const cookie = { + name: 'AuthSession', + origin: 'http://test.mydomain.com', + pathname: '/_session', + protocol: 'http:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + httponly: true, + domain: '.mydomain.com', + ts: new Date(expiryStr).getTime(), + value: v + } + assert.deepEqual(cj.jar[0], cookie) + + // check we get a cookie for the same domain (http) + let cs = cj.getCookieString('http://test.mydomain.com/my/path/extra') + assert.equal(cs, `${cookie.value}`) + + // but not https + cs = cj.getCookieString('https://test.mydomain.com/my/path/extra') + assert.equal(cs, '') +}) + +test('should not send secure-only cookies to http', () => { + const cj = new CookieJar() + const expiry = new Date().getTime() + 1000 * 60 + const expiryStr = new Date(expiry).toGMTString() + const n = 'AuthSession' + const v = `${n}=YWRtaW46NjM5ODgzQ0Y6TuB66MczvkZ7axEJq6Fz0gOdhKY` + const sc = `${v}; Version=1; Expires=${expiryStr}; Max-Age=60; Path=/; Secure; Domain=.mydomain.com` + const url = 'https://test.mydomain.com/_session' + cj.parse(sc, url) + assert.equal(cj.jar.length, 1) + const cookie = { + name: 'AuthSession', + origin: 'https://test.mydomain.com', + pathname: '/_session', + protocol: 'https:', + version: '1', + expires: expiryStr, + 'max-age': '60', + path: '/', + secure: true, + domain: '.mydomain.com', + ts: new Date(expiryStr).getTime(), + value: v + } + assert.deepEqual(cj.jar[0], cookie) + + // check we get a cookie for the same domain (http) + let cs = cj.getCookieString('https://test.mydomain.com/my/path/extra') + assert.equal(cs, `${cookie.value}`) + + // but not http + cs = cj.getCookieString('http://test.mydomain.com/my/path/extra') + assert.equal(cs, '') +})
