createHash.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Hash = require("./Hash");
  7. const BULK_SIZE = 2000;
  8. // We are using an object instead of a Map as this will stay static during the runtime
  9. // so access to it can be optimized by v8
  10. const digestCaches = {};
  11. class BulkUpdateDecorator extends Hash {
  12. /**
  13. * @param {Hash | function(): Hash} hashOrFactory function to create a hash
  14. * @param {string=} hashKey key for caching
  15. */
  16. constructor(hashOrFactory, hashKey) {
  17. super();
  18. this.hashKey = hashKey;
  19. if (typeof hashOrFactory === "function") {
  20. this.hashFactory = hashOrFactory;
  21. this.hash = undefined;
  22. } else {
  23. this.hashFactory = undefined;
  24. this.hash = hashOrFactory;
  25. }
  26. this.buffer = "";
  27. }
  28. /**
  29. * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
  30. * @param {string|Buffer} data data
  31. * @param {string=} inputEncoding data encoding
  32. * @returns {this} updated hash
  33. */
  34. update(data, inputEncoding) {
  35. if (
  36. inputEncoding !== undefined ||
  37. typeof data !== "string" ||
  38. data.length > BULK_SIZE
  39. ) {
  40. if (this.hash === undefined) this.hash = this.hashFactory();
  41. if (this.buffer.length > 0) {
  42. this.hash.update(this.buffer);
  43. this.buffer = "";
  44. }
  45. this.hash.update(data, inputEncoding);
  46. } else {
  47. this.buffer += data;
  48. if (this.buffer.length > BULK_SIZE) {
  49. if (this.hash === undefined) this.hash = this.hashFactory();
  50. this.hash.update(this.buffer);
  51. this.buffer = "";
  52. }
  53. }
  54. return this;
  55. }
  56. /**
  57. * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
  58. * @param {string=} encoding encoding of the return value
  59. * @returns {string|Buffer} digest
  60. */
  61. digest(encoding) {
  62. let digestCache;
  63. const buffer = this.buffer;
  64. if (this.hash === undefined) {
  65. // short data for hash, we can use caching
  66. const cacheKey = `${this.hashKey}-${encoding}`;
  67. digestCache = digestCaches[cacheKey];
  68. if (digestCache === undefined) {
  69. digestCache = digestCaches[cacheKey] = new Map();
  70. }
  71. const cacheEntry = digestCache.get(buffer);
  72. if (cacheEntry !== undefined) return cacheEntry;
  73. this.hash = this.hashFactory();
  74. }
  75. if (buffer.length > 0) {
  76. this.hash.update(buffer);
  77. }
  78. const digestResult = this.hash.digest(encoding);
  79. const result =
  80. typeof digestResult === "string" ? digestResult : digestResult.toString();
  81. if (digestCache !== undefined) {
  82. digestCache.set(buffer, result);
  83. }
  84. return result;
  85. }
  86. }
  87. /* istanbul ignore next */
  88. class DebugHash extends Hash {
  89. constructor() {
  90. super();
  91. this.string = "";
  92. }
  93. /**
  94. * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
  95. * @param {string|Buffer} data data
  96. * @param {string=} inputEncoding data encoding
  97. * @returns {this} updated hash
  98. */
  99. update(data, inputEncoding) {
  100. if (typeof data !== "string") data = data.toString("utf-8");
  101. if (data.startsWith("debug-digest-")) {
  102. data = Buffer.from(data.slice("debug-digest-".length), "hex").toString();
  103. }
  104. this.string += `[${data}](${new Error().stack.split("\n", 3)[2]})\n`;
  105. return this;
  106. }
  107. /**
  108. * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
  109. * @param {string=} encoding encoding of the return value
  110. * @returns {string|Buffer} digest
  111. */
  112. digest(encoding) {
  113. return "debug-digest-" + Buffer.from(this.string).toString("hex");
  114. }
  115. }
  116. let crypto = undefined;
  117. let createXXHash64 = undefined;
  118. let createMd4 = undefined;
  119. let BatchedHash = undefined;
  120. /**
  121. * Creates a hash by name or function
  122. * @param {string | typeof Hash} algorithm the algorithm name or a constructor creating a hash
  123. * @returns {Hash} the hash
  124. */
  125. module.exports = algorithm => {
  126. if (typeof algorithm === "function") {
  127. return new BulkUpdateDecorator(() => new algorithm());
  128. }
  129. switch (algorithm) {
  130. // TODO add non-cryptographic algorithm here
  131. case "debug":
  132. return new DebugHash();
  133. case "xxhash64":
  134. if (createXXHash64 === undefined) {
  135. createXXHash64 = require("./hash/xxhash64");
  136. if (BatchedHash === undefined) {
  137. BatchedHash = require("./hash/BatchedHash");
  138. }
  139. }
  140. return new BatchedHash(createXXHash64());
  141. case "md4":
  142. if (createMd4 === undefined) {
  143. createMd4 = require("./hash/md4");
  144. if (BatchedHash === undefined) {
  145. BatchedHash = require("./hash/BatchedHash");
  146. }
  147. }
  148. return new BatchedHash(createMd4());
  149. case "native-md4":
  150. if (crypto === undefined) crypto = require("crypto");
  151. return new BulkUpdateDecorator(() => crypto.createHash("md4"), "md4");
  152. default:
  153. if (crypto === undefined) crypto = require("crypto");
  154. return new BulkUpdateDecorator(
  155. () => crypto.createHash(algorithm),
  156. algorithm
  157. );
  158. }
  159. };