From 7b0ded04ce3c0b555e50d6bb0e20a16c12db95ac Mon Sep 17 00:00:00 2001 From: luoyaoming Date: Tue, 7 May 2024 15:09:07 +0800 Subject: [PATCH] Import Upstream version 4.1.3+~4.0.2 --- .eslintrc.json | 14 +- .github/workflows/integration.yaml | 26 + .gitignore | 3 + CHANGELOG.md | 5 + CODEOWNERS | 2 + CODE_OF_CONDUCT.md | 105 ++++ Dockerfile | 2 +- README.md | 492 +++++++++--------- SECURITY.md | 7 + debian/changelog | 5 - debian/control | 33 -- debian/copyright | 69 --- debian/docs | 1 - debian/nodejs/root_modules | 1 - debian/rules | 8 - debian/source/format | 1 - debian/source/lintian-overrides | 4 - debian/tests/pkg-js/files | 2 - debian/tests/pkg-js/test | 1 - .../string.prototype.repeat/package.json | 67 --- .../string.prototype.repeat/repeat.js | 50 -- debian/watch | 12 - lib/cookie.js | 133 ++++- lib/memstore.js | 70 ++- lib/permuteDomain.js | 23 +- lib/pubsuffix-psl.js | 37 +- lib/utilHelper.js | 39 ++ lib/validators.js | 95 ++++ lib/version.js | 2 +- package.json | 7 +- test/.eslintrc.json | 5 + test/api_test.js | 166 ++++++ test/cookie_jar_test.js | 177 ++++++- test/domain_and_path_test.js | 34 +- test/node_util_fallback_test.js | 175 +++++++ test/parsing_test.js | 70 ++- test/regression_test.js | 93 ++++ test/same_site_test.js | 8 +- typestough-cookie/LICENSE | 42 +- typestough-cookie/README.md | 2 +- typestough-cookie/index.d.ts | 82 +-- typestough-cookie/package.json | 7 +- 42 files changed, 1547 insertions(+), 630 deletions(-) create mode 100644 .github/workflows/integration.yaml create mode 100644 CHANGELOG.md create mode 100644 CODEOWNERS create mode 100644 CODE_OF_CONDUCT.md create mode 100644 SECURITY.md delete mode 100644 debian/changelog delete mode 100644 debian/control delete mode 100644 debian/copyright delete mode 100644 debian/docs delete mode 100644 debian/nodejs/root_modules delete mode 100755 debian/rules delete mode 100644 debian/source/format delete mode 100644 debian/source/lintian-overrides delete mode 100644 debian/tests/pkg-js/files delete mode 100644 debian/tests/pkg-js/test delete mode 100644 debian/tests/test_modules/string.prototype.repeat/package.json delete mode 100644 debian/tests/test_modules/string.prototype.repeat/repeat.js delete mode 100644 debian/watch create mode 100644 lib/utilHelper.js create mode 100644 lib/validators.js create mode 100644 test/.eslintrc.json create mode 100644 test/node_util_fallback_test.js mode change 100644 => 100755 typestough-cookie/LICENSE mode change 100644 => 100755 typestough-cookie/README.md mode change 100644 => 100755 typestough-cookie/index.d.ts mode change 100644 => 100755 typestough-cookie/package.json diff --git a/.eslintrc.json b/.eslintrc.json index 7dcba34..76a4200 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/.github/workflows/integration.yaml b/.github/workflows/integration.yaml new file mode 100644 index 0000000..29cbf13 --- /dev/null +++ b/.github/workflows/integration.yaml @@ -0,0 +1,26 @@ +name: Actions-CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x, 16.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies and test + run: npm install + - run: npm test diff --git a/.gitignore b/.gitignore index d46f89c..3dbda85 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ docker-compose.override.yml .nyc_output coverage package-lock.json +.npm +.config +.bash_history diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..94427e4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +All notable changes to this project can be found at on the [Releases](https://github.com/salesforce/tough-cookie/releases) +page. + diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..8daccd7 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# Comment line immediately above ownership line is reserved for related information. Please be careful while editing. +#ECCN:Open Source diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b672471 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,105 @@ +# Salesforce Open Source Community Code of Conduct + +## About the Code of Conduct + +Equality is a core value at Salesforce. We believe a diverse and inclusive +community fosters innovation and creativity, and are committed to building a +culture where everyone feels included. + +Salesforce open-source projects are committed to providing a friendly, safe, and +welcoming environment for all, regardless of gender identity and expression, +sexual orientation, disability, physical appearance, body size, ethnicity, nationality, +race, age, religion, level of experience, education, socioeconomic status, or +other similar personal characteristics. + +The goal of this code of conduct is to specify a baseline standard of behavior so +that people with different social values and communication styles can work +together effectively, productively, and respectfully in our open source community. +It also establishes a mechanism for reporting issues and resolving conflicts. + +All questions and reports of abusive, harassing, or otherwise unacceptable behavior +in a Salesforce open-source project may be reported by contacting the Salesforce +Open Source Conduct Committee at ossconduct@salesforce.com. + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of gender +identity and expression, sexual orientation, disability, physical appearance, +body size, ethnicity, nationality, race, age, religion, level of experience, education, +socioeconomic status, or other similar personal characteristics. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy toward other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Personal attacks, insulting/derogatory comments, or trolling +* Public or private harassment +* Publishing, or threatening to publish, others' private information—such as +a physical or electronic address—without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting +* Advocating for or encouraging any of the above behaviors + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned with this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project email +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the Salesforce Open Source Conduct Committee +at ossconduct@salesforce.com. All complaints will be reviewed and investigated +and will result in a response that is deemed necessary and appropriate to the +circumstances. The committee is obligated to maintain confidentiality with +regard to the reporter of an incident. Further details of specific enforcement +policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership and the Salesforce Open Source Conduct +Committee. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], +version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. +It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], +[CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. + +This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. + +[contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/) +[golang-coc]: https://golang.org/conduct +[cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md +[microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ +[cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ diff --git a/Dockerfile b/Dockerfile index c345257..7fdc137 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 +FROM node:16 MAINTAINER awaterman@salesforce.com LABEL Description="Vendor=\"Salesforce.com\" Version=\"1.0\"" RUN apt-get update && \ diff --git a/README.md b/README.md index 2dc9496..fb11f3c 100644 --- a/README.md +++ b/README.md @@ -1,191 +1,204 @@ -[RFC6265](https://tools.ietf.org/html/rfc6265) Cookies and CookieJar for Node.js +# tough-cookie + +[RFC 6265](https://tools.ietf.org/html/rfc6265) Cookies and CookieJar for Node.js [![npm package](https://nodei.co/npm/tough-cookie.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/tough-cookie/) [![Build Status](https://travis-ci.org/salesforce/tough-cookie.svg?branch=master)](https://travis-ci.org/salesforce/tough-cookie) -# Synopsis +## Synopsis -``` javascript -var tough = require('tough-cookie'); +```javascript +var tough = require("tough-cookie"); var Cookie = tough.Cookie; var cookie = Cookie.parse(header); -cookie.value = 'somethingdifferent'; +cookie.value = "somethingdifferent"; header = cookie.toString(); - var cookiejar = new tough.CookieJar(); -cookiejar.setCookie(cookie, 'http://currentdomain.example.com/path', cb); -// ... -cookiejar.getCookies('http://example.com/otherpath',function(err,cookies) { - res.headers['cookie'] = cookies.join('; '); + +// Asynchronous! +var cookie = await cookiejar.setCookie( + cookie, + "https://currentdomain.example.com/path" +); +var cookies = await cookiejar.getCookies("https://example.com/otherpath"); + +// Or with callbacks! +cookiejar.setCookie( + cookie, + "https://currentdomain.example.com/path", + function (err, cookie) { + /* ... */ + } +); +cookiejar.getCookies("http://example.com/otherpath", function (err, cookies) { + /* ... */ }); ``` -# Installation +Why the name? NPM modules `cookie`, `cookies` and `cookiejar` were already taken. -It's _so_ easy! +## Installation -`npm install tough-cookie` +It's _so_ easy! Install with `npm` or your preferred package manager. -Why the name? NPM modules `cookie`, `cookies` and `cookiejar` were already taken. +```sh +npm install tough-cookie +``` -## Version Support +## Node.js Version Support -Support for versions of node.js will follow that of the [request](https://www.npmjs.com/package/request) module. +We follow the [node.js release schedule](https://github.com/nodejs/Release#release-schedule) and support all versions that are in Active LTS or Maintenance. We will always do a major release when dropping support for older versions of node, and we will do so in consultation with our community. -# API +## API -## tough +### tough -Functions on the module you get from `require('tough-cookie')`. All can be used as pure functions and don't need to be "bound". +The top-level exports from `require('tough-cookie')` can all be used as pure functions and don't need to be bound. -**Note**: prior to 1.0.x, several of these functions took a `strict` parameter. This has since been removed from the API as it was no longer necessary. +#### `parseDate(string)` -### `parseDate(string)` +Parse a cookie date string into a `Date`. Parses according to [RFC 6265 Section 5.1.1](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1), not `Date.parse()`. -Parse a cookie date string into a `Date`. Parses according to RFC6265 Section 5.1.1, not `Date.parse()`. +#### `formatDate(date)` -### `formatDate(date)` +Format a `Date` into an [RFC 822](https://datatracker.ietf.org/doc/html/rfc822#section-5) string (the RFC 6265 recommended format). -Format a Date into a RFC1123 string (the RFC6265-recommended format). +#### `canonicalDomain(str)` -### `canonicalDomain(str)` +Transforms a domain name into a canonical domain name. The canonical domain name is a domain name that has been trimmed, lowercased, stripped of leading dot, and optionally punycode-encoded ([Section 5.1.2 of RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.2)). For the most part, this function is idempotent (calling the function with the output from a previous call returns the same output). -Transforms a domain-name into a canonical domain-name. The canonical domain-name is a trimmed, lowercased, stripped-of-leading-dot and optionally punycode-encoded domain-name (Section 5.1.2 of RFC6265). For the most part, this function is idempotent (can be run again on its output without ill effects). +#### `domainMatch(str, domStr[, canonicalize=true])` -### `domainMatch(str,domStr[,canonicalize=true])` +Answers "does this real domain match the domain in a cookie?". The `str` is the "current" domain name and the `domStr` is the "cookie" domain name. Matches according to [RFC 6265 Section 5.1.3](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3), but it helps to think of it as a "suffix match". -Answers "does this real domain match the domain in a cookie?". The `str` is the "current" domain-name and the `domStr` is the "cookie" domain-name. Matches according to RFC6265 Section 5.1.3, but it helps to think of it as a "suffix match". +The `canonicalize` parameter toggles whether the domain parameters get normalized with `canonicalDomain` or not. -The `canonicalize` parameter will run the other two parameters through `canonicalDomain` or not. +#### `defaultPath(path)` -### `defaultPath(path)` +Given a current request/response path, gives the path appropriate for storing in a cookie. This is basically the "directory" of a "file" in the path, but is specified by [Section 5.1.4 of the RFC](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4). -Given a current request/response path, gives the Path apropriate for storing in a cookie. This is basically the "directory" of a "file" in the path, but is specified by Section 5.1.4 of the RFC. +The `path` parameter MUST be _only_ the pathname part of a URI (excluding the hostname, query, fragment, and so on). This is the `.pathname` property of node's `uri.parse()` output. -The `path` parameter MUST be _only_ the pathname part of a URI (i.e. excludes the hostname, query, fragment, etc.). This is the `.pathname` property of node's `uri.parse()` output. +#### `pathMatch(reqPath, cookiePath)` -### `pathMatch(reqPath,cookiePath)` - -Answers "does the request-path path-match a given cookie-path?" as per RFC6265 Section 5.1.4. Returns a boolean. +Answers "does the request-path path-match a given cookie-path?" as per [RFC 6265 Section 5.1.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4). Returns a boolean. This is essentially a prefix-match where `cookiePath` is a prefix of `reqPath`. -### `parse(cookieString[, options])` +#### `parse(cookieString[, options])` -alias for `Cookie.parse(cookieString[, options])` +Alias for [`Cookie.parse(cookieString[, options])`](#cookieparsecookiestring-options). -### `fromJSON(string)` +#### `fromJSON(string)` -alias for `Cookie.fromJSON(string)` +Alias for [`Cookie.fromJSON(string)`](#cookiefromjsonstrorobj). -### `getPublicSuffix(hostname)` +#### `getPublicSuffix(hostname)` -Returns the public suffix of this hostname. The public suffix is the shortest domain-name upon which a cookie can be set. Returns `null` if the hostname cannot have cookies set for it. +Returns the public suffix of this hostname. The public suffix is the shortest domain name upon which a cookie can be set. Returns `null` if the hostname cannot have cookies set for it. For example: `www.example.com` and `www.subdomain.example.com` both have public suffix `example.com`. -For further information, see http://publicsuffix.org/. This module derives its list from that site. This call is currently a wrapper around [`psl`](https://www.npmjs.com/package/psl)'s [get() method](https://www.npmjs.com/package/psl#pslgetdomain). +For further information, see the [Public Suffix List](http://publicsuffix.org/). This module derives its list from that site. This call is a wrapper around [`psl`](https://www.npmjs.com/package/psl)'s [`get` method](https://www.npmjs.com/package/psl##pslgetdomain). -### `cookieCompare(a,b)` +#### `cookieCompare(a, b)` -For use with `.sort()`, sorts a list of cookies into the recommended order given in the RFC (Section 5.4 step 2). The sort algorithm is, in order of precedence: +For use with `.sort()`, sorts a list of cookies into the recommended order given in step 2 of ([RFC 6265 Section 5.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.4)). The sort algorithm is, in order of precedence: -* Longest `.path` -* oldest `.creation` (which has a 1ms precision, same as `Date`) -* lowest `.creationIndex` (to get beyond the 1ms precision) +- Longest `.path` +- oldest `.creation` (which has a 1-ms precision, same as `Date`) +- lowest `.creationIndex` (to get beyond the 1-ms precision) -``` javascript -var cookies = [ /* unsorted array of Cookie objects */ ]; +```javascript +var cookies = [ + /* unsorted array of Cookie objects */ +]; cookies = cookies.sort(cookieCompare); ``` -**Note**: Since JavaScript's `Date` is limited to a 1ms precision, cookies within the same milisecond are entirely possible. This is especially true when using the `now` option to `.setCookie()`. The `.creationIndex` property is a per-process global counter, assigned during construction with `new Cookie()`. This preserves the spirit of the RFC sorting: older cookies go first. This works great for `MemoryCookieStore`, since `Set-Cookie` headers are parsed in order, but may not be so great for distributed systems. Sophisticated `Store`s may wish to set this to some other _logical clock_ such that if cookies A and B are created in the same millisecond, but cookie A is created before cookie B, then `A.creationIndex < B.creationIndex`. If you want to alter the global counter, which you probably _shouldn't_ do, it's stored in `Cookie.cookiesCreated`. +> **Note**: Since the JavaScript `Date` is limited to a 1-ms precision, cookies within the same millisecond are entirely possible. This is especially true when using the `now` option to `.setCookie()`. The `.creationIndex` property is a per-process global counter, assigned during construction with `new Cookie()`, which preserves the spirit of the RFC sorting: older cookies go first. This works great for `MemoryCookieStore` since `Set-Cookie` headers are parsed in order, but is not so great for distributed systems. Sophisticated `Store`s may wish to set this to some other _logical clock_ so that if cookies A and B are created in the same millisecond, but cookie A is created before cookie B, then `A.creationIndex < B.creationIndex`. If you want to alter the global counter, which you probably _shouldn't_ do, it's stored in `Cookie.cookiesCreated`. -### `permuteDomain(domain)` +#### `permuteDomain(domain)` -Generates a list of all possible domains that `domainMatch()` the parameter. May be handy for implementing cookie stores. +Generates a list of all possible domains that `domainMatch()` the parameter. Can be handy for implementing cookie stores. -### `permutePath(path)` +#### `permutePath(path)` -Generates a list of all possible paths that `pathMatch()` the parameter. May be handy for implementing cookie stores. +Generates a list of all possible paths that `pathMatch()` the parameter. Can be handy for implementing cookie stores. - -## Cookie +### Cookie Exported via `tough.Cookie`. -### `Cookie.parse(cookieString[, options])` +#### `Cookie.parse(cookieString[, options])` -Parses a single Cookie or Set-Cookie HTTP header into a `Cookie` object. Returns `undefined` if the string can't be parsed. +Parses a single Cookie or Set-Cookie HTTP header into a `Cookie` object. Returns `undefined` if the string can't be parsed. The options parameter is not required and currently has only one property: - * _loose_ - boolean - if `true` enable parsing of key-less cookies like `=abc` and `=`, which are not RFC-compliant. +- _loose_ - boolean - if `true` enable parsing of keyless cookies like `=abc` and `=`, which are not RFC-compliant. -If options is not an object, it is ignored, which means you can use `Array#map` with it. +If options is not an object it is ignored, which means it can be used with [`Array#map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). -Here's how to process the Set-Cookie header(s) on a node HTTP/HTTPS response: +To process the Set-Cookie header(s) on a node HTTP/HTTPS response: -``` javascript -if (res.headers['set-cookie'] instanceof Array) - cookies = res.headers['set-cookie'].map(Cookie.parse); -else - cookies = [Cookie.parse(res.headers['set-cookie'])]; +```javascript +if (Array.isArray(res.headers["set-cookie"])) + cookies = res.headers["set-cookie"].map(Cookie.parse); +else cookies = [Cookie.parse(res.headers["set-cookie"])]; ``` -_Note:_ in version 2.3.3, tough-cookie limited the number of spaces before the `=` to 256 characters. This limitation has since been removed. -See [Issue 92](https://github.com/salesforce/tough-cookie/issues/92) +_Note:_ In version 2.3.3, tough-cookie limited the number of spaces before the `=` to 256 characters. This limitation was removed in version 2.3.4. +For more details, see [issue #92](https://github.com/salesforce/tough-cookie/issues/92). -### Properties +#### Properties Cookie object properties: - * _key_ - string - the name or key of the cookie (default "") - * _value_ - string - the value of the cookie (default "") - * _expires_ - `Date` - if set, the `Expires=` attribute of the cookie (defaults to the string `"Infinity"`). See `setExpires()` - * _maxAge_ - seconds - if set, the `Max-Age=` attribute _in seconds_ of the cookie. May also be set to strings `"Infinity"` and `"-Infinity"` for non-expiry and immediate-expiry, respectively. See `setMaxAge()` - * _domain_ - string - the `Domain=` attribute of the cookie - * _path_ - string - the `Path=` of the cookie - * _secure_ - boolean - the `Secure` cookie flag - * _httpOnly_ - boolean - the `HttpOnly` cookie flag - * _sameSite_ - string - the `SameSite` cookie attribute (from [RFC6265bis]); must be one of `none`, `lax`, or `strict` - * _extensions_ - `Array` - any unrecognized cookie attributes as strings (even if equal-signs inside) - * _creation_ - `Date` - when this cookie was constructed - * _creationIndex_ - number - set at construction, used to provide greater sort precision (please see `cookieCompare(a,b)` for a full explanation) +- _key_ - string - the name or key of the cookie (default `""`) +- _value_ - string - the value of the cookie (default `""`) +- _expires_ - `Date` - if set, the `Expires=` attribute of the cookie (defaults to the string `"Infinity"`). See `setExpires()` +- _maxAge_ - seconds - if set, the `Max-Age=` attribute _in seconds_ of the cookie. Can also be set to strings `"Infinity"` and `"-Infinity"` for non-expiry and immediate-expiry, respectively. See `setMaxAge()` +- _domain_ - string - the `Domain=` attribute of the cookie +- _path_ - string - the `Path=` of the cookie +- _secure_ - boolean - the `Secure` cookie flag +- _httpOnly_ - boolean - the `HttpOnly` cookie flag +- _sameSite_ - string - the `SameSite` cookie attribute (from [RFC 6265bis](#rfc-6265bis)); must be one of `none`, `lax`, or `strict` +- _extensions_ - `Array` - any unrecognized cookie attributes as strings (even if equal-signs inside) +- _creation_ - `Date` - when this cookie was constructed +- _creationIndex_ - number - set at construction, used to provide greater sort precision (see `cookieCompare(a,b)` for a full explanation) -After a cookie has been passed through `CookieJar.setCookie()` it will have the following additional attributes: +After a cookie has been passed through `CookieJar.setCookie()` it has the following additional attributes: - * _hostOnly_ - boolean - is this a host-only cookie (i.e. no Domain field was set, but was instead implied) - * _pathIsDefault_ - boolean - if true, there was no Path field on the cookie and `defaultPath()` was used to derive one. - * _creation_ - `Date` - **modified** from construction to when the cookie was added to the jar - * _lastAccessed_ - `Date` - last time the cookie got accessed. Will affect cookie cleaning once implemented. Using `cookiejar.getCookies(...)` will update this attribute. +- _hostOnly_ - boolean - is this a host-only cookie (that is, no Domain field was set, but was instead implied). +- _pathIsDefault_ - boolean - if true, there was no Path field on the cookie and `defaultPath()` was used to derive one. +- _creation_ - `Date` - **modified** from construction to when the cookie was added to the jar. +- _lastAccessed_ - `Date` - last time the cookie got accessed. Affects cookie cleaning after it is implemented. Using `cookiejar.getCookies(...)` updates this attribute. -### `Cookie([{properties}])` +#### `new Cookie([properties])` -Receives an options object that can contain any of the above Cookie properties, uses the default for unspecified properties. +Receives an options object that can contain any of the above Cookie properties. Uses the default for unspecified properties. -### `.toString()` +#### `.toString()` -encode to a Set-Cookie header value. The Expires cookie field is set using `formatDate()`, but is omitted entirely if `.expires` is `Infinity`. +Encodes to a Set-Cookie header value. The Expires cookie field is set using `formatDate()`, but is omitted entirely if `.expires` is `Infinity`. -### `.cookieString()` +#### `.cookieString()` -encode to a Cookie header value (i.e. the `.key` and `.value` properties joined with '='). +Encodes to a Cookie header value (specifically, the `.key` and `.value` properties joined with `"="`). -### `.setExpires(String)` +#### `.setExpires(string)` -sets the expiry based on a date-string passed through `parseDate()`. If parseDate returns `null` (i.e. can't parse this date string), `.expires` is set to `"Infinity"` (a string) is set. +Sets the expiry based on a date-string passed through `parseDate()`. If parseDate returns `null` (that is, can't parse this date string), `.expires` is set to `"Infinity"` (a string). -### `.setMaxAge(number)` +#### `.setMaxAge(number)` -sets the maxAge in seconds. Coerces `-Infinity` to `"-Infinity"` and `Infinity` to `"Infinity"` so it JSON serializes correctly. +Sets the maxAge in seconds. Coerces `-Infinity` to `"-Infinity"` and `Infinity` to `"Infinity"` so it correctly serializes to JSON. -### `.expiryTime([now=Date.now()])` +#### `.expiryDate([now=Date.now()])` -### `.expiryDate([now=Date.now()])` - -expiryTime() Computes the absolute unix-epoch milliseconds that this cookie expires. expiryDate() works similarly, except it returns a `Date` object. Note that in both cases the `now` parameter should be milliseconds. +`expiryTime()` computes the absolute unix-epoch milliseconds that this cookie expires. `expiryDate()` works similarly, except it returns a `Date` object. Note that in both cases the `now` parameter should be milliseconds. Max-Age takes precedence over Expires (as per the RFC). The `.creation` attribute -- or, by default, the `now` parameter -- is used to offset the `.maxAge` attribute. @@ -193,45 +206,45 @@ If Expires (`.expires`) is set, that's returned. Otherwise, `expiryTime()` returns `Infinity` and `expiryDate()` returns a `Date` object for "Tue, 19 Jan 2038 03:14:07 GMT" (latest date that can be expressed by a 32-bit `time_t`; the common limit for most user-agents). -### `.TTL([now=Date.now()])` +#### `.TTL([now=Date.now()])` -compute the TTL relative to `now` (milliseconds). The same precedence rules as for `expiryTime`/`expiryDate` apply. +Computes the TTL relative to `now` (milliseconds). The same precedence rules as for `expiryTime`/`expiryDate` apply. -The "number" `Infinity` is returned for cookies without an explicit expiry and `0` is returned if the cookie is expired. Otherwise a time-to-live in milliseconds is returned. +`Infinity` is returned for cookies without an explicit expiry and `0` is returned if the cookie is expired. Otherwise a time-to-live in milliseconds is returned. -### `.canonicalizedDomain()` +#### `.canonicalizedDomain()` -### `.cdomain()` +#### `.cdomain()` -return the canonicalized `.domain` field. This is lower-cased and punycode (RFC3490) encoded if the domain has any non-ASCII characters. +Returns the canonicalized `.domain` field. This is lower-cased and punycode ([RFC 3490](https://datatracker.ietf.org/doc/html/rfc3490)) encoded if the domain has any non-ASCII characters. -### `.toJSON()` +#### `.toJSON()` For convenience in using `JSON.serialize(cookie)`. Returns a plain-old `Object` that can be JSON-serialized. -Any `Date` properties (i.e., `.expires`, `.creation`, and `.lastAccessed`) are exported in ISO format (`.toISOString()`). +Any `Date` properties (such as `.expires`, `.creation`, and `.lastAccessed`) are exported in ISO format (`.toISOString()`). -**NOTE**: Custom `Cookie` properties will be discarded. In tough-cookie 1.x, since there was no `.toJSON` method explicitly defined, all enumerable properties were captured. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array. +> **NOTE**: Custom `Cookie` properties are discarded. In tough-cookie 1.x, since there was no `.toJSON` method explicitly defined, all enumerable properties were captured. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array. -### `Cookie.fromJSON(strOrObj)` +#### `Cookie.fromJSON(strOrObj)` Does the reverse of `cookie.toJSON()`. If passed a string, will `JSON.parse()` that first. -Any `Date` properties (i.e., `.expires`, `.creation`, and `.lastAccessed`) are parsed via `Date.parse()`, not the tough-cookie `parseDate`, since it's JavaScript/JSON-y timestamps being handled at this layer. +Any `Date` properties (such as `.expires`, `.creation`, and `.lastAccessed`) are parsed via [`Date.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse), not tough-cookie's `parseDate`, since ISO timestamps are being handled at this layer. -Returns `null` upon JSON parsing error. +Returns `null` upon a JSON parsing error. -### `.clone()` +#### `.clone()` -Does a deep clone of this cookie, exactly implemented as `Cookie.fromJSON(cookie.toJSON())`. +Does a deep clone of this cookie, implemented exactly as `Cookie.fromJSON(cookie.toJSON())`. -### `.validate()` +#### `.validate()` -Status: *IN PROGRESS*. Works for a few things, but is by no means comprehensive. +Status: _IN PROGRESS_. Works for a few things, but is by no means comprehensive. -validates cookie attributes for semantic correctness. Useful for "lint" checking any Set-Cookie headers you generate. For now, it returns a boolean, but eventually could return a reason string -- you can future-proof with this construct: +Validates cookie attributes for semantic correctness. Useful for "lint" checking any Set-Cookie headers you generate. For now, it returns a boolean, but eventually could return a reason string. Future-proof with this construct: -``` javascript +```javascript if (cookie.validate() === true) { // it's tasty } else { @@ -239,225 +252,225 @@ if (cookie.validate() === true) { } ``` - -## CookieJar +### CookieJar Exported via `tough.CookieJar`. -### `CookieJar([store],[options])` +#### `CookieJar([store][, options])` -Simply use `new CookieJar()`. If you'd like to use a custom store, pass that to the constructor otherwise a `MemoryCookieStore` will be created and used. +Simply use `new CookieJar()`. If a custom store is not passed to the constructor, a [`MemoryCookieStore`](#memorycookiestore) is created and used. The `options` object can be omitted and can have the following properties: - * _rejectPublicSuffixes_ - boolean - default `true` - reject cookies with domains like "com" and "co.uk" - * _looseMode_ - boolean - default `false` - accept malformed cookies like `bar` and `=bar`, which have an implied empty name. - * _prefixSecurity_ - string - default `silent` - set to `'unsafe-disabled'`, `'silent'`, or `'strict'`. See [Cookie Prefixes] below. - * _allowSpecialUseDomain_ - boolean - default `false` - accepts special-use domain suffixes, such as `local`. Useful for testing purposes. - This is not in the standard, but is used sometimes on the web and is accepted by (most) browsers. +- _rejectPublicSuffixes_ - boolean - default `true` - reject cookies with domains like "com" and "co.uk" +- _looseMode_ - boolean - default `false` - accept malformed cookies like `bar` and `=bar`, which have an implied empty name. +- _prefixSecurity_ - string - default `silent` - set to `'unsafe-disabled'`, `'silent'`, or `'strict'`. See [Cookie Prefixes](#cookie-prefixes) below. +- _allowSpecialUseDomain_ - boolean - default `true` - accepts special-use domain suffixes, such as `local`. Useful for testing purposes. + This is not in the standard, but is used sometimes on the web and is accepted by most browsers. -Since eventually this module would like to support database/remote/etc. CookieJars, continuation passing style is used for CookieJar methods. +#### `.setCookie(cookieOrString, currentUrl[, options][, callback(err, cookie)])` -### `.setCookie(cookieOrString, currentUrl, [{options},] cb(err,cookie))` - -Attempt to set the cookie in the cookie jar. If the operation fails, an error will be given to the callback `cb`, otherwise the cookie is passed through. The cookie will have updated `.creation`, `.lastAccessed` and `.hostOnly` properties. +Attempt to set the cookie in the cookie jar. The cookie has updated `.creation`, `.lastAccessed` and `.hostOnly` properties. And returns a promise if a callback is not provided. The `options` object can be omitted and can have the following properties: - * _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects HttpOnly cookies. - * _secure_ - boolean - autodetect from url - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` then this is defaulted to `true`, otherwise `false`. - * _now_ - Date - default `new Date()` - what to use for the creation/access time of cookies - * _ignoreError_ - boolean - default `false` - silently ignore things like parse errors and invalid domains. `Store` errors aren't ignored by this option. - * _sameSiteContext_ - string - default unset - set to `'none'`, `'lax'`, or `'strict'` See [SameSite Cookies] below. +- _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects `HttpOnly` cookies. +- _secure_ - boolean - autodetect from URL - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` this defaults to `true`, otherwise `false`. +- _now_ - Date - default `new Date()` - what to use for the creation or access time of cookies. +- _ignoreError_ - boolean - default `false` - silently ignore things like parse errors and invalid domains. `Store` errors aren't ignored by this option. +- _sameSiteContext_ - string - default unset - set to `'none'`, `'lax'`, or `'strict'` See [SameSite Cookies](#samesite-cookies) below. -As per the RFC, the `.hostOnly` property is set if there was no "Domain=" parameter in the cookie string (or `.domain` was null on the Cookie object). The `.domain` property is set to the fully-qualified hostname of `currentUrl` in this case. Matching this cookie requires an exact hostname match (not a `domainMatch` as per usual). +As per the RFC, the `.hostOnly` property is set if there was no "Domain=" parameter in the cookie string (or `.domain` was null on the Cookie object). The `.domain` property is set to the fully-qualified hostname of `currentUrl` in this case. Matching this cookie requires an exact hostname match (not a `domainMatch` as per usual). -### `.setCookieSync(cookieOrString, currentUrl, [{options}])` +#### `.setCookieSync(cookieOrString, currentUrl[, options])` -Synchronous version of `setCookie`; only works with synchronous stores (e.g. the default `MemoryCookieStore`). +Synchronous version of [`setCookie`](#setcookiecookieorstring-currenturl-options-callbackerr-cookie); only works with synchronous stores (that is, the default `MemoryCookieStore`). -### `.getCookies(currentUrl, [{options},] cb(err,cookies))` +#### `.getCookies(currentUrl[, options][, callback(err, cookies)])` -Retrieve the list of cookies that can be sent in a Cookie header for the current url. +Retrieve the list of cookies that can be sent in a Cookie header for the current URL. Returns a promise if a callback is not provided. -If an error is encountered, that's passed as `err` to the callback, otherwise an `Array` of `Cookie` objects is passed. The array is sorted with `cookieCompare()` unless the `{sort:false}` option is given. +Returns an array of `Cookie` objects, sorted by default using [`cookieCompare`](#cookiecomparea-b). + +If an error is encountered it's passed as `err` to the callback, otherwise an array of `Cookie` objects is passed. The array is sorted with `cookieCompare()` unless the `{sort:false}` option is given. The `options` object can be omitted and can have the following properties: - * _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects HttpOnly cookies. - * _secure_ - boolean - autodetect from url - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` then this is defaulted to `true`, otherwise `false`. - * _now_ - Date - default `new Date()` - what to use for the creation/access time of cookies - * _expire_ - boolean - default `true` - perform expiry-time checking of cookies and asynchronously remove expired cookies from the store. Using `false` will return expired cookies and **not** remove them from the store (which is useful for replaying Set-Cookie headers, potentially). - * _allPaths_ - boolean - default `false` - if `true`, do not scope cookies by path. The default uses RFC-compliant path scoping. **Note**: may not be supported by the underlying store (the default `MemoryCookieStore` supports it). - * _sameSiteContext_ - string - default unset - Set this to `'none'`, `'lax'` or `'strict'` to enforce SameSite cookies upon retrival. See [SameSite Cookies] below. +- _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects `HttpOnly` cookies. +- _secure_ - boolean - autodetect from URL - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` then this is defaulted to `true`, otherwise `false`. +- _now_ - Date - default `new Date()` - what to use for the creation or access time of cookies +- _expire_ - boolean - default `true` - perform expiry-time checking of cookies and asynchronously remove expired cookies from the store. Using `false` returns expired cookies and does **not** remove them from the store (which is potentially useful for replaying Set-Cookie headers). +- _allPaths_ - boolean - default `false` - if `true`, do not scope cookies by path. The default uses RFC-compliant path scoping. **Note**: may not be supported by the underlying store (the default `MemoryCookieStore` supports it). +- _sameSiteContext_ - string - default unset - Set this to `'none'`, `'lax'`, or `'strict'` to enforce SameSite cookies upon retrieval. See [SameSite Cookies](#samesite-cookies) below. +- _sort_ - boolean - whether to sort the list of cookies. The `.lastAccessed` property of the returned cookies will have been updated. -### `.getCookiesSync(currentUrl, [{options}])` +#### `.getCookiesSync(currentUrl, [{options}])` -Synchronous version of `getCookies`; only works with synchronous stores (e.g. the default `MemoryCookieStore`). +Synchronous version of [`getCookies`](#getcookiescurrenturl-options-callbackerr-cookies); only works with synchronous stores (for example, the default `MemoryCookieStore`). -### `.getCookieString(...)` +#### `.getCookieString(...)` -Accepts the same options as `.getCookies()` but passes a string suitable for a Cookie header rather than an array to the callback. Simply maps the `Cookie` array via `.cookieString()`. +Accepts the same options as [`.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies) but returns a string suitable for a Cookie header rather than an Array. -### `.getCookieStringSync(...)` +#### `.getCookieStringSync(...)` -Synchronous version of `getCookieString`; only works with synchronous stores (e.g. the default `MemoryCookieStore`). +Synchronous version of [`getCookieString`](#getcookiestring); only works with synchronous stores (for example, the default `MemoryCookieStore`). -### `.getSetCookieStrings(...)` +#### `.getSetCookieStrings(...)` -Returns an array of strings suitable for **Set-Cookie** headers. Accepts the same options as `.getCookies()`. Simply maps the cookie array via `.toString()`. +Returns an array of strings suitable for **Set-Cookie** headers. Accepts the same options as [`.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies). Simply maps the cookie array via `.toString()`. -### `.getSetCookieStringsSync(...)` +#### `.getSetCookieStringsSync(...)` -Synchronous version of `getSetCookieStrings`; only works with synchronous stores (e.g. the default `MemoryCookieStore`). +Synchronous version of [`getSetCookieStrings`](#getsetcookiestrings); only works with synchronous stores (for example, the default `MemoryCookieStore`). -### `.serialize(cb(err,serializedObject))` +#### `.serialize([callback(err, serializedObject)])` + +Returns a promise if a callback is not provided. Serialize the Jar if the underlying store supports `.getAllCookies`. -**NOTE**: Custom `Cookie` properties will be discarded. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array. +> **NOTE**: Custom `Cookie` properties are discarded. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array. -See [Serialization Format]. +See [Serialization Format](#serialization-format). -### `.serializeSync()` +#### `.serializeSync()` -Sync version of .serialize +Synchronous version of [`serialize`](#serializecallbackerr-serializedobject); only works with synchronous stores (for example, the default `MemoryCookieStore`). -### `.toJSON()` +#### `.toJSON()` -Alias of .serializeSync() for the convenience of `JSON.stringify(cookiejar)`. +Alias of [`.serializeSync()`](#serializesync) for the convenience of `JSON.stringify(cookiejar)`. -### `CookieJar.deserialize(serialized, [store], cb(err,object))` +#### `CookieJar.deserialize(serialized[, store][, callback(err, object)])` -A new Jar is created and the serialized Cookies are added to the underlying store. Each `Cookie` is added via `store.putCookie` in the order in which they appear in the serialization. +A new Jar is created and the serialized Cookies are added to the underlying store. Each `Cookie` is added via `store.putCookie` in the order in which they appear in the serialization. A promise is returned if a callback is not provided. The `store` argument is optional, but should be an instance of `Store`. By default, a new instance of `MemoryCookieStore` is created. -As a convenience, if `serialized` is a string, it is passed through `JSON.parse` first. If that throws an error, this is passed to the callback. +As a convenience, if `serialized` is a string, it is passed through `JSON.parse` first. -### `CookieJar.deserializeSync(serialized, [store])` +#### `CookieJar.deserializeSync(serialized[, store])` -Sync version of `.deserialize`. _Note_ that the `store` must be synchronous for this to work. +Sync version of [`.deserialize`](#cookiejardeserializeserialized-store-callbackerr-object); only works with synchronous stores (for example, the default `MemoryCookieStore`). -### `CookieJar.fromJSON(string)` +#### `CookieJar.fromJSON(string)` -Alias of `.deserializeSync` to provide consistency with `Cookie.fromJSON()`. +Alias of [`.deserializeSync`](#cookiejardeserializesyncserialized-store) to provide consistency with [`Cookie.fromJSON()`](#cookiefromjsonstrorobj). -### `.clone([store,]cb(err,newJar))` +#### `.clone([store][, callback(err, cloned))` -Produces a deep clone of this jar. Modifications to the original won't affect the clone, and vice versa. +Produces a deep clone of this jar. Modifications to the original do not affect the clone, and vice versa. Returns a promise if a callback is not provided. The `store` argument is optional, but should be an instance of `Store`. By default, a new instance of `MemoryCookieStore` is created. Transferring between store types is supported so long as the source implements `.getAllCookies()` and the destination implements `.putCookie()`. -### `.cloneSync([store])` +#### `.cloneSync([store])` -Synchronous version of `.clone`, returning a new `CookieJar` instance. +Synchronous version of [`.clone`](#clonestore-callbackerr-cloned), returning a new `CookieJar` instance. The `store` argument is optional, but must be a _synchronous_ `Store` instance if specified. If not passed, a new instance of `MemoryCookieStore` is used. The _source_ and _destination_ must both be synchronous `Store`s. If one or both stores are asynchronous, use `.clone` instead. Recall that `MemoryCookieStore` supports both synchronous and asynchronous API calls. -### `.removeAllCookies(cb(err))` +#### `.removeAllCookies([callback(err)])` -Removes all cookies from the jar. +Removes all cookies from the jar. Returns a promise if a callback is not provided. This is a new backwards-compatible feature of `tough-cookie` version 2.5, so not all Stores will implement it efficiently. For Stores that do not implement `removeAllCookies`, the fallback is to call `removeCookie` after `getAllCookies`. If `getAllCookies` fails or isn't implemented in the Store, that error is returned. If one or more of the `removeCookie` calls fail, only the first error is returned. -### `.removeAllCookiesSync()` +#### `.removeAllCookiesSync()` -Sync version of `.removeAllCookies()` +Sync version of [`.removeAllCookies()`](#removeallcookiescallbackerr); only works with synchronous stores (for example, the default `MemoryCookieStore`). -## Store +### Store Base class for CookieJar stores. Available as `tough.Store`. -## Store API +### Store API -The storage model for each `CookieJar` instance can be replaced with a custom implementation. The default is `MemoryCookieStore` which can be found in the `lib/memstore.js` file. The API uses continuation-passing-style to allow for asynchronous stores. +The storage model for each `CookieJar` instance can be replaced with a custom implementation. The default is `MemoryCookieStore` which can be found in [`lib/memstore.js`](https://github.com/salesforce/tough-cookie/blob/master/lib/memstore.js). The API uses continuation-passing-style to allow for asynchronous stores. -Stores should inherit from the base `Store` class, which is available as `require('tough-cookie').Store`. +Stores should inherit from the base `Store` class, which is available as a top-level export. -Stores are asynchronous by default, but if `store.synchronous` is set to `true`, then the `*Sync` methods on the of the containing `CookieJar` can be used (however, the continuation-passing style +Stores are asynchronous by default, but if `store.synchronous` is set to `true`, then the `*Sync` methods of the containing `CookieJar` can be used. -All `domain` parameters will have been normalized before calling. +All `domain` parameters are normalized before calling. -The Cookie store must have all of the following methods. +The Cookie store must have all of the following methods. Note that asynchronous implementations **must** support callback parameters. -### `store.findCookie(domain, path, key, cb(err,cookie))` +#### `store.findCookie(domain, path, key, callback(err, cookie))` -Retrieve a cookie with the given domain, path and key (a.k.a. name). The RFC maintains that exactly one of these cookies should exist in a store. If the store is using versioning, this means that the latest/newest such cookie should be returned. +Retrieve a cookie with the given domain, path, and key (name). The RFC maintains that exactly one of these cookies should exist in a store. If the store is using versioning, this means that the latest or newest such cookie should be returned. -Callback takes an error and the resulting `Cookie` object. If no cookie is found then `null` MUST be passed instead (i.e. not an error). +Callback takes an error and the resulting `Cookie` object. If no cookie is found then `null` MUST be passed instead (that is, not an error). -### `store.findCookies(domain, path, cb(err,cookies))` +#### `store.findCookies(domain, path, allowSpecialUseDomain, callback(err, cookies))` -Locates cookies matching the given domain and path. This is most often called in the context of `cookiejar.getCookies()` above. +Locates cookies matching the given domain and path. This is most often called in the context of [`cookiejar.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies). If no cookies are found, the callback MUST be passed an empty array. -The resulting list will be checked for applicability to the current request according to the RFC (domain-match, path-match, http-only-flag, secure-flag, expiry, etc.), so it's OK to use an optimistic search algorithm when implementing this method. However, the search algorithm used SHOULD try to find cookies that `domainMatch()` the domain and `pathMatch()` the path in order to limit the amount of checking that needs to be done. +The resulting list is checked for applicability to the current request according to the RFC (domain-match, path-match, http-only-flag, secure-flag, expiry, and so on), so it's OK to use an optimistic search algorithm when implementing this method. However, the search algorithm used SHOULD try to find cookies that `domainMatch()` the domain and `pathMatch()` the path in order to limit the amount of checking that needs to be done. -As of version 0.9.12, the `allPaths` option to `cookiejar.getCookies()` above will cause the path here to be `null`. If the path is `null`, path-matching MUST NOT be performed (i.e. domain-matching only). +As of version 0.9.12, the `allPaths` option to `cookiejar.getCookies()` above causes the path here to be `null`. If the path is `null`, path-matching MUST NOT be performed (that is, domain-matching only). -### `store.putCookie(cookie, cb(err))` +#### `store.putCookie(cookie, callback(err))` -Adds a new cookie to the store. The implementation SHOULD replace any existing cookie with the same `.domain`, `.path`, and `.key` properties -- depending on the nature of the implementation, it's possible that between the call to `fetchCookie` and `putCookie` that a duplicate `putCookie` can occur. +Adds a new cookie to the store. The implementation SHOULD replace any existing cookie with the same `.domain`, `.path`, and `.key` properties. Depending on the nature of the implementation, it's possible that between the call to `fetchCookie` and `putCookie` that a duplicate `putCookie` can occur. -The `cookie` object MUST NOT be modified; the caller will have already updated the `.creation` and `.lastAccessed` properties. +The `cookie` object MUST NOT be modified; as the caller has already updated the `.creation` and `.lastAccessed` properties. Pass an error if the cookie cannot be stored. -### `store.updateCookie(oldCookie, newCookie, cb(err))` +#### `store.updateCookie(oldCookie, newCookie, callback(err))` -Update an existing cookie. The implementation MUST update the `.value` for a cookie with the same `domain`, `.path` and `.key`. The implementation SHOULD check that the old value in the store is equivalent to `oldCookie` - how the conflict is resolved is up to the store. +Update an existing cookie. The implementation MUST update the `.value` for a cookie with the same `domain`, `.path`, and `.key`. The implementation SHOULD check that the old value in the store is equivalent to `oldCookie` - how the conflict is resolved is up to the store. -The `.lastAccessed` property will always be different between the two objects (to the precision possible via JavaScript's clock). Both `.creation` and `.creationIndex` are guaranteed to be the same. Stores MAY ignore or defer the `.lastAccessed` change at the cost of affecting how cookies are selected for automatic deletion (e.g., least-recently-used, which is up to the store to implement). +The `.lastAccessed` property is always different between the two objects (to the precision possible via JavaScript's clock). Both `.creation` and `.creationIndex` are guaranteed to be the same. Stores MAY ignore or defer the `.lastAccessed` change at the cost of affecting how cookies are selected for automatic deletion (for example, least-recently-used, which is up to the store to implement). -Stores may wish to optimize changing the `.value` of the cookie in the store versus storing a new cookie. If the implementation doesn't define this method a stub that calls `putCookie(newCookie,cb)` will be added to the store object. +Stores may wish to optimize changing the `.value` of the cookie in the store versus storing a new cookie. If the implementation doesn't define this method, a stub that calls [`putCookie`](#storeputcookiecookie-callbackerr) is added to the store object. The `newCookie` and `oldCookie` objects MUST NOT be modified. Pass an error if the newCookie cannot be stored. -### `store.removeCookie(domain, path, key, cb(err))` +#### `store.removeCookie(domain, path, key, callback(err))` -Remove a cookie from the store (see notes on `findCookie` about the uniqueness constraint). +Remove a cookie from the store (see notes on [`findCookie`](#storefindcookiedomain-path-key-callbackerr-cookie) about the uniqueness constraint). -The implementation MUST NOT pass an error if the cookie doesn't exist; only pass an error due to the failure to remove an existing cookie. +The implementation MUST NOT pass an error if the cookie doesn't exist, and only pass an error due to the failure to remove an existing cookie. -### `store.removeCookies(domain, path, cb(err))` +#### `store.removeCookies(domain, path, callback(err))` -Removes matching cookies from the store. The `path` parameter is optional, and if missing means all paths in a domain should be removed. +Removes matching cookies from the store. The `path` parameter is optional and if missing, means all paths in a domain should be removed. Pass an error ONLY if removing any existing cookies failed. -### `store.removeAllCookies(cb(err))` +#### `store.removeAllCookies(callback(err))` _Optional_. Removes all cookies from the store. Pass an error if one or more cookies can't be removed. -**Note**: New method as of `tough-cookie` version 2.5, so not all Stores will implement this, plus some stores may choose not to implement this. +#### `store.getAllCookies(callback(err, cookies))` -### `store.getAllCookies(cb(err, cookies))` +_Optional_. Produces an `Array` of all cookies during [`jar.serialize()`](#serializecallbackerr-serializedobject). The items in the array can be true `Cookie` objects or generic `Object`s with the [Serialization Format](#serialization-format) data structure. -_Optional_. Produces an `Array` of all cookies during `jar.serialize()`. The items in the array can be true `Cookie` objects or generic `Object`s with the [Serialization Format] data structure. - -Cookies SHOULD be returned in creation order to preserve sorting via `compareCookies()`. For reference, `MemoryCookieStore` will sort by `.creationIndex` since it uses true `Cookie` objects internally. If you don't return the cookies in creation order, they'll still be sorted by creation time, but this only has a precision of 1ms. See `compareCookies` for more detail. +Cookies SHOULD be returned in creation order to preserve sorting via [`compareCookie()`](#cookiecomparea-b). For reference, `MemoryCookieStore` sorts by `.creationIndex` since it uses true `Cookie` objects internally. If you don't return the cookies in creation order, they'll still be sorted by creation time, but this only has a precision of 1-ms. See `cookieCompare` for more detail. Pass an error if retrieval fails. -**Note**: not all Stores can implement this due to technical limitations, so it is optional. +**Note**: Not all Stores can implement this due to technical limitations, so it is optional. -## MemoryCookieStore +### MemoryCookieStore Inherits from `Store`. A just-in-memory CookieJar synchronous store implementation, used by default. Despite being a synchronous implementation, it's usable with both the synchronous and asynchronous forms of the `CookieJar` API. Supports serialization, `getAllCookies`, and `removeAllCookies`. -## Community Cookie Stores +### Community Cookie Stores These are some Store implementations authored and maintained by the community. They aren't official and we don't vouch for them but you may be interested to have a look: @@ -467,10 +480,9 @@ These are some Store implementations authored and maintained by the community. T - [`tough-cookie-filestore`](https://github.com/mitsuru/tough-cookie-filestore): JSON on disk - [`tough-cookie-web-storage-store`](https://github.com/exponentjs/tough-cookie-web-storage-store): DOM localStorage and sessionStorage +## Serialization Format -# Serialization Format - -**NOTE**: if you want to have custom `Cookie` properties serialized, add the property name to `Cookie.serializableProperties`. +**NOTE**: If you want to have custom `Cookie` properties serialized, add the property name to `Cookie.serializableProperties`. ```js { @@ -496,57 +508,59 @@ These are some Store implementations authored and maintained by the community. T } ``` -# RFC6265bis +## RFC 6265bis -Support for RFC6265bis revision 02 is being developed. Since this is a bit of an omnibus revision to the RFC6252, support is broken up into the functional areas. +Support for RFC 6265bis revision 02 is being developed. Since this is a bit of an omnibus revision to the RFC 6252, support is broken up into the functional areas. -## Leave Secure Cookies Alone +### Leave Secure Cookies Alone Not yet supported. This change makes it so that if a cookie is sent from the server to the client with a `Secure` attribute, the channel must also be secure or the cookie is ignored. -## SameSite Cookies +### SameSite Cookies Supported. This change makes it possible for servers, and supporting clients, to mitigate certain types of CSRF attacks by disallowing `SameSite` cookies from being sent cross-origin. -On the Cookie object itself, you can get/set the `.sameSite` attribute, which will be serialized into the `SameSite=` cookie attribute. When unset or `undefined`, no `SameSite=` attribute will be serialized. The valid values of this attribute are `'none'`, `'lax'`, or `'strict'`. Other values will be serialized as-is. +On the Cookie object itself, you can get or set the `.sameSite` attribute, which is serialized into the `SameSite=` cookie attribute. When unset or `undefined`, no `SameSite=` attribute is serialized. The valid values of this attribute are `'none'`, `'lax'`, or `'strict'`. Other values are serialized as-is. -When parsing cookies with a `SameSite` cookie attribute, values other than `'lax'` or `'strict'` are parsed as `'none'`. For example, `SomeCookie=SomeValue; SameSite=garbage` will parse so that `cookie.sameSite === 'none'`. +When parsing cookies with a `SameSite` cookie attribute, values other than `'lax'` or `'strict'` are parsed as `'none'`. For example, `SomeCookie=SomeValue; SameSite=garbage` parses so that `cookie.sameSite === 'none'`. In order to support SameSite cookies, you must provide a `sameSiteContext` option to _both_ `setCookie` and `getCookies`. Valid values for this option are just like for the Cookie object, but have particular meanings: -1. `'strict'` mode - If the request is on the same "site for cookies" (see the RFC draft for what this means), pass this option to add a layer of defense against CSRF. -2. `'lax'` mode - If the request is from another site, _but_ is directly because of navigation by the user, e.g., `` or ``, pass `sameSiteContext: 'lax'`. + +1. `'strict'` mode - If the request is on the same "site for cookies" (see the RFC draft for more information), pass this option to add a layer of defense against CSRF. +2. `'lax'` mode - If the request is from another site, _but_ is directly because of navigation by the user, such as, `` or ``, pass `sameSiteContext: 'lax'`. 3. `'none'` - Otherwise, pass `sameSiteContext: 'none'` (this indicates a cross-origin request). -4. unset/`undefined` - SameSite **will not** be enforced! This can be a valid use-case for when CSRF isn't in the threat model of the system being built. +4. unset/`undefined` - SameSite **is not** be enforced! This can be a valid use-case for when CSRF isn't in the threat model of the system being built. -It is highly recommended that you read RFC 6265bis for fine details on SameSite cookies. In particular [Section 8.8](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-02#section-8.8) discusses security considerations and defense in depth. +It is highly recommended that you read RFC 6265bis for fine details on SameSite cookies. In particular [Section 8.8](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-02##section-8.8) discusses security considerations and defense in depth. -## Cookie Prefixes +### Cookie Prefixes Supported. Cookie prefixes are a way to indicate that a given cookie was set with a set of attributes simply by inspecting the first few characters of the cookie's name. -Cookie prefixes are defined in [Section 4.1.3 of 6265bis](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.3). Two prefixes are defined: +Cookie prefixes are defined in [Section 4.1.3 of 6265bis](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03##section-4.1.3). -1. `"__Secure-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "__Secure-", then the cookie will have been set with a "Secure" attribute. -2. `"__Host-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "__Host-", then the cookie will have been set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute. +Two prefixes are defined: -If `prefixSecurity` is enabled for `CookieJar`, then cookies that match the prefixes defined above but do not obey the attribute restrictions will not be added. +1. `"__Secure-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "\_\_Secure-", then the cookie was set with a "Secure" attribute. +2. `"__Host-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "\_\_Host-", then the cookie was set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute. -You can define this functionality by passing in `prefixSecurity` option to `CookieJar`. It can be one of 3 values: +If `prefixSecurity` is enabled for `CookieJar`, then cookies that match the prefixes defined above but do not obey the attribute restrictions are not added. -1. `silent`: Enable cookie prefix checking but silently fail to add the cookie if conditions not met. Default. -2. `strict`: Enable cookie prefix checking and error out if conditions not met. +You can define this functionality by passing in the `prefixSecurity` option to `CookieJar`. It can be one of 3 values: + +1. `silent`: Enable cookie prefix checking but silently fail to add the cookie if conditions are not met. Default. +2. `strict`: Enable cookie prefix checking and error out if conditions are not met. 3. `unsafe-disabled`: Disable cookie prefix checking. -Note that if `ignoreError` is passed in as `true` then the error will be silent regardless of `prefixSecurity` option (assuming it's enabled). +Note that if `ignoreError` is passed in as `true` then the error is silent regardless of the `prefixSecurity` option (assuming it's enabled). - -# Copyright and License +## Copyright and License BSD-3-Clause: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..8249025 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +## Security + +Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com) +as soon as it is discovered. This library limits its runtime dependencies in +order to reduce the total cost of ownership as much as can be, but all consumers +should remain vigilant and have their security stakeholders review all third-party +products (3PP) like this one and their dependencies. diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index 32cc8b4..0000000 --- a/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -node-tough-cookie (4.0.0-ok1) yangtze; urgency=medium - - * Build for openKylin. - - -- zhouganqing Tue, 21 Feb 2023 10:42:59 +0800 diff --git a/debian/control b/debian/control deleted file mode 100644 index 0f299b0..0000000 --- a/debian/control +++ /dev/null @@ -1,33 +0,0 @@ -Source: node-tough-cookie -Maintainer: OpenKylin Developers -Section: javascript -Testsuite: autopkgtest-pkg-nodejs -Priority: optional -Build-Depends: - debhelper-compat (= 13) - , nodejs - , node-async - , node-psl - , node-punycode - , node-universalify - , node-vows - , dh-sequence-nodejs -Standards-Version: 4.5.1 -Vcs-Browser: https://gitee.com/openkylin/node-tough-cookie -Vcs-Git: https://gitee.com/openkylin/node-tough-cookie.git -Homepage: https://github.com/salesforce/tough-cookie -Rules-Requires-Root: no - -Package: node-tough-cookie -Architecture: all -Depends: - ${misc:Depends} - , node-psl - , node-punycode - , node-universalify -Provides: ${nodejs:Provides} -Description: RFC6265 Cookies and Cookie Jar for node.js - This library just provides a way to read and write RFC6265 HTTP cookie - headers. - . - Node.js is an event-based server-side JavaScript engine. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index d265bc0..0000000 --- a/debian/copyright +++ /dev/null @@ -1,69 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: tough-cookie -Upstream-Contact: https://github.com/salesforce/tough-cookie/issues -Source: https://github.com/salesforce/tough-cookie - https://registry.npmjs.org/@types/tough-cookie -Files-Excluded: lib/pubsuffix.js - -Files: * -Copyright: 2015, Salesforce.com, Inc. - Jeremy Stashewsky -License: BSD-3-Clause - -Files: typestough-cookie/* -Copyright: Microsoft Corporation -License: Expat - -Files: debian/* -Copyright: 2017 Pirate Praveen - 2019, Xavier Guimard -License: BSD-3-Clause - -Files: debian/tests/test_modules/string.prototype.repeat/* -Copyright: Mathias Bynens -License: Expat - -License: BSD-3-Clause - 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 the University 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 HOLDERS 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. - -License: Expat - 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 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. diff --git a/debian/docs b/debian/docs deleted file mode 100644 index b43bf86..0000000 --- a/debian/docs +++ /dev/null @@ -1 +0,0 @@ -README.md diff --git a/debian/nodejs/root_modules b/debian/nodejs/root_modules deleted file mode 100644 index 72e8ffc..0000000 --- a/debian/nodejs/root_modules +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 218df65..0000000 --- a/debian/rules +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -%: - dh $@ diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 89ae9db..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) diff --git a/debian/source/lintian-overrides b/debian/source/lintian-overrides deleted file mode 100644 index 862fdc6..0000000 --- a/debian/source/lintian-overrides +++ /dev/null @@ -1,4 +0,0 @@ -# Data -source-is-missing lib/cookie.js line length is 1188 characters (>512) -source-contains-prebuilt-javascript-object lib/cookie.js line length is 1188 characters (>512) -very-long-line-length-in-source-file lib/cookie.js line length is 1188 characters (>512) diff --git a/debian/tests/pkg-js/files b/debian/tests/pkg-js/files deleted file mode 100644 index 8f04f5f..0000000 --- a/debian/tests/pkg-js/files +++ /dev/null @@ -1,2 +0,0 @@ -debian/tests/test_modules -test diff --git a/debian/tests/pkg-js/test b/debian/tests/pkg-js/test deleted file mode 100644 index 3a7e9b9..0000000 --- a/debian/tests/pkg-js/test +++ /dev/null @@ -1 +0,0 @@ -NODE_PATH=debian/tests/test_modules vows test/*_test.js diff --git a/debian/tests/test_modules/string.prototype.repeat/package.json b/debian/tests/test_modules/string.prototype.repeat/package.json deleted file mode 100644 index ce509b4..0000000 --- a/debian/tests/test_modules/string.prototype.repeat/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "_from": "string.prototype.repeat@^0.2.0", - "_id": "string.prototype.repeat@0.2.0", - "_inBundle": false, - "_integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=", - "_location": "/string.prototype.repeat", - "_phantomChildren": {}, - "_requested": { - "type": "range", - "registry": true, - "raw": "string.prototype.repeat@^0.2.0", - "name": "string.prototype.repeat", - "escapedName": "string.prototype.repeat", - "rawSpec": "^0.2.0", - "saveSpec": null, - "fetchSpec": "^0.2.0" - }, - "_requiredBy": [ - "#DEV:/" - ], - "_resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", - "_shasum": "aba36de08dcee6a5a337d49b2ea1da1b28fc0ecf", - "_spec": "string.prototype.repeat@^0.2.0", - "_where": "/home/xavier/dev/debian/src/pkg-js/packages/node-tough-cookie", - "author": { - "name": "Mathias Bynens", - "url": "http://mathiasbynens.be/" - }, - "bugs": { - "url": "https://github.com/mathiasbynens/String.prototype.repeat/issues" - }, - "bundleDependencies": false, - "deprecated": false, - "description": "A robust & optimized `String.prototype.repeat` polyfill, based on the ECMAScript 6 specification.", - "directories": { - "test": "tests" - }, - "files": [ - "LICENSE-MIT.txt", - "repeat.js" - ], - "homepage": "http://mths.be/repeat", - "keywords": [ - "string", - "repeat", - "es6", - "ecmascript", - "polyfill" - ], - "licenses": [ - { - "type": "MIT", - "url": "http://mths.be/mit" - } - ], - "main": "repeat.js", - "name": "string.prototype.repeat", - "repository": { - "type": "git", - "url": "git+https://github.com/mathiasbynens/String.prototype.repeat.git" - }, - "scripts": { - "cover": "istanbul cover --report html --verbose --dir coverage tests/tests.js", - "test": "node tests/tests.js" - }, - "version": "0.2.0" -} diff --git a/debian/tests/test_modules/string.prototype.repeat/repeat.js b/debian/tests/test_modules/string.prototype.repeat/repeat.js deleted file mode 100644 index 30bf044..0000000 --- a/debian/tests/test_modules/string.prototype.repeat/repeat.js +++ /dev/null @@ -1,50 +0,0 @@ -/*! http://mths.be/repeat v0.2.0 by @mathias */ -if (!String.prototype.repeat) { - (function() { - 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` - var defineProperty = (function() { - // IE 8 only supports `Object.defineProperty` on DOM elements - try { - var object = {}; - var $defineProperty = Object.defineProperty; - var result = $defineProperty(object, object, object) && $defineProperty; - } catch(error) {} - return result; - }()); - var repeat = function(count) { - if (this == null) { - throw TypeError(); - } - var string = String(this); - // `ToInteger` - var n = count ? Number(count) : 0; - if (n != n) { // better `isNaN` - n = 0; - } - // Account for out-of-bounds indices - if (n < 0 || n == Infinity) { - throw RangeError(); - } - var result = ''; - while (n) { - if (n % 2 == 1) { - result += string; - } - if (n > 1) { - string += string; - } - n >>= 1; - } - return result; - }; - if (defineProperty) { - defineProperty(String.prototype, 'repeat', { - 'value': repeat, - 'configurable': true, - 'writable': true - }); - } else { - String.prototype.repeat = repeat; - } - }()); -} diff --git a/debian/watch b/debian/watch deleted file mode 100644 index 1698f00..0000000 --- a/debian/watch +++ /dev/null @@ -1,12 +0,0 @@ -version=4 -opts=\ -repacksuffix=+dfsg,\ -repack,compression=xz,\ -dversionmangle=auto,\ -filenamemangle=s/.*\/v?([\d\.-]+)\.tar\.gz/node-tough-cookie-$1.tar.gz/ \ - https://github.com/salesforce/tough-cookie/tags .*/archive/v?([\d\.]+).tar.gz - -# It is not recommended use npmregistry. Please investigate more. -# Take a look at https://wiki.debian.org/debian/watch/ -opts="searchmode=plain,component=typestough-cookie,pgpmode=none" \ - https://registry.npmjs.org/@types/tough-cookie https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-([\d\.]+)@ARCHIVE_EXT@ ignore diff --git a/lib/cookie.js b/lib/cookie.js index a042893..f90d6a7 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2015, Salesforce.com, Inc. + * Copyright (c) 2015-2020, Salesforce.com, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,15 +29,16 @@ * POSSIBILITY OF SUCH DAMAGE. */ "use strict"; -const punycode = require("punycode"); -const urlParse = require("url").parse; -const util = require("util"); +const punycode = require("punycode/"); +const urlParse = require("url-parse"); const pubsuffix = require("./pubsuffix-psl"); const Store = require("./store").Store; const MemoryCookieStore = require("./memstore").MemoryCookieStore; 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 ";" @@ -79,6 +80,7 @@ const SAME_SITE_CONTEXT_VAL_ERR = 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"'; function checkSameSiteContext(value) { + validators.validate(validators.isNonEmptyString(value), value); const context = String(value).toLowerCase(); if (context === "none" || context === "lax" || context === "strict") { return context; @@ -97,7 +99,23 @@ const PrefixSecurityEnum = Object.freeze({ // * all capturing groups converted to non-capturing -- "(?:)" // * support for IPv6 Scoped Literal ("%eth1") removed // * lowercase hexadecimal only -var 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}|:)| +(?:[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 @@ -301,6 +319,7 @@ function parseDate(str) { } function formatDate(date) { + validators.validate(validators.isDate(date), date); return date.toUTCString(); } @@ -311,6 +330,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); @@ -345,7 +368,7 @@ function domainMatch(str, domStr, canonicalize) { /* " o All of the following [three] conditions hold:" */ /* "* The domain string is a suffix of the string" */ - const idx = str.indexOf(domStr); + const idx = str.lastIndexOf(domStr); if (idx <= 0) { return false; // it's a non-match (-1) or prefix (0) } @@ -359,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 "." } @@ -403,6 +426,7 @@ function defaultPath(path) { } function trimTerminator(str) { + if (validators.isEmptyString(str)) return str; for (let t = 0; t < TERMINATORS.length; t++) { const terminatorIdx = str.indexOf(TERMINATORS[t]); if (terminatorIdx !== -1) { @@ -415,6 +439,7 @@ function trimTerminator(str) { function parseCookiePair(cookiePair, looseMode) { cookiePair = trimTerminator(cookiePair); + validators.validate(validators.isString(cookiePair), cookiePair); let firstEq = cookiePair.indexOf("="); if (looseMode) { @@ -454,6 +479,11 @@ function parse(str, options) { if (!options || typeof options !== "object") { options = {}; } + + if (validators.isEmptyString(str) || !validators.isString(str)) { + return null; + } + str = str.trim(); // We use a regex to parse the "name-value-pair" part of S5.2 @@ -589,11 +619,11 @@ function parse(str, options) { case "lax": c.sameSite = "lax"; break; + case "none": + c.sameSite = "none"; + break; default: - // RFC6265bis-02 S5.3.7 step 1: - // "If cookie-av's attribute-value is not a case-insensitive match - // for "Strict" or "Lax", ignore the "cookie-av"." - // This effectively sets it to 'none' from the prototype. + c.sameSite = undefined; break; } break; @@ -616,6 +646,7 @@ function parse(str, options) { * @returns boolean */ function isSecurePrefixConditionMet(cookie) { + validators.validate(validators.isObject(cookie), cookie); return !cookie.key.startsWith("__Secure-") || cookie.secure; } @@ -631,6 +662,7 @@ function isSecurePrefixConditionMet(cookie) { * @returns boolean */ function isHostPrefixConditionMet(cookie) { + validators.validate(validators.isObject(cookie)); return ( !cookie.key.startsWith("__Host-") || (cookie.secure && @@ -652,7 +684,7 @@ function jsonParse(str) { } function fromJSON(str) { - if (!str) { + if (!str || validators.isEmptyString(str)) { return null; } @@ -698,6 +730,8 @@ function fromJSON(str) { */ function cookieCompare(a, b) { + validators.validate(validators.isObject(a), a); + validators.validate(validators.isObject(b), b); let cmp = 0; // descending for length: b CMP a @@ -725,6 +759,7 @@ function cookieCompare(a, b) { // Gives the permutation of all possible pathMatch()es of a given path. The // array is in longest-to-shortest order. Handy for indexing. function permutePath(path) { + validators.validate(validators.isString(path)); if (path === "/") { return ["/"]; } @@ -772,13 +807,14 @@ const cookieDefaults = { pathIsDefault: null, creation: null, lastAccessed: null, - sameSite: "none" + sameSite: undefined }; class Cookie { constructor(options = {}) { - if (util.inspect.custom) { - this[util.inspect.custom] = this.inspect; + const customInspectSymbol = getCustomInspectSymbol(); + if (customInspectSymbol) { + this[customInspectSymbol] = this.inspect; } Object.assign(this, cookieDefaults, options); @@ -1060,9 +1096,13 @@ class CookieJar { if (typeof options === "boolean") { options = { rejectPublicSuffixes: options }; } + validators.validate(validators.isObject(options), options); this.rejectPublicSuffixes = options.rejectPublicSuffixes; this.enableLooseMode = !!options.looseMode; - this.allowSpecialUseDomain = !!options.allowSpecialUseDomain; + this.allowSpecialUseDomain = + typeof options.allowSpecialUseDomain === "boolean" + ? options.allowSpecialUseDomain + : true; this.store = store || new MemoryCookieStore(); this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity); this._cloneSync = syncWrap("clone"); @@ -1076,13 +1116,31 @@ class CookieJar { } setCookie(cookie, url, options, cb) { + validators.validate(validators.isNonEmptyString(url), cb, options); let err; + + if (validators.isFunction(url)) { + cb = url; + return cb(new Error("No URL was specified")); + } + const context = getCookieContext(url); - if (typeof options === "function") { + if (validators.isFunction(options)) { cb = options; options = {}; } + validators.validate(validators.isFunction(cb), cb); + + if ( + !validators.isNonEmptyString(cookie) && + !validators.isObject(cookie) && + cookie instanceof String && + cookie.length == 0 + ) { + return cb(null); + } + const host = canonicalDomain(context.hostname); const loose = options.loose || this.enableLooseMode; @@ -1119,8 +1177,11 @@ class CookieJar { // S5.3 step 5: public suffixes if (this.rejectPublicSuffixes && cookie.domain) { - const suffix = pubsuffix.getPublicSuffix(cookie.cdomain()); - if (suffix == null) { + 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"); return cb(options.ignoreError ? null : err); @@ -1163,7 +1224,11 @@ class CookieJar { } // 6252bis-02 S5.4 Step 13 & 14: - if (cookie.sameSite !== "none" && sameSiteContext) { + if ( + cookie.sameSite !== "none" && + cookie.sameSite !== undefined && + sameSiteContext + ) { // "If the cookie's "same-site-flag" is not "None", and the cookie // is being set from a context whose "site for cookies" is not an // exact match for request-uri's host's registered domain, then @@ -1249,11 +1314,14 @@ class CookieJar { // RFC6365 S5.4 getCookies(url, options, cb) { + validators.validate(validators.isNonEmptyString(url), cb, url); const context = getCookieContext(url); - if (typeof options === "function") { + if (validators.isFunction(options)) { cb = options; options = {}; } + validators.validate(validators.isObject(options), cb, options); + validators.validate(validators.isFunction(cb), cb); const host = canonicalDomain(context.hostname); const path = context.pathname || "/"; @@ -1369,6 +1437,7 @@ class CookieJar { getCookieString(...args) { const cb = args.pop(); + validators.validate(validators.isFunction(cb), cb); const next = function(err, cookies) { if (err) { cb(err); @@ -1388,6 +1457,7 @@ class CookieJar { getSetCookieStrings(...args) { const cb = args.pop(); + validators.validate(validators.isFunction(cb), cb); const next = function(err, cookies) { if (err) { cb(err); @@ -1405,8 +1475,9 @@ class CookieJar { } serialize(cb) { + validators.validate(validators.isFunction(cb), cb); let type = this.store.constructor.name; - if (type === "Object") { + if (validators.isObject(type)) { type = null; } @@ -1422,6 +1493,9 @@ class CookieJar { // CookieJar configuration: rejectPublicSuffixes: !!this.rejectPublicSuffixes, + enableLooseMode: !!this.enableLooseMode, + allowSpecialUseDomain: !!this.allowSpecialUseDomain, + prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity), // this gets filled from getAllCookies: cookies: [] @@ -1524,6 +1598,7 @@ class CookieJar { } removeAllCookies(cb) { + validators.validate(validators.isFunction(cb), cb); const store = this.store; // Check that the store implements its own removeAllCookies(). The default @@ -1577,6 +1652,7 @@ class CookieJar { cb = store; store = null; } + validators.validate(validators.isFunction(cb), cb); let serialized; if (typeof strOrObj === "string") { @@ -1588,7 +1664,12 @@ class CookieJar { serialized = strOrObj; } - const jar = new CookieJar(store, serialized.rejectPublicSuffixes); + const jar = new CookieJar(store, { + rejectPublicSuffixes: serialized.rejectPublicSuffixes, + looseMode: serialized.enableLooseMode, + allowSpecialUseDomain: serialized.allowSpecialUseDomain, + prefixSecurity: serialized.prefixSecurity + }); jar._importCookies(serialized, err => { if (err) { return cb(err); @@ -1600,7 +1681,10 @@ class CookieJar { static deserializeSync(strOrObj, store) { const serialized = typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj; - const jar = new CookieJar(store, serialized.rejectPublicSuffixes); + const jar = new CookieJar(store, { + rejectPublicSuffixes: serialized.rejectPublicSuffixes, + looseMode: serialized.enableLooseMode + }); // catch this mistake early: if (!jar.store.synchronous) { @@ -1669,3 +1753,4 @@ exports.permuteDomain = require("./permuteDomain").permuteDomain; exports.permutePath = permutePath; exports.canonicalDomain = canonicalDomain; exports.PrefixSecurityEnum = PrefixSecurityEnum; +exports.ParameterError = validators.ParameterError; diff --git a/lib/memstore.js b/lib/memstore.js index 912eead..f313bbf 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 { getCustomInspectSymbol, getUtilInspect } = require("./utilHelper"); class MemoryCookieStore extends Store { constructor() { super(); this.synchronous = true; - this.idx = {}; - if (util.inspect.custom) { - this[util.inspect.custom] = this.inspect; + this.idx = Object.create(null); + const customInspectSymbol = getCustomInspectSymbol(); + if (customInspectSymbol) { + this[customInspectSymbol] = this.inspect; } } inspect() { + const util = { inspect: getUtilInspect(inspectFallback) }; return `{ idx: ${util.inspect(this.idx, false, 2)} }`; } @@ -62,7 +64,7 @@ class MemoryCookieStore extends Store { const results = []; if (typeof allowSpecialUseDomain === "function") { cb = allowSpecialUseDomain; - allowSpecialUseDomain = false; + allowSpecialUseDomain = true; } if (!domain) { return cb(null, []); @@ -109,10 +111,10 @@ class MemoryCookieStore extends Store { putCookie(cookie, cb) { if (!this.idx[cookie.domain]) { - this.idx[cookie.domain] = {}; + this.idx[cookie.domain] = Object.create(null); } if (!this.idx[cookie.domain][cookie.path]) { - this.idx[cookie.domain][cookie.path] = {}; + this.idx[cookie.domain][cookie.path] = Object.create(null); } this.idx[cookie.domain][cookie.path][cookie.key] = cookie; cb(null); @@ -144,7 +146,7 @@ class MemoryCookieStore extends Store { return cb(null); } removeAllCookies(cb) { - this.idx = {}; + this.idx = Object.create(null); return cb(null); } getAllCookies(cb) { @@ -184,7 +186,57 @@ class MemoryCookieStore extends Store { "removeAllCookies", "getAllCookies" ].forEach(name => { - MemoryCookieStore[name] = fromCallback(MemoryCookieStore.prototype[name]); + MemoryCookieStore.prototype[name] = fromCallback( + MemoryCookieStore.prototype[name] + ); }); exports.MemoryCookieStore = MemoryCookieStore; + +function inspectFallback(val) { + const domains = Object.keys(val); + if (domains.length === 0) { + return "[Object: null prototype] {}"; + } + let result = "[Object: null prototype] {\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}': [Object: null prototype] {\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}': [Object: null prototype] {\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/permuteDomain.js b/lib/permuteDomain.js index 78e6cad..7553124 100644 --- a/lib/permuteDomain.js +++ b/lib/permuteDomain.js @@ -33,21 +33,11 @@ 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. -const SPECIAL_USE_DOMAINS = ["local"]; // RFC 6761 + function permuteDomain(domain, allowSpecialUseDomain) { - let pubSuf = null; - if (allowSpecialUseDomain) { - const domainParts = domain.split("."); - if (SPECIAL_USE_DOMAINS.includes(domainParts[domainParts.length - 1])) { - pubSuf = `${domainParts[domainParts.length - 2]}.${ - domainParts[domainParts.length - 1] - }`; - } else { - pubSuf = pubsuffix.getPublicSuffix(domain); - } - } else { - pubSuf = pubsuffix.getPublicSuffix(domain); - } + const pubSuf = pubsuffix.getPublicSuffix(domain, { + allowSpecialUseDomain: allowSpecialUseDomain + }); if (!pubSuf) { return null; @@ -56,6 +46,11 @@ function permuteDomain(domain, allowSpecialUseDomain) { return [domain]; } + // Nuke trailing dot + if (domain.slice(-1) == ".") { + domain = domain.slice(0, -1); + } + const prefix = domain.slice(0, -(pubSuf.length + 1)); // ".example.com" const parts = prefix.split(".").reverse(); let cur = pubSuf; diff --git a/lib/pubsuffix-psl.js b/lib/pubsuffix-psl.js index 93a8577..b664934 100644 --- a/lib/pubsuffix-psl.js +++ b/lib/pubsuffix-psl.js @@ -31,7 +31,42 @@ "use strict"; const psl = require("psl"); -function getPublicSuffix(domain) { +// RFC 6761 +const SPECIAL_USE_DOMAINS = [ + "local", + "example", + "invalid", + "localhost", + "test" +]; + +const SPECIAL_TREATMENT_DOMAINS = ["localhost", "invalid"]; + +function getPublicSuffix(domain, options = {}) { + const domainParts = domain.split("."); + const topLevelDomain = domainParts[domainParts.length - 1]; + const allowSpecialUseDomain = !!options.allowSpecialUseDomain; + const ignoreError = !!options.ignoreError; + + if (allowSpecialUseDomain && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { + if (domainParts.length > 1) { + const secondLevelDomain = domainParts[domainParts.length - 2]; + // In aforementioned example, the eTLD/pubSuf will be apple.localhost + return `${secondLevelDomain}.${topLevelDomain}`; + } else if (SPECIAL_TREATMENT_DOMAINS.includes(topLevelDomain)) { + // For a single word special use domain, e.g. 'localhost' or 'invalid', per RFC 6761, + // "Application software MAY recognize {localhost/invalid} names as special, or + // MAY pass them to name resolution APIs as they would for other domain names." + return `${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/lib/utilHelper.js b/lib/utilHelper.js new file mode 100644 index 0000000..feac125 --- /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/lib/validators.js b/lib/validators.js new file mode 100644 index 0000000..8558164 --- /dev/null +++ b/lib/validators.js @@ -0,0 +1,95 @@ +/* ************************************************************************************ +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 +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"; +} + +function isNonEmptyString(data) { + return isString(data) && data !== ""; +} + +function isDate(data) { + return isInstanceStrict(data, Date) && isInteger(data.getTime()); +} + +function isEmptyString(data) { + return data === "" || (data instanceof String && data.toString() === ""); +} + +function isString(data) { + return typeof data === "string" || data instanceof String; +} + +function isObject(data) { + return toString.call(data) === "[object Object]"; +} +function isInstanceStrict(data, prototype) { + try { + return data instanceof prototype; + } catch (error) { + return false; + } +} + +function isInteger(data) { + return typeof data === "number" && data % 1 === 0; +} +/* End validation functions */ + +function validate(bool, cb, options) { + if (!isFunction(cb)) { + options = cb; + cb = null; + } + if (!isObject(options)) options = { Error: "Failed Check" }; + if (!bool) { + if (cb) { + cb(new ParameterError(options)); + } else { + throw new ParameterError(options); + } + } +} + +class ParameterError extends Error { + constructor(...params) { + super(...params); + } +} + +exports.ParameterError = ParameterError; +exports.isFunction = isFunction; +exports.isNonEmptyString = isNonEmptyString; +exports.isDate = isDate; +exports.isEmptyString = isEmptyString; +exports.isString = isString; +exports.isObject = isObject; +exports.validate = validate; diff --git a/lib/version.js b/lib/version.js index e52f25b..a39164c 100644 --- a/lib/version.js +++ b/lib/version.js @@ -1,2 +1,2 @@ // generated by genversion -module.exports = '4.0.0' +module.exports = '4.1.3' diff --git a/package.json b/package.json index e07dcb7..d75db6b 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "RFC6265", "RFC2965" ], - "version": "4.0.0", + "version": "4.1.3", "homepage": "https://github.com/salesforce/tough-cookie", "repository": { "type": "git", @@ -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}'", @@ -104,6 +104,7 @@ "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", - "universalify": "^0.1.2" + "universalify": "^0.2.0", + "url-parse": "^1.5.3" } } diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 0000000..e4149bb --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-restricted-modules": "off" + } +} diff --git a/test/api_test.js b/test/api_test.js index 461f429..73fd3ed 100644 --- a/test/api_test.js +++ b/test/api_test.js @@ -579,4 +579,170 @@ vows } } }) + .addBatch(allowSpecialUseOptionVows()) .export(module); + +function allowSpecialUseOptionVows() { + const specialUseDomains = [ + "local", + "example", + "invalid", + "localhost", + "test" + ]; + + const specialTreatmentDomains = ["localhost", "invalid"]; + + return specialUseDomains.reduce((vows, specialUseDomain) => { + if (specialTreatmentDomains.includes(specialUseDomain)) { + vows[ + `cookie jar with allowSpecialUseDomain set to the default value and domain is "${specialUseDomain}"` + ] = { + topic: function() { + const cb = this.callback; + const cj = new CookieJar(); + cj.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${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://${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 set to the default value and domain is "dev.${specialUseDomain}"` + ] = { + topic: function() { + const cb = this.callback; + const cj = new CookieJar(); + 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 enabled and domain is "dev.${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 "dev.${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; + }, {}); +} diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index 9a5d382..50205bc 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -193,6 +193,94 @@ 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()); + } + }, + "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({ @@ -543,6 +631,19 @@ vows assert.strictEqual(cookies[0].key, ""); assert.strictEqual(cookies[0].value, "FooBar"); } + }, + "Loose Mode Cloned": { + topic: function() { + const cj = new CookieJar(null, { looseMode: true }); + return CookieJar.fromJSON(cj.toJSON()); + }, + "parses loose cookies from serialized cookie jar": function(cj) { + cj.setCookieSync("FooBar", "http://www.foonet.net", {}); + const cookies = cj.getCookiesSync("http://www.foonet.net"); + assert.strictEqual(cookies.length, 1); + assert.strictEqual(cookies[0].key, ""); + assert.strictEqual(cookies[0].value, "FooBar"); + } } }) .addBatch({ @@ -649,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); @@ -669,4 +773,65 @@ vows } } }) + .addBatch({ + "Issue #145 - Missing parameter validation on setCookie function causes TypeError": { + "with missing parameters": { + topic: function() { + const jar = new tough.CookieJar(); + jar.setCookie( + new String("x=y; Domain=example.com; Path=/"), + this.callback + ); + }, + "results in a error being returned because of missing parameters": function( + err, + cookies + ) { + assert(err != null); + assert(err instanceof tough.ParameterError); + } + } + } + }) + .addBatch({ + "Issue #197 - CookieJar().setCookie crashes when empty cookie is passed": { + "with missing parameters": { + topic: function() { + const jar = new tough.CookieJar(); + jar.setCookie("", "https://google.com", this.callback); + }, + "results in a error being returned because of missing parameters": function( + err, + cookies + ) { + assert(cookies == undefined); + } + } + } + }) + .addBatch({ + "Issue #282 - Prototype pollution": { + "when setting a cookie with the domain __proto__": { + topic: function() { + const jar = new tough.CookieJar(undefined, { + rejectPublicSuffixes: false + }); + // try to pollute the prototype + jar.setCookieSync( + "Slonser=polluted; Domain=__proto__; Path=/notauth", + "https://__proto__/admin" + ); + jar.setCookieSync( + "Auth=Lol; Domain=google.com; Path=/notauth", + "https://google.com/" + ); + this.callback(); + }, + "results in a cookie that is not affected by the attempted prototype pollution": function() { + const pollutedObject = {}; + assert(pollutedObject["/notauth"] === undefined); + } + } + } + }) .export(module); diff --git a/test/domain_and_path_test.js b/test/domain_and_path_test.js index 4e2ce09..cfedfd7 100644 --- a/test/domain_and_path_test.js +++ b/test/domain_and_path_test.js @@ -33,7 +33,6 @@ const vows = require("vows"); const assert = require("assert"); const tough = require("../lib/cookie"); -const Cookie = tough.Cookie; function matchVows(func, table) { const theVows = {}; @@ -50,15 +49,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 +74,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({ @@ -105,6 +104,9 @@ vows ["www.aaaa.com", "aaa.com", false], ["www.aaa.com", "aaa.com", true], ["www.aexample.com", "example.com", false], // has to match on "." boundary + ["computer.com", "com", true], // suffix string found at start of domain + ["becoming.com", "com", true], // suffix string found in middle of domain + ["sitcom.com", "com", true], // suffix string found just before the '.' boundary // S5.1.3 "The string is a host name (i.e., not an IP address)" ["192.168.0.1", "168.0.1", false], // because str is an IP (v4) @@ -142,12 +144,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", "/"], @@ -194,6 +196,16 @@ vows "foo.bar.example.localduhmain" ]); } + }, + "trailing dot": { + topic: tough.permuteDomain.bind(null, "foo.bar.example.com."), + "got three things": function(list) { + assert.deepEqual(list, [ + "example.com", + "bar.example.com", + "foo.bar.example.com" + ]); + } } }, permutePath: { diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js new file mode 100644 index 0000000..d8b859a --- /dev/null +++ b/test/node_util_fallback_test.js @@ -0,0 +1,175 @@ +/*! + * Copyright (c) 2022, 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 inspectFallback = require("../lib/memstore").inspectFallback; +const { getCustomInspectSymbol, getUtilInspect } = require("../lib/utilHelper"); +const Cookie = tough.Cookie; +const CookieJar = tough.CookieJar; + +function resetAgeFields(str) { + return str.replace(/\d+ms/g, "0ms"); +} + +vows + .describe("Node util module fallback for non-node environments") + .addBatch({ + 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 (< node v10.12.0)": function() { + assert.equal( + getCustomInspectSymbol({ + lookupCustomInspectSymbol: () => null + }), + Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom + ); + }, + "should be null in a non-node environment since 'util' features cannot be relied on": function() { + assert.equal( + getCustomInspectSymbol({ + lookupCustomInspectSymbol: () => null, + requireUtil: () => null + }), + null + ); + } + }, + 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)) + ); + } + }, + "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; + }, + "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( + `${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 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)) + ); + } + } + } + }) + .export(module); diff --git a/test/parsing_test.js b/test/parsing_test.js index 09cff69..2db4813 100644 --- a/test/parsing_test.js +++ b/test/parsing_test.js @@ -158,8 +158,8 @@ vows "has max-age": function(c) { assert.equal(c.maxAge, 1234); }, - "has same-site 'none'": function(c) { - assert.equal(c.sameSite, "none"); + "has same-site 'undefined'": function(c) { + assert.equal(c.sameSite, undefined); }, "has extensions": function(c) { assert.ok(c.extensions); @@ -677,20 +677,80 @@ vows assert.equal(c.extensions, null); } }, - absent: { + none: { + topic: function() { + return Cookie.parse("abc=xyz; SameSite=NoNe") || null; + }, + parsed: function(c) { + assert.ok(c); + }, + "is none (lowercased)": function(c) { + assert.equal(c.sameSite, "none"); + }, + "no extensions": function(c) { + assert.equal(c.extensions, null); + } + }, + bad: { topic: function() { return Cookie.parse("abc=xyzzy; SameSite=example.com") || null; }, parsed: function(c) { assert.ok(c); }, - "is set to 'none' (by prototype)": function(c) { - assert.equal(c.sameSite, "none"); + "is set to 'undefined'": function(c) { + assert.equal(c.sameSite, undefined); + }, + "no extensions": function(c) { + assert.equal(c.extensions, null); + } + }, + absent: { + topic: function() { + return Cookie.parse("abc=xyzzy;") || null; + }, + parsed: function(c) { + assert.ok(c); + }, + "is set to 'undefined'": function(c) { + assert.equal(c.sameSite, undefined); }, "no extensions": function(c) { assert.equal(c.extensions, null); } } + }, + "empty string": { + topic: function() { + return Cookie.parse(""); + }, + "is empty": function(c) { + assert.isNull(c); + } + }, + "missing string": { + topic: function() { + return Cookie.parse(); + }, + "is empty": function(c) { + assert.isNull(c); + } + }, + "some string object": { + topic: function() { + return Cookie.parse(new String("")); + }, + "is empty": function(c) { + assert.isNull(c, null); + } + }, + "some empty string object": { + topic: function() { + return Cookie.parse(new String()); + }, + "is empty": function(c) { + assert.isNull(c, null); + } } }) .export(module); diff --git a/test/regression_test.js b/test/regression_test.js index 6bb4839..f9ff15b 100644 --- a/test/regression_test.js +++ b/test/regression_test.js @@ -36,6 +36,7 @@ const async = require("async"); const tough = require("../lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; +const MemoryCookieStore = tough.MemoryCookieStore; const atNow = Date.now(); @@ -188,4 +189,96 @@ vows } } }) + .addBatch( + { + "setCookie with localhost (GH-215)": { + topic: function() { + const cookieJar = new CookieJar(); + return cookieJar.setCookieSync( + "a=b; Domain=localhost", + "http://localhost" + ); // Users are free to use localhost names as they would any other domain names. [RFC 6761, Sec. 6.3.1] + }, + works: function(err, c) { + assert.instanceOf(c, Cookie); + assert.match(c, /Domain=localhost/); + } + } + }, + { + "setCookie with localhost (localhost. domain) (GH-215)": { + topic: function() { + const cookieJar = new CookieJar(); + return cookieJar.setCookieSync( + "a=b; Domain=localhost.", + "http://localhost." + ); // Users are free to use localhost names as they would any other domain names. [RFC 6761, Sec. 6.3.1] + }, + works: function(err, c) { + assert.instanceOf(c, Cookie); + assert.match(c, /Domain=localhost/); + } + } + }, + { + "setCookie with localhost (GH-215) (null domain)": { + topic: function() { + const cookieJar = new CookieJar(); + return cookieJar.setCookieSync("a=b; Domain=", "http://localhost"); + }, + 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); + } + } + }, + { + "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); + } + } + } + ) + .addBatch({ + MemoryCookieStore: { + topic: new MemoryCookieStore(), + "has no static methods": function() { + assert.deepEqual(Object.keys(MemoryCookieStore), []); + }, + "has instance methods that return promises": function(store) { + assert.instanceOf(store.findCookie("example.com", "/", "key"), Promise); + assert.instanceOf(store.findCookies("example.com", "/"), Promise); + assert.instanceOf(store.putCookie({}), Promise); + assert.instanceOf(store.updateCookie({}, {}), Promise); + assert.instanceOf( + store.removeCookie("example.com", "/", "key"), + Promise + ); + assert.instanceOf(store.removeCookies("example.com", "/"), Promise); + assert.instanceOf(store.removeAllCookies(), Promise); + assert.instanceOf(store.getAllCookies(), Promise); + } + } + }) .export(module); diff --git a/test/same_site_test.js b/test/same_site_test.js index c43b3f4..e7230fc 100644 --- a/test/same_site_test.js +++ b/test/same_site_test.js @@ -124,9 +124,9 @@ vows topic: function(options) { this.callSetCookie("garbage", options, this.callback); }, - "treated as 'none'": function(err, cookie) { + "treated as 'undefined'": function(err, cookie) { assert.isNull(err); - assert.equal(cookie.sameSite, "none"); + assert.equal(cookie.sameSite, undefined); } }, "for strict cookie": { @@ -151,9 +151,9 @@ vows topic: function(options) { this.callSetCookie("normal", options, this.callback); }, - "treated as 'none'": function(err, cookie) { + "treated as 'undefined'": function(err, cookie) { assert.isNull(err); - assert.equal(cookie.sameSite, "none"); + assert.equal(cookie.sameSite, undefined); } } }, diff --git a/typestough-cookie/LICENSE b/typestough-cookie/LICENSE old mode 100644 new mode 100755 index 4b1ad51..9e841e7 --- a/typestough-cookie/LICENSE +++ b/typestough-cookie/LICENSE @@ -1,21 +1,21 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 - 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 + MIT License + + Copyright (c) Microsoft Corporation. + + 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 + 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 diff --git a/typestough-cookie/README.md b/typestough-cookie/README.md old mode 100644 new mode 100755 index c512af8..e8337b7 --- a/typestough-cookie/README.md +++ b/typestough-cookie/README.md @@ -8,7 +8,7 @@ This package contains type definitions for tough-cookie (https://github.com/sale Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/tough-cookie. ### Additional Details - * Last updated: Tue, 31 Mar 2020 23:35:07 GMT + * Last updated: Thu, 14 Apr 2022 07:01:24 GMT * Dependencies: none * Global values: none diff --git a/typestough-cookie/index.d.ts b/typestough-cookie/index.d.ts old mode 100644 new mode 100755 index d7bed2f..effd1a8 --- a/typestough-cookie/index.d.ts +++ b/typestough-cookie/index.d.ts @@ -132,26 +132,26 @@ export class Cookie { export namespace Cookie { interface ParseOptions { - loose?: boolean; + loose?: boolean | undefined; } interface Properties { - key?: string; - value?: string; - expires?: Date; - maxAge?: number | 'Infinity' | '-Infinity'; - domain?: string; - path?: string; - secure?: boolean; - httpOnly?: boolean; - extensions?: string[]; - creation?: Date; - creationIndex?: number; + key?: string | undefined; + value?: string | undefined; + expires?: Date | 'Infinity' | undefined; + maxAge?: number | 'Infinity' | '-Infinity' | undefined; + domain?: string | undefined; + path?: string | undefined; + secure?: boolean | undefined; + httpOnly?: boolean | undefined; + extensions?: string[] | undefined; + creation?: Date | undefined; + creationIndex?: number | undefined; - hostOnly?: boolean; - pathIsDefault?: boolean; - lastAccessed?: Date; - sameSite?: string; + hostOnly?: boolean | undefined; + pathIsDefault?: boolean | undefined; + lastAccessed?: Date | undefined; + sameSite?: string | undefined; } interface Serialized { @@ -215,25 +215,25 @@ export class CookieJar { export namespace CookieJar { interface Options { - allowSpecialUseDomain?: boolean; - looseMode?: boolean; - rejectPublicSuffixes?: boolean; - prefixSecurity?: string; + allowSpecialUseDomain?: boolean | undefined; + looseMode?: boolean | undefined; + rejectPublicSuffixes?: boolean | undefined; + prefixSecurity?: string | undefined; } interface SetCookieOptions { - http?: boolean; - secure?: boolean; - now?: Date; - ignoreError?: boolean; + http?: boolean | undefined; + secure?: boolean | undefined; + now?: Date | undefined; + ignoreError?: boolean | undefined; } interface GetCookiesOptions { - http?: boolean; - secure?: boolean; - now?: Date; - expire?: boolean; - allPaths?: boolean; + http?: boolean | undefined; + secure?: boolean | undefined; + now?: Date | undefined; + expire?: boolean | undefined; + allPaths?: boolean | undefined; } interface Serialized { @@ -262,4 +262,26 @@ export abstract class Store { getAllCookies(cb: (err: Error | null, cookie: Cookie[]) => void): void; } -export class MemoryCookieStore extends Store { } +export class MemoryCookieStore extends Store { + findCookie(domain: string, path: string, key: string, cb: (err: Error | null, cookie: Cookie | null) => void): void; + findCookie(domain: string, path: string, key: string): Promise; + + findCookies(domain: string, path: string, allowSpecialUseDomain: boolean, cb: (err: Error | null, cookie: Cookie[]) => void): void; + findCookies(domain: string, path: string, cb: (err: Error | null, cookie: Cookie[]) => void): void; + findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise; + + putCookie(cookie: Cookie, cb: (err: Error | null) => void): void; + putCookie(cookie: Cookie): Promise; + + updateCookie(oldCookie: Cookie, newCookie: Cookie, cb: (err: Error | null) => void): void; + updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise; + + removeCookie(domain: string, path: string, key: string, cb: (err: Error | null) => void): void; + removeCookie(domain: string, path: string, key: string): Promise; + + removeCookies(domain: string, path: string, cb: (err: Error | null) => void): void; + removeCookies(domain: string, path: string): Promise; + + getAllCookies(cb: (err: Error | null, cookie: Cookie[]) => void): void; + getAllCookies(): Promise; +} diff --git a/typestough-cookie/package.json b/typestough-cookie/package.json old mode 100644 new mode 100755 index d06939b..a1ce419 --- a/typestough-cookie/package.json +++ b/typestough-cookie/package.json @@ -1,7 +1,8 @@ { "name": "@types/tough-cookie", - "version": "4.0.0", + "version": "4.0.2", "description": "TypeScript definitions for tough-cookie", + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/tough-cookie", "license": "MIT", "contributors": [ { @@ -29,6 +30,6 @@ }, "scripts": {}, "dependencies": {}, - "typesPublisherContentHash": "ca0d31f2e9fc2a883ed38f569a7c21cb47c3ad66089c59faa840a59b14c0daf5", - "typeScriptVersion": "2.8" + "typesPublisherContentHash": "b7a67b8b87baf3d5d1b8d6f2b4493dad84d9ec7e93abf68824da9da25ac176d6", + "typeScriptVersion": "3.9" } \ No newline at end of file