single-file.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /*
  2. * Copyright 2010-2019 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * The code in this file is free software: you can redistribute it and/or
  8. * modify it under the terms of the GNU Affero General Public License
  9. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  10. * of the License, or (at your option) any later version.
  11. *
  12. * The code in this file is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  15. * General Public License for more details.
  16. *
  17. * As additional permission under GNU AGPL version 3 section 7, you may
  18. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  19. * AGPL normally required by section 4, provided you include this license
  20. * notice and a URL through which recipients can access the Corresponding
  21. * Source.
  22. */
  23. /* global
  24. crypto,
  25. fetch,
  26. setTimeout,
  27. Blob,
  28. FileReader,
  29. FontFace,
  30. TextDecoder,
  31. TextEncoder */
  32. this.singlefile.lib.SingleFile = this.singlefile.lib.SingleFile || (() => {
  33. const singlefile = this.singlefile;
  34. const FONT_FACE_TEST_MAX_DELAY = 1000;
  35. const modules = {
  36. helper: singlefile.lib.helper,
  37. srcsetParser: singlefile.lib.vendor.srcsetParser,
  38. cssMinifier: singlefile.lib.vendor.cssMinifier,
  39. htmlMinifier: singlefile.lib.modules.htmlMinifier,
  40. serializer: singlefile.lib.modules.serializer,
  41. fontsMinifier: singlefile.lib.modules.fontsMinifier,
  42. fontsAltMinifier: singlefile.lib.modules.fontsAltMinifier,
  43. cssRulesMinifier: singlefile.lib.modules.cssRulesMinifier,
  44. matchedRules: singlefile.lib.modules.matchedRules,
  45. mediasAltMinifier: singlefile.lib.modules.mediasAltMinifier,
  46. imagesAltMinifier: singlefile.lib.modules.imagesAltMinifier
  47. };
  48. const util = {
  49. getResourceContent,
  50. isValidFontUrl,
  51. digestText
  52. };
  53. const SingleFile = singlefile.lib.core.getClass(singlefile.lib.util.getInstance(modules, util), singlefile.lib.vendor.cssTree);
  54. const fetchResource = (singlefile.lib.fetch.content.resources && singlefile.lib.fetch.content.resources.fetch) || fetch;
  55. return {
  56. getClass: () => SingleFile
  57. };
  58. async function getResourceContent(resourceURL) {
  59. const resourceContent = await fetchResource(resourceURL);
  60. const buffer = await resourceContent.arrayBuffer();
  61. return {
  62. getUrl() {
  63. return resourceContent.url || resourceURL;
  64. },
  65. getContentType() {
  66. return resourceContent.headers && resourceContent.headers.get("content-type");
  67. },
  68. getStatusCode() {
  69. return resourceContent.status;
  70. },
  71. getSize() {
  72. return buffer.byteLength;
  73. },
  74. getText(charset) {
  75. return new TextDecoder(charset).decode(buffer);
  76. },
  77. async getDataUri(contentType) {
  78. const reader = new FileReader();
  79. reader.readAsDataURL(new Blob([buffer], { type: contentType || this.getContentType() }));
  80. return await new Promise((resolve, reject) => {
  81. reader.addEventListener("load", () => resolve(reader.result), false);
  82. reader.addEventListener("error", reject, false);
  83. });
  84. }
  85. };
  86. }
  87. async function digestText(algo, text) {
  88. const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
  89. return hex(hash);
  90. }
  91. // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
  92. function hex(buffer) {
  93. const hexCodes = [];
  94. const view = new DataView(buffer);
  95. for (let i = 0; i < view.byteLength; i += 4) {
  96. const value = view.getUint32(i);
  97. const stringValue = value.toString(16);
  98. const padding = "00000000";
  99. const paddedValue = (padding + stringValue).slice(-padding.length);
  100. hexCodes.push(paddedValue);
  101. }
  102. return hexCodes.join("");
  103. }
  104. async function isValidFontUrl(urlFunction) {
  105. try {
  106. const font = new FontFace("font-test", urlFunction);
  107. await Promise.race([font.load(), new Promise(resolve => setTimeout(() => resolve(true), FONT_FACE_TEST_MAX_DELAY))]);
  108. return true;
  109. } catch (error) {
  110. return false;
  111. }
  112. }
  113. })();