From 626dfde31f4603f606c43762f8c1cfe5fac3ad98 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 22 Nov 2021 23:45:10 -0400 Subject: [PATCH 1/8] Support for React Native This PR fixes #222 by: - introducing a fallback for the `util.*` code paths used as indicated in [this comment](https://github.com/salesforce/tough-cookie/issues/222#issue-1033380775) - adding a dependency on `url-parse` to act as a polyfill for the usage of `require('url').parse` --- lib/cookie.js | 13 ++++---- lib/memstore.js | 4 ++- lib/node-util.js | 20 ++++++++++++ package.json | 3 +- test/jsc_compatibility_test.js | 58 ++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 lib/node-util.js create mode 100644 test/jsc_compatibility_test.js diff --git a/lib/cookie.js b/lib/cookie.js index 559546b0..94876910 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -30,8 +30,8 @@ */ "use strict"; const punycode = require("punycode"); -const urlParse = require("url").parse; -const util = require("util"); +const urlParse = require("url-parse"); +const nodeUtil = require("./node-util"); const pubsuffix = require("./pubsuffix-psl"); const Store = require("./store").Store; const MemoryCookieStore = require("./memstore").MemoryCookieStore; @@ -810,6 +810,7 @@ const cookieDefaults = { class Cookie { constructor(options = {}) { + const util = nodeUtil(); if (util.inspect.custom) { this[util.inspect.custom] = this.inspect; } @@ -1110,7 +1111,7 @@ class CookieJar { } setCookie(cookie, url, options, cb) { - validators.validate(validators.isNonEmptyString(url), cb, options); + validators.validate(validators.isNonEmptyString(url), cb, options); let err; if (validators.isFunction(url)) { @@ -1646,7 +1647,7 @@ class CookieJar { serialized = strOrObj; } - const jar = new CookieJar(store, { + const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, looseMode: serialized.enableLooseMode, allowSpecialUseDomain: serialized.allowSpecialUseDomain, @@ -1663,9 +1664,9 @@ class CookieJar { static deserializeSync(strOrObj, store) { const serialized = typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj; - const jar = new CookieJar(store, { + const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, - looseMode: serialized.enableLooseMode + looseMode: serialized.enableLooseMode }); // catch this mistake early: diff --git a/lib/memstore.js b/lib/memstore.js index 912eead3..051e1ff8 100644 --- a/lib/memstore.js +++ b/lib/memstore.js @@ -33,19 +33,21 @@ const { fromCallback } = require("universalify"); const Store = require("./store").Store; const permuteDomain = require("./permuteDomain").permuteDomain; const pathMatch = require("./pathMatch").pathMatch; -const util = require("util"); +const nodeUtil = require("./node-util"); class MemoryCookieStore extends Store { constructor() { super(); this.synchronous = true; this.idx = {}; + const util = nodeUtil(); if (util.inspect.custom) { this[util.inspect.custom] = this.inspect; } } inspect() { + const util = nodeUtil(); return `{ idx: ${util.inspect(this.idx, false, 2)} }`; } diff --git a/lib/node-util.js b/lib/node-util.js new file mode 100644 index 00000000..16e7743d --- /dev/null +++ b/lib/node-util.js @@ -0,0 +1,20 @@ +// a stand-in for util that does nothing but prevent the existing +// node:util code paths from exploding +function nullUtilInspect() { + return ""; +} +nullUtilInspect.custom = Symbol.for("nodejs.util.inspect.custom"); +const nullUtil = { inspect: nullUtilInspect }; + +module.exports = function nodeUtil() { + try { + const jscCompatibility = + process && process.env && process.env.JSC_COMPATIBILITY === "enabled"; + if (!jscCompatibility) { + return require("util"); + } + } catch (e) { + // ignore + } + return nullUtil; +}; diff --git a/package.json b/package.json index e07dcb7c..3006a819 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.1.2", + "url-parse": "^1.5.3" } } diff --git a/test/jsc_compatibility_test.js b/test/jsc_compatibility_test.js new file mode 100644 index 00000000..663bee7b --- /dev/null +++ b/test/jsc_compatibility_test.js @@ -0,0 +1,58 @@ +/*! + * Copyright (c) 2015, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +"use strict"; +const vows = require("vows"); +const assert = require("assert"); +const tough = require("../lib/cookie"); +const util = require("util"); +const nodeUtil = require("../lib/node-util"); +const Cookie = tough.Cookie; + +vows + .describe("JavaScriptCore Compatibility") + .addBatch({ + util: { + topic: function() { + process.env.JSC_COMPATIBILITY = "enabled"; + return new Cookie(); + }, + "should not error out on util.* code paths": function(c) { + const nullUtil = nodeUtil(); + assert.equal(nullUtil.inspect(c), ""); + assert.equal(nullUtil.inspect.custom, util.inspect.custom); + }, + teardown: function() { + delete process.env.JSC_COMPATIBILITY; + } + } + }) + .export(module); From 9f2f385eb7c920183857cefc69aecc77a6e938f6 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Tue, 23 Nov 2021 23:19:29 -0400 Subject: [PATCH 2/8] Support for React Native This PR fixes #222 by: - introducing a fallback for the `util.*` code paths used as indicated in [this comment](https://github.com/salesforce/tough-cookie/issues/222#issue-1033380775) - adding a dependency on `url-parse` to act as a polyfill for the usage of `require('url').parse` - configuring eslint to error on `no-restricted-modules` which will not be present in native environments --- .eslintrc.json | 14 +++- lib/cookie.js | 21 +++-- lib/node-util.js | 6 +- lib/permuteDomain.js | 2 +- lib/validators.js | 28 +++---- package.json | 2 +- test/.eslintrc.json | 5 ++ test/cookie_jar_test.js | 33 ++++---- test/domain_and_path_test.js | 20 ++--- ...ity_test.js => node_util_fallback_test.js} | 4 +- test/parsing_test.js | 18 ++--- test/regression_test.js | 80 +++++++++++-------- 12 files changed, 137 insertions(+), 96 deletions(-) create mode 100644 test/.eslintrc.json rename test/{jsc_compatibility_test.js => node_util_fallback_test.js} (95%) diff --git a/.eslintrc.json b/.eslintrc.json index 7dcba34d..76a4200c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,5 @@ { + "extends": ["plugin:prettier/recommended"], "parserOptions": { "ecmaVersion": 6 }, @@ -7,7 +8,14 @@ "no-var": "error", "prefer-arrow-callback": "error", "prefer-const": "error", - "prefer-template": "error" - }, - "extends": ["plugin:prettier/recommended"] + "prefer-template": "error", + "no-restricted-modules": ["error", + // we can't rely on node standard modules in "native" or "browser" environments + // - exceptions: + // "punycode": since it's a package.json dependency + "assert", "buffer", "child_process", "cluster", "crypto", "dgram", "dns", "domain", "events", "freelist", "fs", + "http", "https", "module", "net", "os", "path", "querystring", "readline", "repl", "smalloc", "stream", + "string_decoder", "sys", "timers", "tls", "tracing", "tty", "url", "util", "vm", "zlib" + ] + } } diff --git a/lib/cookie.js b/lib/cookie.js index 94876910..09bf5490 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -99,7 +99,7 @@ const PrefixSecurityEnum = Object.freeze({ // * all capturing groups converted to non-capturing -- "(?:)" // * 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_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}|:)| @@ -111,9 +111,11 @@ const IP_V6_REGEX = ` (?:[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}$`) - +` + .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 @@ -380,7 +382,7 @@ function domainMatch(str, domStr, canonicalize) { /* " * The last character of the string that is not included in the * domain string is a %x2E (".") character." */ - if (str.substr(idx-1,1) !== '.') { + if (str.substr(idx - 1, 1) !== ".") { return false; // doesn't align on "." } @@ -479,7 +481,7 @@ function parse(str, options) { } if (validators.isEmptyString(str) || !validators.isString(str)) { - return null + return null; } str = str.trim(); @@ -1127,7 +1129,12 @@ class CookieJar { validators.validate(validators.isFunction(cb), cb); - if(!validators.isNonEmptyString(cookie) && !validators.isObject(cookie) && ( cookie instanceof String && cookie.length == 0)) { + if ( + !validators.isNonEmptyString(cookie) && + !validators.isObject(cookie) && + cookie instanceof String && + cookie.length == 0 + ) { return cb(null); } diff --git a/lib/node-util.js b/lib/node-util.js index 16e7743d..ecca4581 100644 --- a/lib/node-util.js +++ b/lib/node-util.js @@ -9,12 +9,14 @@ const nullUtil = { inspect: nullUtilInspect }; module.exports = function nodeUtil() { try { const jscCompatibility = - process && process.env && process.env.JSC_COMPATIBILITY === "enabled"; + process && process.env && process.env.NODE_UTIL_FALLBACK === "enabled"; if (!jscCompatibility) { + // we want the real "util" module if we are in a node environment + // eslint-disable-next-line no-restricted-modules return require("util"); } } catch (e) { - // ignore + // ignored } return nullUtil; }; diff --git a/lib/permuteDomain.js b/lib/permuteDomain.js index 90923bce..e9172c68 100644 --- a/lib/permuteDomain.js +++ b/lib/permuteDomain.js @@ -70,7 +70,7 @@ function permuteDomain(domain, allowSpecialUseDomain) { // Nuke trailing dot if (domain.slice(-1) == ".") { - domain = domain.slice(0, -1) + domain = domain.slice(0, -1); } const prefix = domain.slice(0, -(pubSuf.length + 1)); // ".example.com" diff --git a/lib/validators.js b/lib/validators.js index d2f39deb..85581641 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -1,21 +1,21 @@ /* ************************************************************************************ Extracted from check-types.js https://gitlab.com/philbooth/check-types.js - + MIT License - + Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Phil Booth - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,17 +23,17 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + ************************************************************************************ */ "use strict"; /* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */ function isFunction(data) { - return typeof data === 'function'; + return typeof data === "function"; } function isNonEmptyString(data) { - return isString(data) && data !== ''; + return isString(data) && data !== ""; } function isDate(data) { @@ -41,15 +41,15 @@ function isDate(data) { } function isEmptyString(data) { - return data === '' || (data instanceof String && data.toString() === ''); + return data === "" || (data instanceof String && data.toString() === ""); } function isString(data) { - return typeof data === 'string' || data instanceof String + return typeof data === "string" || data instanceof String; } function isObject(data) { - return toString.call(data) === '[object Object]'; + return toString.call(data) === "[object Object]"; } function isInstanceStrict(data, prototype) { try { @@ -60,7 +60,7 @@ function isInstanceStrict(data, prototype) { } function isInteger(data) { - return typeof data === 'number' && data % 1 === 0; + return typeof data === "number" && data % 1 === 0; } /* End validation functions */ @@ -83,7 +83,7 @@ class ParameterError extends Error { constructor(...params) { super(...params); } -}; +} exports.ParameterError = ParameterError; exports.isFunction = isFunction; @@ -92,4 +92,4 @@ exports.isDate = isDate; exports.isEmptyString = isEmptyString; exports.isString = isString; exports.isObject = isObject; -exports.validate = validate; \ No newline at end of file +exports.validate = validate; diff --git a/package.json b/package.json index 3006a819..c3f7ae19 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ ], "scripts": { "version": "genversion lib/version.js && git add lib/version.js", - "test": "vows test/*_test.js", + "test": "vows test/*_test.js && npm run eslint", "cover": "nyc --reporter=lcov --reporter=html vows test/*_test.js", "eslint": "eslint --env node --ext .js .", "prettier": "prettier '**/*.{json,ts,yaml,md}'", diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 00000000..e4149bbb --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-restricted-modules": "off" + } +} diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index 328a639b..676e50db 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -281,7 +281,7 @@ vows assert.equal(c.TTL(), Infinity); assert.ok(!c.isPersistent()); } - }, + } }) .addBatch({ "Store eight cookies": { @@ -750,18 +750,21 @@ vows "of undefined": { topic: function() { const jar = new tough.CookieJar(); - const cookieString = `AWSELB=69b2c0038b16e8e27056d1178e0d556c; - Path=/, jses_WS41=5f8dc2f6-ea37-49de-8dfa-b58336c2d9ce; path=/; + const cookieString = `AWSELB=69b2c0038b16e8e27056d1178e0d556c; + Path=/, jses_WS41=5f8dc2f6-ea37-49de-8dfa-b58336c2d9ce; path=/; Secure; HttpOnly, AuthToken=EFKFFFCH@K@GHIHEJCJMMGJM>CDHDEK>CFGK?MHJ >>JI@B??@CAEHBJH@H@A@GCFDLIMLJEEJEIFGALA?BIM?@G@DEDI@JE?I?HKJBIDDHJMEFEFM - >G@J?I??B@C>>LAH?GCGJ@FMEGHBGAF; expires=Sun, 31-Jan-9021 02:39:04 GMT; - path=/; Secure; HttpOnly, FirstReferrer=; expires=Fri, 31-Jan-9020 20:50:44 + >G@J?I??B@C>>LAH?GCGJ@FMEGHBGAF; expires=Sun, 31-Jan-9021 02:39:04 GMT; + path=/; Secure; HttpOnly, FirstReferrer=; expires=Fri, 31-Jan-9020 20:50:44 GMT; path=/`; jar.setCookieSync(cookieString, "https://google.com"); - jar.getCookies("https://google.com", this.callback) + jar.getCookies("https://google.com", this.callback); }, - "results in a 1-length array with a valid cookie": function(err, cookies) { + "results in a 1-length array with a valid cookie": function( + err, + cookies + ) { assert(!err, err); assert(cookies.length == 1); assert.instanceOf(cookies[0], Cookie); @@ -780,7 +783,10 @@ vows this.callback ); }, - "results in a error being returned because of missing parameters": function(err, cookies) { + "results in a error being returned because of missing parameters": function( + err, + cookies + ) { assert(err != null); assert(err instanceof tough.ParameterError); } @@ -792,13 +798,12 @@ vows "with missing parameters": { topic: function() { const jar = new tough.CookieJar(); - jar.setCookie( - '', - "https://google.com", - this.callback - ); + jar.setCookie("", "https://google.com", this.callback); }, - "results in a error being returned because of missing parameters": function(err, cookies) { + "results in a error being returned because of missing parameters": function( + err, + cookies + ) { assert(cookies == undefined); } } diff --git a/test/domain_and_path_test.js b/test/domain_and_path_test.js index de15f317..4291c5be 100644 --- a/test/domain_and_path_test.js +++ b/test/domain_and_path_test.js @@ -50,15 +50,15 @@ function matchVows(func, table) { } function transformVows(fn, table) { - var theVows = {}; - table.forEach(function (item) { - var str = item[0]; - var expect = item[1]; - var label = str + " gives " + expect; + const theVows = {}; + table.forEach(item => { + const str = item[0]; + const expect = item[1]; + let label = `${str} gives ${expect}`; if (item.length >= 3) { - label += " (" + item[2] + ")"; + label += ` (${item[2]})`; } - theVows[label] = function () { + theVows[label] = function() { assert.equal(fn(str), expect); }; }); @@ -75,7 +75,7 @@ vows ["EXAMPLE.com.", "example.com.", "trailing dot"], [".EXAMPLE.com.", "example.com.", "leading and trailing dot"], [".EXAMPLE...com.", "example...com.", "internal dots"], - ["δοκιμή.δοκιμή","xn--jxalpdlp.xn--jxalpdlp", "IDN: test.test in greek"], + ["δοκιμή.δοκιμή", "xn--jxalpdlp.xn--jxalpdlp", "IDN: test.test in greek"] ]) }) .addBatch({ @@ -142,12 +142,12 @@ vows // exact length "TLD" tests: ["com", "net", false], // same len, non-match ["com", "com", true], // "are identical" rule - ["NOTATLD", "notaTLD", true], // "are identical" rule (after canonicalization) + ["NOTATLD", "notaTLD", true] // "are identical" rule (after canonicalization) ]) }) .addBatch({ - "default-path": transformVows(tough.defaultPath,[ + "default-path": transformVows(tough.defaultPath, [ [null, "/"], ["/", "/"], ["/file", "/"], diff --git a/test/jsc_compatibility_test.js b/test/node_util_fallback_test.js similarity index 95% rename from test/jsc_compatibility_test.js rename to test/node_util_fallback_test.js index 663bee7b..6a493e5e 100644 --- a/test/jsc_compatibility_test.js +++ b/test/node_util_fallback_test.js @@ -42,7 +42,7 @@ vows .addBatch({ util: { topic: function() { - process.env.JSC_COMPATIBILITY = "enabled"; + process.env.NODE_UTIL_FALLBACK = "enabled"; return new Cookie(); }, "should not error out on util.* code paths": function(c) { @@ -51,7 +51,7 @@ vows assert.equal(nullUtil.inspect.custom, util.inspect.custom); }, teardown: function() { - delete process.env.JSC_COMPATIBILITY; + delete process.env.NODE_UTIL_FALLBACK; } } }) diff --git a/test/parsing_test.js b/test/parsing_test.js index b26d4cfb..5a9e944c 100644 --- a/test/parsing_test.js +++ b/test/parsing_test.js @@ -693,35 +693,35 @@ vows } }, "empty string": { - topic: function () { - return Cookie.parse(''); + topic: function() { + return Cookie.parse(""); }, - "is empty": function (c) { + "is empty": function(c) { assert.isNull(c); } }, "missing string": { - topic: function () { + topic: function() { return Cookie.parse(); }, - "is empty": function (c) { + "is empty": function(c) { assert.isNull(c); } }, "some string object": { topic: function() { - return Cookie.parse(new String('')) + return Cookie.parse(new String("")); }, "is empty": function(c) { - assert.isNull(c,null) + assert.isNull(c, null); } }, "some empty string object": { topic: function() { - return Cookie.parse(new String()) + return Cookie.parse(new String()); }, "is empty": function(c) { - assert.isNull(c,null) + assert.isNull(c, null); } } }) diff --git a/test/regression_test.js b/test/regression_test.js index 450de5f0..6a110382 100644 --- a/test/regression_test.js +++ b/test/regression_test.js @@ -188,47 +188,61 @@ vows } } }) - .addBatch({ - "setCookie with localhost (GH-215)": { - topic: function() { - const cookieJar = new CookieJar(); - return cookieJar.setCookieSync("a=b; Domain=localhost", "http://localhost") // when domain set to 'localhost', will throw 'Error: Cookie has domain set to a public suffix' - }, - works: function(err, c) { - // localhost as domain throws an error, cookie should not be defined - assert.instanceOf(err, Error) - assert.isUndefined(c) + .addBatch( + { + "setCookie with localhost (GH-215)": { + topic: function() { + const cookieJar = new CookieJar(); + return cookieJar.setCookieSync( + "a=b; Domain=localhost", + "http://localhost" + ); // when domain set to 'localhost', will throw 'Error: Cookie has domain set to a public suffix' + }, + works: function(err, c) { + // localhost as domain throws an error, cookie should not be defined + assert.instanceOf(err, Error); + assert.isUndefined(c); + } } - }}, + }, { - "setCookie with localhost (GH-215) (null domain)": { - topic: function() { - const cookieJar = new CookieJar(); - return cookieJar.setCookieSync("a=b; Domain=", "http://localhost") // when domain set to 'localhost', will throw 'Error: Cookie has domain set to a public suffix' - }, - works: function(c) { - assert.instanceOf(c, Cookie) + "setCookie with localhost (GH-215) (null domain)": { + topic: function() { + const cookieJar = new CookieJar(); + return cookieJar.setCookieSync("a=b; Domain=", "http://localhost"); // when domain set to 'localhost', will throw 'Error: Cookie has domain set to a public suffix' + }, + works: function(c) { + assert.instanceOf(c, Cookie); + } } - }}, - { + }, + { "setCookie with localhost (localhost.local domain) (GH-215)": { topic: function() { - const cookieJar = new CookieJar(); - return cookieJar.setCookieSync("a=b; Domain=localhost.local", "http://localhost") - }, - works: function(c) { - assert.instanceOf(c, Cookie) + const cookieJar = new CookieJar(); + return cookieJar.setCookieSync( + "a=b; Domain=localhost.local", + "http://localhost" + ); + }, + works: function(c) { + assert.instanceOf(c, Cookie); + } } - }}, - { + }, + { "setCookie with localhost (.localhost domain), (GH-215)": { topic: function() { - const cookieJar = new CookieJar(); - return cookieJar.setCookieSync("a=b; Domain=.localhost", "http://localhost") - }, - works: function(c) { - assert.instanceOf(c, Cookie) + const cookieJar = new CookieJar(); + return cookieJar.setCookieSync( + "a=b; Domain=.localhost", + "http://localhost" + ); + }, + works: function(c) { + assert.instanceOf(c, Cookie); + } } } - }) + ) .export(module); From bd71e5fe0d7d710983ad953906cc5623171b769e Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Wed, 24 Nov 2021 22:13:08 -0400 Subject: [PATCH 3/8] Update test/node_util_fallback_test.js Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> --- test/node_util_fallback_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index 6a493e5e..4e9f8f1d 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2015, Salesforce.com, Inc. + * Copyright (c) 2021, Salesforce.com, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without From 8275fd016a77128a988433b00719d5aea17b1c0c Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 25 Nov 2021 16:05:51 -0400 Subject: [PATCH 4/8] Support for React Native Implementation for the fallback inspect function so that it produces equivalent output to the original inspect behavior. --- lib/cookie.js | 2 +- lib/memstore.js | 2 +- lib/node-util.js | 22 ------ lib/nodeUtil.js | 99 +++++++++++++++++++++++++ test/node_util_fallback_test.js | 123 ++++++++++++++++++++++++++++---- 5 files changed, 211 insertions(+), 37 deletions(-) delete mode 100644 lib/node-util.js create mode 100644 lib/nodeUtil.js diff --git a/lib/cookie.js b/lib/cookie.js index 09bf5490..1888947d 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -31,7 +31,7 @@ "use strict"; const punycode = require("punycode"); const urlParse = require("url-parse"); -const nodeUtil = require("./node-util"); +const nodeUtil = require("./nodeUtil"); const pubsuffix = require("./pubsuffix-psl"); const Store = require("./store").Store; const MemoryCookieStore = require("./memstore").MemoryCookieStore; diff --git a/lib/memstore.js b/lib/memstore.js index 051e1ff8..4a9804c6 100644 --- a/lib/memstore.js +++ b/lib/memstore.js @@ -33,7 +33,7 @@ const { fromCallback } = require("universalify"); const Store = require("./store").Store; const permuteDomain = require("./permuteDomain").permuteDomain; const pathMatch = require("./pathMatch").pathMatch; -const nodeUtil = require("./node-util"); +const nodeUtil = require("./nodeUtil"); class MemoryCookieStore extends Store { constructor() { diff --git a/lib/node-util.js b/lib/node-util.js deleted file mode 100644 index ecca4581..00000000 --- a/lib/node-util.js +++ /dev/null @@ -1,22 +0,0 @@ -// a stand-in for util that does nothing but prevent the existing -// node:util code paths from exploding -function nullUtilInspect() { - return ""; -} -nullUtilInspect.custom = Symbol.for("nodejs.util.inspect.custom"); -const nullUtil = { inspect: nullUtilInspect }; - -module.exports = function nodeUtil() { - try { - const jscCompatibility = - process && process.env && process.env.NODE_UTIL_FALLBACK === "enabled"; - if (!jscCompatibility) { - // we want the real "util" module if we are in a node environment - // eslint-disable-next-line no-restricted-modules - return require("util"); - } - } catch (e) { - // ignored - } - return nullUtil; -}; diff --git a/lib/nodeUtil.js b/lib/nodeUtil.js new file mode 100644 index 00000000..5c47271a --- /dev/null +++ b/lib/nodeUtil.js @@ -0,0 +1,99 @@ +/*! + * Copyright (c) 2021, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); + +function inspectFallback(val) { + const domains = Object.keys(val); + if (domains.length === 0) { + return "{}"; + } + let result = "{\n"; + Object.keys(val).forEach((domain, i) => { + result += formatDomain(domain, val[domain]); + if (i < domains.length - 1) { + result += ","; + } + result += "\n"; + }); + result += "}"; + return result; +} + +inspectFallback.custom = customInspectSymbol; + +function formatDomain(domainName, domainValue) { + const indent = " "; + let result = `${indent}'${domainName}': {\n`; + Object.keys(domainValue).forEach((path, i, paths) => { + result += formatPath(path, domainValue[path]); + if (i < paths.length - 1) { + result += ","; + } + result += "\n"; + }); + result += `${indent}}`; + return result; +} + +function formatPath(pathName, pathValue) { + const indent = " "; + let result = `${indent}'${pathName}': {\n`; + Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { + const cookie = pathValue[cookieName]; + result += ` ${cookieName}: ${cookie[customInspectSymbol]()}`; + if (i < cookieNames.length - 1) { + result += ","; + } + result += "\n"; + }); + result += `${indent}}`; + return result; +} + +const nodeUtilFallback = { + inspect: inspectFallback +}; + +module.exports = function nodeUtil() { + try { + const nodeUtilFallbackEnabled = + process && process.env && process.env.NODE_UTIL_FALLBACK === "enabled"; + if (!nodeUtilFallbackEnabled) { + // we want the real "util" module if we are in a node environment + // eslint-disable-next-line no-restricted-modules + return require("util"); + } + } catch (e) { + // ignored + } + return nodeUtilFallback; +}; diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index 4e9f8f1d..978ff7c3 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -34,24 +34,121 @@ const vows = require("vows"); const assert = require("assert"); const tough = require("../lib/cookie"); const util = require("util"); -const nodeUtil = require("../lib/node-util"); + const Cookie = tough.Cookie; +const CookieJar = tough.CookieJar; +const MemoryCookieStore = tough.MemoryCookieStore; + +function usingNodeUtilFallback(fn) { + process.env.NODE_UTIL_FALLBACK = "enabled"; + try { + return fn(); + } finally { + delete process.env.NODE_UTIL_FALLBACK; + } +} + +function resetAgeFields(str) { + return str.replace(/\d+ms/g, "0ms"); +} vows - .describe("JavaScriptCore Compatibility") + .describe("Node util module fallback for non-node environments") .addBatch({ - util: { - topic: function() { - process.env.NODE_UTIL_FALLBACK = "enabled"; - return new Cookie(); - }, - "should not error out on util.* code paths": function(c) { - const nullUtil = nodeUtil(); - assert.equal(nullUtil.inspect(c), ""); - assert.equal(nullUtil.inspect.custom, util.inspect.custom); + "Cookie usage for util.* code paths": { + "should not error out when initializing a Cookie": function() { + assert.doesNotThrow(() => { + usingNodeUtilFallback(() => new Cookie()); + }); + } + }, + "MemoryCookieStore usage for util.* code paths": { + "should not error out when initializing a MemoryCookieStore": function() { + assert.doesNotThrow(() => { + usingNodeUtilFallback(() => new MemoryCookieStore()); + }); }, - teardown: function() { - delete process.env.NODE_UTIL_FALLBACK; + "inspecting contents": { + "when store is empty": { + topic: function() { + const cookieJar = new CookieJar(); + return this.callback( + null, + cookieJar, + util.inspect(cookieJar.store) + ); + }, + "should provide equivalent output to util.inspect(memoryCookieStore)": function( + err, + cookieJar, + expectedResult + ) { + usingNodeUtilFallback(() => { + const fallbackResult = cookieJar.store.inspect(); + assert.equal(fallbackResult, expectedResult); + }); + } + }, + "when store has a single cookie": { + topic: function() { + const cookieJar = new CookieJar(); + cookieJar.setCookieSync( + "a=1; Domain=example.com; Path=/", + "http://example.com/index.html" + ); + return this.callback( + null, + cookieJar, + resetAgeFields(util.inspect(cookieJar.store)) + ); + }, + "should provide equivalent output to util.inspect(memoryCookieStore)": function( + err, + cookieJar, + expectedResult + ) { + usingNodeUtilFallback(() => { + const fallbackResult = resetAgeFields(cookieJar.store.inspect()); + assert.equal(expectedResult, fallbackResult); + }); + } + }, + "when store has a multiple cookies": { + topic: function() { + const cookieJar = new CookieJar(); + ["a", "b", "c"].forEach((cookieName, i) => { + cookieJar.setCookieSync( + `${cookieName}=${i}; Domain=example.com; Path=/`, + "http://example.com/index.html" + ); + }); + ["d", "e"].forEach((cookieName, i) => { + cookieJar.setCookieSync( + `${cookieName}=${i}; Domain=example.com; Path=/some-path/`, + "http://example.com/index.html" + ); + }); + cookieJar.setCookieSync( + `f=0; Domain=another.com; Path=/`, + "http://another.com/index.html" + ); + return this.callback( + null, + cookieJar, + resetAgeFields(util.inspect(cookieJar.store)) + ); + }, + "should provide equivalent output to util.inspect(memoryCookieStore)": function( + err, + cookieJar, + expectedResult + ) { + usingNodeUtilFallback(() => { + const fallbackResult = resetAgeFields(cookieJar.store.inspect()); + assert.equal(expectedResult, fallbackResult); + }); + } + } } } }) From bfc7bc67cba14f063dfcd7ebb1990e8247b1af8a Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 25 Nov 2021 16:28:02 -0400 Subject: [PATCH 5/8] Support for React Native Implementation for the fallback inspect function so that it produces equivalent output to the original inspect behavior. --- lib/nodeUtil.js | 4 +++- test/node_util_fallback_test.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/nodeUtil.js b/lib/nodeUtil.js index 5c47271a..fcb22d60 100644 --- a/lib/nodeUtil.js +++ b/lib/nodeUtil.js @@ -86,7 +86,9 @@ const nodeUtilFallback = { module.exports = function nodeUtil() { try { const nodeUtilFallbackEnabled = - process && process.env && process.env.NODE_UTIL_FALLBACK === "enabled"; + process && + process.env && + process.env.TOUGH_COOKIE_NODE_UTIL_FALLBACK === "enabled"; if (!nodeUtilFallbackEnabled) { // we want the real "util" module if we are in a node environment // eslint-disable-next-line no-restricted-modules diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index 978ff7c3..0471962e 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -40,11 +40,11 @@ const CookieJar = tough.CookieJar; const MemoryCookieStore = tough.MemoryCookieStore; function usingNodeUtilFallback(fn) { - process.env.NODE_UTIL_FALLBACK = "enabled"; + process.env.TOUGH_COOKIE_NODE_UTIL_FALLBACK = "enabled"; try { return fn(); } finally { - delete process.env.NODE_UTIL_FALLBACK; + delete process.env.TOUGH_COOKIE_NODE_UTIL_FALLBACK; } } From 59a1b3dd5c263bc0de30f9888e1c0b26108a24cd Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 2 Dec 2021 09:26:29 -0400 Subject: [PATCH 6/8] Support for React Native Implementation for the fallback inspect function so that it produces equivalent output to the original inspect behavior. --- lib/cookie.js | 8 +- lib/memstore.js | 58 ++++++++- lib/nodeUtil.js | 101 ---------------- lib/utilHelper.js | 39 ++++++ test/node_util_fallback_test.js | 208 +++++++++++++++++--------------- 5 files changed, 210 insertions(+), 204 deletions(-) delete mode 100644 lib/nodeUtil.js create mode 100644 lib/utilHelper.js diff --git a/lib/cookie.js b/lib/cookie.js index 1888947d..60a1f2ed 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -31,7 +31,6 @@ "use strict"; const punycode = require("punycode"); const urlParse = require("url-parse"); -const nodeUtil = require("./nodeUtil"); const pubsuffix = require("./pubsuffix-psl"); const Store = require("./store").Store; const MemoryCookieStore = require("./memstore").MemoryCookieStore; @@ -39,6 +38,7 @@ const pathMatch = require("./pathMatch").pathMatch; const validators = require("./validators.js"); const VERSION = require("./version"); const { fromCallback } = require("universalify"); +const { getCustomInspectSymbol } = require("./utilHelper"); // From RFC6265 S4.1.1 // note that it excludes \x3B ";" @@ -812,9 +812,9 @@ const cookieDefaults = { class Cookie { constructor(options = {}) { - const util = nodeUtil(); - if (util.inspect.custom) { - this[util.inspect.custom] = this.inspect; + const customInspectSymbol = getCustomInspectSymbol(); + if (customInspectSymbol) { + this[customInspectSymbol] = this.inspect; } Object.assign(this, cookieDefaults, options); diff --git a/lib/memstore.js b/lib/memstore.js index 4a9804c6..c3d535ab 100644 --- a/lib/memstore.js +++ b/lib/memstore.js @@ -33,21 +33,21 @@ const { fromCallback } = require("universalify"); const Store = require("./store").Store; const permuteDomain = require("./permuteDomain").permuteDomain; const pathMatch = require("./pathMatch").pathMatch; -const nodeUtil = require("./nodeUtil"); +const { getCustomInspectSymbol, getUtilInspect } = require("./utilHelper"); class MemoryCookieStore extends Store { constructor() { super(); this.synchronous = true; this.idx = {}; - const util = nodeUtil(); - if (util.inspect.custom) { - this[util.inspect.custom] = this.inspect; + const customInspectSymbol = getCustomInspectSymbol(); + if (customInspectSymbol) { + this[customInspectSymbol] = this.inspect; } } inspect() { - const util = nodeUtil(); + const util = { inspect: getUtilInspect(inspectFallback) }; return `{ idx: ${util.inspect(this.idx, false, 2)} }`; } @@ -190,3 +190,51 @@ class MemoryCookieStore extends Store { }); exports.MemoryCookieStore = MemoryCookieStore; + +function inspectFallback(val) { + const domains = Object.keys(val); + if (domains.length === 0) { + return "{}"; + } + let result = "{\n"; + Object.keys(val).forEach((domain, i) => { + result += formatDomain(domain, val[domain]); + if (i < domains.length - 1) { + result += ","; + } + result += "\n"; + }); + result += "}"; + return result; +} + +function formatDomain(domainName, domainValue) { + const indent = " "; + let result = `${indent}'${domainName}': {\n`; + Object.keys(domainValue).forEach((path, i, paths) => { + result += formatPath(path, domainValue[path]); + if (i < paths.length - 1) { + result += ","; + } + result += "\n"; + }); + result += `${indent}}`; + return result; +} + +function formatPath(pathName, pathValue) { + const indent = " "; + let result = `${indent}'${pathName}': {\n`; + Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { + const cookie = pathValue[cookieName]; + result += ` ${cookieName}: ${cookie.inspect()}`; + if (i < cookieNames.length - 1) { + result += ","; + } + result += "\n"; + }); + result += `${indent}}`; + return result; +} + +exports.inspectFallback = inspectFallback; diff --git a/lib/nodeUtil.js b/lib/nodeUtil.js deleted file mode 100644 index fcb22d60..00000000 --- a/lib/nodeUtil.js +++ /dev/null @@ -1,101 +0,0 @@ -/*! - * Copyright (c) 2021, Salesforce.com, Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * 3. Neither the name of Salesforce.com nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); - -function inspectFallback(val) { - const domains = Object.keys(val); - if (domains.length === 0) { - return "{}"; - } - let result = "{\n"; - Object.keys(val).forEach((domain, i) => { - result += formatDomain(domain, val[domain]); - if (i < domains.length - 1) { - result += ","; - } - result += "\n"; - }); - result += "}"; - return result; -} - -inspectFallback.custom = customInspectSymbol; - -function formatDomain(domainName, domainValue) { - const indent = " "; - let result = `${indent}'${domainName}': {\n`; - Object.keys(domainValue).forEach((path, i, paths) => { - result += formatPath(path, domainValue[path]); - if (i < paths.length - 1) { - result += ","; - } - result += "\n"; - }); - result += `${indent}}`; - return result; -} - -function formatPath(pathName, pathValue) { - const indent = " "; - let result = `${indent}'${pathName}': {\n`; - Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { - const cookie = pathValue[cookieName]; - result += ` ${cookieName}: ${cookie[customInspectSymbol]()}`; - if (i < cookieNames.length - 1) { - result += ","; - } - result += "\n"; - }); - result += `${indent}}`; - return result; -} - -const nodeUtilFallback = { - inspect: inspectFallback -}; - -module.exports = function nodeUtil() { - try { - const nodeUtilFallbackEnabled = - process && - process.env && - process.env.TOUGH_COOKIE_NODE_UTIL_FALLBACK === "enabled"; - if (!nodeUtilFallbackEnabled) { - // we want the real "util" module if we are in a node environment - // eslint-disable-next-line no-restricted-modules - return require("util"); - } - } catch (e) { - // ignored - } - return nodeUtilFallback; -}; diff --git a/lib/utilHelper.js b/lib/utilHelper.js new file mode 100644 index 00000000..feac1250 --- /dev/null +++ b/lib/utilHelper.js @@ -0,0 +1,39 @@ +function requireUtil() { + try { + // eslint-disable-next-line no-restricted-modules + return require("util"); + } catch (e) { + return null; + } +} + +// for v10.12.0+ +function lookupCustomInspectSymbol() { + return Symbol.for("nodejs.util.inspect.custom"); +} + +// for older node environments +function tryReadingCustomSymbolFromUtilInspect(options) { + const _requireUtil = options.requireUtil || requireUtil; + const util = _requireUtil(); + return util ? util.inspect.custom : null; +} + +exports.getUtilInspect = function getUtilInspect(fallback, options = {}) { + const _requireUtil = options.requireUtil || requireUtil; + const util = _requireUtil(); + return function inspect(value, showHidden, depth) { + return util ? util.inspect(value, showHidden, depth) : fallback(value); + }; +}; + +exports.getCustomInspectSymbol = function getCustomInspectSymbol(options = {}) { + const _lookupCustomInspectSymbol = + options.lookupCustomInspectSymbol || lookupCustomInspectSymbol; + + // get custom inspect symbol for node environments + return ( + _lookupCustomInspectSymbol() || + tryReadingCustomSymbolFromUtilInspect(options) + ); +}; diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index 0471962e..dc7e2e7b 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -34,19 +34,10 @@ const vows = require("vows"); const assert = require("assert"); const tough = require("../lib/cookie"); const util = require("util"); - +const inspectFallback = require("../lib/memstore").inspectFallback; +const { getCustomInspectSymbol, getUtilInspect } = require("../lib/utilHelper"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; -const MemoryCookieStore = tough.MemoryCookieStore; - -function usingNodeUtilFallback(fn) { - process.env.TOUGH_COOKIE_NODE_UTIL_FALLBACK = "enabled"; - try { - return fn(); - } finally { - delete process.env.TOUGH_COOKIE_NODE_UTIL_FALLBACK; - } -} function resetAgeFields(str) { return str.replace(/\d+ms/g, "0ms"); @@ -55,99 +46,128 @@ function resetAgeFields(str) { vows .describe("Node util module fallback for non-node environments") .addBatch({ - "Cookie usage for util.* code paths": { - "should not error out when initializing a Cookie": function() { - assert.doesNotThrow(() => { - usingNodeUtilFallback(() => new Cookie()); - }); + getCustomInspectSymbol: { + "should not be null in a node environment": function() { + assert.equal( + getCustomInspectSymbol(), + Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom + ); + }, + "should not be null in a node environment when custom inspect symbol cannot be retrieved": function() { + assert.equal( + getCustomInspectSymbol({ + lookupCustomInspectSymbol: () => null + }), + Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom + ); + }, + "should not be null in a non-node environment": function() { + assert.equal( + getCustomInspectSymbol({ + lookupCustomInspectSymbol: () => null, + requireUtil: () => null + }), + null + ); } }, - "MemoryCookieStore usage for util.* code paths": { - "should not error out when initializing a MemoryCookieStore": function() { - assert.doesNotThrow(() => { - usingNodeUtilFallback(() => new MemoryCookieStore()); + getUtilInspect: { + "should use util.inspect in a node environment": function() { + const inspect = getUtilInspect(() => "fallback"); + assert.equal(inspect("util.inspect"), util.inspect("util.inspect")); + }, + "should use fallback inspect function in a non-node environment": function() { + const inspect = getUtilInspect(() => "fallback", { + requireUtil: () => null }); + assert.equal(inspect("util.inspect"), "fallback"); + } + }, + "util usage in Cookie": { + "custom inspect for Cookie still works": function() { + const cookie = Cookie.parse("a=1; Domain=example.com; Path=/"); + assert.equal(cookie.inspect(), util.inspect(cookie)); + } + }, + "util usage in MemoryCookie": { + "when store is empty": { + topic: function() { + const cookieJar = new CookieJar(); + return cookieJar.store; + }, + "custom inspect for MemoryCookie still works": function(memoryStore) { + assert.equal( + resetAgeFields(util.inspect(memoryStore)), + resetAgeFields(memoryStore.inspect()) + ); + }, + "fallback produces equivalent output to custom inspect": function( + memoryStore + ) { + assert.equal( + resetAgeFields(util.inspect(memoryStore.idx)), + resetAgeFields(inspectFallback(memoryStore.idx)) + ); + } }, - "inspecting contents": { - "when store is empty": { - topic: function() { - const cookieJar = new CookieJar(); - return this.callback( - null, - cookieJar, - util.inspect(cookieJar.store) - ); - }, - "should provide equivalent output to util.inspect(memoryCookieStore)": function( - err, - cookieJar, - expectedResult - ) { - usingNodeUtilFallback(() => { - const fallbackResult = cookieJar.store.inspect(); - assert.equal(fallbackResult, expectedResult); - }); - } + "when store has a single cookie": { + topic: function() { + const cookieJar = new CookieJar(); + cookieJar.setCookieSync( + "a=1; Domain=example.com; Path=/", + "http://example.com/index.html" + ); + return cookieJar.store; }, - "when store has a single cookie": { - topic: function() { - const cookieJar = new CookieJar(); + "custom inspect for MemoryCookie still works": function(memoryStore) { + assert.equal( + resetAgeFields(util.inspect(memoryStore)), + resetAgeFields(memoryStore.inspect()) + ); + }, + "fallback produces equivalent output to custom inspect": function( + memoryStore + ) { + assert.equal( + resetAgeFields(util.inspect(memoryStore.idx)), + resetAgeFields(inspectFallback(memoryStore.idx)) + ); + } + }, + "when store has a multiple cookies": { + topic: function() { + const cookieJar = new CookieJar(); + ["a", "b", "c"].forEach((cookieName, i) => { cookieJar.setCookieSync( - "a=1; Domain=example.com; Path=/", + `${cookieName}=${i}; Domain=example.com; Path=/`, "http://example.com/index.html" ); - return this.callback( - null, - cookieJar, - resetAgeFields(util.inspect(cookieJar.store)) - ); - }, - "should provide equivalent output to util.inspect(memoryCookieStore)": function( - err, - cookieJar, - expectedResult - ) { - usingNodeUtilFallback(() => { - const fallbackResult = resetAgeFields(cookieJar.store.inspect()); - assert.equal(expectedResult, fallbackResult); - }); - } - }, - "when store has a multiple cookies": { - topic: function() { - const cookieJar = new CookieJar(); - ["a", "b", "c"].forEach((cookieName, i) => { - cookieJar.setCookieSync( - `${cookieName}=${i}; Domain=example.com; Path=/`, - "http://example.com/index.html" - ); - }); - ["d", "e"].forEach((cookieName, i) => { - cookieJar.setCookieSync( - `${cookieName}=${i}; Domain=example.com; Path=/some-path/`, - "http://example.com/index.html" - ); - }); + }); + ["d", "e"].forEach((cookieName, i) => { cookieJar.setCookieSync( - `f=0; Domain=another.com; Path=/`, - "http://another.com/index.html" - ); - return this.callback( - null, - cookieJar, - resetAgeFields(util.inspect(cookieJar.store)) + `${cookieName}=${i}; Domain=example.com; Path=/some-path/`, + "http://example.com/index.html" ); - }, - "should provide equivalent output to util.inspect(memoryCookieStore)": function( - err, - cookieJar, - expectedResult - ) { - usingNodeUtilFallback(() => { - const fallbackResult = resetAgeFields(cookieJar.store.inspect()); - assert.equal(expectedResult, fallbackResult); - }); - } + }); + cookieJar.setCookieSync( + `f=0; Domain=another.com; Path=/`, + "http://another.com/index.html" + ); + return cookieJar.store; + }, + "custom inspect for MemoryCookie still works": function(memoryStore) { + assert.equal( + resetAgeFields(util.inspect(memoryStore)), + resetAgeFields(memoryStore.inspect()) + ); + }, + "fallback produces equivalent output to custom inspect": function( + memoryStore + ) { + assert.equal( + resetAgeFields(util.inspect(memoryStore.idx)), + resetAgeFields(inspectFallback(memoryStore.idx)) + ); } } } From 733f5c5118a2c4a2c677eb3c5de77c775e4593fa Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 10 Jan 2022 22:56:50 -0400 Subject: [PATCH 7/8] Update node_util_fallback_test.js reworded some of the test names to make the intent clearer --- test/node_util_fallback_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index dc7e2e7b..ef4a325c 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -53,7 +53,7 @@ vows Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom ); }, - "should not be null in a node environment when custom inspect symbol cannot be retrieved": function() { + "should not be null in a node environment when custom inspect symbol cannot be retrieved (< node v10.12.0)": function() { assert.equal( getCustomInspectSymbol({ lookupCustomInspectSymbol: () => null @@ -61,7 +61,7 @@ vows Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom ); }, - "should not be null in a non-node environment": function() { + "should be null in a non-node environment since 'util' features cannot be relied on": function() { assert.equal( getCustomInspectSymbol({ lookupCustomInspectSymbol: () => null, From 0ef85bb3b7342ad3fa9d4b953aad24cbe5b8ae86 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 13 Jan 2022 14:28:41 -0400 Subject: [PATCH 8/8] Update test/node_util_fallback_test.js Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> --- test/node_util_fallback_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index ef4a325c..d8b859a0 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2021, Salesforce.com, Inc. + * Copyright (c) 2022, Salesforce.com, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without