fastify 3.29.2 → 3.29.3

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/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '3.29.2'
3
+ const VERSION = '3.29.3'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -32,9 +32,10 @@ const warning = require('./warnings')
32
32
 
33
33
  function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
34
34
  this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
35
- this.customParsers = {}
36
- this.customParsers['application/json'] = new Parser(true, false, bodyLimit, this[kDefaultJsonParse])
37
- this.customParsers['text/plain'] = new Parser(true, false, bodyLimit, defaultPlainTextParser)
35
+ // using a map instead of a plain object to avoid prototype hijack attacks
36
+ this.customParsers = new Map()
37
+ this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
38
+ this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
38
39
  this.parserList = ['application/json', 'text/plain']
39
40
  this.parserRegExpList = []
40
41
  this.cache = lru(100)
@@ -65,38 +66,42 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
65
66
  )
66
67
 
67
68
  if (contentTypeIsString && contentType === '*') {
68
- this.customParsers[''] = parser
69
+ this.customParsers.set('', parser)
69
70
  } else {
70
71
  if (contentTypeIsString) {
71
72
  this.parserList.unshift(contentType)
72
73
  } else {
73
74
  this.parserRegExpList.unshift(contentType)
74
75
  }
75
- this.customParsers[contentType] = parser
76
+ this.customParsers.set(contentType.toString(), parser)
76
77
  }
77
78
  }
78
79
 
79
80
  ContentTypeParser.prototype.hasParser = function (contentType) {
80
- return contentType in this.customParsers
81
+ return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString())
81
82
  }
82
83
 
83
84
  ContentTypeParser.prototype.existingParser = function (contentType) {
84
- if (contentType === 'application/json') {
85
- return this.customParsers['application/json'] && this.customParsers['application/json'].fn !== this[kDefaultJsonParse]
85
+ if (contentType === 'application/json' && this.customParsers.has(contentType)) {
86
+ return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse]
86
87
  }
87
- if (contentType === 'text/plain') {
88
- return this.customParsers['text/plain'] && this.customParsers['text/plain'].fn !== defaultPlainTextParser
88
+ if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
89
+ return this.customParsers.get(contentType).fn !== defaultPlainTextParser
89
90
  }
90
91
 
91
- return contentType in this.customParsers
92
+ return this.hasParser(contentType)
92
93
  }
93
94
 
94
95
  ContentTypeParser.prototype.getParser = function (contentType) {
96
+ if (this.hasParser(contentType)) {
97
+ return this.customParsers.get(contentType)
98
+ }
99
+
95
100
  // eslint-disable-next-line no-var
96
101
  for (var i = 0; i !== this.parserList.length; ++i) {
97
102
  const parserName = this.parserList[i]
98
- if (contentType.indexOf(parserName) > -1) {
99
- const parser = this.customParsers[parserName]
103
+ if (contentType.indexOf(parserName) !== -1) {
104
+ const parser = this.customParsers.get(parserName)
100
105
  this.cache.set(contentType, parser)
101
106
  return parser
102
107
  }
@@ -106,17 +111,17 @@ ContentTypeParser.prototype.getParser = function (contentType) {
106
111
  for (var j = 0; j !== this.parserRegExpList.length; ++j) {
107
112
  const parserRegExp = this.parserRegExpList[j]
108
113
  if (parserRegExp.test(contentType)) {
109
- const parser = this.customParsers[parserRegExp]
114
+ const parser = this.customParsers.get(parserRegExp.toString())
110
115
  this.cache.set(contentType, parser)
111
116
  return parser
112
117
  }
113
118
  }
114
119
 
115
- return this.customParsers['']
120
+ return this.customParsers.get('')
116
121
  }
117
122
 
118
123
  ContentTypeParser.prototype.removeAll = function () {
119
- this.customParsers = {}
124
+ this.customParsers = new Map()
120
125
  this.parserRegExpList = []
121
126
  this.parserList = []
122
127
  this.cache = lru(100)
@@ -125,7 +130,7 @@ ContentTypeParser.prototype.removeAll = function () {
125
130
  ContentTypeParser.prototype.remove = function (contentType) {
126
131
  if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
127
132
 
128
- delete this.customParsers[contentType]
133
+ this.customParsers.delete(contentType.toString())
129
134
 
130
135
  const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList
131
136
 
@@ -290,7 +295,7 @@ function Parser (asString, asBuffer, bodyLimit, fn) {
290
295
  function buildContentTypeParser (c) {
291
296
  const contentTypeParser = new ContentTypeParser()
292
297
  contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse]
293
- Object.assign(contentTypeParser.customParsers, c.customParsers)
298
+ contentTypeParser.customParsers = new Map(c.customParsers.entries())
294
299
  contentTypeParser.parserList = c.parserList.slice()
295
300
  return contentTypeParser
296
301
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "3.29.2",
3
+ "version": "3.29.3",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -181,7 +181,7 @@ test('add', t => {
181
181
  const contentTypeParser = fastify[keys.kContentTypeParser]
182
182
 
183
183
  contentTypeParser.add('*', {}, first)
184
- t.equal(contentTypeParser.customParsers[''].fn, first)
184
+ t.equal(contentTypeParser.customParsers.get('').fn, first)
185
185
  })
186
186
 
187
187
  t.end()
@@ -239,7 +239,7 @@ test('remove', t => {
239
239
 
240
240
  contentTypeParser.remove('image/png')
241
241
 
242
- t.same(Object.keys(contentTypeParser.customParsers).length, 2)
242
+ t.same(contentTypeParser.customParsers.size, 2)
243
243
  })
244
244
 
245
245
  t.end()
@@ -262,3 +262,69 @@ test('remove all should remove all existing parsers and reset cache', t => {
262
262
  t.same(contentTypeParser.parserRegExpList.length, 0)
263
263
  t.same(Object.keys(contentTypeParser.customParsers).length, 0)
264
264
  })
265
+
266
+ test('Safeguard against malicious content-type / 1', async t => {
267
+ const badNames = Object.getOwnPropertyNames({}.__proto__) // eslint-disable-line
268
+ t.plan(badNames.length)
269
+
270
+ const fastify = Fastify()
271
+
272
+ fastify.post('/', async () => {
273
+ return 'ok'
274
+ })
275
+
276
+ for (const prop of badNames) {
277
+ const response = await fastify.inject({
278
+ method: 'POST',
279
+ path: '/',
280
+ headers: {
281
+ 'content-type': prop
282
+ },
283
+ body: ''
284
+ })
285
+
286
+ t.same(response.statusCode, 415)
287
+ }
288
+ })
289
+
290
+ test('Safeguard against malicious content-type / 2', async t => {
291
+ t.plan(1)
292
+
293
+ const fastify = Fastify()
294
+
295
+ fastify.post('/', async () => {
296
+ return 'ok'
297
+ })
298
+
299
+ const response = await fastify.inject({
300
+ method: 'POST',
301
+ path: '/',
302
+ headers: {
303
+ 'content-type': '\\u0063\\u006fnstructor'
304
+ },
305
+ body: ''
306
+ })
307
+
308
+ t.same(response.statusCode, 415)
309
+ })
310
+
311
+ test('Safeguard against malicious content-type / 3', async t => {
312
+ t.plan(1)
313
+
314
+ const fastify = Fastify()
315
+
316
+ fastify.post('/', async () => {
317
+ return 'ok'
318
+ })
319
+
320
+ const response = await fastify.inject({
321
+ method: 'POST',
322
+ path: '/',
323
+ headers: {
324
+ 'content-type': 'constructor; charset=utf-8'
325
+ },
326
+ body: ''
327
+ })
328
+
329
+ t.same(response.statusCode, 415)
330
+ })