From f07dce27c369c346fcbc1756e10a8edffb9de5c9 Mon Sep 17 00:00:00 2001 From: Mike de Libero Date: Fri, 2 Apr 2021 14:39:33 -0700 Subject: [PATCH 1/2] Fix for issue #153 --- CHANGELOG.md | 1 + lib/cookie.js | 20 +++++++++++++++++++- test/cookie_jar_test.js | 24 +++++++++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 284a8bc7..83cecb5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. ### Minor Changes - Added loose mode to the serialized options. Now a serialized cookie jar with loose mode enabled will honor that flag when deserialized. - Added allowSpecialUseDomain and prefixSecurity to the serialized options. Now any options accepted passed in to the cookie jar will be honored when serialized and deserialized. +- Added handling of IPv6 host names so that they would work with tough cookie. ## 4.0.0 diff --git a/lib/cookie.js b/lib/cookie.js index edf19bbe..fb7602e3 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -100,6 +100,20 @@ const PrefixSecurityEnum = Object.freeze({ // * support for IPv6 Scoped Literal ("%eth1") removed // * lowercase hexadecimal only const IP_REGEX_LOWERCASE =/(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/; +const IP_V6_REGEX = ` +\\[?(?: +(?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)| +(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)| +(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,2}|:)| +(?:[a-fA-F\\d]{1,4}:){4}(?:(?::[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,3}|:)| +(?:[a-fA-F\\d]{1,4}:){3}(?:(?::[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,4}|:)| +(?:[a-fA-F\\d]{1,4}:){2}(?:(?::[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,5}|:)| +(?:[a-fA-F\\d]{1,4}:){1}(?:(?::[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,6}|:)| +(?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)) +)(?:%[0-9a-zA-Z]{1,})?\\]? +`.replace(/\s*\/\/.*$/gm, '').replace(/\n/g, '').trim(); +const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`) + /* * Parses a Natural number (i.e., non-negative integer) with either the @@ -314,6 +328,10 @@ function canonicalDomain(str) { } str = str.trim().replace(/^\./, ""); // S4.1.2.3 & S5.2.3: ignore leading . + if (IP_V6_REGEX_OBJECT.test(str)) { + str = str.replace("[", "").replace("]", ""); + } + // convert to IDN if any non-ASCII characters if (punycode && /[^\u0001-\u007f]/.test(str)) { str = punycode.toASCII(str); @@ -1144,7 +1162,7 @@ class CookieJar { // S5.3 step 5: public suffixes if (this.rejectPublicSuffixes && cookie.domain) { const suffix = pubsuffix.getPublicSuffix(cookie.cdomain()); - if (suffix == null) { + if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { // e.g. "com" err = new Error("Cookie has domain set to a public suffix"); return cb(options.ignoreError ? null : err); diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index 02e2f8a0..c8084e97 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -193,7 +193,29 @@ vows assert.match(err.message, /HttpOnly/i); assert.ok(!c); } - } + }, + "Setting a basic IPv6 cookie": { + topic: function() { + const cj = new CookieJar(); + const c = Cookie.parse("a=b; Domain=[::1]; Path=/"); + assert.strictEqual(c.hostOnly, null); + assert.instanceOf(c.creation, Date); + assert.strictEqual(c.lastAccessed, null); + c.creation = new Date(Date.now() - 10000); + cj.setCookie(c, "http://[::1]/", this.callback); + }, + works: function(c) { + assert.instanceOf(c, Cookie); + }, // C is for Cookie, good enough for me + "gets timestamped": function(c) { + assert.ok(c.creation); + assert.ok(Date.now() - c.creation.getTime() < 5000); // recently stamped + assert.ok(c.lastAccessed); + assert.equal(c.creation, c.lastAccessed); + assert.equal(c.TTL(), Infinity); + assert.ok(!c.isPersistent()); + } + }, }) .addBatch({ "Store eight cookies": { From 66a0221dcc52fc8894dedd33358e86538259938c Mon Sep 17 00:00:00 2001 From: Mike de Libero Date: Wed, 5 May 2021 11:46:03 -0700 Subject: [PATCH 2/2] Added some more IPv6 tests --- test/cookie_jar_test.js | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index c8084e97..328a639b 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -216,6 +216,72 @@ vows assert.ok(!c.isPersistent()); } }, + "Setting a prefix IPv6 cookie": { + topic: function() { + const cj = new CookieJar(); + const c = Cookie.parse("a=b; Domain=[::ffff:127.0.0.1]; Path=/"); + assert.strictEqual(c.hostOnly, null); + assert.instanceOf(c.creation, Date); + assert.strictEqual(c.lastAccessed, null); + c.creation = new Date(Date.now() - 10000); + cj.setCookie(c, "http://[::ffff:127.0.0.1]/", this.callback); + }, + works: function(c) { + assert.instanceOf(c, Cookie); + }, // C is for Cookie, good enough for me + "gets timestamped": function(c) { + assert.ok(c.creation); + assert.ok(Date.now() - c.creation.getTime() < 5000); // recently stamped + assert.ok(c.lastAccessed); + assert.equal(c.creation, c.lastAccessed); + assert.equal(c.TTL(), Infinity); + assert.ok(!c.isPersistent()); + } + }, + "Setting a classic IPv6 cookie": { + topic: function() { + const cj = new CookieJar(); + const c = Cookie.parse("a=b; Domain=[2001:4860:4860::8888]; Path=/"); + assert.strictEqual(c.hostOnly, null); + assert.instanceOf(c.creation, Date); + assert.strictEqual(c.lastAccessed, null); + c.creation = new Date(Date.now() - 10000); + cj.setCookie(c, "http://[2001:4860:4860::8888]/", this.callback); + }, + works: function(c) { + assert.instanceOf(c, Cookie); + }, // C is for Cookie, good enough for me + "gets timestamped": function(c) { + assert.ok(c.creation); + assert.ok(Date.now() - c.creation.getTime() < 5000); // recently stamped + assert.ok(c.lastAccessed); + assert.equal(c.creation, c.lastAccessed); + assert.equal(c.TTL(), Infinity); + assert.ok(!c.isPersistent()); + } + }, + "Setting a short IPv6 cookie": { + topic: function() { + const cj = new CookieJar(); + const c = Cookie.parse("a=b; Domain=[2600::]; Path=/"); + assert.strictEqual(c.hostOnly, null); + assert.instanceOf(c.creation, Date); + assert.strictEqual(c.lastAccessed, null); + c.creation = new Date(Date.now() - 10000); + cj.setCookie(c, "http://[2600::]/", this.callback); + }, + works: function(c) { + assert.instanceOf(c, Cookie); + }, // C is for Cookie, good enough for me + "gets timestamped": function(c) { + assert.ok(c.creation); + assert.ok(Date.now() - c.creation.getTime() < 5000); // recently stamped + assert.ok(c.lastAccessed); + assert.equal(c.creation, c.lastAccessed); + assert.equal(c.TTL(), Infinity); + assert.ok(!c.isPersistent()); + } + }, }) .addBatch({ "Store eight cookies": {