AK-21/Graphite-Industrial-Intelligence
0
1'use strict';2 3var crypto = require('crypto');4 5/**6 * Exported function7 *8 * Options:9 *10 * - `algorithm` hash algo to be used by this instance: *'sha1', 'md5'11 * - `excludeValues` {true|*false} hash object keys, values ignored12 * - `encoding` hash encoding, supports 'buffer', '*hex', 'binary', 'base64'13 * - `ignoreUnknown` {true|*false} ignore unknown object types14 * - `replacer` optional function that replaces values before hashing15 * - `respectFunctionProperties` {*true|false} consider function properties when hashing16 * - `respectFunctionNames` {*true|false} consider 'name' property of functions for hashing17 * - `respectType` {*true|false} Respect special properties (prototype, constructor)18 * when hashing to distinguish between types19 * - `unorderedArrays` {true|*false} Sort all arrays before hashing20 * - `unorderedSets` {*true|false} Sort `Set` and `Map` instances before hashing21 * * = default22 *23 * @param {object} object value to hash24 * @param {object} options hashing options25 * @return {string} hash value26 * @api public27 */28exports = module.exports = objectHash;29 30function objectHash(object, options){31 options = applyDefaults(object, options);32 33 return hash(object, options);34}35 36/**37 * Exported sugar methods38 *39 * @param {object} object value to hash40 * @return {string} hash value41 * @api public42 */43exports.sha1 = function(object){44 return objectHash(object);45};46exports.keys = function(object){47 return objectHash(object, {excludeValues: true, algorithm: 'sha1', encoding: 'hex'});48};49exports.MD5 = function(object){50 return objectHash(object, {algorithm: 'md5', encoding: 'hex'});51};52exports.keysMD5 = function(object){53 return objectHash(object, {algorithm: 'md5', encoding: 'hex', excludeValues: true});54};55 56// Internals57var hashes = crypto.getHashes ? crypto.getHashes().slice() : ['sha1', 'md5'];58hashes.push('passthrough');59var encodings = ['buffer', 'hex', 'binary', 'base64'];60 61function applyDefaults(object, sourceOptions){62 sourceOptions = sourceOptions || {};63 64 // create a copy rather than mutating65 var options = {};66 options.algorithm = sourceOptions.algorithm || 'sha1';67 options.encoding = sourceOptions.encoding || 'hex';68 options.excludeValues = sourceOptions.excludeValues ? true : false;69 options.algorithm = options.algorithm.toLowerCase();70 options.encoding = options.encoding.toLowerCase();71 options.ignoreUnknown = sourceOptions.ignoreUnknown !== true ? false : true; // default to false72 options.respectType = sourceOptions.respectType === false ? false : true; // default to true73 options.respectFunctionNames = sourceOptions.respectFunctionNames === false ? false : true;74 options.respectFunctionProperties = sourceOptions.respectFunctionProperties === false ? false : true;75 options.unorderedArrays = sourceOptions.unorderedArrays !== true ? false : true; // default to false76 options.unorderedSets = sourceOptions.unorderedSets === false ? false : true; // default to false77 options.unorderedObjects = sourceOptions.unorderedObjects === false ? false : true; // default to true78 options.replacer = sourceOptions.replacer || undefined;79 options.excludeKeys = sourceOptions.excludeKeys || undefined;80 81 if(typeof object === 'undefined') {82 throw new Error('Object argument required.');83 }84 85 // if there is a case-insensitive match in the hashes list, accept it86 // (i.e. SHA256 for sha256)87 for (var i = 0; i < hashes.length; ++i) {88 if (hashes[i].toLowerCase() === options.algorithm.toLowerCase()) {89 options.algorithm = hashes[i];90 }91 }92 93 if(hashes.indexOf(options.algorithm) === -1){94 throw new Error('Algorithm "' + options.algorithm + '" not supported. ' +95 'supported values: ' + hashes.join(', '));96 }97 98 if(encodings.indexOf(options.encoding) === -1 &&99 options.algorithm !== 'passthrough'){100 throw new Error('Encoding "' + options.encoding + '" not supported. ' +101 'supported values: ' + encodings.join(', '));102 }103 104 return options;105}106 107/** Check if the given function is a native function */108function isNativeFunction(f) {109 if ((typeof f) !== 'function') {110 return false;111 }112 var exp = /^function\s+\w*\s*\(\s*\)\s*{\s+\[native code\]\s+}$/i;113 return exp.exec(Function.prototype.toString.call(f)) != null;114}115 116function hash(object, options) {117 var hashingStream;118 119 if (options.algorithm !== 'passthrough') {120 hashingStream = crypto.createHash(options.algorithm);121 } else {122 hashingStream = new PassThrough();123 }124 125 if (typeof hashingStream.write === 'undefined') {126 hashingStream.write = hashingStream.update;127 hashingStream.end = hashingStream.update;128 }129 130 var hasher = typeHasher(options, hashingStream);131 hasher.dispatch(object);132 if (!hashingStream.update) {133 hashingStream.end('');134 }135 136 if (hashingStream.digest) {137 return hashingStream.digest(options.encoding === 'buffer' ? undefined : options.encoding);138 }139 140 var buf = hashingStream.read();141 if (options.encoding === 'buffer') {142 return buf;143 }144 145 return buf.toString(options.encoding);146}147 148/**149 * Expose streaming API150 *151 * @param {object} object Value to serialize152 * @param {object} options Options, as for hash()153 * @param {object} stream A stream to write the serializiation to154 * @api public155 */156exports.writeToStream = function(object, options, stream) {157 if (typeof stream === 'undefined') {158 stream = options;159 options = {};160 }161 162 options = applyDefaults(object, options);163 164 return typeHasher(options, stream).dispatch(object);165};166 167function typeHasher(options, writeTo, context){168 context = context || [];169 var write = function(str) {170 if (writeTo.update) {171 return writeTo.update(str, 'utf8');172 } else {173 return writeTo.write(str, 'utf8');174 }175 };176 177 return {178 dispatch: function(value){179 if (options.replacer) {180 value = options.replacer(value);181 }182 183 var type = typeof value;184 if (value === null) {185 type = 'null';186 }187 188 //console.log("[DEBUG] Dispatch: ", value, "->", type, " -> ", "_" + type);189 190 return this['_' + type](value);191 },192 _object: function(object) {193 var pattern = (/\[object (.*)\]/i);194 var objString = Object.prototype.toString.call(object);195 var objType = pattern.exec(objString);196 if (!objType) { // object type did not match [object ...]197 objType = 'unknown:[' + objString + ']';198 } else {199 objType = objType[1]; // take only the class name200 }201 202 objType = objType.toLowerCase();203 204 var objectNumber = null;205 206 if ((objectNumber = context.indexOf(object)) >= 0) {207 return this.dispatch('[CIRCULAR:' + objectNumber + ']');208 } else {209 context.push(object);210 }211 212 if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(object)) {213 write('buffer:');214 return write(object);215 }216 217 if(objType !== 'object' && objType !== 'function' && objType !== 'asyncfunction') {218 if(this['_' + objType]) {219 this['_' + objType](object);220 } else if (options.ignoreUnknown) {221 return write('[' + objType + ']');222 } else {223 throw new Error('Unknown object type "' + objType + '"');224 }225 }else{226 var keys = Object.keys(object);227 if (options.unorderedObjects) {228 keys = keys.sort();229 }230 // Make sure to incorporate special properties, so231 // Types with different prototypes will produce232 // a different hash and objects derived from233 // different functions (`new Foo`, `new Bar`) will234 // produce different hashes.235 // We never do this for native functions since some236 // seem to break because of that.237 if (options.respectType !== false && !isNativeFunction(object)) {238 keys.splice(0, 0, 'prototype', '__proto__', 'constructor');239 }240 241 if (options.excludeKeys) {242 keys = keys.filter(function(key) { return !options.excludeKeys(key); });243 }244 245 write('object:' + keys.length + ':');246 var self = this;247 return keys.forEach(function(key){248 self.dispatch(key);249 write(':');250 if(!options.excludeValues) {251 self.dispatch(object[key]);252 }253 write(',');254 });255 }256 },257 _array: function(arr, unordered){258 unordered = typeof unordered !== 'undefined' ? unordered :259 options.unorderedArrays !== false; // default to options.unorderedArrays260 261 var self = this;262 write('array:' + arr.length + ':');263 if (!unordered || arr.length <= 1) {264 return arr.forEach(function(entry) {265 return self.dispatch(entry);266 });267 }268 269 // the unordered case is a little more complicated:270 // since there is no canonical ordering on objects,271 // i.e. {a:1} < {a:2} and {a:1} > {a:2} are both false,272 // we first serialize each entry using a PassThrough stream273 // before sorting.274 // also: we can’t use the same context array for all entries275 // since the order of hashing should *not* matter. instead,276 // we keep track of the additions to a copy of the context array277 // and add all of them to the global context array when we’re done278 var contextAdditions = [];279 var entries = arr.map(function(entry) {280 var strm = new PassThrough();281 var localContext = context.slice(); // make copy282 var hasher = typeHasher(options, strm, localContext);283 hasher.dispatch(entry);284 // take only what was added to localContext and append it to contextAdditions285 contextAdditions = contextAdditions.concat(localContext.slice(context.length));286 return strm.read().toString();287 });288 context = context.concat(contextAdditions);289 entries.sort();290 return this._array(entries, false);291 },292 _date: function(date){293 return write('date:' + date.toJSON());294 },295 _symbol: function(sym){296 return write('symbol:' + sym.toString());297 },298 _error: function(err){299 return write('error:' + err.toString());300 },301 _boolean: function(bool){302 return write('bool:' + bool.toString());303 },304 _string: function(string){305 write('string:' + string.length + ':');306 write(string.toString());307 },308 _function: function(fn){309 write('fn:');310 if (isNativeFunction(fn)) {311 this.dispatch('[native]');312 } else {313 this.dispatch(fn.toString());314 }315 316 if (options.respectFunctionNames !== false) {317 // Make sure we can still distinguish native functions318 // by their name, otherwise String and Function will319 // have the same hash320 this.dispatch("function-name:" + String(fn.name));321 }322 323 if (options.respectFunctionProperties) {324 this._object(fn);325 }326 },327 _number: function(number){328 return write('number:' + number.toString());329 },330 _xml: function(xml){331 return write('xml:' + xml.toString());332 },333 _null: function() {334 return write('Null');335 },336 _undefined: function() {337 return write('Undefined');338 },339 _regexp: function(regex){340 return write('regex:' + regex.toString());341 },342 _uint8array: function(arr){343 write('uint8array:');344 return this.dispatch(Array.prototype.slice.call(arr));345 },346 _uint8clampedarray: function(arr){347 write('uint8clampedarray:');348 return this.dispatch(Array.prototype.slice.call(arr));349 },350 _int8array: function(arr){351 write('int8array:');352 return this.dispatch(Array.prototype.slice.call(arr));353 },354 _uint16array: function(arr){355 write('uint16array:');356 return this.dispatch(Array.prototype.slice.call(arr));357 },358 _int16array: function(arr){359 write('int16array:');360 return this.dispatch(Array.prototype.slice.call(arr));361 },362 _uint32array: function(arr){363 write('uint32array:');364 return this.dispatch(Array.prototype.slice.call(arr));365 },366 _int32array: function(arr){367 write('int32array:');368 return this.dispatch(Array.prototype.slice.call(arr));369 },370 _float32array: function(arr){371 write('float32array:');372 return this.dispatch(Array.prototype.slice.call(arr));373 },374 _float64array: function(arr){375 write('float64array:');376 return this.dispatch(Array.prototype.slice.call(arr));377 },378 _arraybuffer: function(arr){379 write('arraybuffer:');380 return this.dispatch(new Uint8Array(arr));381 },382 _url: function(url) {383 return write('url:' + url.toString(), 'utf8');384 },385 _map: function(map) {386 write('map:');387 var arr = Array.from(map);388 return this._array(arr, options.unorderedSets !== false);389 },390 _set: function(set) {391 write('set:');392 var arr = Array.from(set);393 return this._array(arr, options.unorderedSets !== false);394 },395 _file: function(file) {396 write('file:');397 return this.dispatch([file.name, file.size, file.type, file.lastModfied]);398 },399 _blob: function() {400 if (options.ignoreUnknown) {401 return write('[blob]');402 }403 404 throw Error('Hashing Blob objects is currently not supported\n' +405 '(see https://github.com/puleos/object-hash/issues/26)\n' +406 'Use "options.replacer" or "options.ignoreUnknown"\n');407 },408 _domwindow: function() { return write('domwindow'); },409 _bigint: function(number){410 return write('bigint:' + number.toString());411 },412 /* Node.js standard native objects */413 _process: function() { return write('process'); },414 _timer: function() { return write('timer'); },415 _pipe: function() { return write('pipe'); },416 _tcp: function() { return write('tcp'); },417 _udp: function() { return write('udp'); },418 _tty: function() { return write('tty'); },419 _statwatcher: function() { return write('statwatcher'); },420 _securecontext: function() { return write('securecontext'); },421 _connection: function() { return write('connection'); },422 _zlib: function() { return write('zlib'); },423 _context: function() { return write('context'); },424 _nodescript: function() { return write('nodescript'); },425 _httpparser: function() { return write('httpparser'); },426 _dataview: function() { return write('dataview'); },427 _signal: function() { return write('signal'); },428 _fsevent: function() { return write('fsevent'); },429 _tlswrap: function() { return write('tlswrap'); },430 };431}432 433// Mini-implementation of stream.PassThrough434// We are far from having need for the full implementation, and we can435// make assumptions like "many writes, then only one final read"436// and we can ignore encoding specifics437function PassThrough() {438 return {439 buf: '',440 441 write: function(b) {442 this.buf += b;443 },444 445 end: function(b) {446 this.buf += b;447 },448 449 read: function() {450 return this.buf;451 }452 };453}454 