1
- import MIMEType from "whatwg-mimetype" ;
2
1
import { atob } from "abab" ;
3
2
3
+ const removeLeadingAndTrailingHTTPWhitespace = ( string ) =>
4
+ string . replace ( / ^ [ \t \n \r ] + / , "" ) . replace ( / [ \t \n \r ] + $ / , "" ) ;
5
+
6
+ const removeTrailingHTTPWhitespace = ( string ) =>
7
+ string . replace ( / [ \t \n \r ] + $ / , "" ) ;
8
+
9
+ const isHTTPWhitespaceChar = ( char ) =>
10
+ char === " " || char === "\t" || char === "\n" || char === "\r" ;
11
+
12
+ const solelyContainsHTTPTokenCodePoints = ( string ) =>
13
+ / ^ [ - ! # $ % & ' * + . ^ _ ` | ~ A - Z a - z 0 - 9 ] * $ / . test ( string ) ;
14
+
15
+ const soleyContainsHTTPQuotedStringTokenCodePoints = ( string ) =>
16
+ / ^ [ \t \u0020 - \u007E \u0080 - \u00FF ] * $ / . test ( string ) ;
17
+
18
+ const asciiLowercase = ( string ) =>
19
+ string . replace ( / [ A - Z ] / g, ( l ) => l . toLowerCase ( ) ) ;
20
+
21
+ const collectAnHTTPQuotedString = ( input , position ) => {
22
+ let value = "" ;
23
+
24
+ // eslint-disable-next-line no-param-reassign
25
+ position += 1 ;
26
+
27
+ // eslint-disable-next-line no-constant-condition
28
+ while ( true ) {
29
+ while (
30
+ position < input . length &&
31
+ input [ position ] !== '"' &&
32
+ input [ position ] !== "\\"
33
+ ) {
34
+ value += input [ position ] ;
35
+ // eslint-disable-next-line no-param-reassign
36
+ position += 1 ;
37
+ }
38
+
39
+ if ( position >= input . length ) {
40
+ break ;
41
+ }
42
+
43
+ const quoteOrBackslash = input [ position ] ;
44
+
45
+ // eslint-disable-next-line no-param-reassign
46
+ position += 1 ;
47
+
48
+ if ( quoteOrBackslash === "\\" ) {
49
+ if ( position >= input . length ) {
50
+ value += "\\" ;
51
+ break ;
52
+ }
53
+
54
+ value += input [ position ] ;
55
+ // eslint-disable-next-line no-param-reassign
56
+ position += 1 ;
57
+ } else {
58
+ break ;
59
+ }
60
+ }
61
+
62
+ return [ value , position ] ;
63
+ } ;
64
+
4
65
function isASCIIHex ( c ) {
5
66
return (
6
67
( c >= 0x30 && c <= 0x39 ) ||
@@ -56,14 +117,16 @@ export default function parseDataUrl(stringInput) {
56
117
const input = parsedUrl . toString ( ) . substring ( 5 ) ;
57
118
58
119
let position = 0 ;
59
- let mimeType = "" ;
120
+ let mediaType = "" ;
60
121
61
122
while ( position < input . length && input [ position ] !== "," ) {
62
- mimeType += input [ position ] ;
123
+ mediaType += input [ position ] ;
63
124
position += 1 ;
64
125
}
65
126
66
- mimeType = mimeType . replace ( / ^ [ \t \n \f \r ] + / , "" ) . replace ( / [ \t \n \f \r ] + $ / , "" ) ;
127
+ mediaType = mediaType
128
+ . replace ( / ^ [ \t \n \f \r ] + / , "" )
129
+ . replace ( / [ \t \n \f \r ] + $ / , "" ) ;
67
130
68
131
if ( position === input . length ) {
69
132
return null ;
@@ -76,7 +139,9 @@ export default function parseDataUrl(stringInput) {
76
139
let body = Buffer . from ( percentDecodeBytes ( Buffer . from ( encodedBody , "utf-8" ) ) ) ;
77
140
78
141
// Can't use /i regexp flag because it isn't restricted to ASCII.
79
- const mimeTypeBase64MatchResult = / ( .* ) ; * [ B b ] [ A a ] [ S s ] [ E e ] 6 4 $ / . exec ( mimeType ) ;
142
+ const mimeTypeBase64MatchResult = / ( .* ) ; * [ B b ] [ A a ] [ S s ] [ E e ] 6 4 $ / . exec (
143
+ mediaType
144
+ ) ;
80
145
81
146
if ( mimeTypeBase64MatchResult ) {
82
147
const stringBody = body . toString ( "binary" ) ;
@@ -88,20 +153,141 @@ export default function parseDataUrl(stringInput) {
88
153
89
154
body = Buffer . from ( asString , "binary" ) ;
90
155
91
- [ , mimeType ] = mimeTypeBase64MatchResult ;
156
+ [ , mediaType ] = mimeTypeBase64MatchResult ;
92
157
}
93
158
94
- if ( mimeType . startsWith ( ";" ) ) {
95
- mimeType = `text/plain ${ mimeType } ` ;
159
+ if ( mediaType . startsWith ( ";" ) ) {
160
+ mediaType = `text/plain ${ mediaType } ` ;
96
161
}
97
162
98
- let mimeTypeRecord ;
163
+ const result = {
164
+ // eslint-disable-next-line no-undefined
165
+ type : undefined ,
166
+ // eslint-disable-next-line no-undefined
167
+ subtype : undefined ,
168
+ parameters : new Map ( ) ,
169
+ isBase64 : Boolean ( mimeTypeBase64MatchResult ) ,
170
+ body,
171
+ } ;
99
172
100
- try {
101
- mimeTypeRecord = new MIMEType ( mimeType ) ;
102
- } catch ( e ) {
103
- mimeTypeRecord = new MIMEType ( "text/plain;charset=US-ASCII" ) ;
173
+ if ( ! mediaType ) {
174
+ return result ;
175
+ }
176
+
177
+ const inputMediaType = removeLeadingAndTrailingHTTPWhitespace ( mediaType ) ;
178
+
179
+ let positionMediaType = 0 ;
180
+ let type = "" ;
181
+
182
+ while (
183
+ positionMediaType < inputMediaType . length &&
184
+ inputMediaType [ positionMediaType ] !== "/"
185
+ ) {
186
+ type += inputMediaType [ positionMediaType ] ;
187
+ positionMediaType += 1 ;
188
+ }
189
+
190
+ if ( type . length === 0 || ! solelyContainsHTTPTokenCodePoints ( type ) ) {
191
+ return result ;
192
+ }
193
+
194
+ if ( positionMediaType >= inputMediaType . length ) {
195
+ return result ;
196
+ }
197
+
198
+ // Skips past "/"
199
+ positionMediaType += 1 ;
200
+
201
+ let subtype = "" ;
202
+
203
+ while (
204
+ positionMediaType < inputMediaType . length &&
205
+ inputMediaType [ positionMediaType ] !== ";"
206
+ ) {
207
+ subtype += inputMediaType [ positionMediaType ] ;
208
+ positionMediaType += 1 ;
209
+ }
210
+
211
+ subtype = removeTrailingHTTPWhitespace ( subtype ) ;
212
+
213
+ if ( subtype . length === 0 || ! solelyContainsHTTPTokenCodePoints ( subtype ) ) {
214
+ return result ;
215
+ }
216
+
217
+ result . type = asciiLowercase ( type ) ;
218
+ result . subtype = asciiLowercase ( subtype ) ;
219
+
220
+ while ( positionMediaType < inputMediaType . length ) {
221
+ // Skip past ";"
222
+ positionMediaType += 1 ;
223
+
224
+ while ( isHTTPWhitespaceChar ( inputMediaType [ positionMediaType ] ) ) {
225
+ positionMediaType += 1 ;
226
+ }
227
+
228
+ let parameterName = "" ;
229
+
230
+ while (
231
+ positionMediaType < inputMediaType . length &&
232
+ inputMediaType [ positionMediaType ] !== ";" &&
233
+ inputMediaType [ positionMediaType ] !== "="
234
+ ) {
235
+ parameterName += inputMediaType [ positionMediaType ] ;
236
+ positionMediaType += 1 ;
237
+ }
238
+
239
+ parameterName = asciiLowercase ( parameterName ) ;
240
+
241
+ if ( positionMediaType < inputMediaType . length ) {
242
+ if ( inputMediaType [ positionMediaType ] === ";" ) {
243
+ // eslint-disable-next-line no-continue
244
+ continue ;
245
+ }
246
+
247
+ // Skip past "="
248
+ positionMediaType += 1 ;
249
+ }
250
+
251
+ let parameterValue = "" ;
252
+
253
+ if ( inputMediaType [ positionMediaType ] === '"' ) {
254
+ [ parameterValue , positionMediaType ] = collectAnHTTPQuotedString (
255
+ inputMediaType ,
256
+ positionMediaType
257
+ ) ;
258
+
259
+ while (
260
+ positionMediaType < inputMediaType . length &&
261
+ inputMediaType [ positionMediaType ] !== ";"
262
+ ) {
263
+ positionMediaType += 1 ;
264
+ }
265
+ } else {
266
+ while (
267
+ positionMediaType < inputMediaType . length &&
268
+ inputMediaType [ positionMediaType ] !== ";"
269
+ ) {
270
+ parameterValue += inputMediaType [ positionMediaType ] ;
271
+ positionMediaType += 1 ;
272
+ }
273
+
274
+ parameterValue = removeTrailingHTTPWhitespace ( parameterValue ) ;
275
+
276
+ if ( parameterValue === "" ) {
277
+ // eslint-disable-next-line no-continue
278
+ continue ;
279
+ }
280
+ }
281
+
282
+ if (
283
+ parameterName . length > 0 &&
284
+ solelyContainsHTTPTokenCodePoints ( parameterName ) &&
285
+ soleyContainsHTTPQuotedStringTokenCodePoints ( parameterValue ) &&
286
+ ! result . parameters . has ( parameterName )
287
+ ) {
288
+ result . parameters . set ( parameterName , parameterValue ) ;
289
+ }
104
290
}
105
291
106
- return { mimeType : mimeTypeRecord , body } ;
292
+ return result ;
107
293
}
0 commit comments