| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 | 'use strict';var crypto = require('crypto');/** * Exported function * * Options: * *  - `algorithm` hash algo to be used by this instance: *'sha1', 'md5' *  - `excludeValues` {true|*false} hash object keys, values ignored *  - `encoding` hash encoding, supports 'buffer', '*hex', 'binary', 'base64' *  - `ignoreUnknown` {true|*false} ignore unknown object types *  - `replacer` optional function that replaces values before hashing *  - `respectFunctionProperties` {*true|false} consider function properties when hashing *  - `respectFunctionNames` {*true|false} consider 'name' property of functions for hashing *  - `respectType` {*true|false} Respect special properties (prototype, constructor) *    when hashing to distinguish between types *  - `unorderedArrays` {true|*false} Sort all arrays before hashing *  - `unorderedSets` {*true|false} Sort `Set` and `Map` instances before hashing *  * = default * * @param {object} object value to hash * @param {object} options hashing options * @return {string} hash value * @api public */exports = module.exports = objectHash;function objectHash(object, options){  options = applyDefaults(object, options);  return hash(object, options);}/** * Exported sugar methods * * @param {object} object value to hash * @return {string} hash value * @api public */exports.sha1 = function(object){  return objectHash(object);};exports.keys = function(object){  return objectHash(object, {excludeValues: true, algorithm: 'sha1', encoding: 'hex'});};exports.MD5 = function(object){  return objectHash(object, {algorithm: 'md5', encoding: 'hex'});};exports.keysMD5 = function(object){  return objectHash(object, {algorithm: 'md5', encoding: 'hex', excludeValues: true});};// Internalsvar hashes = crypto.getHashes ? crypto.getHashes().slice() : ['sha1', 'md5'];hashes.push('passthrough');var encodings = ['buffer', 'hex', 'binary', 'base64'];function applyDefaults(object, options){  options = options || {};  options.algorithm = options.algorithm || 'sha1';  options.encoding = options.encoding || 'hex';  options.excludeValues = options.excludeValues ? true : false;  options.algorithm = options.algorithm.toLowerCase();  options.encoding = options.encoding.toLowerCase();  options.ignoreUnknown = options.ignoreUnknown !== true ? false : true; // default to false  options.respectType = options.respectType === false ? false : true; // default to true  options.respectFunctionNames = options.respectFunctionNames === false ? false : true;  options.respectFunctionProperties = options.respectFunctionProperties === false ? false : true;  options.unorderedArrays = options.unorderedArrays !== true ? false : true; // default to false  options.unorderedSets = options.unorderedSets === false ? false : true; // default to false  options.unorderedObjects = options.unorderedObjects === false ? false : true; // default to true  options.replacer = options.replacer || undefined;  options.excludeKeys = options.excludeKeys || undefined;  if(typeof object === 'undefined') {    throw new Error('Object argument required.');  }  // if there is a case-insensitive match in the hashes list, accept it  // (i.e. SHA256 for sha256)  for (var i = 0; i < hashes.length; ++i) {    if (hashes[i].toLowerCase() === options.algorithm.toLowerCase()) {      options.algorithm = hashes[i];    }  }  if(hashes.indexOf(options.algorithm) === -1){    throw new Error('Algorithm "' + options.algorithm + '"  not supported. ' +      'supported values: ' + hashes.join(', '));  }  if(encodings.indexOf(options.encoding) === -1 &&     options.algorithm !== 'passthrough'){    throw new Error('Encoding "' + options.encoding + '"  not supported. ' +      'supported values: ' + encodings.join(', '));  }  return options;}/** Check if the given function is a native function */function isNativeFunction(f) {  if ((typeof f) !== 'function') {    return false;  }  var exp = /^function\s+\w*\s*\(\s*\)\s*{\s+\[native code\]\s+}$/i;  return exp.exec(Function.prototype.toString.call(f)) != null;}function hash(object, options) {  var hashingStream;  if (options.algorithm !== 'passthrough') {    hashingStream = crypto.createHash(options.algorithm);  } else {    hashingStream = new PassThrough();  }  if (typeof hashingStream.write === 'undefined') {    hashingStream.write = hashingStream.update;    hashingStream.end   = hashingStream.update;  }  var hasher = typeHasher(options, hashingStream);  hasher.dispatch(object);  if (!hashingStream.update)    hashingStream.end('')  if (hashingStream.digest) {    return hashingStream.digest(options.encoding === 'buffer' ? undefined : options.encoding);  }  var buf = hashingStream.read();  if (options.encoding === 'buffer') {    return buf;  }  return buf.toString(options.encoding);}/** * Expose streaming API * * @param {object} object  Value to serialize * @param {object} options  Options, as for hash() * @param {object} stream  A stream to write the serializiation to * @api public */exports.writeToStream = function(object, options, stream) {  if (typeof stream === 'undefined') {    stream = options;    options = {};  }  options = applyDefaults(object, options);  return typeHasher(options, stream).dispatch(object);};function typeHasher(options, writeTo, context){  context = context || [];  var write = function(str) {    if (writeTo.update)      return writeTo.update(str, 'utf8');    else      return writeTo.write(str, 'utf8');  }  return {    dispatch: function(value){      if (options.replacer) {        value = options.replacer(value);      }      var type = typeof value;      if (value === null) {        type = 'null';      }      //console.log("[DEBUG] Dispatch: ", value, "->", type, " -> ", "_" + type);      return this['_' + type](value);    },    _object: function(object) {      var pattern = (/\[object (.*)\]/i);      var objString = Object.prototype.toString.call(object);      var objType = pattern.exec(objString);      if (!objType) { // object type did not match [object ...]        objType = 'unknown:[' + objString + ']';      } else {        objType = objType[1]; // take only the class name      }      objType = objType.toLowerCase();      var objectNumber = null;      if ((objectNumber = context.indexOf(object)) >= 0) {        return this.dispatch('[CIRCULAR:' + objectNumber + ']');      } else {        context.push(object);      }      if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(object)) {        write('buffer:');        return write(object);      }      if(objType !== 'object' && objType !== 'function') {        if(this['_' + objType]) {          this['_' + objType](object);        } else if (options.ignoreUnknown) {          return write('[' + objType + ']');        } else {          throw new Error('Unknown object type "' + objType + '"');        }      }else{        var keys = Object.keys(object);        if (options.unorderedObjects) {          keys = keys.sort();        }        // Make sure to incorporate special properties, so        // Types with different prototypes will produce        // a different hash and objects derived from        // different functions (`new Foo`, `new Bar`) will        // produce different hashes.        // We never do this for native functions since some        // seem to break because of that.        if (options.respectType !== false && !isNativeFunction(object)) {          keys.splice(0, 0, 'prototype', '__proto__', 'constructor');        }        if (options.excludeKeys) {          keys = keys.filter(function(key) { return !options.excludeKeys(key); });        }        write('object:' + keys.length + ':');        var self = this;        return keys.forEach(function(key){          self.dispatch(key);          write(':');          if(!options.excludeValues) {            self.dispatch(object[key]);          }          write(',');        });      }    },    _array: function(arr, unordered){      unordered = typeof unordered !== 'undefined' ? unordered :        options.unorderedArrays !== false; // default to options.unorderedArrays      var self = this;      write('array:' + arr.length + ':');      if (!unordered || arr.length <= 1) {        return arr.forEach(function(entry) {          return self.dispatch(entry);        });      }      // the unordered case is a little more complicated:      // since there is no canonical ordering on objects,      // i.e. {a:1} < {a:2} and {a:1} > {a:2} are both false,      // we first serialize each entry using a PassThrough stream      // before sorting.      // also: we can’t use the same context array for all entries      // since the order of hashing should *not* matter. instead,      // we keep track of the additions to a copy of the context array      // and add all of them to the global context array when we’re done      var contextAdditions = [];      var entries = arr.map(function(entry) {        var strm = new PassThrough();        var localContext = context.slice(); // make copy        var hasher = typeHasher(options, strm, localContext);        hasher.dispatch(entry);        // take only what was added to localContext and append it to contextAdditions        contextAdditions = contextAdditions.concat(localContext.slice(context.length));        return strm.read().toString();      });      context = context.concat(contextAdditions);      entries.sort();      return this._array(entries, false);    },    _date: function(date){      return write('date:' + date.toJSON());    },    _symbol: function(sym){      return write('symbol:' + sym.toString());    },    _error: function(err){      return write('error:' + err.toString());    },    _boolean: function(bool){      return write('bool:' + bool.toString());    },    _string: function(string){      write('string:' + string.length + ':');      write(string.toString());    },    _function: function(fn){      write('fn:');      if (isNativeFunction(fn)) {        this.dispatch('[native]');      } else {        this.dispatch(fn.toString());      }      if (options.respectFunctionNames !== false) {        // Make sure we can still distinguish native functions        // by their name, otherwise String and Function will        // have the same hash        this.dispatch("function-name:" + String(fn.name));      }      if (options.respectFunctionProperties) {        this._object(fn);      }    },    _number: function(number){      return write('number:' + number.toString());    },    _xml: function(xml){      return write('xml:' + xml.toString());    },    _null: function() {      return write('Null');    },    _undefined: function() {      return write('Undefined');    },    _regexp: function(regex){      return write('regex:' + regex.toString());    },    _uint8array: function(arr){      write('uint8array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _uint8clampedarray: function(arr){      write('uint8clampedarray:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _int8array: function(arr){      write('uint8array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _uint16array: function(arr){      write('uint16array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _int16array: function(arr){      write('uint16array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _uint32array: function(arr){      write('uint32array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _int32array: function(arr){      write('uint32array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _float32array: function(arr){      write('float32array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _float64array: function(arr){      write('float64array:');      return this.dispatch(Array.prototype.slice.call(arr));    },    _arraybuffer: function(arr){      write('arraybuffer:');      return this.dispatch(new Uint8Array(arr));    },    _url: function(url) {      return write('url:' + url.toString(), 'utf8');    },    _map: function(map) {      write('map:');      var arr = Array.from(map);      return this._array(arr, options.unorderedSets !== false);    },    _set: function(set) {      write('set:');      var arr = Array.from(set);      return this._array(arr, options.unorderedSets !== false);    },    _blob: function() {      if (options.ignoreUnknown) {        return write('[blob]');      }      throw Error('Hashing Blob objects is currently not supported\n' +        '(see https://github.com/puleos/object-hash/issues/26)\n' +        'Use "options.replacer" or "options.ignoreUnknown"\n');    },    _domwindow: function() { return write('domwindow'); },    /* Node.js standard native objects */    _process: function() { return write('process'); },    _timer: function() { return write('timer'); },    _pipe: function() { return write('pipe'); },    _tcp: function() { return write('tcp'); },    _udp: function() { return write('udp'); },    _tty: function() { return write('tty'); },    _statwatcher: function() { return write('statwatcher'); },    _securecontext: function() { return write('securecontext'); },    _connection: function() { return write('connection'); },    _zlib: function() { return write('zlib'); },    _context: function() { return write('context'); },    _nodescript: function() { return write('nodescript'); },    _httpparser: function() { return write('httpparser'); },    _dataview: function() { return write('dataview'); },    _signal: function() { return write('signal'); },    _fsevent: function() { return write('fsevent'); },    _tlswrap: function() { return write('tlswrap'); }  };}// Mini-implementation of stream.PassThrough// We are far from having need for the full implementation, and we can// make assumptions like "many writes, then only one final read"// and we can ignore encoding specificsfunction PassThrough() {  return {    buf: '',    write: function(b) {      this.buf += b;    },    end: function(b) {      this.buf += b;    },    read: function() {      return this.buf;    }  };}
 |