@@ -3,23 +3,29 @@ import { Request } from 'express';
3
3
import { ValidationError } from '../framework/types' ;
4
4
5
5
export class ContentType {
6
- public readonly contentType : string = null ;
7
6
public readonly mediaType : string = null ;
8
- public readonly charSet : string = null ;
9
- public readonly withoutBoundary : string = null ;
10
7
public readonly isWildCard : boolean ;
8
+ public readonly parameters : { charset ?: string , boundary ?: string } & Record < string , string > = { } ;
11
9
private constructor ( contentType : string | null ) {
12
- this . contentType = contentType ;
13
10
if ( contentType ) {
14
- this . withoutBoundary = contentType
15
- . replace ( / ; \s { 0 , } b o u n d a r y .* / , '' )
16
- . toLowerCase ( ) ;
17
- this . mediaType = this . withoutBoundary . split ( ';' ) [ 0 ] . toLowerCase ( ) . trim ( ) ;
18
- this . charSet = this . withoutBoundary . split ( ';' ) [ 1 ] ?. toLowerCase ( ) ;
19
- this . isWildCard = RegExp ( / ^ [ a - z ] + \/ \* $ / ) . test ( this . contentType ) ;
20
- if ( this . charSet ) {
21
- this . charSet = this . charSet . toLowerCase ( ) . trim ( ) ;
11
+ const parameterRegExp = / ; \s * ( [ ^ = ] + ) = ( [ ^ ; ] + ) / g;
12
+ const paramMatches = contentType . matchAll ( parameterRegExp )
13
+ if ( paramMatches ) {
14
+ this . parameters = { } ;
15
+ for ( let match of paramMatches ) {
16
+ const key = match [ 1 ] . toLowerCase ( ) ;
17
+ let value = match [ 2 ] ;
18
+
19
+ if ( key === 'charset' ) {
20
+ // charset parameter is case insensitive
21
+ // @see [rfc2046, Section 4.1.2](https://www.rfc-editor.org/rfc/rfc2046#section-4.1.2)
22
+ value = value . toLowerCase ( ) ;
23
+ }
24
+ this . parameters [ key ] = value ;
25
+ } ;
22
26
}
27
+ this . mediaType = contentType . split ( ';' ) [ 0 ] . toLowerCase ( ) . trim ( ) ;
28
+ this . isWildCard = RegExp ( / ^ [ a - z ] + \/ \* $ / ) . test ( contentType ) ;
23
29
}
24
30
}
25
31
public static from ( req : Request ) : ContentType {
@@ -30,12 +36,30 @@ export class ContentType {
30
36
return new ContentType ( type ) ;
31
37
}
32
38
33
- public equivalents ( ) : string [ ] {
34
- if ( ! this . withoutBoundary ) return [ ] ;
35
- if ( this . charSet ) {
36
- return [ this . mediaType , `${ this . mediaType } ; ${ this . charSet } ` ] ;
39
+ public equivalents ( ) : ContentType [ ] {
40
+ const types : ContentType [ ] = [ ] ;
41
+ if ( ! this . mediaType ) {
42
+ return types ;
43
+ }
44
+ types . push ( new ContentType ( this . mediaType ) ) ;
45
+
46
+ if ( ! this . parameters [ 'charset' ] ) {
47
+ types . push ( new ContentType ( `${ this . normalize ( [ 'charset' ] ) } ; charset=utf-8` ) ) ;
37
48
}
38
- return [ this . withoutBoundary , `${ this . mediaType } ; charset=utf-8` ] ;
49
+ return types ;
50
+ }
51
+
52
+ public normalize ( excludeParams : string [ ] = [ 'boundary' ] ) {
53
+ let parameters = '' ;
54
+ Object . keys ( this . parameters )
55
+ . sort ( )
56
+ . forEach ( ( key ) => {
57
+ if ( ! excludeParams . includes ( key ) ) {
58
+ parameters += `; ${ key } =${ this . parameters [ key ] } `
59
+ }
60
+ } ) ;
61
+ if ( this . mediaType )
62
+ return this . mediaType + parameters ;
39
63
}
40
64
}
41
65
@@ -105,23 +129,28 @@ export const findResponseContent = function (
105
129
accepts : string [ ] ,
106
130
expectedTypes : string [ ] ,
107
131
) : string {
108
- const expectedTypesSet = new Set ( expectedTypes ) ;
132
+ const expectedTypesMap = new Map ( ) ;
133
+ for ( let type of expectedTypes ) {
134
+ expectedTypesMap . set ( ContentType . fromString ( type ) . normalize ( ) , type ) ;
135
+ }
136
+
109
137
// if accepts are supplied, try to find a match, and use its validator
110
138
for ( const accept of accepts ) {
111
139
const act = ContentType . fromString ( accept ) ;
112
- if ( act . contentType === '*/*' ) {
140
+ const normalizedCT = act . normalize ( ) ;
141
+ if ( normalizedCT === '*/*' ) {
113
142
return expectedTypes [ 0 ] ;
114
- } else if ( expectedTypesSet . has ( act . contentType ) ) {
115
- return act . contentType ;
116
- } else if ( expectedTypesSet . has ( act . mediaType ) ) {
143
+ } else if ( expectedTypesMap . has ( normalizedCT ) ) {
144
+ return normalizedCT ;
145
+ } else if ( expectedTypesMap . has ( act . mediaType ) ) {
117
146
return act . mediaType ;
118
147
} else if ( act . isWildCard ) {
119
148
// wildcard of type application/*
120
- const [ type ] = act . contentType . split ( '/' , 1 ) ;
149
+ const [ type ] = normalizedCT . split ( '/' , 1 ) ;
121
150
122
- for ( const expectedType of expectedTypesSet ) {
123
- if ( new RegExp ( `^${ type } \/.+$` ) . test ( expectedType ) ) {
124
- return expectedType ;
151
+ for ( const expectedType of expectedTypesMap ) {
152
+ if ( new RegExp ( `^${ type } \/.+$` ) . test ( expectedType [ 0 ] ) ) {
153
+ return expectedType [ 1 ] ;
125
154
}
126
155
}
127
156
} else {
0 commit comments