hash-digest.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. /** @typedef {import("../Hash")} Hash */
  7. /** @typedef {import("../../../declarations/WebpackOptions").HashDigest} Encoding */
  8. /** @typedef {"26" | "32" | "36" | "49" | "52" | "58" | "62"} Base */
  9. /* cSpell:disable */
  10. /** @type {Record<Base, string>} */
  11. const ENCODE_TABLE = Object.freeze({
  12. 26: "abcdefghijklmnopqrstuvwxyz",
  13. 32: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
  14. 36: "0123456789abcdefghijklmnopqrstuvwxyz",
  15. 49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ",
  16. 52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
  17. 58: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
  18. 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  19. });
  20. /* cSpell:enable */
  21. const ZERO = BigInt("0");
  22. const EIGHT = BigInt("8");
  23. const FF = BigInt("0xff");
  24. /**
  25. * It encodes octet arrays by doing long divisions on all significant digits in the array, creating a representation of that number in the new base.
  26. * Then for every leading zero in the input (not significant as a number) it will encode as a single leader character.
  27. * This is the first in the alphabet and will decode as 8 bits. The other characters depend upon the base.
  28. * For example, a base58 alphabet packs roughly 5.858 bits per character.
  29. * This means the encoded string 000f (using a base16, 0-f alphabet) will actually decode to 4 bytes unlike a canonical hex encoding which uniformly packs 4 bits into each character.
  30. * While unusual, this does mean that no padding is required, and it works for bases like 43.
  31. * @param {Buffer} buffer buffer
  32. * @param {Base} base base
  33. * @returns {string} encoded buffer
  34. */
  35. const encode = (buffer, base) => {
  36. if (buffer.length === 0) return "";
  37. const bigIntBase = BigInt(ENCODE_TABLE[base].length);
  38. // Convert buffer to BigInt efficiently using bitwise operations
  39. let value = ZERO;
  40. for (let i = 0; i < buffer.length; i++) {
  41. value = (value << EIGHT) | BigInt(buffer[i]);
  42. }
  43. // Convert to baseX string efficiently using array
  44. const digits = [];
  45. if (value === ZERO) return ENCODE_TABLE[base][0];
  46. while (value > ZERO) {
  47. const remainder = Number(value % bigIntBase);
  48. digits.push(ENCODE_TABLE[base][remainder]);
  49. value /= bigIntBase;
  50. }
  51. return digits.reverse().join("");
  52. };
  53. /**
  54. * @param {string} data string
  55. * @param {Base} base base
  56. * @returns {Buffer} buffer
  57. */
  58. const decode = (data, base) => {
  59. if (data.length === 0) return Buffer.from("");
  60. const bigIntBase = BigInt(ENCODE_TABLE[base].length);
  61. // Convert the baseX string to a BigInt value
  62. let value = ZERO;
  63. for (let i = 0; i < data.length; i++) {
  64. const digit = ENCODE_TABLE[base].indexOf(data[i]);
  65. if (digit === -1) {
  66. throw new Error(`Invalid character at position ${i}: ${data[i]}`);
  67. }
  68. value = value * bigIntBase + BigInt(digit);
  69. }
  70. // If value is 0, return a single-byte buffer with value 0
  71. if (value === ZERO) {
  72. return Buffer.alloc(1);
  73. }
  74. // Determine buffer size efficiently by counting bytes
  75. let temp = value;
  76. let byteLength = 0;
  77. while (temp > ZERO) {
  78. temp >>= EIGHT;
  79. byteLength++;
  80. }
  81. // Create buffer and fill it from right to left
  82. const buffer = Buffer.alloc(byteLength);
  83. for (let i = byteLength - 1; i >= 0; i--) {
  84. buffer[i] = Number(value & FF);
  85. value >>= EIGHT;
  86. }
  87. return buffer;
  88. };
  89. // Compatibility with the old hash libraries, they can return different structures, so let's stringify them firstly
  90. /**
  91. * @param {string | { toString: (radix: number) => string }} value value
  92. * @param {string} encoding encoding
  93. * @returns {string} string
  94. */
  95. const toString = (value, encoding) =>
  96. typeof value === "string"
  97. ? value
  98. : Buffer.from(value.toString(16), "hex").toString(
  99. /** @type {NodeJS.BufferEncoding} */
  100. (encoding)
  101. );
  102. /**
  103. * @param {Buffer | { toString: (radix: number) => string }} value value
  104. * @returns {Buffer} buffer
  105. */
  106. const toBuffer = (value) =>
  107. Buffer.isBuffer(value) ? value : Buffer.from(value.toString(16), "hex");
  108. let isBase64URLSupported = false;
  109. try {
  110. isBase64URLSupported = Boolean(Buffer.from("", "base64url"));
  111. } catch (_err) {
  112. // Nothing
  113. }
  114. /**
  115. * @param {Hash} hash hash
  116. * @param {string | Buffer} data data
  117. * @param {Encoding=} encoding encoding of the return value
  118. * @returns {void}
  119. */
  120. const update = (hash, data, encoding) => {
  121. if (encoding === "base64url" && !isBase64URLSupported) {
  122. const base64String = /** @type {string} */ (data)
  123. .replace(/-/g, "+")
  124. .replace(/_/g, "/");
  125. const buf = Buffer.from(base64String, "base64");
  126. hash.update(buf);
  127. return;
  128. } else if (
  129. typeof data === "string" &&
  130. encoding &&
  131. typeof ENCODE_TABLE[/** @type {Base} */ (encoding.slice(4))] !== "undefined"
  132. ) {
  133. const buf = decode(data, /** @type {Base} */ (encoding.slice(4)));
  134. hash.update(buf);
  135. return;
  136. }
  137. if (encoding) {
  138. hash.update(/** @type {string} */ (data), encoding);
  139. } else {
  140. hash.update(data);
  141. }
  142. };
  143. /**
  144. * @overload
  145. * @param {Hash} hash hash
  146. * @returns {Buffer} digest
  147. */
  148. /**
  149. * @overload
  150. * @param {Hash} hash hash
  151. * @param {undefined} encoding encoding of the return value
  152. * @param {boolean=} isSafe true when we await right types from digest(), otherwise false
  153. * @returns {Buffer} digest
  154. */
  155. /**
  156. * @overload
  157. * @param {Hash} hash hash
  158. * @param {Encoding} encoding encoding of the return value
  159. * @param {boolean=} isSafe true when we await right types from digest(), otherwise false
  160. * @returns {string} digest
  161. */
  162. /**
  163. * @param {Hash} hash hash
  164. * @param {Encoding=} encoding encoding of the return value
  165. * @param {boolean=} isSafe true when we await right types from digest(), otherwise false
  166. * @returns {string | Buffer} digest
  167. */
  168. const digest = (hash, encoding, isSafe) => {
  169. if (typeof encoding === "undefined") {
  170. return isSafe ? hash.digest() : toBuffer(hash.digest());
  171. }
  172. if (encoding === "base64url" && !isBase64URLSupported) {
  173. const digest = isSafe
  174. ? hash.digest("base64")
  175. : toString(hash.digest("base64"), "base64");
  176. return digest.replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]+$/, "");
  177. } else if (
  178. typeof ENCODE_TABLE[/** @type {Base} */ (encoding.slice(4))] !== "undefined"
  179. ) {
  180. const buf = isSafe ? hash.digest() : toBuffer(hash.digest());
  181. return encode(
  182. buf,
  183. /** @type {Base} */
  184. (encoding.slice(4))
  185. );
  186. }
  187. return isSafe
  188. ? hash.digest(encoding)
  189. : toString(hash.digest(encoding), encoding);
  190. };
  191. module.exports.decode = decode;
  192. module.exports.digest = digest;
  193. module.exports.encode = encode;
  194. module.exports.update = update;