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 +1 -1
- package/lib/contentTypeParser.js +23 -18
- package/package.json +1 -1
- package/test/content-parser.test.js +68 -2
package/fastify.js
CHANGED
package/lib/contentTypeParser.js
CHANGED
|
@@ -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
|
-
|
|
36
|
-
this.customParsers
|
|
37
|
-
this.customParsers
|
|
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
|
|
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
|
|
76
|
+
this.customParsers.set(contentType.toString(), parser)
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
ContentTypeParser.prototype.hasParser = function (contentType) {
|
|
80
|
-
return contentType
|
|
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
|
|
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
|
|
88
|
+
if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
|
|
89
|
+
return this.customParsers.get(contentType).fn !== defaultPlainTextParser
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
return
|
|
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)
|
|
99
|
-
const parser = this.customParsers
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
|
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(
|
|
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
|
+
})
|