parse-data-url.js 6.5 KB

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