@fastify/cookie 9.2.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,13 +4,15 @@
4
4
  [![NPM version](https://img.shields.io/npm/v/@fastify/cookie.svg?style=flat)](https://www.npmjs.com/package/@fastify/cookie)
5
5
  [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
6
6
 
7
- A plugin for [Fastify](http://fastify.io/) that adds support for reading and
7
+ A plugin for [Fastify](http://fastify.dev/) that adds support for reading and
8
8
  setting cookies.
9
9
 
10
10
  This plugin's cookie parsing works via Fastify's `onRequest` hook. Therefore,
11
11
  you should register it prior to any other `onRequest` hooks that will depend
12
12
  upon this plugin's actions.
13
13
 
14
+ It is also possible to [import the low-level cookie parsing and serialization functions](#importing-serialize-and-parse).
15
+
14
16
  `@fastify/cookie` [v2.x](https://github.com/fastify/fastify-cookie/tree/v2.x)
15
17
  supports both Fastify@1 and Fastify@2.
16
18
  `@fastify/cookie` v3 only supports Fastify@2.
@@ -70,6 +72,23 @@ app.register(cookie, {
70
72
  } as FastifyCookieOptions)
71
73
  ```
72
74
 
75
+ ## Importing `serialize` and `parse`
76
+
77
+ ```js
78
+ const { serialize, parse } = require('@fastify/cookie')
79
+ const fastify = require('fastify')()
80
+
81
+ fastify.get('/', (req, reply) => {
82
+ const cookie = serialize('lang', 'en', {
83
+ maxAge: 60_000,
84
+ })
85
+
86
+ reply.header('Set-Cookie', cookie)
87
+
88
+ reply.send('Language set!')
89
+ })
90
+ ```
91
+
73
92
  ## Options
74
93
 
75
94
  - `secret` (`String` | `Array` | `Buffer` | `Object`):
package/cookie.js CHANGED
@@ -28,22 +28,6 @@
28
28
 
29
29
  'use strict'
30
30
 
31
- /**
32
- * Module exports.
33
- * @public
34
- */
35
-
36
- exports.parse = parse
37
- exports.serialize = serialize
38
-
39
- /**
40
- * Module variables.
41
- * @private
42
- */
43
-
44
- const decode = decodeURIComponent
45
- const encode = encodeURIComponent
46
-
47
31
  /**
48
32
  * RegExp to match field-content in RFC 7230 sec 3.2
49
33
  *
@@ -61,51 +45,49 @@ const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/ // eslint-dis
61
45
  * The object has the various cookies as keys(names) => values
62
46
  *
63
47
  * @param {string} str
64
- * @param {object} [options]
48
+ * @param {object} [opt]
65
49
  * @return {object}
66
50
  * @public
67
51
  */
68
52
 
69
- function parse (str, options) {
53
+ function parse (str, opt) {
70
54
  if (typeof str !== 'string') {
71
55
  throw new TypeError('argument str must be a string')
72
56
  }
73
57
 
58
+ const dec = opt?.decode || decodeURIComponent
74
59
  const result = {}
75
- const dec = (options && options.decode) || decode
76
60
 
61
+ const strLen = str.length
77
62
  let pos = 0
78
63
  let terminatorPos = 0
79
- let eqIdx = 0
80
-
81
64
  while (true) {
82
- if (terminatorPos === str.length) {
83
- break
84
- }
65
+ if (terminatorPos === strLen) break
85
66
  terminatorPos = str.indexOf(';', pos)
86
- terminatorPos = (terminatorPos === -1) ? str.length : terminatorPos
87
- eqIdx = str.indexOf('=', pos)
67
+ if (terminatorPos === -1) terminatorPos = strLen // This is the last pair
88
68
 
89
- // skip things that don't look like key=value
90
- if (eqIdx === -1 || eqIdx > terminatorPos) {
69
+ let eqIdx = str.indexOf('=', pos)
70
+ if (eqIdx === -1) break // No key-value pairs left
71
+ if (eqIdx > terminatorPos) {
72
+ // Malformed key-value pair
91
73
  pos = terminatorPos + 1
92
74
  continue
93
75
  }
94
76
 
95
77
  const key = str.substring(pos, eqIdx++).trim()
96
-
97
- // only assign once
98
78
  if (result[key] === undefined) {
99
- const val = (str.charCodeAt(eqIdx) === 0x22)
79
+ const val = str.charCodeAt(eqIdx) === 0x22
100
80
  ? str.substring(eqIdx + 1, terminatorPos - 1).trim()
101
81
  : str.substring(eqIdx, terminatorPos).trim()
102
82
 
103
- result[key] = (dec !== decode || val.indexOf('%') !== -1)
83
+ result[key] = !(dec === decodeURIComponent && val.indexOf('%') === -1)
104
84
  ? tryDecode(val, dec)
105
85
  : val
106
86
  }
87
+
107
88
  pos = terminatorPos + 1
108
89
  }
90
+
109
91
  return result
110
92
  }
111
93
 
@@ -120,19 +102,18 @@ function parse (str, options) {
120
102
  *
121
103
  * @param {string} name
122
104
  * @param {string} val
123
- * @param {object} [options]
105
+ * @param {object} [opt]
124
106
  * @return {string}
125
107
  * @public
126
108
  */
127
109
 
128
- function serialize (name, val, options) {
129
- const opt = options || {}
130
- const enc = opt.encode || encode
110
+ function serialize (name, val, opt) {
111
+ const enc = opt?.encode || encodeURIComponent
131
112
  if (typeof enc !== 'function') {
132
113
  throw new TypeError('option encode is invalid')
133
114
  }
134
115
 
135
- if (!fieldContentRegExp.test(name)) {
116
+ if (name && !fieldContentRegExp.test(name)) {
136
117
  throw new TypeError('argument name is invalid')
137
118
  }
138
119
 
@@ -142,13 +123,17 @@ function serialize (name, val, options) {
142
123
  }
143
124
 
144
125
  let str = name + '=' + value
126
+
127
+ if (opt == null) return str
128
+
145
129
  if (opt.maxAge != null) {
146
- const maxAge = opt.maxAge - 0
147
- if (isNaN(maxAge) || !isFinite(maxAge)) {
130
+ const maxAge = +opt.maxAge
131
+
132
+ if (!isFinite(maxAge)) {
148
133
  throw new TypeError('option maxAge is invalid')
149
134
  }
150
135
 
151
- str += '; Max-Age=' + Math.floor(maxAge)
136
+ str += '; Max-Age=' + Math.trunc(maxAge)
152
137
  }
153
138
 
154
139
  if (opt.domain) {
@@ -193,6 +178,7 @@ function serialize (name, val, options) {
193
178
  const sameSite = typeof opt.sameSite === 'string'
194
179
  ? opt.sameSite.toLowerCase()
195
180
  : opt.sameSite
181
+
196
182
  switch (sameSite) {
197
183
  case true:
198
184
  str += '; SameSite=Strict'
@@ -219,13 +205,23 @@ function serialize (name, val, options) {
219
205
  *
220
206
  * @param {string} str
221
207
  * @param {function} decode
208
+ * @returns {string}
222
209
  * @private
223
210
  */
224
-
225
211
  function tryDecode (str, decode) {
226
212
  try {
227
213
  return decode(str)
228
- } catch (e) {
214
+ } catch {
229
215
  return str
230
216
  }
231
217
  }
218
+
219
+ /**
220
+ * Module exports.
221
+ * @public
222
+ */
223
+
224
+ module.exports = {
225
+ parse,
226
+ serialize
227
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/cookie",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
4
4
  "description": "Plugin for fastify to add support for cookies",
5
5
  "main": "plugin.js",
6
6
  "type": "commonjs",
@@ -48,7 +48,7 @@
48
48
  "snazzy": "^9.0.0",
49
49
  "standard": "^17.0.0",
50
50
  "tap": "^16.0.0",
51
- "tsd": "^0.29.0"
51
+ "tsd": "^0.30.0"
52
52
  },
53
53
  "dependencies": {
54
54
  "fastify-plugin": "^4.0.0",
package/plugin.js CHANGED
@@ -70,32 +70,31 @@ function onReqHandlerWrapper (fastify, hook) {
70
70
  }
71
71
 
72
72
  function setCookies (reply) {
73
- let setCookie = reply.getHeader('Set-Cookie')
74
- const setCookieIsUndefined = setCookie === undefined
73
+ const setCookieHeaderValue = reply.getHeader('Set-Cookie')
74
+ let cookieValue
75
75
 
76
- /* istanbul ignore else */
77
- if (setCookieIsUndefined) {
76
+ if (setCookieHeaderValue === undefined) {
78
77
  if (reply[kReplySetCookies].size === 1) {
79
- for (const c of reply[kReplySetCookies].values()) {
80
- reply.header('Set-Cookie', cookie.serialize(c.name, c.value, c.opts))
81
- }
82
-
78
+ // Fast path for single cookie
79
+ const c = reply[kReplySetCookies].values().next().value
80
+ reply.header('Set-Cookie', cookie.serialize(c.name, c.value, c.opts))
83
81
  reply[kReplySetCookies].clear()
84
-
85
82
  return
86
83
  }
87
84
 
88
- setCookie = []
89
- } else if (typeof setCookie === 'string') {
90
- setCookie = [setCookie]
85
+ cookieValue = []
86
+ } else if (typeof setCookieHeaderValue === 'string') {
87
+ cookieValue = [setCookieHeaderValue]
88
+ } else {
89
+ cookieValue = setCookieHeaderValue
91
90
  }
92
91
 
93
92
  for (const c of reply[kReplySetCookies].values()) {
94
- setCookie.push(cookie.serialize(c.name, c.value, c.opts))
93
+ cookieValue.push(cookie.serialize(c.name, c.value, c.opts))
95
94
  }
96
95
 
97
- if (!setCookieIsUndefined) reply.removeHeader('Set-Cookie')
98
- reply.header('Set-Cookie', setCookie)
96
+ reply.removeHeader('Set-Cookie')
97
+ reply.header('Set-Cookie', cookieValue)
99
98
  reply[kReplySetCookies].clear()
100
99
  }
101
100
 
@@ -214,6 +213,9 @@ module.exports = fastifyCookie
214
213
  module.exports.default = fastifyCookie // supersedes fastifyCookie.default = fastifyCookie
215
214
  module.exports.fastifyCookie = fastifyCookie // supersedes fastifyCookie.fastifyCookie = fastifyCookie
216
215
 
216
+ module.exports.serialize = cookie.serialize
217
+ module.exports.parse = cookie.parse
218
+
217
219
  module.exports.signerFactory = Signer
218
220
  module.exports.Signer = Signer
219
221
  module.exports.sign = sign
@@ -3,7 +3,7 @@
3
3
  const tap = require('tap')
4
4
  const test = tap.test
5
5
 
6
- const cookie = require('../cookie')
6
+ const cookie = require('..')
7
7
 
8
8
  test('parse: argument validation', (t) => {
9
9
  t.plan(2)
@@ -132,6 +132,39 @@ test('should set multiple cookies', (t) => {
132
132
  })
133
133
  })
134
134
 
135
+ test('should set multiple cookies (an array already exists)', (t) => {
136
+ t.plan(10)
137
+ const fastify = Fastify()
138
+ fastify.register(plugin)
139
+
140
+ fastify.get('/test1', (req, reply) => {
141
+ reply
142
+ .header('Set-Cookie', ['bar=bar'])
143
+ .setCookie('foo', 'foo', { path: '/' })
144
+ .setCookie('foo', 'foo', { path: '/path' })
145
+ .send({ hello: 'world' })
146
+ })
147
+
148
+ fastify.inject({
149
+ method: 'GET',
150
+ url: '/test1'
151
+ }, (err, res) => {
152
+ t.error(err)
153
+ t.equal(res.statusCode, 200)
154
+ t.same(JSON.parse(res.body), { hello: 'world' })
155
+
156
+ const cookies = res.cookies
157
+ t.equal(cookies.length, 3)
158
+ t.equal(cookies[0].name, 'bar')
159
+ t.equal(cookies[0].value, 'bar')
160
+ t.equal(cookies[0].path, undefined)
161
+
162
+ t.equal(cookies[1].name, 'foo')
163
+ t.equal(cookies[1].value, 'foo')
164
+ t.equal(cookies[2].path, '/path')
165
+ })
166
+ })
167
+
135
168
  test('cookies get set correctly with millisecond dates', (t) => {
136
169
  t.plan(8)
137
170
  const fastify = Fastify()
package/types/plugin.d.ts CHANGED
@@ -65,11 +65,7 @@ declare module "fastify" {
65
65
  * @param value Cookie value
66
66
  * @param options Serialize options
67
67
  */
68
- setCookie(
69
- name: string,
70
- value: string,
71
- options?: fastifyCookie.CookieSerializeOptions
72
- ): this;
68
+ setCookie: setCookieWrapper;
73
69
 
74
70
  /**
75
71
  * @alias setCookie
@@ -139,6 +135,10 @@ declare namespace fastifyCookie {
139
135
  signed?: boolean;
140
136
  }
141
137
 
138
+ export interface ParseOptions {
139
+ decode?: (encodedURIComponent: string) => string;
140
+ }
141
+
142
142
  type HookType = 'onRequest' | 'preParsing' | 'preValidation' | 'preHandler' | 'preSerialization';
143
143
 
144
144
  export interface FastifyCookieOptions {
@@ -162,6 +162,8 @@ declare namespace fastifyCookie {
162
162
  export const unsign: Unsign;
163
163
 
164
164
  export interface FastifyCookie extends FastifyCookiePlugin {
165
+ parse: (cookieHeader: string, opts?: ParseOptions) => { [key: string]: string };
166
+ serialize: (name: string, value: string, opts?: SerializeOptions) => string;
165
167
  signerFactory: SignerFactory;
166
168
  Signer: Signer;
167
169
  sign: Sign;
@@ -232,3 +232,6 @@ appWithHook.register(cookie, { hook: 'preSerialization' });
232
232
  appWithHook.register(cookie, { hook: 'preValidation' });
233
233
  expectError(appWithHook.register(cookie, { hook: true }));
234
234
  expectError(appWithHook.register(cookie, { hook: 'false' }));
235
+
236
+ expectType<(cookieHeader: string, opts?: fastifyCookieStar.ParseOptions) => { [key: string]: string }>(fastifyCookieDefault.parse);
237
+ expectType<(name: string, value: string, opts?: fastifyCookieStar.SerializeOptions) => string>(fastifyCookieDefault.serialize);