123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- /*!
- * 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 sameSiteRegExp = /^(?:lax|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 = secure
- if (opts && "secure" in opts) cookie.secure = opts.secure
- if (opts && "secureProxy" in opts) {
- deprecate('"secureProxy" option; use "secure" option, provide "secure" to constructor if needed')
- cookie.secure = opts.secureProxy
- }
- headers = 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"
- headers = 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');
- }
- value || (this.expires = new Date(0))
- this.name = name
- this.value = value || ""
- for (var name in attrs) {
- this[name] = attrs[name]
- }
- 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 && !sameSiteRegExp.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(cookies, cookie) {
- if (cookie.overwrite) {
- cookies = cookies.filter(function(c) { return c.indexOf(cookie.name+'=') !== 0 })
- }
- cookies.push(cookie.toHeader())
- return cookies
- }
- 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
|