index.js 4.0 KB

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