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, '')
+})

Reply via email to