You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
4.0 KiB

  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) || !isFinite(maxAge)) {
  100. throw new TypeError('option maxAge is invalid')
  101. }
  102. str += '; Max-Age=' + Math.floor(maxAge);
  103. }
  104. if (opt.domain) {
  105. if (!fieldContentRegExp.test(opt.domain)) {
  106. throw new TypeError('option domain is invalid');
  107. }
  108. str += '; Domain=' + opt.domain;
  109. }
  110. if (opt.path) {
  111. if (!fieldContentRegExp.test(opt.path)) {
  112. throw new TypeError('option path is invalid');
  113. }
  114. str += '; Path=' + opt.path;
  115. }
  116. if (opt.expires) {
  117. if (typeof opt.expires.toUTCString !== 'function') {
  118. throw new TypeError('option expires is invalid');
  119. }
  120. str += '; Expires=' + opt.expires.toUTCString();
  121. }
  122. if (opt.httpOnly) {
  123. str += '; HttpOnly';
  124. }
  125. if (opt.secure) {
  126. str += '; Secure';
  127. }
  128. if (opt.sameSite) {
  129. var sameSite = typeof opt.sameSite === 'string'
  130. ? opt.sameSite.toLowerCase() : opt.sameSite;
  131. switch (sameSite) {
  132. case true:
  133. str += '; SameSite=Strict';
  134. break;
  135. case 'lax':
  136. str += '; SameSite=Lax';
  137. break;
  138. case 'strict':
  139. str += '; SameSite=Strict';
  140. break;
  141. case 'none':
  142. str += '; SameSite=None';
  143. break;
  144. default:
  145. throw new TypeError('option sameSite is invalid');
  146. }
  147. }
  148. return str;
  149. }
  150. /**
  151. * Try decoding a string using a decoding function.
  152. *
  153. * @param {string} str
  154. * @param {function} decode
  155. * @private
  156. */
  157. function tryDecode(str, decode) {
  158. try {
  159. return decode(str);
  160. } catch (e) {
  161. return str;
  162. }
  163. }