Comparing version 3.29.3 to 3.29.4
'use strict' | ||
const VERSION = '3.29.3' | ||
const VERSION = '3.29.4' | ||
@@ -5,0 +5,0 @@ const Avvio = require('avvio') |
@@ -9,2 +9,3 @@ 'use strict' | ||
lru = typeof lru === 'function' ? lru : lru.default | ||
const { parse: parseContentType } = require('content-type') | ||
@@ -40,3 +41,3 @@ const secureJson = require('secure-json-parse') | ||
this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser)) | ||
this.parserList = ['application/json', 'text/plain'] | ||
this.parserList = [new ParserListItem('application/json'), new ParserListItem('text/plain')] | ||
this.parserRegExpList = [] | ||
@@ -74,3 +75,3 @@ this.cache = lru(100) | ||
if (contentTypeIsString) { | ||
this.parserList.unshift(contentType) | ||
this.parserList.unshift(new ParserListItem(contentType)) | ||
} else { | ||
@@ -103,7 +104,21 @@ this.parserRegExpList.unshift(contentType) | ||
const parser = this.cache.get(contentType) | ||
// TODO not covered by tests, this is a security backport | ||
/* istanbul ignore next */ | ||
if (parser !== undefined) return parser | ||
const parsed = safeParseContentType(contentType) | ||
// dummyContentType always the same object | ||
// we can use === for the comparsion and return early | ||
if (parsed === dummyContentType) { | ||
return this.customParsers.get('') | ||
} | ||
// eslint-disable-next-line no-var | ||
for (var i = 0; i !== this.parserList.length; ++i) { | ||
const parserName = this.parserList[i] | ||
if (contentType.indexOf(parserName) !== -1) { | ||
const parser = this.customParsers.get(parserName) | ||
const parserListItem = this.parserList[i] | ||
if (compareContentType(parsed, parserListItem)) { | ||
const parser = this.customParsers.get(parserListItem.name) | ||
// we set request content-type in cache to reduce parsing of MIME type | ||
this.cache.set(contentType, parser) | ||
@@ -117,4 +132,5 @@ return parser | ||
const parserRegExp = this.parserRegExpList[j] | ||
if (parserRegExp.test(contentType)) { | ||
if (compareRegExpContentType(contentType, parsed.type, parserRegExp)) { | ||
const parser = this.customParsers.get(parserRegExp.toString()) | ||
// we set request content-type in cache to reduce parsing of MIME type | ||
this.cache.set(contentType, parser) | ||
@@ -356,2 +372,59 @@ return parser | ||
// dummy here to prevent repeated object creation | ||
const dummyContentType = { type: '', parameters: Object.create(null) } | ||
function safeParseContentType (contentType) { | ||
try { | ||
return parseContentType(contentType) | ||
} catch (err) { | ||
return dummyContentType | ||
} | ||
} | ||
function compareContentType (contentType, parserListItem) { | ||
if (parserListItem.isEssence) { | ||
// we do essence check | ||
return contentType.type.indexOf(parserListItem) !== -1 | ||
} else { | ||
// when the content-type includes parameters | ||
// we do a full-text search | ||
// reject essence content-type before checking parameters | ||
if (contentType.type.indexOf(parserListItem.type) === -1) return false | ||
for (const key of parserListItem.parameterKeys) { | ||
// reject when missing parameters | ||
if (!(key in contentType.parameters)) return false | ||
// reject when parameters do not match | ||
if (contentType.parameters[key] !== parserListItem.parameters[key]) return false | ||
} | ||
return true | ||
} | ||
} | ||
function compareRegExpContentType (contentType, essenceMIMEType, regexp) { | ||
if (regexp.source.indexOf(';') === -1) { | ||
// we do essence check | ||
return regexp.test(essenceMIMEType) | ||
} else { | ||
// when the content-type includes parameters | ||
// we do a full-text match | ||
return regexp.test(contentType) | ||
} | ||
} | ||
function ParserListItem (contentType) { | ||
this.name = contentType | ||
// we pre-calculate all the needed information | ||
// before content-type comparsion | ||
const parsed = safeParseContentType(contentType) | ||
this.type = parsed.type | ||
this.parameters = parsed.parameters | ||
this.parameterKeys = Object.keys(parsed.parameters) | ||
this.isEssence = contentType.indexOf(';') === -1 | ||
} | ||
// used in ContentTypeParser.remove | ||
ParserListItem.prototype.toString = function () { | ||
return this.name | ||
} | ||
module.exports = ContentTypeParser | ||
@@ -358,0 +431,0 @@ module.exports.helpers = { |
{ | ||
"name": "fastify", | ||
"version": "3.29.3", | ||
"version": "3.29.4", | ||
"description": "Fast and low overhead web framework, for Node.js", | ||
@@ -190,2 +190,3 @@ "main": "fastify.js", | ||
"process-warning": "^1.0.0", | ||
"content-type": "^1.0.4", | ||
"proxy-addr": "^2.0.7", | ||
@@ -192,0 +193,0 @@ "rfdc": "^1.1.4", |
@@ -331,1 +331,215 @@ 'use strict' | ||
}) | ||
test('Safeguard against content-type spoofing - string', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser('text/plain', function (request, body, done) { | ||
t.pass('should be called') | ||
done(null, body) | ||
}) | ||
fastify.addContentTypeParser('application/json', function (request, body, done) { | ||
t.fail('shouldn\'t be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'text/plain; content-type="application/json"' | ||
}, | ||
body: '' | ||
}) | ||
}) | ||
test('Safeguard against content-type spoofing - regexp', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser(/text\/plain/, function (request, body, done) { | ||
t.pass('should be called') | ||
done(null, body) | ||
}) | ||
fastify.addContentTypeParser(/application\/json/, function (request, body, done) { | ||
t.fail('shouldn\'t be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'text/plain; content-type="application/json"' | ||
}, | ||
body: '' | ||
}) | ||
}) | ||
test('content-type match parameters - string 1', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser('text/plain; charset=utf8', function (request, body, done) { | ||
t.fail('shouldn\'t be called') | ||
done(null, body) | ||
}) | ||
fastify.addContentTypeParser('application/json; charset=utf8', function (request, body, done) { | ||
t.pass('should be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'application/json; charset=utf8' | ||
}, | ||
body: '' | ||
}) | ||
}) | ||
test('content-type match parameters - string 2', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { | ||
t.pass('should be called') | ||
done(null, body) | ||
}) | ||
fastify.addContentTypeParser('text/plain; charset=utf8; foo=bar', function (request, body, done) { | ||
t.fail('shouldn\'t be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'application/json; foo=bar; charset=utf8' | ||
}, | ||
body: '' | ||
}) | ||
}) | ||
test('content-type match parameters - regexp', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser(/application\/json; charset=utf8/, function (request, body, done) { | ||
t.pass('should be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'application/json; charset=utf8' | ||
}, | ||
body: '' | ||
}) | ||
}) | ||
test('content-type fail when parameters not match - string 1', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { | ||
t.fail('shouldn\'t be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
const response = await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'application/json; charset=utf8' | ||
}, | ||
body: '' | ||
}) | ||
t.same(response.statusCode, 415) | ||
}) | ||
test('content-type fail when parameters not match - string 2', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) { | ||
t.fail('shouldn\'t be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
const response = await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'application/json; charset=utf8; foo=baz' | ||
}, | ||
body: '' | ||
}) | ||
t.same(response.statusCode, 415) | ||
}) | ||
test('content-type fail when parameters not match - regexp', async t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.removeAllContentTypeParsers() | ||
fastify.addContentTypeParser(/application\/json; charset=utf8; foo=bar/, function (request, body, done) { | ||
t.fail('shouldn\'t be called') | ||
done(null, body) | ||
}) | ||
fastify.post('/', async () => { | ||
return 'ok' | ||
}) | ||
const response = await fastify.inject({ | ||
method: 'POST', | ||
path: '/', | ||
headers: { | ||
'content-type': 'application/json; charset=utf8' | ||
}, | ||
body: '' | ||
}) | ||
t.same(response.statusCode, 415) | ||
}) |
@@ -1123,3 +1123,3 @@ 'use strict' | ||
headers: { | ||
'Content-Type': 'application/json charset=utf-8' | ||
'Content-Type': 'application/json; charset=utf-8' | ||
} | ||
@@ -1307,3 +1307,3 @@ }, (err, response, body) => { | ||
headers: { | ||
'Content-Type': 'weird-content-type+json' | ||
'Content-Type': 'weird/content-type+json' | ||
} | ||
@@ -1338,3 +1338,3 @@ }, (err, response, body) => { | ||
fastify.addContentTypeParser(/.*\+myExtension$/, function (req, payload, done) { | ||
fastify.addContentTypeParser(/.*\+myExtension$/i, function (req, payload, done) { | ||
let data = '' | ||
@@ -1341,0 +1341,0 @@ payload.on('data', chunk => { data += chunk }) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1661386
40012
16
+ Addedcontent-type@^1.0.4
+ Addedcontent-type@1.0.5(transitive)