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