UNPKG

5.28 kBJavaScriptView Raw
1/*!
2 * cookie
3 * Copyright(c) 2012-2014 Roman Shtylman
4 * Copyright(c) 2015 Douglas Christopher Wilson
5 * MIT Licensed
6 */
7
8'use strict';
9
10/**
11 * Module exports.
12 * @public
13 */
14
15exports.parse = parse;
16exports.serialize = serialize;
17
18/**
19 * Module variables.
20 * @private
21 */
22
23var __toString = Object.prototype.toString
24
25/**
26 * RegExp to match field-content in RFC 7230 sec 3.2
27 *
28 * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
29 * field-vchar = VCHAR / obs-text
30 * obs-text = %x80-FF
31 */
32
33var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
34
35/**
36 * Parse a cookie header.
37 *
38 * Parse the given cookie header string into an object
39 * The object has the various cookies as keys(names) => values
40 *
41 * @param {string} str
42 * @param {object} [options]
43 * @return {object}
44 * @public
45 */
46
47function parse(str, options) {
48 if (typeof str !== 'string') {
49 throw new TypeError('argument str must be a string');
50 }
51
52 var obj = {}
53 var opt = options || {};
54 var dec = opt.decode || decode;
55
56 var index = 0
57 while (index < str.length) {
58 var eqIdx = str.indexOf('=', index)
59
60 // no more cookie pairs
61 if (eqIdx === -1) {
62 break
63 }
64
65 var endIdx = str.indexOf(';', index)
66
67 if (endIdx === -1) {
68 endIdx = str.length
69 } else if (endIdx < eqIdx) {
70 // backtrack on prior semicolon
71 index = str.lastIndexOf(';', eqIdx - 1) + 1
72 continue
73 }
74
75 var key = str.slice(index, eqIdx).trim()
76
77 // only assign once
78 if (undefined === obj[key]) {
79 var val = str.slice(eqIdx + 1, endIdx).trim()
80
81 // quoted values
82 if (val.charCodeAt(0) === 0x22) {
83 val = val.slice(1, -1)
84 }
85
86 obj[key] = tryDecode(val, dec);
87 }
88
89 index = endIdx + 1
90 }
91
92 return obj;
93}
94
95/**
96 * Serialize data into a cookie header.
97 *
98 * Serialize the a name value pair into a cookie string suitable for
99 * http headers. An optional options object specified cookie parameters.
100 *
101 * serialize('foo', 'bar', { httpOnly: true })
102 * => "foo=bar; httpOnly"
103 *
104 * @param {string} name
105 * @param {string} val
106 * @param {object} [options]
107 * @return {string}
108 * @public
109 */
110
111function serialize(name, val, options) {
112 var opt = options || {};
113 var enc = opt.encode || encode;
114
115 if (typeof enc !== 'function') {
116 throw new TypeError('option encode is invalid');
117 }
118
119 if (!fieldContentRegExp.test(name)) {
120 throw new TypeError('argument name is invalid');
121 }
122
123 var value = enc(val);
124
125 if (value && !fieldContentRegExp.test(value)) {
126 throw new TypeError('argument val is invalid');
127 }
128
129 var str = name + '=' + value;
130
131 if (null != opt.maxAge) {
132 var maxAge = opt.maxAge - 0;
133
134 if (isNaN(maxAge) || !isFinite(maxAge)) {
135 throw new TypeError('option maxAge is invalid')
136 }
137
138 str += '; Max-Age=' + Math.floor(maxAge);
139 }
140
141 if (opt.domain) {
142 if (!fieldContentRegExp.test(opt.domain)) {
143 throw new TypeError('option domain is invalid');
144 }
145
146 str += '; Domain=' + opt.domain;
147 }
148
149 if (opt.path) {
150 if (!fieldContentRegExp.test(opt.path)) {
151 throw new TypeError('option path is invalid');
152 }
153
154 str += '; Path=' + opt.path;
155 }
156
157 if (opt.expires) {
158 var expires = opt.expires
159
160 if (!isDate(expires) || isNaN(expires.valueOf())) {
161 throw new TypeError('option expires is invalid');
162 }
163
164 str += '; Expires=' + expires.toUTCString()
165 }
166
167 if (opt.httpOnly) {
168 str += '; HttpOnly';
169 }
170
171 if (opt.secure) {
172 str += '; Secure';
173 }
174
175 if (opt.partitioned) {
176 str += '; Partitioned'
177 }
178
179 if (opt.priority) {
180 var priority = typeof opt.priority === 'string'
181 ? opt.priority.toLowerCase()
182 : opt.priority
183
184 switch (priority) {
185 case 'low':
186 str += '; Priority=Low'
187 break
188 case 'medium':
189 str += '; Priority=Medium'
190 break
191 case 'high':
192 str += '; Priority=High'
193 break
194 default:
195 throw new TypeError('option priority is invalid')
196 }
197 }
198
199 if (opt.sameSite) {
200 var sameSite = typeof opt.sameSite === 'string'
201 ? opt.sameSite.toLowerCase() : opt.sameSite;
202
203 switch (sameSite) {
204 case true:
205 str += '; SameSite=Strict';
206 break;
207 case 'lax':
208 str += '; SameSite=Lax';
209 break;
210 case 'strict':
211 str += '; SameSite=Strict';
212 break;
213 case 'none':
214 str += '; SameSite=None';
215 break;
216 default:
217 throw new TypeError('option sameSite is invalid');
218 }
219 }
220
221 return str;
222}
223
224/**
225 * URL-decode string value. Optimized to skip native call when no %.
226 *
227 * @param {string} str
228 * @returns {string}
229 */
230
231function decode (str) {
232 return str.indexOf('%') !== -1
233 ? decodeURIComponent(str)
234 : str
235}
236
237/**
238 * URL-encode value.
239 *
240 * @param {string} val
241 * @returns {string}
242 */
243
244function encode (val) {
245 return encodeURIComponent(val)
246}
247
248/**
249 * Determine if value is a Date.
250 *
251 * @param {*} val
252 * @private
253 */
254
255function isDate (val) {
256 return __toString.call(val) === '[object Date]' ||
257 val instanceof Date
258}
259
260/**
261 * Try decoding a string using a decoding function.
262 *
263 * @param {string} str
264 * @param {function} decode
265 * @private
266 */
267
268function tryDecode(str, decode) {
269 try {
270 return decode(str);
271 } catch (e) {
272 return str;
273 }
274}