diff --git a/lib/cookie.js b/lib/cookie.js index 559546b0..f6ba38fd 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -1166,7 +1166,10 @@ class CookieJar { // S5.3 step 5: public suffixes if (this.rejectPublicSuffixes && cookie.domain) { - const suffix = pubsuffix.getPublicSuffix(cookie.cdomain()); + const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), { + allowSpecialUseDomain: this.allowSpecialUseDomain, + ignoreError: options.ignoreError + }); if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { // e.g. "com" err = new Error("Cookie has domain set to a public suffix"); diff --git a/lib/permuteDomain.js b/lib/permuteDomain.js index 90923bce..35ab0228 100644 --- a/lib/permuteDomain.js +++ b/lib/permuteDomain.js @@ -34,32 +34,10 @@ const pubsuffix = require("./pubsuffix-psl"); // Gives the permutation of all possible domainMatch()es of a given domain. The // array is in shortest-to-longest order. Handy for indexing. -// RFC 6761 -const SPECIAL_USE_DOMAINS = [ - "local", - "example", - "invalid", - "localhost", - "test" -]; - function permuteDomain(domain, allowSpecialUseDomain) { - let pubSuf = null; - if (allowSpecialUseDomain) { - const domainParts = domain.split("."); - // If the right-most label in the name is a special-use domain (e.g. bananas.apple.localhost), - // then don't use PSL. This is because most special-use domains are not listed on PSL. - const topLevelDomain = domainParts[domainParts.length - 1]; - if (SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { - const secondLevelDomain = domainParts[domainParts.length - 2]; - // In aforementioned example, the eTLD/pubSuf will be apple.localhost - pubSuf = `${secondLevelDomain}.${topLevelDomain}`; - } else { - pubSuf = pubsuffix.getPublicSuffix(domain); - } - } else { - pubSuf = pubsuffix.getPublicSuffix(domain); - } + const pubSuf = pubsuffix.getPublicSuffix(domain, { + allowSpecialUseDomain: allowSpecialUseDomain + }); if (!pubSuf) { return null; diff --git a/lib/pubsuffix-psl.js b/lib/pubsuffix-psl.js index 93a8577c..d5555523 100644 --- a/lib/pubsuffix-psl.js +++ b/lib/pubsuffix-psl.js @@ -31,7 +31,39 @@ "use strict"; const psl = require("psl"); -function getPublicSuffix(domain) { +// RFC 6761 +const SPECIAL_USE_DOMAINS = [ + "local", + "example", + "invalid", + "localhost", + "test" +]; + +function getPublicSuffix(domain, options = {}) { + const domainParts = domain.split("."); + const topLevelDomain = domainParts[domainParts.length - 1]; + const allowSpecialUseDomain = !!options.allowSpecialUseDomain; + const ignoreError = !!options.ignoreError; + + if ( + allowSpecialUseDomain && + domainParts.length > 1 && + SPECIAL_USE_DOMAINS.includes(topLevelDomain) + ) { + // If the right-most label in the name is a special-use domain (e.g. bananas.apple.localhost), + // then don't use PSL. This is because most special-use domains are not listed on PSL. + const secondLevelDomain = domainParts[domainParts.length - 2]; + // In aforementioned example, the eTLD/pubSuf will be apple.localhost + return `${secondLevelDomain}.${topLevelDomain}`; + } + + if (!ignoreError && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { + throw new Error( + `Cookie has domain set to the public suffix "${topLevelDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.` + ); + } + return psl.get(domain); } diff --git a/test/api_test.js b/test/api_test.js index 461f4295..1144cf34 100644 --- a/test/api_test.js +++ b/test/api_test.js @@ -579,4 +579,86 @@ vows } } }) + .addBatch(allowSpecialUseOptionVows()) .export(module); + +function allowSpecialUseOptionVows() { + const specialUseDomains = [ + "local", + "example", + "invalid", + "localhost", + "test" + ]; + + return specialUseDomains.reduce((vows, specialUseDomain) => { + vows[ + `cookie jar with allowSpecialUseDomain enabled and domain is "${specialUseDomain}"` + ] = { + topic: function() { + const cb = this.callback; + const cj = new CookieJar(new tough.MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: true + }); + cj.setCookie( + `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}`, + at(-1), + (err, cookie) => { + cb(err, { cj: cj, cookie: cookie }); + } + ); + }, + "set the cookie": function(t) { + assert.ok(t.cookie, "didn't set?!"); + assert.equal(t.cookie.key, "settingThisShouldPass"); + }, + "then, retrieving": { + topic: function(t) { + const cb = this.callback; + setTimeout(() => { + t.cj.getCookies( + `http://dev.${specialUseDomain}`, + { http: true }, + (err, cookies) => { + t.cookies = cookies; + cb(err, t); + } + ); + }, 2000); + }, + "got the cookie": function(t) { + assert.lengthOf(t.cookies, 1); + assert.equal(t.cookies[0].key, "settingThisShouldPass"); + } + } + }; + + vows[ + `cookie jar with allowSpecialUseDomain disabled and domain is "${specialUseDomain}"` + ] = { + topic: function() { + const cj = new CookieJar(new tough.MemoryCookieStore(), { + allowSpecialUseDomain: false, + rejectPublicSuffixes: true + }); + cj.setCookie( + `settingThisShouldFail=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}`, + this.callback + ); + }, + errors: function(err, cookie) { + assert.ok(err); + assert.ok(!cookie); + assert.equal( + err.message, + `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.` + ); + } + }; + + return vows; + }, {}); +}