| | |
| | | |
| | | "use strict"; |
| | | |
| | | const Hash = require("./Hash"); |
| | | |
| | | /** @typedef {import("../../declarations/WebpackOptions").HashDigest} Encoding */ |
| | | /** @typedef {import("./Hash")} Hash */ |
| | | /** @typedef {import("../../declarations/WebpackOptions").HashFunction} HashFunction */ |
| | | |
| | | const BULK_SIZE = 3; |
| | | |
| | | // We are using an object instead of a Map as this will stay static during the runtime |
| | | // so access to it can be optimized by v8 |
| | | /** @type {{[key: string]: Map<string, string>}} */ |
| | | const digestCaches = {}; |
| | | |
| | | /** @typedef {() => Hash} HashFactory */ |
| | | |
| | | class BulkUpdateDecorator extends Hash { |
| | | /** |
| | | * @param {Hash | HashFactory} hashOrFactory function to create a hash |
| | | * @param {string=} hashKey key for caching |
| | | */ |
| | | constructor(hashOrFactory, hashKey) { |
| | | super(); |
| | | this.hashKey = hashKey; |
| | | if (typeof hashOrFactory === "function") { |
| | | this.hashFactory = hashOrFactory; |
| | | this.hash = undefined; |
| | | } else { |
| | | this.hashFactory = undefined; |
| | | this.hash = hashOrFactory; |
| | | } |
| | | this.buffer = ""; |
| | | } |
| | | |
| | | /** |
| | | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} |
| | | * @overload |
| | | * @param {string | Buffer} data data |
| | | * @returns {Hash} updated hash |
| | | */ |
| | | /** |
| | | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} |
| | | * @overload |
| | | * @param {string} data data |
| | | * @param {Encoding} inputEncoding data encoding |
| | | * @returns {Hash} updated hash |
| | | */ |
| | | /** |
| | | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} |
| | | * @param {string | Buffer} data data |
| | | * @param {Encoding=} inputEncoding data encoding |
| | | * @returns {Hash} updated hash |
| | | */ |
| | | update(data, inputEncoding) { |
| | | if ( |
| | | inputEncoding !== undefined || |
| | | typeof data !== "string" || |
| | | data.length > BULK_SIZE |
| | | ) { |
| | | if (this.hash === undefined) { |
| | | this.hash = /** @type {HashFactory} */ (this.hashFactory)(); |
| | | } |
| | | if (this.buffer.length > 0) { |
| | | this.hash.update(this.buffer); |
| | | this.buffer = ""; |
| | | } |
| | | if (typeof data === "string" && inputEncoding) { |
| | | this.hash.update(data, inputEncoding); |
| | | } else { |
| | | this.hash.update(data); |
| | | } |
| | | } else { |
| | | this.buffer += data; |
| | | if (this.buffer.length > BULK_SIZE) { |
| | | if (this.hash === undefined) { |
| | | this.hash = /** @type {HashFactory} */ (this.hashFactory)(); |
| | | } |
| | | this.hash.update(this.buffer); |
| | | this.buffer = ""; |
| | | } |
| | | } |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} |
| | | * @overload |
| | | * @returns {Buffer} digest |
| | | */ |
| | | /** |
| | | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} |
| | | * @overload |
| | | * @param {Encoding} encoding encoding of the return value |
| | | * @returns {string} digest |
| | | */ |
| | | /** |
| | | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} |
| | | * @param {Encoding=} encoding encoding of the return value |
| | | * @returns {string | Buffer} digest |
| | | */ |
| | | digest(encoding) { |
| | | let digestCache; |
| | | const buffer = this.buffer; |
| | | if (this.hash === undefined) { |
| | | // short data for hash, we can use caching |
| | | const cacheKey = `${this.hashKey}-${encoding}`; |
| | | digestCache = digestCaches[cacheKey]; |
| | | if (digestCache === undefined) { |
| | | digestCache = digestCaches[cacheKey] = new Map(); |
| | | } |
| | | const cacheEntry = digestCache.get(buffer); |
| | | if (cacheEntry !== undefined) return cacheEntry; |
| | | this.hash = /** @type {HashFactory} */ (this.hashFactory)(); |
| | | } |
| | | if (buffer.length > 0) { |
| | | this.hash.update(buffer); |
| | | } |
| | | if (!encoding) { |
| | | const result = this.hash.digest(); |
| | | if (digestCache !== undefined) { |
| | | digestCache.set(buffer, result); |
| | | } |
| | | return result; |
| | | } |
| | | const digestResult = this.hash.digest(encoding); |
| | | // Compatibility with the old hash library |
| | | const result = |
| | | typeof digestResult === "string" |
| | | ? digestResult |
| | | : /** @type {NodeJS.TypedArray} */ (digestResult).toString(); |
| | | if (digestCache !== undefined) { |
| | | digestCache.set(buffer, result); |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | /* istanbul ignore next */ |
| | | class DebugHash extends Hash { |
| | | constructor() { |
| | | super(); |
| | | this.string = ""; |
| | | } |
| | | |
| | | /** |
| | | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} |
| | | * @overload |
| | | * @param {string | Buffer} data data |
| | | * @returns {Hash} updated hash |
| | | */ |
| | | /** |
| | | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} |
| | | * @overload |
| | | * @param {string} data data |
| | | * @param {Encoding} inputEncoding data encoding |
| | | * @returns {Hash} updated hash |
| | | */ |
| | | /** |
| | | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} |
| | | * @param {string | Buffer} data data |
| | | * @param {Encoding=} inputEncoding data encoding |
| | | * @returns {Hash} updated hash |
| | | */ |
| | | update(data, inputEncoding) { |
| | | if (typeof data !== "string") data = data.toString("utf8"); |
| | | const prefix = Buffer.from("@webpack-debug-digest@").toString("hex"); |
| | | if (data.startsWith(prefix)) { |
| | | data = Buffer.from(data.slice(prefix.length), "hex").toString(); |
| | | } |
| | | this.string += `[${data}](${ |
| | | /** @type {string} */ |
| | | ( |
| | | // eslint-disable-next-line unicorn/error-message |
| | | new Error().stack |
| | | ).split("\n", 3)[2] |
| | | })\n`; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} |
| | | * @overload |
| | | * @returns {Buffer} digest |
| | | */ |
| | | /** |
| | | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} |
| | | * @overload |
| | | * @param {Encoding} encoding encoding of the return value |
| | | * @returns {string} digest |
| | | */ |
| | | /** |
| | | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} |
| | | * @param {Encoding=} encoding encoding of the return value |
| | | * @returns {string | Buffer} digest |
| | | */ |
| | | digest(encoding) { |
| | | return Buffer.from(`@webpack-debug-digest@${this.string}`).toString("hex"); |
| | | } |
| | | } |
| | | |
| | | /** @type {typeof import("crypto") | undefined} */ |
| | | let crypto; |
| | |
| | | let createXXHash64; |
| | | /** @type {typeof import("./hash/md4") | undefined} */ |
| | | let createMd4; |
| | | /** @type {typeof import("./hash/DebugHash") | undefined} */ |
| | | let DebugHash; |
| | | /** @type {typeof import("./hash/BatchedHash") | undefined} */ |
| | | let BatchedHash; |
| | | /** @type {typeof import("./hash/BulkUpdateHash") | undefined} */ |
| | | let BulkUpdateHash; |
| | | |
| | | /** |
| | | * Creates a hash by name or function |
| | |
| | | */ |
| | | module.exports = (algorithm) => { |
| | | if (typeof algorithm === "function") { |
| | | if (BulkUpdateHash === undefined) { |
| | | BulkUpdateHash = require("./hash/BulkUpdateHash"); |
| | | } |
| | | // eslint-disable-next-line new-cap |
| | | return new BulkUpdateDecorator(() => new algorithm()); |
| | | return new BulkUpdateHash(() => new algorithm()); |
| | | } |
| | | switch (algorithm) { |
| | | // TODO add non-cryptographic algorithm here |
| | | case "debug": |
| | | if (DebugHash === undefined) { |
| | | DebugHash = require("./hash/DebugHash"); |
| | | } |
| | | return new DebugHash(); |
| | | case "xxhash64": |
| | | if (createXXHash64 === undefined) { |
| | |
| | | )(createMd4()); |
| | | case "native-md4": |
| | | if (crypto === undefined) crypto = require("crypto"); |
| | | return new BulkUpdateDecorator( |
| | | if (BulkUpdateHash === undefined) { |
| | | BulkUpdateHash = require("./hash/BulkUpdateHash"); |
| | | } |
| | | return new BulkUpdateHash( |
| | | () => |
| | | /** @type {Hash} */ ( |
| | | /** @type {typeof import("crypto")} */ |
| | |
| | | ); |
| | | default: |
| | | if (crypto === undefined) crypto = require("crypto"); |
| | | return new BulkUpdateDecorator( |
| | | if (BulkUpdateHash === undefined) { |
| | | BulkUpdateHash = require("./hash/BulkUpdateHash"); |
| | | } |
| | | return new BulkUpdateHash( |
| | | () => |
| | | /** @type {Hash} */ ( |
| | | /** @type {typeof import("crypto")} */ |