CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
content-parser.test.js733 linesDownload Raw Back to test
1'use strict'2 3const { test } = require('node:test')4const Fastify = require('..')5const keys = require('../lib/symbols')6const { FST_ERR_CTP_ALREADY_PRESENT, FST_ERR_CTP_INVALID_TYPE, FST_ERR_CTP_INVALID_MEDIA_TYPE } = require('../lib/errors')7 8const first = function (req, payload, done) {}9const second = function (req, payload, done) {}10const third = function (req, payload, done) {}11 12test('hasContentTypeParser', async t => {13  await t.test('should know about internal parsers', (t, done) => {14    t.plan(5)15 16    const fastify = Fastify()17    fastify.ready(err => {18      t.assert.ifError(err)19      t.assert.ok(fastify.hasContentTypeParser('application/json'))20      t.assert.ok(fastify.hasContentTypeParser('text/plain'))21      t.assert.ok(fastify.hasContentTypeParser('  text/plain  '))22      t.assert.ok(!fastify.hasContentTypeParser('application/jsoff'))23      done()24    })25  })26 27  await t.test('should only work with string and RegExp', t => {28    t.plan(8)29 30    const fastify = Fastify()31    fastify.addContentTypeParser(/^image\/.*/, first)32    fastify.addContentTypeParser(/^application\/.+\+xml/, first)33    fastify.addContentTypeParser('image/gif', first)34 35    t.assert.ok(fastify.hasContentTypeParser('application/json'))36    t.assert.ok(fastify.hasContentTypeParser(/^image\/.*/))37    t.assert.ok(fastify.hasContentTypeParser(/^application\/.+\+xml/))38    t.assert.ok(fastify.hasContentTypeParser('image/gif'))39    t.assert.ok(!fastify.hasContentTypeParser(/^image\/.+\+xml/))40    t.assert.ok(!fastify.hasContentTypeParser('image/png'))41    t.assert.ok(!fastify.hasContentTypeParser('*'))42    t.assert.throws(43      () => fastify.hasContentTypeParser(123),44      FST_ERR_CTP_INVALID_TYPE45    )46  })47})48 49test('getParser', async t => {50  await t.test('should return matching parser', t => {51    t.plan(6)52 53    const fastify = Fastify()54 55    fastify.addContentTypeParser(/^image\/.*/, first)56    fastify.addContentTypeParser(/^application\/.+\+xml/, second)57    fastify.addContentTypeParser('text/html', third)58 59    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('application/t+xml').fn, second)60    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/png').fn, first)61    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, third)62    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html; charset=utf-8').fn, third)63    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ; charset=utf-8').fn, third)64    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/htmlINVALID')?.fn, undefined)65  })66 67  await t.test('should return matching parser with caching /1', t => {68    t.plan(6)69 70    const fastify = Fastify()71 72    fastify.addContentTypeParser('text/html', first)73 74    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)75    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0)76    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first)77    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)78    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first)79    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)80  })81 82  await t.test('should return matching parser with caching /2', t => {83    t.plan(8)84 85    const fastify = Fastify()86 87    fastify.addContentTypeParser('text/html', first)88 89    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)90    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0)91    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/HTML').fn, first)92    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)93    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first)94    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2)95    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first)96    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2)97  })98 99  await t.test('should return matching parser with caching /3', t => {100    t.plan(6)101 102    const fastify = Fastify()103 104    fastify.addContentTypeParser(/^text\/html(;\s*charset=[^;]+)?$/, first)105 106    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)107    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)108    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first)109    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2)110    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html;charset=utf-8').fn, first)111    t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 2)112  })113 114  await t.test('should prefer content type parser with string value', t => {115    t.plan(2)116 117    const fastify = Fastify()118 119    fastify.addContentTypeParser(/^image\/.*/, first)120    fastify.addContentTypeParser('image/gif', second)121 122    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/gif').fn, second)123    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/png').fn, first)124  })125 126  await t.test('should return parser that catches all if no other is set', t => {127    t.plan(3)128 129    const fastify = Fastify()130 131    fastify.addContentTypeParser('*', first)132    fastify.addContentTypeParser(/^text\/.*/, second)133 134    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/gif').fn, first)135    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, second)136    t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text').fn, first)137  })138 139  await t.test('should return undefined if no matching parser exist', t => {140    t.plan(2)141 142    const fastify = Fastify()143 144    fastify.addContentTypeParser(/^weirdType\/.+/, first)145    fastify.addContentTypeParser('application/javascript', first)146 147    t.assert.ok(!fastify[keys.kContentTypeParser].getParser('application/xml'))148    t.assert.ok(!fastify[keys.kContentTypeParser].getParser('weirdType/'))149  })150})151 152test('existingParser', async t => {153  await t.test('returns always false for "*"', t => {154    t.plan(2)155 156    const fastify = Fastify()157 158    fastify.addContentTypeParser(/^image\/.*/, first)159    fastify.addContentTypeParser(/^application\/.+\+xml/, first)160    fastify.addContentTypeParser('text/html', first)161 162    t.assert.ok(!fastify[keys.kContentTypeParser].existingParser('*'))163 164    fastify.addContentTypeParser('*', first)165 166    t.assert.ok(!fastify[keys.kContentTypeParser].existingParser('*'))167  })168 169  await t.test('let you override the default parser once', t => {170    t.plan(2)171 172    const fastify = Fastify()173 174    fastify.addContentTypeParser('application/json', first)175    fastify.addContentTypeParser('text/plain', first)176 177    t.assert.throws(178      () => fastify.addContentTypeParser('application/json', first),179      FST_ERR_CTP_ALREADY_PRESENT180    )181    t.assert.throws(182      () => fastify.addContentTypeParser('text/plain', first),183      FST_ERR_CTP_ALREADY_PRESENT184    )185  })186 187  const fastify = Fastify()188  const contentTypeParser = fastify[keys.kContentTypeParser]189 190  fastify.addContentTypeParser(/^image\/.*/, first)191  fastify.addContentTypeParser(/^application\/.+\+xml/, first)192  fastify.addContentTypeParser('text/html', first)193 194  t.assert.ok(contentTypeParser.existingParser(/^image\/.*/))195  t.assert.ok(contentTypeParser.existingParser('text/html'))196  t.assert.ok(contentTypeParser.existingParser(/^application\/.+\+xml/))197  t.assert.ok(!contentTypeParser.existingParser('application/json'))198  t.assert.ok(!contentTypeParser.existingParser('text/plain'))199  t.assert.ok(!contentTypeParser.existingParser('image/png'))200  t.assert.ok(!contentTypeParser.existingParser(/^application\/.+\+json/))201})202 203test('add', async t => {204  await t.test('should only accept string and RegExp', t => {205    t.plan(4)206 207    const fastify = Fastify()208    const contentTypeParser = fastify[keys.kContentTypeParser]209 210    t.assert.ifError(contentTypeParser.add('test', {}, first))211    t.assert.ifError(contentTypeParser.add(/test/, {}, first))212    t.assert.throws(213      () => contentTypeParser.add({}, {}, first),214      FST_ERR_CTP_INVALID_TYPE,215      'The content type should be a string or a RegExp'216    )217    t.assert.throws(218      () => contentTypeParser.add(1, {}, first),219      FST_ERR_CTP_INVALID_TYPE,220      'The content type should be a string or a RegExp'221    )222  })223 224  await t.test('should set "*" as parser that catches all', t => {225    t.plan(1)226 227    const fastify = Fastify()228    const contentTypeParser = fastify[keys.kContentTypeParser]229 230    contentTypeParser.add('*', {}, first)231    t.assert.strictEqual(contentTypeParser.customParsers.get('').fn, first)232  })233 234  await t.test('should lowercase contentTypeParser name', async t => {235    t.plan(1)236    const fastify = Fastify()237    fastify.addContentTypeParser('text/html', function (req, done) {238      done()239    })240    try {241      fastify.addContentTypeParser('TEXT/html', function (req, done) {242        done()243      })244    } catch (err) {245      t.assert.strictEqual(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message)246    }247  })248 249  await t.test('should trim contentTypeParser name', async t => {250    t.plan(1)251    const fastify = Fastify()252    fastify.addContentTypeParser('text/html', function (req, done) {253      done()254    })255    try {256      fastify.addContentTypeParser('    text/html', function (req, done) {257        done()258      })259    } catch (err) {260      t.assert.strictEqual(err.message, FST_ERR_CTP_ALREADY_PRESENT('text/html').message)261    }262  })263})264 265test('non-Error thrown from content parser is properly handled', (t, done) => {266  t.plan(3)267 268  const fastify = Fastify()269 270  const throwable = 'test'271  const payload = 'error'272 273  fastify.addContentTypeParser('text/test', (request, payload, done) => {274    done(throwable)275  })276 277  fastify.post('/', (req, reply) => {278  })279 280  fastify.setErrorHandler((err, req, res) => {281    t.assert.strictEqual(err, throwable)282 283    res.send(payload)284  })285 286  fastify.inject({287    method: 'POST',288    url: '/',289    headers: { 'Content-Type': 'text/test' },290    body: 'some text'291  }, (err, res) => {292    t.assert.ifError(err)293    t.assert.strictEqual(res.payload, payload)294    done()295  })296})297 298test('Error thrown 415 from content type is null and make post request to server', (t, done) => {299  t.plan(3)300 301  const fastify = Fastify()302  const errMsg = new FST_ERR_CTP_INVALID_MEDIA_TYPE(undefined).message303 304  fastify.post('/', (req, reply) => {305  })306 307  fastify.inject({308    method: 'POST',309    url: '/',310    body: 'some text'311  }, (err, res) => {312    t.assert.ifError(err)313    t.assert.strictEqual(res.statusCode, 415)314    t.assert.strictEqual(JSON.parse(res.body).message, errMsg)315    done()316  })317})318 319test('remove', async t => {320  await t.test('should remove default parser', t => {321    t.plan(6)322 323    const fastify = Fastify()324    const contentTypeParser = fastify[keys.kContentTypeParser]325 326    t.assert.ok(contentTypeParser.remove('application/json'))327    t.assert.ok(!contentTypeParser.customParsers['application/json'])328    t.assert.ok(!contentTypeParser.parserList.find(parser => parser === 'application/json'))329    t.assert.ok(contentTypeParser.remove('  text/plain  '))330    t.assert.ok(!contentTypeParser.customParsers['text/plain'])331    t.assert.ok(!contentTypeParser.parserList.find(parser => parser === 'text/plain'))332  })333 334  await t.test('should remove RegExp parser', t => {335    t.plan(3)336 337    const fastify = Fastify()338    fastify.addContentTypeParser(/^text\/*/, first)339 340    const contentTypeParser = fastify[keys.kContentTypeParser]341 342    t.assert.ok(contentTypeParser.remove(/^text\/*/))343    t.assert.ok(!contentTypeParser.customParsers[/^text\/*/])344    t.assert.ok(!contentTypeParser.parserRegExpList.find(parser => parser.toString() === /^text\/*/.toString()))345  })346 347  await t.test('should throw an error if content type is neither string nor RegExp', t => {348    t.plan(1)349 350    const fastify = Fastify()351 352    t.assert.throws(() => fastify[keys.kContentTypeParser].remove(12), FST_ERR_CTP_INVALID_TYPE)353  })354 355  await t.test('should return false if content type does not exist', t => {356    t.plan(1)357 358    const fastify = Fastify()359 360    t.assert.ok(!fastify[keys.kContentTypeParser].remove('image/png'))361  })362 363  await t.test('should not remove any content type parser if content type does not exist', t => {364    t.plan(2)365 366    const fastify = Fastify()367 368    const contentTypeParser = fastify[keys.kContentTypeParser]369 370    t.assert.ok(!contentTypeParser.remove('image/png'))371    t.assert.strictEqual(contentTypeParser.customParsers.size, 2)372  })373})374 375test('remove all should remove all existing parsers and reset cache', t => {376  t.plan(4)377 378  const fastify = Fastify()379  fastify.addContentTypeParser('application/xml', first)380  fastify.addContentTypeParser(/^image\/.*/, first)381 382  const contentTypeParser = fastify[keys.kContentTypeParser]383 384  contentTypeParser.getParser('application/xml') // fill cache with one entry385  contentTypeParser.removeAll()386 387  t.assert.strictEqual(contentTypeParser.cache.size, 0)388  t.assert.strictEqual(contentTypeParser.parserList.length, 0)389  t.assert.strictEqual(contentTypeParser.parserRegExpList.length, 0)390  t.assert.strictEqual(Object.keys(contentTypeParser.customParsers).length, 0)391})392 393test('Safeguard against malicious content-type / 1', async t => {394  const badNames = Object.getOwnPropertyNames({}.__proto__) // eslint-disable-line395  t.plan(badNames.length)396 397  const fastify = Fastify()398 399  fastify.post('/', async () => {400    return 'ok'401  })402 403  for (const prop of badNames) {404    const response = await fastify.inject({405      method: 'POST',406      path: '/',407      headers: {408        'content-type': prop409      },410      body: ''411    })412 413    t.assert.strictEqual(response.statusCode, 415)414  }415})416 417test('Safeguard against malicious content-type / 2', async t => {418  t.plan(1)419 420  const fastify = Fastify()421 422  fastify.post('/', async () => {423    return 'ok'424  })425 426  const response = await fastify.inject({427    method: 'POST',428    path: '/',429    headers: {430      'content-type': '\\u0063\\u006fnstructor'431    },432    body: ''433  })434 435  t.assert.strictEqual(response.statusCode, 415)436})437 438test('Safeguard against malicious content-type / 3', async t => {439  t.plan(1)440 441  const fastify = Fastify()442 443  fastify.post('/', async () => {444    return 'ok'445  })446 447  const response = await fastify.inject({448    method: 'POST',449    path: '/',450    headers: {451      'content-type': 'constructor; charset=utf-8'452    },453    body: ''454  })455 456  t.assert.strictEqual(response.statusCode, 415)457})458 459test('Safeguard against content-type spoofing - string', async t => {460  t.plan(1)461 462  const fastify = Fastify()463  fastify.removeAllContentTypeParsers()464  fastify.addContentTypeParser('text/plain', function (request, body, done) {465    t.assert.ok('should be called')466    done(null, body)467  })468  fastify.addContentTypeParser('application/json', function (request, body, done) {469    t.assert.fail('shouldn\'t be called')470    done(null, body)471  })472 473  fastify.post('/', async () => {474    return 'ok'475  })476 477  await fastify.inject({478    method: 'POST',479    path: '/',480    headers: {481      'content-type': 'text/plain; content-type="application/json"'482    },483    body: ''484  })485})486 487test('Warning against improper content-type - regexp', async t => {488  await t.test('improper regex - text plain', (t, done) => {489    t.plan(2)490    const fastify = Fastify()491 492    process.on('warning', onWarning)493    function onWarning (warning) {494      t.assert.strictEqual(warning.name, 'FastifySecurity')495      t.assert.strictEqual(warning.code, 'FSTSEC001')496      done()497    }498    t.after(() => process.removeListener('warning', onWarning))499 500    fastify.removeAllContentTypeParsers()501    fastify.addContentTypeParser(/text\/plain/, function (request, body, done) {502      done(null, body)503    })504  })505 506  await t.test('improper regex - application json', (t, done) => {507    t.plan(2)508    const fastify = Fastify()509 510    process.on('warning', onWarning)511    function onWarning (warning) {512      t.assert.strictEqual(warning.name, 'FastifySecurity')513      t.assert.strictEqual(warning.code, 'FSTSEC001')514      done()515    }516    t.after(() => process.removeListener('warning', onWarning))517 518    fastify.removeAllContentTypeParsers()519 520    fastify.addContentTypeParser(/application\/json/, function (request, body, done) {521      done(null, body)522    })523  })524})525 526test('content-type match parameters - string 1', async t => {527  t.plan(1)528 529  const fastify = Fastify()530  fastify.removeAllContentTypeParsers()531  fastify.addContentTypeParser('text/plain; charset=utf8', function (request, body, done) {532    t.assert.fail('shouldn\'t be called')533    done(null, body)534  })535  fastify.addContentTypeParser('application/json; charset=utf8', function (request, body, done) {536    t.assert.ok('should be called')537    done(null, body)538  })539 540  fastify.post('/', async () => {541    return 'ok'542  })543 544  await fastify.inject({545    method: 'POST',546    path: '/',547    headers: {548      'content-type': 'application/json; charset=utf8'549    },550    body: ''551  })552})553 554test('content-type match parameters - regexp', async t => {555  t.plan(1)556 557  const fastify = Fastify()558  fastify.removeAllContentTypeParsers()559  fastify.addContentTypeParser(/application\/json; charset=utf8/, function (request, body, done) {560    t.assert.ok('should be called')561    done(null, body)562  })563 564  fastify.post('/', async () => {565    return 'ok'566  })567 568  await fastify.inject({569    method: 'POST',570    path: '/',571    headers: {572      'content-type': 'application/json; charset=utf8'573    },574    body: ''575  })576})577 578test('content-type fail when parameters not match - string 1', async t => {579  t.plan(1)580 581  const fastify = Fastify()582  fastify.removeAllContentTypeParsers()583  fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) {584    t.assert.fail('shouldn\'t be called')585    done(null, body)586  })587 588  fastify.post('/', async () => {589    return 'ok'590  })591 592  const response = await fastify.inject({593    method: 'POST',594    path: '/',595    headers: {596      'content-type': 'application/json; charset=utf8'597    },598    body: ''599  })600 601  t.assert.strictEqual(response.statusCode, 415)602})603 604test('content-type fail when parameters not match - string 2', async t => {605  t.plan(1)606 607  const fastify = Fastify()608  fastify.removeAllContentTypeParsers()609  fastify.addContentTypeParser('application/json; charset=utf8; foo=bar', function (request, body, done) {610    t.assert.fail('shouldn\'t be called')611    done(null, body)612  })613 614  fastify.post('/', async () => {615    return 'ok'616  })617 618  const response = await fastify.inject({619    method: 'POST',620    path: '/',621    headers: {622      'content-type': 'application/json; charset=utf8; foo=baz'623    },624    body: ''625  })626 627  t.assert.strictEqual(response.statusCode, 415)628})629 630test('content-type fail when parameters not match - regexp', async t => {631  t.plan(1)632 633  const fastify = Fastify()634  fastify.removeAllContentTypeParsers()635  fastify.addContentTypeParser(/application\/json; charset=utf8; foo=bar/, function (request, body, done) {636    t.assert.fail('shouldn\'t be called')637    done(null, body)638  })639 640  fastify.post('/', async () => {641    return 'ok'642  })643 644  const response = await fastify.inject({645    method: 'POST',646    path: '/',647    headers: {648      'content-type': 'application/json; charset=utf8'649    },650    body: ''651  })652 653  t.assert.strictEqual(response.statusCode, 415)654})655 656// Refs: https://github.com/fastify/fastify/issues/4495657test('content-type regexp list should be cloned when plugin override', async t => {658  t.plan(6)659 660  const fastify = Fastify()661 662  fastify.addContentTypeParser(/^image\/.*/, { parseAs: 'buffer' }, (req, payload, done) => {663    done(null, payload)664  })665 666  fastify.register(function plugin (fastify, options, done) {667    fastify.post('/', function (request, reply) {668      reply.type(request.headers['content-type']).send(request.body)669    })670 671    done()672  })673 674  {675    const { payload, headers, statusCode } = await fastify.inject({676      method: 'POST',677      path: '/',678      payload: 'jpeg',679      headers: { 'content-type': 'image/jpeg' }680    })681    t.assert.strictEqual(statusCode, 200)682    t.assert.strictEqual(headers['content-type'], 'image/jpeg')683    t.assert.strictEqual(payload, 'jpeg')684  }685 686  {687    const { payload, headers, statusCode } = await fastify.inject({688      method: 'POST',689      path: '/',690      payload: 'png',691      headers: { 'content-type': 'image/png' }692    })693    t.assert.strictEqual(statusCode, 200)694    t.assert.strictEqual(headers['content-type'], 'image/png')695    t.assert.strictEqual(payload, 'png')696  }697})698 699test('edge case content-type - ;', async t => {700  t.plan(1)701 702  const fastify = Fastify()703  fastify.removeAllContentTypeParsers()704  fastify.addContentTypeParser(';', function (request, body, done) {705    t.assert.fail('should not be called')706    done(null, body)707  })708 709  fastify.post('/', async () => {710    return 'ok'711  })712 713  await fastify.inject({714    method: 'POST',715    path: '/',716    headers: {717      'content-type': 'application/json; foo=bar; charset=utf8'718    },719    body: ''720  })721 722  await fastify.inject({723    method: 'POST',724    path: '/',725    headers: {726      'content-type': 'image/jpeg'727    },728    body: ''729  })730 731  t.assert.ok('end')732})733