123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.default = parseDataUrl;
- const removeLeadingAndTrailingHTTPWhitespace = string => string.replace(/^[ \t\n\r]+/, "").replace(/[ \t\n\r]+$/, "");
- const removeTrailingHTTPWhitespace = string => string.replace(/[ \t\n\r]+$/, "");
- const isHTTPWhitespaceChar = char => char === " " || char === "\t" || char === "\n" || char === "\r";
- const solelyContainsHTTPTokenCodePoints = string => /^[-!#$%&'*+.^_`|~A-Za-z0-9]*$/.test(string);
- const soleyContainsHTTPQuotedStringTokenCodePoints = string => /^[\t\u0020-\u007E\u0080-\u00FF]*$/.test(string);
- const asciiLowercase = string => string.replace(/[A-Z]/g, l => l.toLowerCase());
- const collectAnHTTPQuotedString = (input, position) => {
- let value = "";
- // eslint-disable-next-line no-param-reassign
- position += 1;
- // eslint-disable-next-line no-constant-condition
- while (true) {
- while (position < input.length && input[position] !== '"' && input[position] !== "\\") {
- value += input[position];
- // eslint-disable-next-line no-param-reassign
- position += 1;
- }
- if (position >= input.length) {
- break;
- }
- const quoteOrBackslash = input[position];
- // eslint-disable-next-line no-param-reassign
- position += 1;
- if (quoteOrBackslash === "\\") {
- if (position >= input.length) {
- value += "\\";
- break;
- }
- value += input[position];
- // eslint-disable-next-line no-param-reassign
- position += 1;
- } else {
- break;
- }
- }
- return [value, position];
- };
- function isASCIIHex(c) {
- return c >= 0x30 && c <= 0x39 || c >= 0x41 && c <= 0x46 || c >= 0x61 && c <= 0x66;
- }
- function percentDecodeBytes(input) {
- const output = new Uint8Array(input.byteLength);
- let outputIndex = 0;
- for (let i = 0; i < input.byteLength; ++i) {
- const byte = input[i];
- if (byte !== 0x25) {
- output[outputIndex] = byte;
- } else if (byte === 0x25 && (!isASCIIHex(input[i + 1]) || !isASCIIHex(input[i + 2]))) {
- output[outputIndex] = byte;
- } else {
- output[outputIndex] = parseInt(String.fromCodePoint(input[i + 1], input[i + 2]), 16);
- i += 2;
- }
- outputIndex += 1;
- }
- return output.slice(0, outputIndex);
- }
- /**
- * A lookup table for atob(), which converts an ASCII character to the
- * corresponding six-bit number.
- */
- const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- function atobLookup(chr) {
- const index = characters.indexOf(chr);
- // Throw exception if character is not in the lookup string; should not be hit in tests
- // eslint-disable-next-line no-undefined
- return index < 0 ? undefined : index;
- }
- /**
- * Implementation of atob() according to the HTML and Infra specs, except that
- * instead of throwing INVALID_CHARACTER_ERR we return null.
- */
- function atob(input) {
- /* eslint-disable no-bitwise */
- // Web IDL requires DOMStrings to just be converted using ECMAScript
- // ToString, which in our case amounts to using a template literal.
- let data = `${input}`;
- // "Remove all ASCII whitespace from data."
- data = data.replace(/[ \t\n\f\r]/g, "");
- // "If data's length divides by 4 leaving no remainder, then: if data ends
- // with one or two U+003D (=) code points, then remove them from data."
- if (data.length % 4 === 0) {
- data = data.replace(/==?$/, "");
- }
- // "If data's length divides by 4 leaving a remainder of 1, then return
- // failure."
- //
- // "If data contains a code point that is not one of
- //
- // U+002B (+)
- // U+002F (/)
- // ASCII alphanumeric
- //
- // then return failure."
- if (data.length % 4 === 1 || /[^+/0-9A-Za-z]/.test(data)) {
- return null;
- }
- // "Let output be an empty byte sequence."
- let output = "";
- // "Let buffer be an empty buffer that can have bits appended to it."
- //
- // We append bits via left-shift and or. accumulatedBits is used to track
- // when we've gotten to 24 bits.
- let buffer = 0;
- let accumulatedBits = 0;
- // "Let position be a position variable for data, initially pointing at the
- // start of data."
- //
- // "While position does not point past the end of data:"
- for (let i = 0; i < data.length; i++) {
- // "Find the code point pointed to by position in the second column of
- // Table 1: The Base 64 Alphabet of RFC 4648. Let n be the number given in
- // the first cell of the same row.
- //
- // "Append to buffer the six bits corresponding to n, most significant bit
- // first."
- //
- // atobLookup() implements the table from RFC 4648.
- // eslint-disable-next-line no-bitwise
- buffer <<= 6;
- // eslint-disable-next-line no-bitwise
- buffer |= atobLookup(data[i]);
- accumulatedBits += 6;
- // "If buffer has accumulated 24 bits, interpret them as three 8-bit
- // big-endian numbers. Append three bytes with values equal to those
- // numbers to output, in the same order, and then empty buffer."
- if (accumulatedBits === 24) {
- output += String.fromCharCode((buffer & 0xff0000) >> 16);
- output += String.fromCharCode((buffer & 0xff00) >> 8);
- output += String.fromCharCode(buffer & 0xff);
- accumulatedBits = 0;
- buffer = 0;
- }
- // "Advance position by 1."
- }
- // "If buffer is not empty, it contains either 12 or 18 bits. If it contains
- // 12 bits, then discard the last four and interpret the remaining eight as
- // an 8-bit big-endian number. If it contains 18 bits, then discard the last
- // two and interpret the remaining 16 as two 8-bit big-endian numbers. Append
- // the one or two bytes with values equal to those one or two numbers to
- // output, in the same order."
- if (accumulatedBits === 12) {
- buffer >>= 4;
- output += String.fromCharCode(buffer);
- } else if (accumulatedBits === 18) {
- buffer >>= 2;
- output += String.fromCharCode((buffer & 0xff00) >> 8);
- output += String.fromCharCode(buffer & 0xff);
- }
- /* eslint-enable no-bitwise */
- // "Return output."
- return output;
- }
- function parseDataUrl(stringInput) {
- let parsedUrl;
- try {
- parsedUrl = new URL(stringInput);
- } catch (error) {
- return null;
- }
- if (parsedUrl.protocol !== "data:") {
- return null;
- }
- parsedUrl.hash = "";
- // `5` is value of `'data:'.length`
- const input = parsedUrl.toString().substring(5);
- let position = 0;
- let mediaType = "";
- while (position < input.length && input[position] !== ",") {
- mediaType += input[position];
- position += 1;
- }
- mediaType = mediaType.replace(/^[ \t\n\f\r]+/, "").replace(/[ \t\n\f\r]+$/, "");
- if (position === input.length) {
- return null;
- }
- position += 1;
- const encodedBody = input.substring(position);
- let body = Buffer.from(percentDecodeBytes(Buffer.from(encodedBody, "utf-8")));
- // Can't use /i regexp flag because it isn't restricted to ASCII.
- const mimeTypeBase64MatchResult = /(.*); *[Bb][Aa][Ss][Ee]64$/.exec(mediaType);
- if (mimeTypeBase64MatchResult) {
- const stringBody = body.toString("binary");
- const asString = atob(stringBody);
- if (asString === null) {
- return null;
- }
- body = Buffer.from(asString, "binary");
- [, mediaType] = mimeTypeBase64MatchResult;
- }
- if (mediaType.startsWith(";")) {
- mediaType = `text/plain ${mediaType}`;
- }
- const result = {
- // eslint-disable-next-line no-undefined
- type: undefined,
- // eslint-disable-next-line no-undefined
- subtype: undefined,
- parameters: new Map(),
- isBase64: Boolean(mimeTypeBase64MatchResult),
- body
- };
- if (!mediaType) {
- return result;
- }
- const inputMediaType = removeLeadingAndTrailingHTTPWhitespace(mediaType);
- let positionMediaType = 0;
- let type = "";
- while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== "/") {
- type += inputMediaType[positionMediaType];
- positionMediaType += 1;
- }
- if (type.length === 0 || !solelyContainsHTTPTokenCodePoints(type)) {
- return result;
- }
- if (positionMediaType >= inputMediaType.length) {
- return result;
- }
- // Skips past "/"
- positionMediaType += 1;
- let subtype = "";
- while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";") {
- subtype += inputMediaType[positionMediaType];
- positionMediaType += 1;
- }
- subtype = removeTrailingHTTPWhitespace(subtype);
- if (subtype.length === 0 || !solelyContainsHTTPTokenCodePoints(subtype)) {
- return result;
- }
- result.type = asciiLowercase(type);
- result.subtype = asciiLowercase(subtype);
- while (positionMediaType < inputMediaType.length) {
- // Skip past ";"
- positionMediaType += 1;
- while (isHTTPWhitespaceChar(inputMediaType[positionMediaType])) {
- positionMediaType += 1;
- }
- let parameterName = "";
- while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";" && inputMediaType[positionMediaType] !== "=") {
- parameterName += inputMediaType[positionMediaType];
- positionMediaType += 1;
- }
- parameterName = asciiLowercase(parameterName);
- if (positionMediaType < inputMediaType.length) {
- if (inputMediaType[positionMediaType] === ";") {
- // eslint-disable-next-line no-continue
- continue;
- }
- // Skip past "="
- positionMediaType += 1;
- }
- let parameterValue = "";
- if (inputMediaType[positionMediaType] === '"') {
- [parameterValue, positionMediaType] = collectAnHTTPQuotedString(inputMediaType, positionMediaType);
- while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";") {
- positionMediaType += 1;
- }
- } else {
- while (positionMediaType < inputMediaType.length && inputMediaType[positionMediaType] !== ";") {
- parameterValue += inputMediaType[positionMediaType];
- positionMediaType += 1;
- }
- parameterValue = removeTrailingHTTPWhitespace(parameterValue);
- if (parameterValue === "") {
- // eslint-disable-next-line no-continue
- continue;
- }
- }
- if (parameterName.length > 0 && solelyContainsHTTPTokenCodePoints(parameterName) && soleyContainsHTTPQuotedStringTokenCodePoints(parameterValue) && !result.parameters.has(parameterName)) {
- result.parameters.set(parameterName, parameterValue);
- }
- }
- return result;
- }
|