cookie.js 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. 'use strict';
  2. const assert = require('assert');
  3. /**
  4. * RegExp to match field-content in RFC 7230 sec 3.2
  5. *
  6. * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
  7. * field-vchar = VCHAR / obs-text
  8. * obs-text = %x80-FF
  9. */
  10. const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; // eslint-disable-line no-control-regex
  11. /**
  12. * RegExp to match Same-Site cookie attribute value.
  13. * https://en.wikipedia.org/wiki/HTTP_cookie#SameSite_cookie
  14. */
  15. const sameSiteRegExp = /^(?:none|lax|strict)$/i;
  16. class Cookie {
  17. constructor(name, value, attrs) {
  18. assert(fieldContentRegExp.test(name), 'argument name is invalid');
  19. assert(!value || fieldContentRegExp.test(value), 'argument value is invalid');
  20. this.name = name;
  21. this.value = value || '';
  22. this.attrs = mergeDefaultAttrs(attrs);
  23. assert(!this.attrs.path || fieldContentRegExp.test(this.attrs.path), 'argument option path is invalid');
  24. assert(!this.attrs.domain || fieldContentRegExp.test(this.attrs.domain), 'argument option domain is invalid');
  25. assert(!this.attrs.sameSite || this.attrs.sameSite === true || sameSiteRegExp.test(this.attrs.sameSite), 'argument option sameSite is invalid');
  26. if (!value) {
  27. this.attrs.expires = new Date(0);
  28. // make sure maxAge is empty
  29. this.attrs.maxAge = null;
  30. }
  31. }
  32. toString() {
  33. return this.name + '=' + this.value;
  34. }
  35. toHeader() {
  36. let header = this.toString();
  37. const attrs = this.attrs;
  38. if (attrs.path) header += '; path=' + attrs.path;
  39. const maxAge = parseInt(attrs.maxAge, 10);
  40. // ignore 0, `session` and other invalid maxAge
  41. if (maxAge) {
  42. header += '; max-age=' + Math.round(maxAge / 1000);
  43. attrs.expires = new Date(Date.now() + maxAge);
  44. }
  45. if (attrs.expires) header += '; expires=' + attrs.expires.toUTCString();
  46. if (attrs.domain) header += '; domain=' + attrs.domain;
  47. if (attrs.sameSite) header += '; samesite=' + (attrs.sameSite === true ? 'strict' : attrs.sameSite.toLowerCase());
  48. if (attrs.secure) header += '; secure';
  49. if (attrs.httpOnly) header += '; httponly';
  50. return header;
  51. }
  52. }
  53. const ATTRS = [ 'path', 'expires', 'domain', 'httpOnly', 'secure', 'maxAge', 'overwrite', 'sameSite' ];
  54. function mergeDefaultAttrs(attrs) {
  55. const merged = {
  56. path: '/',
  57. httpOnly: true,
  58. secure: false,
  59. overwrite: false,
  60. sameSite: false,
  61. };
  62. if (!attrs) return merged;
  63. for (let i = 0; i < ATTRS.length; i++) {
  64. const key = ATTRS[i];
  65. if (key in attrs) merged[key] = attrs[key];
  66. }
  67. return merged;
  68. }
  69. module.exports = Cookie;