| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 | 
							- 'use strict';
 
- const assert = require('assert');
 
- const utility = require('utility');
 
- const _isSameSiteNoneCompatible = require('should-send-same-site-none').isSameSiteNoneCompatible;
 
- const Keygrip = require('./keygrip');
 
- const Cookie = require('./cookie');
 
- const CookieError = require('./error');
 
- const KEYS_ARRAY = Symbol('eggCookies:keysArray');
 
- const KEYS = Symbol('eggCookies:keys');
 
- const keyCache = new Map();
 
- /**
 
-  * cookies for egg
 
-  * extend pillarjs/cookies, add encrypt and decrypt
 
-  */
 
- class Cookies {
 
-   constructor(ctx, keys, defaultCookieOptions) {
 
-     this[KEYS_ARRAY] = keys;
 
-     this._keys = keys;
 
-     // default cookie options
 
-     this._defaultCookieOptions = defaultCookieOptions;
 
-     this.ctx = ctx;
 
-     this.secure = this.ctx.secure;
 
-     this.app = ctx.app;
 
-   }
 
-   get keys() {
 
-     if (!this[KEYS]) {
 
-       const keysArray = this[KEYS_ARRAY];
 
-       assert(Array.isArray(keysArray), '.keys required for encrypt/sign cookies');
 
-       const cache = keyCache.get(keysArray);
 
-       if (cache) {
 
-         this[KEYS] = cache;
 
-       } else {
 
-         this[KEYS] = new Keygrip(this[KEYS_ARRAY]);
 
-         keyCache.set(keysArray, this[KEYS]);
 
-       }
 
-     }
 
-     return this[KEYS];
 
-   }
 
-   /**
 
-    * get cookie value by name
 
-    * @param  {String} name - cookie's name
 
-    * @param  {Object} opts - cookies' options
 
-    *            - {Boolean} signed - default to true
 
-    *            - {Boolean} encrypt - default to false
 
-    * @return {String} value - cookie's value
 
-    */
 
-   get(name, opts) {
 
-     opts = opts || {};
 
-     const signed = computeSigned(opts);
 
-     const header = this.ctx.get('cookie');
 
-     if (!header) return;
 
-     const match = header.match(getPattern(name));
 
-     if (!match) return;
 
-     let value = match[1];
 
-     if (!opts.encrypt && !signed) return value;
 
-     // signed
 
-     if (signed) {
 
-       const sigName = name + '.sig';
 
-       const sigValue = this.get(sigName, { signed: false });
 
-       if (!sigValue) return;
 
-       const raw = name + '=' + value;
 
-       const index = this.keys.verify(raw, sigValue);
 
-       if (index < 0) {
 
-         // can not match any key, remove ${name}.sig
 
-         this.set(sigName, null, { path: '/', signed: false, overwrite: true });
 
-         return;
 
-       }
 
-       if (index > 0) {
 
-         // not signed by the first key, update sigValue
 
-         this.set(sigName, this.keys.sign(raw), { signed: false, overwrite: true });
 
-       }
 
-       return value;
 
-     }
 
-     // encrypt
 
-     value = utility.base64decode(value, true, 'buffer');
 
-     const res = this.keys.decrypt(value);
 
-     return res ? res.value.toString() : undefined;
 
-   }
 
-   set(name, value, opts) {
 
-     opts = Object.assign({}, this._defaultCookieOptions, opts);
 
-     const signed = computeSigned(opts);
 
-     value = value || '';
 
-     if (!this.secure && opts.secure) {
 
-       throw new CookieError('Cannot send secure cookie over unencrypted connection');
 
-     }
 
-     let headers = this.ctx.response.get('set-cookie') || [];
 
-     if (!Array.isArray(headers)) headers = [ headers ];
 
-     // encrypt
 
-     if (opts.encrypt) {
 
-       value = value && utility.base64encode(this.keys.encrypt(value), true);
 
-     }
 
-     // http://browsercookielimits.squawky.net/
 
-     if (value.length > 4093) {
 
-       this.app.emit('cookieLimitExceed', { name, value, ctx: this.ctx });
 
-     }
 
-     // https://github.com/linsight/should-send-same-site-none
 
-     // fixed SameSite=None: Known Incompatible Clients
 
-     if (opts.sameSite && typeof opts.sameSite === 'string' && opts.sameSite.toLowerCase() === 'none') {
 
-       const userAgent = this.ctx.get('user-agent');
 
-       if (!this.secure || (userAgent && !this.isSameSiteNoneCompatible(userAgent))) {
 
-         // Non-secure context or Incompatible clients, don't send SameSite=None property
 
-         opts.sameSite = false;
 
-       }
 
-     }
 
-     const cookie = new Cookie(name, value, opts);
 
-     // if user not set secure, reset secure to ctx.secure
 
-     if (opts.secure === undefined) cookie.attrs.secure = this.secure;
 
-     headers = pushCookie(headers, cookie);
 
-     // signed
 
-     if (signed) {
 
-       cookie.value = value && this.keys.sign(cookie.toString());
 
-       cookie.name += '.sig';
 
-       headers = pushCookie(headers, cookie);
 
-     }
 
-     this.ctx.set('set-cookie', headers);
 
-     return this;
 
-   }
 
-   isSameSiteNoneCompatible(userAgent) {
 
-     // Chrome >= 80.0.0.0
 
-     const result = parseChromiumAndMajorVersion(userAgent);
 
-     if (result.chromium) return result.majorVersion >= 80;
 
-     return _isSameSiteNoneCompatible(userAgent);
 
-   }
 
- }
 
- // https://github.com/linsight/should-send-same-site-none/blob/master/index.js#L86
 
- function parseChromiumAndMajorVersion(userAgent) {
 
-   const m = /Chrom[^ \/]{1,100}\/(\d{1,100}?)\./.exec(userAgent);
 
-   if (!m) return { chromium: false, version: null };
 
-   // Extract digits from first capturing group.
 
-   return { chromium: true, majorVersion: parseInt(m[1]) };
 
- }
 
- const partternCache = new Map();
 
- function getPattern(name) {
 
-   const cache = partternCache.get(name);
 
-   if (cache) return cache;
 
-   const reg = new RegExp(
 
-     '(?:^|;) *' +
 
-     name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +
 
-     '=([^;]*)'
 
-   );
 
-   partternCache.set(name, reg);
 
-   return reg;
 
- }
 
- function computeSigned(opts) {
 
-   // encrypt default to false, signed default to true.
 
-   // disable singed when encrypt is true.
 
-   if (opts.encrypt) return false;
 
-   return opts.signed !== false;
 
- }
 
- function pushCookie(cookies, cookie) {
 
-   if (cookie.attrs.overwrite) {
 
-     cookies = cookies.filter(c => !c.startsWith(cookie.name + '='));
 
-   }
 
-   cookies.push(cookie.toHeader());
 
-   return cookies;
 
- }
 
- Cookies.CookieError = CookieError;
 
- module.exports = Cookies;
 
 
  |