index.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /*!
  2. * cookie
  3. * Copyright(c) 2012-2014 Roman Shtylman
  4. * Copyright(c) 2015 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict';
  8. /**
  9. * Module exports.
  10. * @public
  11. */
  12. exports.parse = parse;
  13. exports.serialize = serialize;
  14. /**
  15. * Module variables.
  16. * @private
  17. */
  18. var decode = decodeURIComponent;
  19. var encode = encodeURIComponent;
  20. var pairSplitRegExp = /; */;
  21. /**
  22. * RegExp to match field-content in RFC 7230 sec 3.2
  23. *
  24. * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
  25. * field-vchar = VCHAR / obs-text
  26. * obs-text = %x80-FF
  27. */
  28. var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
  29. /**
  30. * Parse a cookie header.
  31. *
  32. * Parse the given cookie header string into an object
  33. * The object has the various cookies as keys(names) => values
  34. *
  35. * @param {string} str
  36. * @param {object} [options]
  37. * @return {object}
  38. * @public
  39. */
  40. function parse(str, options) {
  41. if (typeof str !== 'string') {
  42. throw new TypeError('argument str must be a string');
  43. }
  44. var obj = {}
  45. var opt = options || {};
  46. var pairs = str.split(pairSplitRegExp);
  47. var dec = opt.decode || decode;
  48. for (var i = 0; i < pairs.length; i++) {
  49. var pair = pairs[i];
  50. var eq_idx = pair.indexOf('=');
  51. // skip things that don't look like key=value
  52. if (eq_idx < 0) {
  53. continue;
  54. }
  55. var key = pair.substr(0, eq_idx).trim()
  56. var val = pair.substr(++eq_idx, pair.length).trim();
  57. // quoted values
  58. if ('"' == val[0]) {
  59. val = val.slice(1, -1);
  60. }
  61. // only assign once
  62. if (undefined == obj[key]) {
  63. obj[key] = tryDecode(val, dec);
  64. }
  65. }
  66. return obj;
  67. }
  68. /**
  69. * Serialize data into a cookie header.
  70. *
  71. * Serialize the a name value pair into a cookie string suitable for
  72. * http headers. An optional options object specified cookie parameters.
  73. *
  74. * serialize('foo', 'bar', { httpOnly: true })
  75. * => "foo=bar; httpOnly"
  76. *
  77. * @param {string} name
  78. * @param {string} val
  79. * @param {object} [options]
  80. * @return {string}
  81. * @public
  82. */
  83. function serialize(name, val, options) {
  84. var opt = options || {};
  85. var enc = opt.encode || encode;
  86. if (typeof enc !== 'function') {
  87. throw new TypeError('option encode is invalid');
  88. }
  89. if (!fieldContentRegExp.test(name)) {
  90. throw new TypeError('argument name is invalid');
  91. }
  92. var value = enc(val);
  93. if (value && !fieldContentRegExp.test(value)) {
  94. throw new TypeError('argument val is invalid');
  95. }
  96. var str = name + '=' + value;
  97. if (null != opt.maxAge) {
  98. var maxAge = opt.maxAge - 0;
  99. if (isNaN(maxAge)) throw new Error('maxAge should be a Number');
  100. str += '; Max-Age=' + Math.floor(maxAge);
  101. }
  102. if (opt.domain) {
  103. if (!fieldContentRegExp.test(opt.domain)) {
  104. throw new TypeError('option domain is invalid');
  105. }
  106. str += '; Domain=' + opt.domain;
  107. }
  108. if (opt.path) {
  109. if (!fieldContentRegExp.test(opt.path)) {
  110. throw new TypeError('option path is invalid');
  111. }
  112. str += '; Path=' + opt.path;
  113. }
  114. if (opt.expires) {
  115. if (typeof opt.expires.toUTCString !== 'function') {
  116. throw new TypeError('option expires is invalid');
  117. }
  118. str += '; Expires=' + opt.expires.toUTCString();
  119. }
  120. if (opt.httpOnly) {
  121. str += '; HttpOnly';
  122. }
  123. if (opt.secure) {
  124. str += '; Secure';
  125. }
  126. if (opt.sameSite) {
  127. var sameSite = typeof opt.sameSite === 'string'
  128. ? opt.sameSite.toLowerCase() : opt.sameSite;
  129. switch (sameSite) {
  130. case true:
  131. str += '; SameSite=Strict';
  132. break;
  133. case 'lax':
  134. str += '; SameSite=Lax';
  135. break;
  136. case 'strict':
  137. str += '; SameSite=Strict';
  138. break;
  139. default:
  140. throw new TypeError('option sameSite is invalid');
  141. }
  142. }
  143. return str;
  144. }
  145. /**
  146. * Try decoding a string using a decoding function.
  147. *
  148. * @param {string} str
  149. * @param {function} decode
  150. * @private
  151. */
  152. function tryDecode(str, decode) {
  153. try {
  154. return decode(str);
  155. } catch (e) {
  156. return str;
  157. }
  158. }