createHash.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const AbstractMethodError = require("../AbstractMethodError");
  7. const BULK_SIZE = 1000;
  8. class Hash {
  9. /**
  10. * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
  11. * @param {string|Buffer} data data
  12. * @param {string=} inputEncoding data encoding
  13. * @returns {this} updated hash
  14. */
  15. update(data, inputEncoding) {
  16. throw new AbstractMethodError();
  17. }
  18. /**
  19. * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
  20. * @param {string=} encoding encoding of the return value
  21. * @returns {string|Buffer} digest
  22. */
  23. digest(encoding) {
  24. throw new AbstractMethodError();
  25. }
  26. }
  27. class BulkUpdateDecorator extends Hash {
  28. /**
  29. * @param {Hash} hash hash
  30. */
  31. constructor(hash) {
  32. super();
  33. this.hash = hash;
  34. this.buffer = "";
  35. }
  36. /**
  37. * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
  38. * @param {string|Buffer} data data
  39. * @param {string=} inputEncoding data encoding
  40. * @returns {this} updated hash
  41. */
  42. update(data, inputEncoding) {
  43. if (
  44. inputEncoding !== undefined ||
  45. typeof data !== "string" ||
  46. data.length > BULK_SIZE
  47. ) {
  48. if (this.buffer.length > 0) {
  49. this.hash.update(this.buffer);
  50. this.buffer = "";
  51. }
  52. this.hash.update(data, inputEncoding);
  53. } else {
  54. this.buffer += data;
  55. if (this.buffer.length > BULK_SIZE) {
  56. this.hash.update(this.buffer);
  57. this.buffer = "";
  58. }
  59. }
  60. return this;
  61. }
  62. /**
  63. * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
  64. * @param {string=} encoding encoding of the return value
  65. * @returns {string|Buffer} digest
  66. */
  67. digest(encoding) {
  68. if (this.buffer.length > 0) {
  69. this.hash.update(this.buffer);
  70. }
  71. var digestResult = this.hash.digest(encoding);
  72. return typeof digestResult === "string"
  73. ? digestResult
  74. : digestResult.toString();
  75. }
  76. }
  77. /**
  78. * istanbul ignore next
  79. */
  80. class DebugHash extends Hash {
  81. constructor() {
  82. super();
  83. this.string = "";
  84. }
  85. /**
  86. * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
  87. * @param {string|Buffer} data data
  88. * @param {string=} inputEncoding data encoding
  89. * @returns {this} updated hash
  90. */
  91. update(data, inputEncoding) {
  92. if (typeof data !== "string") data = data.toString("utf-8");
  93. this.string += data;
  94. return this;
  95. }
  96. /**
  97. * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
  98. * @param {string=} encoding encoding of the return value
  99. * @returns {string|Buffer} digest
  100. */
  101. digest(encoding) {
  102. return this.string.replace(/[^a-z0-9]+/gi, m =>
  103. Buffer.from(m).toString("hex")
  104. );
  105. }
  106. }
  107. /** @type {typeof import("crypto") | undefined} */
  108. let crypto = undefined;
  109. /** @type {typeof import("./hash/md4") | undefined} */
  110. let createMd4 = undefined;
  111. /** @type {typeof import("./hash/BatchedHash") | undefined} */
  112. let BatchedHash = undefined;
  113. /** @type {number} */
  114. const NODE_MAJOR_VERSION = parseInt(process.versions.node, 10);
  115. /**
  116. * Creates a hash by name or function
  117. * @param {string | HashConstructor} algorithm the algorithm name or a constructor creating a hash
  118. * @returns {Hash} the hash
  119. */
  120. module.exports = algorithm => {
  121. if (typeof algorithm === "function") {
  122. return new BulkUpdateDecorator(new algorithm());
  123. }
  124. switch (algorithm) {
  125. // TODO add non-cryptographic algorithm here
  126. case "debug":
  127. return new DebugHash();
  128. case "md4":
  129. if (NODE_MAJOR_VERSION >= 18) {
  130. if (createMd4 === undefined) {
  131. createMd4 = require("./hash/md4");
  132. if (BatchedHash === undefined) {
  133. BatchedHash = require("./hash/BatchedHash");
  134. }
  135. }
  136. return new /** @type {typeof import("./hash/BatchedHash")} */ (BatchedHash)(
  137. createMd4()
  138. );
  139. }
  140. // If we are on Node.js < 18, fall through to the default case
  141. // eslint-disable-next-line no-fallthrough
  142. case "native-md4":
  143. if (NODE_MAJOR_VERSION >= 18) {
  144. if (crypto === undefined) crypto = require("crypto");
  145. return new BulkUpdateDecorator(
  146. /** @type {typeof import("crypto")} */ (crypto).createHash("md4")
  147. );
  148. }
  149. // If we are on Node.js < 18, fall through to the default case
  150. // eslint-disable-next-line no-fallthrough
  151. default:
  152. if (crypto === undefined) crypto = require("crypto");
  153. return new BulkUpdateDecorator(crypto.createHash(algorithm));
  154. }
  155. };
  156. module.exports.Hash = Hash;
  157. /** @typedef {typeof Hash} HashConstructor */