fastify 4.8.0 → 4.8.1
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 +20 -19
- package/package.json +1 -1
- package/test/content-parser.test.js +68 -2
package/fastify.js
CHANGED
package/lib/contentTypeParser.js
CHANGED
|
@@ -29,9 +29,10 @@ const {
|
|
|
29
29
|
|
|
30
30
|
function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
|
|
31
31
|
this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
|
|
32
|
-
|
|
33
|
-
this.customParsers
|
|
34
|
-
this.customParsers
|
|
32
|
+
// using a map instead of a plain object to avoid prototype hijack attacks
|
|
33
|
+
this.customParsers = new Map()
|
|
34
|
+
this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
|
|
35
|
+
this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
|
|
35
36
|
this.parserList = ['application/json', 'text/plain']
|
|
36
37
|
this.parserRegExpList = []
|
|
37
38
|
this.cache = lru(100)
|
|
@@ -62,35 +63,35 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
|
|
62
63
|
)
|
|
63
64
|
|
|
64
65
|
if (contentTypeIsString && contentType === '*') {
|
|
65
|
-
this.customParsers
|
|
66
|
+
this.customParsers.set('', parser)
|
|
66
67
|
} else {
|
|
67
68
|
if (contentTypeIsString) {
|
|
68
69
|
this.parserList.unshift(contentType)
|
|
69
70
|
} else {
|
|
70
71
|
this.parserRegExpList.unshift(contentType)
|
|
71
72
|
}
|
|
72
|
-
this.customParsers
|
|
73
|
+
this.customParsers.set(contentType.toString(), parser)
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
ContentTypeParser.prototype.hasParser = function (contentType) {
|
|
77
|
-
return contentType
|
|
78
|
+
return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString())
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
ContentTypeParser.prototype.existingParser = function (contentType) {
|
|
81
|
-
if (contentType === 'application/json') {
|
|
82
|
-
return this.customParsers
|
|
82
|
+
if (contentType === 'application/json' && this.customParsers.has(contentType)) {
|
|
83
|
+
return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse]
|
|
83
84
|
}
|
|
84
|
-
if (contentType === 'text/plain') {
|
|
85
|
-
return this.customParsers
|
|
85
|
+
if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
|
|
86
|
+
return this.customParsers.get(contentType).fn !== defaultPlainTextParser
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
return
|
|
89
|
+
return this.hasParser(contentType)
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
ContentTypeParser.prototype.getParser = function (contentType) {
|
|
92
|
-
if (
|
|
93
|
-
return this.customParsers
|
|
93
|
+
if (this.hasParser(contentType)) {
|
|
94
|
+
return this.customParsers.get(contentType)
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
if (this.cache.has(contentType)) {
|
|
@@ -101,7 +102,7 @@ ContentTypeParser.prototype.getParser = function (contentType) {
|
|
|
101
102
|
for (var i = 0; i !== this.parserList.length; ++i) {
|
|
102
103
|
const parserName = this.parserList[i]
|
|
103
104
|
if (contentType.indexOf(parserName) !== -1) {
|
|
104
|
-
const parser = this.customParsers
|
|
105
|
+
const parser = this.customParsers.get(parserName)
|
|
105
106
|
this.cache.set(contentType, parser)
|
|
106
107
|
return parser
|
|
107
108
|
}
|
|
@@ -111,17 +112,17 @@ ContentTypeParser.prototype.getParser = function (contentType) {
|
|
|
111
112
|
for (var j = 0; j !== this.parserRegExpList.length; ++j) {
|
|
112
113
|
const parserRegExp = this.parserRegExpList[j]
|
|
113
114
|
if (parserRegExp.test(contentType)) {
|
|
114
|
-
const parser = this.customParsers
|
|
115
|
+
const parser = this.customParsers.get(parserRegExp.toString())
|
|
115
116
|
this.cache.set(contentType, parser)
|
|
116
117
|
return parser
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
return this.customParsers
|
|
121
|
+
return this.customParsers.get('')
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
ContentTypeParser.prototype.removeAll = function () {
|
|
124
|
-
this.customParsers =
|
|
125
|
+
this.customParsers = new Map()
|
|
125
126
|
this.parserRegExpList = []
|
|
126
127
|
this.parserList = []
|
|
127
128
|
this.cache = lru(100)
|
|
@@ -130,7 +131,7 @@ ContentTypeParser.prototype.removeAll = function () {
|
|
|
130
131
|
ContentTypeParser.prototype.remove = function (contentType) {
|
|
131
132
|
if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
|
132
133
|
|
|
133
|
-
|
|
134
|
+
this.customParsers.delete(contentType.toString())
|
|
134
135
|
|
|
135
136
|
const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList
|
|
136
137
|
|
|
@@ -289,7 +290,7 @@ function Parser (asString, asBuffer, bodyLimit, fn) {
|
|
|
289
290
|
function buildContentTypeParser (c) {
|
|
290
291
|
const contentTypeParser = new ContentTypeParser()
|
|
291
292
|
contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse]
|
|
292
|
-
|
|
293
|
+
contentTypeParser.customParsers = new Map(c.customParsers.entries())
|
|
293
294
|
contentTypeParser.parserList = c.parserList.slice()
|
|
294
295
|
return contentTypeParser
|
|
295
296
|
}
|
package/package.json
CHANGED
|
@@ -196,7 +196,7 @@ test('add', t => {
|
|
|
196
196
|
const contentTypeParser = fastify[keys.kContentTypeParser]
|
|
197
197
|
|
|
198
198
|
contentTypeParser.add('*', {}, first)
|
|
199
|
-
t.equal(contentTypeParser.customParsers
|
|
199
|
+
t.equal(contentTypeParser.customParsers.get('').fn, first)
|
|
200
200
|
})
|
|
201
201
|
|
|
202
202
|
t.end()
|
|
@@ -306,7 +306,7 @@ test('remove', t => {
|
|
|
306
306
|
|
|
307
307
|
contentTypeParser.remove('image/png')
|
|
308
308
|
|
|
309
|
-
t.same(
|
|
309
|
+
t.same(contentTypeParser.customParsers.size, 2)
|
|
310
310
|
})
|
|
311
311
|
|
|
312
312
|
t.end()
|
|
@@ -329,3 +329,69 @@ test('remove all should remove all existing parsers and reset cache', t => {
|
|
|
329
329
|
t.same(contentTypeParser.parserRegExpList.length, 0)
|
|
330
330
|
t.same(Object.keys(contentTypeParser.customParsers).length, 0)
|
|
331
331
|
})
|
|
332
|
+
|
|
333
|
+
test('Safeguard against malicious content-type / 1', async t => {
|
|
334
|
+
const badNames = Object.getOwnPropertyNames({}.__proto__) // eslint-disable-line
|
|
335
|
+
t.plan(badNames.length)
|
|
336
|
+
|
|
337
|
+
const fastify = Fastify()
|
|
338
|
+
|
|
339
|
+
fastify.post('/', async () => {
|
|
340
|
+
return 'ok'
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
for (const prop of badNames) {
|
|
344
|
+
const response = await fastify.inject({
|
|
345
|
+
method: 'POST',
|
|
346
|
+
path: '/',
|
|
347
|
+
headers: {
|
|
348
|
+
'content-type': prop
|
|
349
|
+
},
|
|
350
|
+
body: ''
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
t.same(response.statusCode, 415)
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
test('Safeguard against malicious content-type / 2', async t => {
|
|
358
|
+
t.plan(1)
|
|
359
|
+
|
|
360
|
+
const fastify = Fastify()
|
|
361
|
+
|
|
362
|
+
fastify.post('/', async () => {
|
|
363
|
+
return 'ok'
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const response = await fastify.inject({
|
|
367
|
+
method: 'POST',
|
|
368
|
+
path: '/',
|
|
369
|
+
headers: {
|
|
370
|
+
'content-type': '\\u0063\\u006fnstructor'
|
|
371
|
+
},
|
|
372
|
+
body: ''
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
t.same(response.statusCode, 415)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test('Safeguard against malicious content-type / 3', async t => {
|
|
379
|
+
t.plan(1)
|
|
380
|
+
|
|
381
|
+
const fastify = Fastify()
|
|
382
|
+
|
|
383
|
+
fastify.post('/', async () => {
|
|
384
|
+
return 'ok'
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
const response = await fastify.inject({
|
|
388
|
+
method: 'POST',
|
|
389
|
+
path: '/',
|
|
390
|
+
headers: {
|
|
391
|
+
'content-type': 'constructor; charset=utf-8'
|
|
392
|
+
},
|
|
393
|
+
body: ''
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
t.same(response.statusCode, 415)
|
|
397
|
+
})
|