UNPKG

6.61 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = parseDataUrl;
7
8var _abab = require("abab");
9
10const removeLeadingAndTrailingHTTPWhitespace = string => string.replace(/^[ \t\n\r]+/, "").replace(/[ \t\n\r]+$/, "");
11
12const removeTrailingHTTPWhitespace = string => string.replace(/[ \t\n\r]+$/, "");
13
14const isHTTPWhitespaceChar = char => char === " " || char === "\t" || char === "\n" || char === "\r";
15
16const solelyContainsHTTPTokenCodePoints = string => /^[-!#$%&'*+.^_`|~A-Za-z0-9]*$/.test(string);
17
18const soleyContainsHTTPQuotedStringTokenCodePoints = string => /^[\t\u0020-\u007E\u0080-\u00FF]*$/.test(string);
19
20const asciiLowercase = string => string.replace(/[A-Z]/g, l => l.toLowerCase());
21
22const collectAnHTTPQuotedString = (input, position) => {
23 let value = ""; // eslint-disable-next-line no-param-reassign
24
25 position += 1; // eslint-disable-next-line no-constant-condition
26
27 while (true) {
28 while (position < input.length && input[position] !== '"' && input[position] !== "\\") {
29 value += input[position]; // eslint-disable-next-line no-param-reassign
30
31 position += 1;
32 }
33
34 if (position >= input.length) {
35 break;
36 }
37
38 const quoteOrBackslash = input[position]; // eslint-disable-next-line no-param-reassign
39
40 position += 1;
41
42 if (quoteOrBackslash === "\\") {
43 if (position >= input.length) {
44 value += "\\";
45 break;
46 }
47
48 value += input[position]; // eslint-disable-next-line no-param-reassign
49
50 position += 1;
51 } else {
52 break;
53 }
54 }
55
56 return [value, position];
57};
58
59function isASCIIHex(c) {
60 return c >= 0x30 && c <= 0x39 || c >= 0x41 && c <= 0x46 || c >= 0x61 && c <= 0x66;
61}
62
63function percentDecodeBytes(input) {
64 const output = new Uint8Array(input.byteLength);
65 let outputIndex = 0;
66
67 for (let i = 0; i < input.byteLength; ++i) {
68 const byte = input[i];
69
70 if (byte !== 0x25) {
71 output[outputIndex] = byte;
72 } else if (byte === 0x25 && (!isASCIIHex(input[i + 1]) || !isASCIIHex(input[i + 2]))) {
73 output[outputIndex] = byte;
74 } else {
75 output[outputIndex] = parseInt(String.fromCodePoint(input[i + 1], input[i + 2]), 16);
76 i += 2;
77 }
78
79 outputIndex += 1;
80 }
81
82 return output.slice(0, outputIndex);
83}
84
85function parseDataUrl(stringInput) {
86 let parsedUrl;
87
88 try {
89 parsedUrl = new URL(stringInput);
90 } catch (error) {
91 return null;
92 }
93
94 if (parsedUrl.protocol !== "data:") {
95 return null;
96 }
97
98 parsedUrl.hash = ""; // `5` is value of `'data:'.length`
99
100 const input = parsedUrl.toString().substring(5);
101 let position = 0;
102 let mediaType = "";
103
104 while (position < input.length && input[position] !== ",") {
105 mediaType += input[position];
106 position += 1;
107 }
108
109 mediaType = mediaType.replace(/^[ \t\n\f\r]+/, "").replace(/[ \t\n\f\r]+$/, "");
110
111 if (position === input.length) {
112 return null;
113 }
114
115 position += 1;
116 const encodedBody = input.substring(position);
117 let body = Buffer.from(percentDecodeBytes(Buffer.from(encodedBody, "utf-8"))); // Can't use /i regexp flag because it isn't restricted to ASCII.
118
119 const mimeTypeBase64MatchResult = /(.*); *[Bb][Aa][Ss][Ee]64$/.exec(mediaType);
120
121 if (mimeTypeBase64MatchResult) {
122 const stringBody = body.toString("binary");
123 const asString = (0, _abab.atob)(stringBody);
124
125 if (asString === null) {
126 return null;
127 }
128
129 body = Buffer.from(asString, "binary");
130 [, mediaType] = mimeTypeBase64MatchResult;
131 }
132
133 if (mediaType.startsWith(";")) {
134 mediaType = `text/plain ${mediaType}`;
135 }
136
137 const result = {
138 // eslint-disable-next-line no-undefined
139 type: undefined,
140 // eslint-disable-next-line no-undefined
141 subtype: undefined,
142 parameters: new Map(),
143 isBase64: Boolean(mimeTypeBase64MatchResult),
144 body
145 };
146
147 if (!mediaType) {
148 return result;
149 }
150
151 const inputMediaType = removeLeadingAndTrailingHTTPWhitespace(mediaType);
152 let positionMediaType = 0;
153 let type = "";
154
155 while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== "/") {
156 type += inputMediaType[positionMediaType];
157 positionMediaType += 1;
158 }
159
160 if (type.length === 0 || !solelyContainsHTTPTokenCodePoints(type)) {
161 return result;
162 }
163
164 if (positionMediaType >= inputMediaType.length) {
165 return result;
166 } // Skips past "/"
167
168
169 positionMediaType += 1;
170 let subtype = "";
171
172 while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";") {
173 subtype += inputMediaType[positionMediaType];
174 positionMediaType += 1;
175 }
176
177 subtype = removeTrailingHTTPWhitespace(subtype);
178
179 if (subtype.length === 0 || !solelyContainsHTTPTokenCodePoints(subtype)) {
180 return result;
181 }
182
183 result.type = asciiLowercase(type);
184 result.subtype = asciiLowercase(subtype);
185
186 while (positionMediaType < inputMediaType.length) {
187 // Skip past ";"
188 positionMediaType += 1;
189
190 while (isHTTPWhitespaceChar(inputMediaType[positionMediaType])) {
191 positionMediaType += 1;
192 }
193
194 let parameterName = "";
195
196 while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";" && inputMediaType[positionMediaType] !== "=") {
197 parameterName += inputMediaType[positionMediaType];
198 positionMediaType += 1;
199 }
200
201 parameterName = asciiLowercase(parameterName);
202
203 if (positionMediaType < inputMediaType.length) {
204 if (inputMediaType[positionMediaType] === ";") {
205 // eslint-disable-next-line no-continue
206 continue;
207 } // Skip past "="
208
209
210 positionMediaType += 1;
211 }
212
213 let parameterValue = "";
214
215 if (inputMediaType[positionMediaType] === '"') {
216 [parameterValue, positionMediaType] = collectAnHTTPQuotedString(inputMediaType, positionMediaType);
217
218 while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";") {
219 positionMediaType += 1;
220 }
221 } else {
222 while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";") {
223 parameterValue += inputMediaType[positionMediaType];
224 positionMediaType += 1;
225 }
226
227 parameterValue = removeTrailingHTTPWhitespace(parameterValue);
228
229 if (parameterValue === "") {
230 // eslint-disable-next-line no-continue
231 continue;
232 }
233 }
234
235 if (parameterName.length > 0 && solelyContainsHTTPTokenCodePoints(parameterName) && soleyContainsHTTPQuotedStringTokenCodePoints(parameterValue) && !result.parameters.has(parameterName)) {
236 result.parameters.set(parameterName, parameterValue);
237 }
238 }
239
240 return result;
241}
\No newline at end of file