pax_global_header00006660000000000000000000000064135502432100014505gustar00rootroot0000000000000052 comment=2b72d42150b02969bde3ef189d397891cf520ef9 cookies-0.8.0/000077500000000000000000000000001355024321000131465ustar00rootroot00000000000000cookies-0.8.0/.editorconfig000066400000000000000000000002631355024321000156240ustar00rootroot00000000000000# http://editorconfig.org root = true [*] charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true [{*.js,*.json,*.yml}] indent_size = 2 indent_style = space cookies-0.8.0/.eslintignore000066400000000000000000000000421355024321000156450ustar00rootroot00000000000000.nyc_output coverage node_modules cookies-0.8.0/.eslintrc.yml000066400000000000000000000003421355024321000155710ustar00rootroot00000000000000root: true rules: eol-last: error indent: ["error", 2, { "SwitchCase": 1 }] no-param-reassign: error no-trailing-spaces: error no-unused-vars: ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }] cookies-0.8.0/.gitignore000066400000000000000000000000661355024321000151400ustar00rootroot00000000000000.nyc_output/ coverage/ node_modules package-lock.json cookies-0.8.0/.travis.yml000066400000000000000000000062621355024321000152650ustar00rootroot00000000000000language: node_js node_js: - "0.8" - "0.10" - "0.12" - "1.8" - "2.5" - "3.3" - "4.9" - "5.12" - "6.17" - "7.10" - "8.16" - "9.11" - "10.16" - "11.15" - "12.11" sudo: false cache: directories: - node_modules before_install: # Configure npm - | # Skip updating shrinkwrap / lock npm config set shrinkwrap false # Setup Node.js version-specific dependencies - | # mocha for testing # - use 2.x for Node.js < 0.10 # - use 3.x for Node.js < 4 # - use 5.x for Node.js < 6 if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -eq 0 && "$(cut -d. -f2 <<< "$TRAVIS_NODE_VERSION")" -lt 10 ]]; then npm install --save-dev mocha@2.5.3 elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 4 ]]; then npm install --save-dev mocha@3.5.3 elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then npm install --save-dev mocha@5.2.0 fi - | # nyc for coverage # - remove on Node.js < 0.10 # - use 10.x for Node.js < 4 # - use 11.x for Node.js < 6 if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -eq 0 && "$(cut -d. -f2 <<< "$TRAVIS_NODE_VERSION")" -lt 10 ]]; then npm rm --silent --save-dev nyc elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 4 ]]; then npm install --save-dev nyc@10.3.2 elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then npm install --save-dev nyc@11.9.0 fi - | # supertest for http calls # - use 1.1.0 for Node.js < 0.10 # - use 2.0.0 for Node.js < 4 # - use 3.4.2 for Node.js < 6 if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -eq 0 && "$(cut -d. -f2 <<< "$TRAVIS_NODE_VERSION")" -lt 10 ]]; then npm install --save-dev supertest@1.1.0 elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 4 ]]; then npm install --save-dev supertest@2.0.0 elif [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 6 ]]; then npm install --save-dev supertest@3.4.2 fi - | # express framework # - remove on Node.js < 0.10 if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -eq 0 && "$(cut -d. -f2 <<< "$TRAVIS_NODE_VERSION")" -lt 10 ]]; then npm rm --silent --save-dev express fi - | # restify framework # - remove on Node.js < 8 if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 8 ]]; then npm rm --silent --save-dev restify fi - | # eslint for linting # - remove on Node.js < 4 if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -lt 4 ]]; then node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ grep -E '^eslint(-|$)' | \ xargs npm rm --save-dev fi # Update Node.js modules - | # Prune and rebuild node_modules if [[ -d node_modules ]]; then npm prune npm rebuild fi script: # Run test script - | if npm -ps ls nyc | grep -q nyc; then npm run test-ci else npm test fi # Run linting - | if npm -ps ls eslint | grep -q eslint; then npm run lint fi after_script: - | # Upload coverage to coveralls if [[ -d .nyc_output ]]; then npm install --save-dev coveralls@2 nyc report --reporter=text-lcov | coveralls fi cookies-0.8.0/HISTORY.md000066400000000000000000000052131355024321000146320ustar00rootroot000000000000000.8.0 / 2019-10-11 ================== * Fix check for default `secure` option behavior * Fix `maxAge` option preventing cookie deletion * Support `"none"` in `sameSite` option * deps: depd@~2.0.0 - Replace internal `eval` usage with `Function` constructor - Use instance methods on `process` to check for listeners * deps: keygrip@~1.1.0 - Use `tsscmp` module for timing-safe signature verification 0.7.3 / 2018-11-04 ================== * deps: keygrip@~1.0.3 - perf: enable strict mode 0.7.2 / 2018-09-09 ================== * deps: depd@~1.1.2 * perf: remove argument reassignment 0.7.1 / 2017-08-26 ================== * deps: depd@~1.1.1 - Remove unnecessary `Buffer` loading * deps: keygrip@~1.0.2 - perf: improve comparison speed 0.7.0 / 2017-02-19 ================== * Add `sameSite` option for SameSite cookie support * pref: enable strict mode 0.6.2 / 2016-11-12 ================== * Fix `keys` deprecation message * deps: keygrip@~1.0.1 0.6.1 / 2016-02-29 ================== * Fix regression in 0.6.0 for array of strings in `keys` option 0.6.0 / 2016-02-29 ================== * Add `secure` constructor option for secure connection checking * Change constructor to signature `new Cookies(req, res, [options])` - Replace `new Cookies(req, res, key)` with `new Cookies(req, res, {'keys': keys})` * Change prototype construction for proper "constructor" property * Deprecate `secureProxy` option in `.set`; use `secure` option instead - If `secure: true` throws even over SSL, use the `secure` constructor option 0.5.1 / 2014-07-27 ================== * Throw on invalid values provided to `Cookie` constructor - This is not strict validation, but basic RFC 7230 validation 0.5.0 / 2014-07-27 ================== * Integrate with `req.protocol` for secure cookies * Support `maxAge` as well as `maxage` 0.4.1 / 2014-05-07 ================== * Update package for repo move 0.4.0 / 2014-01-31 ================== * Allow passing an array of strings as keys 0.3.8-0.2.0 =========== * TODO: write down history for these releases 0.1.6 / 2011-03-01 ================== * SSL cookies secure by default * Use httpOnly by default unless explicitly false 0.1.5 / 2011-02-26 ================== * Delete sig cookie if signed cookie is deleted 0.1.4 / 2011-02-26 ================== * Always set path 0.1.3 / 2011-02-26 ================== * Add sensible defaults for path 0.1.2 / 2011-02-26 ================== * Inherit cookie properties to signature cookie 0.1.1 / 2011-02-25 ================== * Readme updates 0.1.0 / 2011-02-25 ================== * Initial release cookies-0.8.0/LICENSE000066400000000000000000000022161355024321000141540ustar00rootroot00000000000000(The MIT License) Copyright (c) 2014 Jed Schmidt, http://jed.is/ Copyright (c) 2015-2016 Douglas Christopher Wilson 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. cookies-0.8.0/README.md000066400000000000000000000210001355024321000144160ustar00rootroot00000000000000Cookies ======= [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Node.js Version][node-version-image]][node-version-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] Cookies is a [node.js](http://nodejs.org/) module for getting and setting HTTP(S) cookies. Cookies can be signed to prevent tampering, using [Keygrip](https://www.npmjs.com/package/keygrip). It can be used with the built-in node.js HTTP library, or as Connect/Express middleware. ## Install This is a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/). Installation is done using the [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): ``` $ npm install cookies ``` ## Features * **Lazy**: Since cookie verification against multiple keys could be expensive, cookies are only verified lazily when accessed, not eagerly on each request. * **Secure**: All cookies are `httponly` by default, and cookies sent over SSL are `secure` by default. An error will be thrown if you try to send secure cookies over an insecure socket. * **Unobtrusive**: Signed cookies are stored the same way as unsigned cookies, instead of in an obfuscated signing format. An additional signature cookie is stored for each signed cookie, using a standard naming convention (_cookie-name_`.sig`). This allows other libraries to access the original cookies without having to know the signing mechanism. * **Agnostic**: This library is optimized for use with [Keygrip](https://www.npmjs.com/package/keygrip), but does not require it; you can implement your own signing scheme instead if you like and use this library only to read/write cookies. Factoring the signing into a separate library encourages code reuse and allows you to use the same signing library for other areas where signing is needed, such as in URLs. ## API ### cookies = new Cookies( request, response, [ options ] ) This creates a cookie jar corresponding to the current _request_ and _response_, additionally passing an object _options_. A [Keygrip](https://www.npmjs.com/package/keygrip) object or an array of keys can optionally be passed as _options.keys_ to enable cryptographic signing based on SHA1 HMAC, using rotated credentials. A Boolean can optionally be passed as _options.secure_ to explicitally specify if the connection is secure, rather than this module examining _request_. Note that since this only saves parameters without any other processing, it is very lightweight. Cookies are only parsed on demand when they are accessed. ### express.createServer( Cookies.express( keys ) ) This adds cookie support as a Connect middleware layer for use in Express apps, allowing inbound cookies to be read using `req.cookies.get` and outbound cookies to be set using `res.cookies.set`. ### cookies.get( name, [ options ] ) This extracts the cookie with the given name from the `Cookie` header in the request. If such a cookie exists, its value is returned. Otherwise, nothing is returned. `{ signed: true }` can optionally be passed as the second parameter _options_. In this case, a signature cookie (a cookie of same name ending with the `.sig` suffix appended) is fetched. If no such cookie exists, nothing is returned. If the signature cookie _does_ exist, the provided [Keygrip](https://www.npmjs.com/package/keygrip) object is used to check whether the hash of _cookie-name_=_cookie-value_ matches that of any registered key: * If the signature cookie hash matches the first key, the original cookie value is returned. * If the signature cookie hash matches any other key, the original cookie value is returned AND an outbound header is set to update the signature cookie's value to the hash of the first key. This enables automatic freshening of signature cookies that have become stale due to key rotation. * If the signature cookie hash does not match any key, nothing is returned, and an outbound header with an expired date is used to delete the cookie. ### cookies.set( name, [ value ], [ options ] ) This sets the given cookie in the response and returns the current context to allow chaining. If the _value_ is omitted, an outbound header with an expired date is used to delete the cookie. If the _options_ object is provided, it will be used to generate the outbound cookie header as follows: * `maxAge`: a number representing the milliseconds from `Date.now()` for expiry * `expires`: a `Date` object indicating the cookie's expiration date (expires at the end of session by default). * `path`: a string indicating the path of the cookie (`/` by default). * `domain`: a string indicating the domain of the cookie (no default). * `secure`: a boolean indicating whether the cookie is only to be sent over HTTPS (`false` by default for HTTP, `true` by default for HTTPS). [Read more about this option below](#secure-cookies). * `httpOnly`: a boolean indicating whether the cookie is only to be sent over HTTP(S), and not made available to client JavaScript (`true` by default). * `sameSite`: a boolean or string indicating whether the cookie is a "same site" cookie (`false` by default). This can be set to `'strict'`, `'lax'`, or `true` (which maps to `'strict'`). * `signed`: a boolean indicating whether the cookie is to be signed (`false` by default). If this is true, another cookie of the same name with the `.sig` suffix appended will also be sent, with a 27-byte url-safe base64 SHA1 value representing the hash of _cookie-name_=_cookie-value_ against the first [Keygrip](https://www.npmjs.com/package/keygrip) key. This signature key is used to detect tampering the next time a cookie is received. * `overwrite`: a boolean indicating whether to overwrite previously set cookies of the same name (`false` by default). If this is true, all cookies set during the same request with the same name (regardless of path or domain) are filtered out of the Set-Cookie header when setting this cookie. ### Secure cookies To send a secure cookie, you set a cookie with the `secure: true` option. HTTPS is necessary for secure cookies. When `cookies.set` is called with `secure: true` and a secure connection is not detected, the cookie will not be set and an error will be thrown. This module will test each request to see if it's secure by checking: * if the `protocol` property of the request is set to `https`, or * if the `connection.encrypted` property of the request is set to `true`. If your server is running behind a proxy and you are using `secure: true`, you need to configure your server to read the request headers added by your proxy to determine whether the request is using a secure connection. For more information about working behind proxies, consult the framework you are using: * For Koa - [`app.proxy = true`](http://koajs.com/#settings) * For Express - [trust proxy setting](http://expressjs.com/en/4x/api.html#trust.proxy.options.table) If your Koa or Express server is properly configured, the `protocol` property of the request will be set to match the protocol reported by the proxy in the `X-Forwarded-Proto` header. ## Example ```js var http = require('http') var Cookies = require('cookies') // Optionally define keys to sign cookie values // to prevent client tampering var keys = ['keyboard cat'] var server = http.createServer(function (req, res) { // Create a cookies object var cookies = new Cookies(req, res, { keys: keys }) // Get a cookie var lastVisit = cookies.get('LastVisit', { signed: true }) // Set the cookie to a value cookies.set('LastVisit', new Date().toISOString(), { signed: true }) if (!lastVisit) { res.setHeader('Content-Type', 'text/plain') res.end('Welcome, first time visitor!') } else { res.setHeader('Content-Type', 'text/plain') res.end('Welcome back! Nothing much changed since your last visit at ' + lastVisit + '.') } }) server.listen(3000, function () { console.log('Visit us at http://127.0.0.1:3000/ !') }) ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/cookies.svg [npm-url]: https://npmjs.org/package/cookies [coveralls-image]: https://img.shields.io/coveralls/pillarjs/cookies/master.svg [coveralls-url]: https://coveralls.io/r/pillarjs/cookies?branch=master [downloads-image]: https://img.shields.io/npm/dm/cookies.svg [downloads-url]: https://npmjs.org/package/cookies [node-version-image]: https://img.shields.io/node/v/cookies.svg [node-version-url]: https://nodejs.org/en/download/ [travis-image]: https://img.shields.io/travis/pillarjs/cookies/master.svg [travis-url]: https://travis-ci.org/pillarjs/cookies cookies-0.8.0/index.js000066400000000000000000000137651355024321000146270ustar00rootroot00000000000000/*! * cookies * Copyright(c) 2014 Jed Schmidt, http://jed.is/ * Copyright(c) 2015-2016 Douglas Christopher Wilson * MIT Licensed */ 'use strict' var deprecate = require('depd')('cookies') var Keygrip = require('keygrip') var http = require('http') var cache = {} /** * RegExp to match field-content in RFC 7230 sec 3.2 * * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] * field-vchar = VCHAR / obs-text * obs-text = %x80-FF */ var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; /** * RegExp to match Same-Site cookie attribute value. */ var SAME_SITE_REGEXP = /^(?:lax|none|strict)$/i function Cookies(request, response, options) { if (!(this instanceof Cookies)) return new Cookies(request, response, options) this.secure = undefined this.request = request this.response = response if (options) { if (Array.isArray(options)) { // array of key strings deprecate('"keys" argument; provide using options {"keys": [...]}') this.keys = new Keygrip(options) } else if (options.constructor && options.constructor.name === 'Keygrip') { // any keygrip constructor to allow different versions deprecate('"keys" argument; provide using options {"keys": keygrip}') this.keys = options } else { this.keys = Array.isArray(options.keys) ? new Keygrip(options.keys) : options.keys this.secure = options.secure } } } Cookies.prototype.get = function(name, opts) { var sigName = name + ".sig" , header, match, value, remote, data, index , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys header = this.request.headers["cookie"] if (!header) return match = header.match(getPattern(name)) if (!match) return value = match[1] if (!opts || !signed) return value remote = this.get(sigName) if (!remote) return data = name + "=" + value if (!this.keys) throw new Error('.keys required for signed cookies'); index = this.keys.index(data, remote) if (index < 0) { this.set(sigName, null, {path: "/", signed: false }) } else { index && this.set(sigName, this.keys.sign(data), { signed: false }) return value } }; Cookies.prototype.set = function(name, value, opts) { var res = this.response , req = this.request , headers = res.getHeader("Set-Cookie") || [] , secure = this.secure !== undefined ? !!this.secure : req.protocol === 'https' || req.connection.encrypted , cookie = new Cookie(name, value, opts) , signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys if (typeof headers == "string") headers = [headers] if (!secure && opts && opts.secure) { throw new Error('Cannot send secure cookie over unencrypted connection') } cookie.secure = opts && opts.secure !== undefined ? opts.secure : secure if (opts && "secureProxy" in opts) { deprecate('"secureProxy" option; use "secure" option, provide "secure" to constructor if needed') cookie.secure = opts.secureProxy } pushCookie(headers, cookie) if (opts && signed) { if (!this.keys) throw new Error('.keys required for signed cookies'); cookie.value = this.keys.sign(cookie.toString()) cookie.name += ".sig" pushCookie(headers, cookie) } var setHeader = res.set ? http.OutgoingMessage.prototype.setHeader : res.setHeader setHeader.call(res, 'Set-Cookie', headers) return this }; function Cookie(name, value, attrs) { if (!fieldContentRegExp.test(name)) { throw new TypeError('argument name is invalid'); } if (value && !fieldContentRegExp.test(value)) { throw new TypeError('argument value is invalid'); } this.name = name this.value = value || "" for (var name in attrs) { this[name] = attrs[name] } if (!this.value) { this.expires = new Date(0) this.maxAge = null } if (this.path && !fieldContentRegExp.test(this.path)) { throw new TypeError('option path is invalid'); } if (this.domain && !fieldContentRegExp.test(this.domain)) { throw new TypeError('option domain is invalid'); } if (this.sameSite && this.sameSite !== true && !SAME_SITE_REGEXP.test(this.sameSite)) { throw new TypeError('option sameSite is invalid') } } Cookie.prototype.path = "/"; Cookie.prototype.expires = undefined; Cookie.prototype.domain = undefined; Cookie.prototype.httpOnly = true; Cookie.prototype.sameSite = false; Cookie.prototype.secure = false; Cookie.prototype.overwrite = false; Cookie.prototype.toString = function() { return this.name + "=" + this.value }; Cookie.prototype.toHeader = function() { var header = this.toString() if (this.maxAge) this.expires = new Date(Date.now() + this.maxAge); if (this.path ) header += "; path=" + this.path if (this.expires ) header += "; expires=" + this.expires.toUTCString() if (this.domain ) header += "; domain=" + this.domain if (this.sameSite ) header += "; samesite=" + (this.sameSite === true ? 'strict' : this.sameSite.toLowerCase()) if (this.secure ) header += "; secure" if (this.httpOnly ) header += "; httponly" return header }; // back-compat so maxage mirrors maxAge Object.defineProperty(Cookie.prototype, 'maxage', { configurable: true, enumerable: true, get: function () { return this.maxAge }, set: function (val) { return this.maxAge = val } }); deprecate.property(Cookie.prototype, 'maxage', '"maxage"; use "maxAge" instead') function getPattern(name) { if (cache[name]) return cache[name] return cache[name] = new RegExp( "(?:^|;) *" + name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "=([^;]*)" ) } function pushCookie(headers, cookie) { if (cookie.overwrite) { for (var i = headers.length - 1; i >= 0; i--) { if (headers[i].indexOf(cookie.name + '=') === 0) { headers.splice(i, 1) } } } headers.push(cookie.toHeader()) } Cookies.connect = Cookies.express = function(keys) { return function(req, res, next) { req.cookies = res.cookies = new Cookies(req, res, { keys: keys }) next() } } Cookies.Cookie = Cookie module.exports = Cookies cookies-0.8.0/package.json000066400000000000000000000016231355024321000154360ustar00rootroot00000000000000{ "name": "cookies", "description": "Cookies, optionally signed using Keygrip.", "version": "0.8.0", "author": "Jed Schmidt (http://jed.is)", "contributors": [ "Douglas Christopher Wilson " ], "license": "MIT", "repository": "pillarjs/cookies", "dependencies": { "depd": "~2.0.0", "keygrip": "~1.1.0" }, "devDependencies": { "eslint": "4.19.1", "express": "4.17.1", "mocha": "6.2.1", "nyc": "14.1.1", "restify": "8.4.0", "supertest": "4.0.2" }, "files": [ "HISTORY.md", "LICENSE", "README.md", "index.js" ], "engines": { "node": ">= 0.8" }, "scripts": { "lint": "eslint .", "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/", "test-ci": "nyc --reporter=text npm test", "test-cov": "nyc --reporter=html --reporter=text npm test" } } cookies-0.8.0/test/000077500000000000000000000000001355024321000141255ustar00rootroot00000000000000cookies-0.8.0/test/cookie.js000066400000000000000000000103401355024321000157320ustar00rootroot00000000000000 var assert = require('assert') var cookies = require('..') describe('new Cookie(name, value, [options])', function () { it('should have correct constructor', function () { var cookie = new cookies.Cookie('foo', 'bar') assert.equal(cookie.constructor, cookies.Cookie) }) it('should throw on invalid name', function () { assert.throws(function () { new cookies.Cookie('foo\n', 'bar') }, /argument name is invalid/) }) it('should throw on invalid value', function () { assert.throws(function () { new cookies.Cookie('foo', 'bar\n') }, /argument value is invalid/) }) it('should throw on invalid path', function () { assert.throws(function () { new cookies.Cookie('foo', 'bar', { path: '/\n' }) }, /option path is invalid/) }) it('should throw on invalid domain', function () { assert.throws(function () { new cookies.Cookie('foo', 'bar', { domain: 'example.com\n' }) }, /option domain is invalid/) }) describe('options', function () { describe('maxage', function () { it('should set the .maxAge property', function () { var cookie = new cookies.Cookie('foo', 'bar', { maxage: 86400 }) assert.equal(cookie.maxAge, 86400) }) it('should set the .maxage property', function () { var cookie = new cookies.Cookie('foo', 'bar', { maxage: 86400 }) assert.equal(cookie.maxage, 86400) }) }) describe('maxAge', function () { it('should set the .maxAge property', function () { var cookie = new cookies.Cookie('foo', 'bar', { maxAge: 86400 }) assert.equal(cookie.maxAge, 86400) }) it('should set the .maxage property', function () { var cookie = new cookies.Cookie('foo', 'bar', { maxAge: 86400 }) assert.equal(cookie.maxage, 86400) }) }) describe('sameSite', function () { it('should set the .sameSite property', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: true }) assert.equal(cookie.sameSite, true) }) it('should default to false', function () { var cookie = new cookies.Cookie('foo', 'bar') assert.equal(cookie.sameSite, false) }) it('should throw on invalid value', function () { assert.throws(function () { new cookies.Cookie('foo', 'bar', { sameSite: 'foo' }) }, /option sameSite is invalid/) }) describe('when set to "false"', function () { it('should not set "samesite" attribute in header', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: false }) assert.equal(cookie.toHeader(), 'foo=bar; path=/; httponly') }) }) describe('when set to "true"', function () { it('should set "samesite=strict" attribute in header', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: true }) assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=strict; httponly') }) }) describe('when set to "lax"', function () { it('should set "samesite=lax" attribute in header', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: 'lax' }) assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=lax; httponly') }) }) describe('when set to "none"', function () { it('should set "samesite=none" attribute in header', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: 'none' }) assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=none; httponly') }) }) describe('when set to "strict"', function () { it('should set "samesite=strict" attribute in header', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: 'strict' }) assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=strict; httponly') }) }) describe('when set to "STRICT"', function () { it('should set "samesite=strict" attribute in header', function () { var cookie = new cookies.Cookie('foo', 'bar', { sameSite: 'STRICT' }) assert.equal(cookie.toHeader(), 'foo=bar; path=/; samesite=strict; httponly') }) }) }) }) }) cookies-0.8.0/test/express.js000066400000000000000000000144421355024321000161610ustar00rootroot00000000000000 var assert = require( "assert" ) , keys = require( "keygrip" )(['a', 'b']) , cookies = require( "../" ).express , request = require('supertest') var express = tryRequire('express') var describeExpress = express ? describe : describe.skip describeExpress('Express', function () { it('should set a cookie on the response', function (done) { var app = express() app.set('env', 'test') app.use(cookies()) app.get('/', function (req, res) { res.cookies.set('foo', 'bar') res.end() }) request(app) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true } ])) .expect(200, done) }) it('should get a cookie from the request', function (done) { var app = express() app.set('env', 'test') app.use(cookies()) app.get('/', function (req, res) { res.json({ foo: String(res.cookies.get('foo')) }) }) request(app) .get('/') .set('cookie', 'foo=bar') .expect(200, { foo: 'bar' }, done) }) describe('with multiple cookies', function () { it('should set all cookies on the response', function (done) { var app = express() app.set('env', 'test') app.use(cookies()) app.get('/', function (req, res) { res.cookies.set('foo', 'bar') res.cookies.set('fizz', 'buzz') res.end() }) request(app) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true }, { name: 'fizz', value: 'buzz', path: '/', httponly: true } ])) .expect(200, done) }) it('should get each cookie from the request', function (done) { var app = express() app.set('env', 'test') app.use(cookies()) app.get('/', function (req, res) { res.json({ fizz: String(res.cookies.get('fizz')), foo: String(res.cookies.get('foo')) }) }) request(app) .get('/') .set('cookie', 'foo=bar; fizz=buzz') .expect(200, { foo: 'bar', fizz: 'buzz' }, done) }) }) describe('when "overwrite: false"', function () { it('should set second cookie with same name', function (done) { var app = express() app.set('env', 'test') app.use(cookies()) app.get('/', function (req, res) { res.cookies.set('foo', 'bar') res.cookies.set('foo', 'fizz', { overwrite: false }) res.end() }) request(app) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true }, { name: 'foo', value: 'fizz', path: '/', httponly: true } ])) .expect(200, done) }) }) describe('when "overwrite: true"', function () { it('should replace previously set value', function (done) { var app = express() app.set('env', 'test') app.use(cookies()) app.get('/', function (req, res, next) { res.cookies.set('foo', 'bar') res.cookies.set('foo', 'fizz', { overwrite: true }) res.end() }) request(app) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'fizz', path: '/', httponly: true } ])) .expect(200, done) }) it('should set signature correctly', function (done) { var app = express() app.set('env', 'test') app.use(cookies(keys)) app.get('/', function (req, res, next) { res.cookies.set('foo', 'bar') res.cookies.set('foo', 'fizz', { overwrite: true }) res.end() }) request(app) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'fizz', path: '/', httponly: true }, { name: 'foo.sig', value: 'hVIYdxZSelh3gIK5wQxzrqoIndU', path: '/', httponly: true } ])) .expect(200, done) }) }) describe('when "secure: true"', function () { it('should not set when not secure', function (done) { var app = express() app.set('env', 'test') app.use(cookies(keys)) app.use(function (req, res) { res.cookies.set('foo', 'bar', {secure: true}) res.end() }) request(app) .get('/') .expect(500, /Cannot send secure cookie over unencrypted connection/, done) }) it('should set for secure connection', function (done) { var app = express() app.set('env', 'test') app.use(cookies(keys)) app.use(function (req, res, next) { res.connection.encrypted = true next() }) app.use(function (req, res) { res.cookies.set('foo', 'bar', {secure: true}) res.end() }) request(app) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true, secure: true }, { name: 'foo.sig', value: 'p5QVCZeqNBulWOhYipO0jqjrzz4', path: '/', httponly: true, secure: true } ])) .expect(200, done) }) it('should set for proxy settings', function (done) { var app = express() app.set('env', 'test') app.set('trust proxy', true) app.use(cookies(keys)) app.use(function (req, res) { res.cookies.set('foo', 'bar', {secure: true}) res.end() }) request(app) .get('/') .set('X-Forwarded-Proto', 'https') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true, secure: true }, { name: 'foo.sig', value: 'p5QVCZeqNBulWOhYipO0jqjrzz4', path: '/', httponly: true, secure: true } ])) .expect(200, done) }) }) }) function getCookies (res) { var setCookies = res.headers['set-cookie'] || [] return setCookies.map(parseSetCookie) } function parseSetCookie (header) { var match var pairs = [] var pattern = /\s*([^=;]+)(?:=([^;]*);?|;|$)/g while ((match = pattern.exec(header))) { pairs.push({ name: match[1], value: match[2] }) } var cookie = pairs.shift() for (var i = 0; i < pairs.length; i++) { match = pairs[i] cookie[match.name.toLowerCase()] = (match.value || true) } return cookie } function shouldSetCookies (expected) { return function (res) { assert.deepEqual(getCookies(res), expected) } } function tryRequire (name) { try { return require(name) } catch (e) { return undefined } } cookies-0.8.0/test/fixtures/000077500000000000000000000000001355024321000157765ustar00rootroot00000000000000cookies-0.8.0/test/fixtures/server.crt000066400000000000000000000017651355024321000200270ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICwDCCAaigAwIBAgIJAM6yBH2RuaRYMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV BAMTCWxvY2FsaG9zdDAgFw0xODExMDQyMzMyMDBaGA8yMDY4MTAyMjIzMzIwMFow FDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAurkioeD9fi3tDt80CNr0sWxQHrZaugzjIAV91194m1fslhD4PaxYOn1A rPS1ntx7KnBU4drf1e/0WOuv7UJki84ic4mzmIQaZGvTyJsop9EpOc+1NNli6BVe hU/zd/5Qp9JCjQmHt+fTbEwxdAEL77f1OdHRnyDo7VexYjReCFuFaQm/Yp60BOmi i5mCBuof6rBwmxD/lTQUoQOG3APwna7udBxC7FUSvKVv1tY6QzhrSedRkDW6J3Gr ylDzG+1qmEfNudWEU+WFlL0syDfrxWM4kdaOotYdeo2SztrDzRwwnZ6DtyqH/Pcp jlUGZS5thb0G0tJcjQoDHoybmPTnRwIDAQABoxMwETAPBgNVHREECDAGhwR/AAAB MA0GCSqGSIb3DQEBCwUAA4IBAQAwzoLRl2U1i3AsBE7wgOWzxY81XmrN1ODJdFOl a9x0DH2K1v5vueU+1CWYgCRKhNVPUHXT6FWFwG0WkvjvLBiSfD/CeyYDN3DSRovg AhOQfnQh3Owdm/D0vDb3LqWBdcIoFAWPAarkxClohMfqd3+8XojEIeMm+mE6dwHc mP6kxKTtUcuE8OSOc/re4r1VEeX9MzPkHF2HAzWMsXF56HS5fMRdfyZuIlpTavwi R9IEM74l58d0LSC2Qa5oK+McBE7wX/xCjdiuQNoapq4WcG9vX2VuJnehv6a+iBLG 06PP7sLHbPq0mHJKmnZfZWFl+fnYEhSSV1OycoICiSAzUQ/k -----END CERTIFICATE----- cookies-0.8.0/test/fixtures/server.key000066400000000000000000000032501355024321000200160ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6uSKh4P1+Le0O 3zQI2vSxbFAetlq6DOMgBX3XX3ibV+yWEPg9rFg6fUCs9LWe3HsqcFTh2t/V7/RY 66/tQmSLziJzibOYhBpka9PImyin0Sk5z7U02WLoFV6FT/N3/lCn0kKNCYe359Ns TDF0AQvvt/U50dGfIOjtV7FiNF4IW4VpCb9inrQE6aKLmYIG6h/qsHCbEP+VNBSh A4bcA/Cdru50HELsVRK8pW/W1jpDOGtJ51GQNboncavKUPMb7WqYR8251YRT5YWU vSzIN+vFYziR1o6i1h16jZLO2sPNHDCdnoO3Kof89ymOVQZlLm2FvQbS0lyNCgMe jJuY9OdHAgMBAAECggEAQC3uR3HL75jdiGUTv49y16EBEO1g1d4kGxeIH4UDMXHR Met3R8t7L+9pUYly+72Q4A9oPZE7qo8lj4LDH2vYn20kzk2gW2XcpHOjgapDGRt9 bg+Emzu1EUx3Bp9qce4JzwUoNs31xjJ6qxitTtAlSCoUseD6ihWHujyQDc8uGvSz zkGNQxDdEC5JMDMNWp/0syDPqqNynUcGbvanwJP5MqJwVDRfRA087iZcwlqMQW0X C0EJQ1h5xulJz327lRjSzgAxmYlVOMrQRMhAniiN90Y4FNxMDNWb+RsxQMeGo4fv zUDbT9s/Dk6u29Ts982Yd973M6uW4ukMvltN0AHHQQKBgQDiHzA5W5QKAXQvpuRN T19QKRxuoCRIm2VvXgd4ZSJoMi8+gshmj0+H440wMXa1pWbFIHsbUn7ua1V4NzIP 5STnInswXfOjezSDBe6BtCe6Fr1PPR8MAcqvS/hrHPaKOUX4m4b6Okff0JYAHbr0 AXTVP6hUujAV1EBud6T5XcOikQKBgQDTZT7spKr46vD3iTg+z/vrkILcXpR3JMra YVDZ8hl+tSVT0EVvB9lp5cNSWBQkCj6+i+KUhJBwUxa3Kx3aP81bIQfIl9TkKdt6 nlkkVZ+6Zp3rBfpwC+BNwwmAkRWTqhUM1OveH6S7YnpscKCEkr7XmobOqONAwtWh kacDLfIoVwKBgEJz3/w7SZpXKwoGBfoiZWRtcImiTod4A6ti+tcLAb2VYgUA8lwR qdHJseiD4NspLLaqAQPajqsKqCeYMQIy2VGD9KgWNE/LGXeX+qvrgfFSVXhAAivJ KwOxU+RGsr2Ub1fMfTJ0hkLkTfDiy9qBwxAYkSO1RARmSDkuuDEAuUnBAoGBAJdx AuE5HprwhOxwy3CEQ2+AuZ7xyt6H5yMHcIqSXB1f3HvsyrE+KE0rIwCMxPEEvep4 ADxxs6AkhjN2mg5Ogul0AkV8MDG8otV3N1lGVgWNmjhSshUvDOPog5gtWA3PXQEy UD9y3+q2JAonrXcMQdfnhgfrCdLNQYpb9A/dDIxFAoGAaeMsz3naEEDUFvPEUV63 Bc/dnbD/SavZBZ8oldkw75HdnNqtaYEauvW4uaQ9oYQpkSQ+X07eBdduiBvfkh8m Uhv4M/K2u78EJz0ie+ez6wKNI5oAKzY5KtXioKQ1Mu+DZgYU9m06B6HSI2rSzHiT 5viM6XkrV2KXrNFOH6/z+ak= -----END PRIVATE KEY----- cookies-0.8.0/test/restify.js000066400000000000000000000111441355024321000161510ustar00rootroot00000000000000var assert = require('assert') , keys = require('keygrip')(['a', 'b']) , Cookies = require('../') , request = require('supertest') var restify = tryRequire('restify') var describeRestify = restify ? describe : describe.skip describeRestify('Restify', function () { it('should set a cookie on the response', function (done) { var server = restify.createServer() server.get('/', function (req, res) { var cookies = new Cookies(req, res) cookies.set('foo', 'bar') res.send(200) }) request(server) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true } ])) .expect(200, done) }) it('should get a cookie from the request', function (done) { var server = restify.createServer() server.get('/', function (req, res) { var cookies = new Cookies(req, res) res.send({ foo: String(cookies.get('foo')) }) }) request(server) .get('/') .set('cookie', 'foo=bar') .expect(200, { foo: 'bar' }, done) }) describe('with multiple cookies', function () { it('should set all cookies on the response', function (done) { var server = restify.createServer() server.get('/', function (req, res) { var cookies = new Cookies(req, res) cookies.set('foo', 'bar') cookies.set('fizz', 'buzz') res.send(200) }) request(server) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true }, { name: 'fizz', value: 'buzz', path: '/', httponly: true } ])) .expect(200, done) }) it('should get each cookie from the request', function (done) { var server = restify.createServer() server.get('/', function (req, res) { var cookies = new Cookies(req, res) res.send({ fizz: String(cookies.get('fizz')), foo: String(cookies.get('foo')) }) }) request(server) .get('/') .set('cookie', 'foo=bar; fizz=buzz') .expect(200, { foo: 'bar', fizz: 'buzz' }, done) }) }) describe('when "overwrite: false"', function () { it('should set second cookie with same name', function (done) { var server = restify.createServer() server.get('/', function (req, res) { var cookies = new Cookies(req, res) cookies.set('foo', 'bar') cookies.set('foo', 'fizz', { overwrite: false }) res.send(200) }) request(server) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'bar', path: '/', httponly: true }, { name: 'foo', value: 'fizz', path: '/', httponly: true } ])) .expect(200, done) }) }) describe('when "overwrite: true"', function () { it('should replace previously set value', function (done) { var server = restify.createServer() server.get('/', function (req, res) { var cookies = new Cookies(req, res) cookies.set('foo', 'bar') cookies.set('foo', 'fizz', { overwrite: true }) res.send(200) }) request(server) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'fizz', path: '/', httponly: true } ])) .expect(200, done) }) it('should set signature correctly', function (done) { var server = restify.createServer() server.get('/', function (req, res) { var cookies = new Cookies(req, res, keys) cookies.set('foo', 'bar') cookies.set('foo', 'fizz', { overwrite: true }) res.send(200) }) request(server) .get('/') .expect(shouldSetCookies([ { name: 'foo', value: 'fizz', path: '/', httponly: true }, { name: 'foo.sig', value: 'hVIYdxZSelh3gIK5wQxzrqoIndU', path: '/', httponly: true } ])) .expect(200, done) }) }) }) function getCookies (res) { var setCookies = res.headers['set-cookie'] || [] return setCookies.map(parseSetCookie) } function parseSetCookie (header) { var match var pairs = [] var pattern = /\s*([^=;]+)(?:=([^;]*);?|;|$)/g while ((match = pattern.exec(header))) { pairs.push({ name: match[1], value: match[2] }) } var cookie = pairs.shift() for (var i = 0; i < pairs.length; i++) { match = pairs[i] cookie[match.name.toLowerCase()] = (match.value || true) } return cookie } function shouldSetCookies (expected) { return function (res) { assert.deepEqual(getCookies(res), expected) } } function tryRequire (name) { try { return require(name) } catch (e) { return undefined } } cookies-0.8.0/test/support/000077500000000000000000000000001355024321000156415ustar00rootroot00000000000000cookies-0.8.0/test/support/env.js000066400000000000000000000001051355024321000167630ustar00rootroot00000000000000process.env.NODE_ENV = 'test' process.env.NO_DEPRECATION = 'cookies' cookies-0.8.0/test/test.js000066400000000000000000000605651355024321000154560ustar00rootroot00000000000000 var assert = require('assert') var Cookies = require('..') var fs = require('fs') var http = require('http') var https = require('https') var Keygrip = require('keygrip') var path = require('path') var request = require('supertest') describe('new Cookies(req, res, [options])', function () { it('should create new cookies instance', function (done) { assertServer(done, function (req, res) { var cookies = new Cookies(req, res) assert.ok(cookies) assert.strictEqual(cookies.constructor, Cookies) assert.strictEqual(cookies.request, req) assert.strictEqual(cookies.response, res) assert.strictEqual(cookies.keys, undefined) }) }) describe('options', function () { it('should accept array of keys', function (done) { assertServer(done, function (req, res) { var cookies = new Cookies(req, res, ['keyboard cat']) assert.strictEqual(typeof cookies.keys, 'object') assert.strictEqual(cookies.keys.sign('foo=bar'), 'iW2fuCIzk9Cg_rqLT1CAqrtdWs8') }) }) it('should accept Keygrip instance', function (done) { assertServer(done, function (req, res) { var keys = new Keygrip(['keyboard cat']) var cookies = new Cookies(req, res, keys) assert.strictEqual(typeof cookies.keys, 'object') assert.strictEqual(cookies.keys.sign('foo=bar'), 'iW2fuCIzk9Cg_rqLT1CAqrtdWs8') }) }) describe('.keys', function () { it('should accept array of keys', function (done) { assertServer(done, function (req, res) { var cookies = new Cookies(req, res, { keys: ['keyboard cat'] }) assert.strictEqual(typeof cookies.keys, 'object') assert.strictEqual(cookies.keys.sign('foo=bar'), 'iW2fuCIzk9Cg_rqLT1CAqrtdWs8') }) }) it('should accept Keygrip instance', function (done) { assertServer(done, function (req, res) { var keys = new Keygrip(['keyboard cat']) var cookies = new Cookies(req, res, { keys: keys }) assert.strictEqual(typeof cookies.keys, 'object') assert.strictEqual(cookies.keys.sign('foo=bar'), 'iW2fuCIzk9Cg_rqLT1CAqrtdWs8') }) }) }) describe('.secure', function () { it('should default to undefined', function (done) { assertServer(done, function (req, res) { var cookies = new Cookies(req, res) assert.strictEqual(cookies.secure, undefined) }) }) it('should set secure flag', function (done) { assertServer(done, function (req, res) { var cookies = new Cookies(req, res, { secure: true }) assert.strictEqual(cookies.secure, true) }) }) }) }) describe('.get(name, [options])', function () { it('should return value of cookie', function (done) { request(createServer(getCookieHandler('foo'))) .get('/') .set('Cookie', 'foo=bar') .expect(200, 'bar', done) }) it('should work for cookie name with special characters', function (done) { request(createServer(getCookieHandler('foo*(#bar)?.|$'))) .get('/') .set('Cookie', 'foo*(#bar)?.|$=buzz') .expect(200, 'buzz', done) }) it('should return undefined without cookie', function (done) { request(createServer(getCookieHandler('fizz'))) .get('/') .set('Cookie', 'foo=bar') .expect(200, 'undefined', done) }) it('should return undefined without header', function (done) { request(createServer(getCookieHandler('foo'))) .get('/') .expect(200, 'undefined', done) }) describe('"signed" option', function () { describe('when true', function () { it('should throw without .keys', function (done) { request(createServer(getCookieHandler('foo', { signed: true }))) .get('/') .set('Cookie', 'foo=bar; foo.sig=iW2fuCIzk9Cg_rqLT1CAqrtdWs8') .expect(500) .expect('Error: .keys required for signed cookies') .end(done) }) it('should return signed cookie value', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, getCookieHandler('foo', { signed: true }))) .get('/') .set('Cookie', 'foo=bar; foo.sig=iW2fuCIzk9Cg_rqLT1CAqrtdWs8') .expect(200, 'bar', done) }) describe('when signature is invalid', function () { it('should return undefined', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, getCookieHandler('foo', { signed: true }))) .get('/') .set('Cookie', 'foo=bar; foo.sig=v5f380JakwVgx2H9B9nA6kJaZNg') .expect(200, 'undefined', done) }) it('should delete signature cookie', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, getCookieHandler('foo', { signed: true }))) .get('/') .set('Cookie', 'foo=bar; foo.sig=v5f380JakwVgx2H9B9nA6kJaZNg') .expect(200) .expect('undefined') .expect(shouldSetCookieCount(1)) .expect(shouldSetCookieWithAttributeAndValue('foo.sig', 'expires', 'Thu, 01 Jan 1970 00:00:00 GMT')) .end(done) }) }) describe('when signature matches old key', function () { it('should return signed value', function (done) { var opts = { keys: ['keyboard cat a', 'keyboard cat b'] } request(createServer(opts, getCookieHandler('foo', { signed: true }))) .get('/') .set('Cookie', 'foo=bar; foo.sig=NzdRHeORj7MtAMhSsILYRsyVNI8') .expect(200, 'bar', done) }) it('should set signature with new key', function (done) { var opts = { keys: ['keyboard cat a', 'keyboard cat b'] } request(createServer(opts, getCookieHandler('foo', { signed: true }))) .get('/') .set('Cookie', 'foo=bar; foo.sig=NzdRHeORj7MtAMhSsILYRsyVNI8') .expect(200) .expect('bar') .expect(shouldSetCookieCount(1)) .expect(shouldSetCookieToValue('foo.sig', 'tecF04p5ua6TnfYxUTDskgWSKJE')) .end(done) }) }) }) }) }) describe('.set(name, value, [options])', function () { it('should set cookie', function (done) { request(createServer(setCookieHandler('foo', 'bar'))) .get('/') .expect(200) .expect(shouldSetCookieToValue('foo', 'bar')) .end(done) }) it('should work for cookie name with special characters', function (done) { request(createServer(setCookieHandler('foo*(#bar)?.|$', 'buzz'))) .get('/') .expect(200) .expect(shouldSetCookieToValue('foo*(#bar)?.|$', 'buzz')) .end(done) }) it('should work for cookie value with special characters', function (done) { request(createServer(setCookieHandler('foo', '*(#bar)?.|$'))) .get('/') .expect(200) .expect(shouldSetCookieToValue('foo', '*(#bar)?.|$')) .end(done) }) describe('when value is falsy', function () { it('should delete cookie', function (done) { request(createServer(setCookieHandler('foo', null))) .get('/') .expect(200) .expect(shouldSetCookieCount(1)) .expect(shouldSetCookieToValue('foo', '')) .expect(shouldSetCookieWithAttributeAndValue('foo', 'expires', 'Thu, 01 Jan 1970 00:00:00 GMT')) .end(done) }) }) describe('"httpOnly" option', function () { it('should be set by default', function (done) { request(createServer(setCookieHandler('foo', 'bar'))) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'httpOnly')) .end(done) }) it('should set to true', function (done) { request(createServer(setCookieHandler('foo', 'bar', { httpOnly: true }))) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'httpOnly')) .end(done) }) it('should set to false', function (done) { request(createServer(setCookieHandler('foo', 'bar', { httpOnly: false }))) .get('/') .expect(200) .expect(shouldSetCookieWithoutAttribute('foo', 'httpOnly')) .end(done) }) }) describe('"domain" option', function () { it('should not be set by default', function (done) { request(createServer(setCookieHandler('foo', 'bar'))) .get('/') .expect(200) .expect(shouldSetCookieWithoutAttribute('foo', 'domain')) .end(done) }) it('should set to custom value', function (done) { request(createServer(setCookieHandler('foo', 'bar', { domain: 'foo.local' }))) .get('/') .expect(200) .expect(shouldSetCookieWithAttributeAndValue('foo', 'domain', 'foo.local')) .end(done) }) it('should reject invalid value', function (done) { request(createServer(setCookieHandler('foo', 'bar', { domain: 'foo\nbar' }))) .get('/') .expect(500, 'TypeError: option domain is invalid', done) }) }) describe('"maxAge" option', function () { it('should set the "expires" attribute', function (done) { var maxAge = 86400000 request(createServer(setCookieHandler('foo', 'bar', { maxAge: maxAge }))) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'expires')) .expect(function (res) { var cookie = getCookieForName(res, 'foo') var expected = new Date(Date.parse(res.headers.date) + maxAge).toUTCString() assert.strictEqual(cookie.expires, expected) }) .end(done) }) it('should not set the "maxAge" attribute', function (done) { request(createServer(setCookieHandler('foo', 'bar', { maxAge: 86400000 }))) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'expires')) .expect(shouldSetCookieWithoutAttribute('foo', 'maxAge')) .end(done) }) it('should not affect cookie deletion', function (done) { request(createServer(setCookieHandler('foo', null, { maxAge: 86400000 }))) .get('/') .expect(200) .expect(shouldSetCookieCount(1)) .expect(shouldSetCookieToValue('foo', '')) .expect(shouldSetCookieWithAttributeAndValue('foo', 'expires', 'Thu, 01 Jan 1970 00:00:00 GMT')) .end(done) }) }) describe('"overwrite" option', function () { it('should be off by default', function (done) { request(createServer(function (req, res, cookies) { cookies.set('foo', 'bar') cookies.set('foo', 'baz') res.end() })) .get('/') .expect(200) .expect(shouldSetCookieCount(2)) .expect(shouldSetCookieToValue('foo', 'bar')) .end(done) }) it('should overwrite same cookie by name when true', function (done) { request(createServer(function (req, res, cookies) { cookies.set('foo', 'bar') cookies.set('foo', 'baz', { overwrite: true }) res.end() })) .get('/') .expect(200) .expect(shouldSetCookieCount(1)) .expect(shouldSetCookieToValue('foo', 'baz')) .end(done) }) it('should overwrite based on name only', function (done) { request(createServer(function (req, res, cookies) { cookies.set('foo', 'bar', { path: '/foo' }) cookies.set('foo', 'baz', { path: '/bar', overwrite: true }) res.end() })) .get('/') .expect(200) .expect(shouldSetCookieCount(1)) .expect(shouldSetCookieToValue('foo', 'baz')) .expect(shouldSetCookieWithAttributeAndValue('foo', 'path', '/bar')) .end(done) }) }) describe('"path" option', function () { it('should default to "/"', function (done) { request(createServer(setCookieHandler('foo', 'bar'))) .get('/') .expect(200) .expect(shouldSetCookieWithAttributeAndValue('foo', 'path', '/')) .end(done) }) it('should set to custom value', function (done) { request(createServer(setCookieHandler('foo', 'bar', { path: '/admin' }))) .get('/') .expect(200) .expect(shouldSetCookieWithAttributeAndValue('foo', 'path', '/admin')) .end(done) }) it('should set to ""', function (done) { request(createServer(setCookieHandler('foo', 'bar', { path: '' }))) .get('/') .expect(200) .expect(shouldSetCookieWithoutAttribute('foo', 'path')) .end(done) }) it('should reject invalid value', function (done) { request(createServer(setCookieHandler('foo', 'bar', { path: 'foo\nbar' }))) .get('/') .expect(500, 'TypeError: option path is invalid', done) }) }) describe('"secure" option', function () { describe('when true', function () { it('should throw on unencrypted connection', function (done) { request(createServer(setCookieHandler('foo', 'bar', { secure: true }))) .get('/') .expect(500) .expect('Error: Cannot send secure cookie over unencrypted connection') .end(done) }) it('should set secure attribute on encrypted connection', function (done) { var server = createSecureServer(setCookieHandler('foo', 'bar', { secure: true })) request(server) .get('/') .ca(server.cert) .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'Secure')) .end(done) }) describe('with "secure: true" constructor option', function () { it('should set secure attribute on unencrypted connection', function (done) { var opts = { secure: true } request(createServer(opts, setCookieHandler('foo', 'bar', { secure: true }))) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'Secure')) .end(done) }) }) describe('with req.protocol === "https"', function () { it('should set secure attribute on unencrypted connection', function (done) { request(createServer(function (req, res, cookies) { req.protocol = 'https' cookies.set('foo', 'bar', { secure: true }) res.end() })) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'Secure')) .end(done) }) }) }) describe('when undefined', function () { it('should set secure attribute on encrypted connection', function (done) { var server = createSecureServer(setCookieHandler('foo', 'bar', { secure: undefined })) request(server) .get('/') .ca(server.cert) .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'Secure')) .end(done) }) describe('with "secure: undefined" constructor option', function () { it('should not set secure attribute on unencrypted connection', function (done) { var opts = { secure: undefined } request(createServer(opts, setCookieHandler('foo', 'bar', { secure: undefined }))) .get('/') .expect(200) .expect(shouldSetCookieWithoutAttribute('foo', 'Secure')) .end(done) }) }) describe('with req.protocol === "https"', function () { it('should set secure attribute on unencrypted connection', function (done) { request(createServer(function (req, res, cookies) { req.protocol = 'https' cookies.set('foo', 'bar', { secure: undefined }) res.end() })) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'Secure')) .end(done) }) }) }) }) describe('"secureProxy" option', function () { it('should set secure attribute over http', function (done) { request(createServer(setCookieHandler('foo', 'bar', { secureProxy: true }))) .get('/') .expect(200) .expect(shouldSetCookieWithAttribute('foo', 'Secure')) .end(done) }) }) describe('"signed" option', function () { describe('when true', function () { it('should throw without .keys', function (done) { request(createServer(setCookieHandler('foo', 'bar', { signed: true }))) .get('/') .expect(500) .expect('Error: .keys required for signed cookies') .end(done) }) it('should set additional .sig cookie', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, setCookieHandler('foo', 'bar', { signed: true }))) .get('/') .expect(200) .expect(shouldSetCookieCount(2)) .expect(shouldSetCookieToValue('foo', 'bar')) .expect(shouldSetCookieToValue('foo.sig', 'iW2fuCIzk9Cg_rqLT1CAqrtdWs8')) .end(done) }) it('should use first key for signature', function (done) { var opts = { keys: ['keyboard cat a', 'keyboard cat b'] } request(createServer(opts, setCookieHandler('foo', 'bar', { signed: true }))) .get('/') .expect(200) .expect(shouldSetCookieCount(2)) .expect(shouldSetCookieToValue('foo', 'bar')) .expect(shouldSetCookieToValue('foo.sig', 'tecF04p5ua6TnfYxUTDskgWSKJE')) .end(done) }) describe('when value is falsy', function () { it('should delete additional .sig cookie', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, setCookieHandler('foo', null, { signed: true }))) .get('/') .expect(200) .expect(shouldSetCookieCount(2)) .expect(shouldSetCookieToValue('foo', '')) .expect(shouldSetCookieWithAttributeAndValue('foo', 'expires', 'Thu, 01 Jan 1970 00:00:00 GMT')) .expect(shouldSetCookieWithAttributeAndValue('foo.sig', 'expires', 'Thu, 01 Jan 1970 00:00:00 GMT')) .end(done) }) }) describe('with "path"', function () { it('should set additional .sig cookie with path', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, setCookieHandler('foo', 'bar', { signed: true, path: '/admin' }))) .get('/') .expect(200) .expect(shouldSetCookieCount(2)) .expect(shouldSetCookieWithAttributeAndValue('foo', 'path', '/admin')) .expect(shouldSetCookieWithAttributeAndValue('foo.sig', 'path', '/admin')) .end(done) }) }) describe('with "overwrite"', function () { it('should set additional .sig cookie with httpOnly', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, function (req, res, cookies) { cookies.set('foo', 'bar', { signed: true }) cookies.set('foo', 'baz', { signed: true, overwrite: true }) res.end() })) .get('/') .expect(200) .expect(shouldSetCookieCount(2)) .expect(shouldSetCookieToValue('foo', 'baz')) .expect(shouldSetCookieToValue('foo.sig', 'ptOkbbiPiGfLWRzz1yXP3XqaW4E')) .end(done) }) }) describe('with "secureProxy"', function () { it('should set additional .sig cookie with secure', function (done) { var opts = { keys: ['keyboard cat'] } request(createServer(opts, setCookieHandler('foo', 'bar', { signed: true, secureProxy: true }))) .get('/') .expect(200) .expect(shouldSetCookieCount(2)) .expect(shouldSetCookieWithAttribute('foo', 'Secure')) .expect(shouldSetCookieWithAttribute('foo.sig', 'Secure')) .end(done) }) }) }) }) }) }) describe('Cookies(req, res, [options])', function () { it('should create new cookies instance', function (done) { assertServer(done, function (req, res) { var cookies = Cookies(req, res, { keys: ['a', 'b'] }) assert.ok(cookies) assert.strictEqual(cookies.constructor, Cookies) assert.strictEqual(cookies.request, req) assert.strictEqual(cookies.response, res) assert.strictEqual(typeof cookies.keys, 'object') }) }) }) function assertServer (done, test) { var server = http.createServer(function (req, res) { try { test(req, res) res.end('OK') } catch (e) { res.statusCode = 500 res.end(e.name + ': ' + e.message) } }) request(server) .get('/') .expect('OK') .expect(200) .end(done) } function createRequestListener (options, handler) { var next = handler || options var opts = next === options ? undefined : options return function (req, res) { var cookies = new Cookies(req, res, opts) try { next(req, res, cookies) } catch (e) { res.statusCode = 500 res.end(e.name + ': ' + e.message) } } } function createSecureServer (options, handler) { var cert = fs.readFileSync(path.join(__dirname, 'fixtures', 'server.crt'), 'ascii') var key = fs.readFileSync(path.join(__dirname, 'fixtures', 'server.key'), 'ascii') return https.createServer({ cert: cert, key: key }) .on('request', createRequestListener(options, handler)) } function createServer (options, handler) { return http.createServer() .on('request', createRequestListener(options, handler)) } function getCookieForName (res, name) { var cookies = getCookies(res) for (var i = 0; i < cookies.length; i++) { if (cookies[i].name === name) { return cookies[i] } } } function getCookieHandler (name, options) { return function (req, res, cookies) { res.end(String(cookies.get(name, options))) } } function getCookies (res) { var setCookies = res.headers['set-cookie'] || [] return setCookies.map(parseSetCookie) } function parseSetCookie (header) { var match var pairs = [] var pattern = /\s*([^=;]+)(?:=([^;]*);?|;|$)/g while ((match = pattern.exec(header))) { pairs.push({ name: match[1], value: match[2] }) } var cookie = pairs.shift() for (var i = 0; i < pairs.length; i++) { match = pairs[i] cookie[match.name.toLowerCase()] = (match.value || true) } return cookie } function setCookieHandler (name, value, options) { return function (req, res, cookies) { cookies.set(name, value, options) res.end() } } function shouldSetCookieCount (num) { return function (res) { var count = getCookies(res).length assert.equal(count, num, 'should set cookie ' + num + ' times') } } function shouldSetCookieToValue (name, val) { return function (res) { var cookie = getCookieForName(res, name) assert.ok(cookie, 'should set cookie ' + name) assert.equal(cookie.value, val, 'should set cookie ' + name + ' to ' + val) } } function shouldSetCookieWithAttribute (name, attrib) { return function (res) { var cookie = getCookieForName(res, name) assert.ok(cookie, 'should set cookie ' + name) assert.ok((attrib.toLowerCase() in cookie), 'should set cookie with attribute ' + attrib) } } function shouldSetCookieWithAttributeAndValue (name, attrib, value) { return function (res) { var cookie = getCookieForName(res, name) assert.ok(cookie, 'should set cookie ' + name) assert.ok((attrib.toLowerCase() in cookie), 'should set cookie with attribute ' + attrib) assert.equal(cookie[attrib.toLowerCase()], value, 'should set cookie with attribute ' + attrib + ' set to ' + value) } } function shouldSetCookieWithoutAttribute (name, attrib) { return function (res) { var cookie = getCookieForName(res, name) assert.ok(cookie, 'should set cookie ' + name) assert.ok(!(attrib.toLowerCase() in cookie), 'should set cookie without attribute ' + attrib) } }