UNPKG

hash-anything

Version:

Hash any Javascript primitive or object

519 lines (440 loc) 13.8 kB
'use strict'; /** @module hash-anything **/ const Buffer = require('buffer').Buffer; const crypto = require('crypto'); const hashes = crypto.getHashes(); const createHash = crypto.createHash; const TYPE = Object.freeze({ Array: 1, Boolean: 2, Date: 3, Function: 4, Null: 5, Number: 6, Object: 7, RegExp: 8, String: 9, Undefined: 10, NaN : 11, PositiveInfinity: 12, NegativeInfinity: 13, Int8Array: 14, Uint8Array: 15, Uint8ClampedArray: 16, Int16Array: 17, Uint16Array: 18, Int32Array: 19, Uint32Array: 20, Float32Array: 21, Float64Array: 22, Symbol: 23, BigInt: 24, Map: 25, Set: 26, GeneratorFunction: 27, AsyncFunction: 28 }); class Hash { constructor(algorithm) { if (!(this instanceof Hash)) throw new Error('new must be used when calling Hash()'); if (!algorithm) throw new Error('algorithm is required.'); this._buffer = Buffer.alloc(128); this._index = 0; switch (typeof algorithm) { case 'string': algorithm = algorithm.toLowerCase(); for (const hash of hashes) { if (hash.toLowerCase() === algorithm) { this._algorithm = hash; return; } } throw new Error('unrecognized hash algorithm: ' + algorithm); case 'function': this._hashFunction = algorithm; break; default: throw new Error('unrecognized hash algorithm') } } getValue() { const buffer = this._buffer.slice(0, this._index); if (this._hashFunction) return this._hashFunction(buffer); const hasher = createHash(this._algorithm); hasher.update(buffer); return hasher.digest('hex'); } hash(obj) { this._add(obj); return this; } clear() { this._index = 0; return this; } _resizeBuffer() { const buffer = Buffer.alloc(this._buffer.length * 2); this._buffer.copy(buffer); this._buffer = buffer; } _writeInt8(v) { if (this._index + 1 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeUInt8(v, this._index++); } _writeUInt8(v) { if (this._index + 1 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeUInt8(v, this._index++); } _writeInt16LE(v) { if (this._index + 2 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeUInt16LE(v, this._index); this._index += 2; } _writeUInt16LE(v) { if (this._index + 2 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeUInt16LE(v, this._index); this._index += 2; } _writeInt32LE(v) { if (this._index + 4 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeUInt32LE(v, this._index); this._index += 4; } _writeUInt32LE(v) { if (this._index + 4 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeUInt32LE(v, this._index); this._index += 4; } _writeUInt64LE(v) { if (this._index + 8 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeUInt32LE((v << 32) >>> 0, this._index); this._index += 4; this._buffer.writeUInt32LE(v >>> 0, this._index); this._index += 4; } _writeFloatLE(v) { if (this._index + 4 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeFloatLE(v, this._index); this._index += 8; } _writeDoubleLE(v) { if (this._index + 8 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeDoubleLE(v, this._index); this._index += 8; } _writeString(v) { while (this._index + v.length >= this._buffer.length) this._resizeBuffer(); this._buffer.write(v, this._index); this._index += v.length; } _writeBigInt(v) { while (this._index + 8 >= this._buffer.length) this._resizeBuffer(); this._buffer.writeBigUInt64LE(v, this._index); this._index += 8; } _add(obj) { const className = Object.prototype.toString.call(obj); switch (className) { case '[object Array]': this._addArray(obj); return; case '[object Boolean]': this._addBoolean(obj); return; case '[object Date]': this._addDate(obj); return; case '[object Function]': this._addFunction(obj); return; case '[object Null]': this._addNull(); return; case '[object Number]': this._addNumber(obj); return; case '[object Object]': this._addObject(obj); return; case '[object RegExp]': this._addRegExp(obj); return; case '[object String]': this._addString(obj); return; case '[object Undefined]': this._addUndefined(); return; case '[object Int8Array]': this._addInt8Array(obj); return; case '[object Uint8Array]': this._addUint8Array(obj); return; case '[object Uint8ClampedArray]': this._addUint8ClampedArray(obj); return; case '[object Int16Array]': this._addInt16Array(obj); return; case '[object Uint16Array]': this._addUint16Array(obj); return; case '[object Int32Array]': this._addInt32Array(obj); return; case '[object Uint32Array]': this._addUint32Array(obj); return; case '[object Float32Array]': this._addFloat32Array(obj); return; case '[object Float64Array]': this._addFloat64Array(obj); return; case '[object Symbol]': this._addSymbol(obj); return; case '[object BigInt]': this._addBigInt(obj); return; case '[object Map]': this._addMap(obj); return; case '[object Set]': this._addSet(obj); return; case '[object GeneratorFunction]': this._addGeneratorFunction(obj); return; case '[object AsyncFunction]': this._addAsyncFunction(obj); return; case '[object WeakMap]': throw new TypeError('WeakMap can not be hashed.'); case '[object WeakSet]': throw new TypeError('WeakSet can not be hashed.'); case '[object Promise]': throw new TypeError('Promise can not be hashed.'); case '[object Generator]': throw new TypeError('Generator can not be hashed.'); case '[object DataView]': throw new TypeError('DataView can not be hashed.'); case '[object Proxy]': throw new TypeError('Proxy can not be hashed.'); default: this._addObject(obj); return; } } _addArray(arr) { this._writeUInt8(TYPE.Array); const l = arr.length; for (let i = 0; i < l; i++) { this._addNumber(i); this._add(arr[i]); } } _addInt8Array(arr) { this._writeUInt8(TYPE.Int8Array); for (let i = 0; i < arr.length; i++) this._writeInt8(arr[i]); } _addUint8Array(arr) { this._writeUInt8(TYPE.Uint8Array); for (let i = 0; i < arr.length; i++) this._writeUInt8(arr[i]); } _addUint8ClampedArray(arr) { this._writeUInt8(TYPE.Uint8ClampedArray); for (let i = 0; i < arr.length; i++) this._writeUInt8(arr[i]); } _addInt16Array(arr) { this._writeUInt8(TYPE.Int16Array); for (let i = 0; i < arr.length; i++) this._writeInt16LE(arr[i]); } _addUint16Array(arr) { this._writeUInt8(TYPE.Uint16Array); for (let i = 0; i < arr.length; i++) this._writeUInt16LE(arr[i]); } _addInt32Array(arr) { this._writeUInt8(TYPE.Int32Array); for (let i = 0; i < arr.length; i++) this._writeInt32LE(arr[i]); } _addUint32Array(arr) { this._writeUInt8(TYPE.Uint32Array); for (let i = 0; i < arr.length; i++) this._writeUInt32LE(arr[i]); } _addFloat32Array(arr) { this._writeUInt8(TYPE.Float32Array); for (let i = 0; i < arr.length; i++) this._writeFloatLE(arr[i]); } _addFloat64Array(arr) { this._writeUInt8(TYPE.Float64Array); for (let i = 0; i < arr.length; i++) this._writeDoubleLE(arr[i]); } _addBoolean(bool) { this._writeUInt8(TYPE.Boolean); this._writeUInt8(bool ? 0xF : 0xFF); } _addDate(date) { this._writeUInt8(TYPE.Date); this._writeUInt64LE(+date); } _addFunction(func) { this._writeUInt8(TYPE.Function); this._writeString(func.toString()); const proto = Object.getPrototypeOf(func); if (proto && proto !== Object.prototype) this._addObject(proto); } _addGeneratorFunction(func) { this._writeUInt8(TYPE.GeneratorFunction); this._writeString(func.toString()); const proto = Object.getPrototypeOf(func); if (proto && proto !== Object.prototype) this._addObject(proto); } _addAsyncFunction(func) { this._writeUInt8(TYPE.AsyncFunction); this._writeString(func.toString()); const proto = Object.getPrototypeOf(func); if (proto && proto !== Object.prototype) this._addObject(proto); } _addNull() { this._writeUInt8(TYPE.Null); } _addNumber(number) { if (isFinite(number)) { if (Math.floor(number) === number) { //integers this._writeUInt8(TYPE.Number); this._writeUInt64LE(number); } else { //decimal & float numbers this._writeUInt8(TYPE.Number); this._writeDoubleLE(number); } } else { if (isNaN(number)) this._writeUInt8(TYPE.NaN); else if (number === Number.POSITIVE_INFINITY) this._writeUInt8(TYPE.PositiveInfinity); else if (number === Number.NEGATIVE_INFINITY) this._writeUInt8(TYPE.NegativeInfinity); else throw new Error('unhandled infinite number'); } } _addObject(obj) { this._writeUInt8(TYPE.Object); for (let propertyName in obj) { if (obj.hasOwnProperty(propertyName)) { if (this.includeFunc) { if (!this.includeFunc(obj, propertyName)) continue; } this._addString(propertyName); this._add(obj[propertyName]); } } const proto = Object.getPrototypeOf(obj); if (proto && proto !== Object.prototype) this._addObject(proto); } _addRegExp(regex) { this._writeUInt8(TYPE.RegExp); this._writeString(regex.toString()); } _addString(str) { this._writeUInt8(TYPE.String); this._writeString(str); } _addUndefined() { this._writeUInt8(TYPE.Undefined); } _addSymbol(symbol) { this._writeUInt8(TYPE.Symbol); this._writeString(symbol.description); } _addBigInt(bigInt) { this._writeUInt8(TYPE.BigInt); this._writeBigInt(bigInt); } _addMap(map) { this._writeUInt8(TYPE.Map); for (let [key, value] of map) { this._add(key); this._add(value); } } _addSet(set) { this._writeUInt8(TYPE.Set); for (let item of set) this._add(item); } } module.exports = { /** * Constructor for Hash object * @param algorithm {String} - algorithm to use to calculate the hash: 'sha1', 'md5', 'sha256', 'sha512' * @constructor */ Hash: Hash, /** * Returns the SHA1 hash of anything * @param anything */ sha1: function (anything) { const hash = new Hash('sha1'); hash.hash(anything); return hash.getValue(); }, /** * Returns the MD5 hash of anything * @param anything */ md5: function (anything) { const hash = new Hash('md5'); hash.hash(anything); return hash.getValue(); }, /** * Returns the SHA256 hash of anything * @param anything */ sha256: function (anything) { const hash = new Hash('sha256'); hash.hash(anything); return hash.getValue(); }, /** * Returns the SHA512 hash of anything * @param anything */ sha512: function (anything) { const hash = new Hash('sha512'); hash.hash(anything); return hash.getValue(); } };