util.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /* global URL, TextDecoder, TextEncoder, btoa, atob */
  2. const EVENT_HANDLER_ATTRIBUTES = [
  3. "onafterprint",
  4. "onbeforeprint",
  5. "onbeforeunload",
  6. "onhashchange",
  7. "onlanguagechange",
  8. "onmessage",
  9. "onmessageerror",
  10. "onoffline",
  11. "ononline",
  12. "onpagehide",
  13. "onpageshow",
  14. "onpopstate",
  15. "onrejectionhandled",
  16. "onstorage",
  17. "onunhandledrejection",
  18. "onunload",
  19. "ongamepadconnected",
  20. "ongamepaddisconnected",
  21. "onabort",
  22. "onblur",
  23. "onfocus",
  24. "oncancel",
  25. "onauxclick",
  26. "onbeforeinput",
  27. "onbeforetoggle",
  28. "oncanplay",
  29. "oncanplaythrough",
  30. "onchange",
  31. "onclick",
  32. "onclose",
  33. "oncontentvisibilityautostatechange",
  34. "oncontextlost",
  35. "oncontextmenu",
  36. "oncontextrestored",
  37. "oncopy",
  38. "oncuechange",
  39. "oncut",
  40. "ondblclick",
  41. "ondrag",
  42. "ondragend",
  43. "ondragenter",
  44. "ondragleave",
  45. "ondragover",
  46. "ondragstart",
  47. "ondrop",
  48. "ondurationchange",
  49. "onemptied",
  50. "onended",
  51. "onformdata",
  52. "oninput",
  53. "oninvalid",
  54. "onkeydown",
  55. "onkeypress",
  56. "onkeyup",
  57. "onload",
  58. "onloadeddata",
  59. "onloadedmetadata",
  60. "onloadstart",
  61. "onmousedown",
  62. "onmouseenter",
  63. "onmouseleave",
  64. "onmousemove",
  65. "onmouseout",
  66. "onmouseover",
  67. "onmouseup",
  68. "onwheel",
  69. "onpaste",
  70. "onpause",
  71. "onplay",
  72. "onplaying",
  73. "onprogress",
  74. "onratechange",
  75. "onreset",
  76. "onresize",
  77. "onscroll",
  78. "onscrollend",
  79. "onsecuritypolicyviolation",
  80. "onseeked",
  81. "onseeking",
  82. "onselect",
  83. "onslotchange",
  84. "onstalled",
  85. "onsubmit",
  86. "onsuspend",
  87. "ontimeupdate",
  88. "onvolumechange",
  89. "onwaiting",
  90. "onselectstart",
  91. "onselectionchange",
  92. "ontoggle",
  93. "onpointercancel",
  94. "onpointerdown",
  95. "onpointerup",
  96. "onpointermove",
  97. "onpointerout",
  98. "onpointerover",
  99. "onpointerenter",
  100. "onpointerleave",
  101. "ongotpointercapture",
  102. "onlostpointercapture",
  103. "onanimationcancel",
  104. "onanimationend",
  105. "onanimationiteration",
  106. "onanimationstart",
  107. "ontransitioncancel",
  108. "ontransitionend",
  109. "ontransitionrun",
  110. "ontransitionstart",
  111. "onerror",
  112. "onfullscreenchange",
  113. "onfullscreenerror"
  114. ];
  115. export {
  116. EVENT_HANDLER_ATTRIBUTES,
  117. decodeQuotedPrintable,
  118. decodeBinary,
  119. decodeMimeHeader,
  120. parseDOM,
  121. decodeBase64,
  122. decodeString,
  123. encodeString,
  124. getCharset,
  125. replaceCharset,
  126. isDocument,
  127. isStylesheet,
  128. isText,
  129. isMultipartAlternative,
  130. getBoundary,
  131. indexOf,
  132. startsWithBoundary,
  133. isLineFeed,
  134. endsWithCRLF,
  135. endsWithLF,
  136. getResourceURI,
  137. resolvePath
  138. };
  139. function decodeQuotedPrintable(array) {
  140. const result = [];
  141. for (let i = 0; i < array.length; i++) {
  142. if (array[i] === 0x3D) {
  143. if (isHex(array[i + 1]) && isHex(array[i + 2])) {
  144. const hex = parseInt(String.fromCharCode(array[i + 1], array[i + 2]), 16);
  145. result.push(hex);
  146. i += 2;
  147. } else {
  148. result.push(array[i]);
  149. }
  150. } else {
  151. result.push(array[i]);
  152. }
  153. }
  154. return new Uint8Array(result);
  155. function isHex(value) {
  156. return value >= 0x30 && value <= 0x39 || value >= 0x41 && value <= 0x46;
  157. }
  158. }
  159. function decodeBinary(array) {
  160. let data = "";
  161. for (let indexData = 0; indexData < array.length; indexData++) {
  162. data += String.fromCharCode(array[indexData]);
  163. }
  164. return btoa(data);
  165. }
  166. function decodeBase64(value, charset) {
  167. const decodedData = new Uint8Array(atob(value).split("").map(char => char.charCodeAt(0)));
  168. return new TextDecoder(charset).decode(decodedData);
  169. }
  170. function decodeMimeHeader(encodedSubject) {
  171. if (encodedSubject && encodedSubject.startsWith("=?") && encodedSubject.endsWith("?=")) {
  172. const encodedSubjectParts = [];
  173. let index = 0;
  174. while (index < encodedSubject.length) {
  175. const start = encodedSubject.indexOf("=?", index);
  176. if (start === -1) {
  177. break;
  178. }
  179. const endCharset = encodedSubject.indexOf("?", start + 2);
  180. if (endCharset === -1) {
  181. break;
  182. }
  183. const charset = encodedSubject.substring(start + 2, endCharset);
  184. const endEncoding = encodedSubject.indexOf("?", endCharset + 1);
  185. if (endEncoding === -1) {
  186. break;
  187. }
  188. const encoding = encodedSubject.substring(endCharset + 1, endEncoding);
  189. const endValue = encodedSubject.indexOf("?=", endEncoding + 1);
  190. if (endValue === -1) {
  191. break;
  192. }
  193. const value = encodedSubject.substring(endEncoding + 1, endValue);
  194. index = endValue + 2;
  195. if (encoding === "Q") {
  196. encodedSubjectParts.push(new TextDecoder(charset).decode(decodeQuotedPrintable(new TextEncoder().encode(value))));
  197. } else if (encoding === "B") {
  198. encodedSubjectParts.push(decodeBase64(value, charset));
  199. }
  200. }
  201. encodedSubject = encodedSubjectParts.join("");
  202. }
  203. return encodedSubject || "";
  204. }
  205. function parseDOM(asset, contentType = "text/html", DOMParser = globalThis.DOMParser) {
  206. let document;
  207. try {
  208. document = new DOMParser().parseFromString(asset, contentType);
  209. // eslint-disable-next-line no-unused-vars
  210. } catch (_) {
  211. document = new DOMParser().parseFromString(asset, "text/html");
  212. }
  213. return {
  214. document,
  215. serialize() {
  216. let result = "";
  217. if (this.document.doctype) {
  218. result += serializeDocType(this.document.doctype) + "\n";
  219. }
  220. result += this.document.documentElement.outerHTML;
  221. return result;
  222. }
  223. };
  224. }
  225. function serializeDocType(doctype) {
  226. return `<!DOCTYPE ${doctype.name}${(doctype.publicId ? ` PUBLIC "${doctype.publicId}"` : "")}${(doctype.systemId ? ` "${doctype.systemId}"` : "")}>`;
  227. }
  228. function decodeString(array, charset) {
  229. return new TextDecoder(charset).decode(array);
  230. }
  231. function encodeString(string, charset) {
  232. return new TextEncoder(charset).encode(string);
  233. }
  234. function getCharset(contentType) {
  235. const charsetMatch = contentType.match(/charset=([^;]+)/);
  236. if (charsetMatch) {
  237. return removeQuotes(charsetMatch[1]).toLowerCase();
  238. }
  239. }
  240. function removeQuotes(value) {
  241. return value.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
  242. }
  243. function replaceCharset(contentType, charset) {
  244. return contentType.replace(/charset=([^;]+)/, `charset=${charset}`);
  245. }
  246. function isDocument(contentType) {
  247. return contentType.startsWith("text/html") || contentType.startsWith("application/xhtml+xml");
  248. }
  249. function isStylesheet(contentType) {
  250. return contentType.startsWith("text/css");
  251. }
  252. function isText(contentType) {
  253. return contentType.startsWith("text/");
  254. }
  255. function isMultipartAlternative(contentType) {
  256. return contentType.startsWith("multipart/alternative");
  257. }
  258. function getBoundary(contentType) {
  259. const contentTypeParams = contentType.split(";");
  260. contentTypeParams.shift();
  261. const boundaryParam = contentTypeParams.find(param => param.startsWith("boundary="));
  262. if (boundaryParam) {
  263. return removeQuotes(boundaryParam.substring(9));
  264. }
  265. }
  266. function indexOf(array, string) {
  267. const stringBytes = new TextEncoder().encode(string);
  268. for (let i = 0; i < array.length; i++) {
  269. if (array[i] === stringBytes[0]) {
  270. let match = true;
  271. for (let j = 1; j < stringBytes.length; j++) {
  272. if (array[i + j] !== stringBytes[j]) {
  273. match = false;
  274. break;
  275. }
  276. }
  277. if (match) {
  278. // return index
  279. return i;
  280. }
  281. }
  282. }
  283. return -1;
  284. }
  285. function isLineFeed(array) {
  286. return array.length == 2 ? array[0] == 0x0D && array[1] == 0x0A : array.length == 1 ? array[0] == 0x0A : false;
  287. }
  288. function endsWithCRLF(array) {
  289. return array.length >= 2 ? array[array.length - 2] == 0x0D && array[array.length - 1] == 0x0A : array.length >= 1 ? array[array.length - 1] == 0x0D : false;
  290. }
  291. function endsWithLF(array) {
  292. return array.length >= 1 ? array[array.length - 1] == 0x0A : false;
  293. }
  294. function startsWithBoundary(array) {
  295. return array.length >= 2 ? array[0] == 0x2D && array[1] == 0x2D : false;
  296. }
  297. function getResourceURI({ contentType, transferEncoding, data }) {
  298. return `data:${contentType};${"base64"},${transferEncoding === "base64" ? data : btoa(unescape(encodeURIComponent(data)))}`;
  299. }
  300. function resolvePath(path, base) {
  301. if (base && !path.startsWith("data:")) {
  302. try {
  303. return new URL(path, base).href;
  304. // eslint-disable-next-line no-unused-vars
  305. } catch (_) {
  306. if (path.startsWith("//")) {
  307. const protocol = base.match(/^[^:]+/);
  308. if (protocol) {
  309. return `${protocol[0]}:${path}`;
  310. } else {
  311. return path;
  312. }
  313. } else {
  314. return path;
  315. }
  316. }
  317. } else {
  318. return path;
  319. }
  320. }