/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/util/hash/hash-digest.js
234 строки
7 KB
Alexander Akait
Re-encode inline hash digests ([contenthash]/[chunkhash]/[fullhash]/[modulehash]) from full content (#21267)
24 июн 2026, 16:53
Не верифицирован
24 июн 2026, 16:53
7c81bde
Код
Авторство
О чём код?
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Alexander Akait @alexander-akait */ "use strict"; /** @typedef {import("../Hash")} Hash */ /** @typedef {import("../../../declarations/WebpackOptions").HashDigest} Encoding */ /** @typedef {"26" | "32" | "36" | "49" | "52" | "58" | "62"} Base */ /* cSpell:disable */ /** @type {Record<Base, string>} */ const ENCODE_TABLE = Object.freeze({ 26: "abcdefghijklmnopqrstuvwxyz", 32: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", 36: "0123456789abcdefghijklmnopqrstuvwxyz", 49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", 52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 58: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" }); /* cSpell:enable */ const ZERO = BigInt("0"); const EIGHT = BigInt("8"); const FF = BigInt("0xff"); /** * It encodes octet arrays by doing long divisions on all significant digits in the array, creating a representation of that number in the new base. * Then for every leading zero in the input (not significant as a number) it will encode as a single leader character. * This is the first in the alphabet and will decode as 8 bits. The other characters depend upon the base. * For example, a base58 alphabet packs roughly 5.858 bits per character. * This means the encoded string 000f (using a base16, 0-f alphabet) will actually decode to 4 bytes unlike a canonical hex encoding which uniformly packs 4 bits into each character. * While unusual, this does mean that no padding is required, and it works for bases like 43. * @param {Buffer} buffer buffer * @param {Base} base base * @returns {string} encoded buffer */ const encode = (buffer, base) => { if (buffer.length === 0) return ""; const alphabet = ENCODE_TABLE[base]; const bigIntBase = BigInt(alphabet.length); // Leading zero bytes are not significant in the BigInt below, so they would be // lost; Base58Check-style, re-emit each as one leading alphabet[0] char. Keeps // the encoding reversible and its length tracking the input (prefix-stable slices). let zeros = 0; while (zeros < buffer.length && buffer[zeros] === 0) zeros++; // Convert buffer to BigInt efficiently using bitwise operations let value = ZERO; for (let i = 0; i < buffer.length; i++) { value = (value << EIGHT) | BigInt(buffer[i]); } // Convert to baseX string efficiently using array /** @type {string[]} */ const digits = []; while (value > ZERO) { const remainder = Number(value % bigIntBase); digits.push(alphabet[remainder]); value /= bigIntBase; } // Pushed last so they land first after the reverse below. for (let i = 0; i < zeros; i++) digits.push(alphabet[0]); return digits.reverse().join(""); }; /** * Returns buffer. * @param {string} data string * @param {Base} base base * @returns {Buffer} buffer */ const decode = (data, base) => { if (data.length === 0) return Buffer.from(""); const alphabet = ENCODE_TABLE[base]; const bigIntBase = BigInt(alphabet.length); // Leading alphabet[0] chars decode back to leading zero bytes (inverse of encode). const zeroChar = alphabet[0]; let zeros = 0; while (zeros < data.length && data[zeros] === zeroChar) zeros++; // Convert the baseX string to a BigInt value let value = ZERO; for (let i = 0; i < data.length; i++) { const digit = alphabet.indexOf(data[i]); if (digit === -1) { throw new Error(`Invalid character at position ${i}: ${data[i]}`); } value = value * bigIntBase + BigInt(digit); } // Significant byte count of the numeric part (excludes the leading zeros above) let temp = value; let numLength = 0; while (temp > ZERO) { temp >>= EIGHT; numLength++; } // Create buffer and fill the numeric part from right to left, leaving the // leading `zeros` bytes as 0. const buffer = Buffer.alloc(zeros + numLength); for (let i = zeros + numLength - 1; i >= zeros; i--) { buffer[i] = Number(value & FF); value >>= EIGHT; } return buffer; }; // Compatibility with the old hash libraries, they can return different structures, so let's stringify them firstly /** * Returns a string representation. * @param {string | { toString: (radix: number) => string }} value value * @param {string} encoding encoding * @returns {string} string */ const toString = (value, encoding) => typeof value === "string" ? value : Buffer.from(value.toString(16), "hex").toString( /** @type {NodeJS.BufferEncoding} */ (encoding) ); /** * Returns buffer. * @param {Buffer | { toString: (radix: number) => string }} value value * @returns {Buffer} buffer */ const toBuffer = (value) => Buffer.isBuffer(value) ? value : Buffer.from(value.toString(16), "hex"); let isBase64URLSupported = false; try { isBase64URLSupported = Boolean(Buffer.from("", "base64url")); } catch (_err) { // Nothing } /** * Processes the provided hash. * @param {Hash} hash hash * @param {string | Buffer} data data * @param {Encoding=} encoding encoding of the return value * @returns {void} */ const update = (hash, data, encoding) => { if (encoding === "base64url" && !isBase64URLSupported) { const base64String = /** @type {string} */ (data) .replace(/-/g, "+") .replace(/_/g, "/"); const buf = Buffer.from(base64String, "base64"); hash.update(buf); return; } else if ( typeof data === "string" && encoding && typeof ENCODE_TABLE[/** @type {Base} */ (encoding.slice(4))] !== "undefined" ) { const buf = decode(data, /** @type {Base} */ (encoding.slice(4))); hash.update(buf); return; } if (encoding) { hash.update(/** @type {string} */ (data), encoding); } else { hash.update(data); } }; /** * Returns digest. * @overload * @param {Hash} hash hash * @returns {Buffer} digest */ /** * Returns digest. * @overload * @param {Hash} hash hash * @param {undefined} encoding encoding of the return value * @param {boolean=} isSafe true when we await right types from digest(), otherwise false * @returns {Buffer} digest */ /** * Returns digest. * @overload * @param {Hash} hash hash * @param {Encoding} encoding encoding of the return value * @param {boolean=} isSafe true when we await right types from digest(), otherwise false * @returns {string} digest */ /** * Returns digest. * @param {Hash} hash hash * @param {Encoding=} encoding encoding of the return value * @param {boolean=} isSafe true when we await right types from digest(), otherwise false * @returns {string | Buffer} digest */ const digest = (hash, encoding, isSafe) => { if (typeof encoding === "undefined") { return isSafe ? hash.digest() : toBuffer(hash.digest()); } if (encoding === "base64url" && !isBase64URLSupported) { const digest = isSafe ? hash.digest("base64") : toString(hash.digest("base64"), "base64"); return digest.replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]+$/, ""); } else if ( typeof ENCODE_TABLE[/** @type {Base} */ (encoding.slice(4))] !== "undefined" ) { const buf = isSafe ? hash.digest() : toBuffer(hash.digest()); return encode( buf, /** @type {Base} */ (encoding.slice(4)) ); } return isSafe ? hash.digest(encoding) : toString(hash.digest(encoding), encoding); }; module.exports.decode = decode; module.exports.digest = digest; module.exports.encode = encode; module.exports.update = update;