/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const AbstractMethodError = require("../AbstractMethodError"); const BULK_SIZE = 1000; class Hash { /** * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} * @param {string|Buffer} data data * @param {string=} inputEncoding data encoding * @returns {this} updated hash */ update(data, inputEncoding) { throw new AbstractMethodError(); } /** * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} * @param {string=} encoding encoding of the return value * @returns {string|Buffer} digest */ digest(encoding) { throw new AbstractMethodError(); } } exports.Hash = Hash; /** @typedef {typeof Hash} HashConstructor */ class BulkUpdateDecorator extends Hash { /** * @param {Hash} hash hash */ constructor(hash) { super(); this.hash = hash; this.buffer = ""; } /** * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} * @param {string|Buffer} data data * @param {string=} inputEncoding data encoding * @returns {this} updated hash */ update(data, inputEncoding) { if ( inputEncoding !== undefined || typeof data !== "string" || data.length > BULK_SIZE ) { if (this.buffer.length > 0) { this.hash.update(this.buffer); this.buffer = ""; } this.hash.update(data, inputEncoding); } else { this.buffer += data; if (this.buffer.length > BULK_SIZE) { this.hash.update(this.buffer); this.buffer = ""; } } return this; } /** * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} * @param {string=} encoding encoding of the return value * @returns {string|Buffer} digest */ digest(encoding) { if (this.buffer.length > 0) { this.hash.update(this.buffer); } var digestResult = this.hash.digest(encoding); return typeof digestResult === "string" ? digestResult : digestResult.toString(); } } /** * 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} * @param {string|Buffer} data data * @param {string=} inputEncoding data encoding * @returns {this} updated hash */ update(data, inputEncoding) { if (typeof data !== "string") data = data.toString("utf-8"); this.string += data; return this; } /** * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} * @param {string=} encoding encoding of the return value * @returns {string|Buffer} digest */ digest(encoding) { return this.string.replace(/[^a-z0-9]+/gi, m => Buffer.from(m).toString("hex") ); } } /** * Creates a hash by name or function * @param {string | HashConstructor} algorithm the algorithm name or a constructor creating a hash * @returns {Hash} the hash */ module.exports = algorithm => { if (typeof algorithm === "function") { return new BulkUpdateDecorator(new algorithm()); } switch (algorithm) { // TODO add non-cryptographic algorithm here case "debug": return new DebugHash(); default: return new BulkUpdateDecorator(require("crypto").createHash(algorithm)); } };