11'use strict'
22
33/**
4- * tokensReg is used to parse the media-type and media-subtype fields.
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.
515 *
616 * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators
717 * @type {RegExp }
818 */
9- const tokensReg = / ^ ( [ \w ! # $ % & ' * + . ^ ` | ~ - ] + ) \/ ( [ \w ! # $ % & ' * + . ^ ` | ~ - ] + ) \s * ( ; . * ) ? /
19+ const typeNameReg = / ^ [ \w ! # $ % & ' * + . ^ ` | ~ - ] + $ /
1020
1121/**
12- * keyValuePairsReg is used to split the parameters list into associated
13- * key value pairings .
22+ * subtypeNameReg is used to validate that the second part of the media-type
23+ * does not use disallowed characters .
1424 *
15- * @see https://httpwg.org/specs/rfc9110.html#parameter
25+ * @see https://httpwg.org/specs/rfc9110.html#rule.token.separators
1626 * @type {RegExp }
1727 */
18- const keyValuePairsReg = / ( [ \w ! # $ % & ' * + . ^ ` | ~ - ] + ) = ( [ ^ ; ] * ) / gm
28+ const subtypeNameReg = / ^ [ \w ! # $ % & ' * + . ^ ` | ~ - ] + \s * /
1929
2030/**
2131 * ContentType parses and represents the value of the content-type header.
@@ -36,30 +46,64 @@ class ContentType {
3646 return
3747 }
3848
39- const hv = headerValue . trim ( )
40- let matches = tokensReg . exec ( hv )
41- if ( matches === null ) {
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+
4271 return
4372 }
44- this . #type = matches [ 1 ] . toLowerCase ( )
45- this . #subtype = matches [ 2 ] . toLowerCase ( )
4673
47- this . #valid = true
48- this . #empty = false
49- if ( ! matches [ 3 ] ) {
50- // We don't need to parse the parameters because none were supplied.
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.
5192 return
5293 }
94+ this . #type = type
95+ this . #subtype = subtype
96+ this . #valid = true
97+ this . #empty = false
5398
54- const paramsString = matches [ 3 ]
55- matches = keyValuePairsReg . exec ( paramsString )
99+ let matches = keyValuePairsReg . exec ( paramsList )
56100 while ( matches ) {
57101 const key = matches [ 1 ]
58102 const value = matches [ 2 ]
59103 if ( value [ 0 ] === '"' ) {
60104 if ( value . at ( - 1 ) !== '"' ) {
61105 this . #parameters. set ( key , 'invalid quoted string' )
62- matches = keyValuePairsReg . exec ( paramsString )
106+ matches = keyValuePairsReg . exec ( paramsList )
63107 continue
64108 }
65109 // We should probably verify the value matches a quoted string
@@ -70,7 +114,7 @@ class ContentType {
70114 } else {
71115 this . #parameters. set ( key , value )
72116 }
73- matches = keyValuePairsReg . exec ( paramsString )
117+ matches = keyValuePairsReg . exec ( paramsList )
74118 }
75119 }
76120
0 commit comments