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 | /** @type {{[key: string]: Map<string, string>}} */
|
---|
15 | const digestCaches = {};
|
---|
16 |
|
---|
17 | /** @typedef {function(): Hash} HashFactory */
|
---|
18 |
|
---|
19 | class BulkUpdateDecorator extends Hash {
|
---|
20 | /**
|
---|
21 | * @param {Hash | HashFactory} hashOrFactory function to create a hash
|
---|
22 | * @param {string=} hashKey key for caching
|
---|
23 | */
|
---|
24 | constructor(hashOrFactory, hashKey) {
|
---|
25 | super();
|
---|
26 | this.hashKey = hashKey;
|
---|
27 | if (typeof hashOrFactory === "function") {
|
---|
28 | this.hashFactory = hashOrFactory;
|
---|
29 | this.hash = undefined;
|
---|
30 | } else {
|
---|
31 | this.hashFactory = undefined;
|
---|
32 | this.hash = hashOrFactory;
|
---|
33 | }
|
---|
34 | this.buffer = "";
|
---|
35 | }
|
---|
36 |
|
---|
37 | /**
|
---|
38 | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
|
---|
39 | * @param {string|Buffer} data data
|
---|
40 | * @param {string=} inputEncoding data encoding
|
---|
41 | * @returns {this} updated hash
|
---|
42 | */
|
---|
43 | update(data, inputEncoding) {
|
---|
44 | if (
|
---|
45 | inputEncoding !== undefined ||
|
---|
46 | typeof data !== "string" ||
|
---|
47 | data.length > BULK_SIZE
|
---|
48 | ) {
|
---|
49 | if (this.hash === undefined)
|
---|
50 | this.hash = /** @type {HashFactory} */ (this.hashFactory)();
|
---|
51 | if (this.buffer.length > 0) {
|
---|
52 | this.hash.update(this.buffer);
|
---|
53 | this.buffer = "";
|
---|
54 | }
|
---|
55 | this.hash.update(data, inputEncoding);
|
---|
56 | } else {
|
---|
57 | this.buffer += data;
|
---|
58 | if (this.buffer.length > BULK_SIZE) {
|
---|
59 | if (this.hash === undefined)
|
---|
60 | this.hash = /** @type {HashFactory} */ (this.hashFactory)();
|
---|
61 | this.hash.update(this.buffer);
|
---|
62 | this.buffer = "";
|
---|
63 | }
|
---|
64 | }
|
---|
65 | return this;
|
---|
66 | }
|
---|
67 |
|
---|
68 | /**
|
---|
69 | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
|
---|
70 | * @param {string=} encoding encoding of the return value
|
---|
71 | * @returns {string|Buffer} digest
|
---|
72 | */
|
---|
73 | digest(encoding) {
|
---|
74 | let digestCache;
|
---|
75 | const buffer = this.buffer;
|
---|
76 | if (this.hash === undefined) {
|
---|
77 | // short data for hash, we can use caching
|
---|
78 | const cacheKey = `${this.hashKey}-${encoding}`;
|
---|
79 | digestCache = digestCaches[cacheKey];
|
---|
80 | if (digestCache === undefined) {
|
---|
81 | digestCache = digestCaches[cacheKey] = new Map();
|
---|
82 | }
|
---|
83 | const cacheEntry = digestCache.get(buffer);
|
---|
84 | if (cacheEntry !== undefined) return cacheEntry;
|
---|
85 | this.hash = /** @type {HashFactory} */ (this.hashFactory)();
|
---|
86 | }
|
---|
87 | if (buffer.length > 0) {
|
---|
88 | this.hash.update(buffer);
|
---|
89 | }
|
---|
90 | const digestResult = this.hash.digest(encoding);
|
---|
91 | const result =
|
---|
92 | typeof digestResult === "string" ? digestResult : digestResult.toString();
|
---|
93 | if (digestCache !== undefined) {
|
---|
94 | digestCache.set(buffer, result);
|
---|
95 | }
|
---|
96 | return result;
|
---|
97 | }
|
---|
98 | }
|
---|
99 |
|
---|
100 | /* istanbul ignore next */
|
---|
101 | class DebugHash extends Hash {
|
---|
102 | constructor() {
|
---|
103 | super();
|
---|
104 | this.string = "";
|
---|
105 | }
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
|
---|
109 | * @param {string|Buffer} data data
|
---|
110 | * @param {string=} inputEncoding data encoding
|
---|
111 | * @returns {this} updated hash
|
---|
112 | */
|
---|
113 | update(data, inputEncoding) {
|
---|
114 | if (typeof data !== "string") data = data.toString("utf-8");
|
---|
115 | const prefix = Buffer.from("@webpack-debug-digest@").toString("hex");
|
---|
116 | if (data.startsWith(prefix)) {
|
---|
117 | data = Buffer.from(data.slice(prefix.length), "hex").toString();
|
---|
118 | }
|
---|
119 | this.string += `[${data}](${
|
---|
120 | /** @type {string} */ (new Error().stack).split("\n", 3)[2]
|
---|
121 | })\n`;
|
---|
122 | return this;
|
---|
123 | }
|
---|
124 |
|
---|
125 | /**
|
---|
126 | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
|
---|
127 | * @param {string=} encoding encoding of the return value
|
---|
128 | * @returns {string|Buffer} digest
|
---|
129 | */
|
---|
130 | digest(encoding) {
|
---|
131 | return Buffer.from(`@webpack-debug-digest@${this.string}`).toString("hex");
|
---|
132 | }
|
---|
133 | }
|
---|
134 |
|
---|
135 | /** @type {typeof import("crypto") | undefined} */
|
---|
136 | let crypto;
|
---|
137 | /** @type {typeof import("./hash/xxhash64") | undefined} */
|
---|
138 | let createXXHash64;
|
---|
139 | /** @type {typeof import("./hash/md4") | undefined} */
|
---|
140 | let createMd4;
|
---|
141 | /** @type {typeof import("./hash/BatchedHash") | undefined} */
|
---|
142 | let BatchedHash;
|
---|
143 |
|
---|
144 | /** @typedef {string | typeof Hash} Algorithm */
|
---|
145 |
|
---|
146 | /**
|
---|
147 | * Creates a hash by name or function
|
---|
148 | * @param {Algorithm} algorithm the algorithm name or a constructor creating a hash
|
---|
149 | * @returns {Hash} the hash
|
---|
150 | */
|
---|
151 | module.exports = algorithm => {
|
---|
152 | if (typeof algorithm === "function") {
|
---|
153 | // eslint-disable-next-line new-cap
|
---|
154 | return new BulkUpdateDecorator(() => new algorithm());
|
---|
155 | }
|
---|
156 | switch (algorithm) {
|
---|
157 | // TODO add non-cryptographic algorithm here
|
---|
158 | case "debug":
|
---|
159 | return new DebugHash();
|
---|
160 | case "xxhash64":
|
---|
161 | if (createXXHash64 === undefined) {
|
---|
162 | createXXHash64 = require("./hash/xxhash64");
|
---|
163 | if (BatchedHash === undefined) {
|
---|
164 | BatchedHash = require("./hash/BatchedHash");
|
---|
165 | }
|
---|
166 | }
|
---|
167 | return new /** @type {typeof import("./hash/BatchedHash")} */ (
|
---|
168 | BatchedHash
|
---|
169 | )(createXXHash64());
|
---|
170 | case "md4":
|
---|
171 | if (createMd4 === undefined) {
|
---|
172 | createMd4 = require("./hash/md4");
|
---|
173 | if (BatchedHash === undefined) {
|
---|
174 | BatchedHash = require("./hash/BatchedHash");
|
---|
175 | }
|
---|
176 | }
|
---|
177 | return new /** @type {typeof import("./hash/BatchedHash")} */ (
|
---|
178 | BatchedHash
|
---|
179 | )(createMd4());
|
---|
180 | case "native-md4":
|
---|
181 | if (crypto === undefined) crypto = require("crypto");
|
---|
182 | return new BulkUpdateDecorator(
|
---|
183 | () => /** @type {typeof import("crypto")} */ (crypto).createHash("md4"),
|
---|
184 | "md4"
|
---|
185 | );
|
---|
186 | default:
|
---|
187 | if (crypto === undefined) crypto = require("crypto");
|
---|
188 | return new BulkUpdateDecorator(
|
---|
189 | () =>
|
---|
190 | /** @type {typeof import("crypto")} */ (crypto).createHash(algorithm),
|
---|
191 | algorithm
|
---|
192 | );
|
---|
193 | }
|
---|
194 | };
|
---|