fastify 5.7.0 → 5.7.2

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.
Files changed (32) hide show
  1. package/.idea/fastify.iml +12 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  3. package/.idea/jsLibraryMappings.xml +6 -0
  4. package/.idea/jsLinters/eslint.xml +6 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/vcs.xml +6 -0
  7. package/LICENSE +1 -4
  8. package/SPONSORS.md +2 -2
  9. package/docs/Guides/Ecosystem.md +2 -1
  10. package/docs/Guides/Plugins-Guide.md +8 -8
  11. package/docs/Reference/Server.md +6 -2
  12. package/docs/Reference/Validation-and-Serialization.md +15 -6
  13. package/fastify.js +1 -1
  14. package/lib/content-type-parser.js +40 -30
  15. package/lib/content-type.js +152 -0
  16. package/lib/error-serializer.js +24 -10
  17. package/lib/handle-request.js +12 -4
  18. package/package.json +2 -2
  19. package/test/content-parser.test.js +21 -39
  20. package/test/content-type.test.js +102 -1
  21. package/test/custom-parser.0.test.js +3 -3
  22. package/test/custom-parser.1.test.js +0 -35
  23. package/test/custom-parser.3.test.js +1 -1
  24. package/test/schema-validation.test.js +66 -33
  25. package/.claude/settings.local.json +0 -11
  26. package/docs/fastify-fastify-pr-6425-run-20528050272-Lint_Docs.log +0 -180
  27. package/docs/fastify-fastify-pr-6425-run-20528054188-PR_#6425.log +0 -4006
  28. package/docs/fastify-fastify-pr-6425-run-20528054403-Pull_Request_Labeler.log +0 -42
  29. package/docs/fastify-fastify-pr-6425-run-20528054421-Lint_Docs.log +0 -196
  30. package/docs/fastify-fastify-pr-6425-run-20528054423-Internal_Links_Check.log +0 -1864
  31. package/docs/fastify-fastify-pr-6425-run-20528054430-Test_compare.log +0 -5
  32. package/docs/fastify-fastify-pr-6425-run-20528054438-pull_request_title_check.log +0 -41
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ </content>
9
+ <orderEntry type="inheritedJdk" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ </profile>
6
+ </component>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="JavaScriptLibraryMappings">
4
+ <includedPredefinedLibrary name="Node.js Core" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="EslintConfiguration">
4
+ <option name="fix-on-save" value="true" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/fastify.iml" filepath="$PROJECT_DIR$/.idea/fastify.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
package/LICENSE CHANGED
@@ -1,9 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2016-present The Fastify Team
4
-
5
- The Fastify team members are listed at https://github.com/fastify/fastify#team
6
- and in the README file.
3
+ Copyright (c) 2016-present The Fastify Team (members are listed in the README file)
7
4
 
8
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
9
6
  of this software and associated documentation files (the "Software"), to deal
package/SPONSORS.md CHANGED
@@ -9,7 +9,7 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)!
9
9
 
10
10
  ## Tier 4
11
11
 
12
- - [SerpApi](http://serpapi.com/)
12
+ - [SerpApi](https://serpapi.com/?utm_source=fastify)
13
13
 
14
14
  ## Tier 3
15
15
 
@@ -17,7 +17,7 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)!
17
17
  - [Val Town, Inc.](https://opencollective.com/valtown)
18
18
  - [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024)
19
19
  - [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship)
20
- - [Lambdatest](https://www.lambdatest.com/)
20
+ - [TestMu AI](https://www.testmu.ai/)
21
21
 
22
22
  ## Tier 2
23
23
 
@@ -427,6 +427,8 @@ section.
427
427
  context to take place per API call within the Fastify lifecycle of calls.
428
428
  - [`fastify-http-errors-enhanced`](https://github.com/ShogunPanda/fastify-http-errors-enhanced)
429
429
  An error handling plugin for Fastify that uses enhanced HTTP errors.
430
+ - [`fastify-http-exceptions`](https://github.com/bhouston/fastify-http-exceptions)
431
+ Typed HTTP status exceptions which are automatically converted into Fastify responses.
430
432
  - [`fastify-http2https`](https://github.com/lolo32/fastify-http2https) Redirect
431
433
  HTTP requests to HTTPS, both using the same port number, or different response
432
434
  on HTTP and HTTPS.
@@ -746,7 +748,6 @@ middlewares into Fastify plugins
746
748
  - [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple
747
749
  and updated Typeorm plugin for use with Fastify.
748
750
 
749
-
750
751
  #### [Community Tools](#community-tools)
751
752
 
752
753
  - [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows)
@@ -204,12 +204,12 @@ of an *arrow function expression*.
204
204
 
205
205
  You can do the same for the `request` object:
206
206
  ```js
207
- fastify.decorate('getHeader', (req, header) => {
208
- return req.headers[header]
207
+ fastify.decorate('getBoolHeader', (req, name) => {
208
+ return req.headers[name] ?? false // We return `false` if header is missing
209
209
  })
210
210
 
211
211
  fastify.addHook('preHandler', (request, reply, done) => {
212
- request.isHappy = fastify.getHeader(request.raw, 'happy')
212
+ request.isHappy = fastify.getBoolHeader(request, 'happy')
213
213
  done()
214
214
  })
215
215
 
@@ -219,14 +219,14 @@ fastify.get('/happiness', (request, reply) => {
219
219
  ```
220
220
  Again, it works, but it can be much better!
221
221
  ```js
222
- fastify.decorateRequest('setHeader', function (header) {
223
- this.isHappy = this.headers[header]
222
+ fastify.decorateRequest('setBoolHeader', function (name) {
223
+ this.isHappy = this.headers[name] ?? false
224
224
  })
225
225
 
226
226
  fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed!
227
227
 
228
228
  fastify.addHook('preHandler', (request, reply, done) => {
229
- request.setHeader('happy')
229
+ request.setBoolHeader('happy')
230
230
  done()
231
231
  })
232
232
 
@@ -337,11 +337,11 @@ fastify.register((instance, opts, done) => {
337
337
  }
338
338
  })
339
339
 
340
- fastify.get('/plugin1', {config: {useUtil: true}}, (request, reply) => {
340
+ instance.get('/plugin1', {config: {useUtil: true}}, (request, reply) => {
341
341
  reply.send(request)
342
342
  })
343
343
 
344
- fastify.get('/plugin2', (request, reply) => {
344
+ instance.get('/plugin2', (request, reply) => {
345
345
  reply.send(request)
346
346
  })
347
347
 
@@ -534,7 +534,9 @@ recommend using a custom parser to convert only the keys to lowercase.
534
534
  ```js
535
535
  const qs = require('qs')
536
536
  const fastify = require('fastify')({
537
- querystringParser: str => qs.parse(str)
537
+ routerOptions: {
538
+ querystringParser: str => qs.parse(str)
539
+ }
538
540
  })
539
541
  ```
540
542
 
@@ -544,7 +546,9 @@ like the example below for case insensitive keys and values:
544
546
  ```js
545
547
  const querystring = require('fast-querystring')
546
548
  const fastify = require('fastify')({
547
- querystringParser: str => querystring.parse(str.toLowerCase())
549
+ routerOptions: {
550
+ querystringParser: str => querystring.parse(str.toLowerCase())
551
+ }
548
552
  })
549
553
  ```
550
554
 
@@ -675,9 +675,12 @@ const schema = {
675
675
  content: {
676
676
  'application/json': {
677
677
  schema: {
678
- name: { type: 'string' },
679
- image: { type: 'string' },
680
- address: { type: 'string' }
678
+ type: 'object',
679
+ properties: {
680
+ name: { type: 'string' },
681
+ image: { type: 'string' },
682
+ address: { type: 'string' }
683
+ }
681
684
  }
682
685
  },
683
686
  'application/vnd.v1+json': {
@@ -692,8 +695,11 @@ const schema = {
692
695
  content: {
693
696
  'application/vnd.v2+json': {
694
697
  schema: {
695
- fullName: { type: 'string' },
696
- phone: { type: 'string' }
698
+ type: 'object',
699
+ properties: {
700
+ fullName: { type: 'string' },
701
+ phone: { type: 'string' }
702
+ }
697
703
  }
698
704
  }
699
705
  }
@@ -703,7 +709,10 @@ const schema = {
703
709
  // */* is match-all content-type
704
710
  '*/*': {
705
711
  schema: {
706
- desc: { type: 'string' }
712
+ type: 'object',
713
+ properties: {
714
+ desc: { type: 'string' }
715
+ }
707
716
  }
708
717
  }
709
718
  }
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '5.6.2'
3
+ const VERSION = '5.7.2'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('node:http')
@@ -3,6 +3,7 @@
3
3
  const { AsyncResource } = require('node:async_hooks')
4
4
  const { FifoMap: Fifo } = require('toad-cache')
5
5
  const { parse: secureJsonParse } = require('secure-json-parse')
6
+ const ContentType = require('./content-type')
6
7
  const {
7
8
  kDefaultJsonParse,
8
9
  kContentTypeParser,
@@ -75,8 +76,13 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
75
76
  this.customParsers.set('', parser)
76
77
  } else {
77
78
  if (contentTypeIsString) {
78
- this.parserList.unshift(contentType)
79
- this.customParsers.set(contentType, parser)
79
+ const ct = new ContentType(contentType)
80
+ if (ct.isValid === false) {
81
+ throw new FST_ERR_CTP_INVALID_TYPE()
82
+ }
83
+ const normalizedContentType = ct.toString()
84
+ this.parserList.unshift(normalizedContentType)
85
+ this.customParsers.set(normalizedContentType, parser)
80
86
  } else {
81
87
  validateRegExp(contentType)
82
88
  this.parserRegExpList.unshift(contentType)
@@ -87,7 +93,7 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
87
93
 
88
94
  ContentTypeParser.prototype.hasParser = function (contentType) {
89
95
  if (typeof contentType === 'string') {
90
- contentType = contentType.trim().toLowerCase()
96
+ contentType = new ContentType(contentType).toString()
91
97
  } else {
92
98
  if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
93
99
  contentType = contentType.toString()
@@ -97,45 +103,49 @@ ContentTypeParser.prototype.hasParser = function (contentType) {
97
103
  }
98
104
 
99
105
  ContentTypeParser.prototype.existingParser = function (contentType) {
100
- if (contentType === 'application/json' && this.customParsers.has(contentType)) {
101
- return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse]
102
- }
103
- if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
104
- return this.customParsers.get(contentType).fn !== defaultPlainTextParser
106
+ if (typeof contentType === 'string') {
107
+ const ct = new ContentType(contentType).toString()
108
+ if (contentType === 'application/json' && this.customParsers.has(contentType)) {
109
+ return this.customParsers.get(ct).fn !== this[kDefaultJsonParse]
110
+ }
111
+ if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
112
+ return this.customParsers.get(ct).fn !== defaultPlainTextParser
113
+ }
105
114
  }
106
115
 
107
116
  return this.hasParser(contentType)
108
117
  }
109
118
 
110
119
  ContentTypeParser.prototype.getParser = function (contentType) {
111
- let parser = this.customParsers.get(contentType)
112
- if (parser !== undefined) return parser
113
- parser = this.cache.get(contentType)
120
+ if (typeof contentType === 'string') {
121
+ contentType = new ContentType(contentType)
122
+ }
123
+ const ct = contentType.toString()
124
+
125
+ let parser = this.cache.get(ct)
114
126
  if (parser !== undefined) return parser
127
+ parser = this.customParsers.get(ct)
128
+ if (parser !== undefined) {
129
+ this.cache.set(ct, parser)
130
+ return parser
131
+ }
115
132
 
116
- const caseInsensitiveContentType = contentType.toLowerCase()
117
- for (let i = 0; i !== this.parserList.length; ++i) {
118
- const parserListItem = this.parserList[i]
119
- if (
120
- caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem &&
121
- (
122
- caseInsensitiveContentType.length === parserListItem.length ||
123
- caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ ||
124
- caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ ||
125
- caseInsensitiveContentType.charCodeAt(parserListItem.length) === 9 /* `\t` */
126
- )
127
- ) {
128
- parser = this.customParsers.get(parserListItem)
129
- this.cache.set(contentType, parser)
130
- return parser
131
- }
133
+ // We have conflicting desires across our test suite. In some cases, we
134
+ // expect to get a parser by just passing the media-type. In others, we expect
135
+ // to get a parser registered under the media-type while also providing
136
+ // parameters. And in yet others, we expect to register a parser under the
137
+ // media-type and have it apply to any request with a header that starts
138
+ // with that type.
139
+ parser = this.customParsers.get(contentType.mediaType)
140
+ if (parser !== undefined) {
141
+ return parser
132
142
  }
133
143
 
134
144
  for (let j = 0; j !== this.parserRegExpList.length; ++j) {
135
145
  const parserRegExp = this.parserRegExpList[j]
136
- if (parserRegExp.test(contentType)) {
146
+ if (parserRegExp.test(ct)) {
137
147
  parser = this.customParsers.get(parserRegExp.toString())
138
- this.cache.set(contentType, parser)
148
+ this.cache.set(ct, parser)
139
149
  return parser
140
150
  }
141
151
  }
@@ -154,7 +164,7 @@ ContentTypeParser.prototype.remove = function (contentType) {
154
164
  let parsers
155
165
 
156
166
  if (typeof contentType === 'string') {
157
- contentType = contentType.trim().toLowerCase()
167
+ contentType = new ContentType(contentType).toString()
158
168
  parsers = this.parserList
159
169
  } else {
160
170
  if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
@@ -0,0 +1,152 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * keyValuePairsReg is used to split the parameters list into associated
5
+ * key value pairings.
6
+ *
7
+ * @see https://httpwg.org/specs/rfc9110.html#parameter
8
+ * @type {RegExp}
9
+ */
10
+ const keyValuePairsReg = /([\w!#$%&'*+.^`|~-]+)=([^;]*)/gm
11
+
12
+ /**
13
+ * typeNameReg is used to validate that the first part of the media-type
14
+ * does not use disallowed characters.
15
+ *
16
+ * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators
17
+ * @type {RegExp}
18
+ */
19
+ const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/
20
+
21
+ /**
22
+ * subtypeNameReg is used to validate that the second part of the media-type
23
+ * does not use disallowed characters.
24
+ *
25
+ * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators
26
+ * @type {RegExp}
27
+ */
28
+ const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*/
29
+
30
+ /**
31
+ * ContentType parses and represents the value of the content-type header.
32
+ *
33
+ * @see https://httpwg.org/specs/rfc9110.html#media.type
34
+ * @see https://httpwg.org/specs/rfc9110.html#parameter
35
+ */
36
+ class ContentType {
37
+ #valid = false
38
+ #empty = true
39
+ #type = ''
40
+ #subtype = ''
41
+ #parameters = new Map()
42
+ #string
43
+
44
+ constructor (headerValue) {
45
+ if (headerValue == null || headerValue === '' || headerValue === 'undefined') {
46
+ return
47
+ }
48
+
49
+ let sepIdx = headerValue.indexOf(';')
50
+ if (sepIdx === -1) {
51
+ // The value is the simplest `type/subtype` variant.
52
+ sepIdx = headerValue.indexOf('/')
53
+ if (sepIdx === -1) {
54
+ // Got a string without the correct `type/subtype` format.
55
+ return
56
+ }
57
+
58
+ const type = headerValue.slice(0, sepIdx).trimStart().toLowerCase()
59
+ const subtype = headerValue.slice(sepIdx + 1).trimEnd().toLowerCase()
60
+
61
+ if (
62
+ typeNameReg.test(type) === true &&
63
+ subtypeNameReg.test(subtype) === true
64
+ ) {
65
+ this.#valid = true
66
+ this.#empty = false
67
+ this.#type = type
68
+ this.#subtype = subtype
69
+ }
70
+
71
+ return
72
+ }
73
+
74
+ // We have a `type/subtype; params=list...` header value.
75
+ const mediaType = headerValue.slice(0, sepIdx).toLowerCase()
76
+ const paramsList = headerValue.slice(sepIdx + 1).trim()
77
+
78
+ sepIdx = mediaType.indexOf('/')
79
+ if (sepIdx === -1) {
80
+ // We got an invalid string like `something; params=list...`.
81
+ return
82
+ }
83
+ const type = mediaType.slice(0, sepIdx).trimStart()
84
+ const subtype = mediaType.slice(sepIdx + 1).trimEnd()
85
+
86
+ if (
87
+ typeNameReg.test(type) === false ||
88
+ subtypeNameReg.test(subtype) === false
89
+ ) {
90
+ // Some portion of the media-type is using invalid characters. Therefore,
91
+ // the content-type header is invalid.
92
+ return
93
+ }
94
+ this.#type = type
95
+ this.#subtype = subtype
96
+ this.#valid = true
97
+ this.#empty = false
98
+
99
+ let matches = keyValuePairsReg.exec(paramsList)
100
+ while (matches) {
101
+ const key = matches[1]
102
+ const value = matches[2]
103
+ if (value[0] === '"') {
104
+ if (value.at(-1) !== '"') {
105
+ this.#parameters.set(key, 'invalid quoted string')
106
+ matches = keyValuePairsReg.exec(paramsList)
107
+ continue
108
+ }
109
+ // We should probably verify the value matches a quoted string
110
+ // (https://httpwg.org/specs/rfc9110.html#rule.quoted-string) value.
111
+ // But we are not really doing much with the parameter values, so we
112
+ // are omitting that at this time.
113
+ this.#parameters.set(key, value.slice(1, value.length - 1))
114
+ } else {
115
+ this.#parameters.set(key, value)
116
+ }
117
+ matches = keyValuePairsReg.exec(paramsList)
118
+ }
119
+ }
120
+
121
+ get [Symbol.toStringTag] () { return 'ContentType' }
122
+
123
+ get isEmpty () { return this.#empty }
124
+
125
+ get isValid () { return this.#valid }
126
+
127
+ get mediaType () { return `${this.#type}/${this.#subtype}` }
128
+
129
+ get type () { return this.#type }
130
+
131
+ get subtype () { return this.#subtype }
132
+
133
+ get parameters () { return this.#parameters }
134
+
135
+ toString () {
136
+ /* c8 ignore next: we don't need to verify the cache */
137
+ if (this.#string) return this.#string
138
+ const parameters = []
139
+ for (const [key, value] of this.#parameters.entries()) {
140
+ parameters.push(`${key}="${value}"`)
141
+ }
142
+ const result = [this.#type, '/', this.#subtype]
143
+ if (parameters.length > 0) {
144
+ result.push('; ')
145
+ result.push(parameters.join('; '))
146
+ }
147
+ this.#string = result.join('')
148
+ return this.#string
149
+ }
150
+ }
151
+
152
+ module.exports = ContentType
@@ -13,6 +13,20 @@
13
13
  module.exports = function anonymous(validator,serializer
14
14
  ) {
15
15
 
16
+
17
+ const {
18
+ asString,
19
+ asNumber,
20
+ asBoolean,
21
+ asDateTime,
22
+ asDate,
23
+ asTime,
24
+ asUnsafeString
25
+ } = serializer
26
+
27
+ const asInteger = serializer.asInteger.bind(serializer)
28
+
29
+
16
30
  const JSON_STR_BEGIN_OBJECT = '{'
17
31
  const JSON_STR_END_OBJECT = '}'
18
32
  const JSON_STR_BEGIN_ARRAY = '['
@@ -43,7 +57,7 @@ let addComma = false
43
57
  if (value !== undefined) {
44
58
  !addComma && (addComma = true) || (json += JSON_STR_COMMA)
45
59
  json += "\"statusCode\":"
46
- json += serializer.asNumber(value)
60
+ json += asNumber(value)
47
61
  }
48
62
 
49
63
  value = obj["code"]
@@ -57,12 +71,12 @@ let addComma = false
57
71
  } else if (value instanceof Date) {
58
72
  json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
59
73
  } else if (value instanceof RegExp) {
60
- json += serializer.asString(value.source)
74
+ json += asString(value.source)
61
75
  } else {
62
- json += serializer.asString(value.toString())
76
+ json += asString(value.toString())
63
77
  }
64
78
  } else {
65
- json += serializer.asString(value)
79
+ json += asString(value)
66
80
  }
67
81
 
68
82
  }
@@ -78,12 +92,12 @@ let addComma = false
78
92
  } else if (value instanceof Date) {
79
93
  json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
80
94
  } else if (value instanceof RegExp) {
81
- json += serializer.asString(value.source)
95
+ json += asString(value.source)
82
96
  } else {
83
- json += serializer.asString(value.toString())
97
+ json += asString(value.toString())
84
98
  }
85
99
  } else {
86
- json += serializer.asString(value)
100
+ json += asString(value)
87
101
  }
88
102
 
89
103
  }
@@ -99,12 +113,12 @@ let addComma = false
99
113
  } else if (value instanceof Date) {
100
114
  json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
101
115
  } else if (value instanceof RegExp) {
102
- json += serializer.asString(value.source)
116
+ json += asString(value.source)
103
117
  } else {
104
- json += serializer.asString(value.toString())
118
+ json += asString(value.toString())
105
119
  }
106
120
  } else {
107
- json += serializer.asString(value)
121
+ json += asString(value)
108
122
  }
109
123
 
110
124
  }
@@ -1,9 +1,11 @@
1
1
  'use strict'
2
2
 
3
3
  const diagnostics = require('node:diagnostics_channel')
4
+ const ContentType = require('./content-type')
5
+ const wrapThenable = require('./wrap-thenable')
4
6
  const { validate: validateSchema } = require('./validation')
5
7
  const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
6
- const wrapThenable = require('./wrap-thenable')
8
+ const { FST_ERR_CTP_INVALID_MEDIA_TYPE } = require('./errors')
7
9
  const { setErrorStatusCode } = require('./error-status')
8
10
  const {
9
11
  kReplyIsError,
@@ -31,9 +33,9 @@ function handleRequest (err, request, reply) {
31
33
 
32
34
  if (this[kSupportedHTTPMethods].bodywith.has(method)) {
33
35
  const headers = request.headers
34
- const contentType = headers['content-type']
36
+ const ctHeader = headers['content-type']
35
37
 
36
- if (contentType === undefined) {
38
+ if (ctHeader === undefined) {
37
39
  const contentLength = headers['content-length']
38
40
  const transferEncoding = headers['transfer-encoding']
39
41
  const isEmptyBody = transferEncoding === undefined &&
@@ -49,7 +51,13 @@ function handleRequest (err, request, reply) {
49
51
  return
50
52
  }
51
53
 
52
- request[kRouteContext].contentTypeParser.run(contentType, handler, request, reply)
54
+ const contentType = new ContentType(ctHeader)
55
+ if (contentType.isValid === false) {
56
+ reply[kReplyIsError] = true
57
+ reply.status(415).send(new FST_ERR_CTP_INVALID_MEDIA_TYPE())
58
+ return
59
+ }
60
+ request[kRouteContext].contentTypeParser.run(contentType.toString(), handler, request, reply)
53
61
  return
54
62
  }
55
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "5.7.0",
3
+ "version": "5.7.2",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -12,7 +12,7 @@
12
12
  "benchmark:parser:error": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -H \"content-length:123\" -m POST localhost:3000/\"",
13
13
  "build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
14
14
  "build:sync-version": "node build/sync-version.js",
15
- "coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100 ",
15
+ "coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter",
16
16
  "coverage:ci-check-coverage": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100",
17
17
  "lint": "npm run lint:eslint",
18
18
  "lint:fix": "eslint --fix",