| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 | /*Copyright (c) 2014, Yahoo! Inc. All rights reserved.Copyrights licensed under the New BSD License.See the accompanying LICENSE file for terms.*/'use strict';var randomBytes = require('randombytes');// Generate an internal UID to make the regexp pattern harder to guess.var UID_LENGTH          = 16;var UID                 = generateUID();var PLACE_HOLDER_REGEXP = new RegExp('(\\\\)?"@__(F|R|D|M|S|U|I|B)-' + UID + '-(\\d+)__@"', 'g');var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;var IS_PURE_FUNCTION = /function.*?\(/;var IS_ARROW_FUNCTION = /.*?=>.*?/;var UNSAFE_CHARS_REGEXP   = /[<>\/\u2028\u2029]/g;var RESERVED_SYMBOLS = ['*', 'async'];// Mapping of unsafe HTML and invalid JavaScript line terminator chars to their// Unicode char counterparts which are safe to use in JavaScript strings.var ESCAPED_CHARS = {    '<'     : '\\u003C',    '>'     : '\\u003E',    '/'     : '\\u002F',    '\u2028': '\\u2028',    '\u2029': '\\u2029'};function escapeUnsafeChars(unsafeChar) {    return ESCAPED_CHARS[unsafeChar];}function generateUID() {    var bytes = randomBytes(UID_LENGTH);    var result = '';    for(var i=0; i<UID_LENGTH; ++i) {        result += bytes[i].toString(16);    }    return result;}function deleteFunctions(obj){    var functionKeys = [];    for (var key in obj) {        if (typeof obj[key] === "function") {            functionKeys.push(key);        }    }    for (var i = 0; i < functionKeys.length; i++) {        delete obj[functionKeys[i]];    }}module.exports = function serialize(obj, options) {    options || (options = {});    // Backwards-compatibility for `space` as the second argument.    if (typeof options === 'number' || typeof options === 'string') {        options = {space: options};    }    var functions = [];    var regexps   = [];    var dates     = [];    var maps      = [];    var sets      = [];    var undefs    = [];    var infinities= [];    var bigInts = [];    // Returns placeholders for functions and regexps (identified by index)    // which are later replaced by their string representation.    function replacer(key, value) {        // For nested function        if(options.ignoreFunction){            deleteFunctions(value);        }        if (!value && value !== undefined) {            return value;        }        // If the value is an object w/ a toJSON method, toJSON is called before        // the replacer runs, so we use this[key] to get the non-toJSONed value.        var origValue = this[key];        var type = typeof origValue;        if (type === 'object') {            if(origValue instanceof RegExp) {                return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';            }            if(origValue instanceof Date) {                return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';            }            if(origValue instanceof Map) {                return '@__M-' + UID + '-' + (maps.push(origValue) - 1) + '__@';            }            if(origValue instanceof Set) {                return '@__S-' + UID + '-' + (sets.push(origValue) - 1) + '__@';            }        }        if (type === 'function') {            return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@';        }        if (type === 'undefined') {            return '@__U-' + UID + '-' + (undefs.push(origValue) - 1) + '__@';        }        if (type === 'number' && !isNaN(origValue) && !isFinite(origValue)) {            return '@__I-' + UID + '-' + (infinities.push(origValue) - 1) + '__@';        }        if (type === 'bigint') {            return '@__B-' + UID + '-' + (bigInts.push(origValue) - 1) + '__@';        }        return value;    }    function serializeFunc(fn) {      var serializedFn = fn.toString();      if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {          throw new TypeError('Serializing native function: ' + fn.name);      }      // pure functions, example: {key: function() {}}      if(IS_PURE_FUNCTION.test(serializedFn)) {          return serializedFn;      }      // arrow functions, example: arg1 => arg1+5      if(IS_ARROW_FUNCTION.test(serializedFn)) {          return serializedFn;      }      var argsStartsAt = serializedFn.indexOf('(');      var def = serializedFn.substr(0, argsStartsAt)        .trim()        .split(' ')        .filter(function(val) { return val.length > 0 });      var nonReservedSymbols = def.filter(function(val) {        return RESERVED_SYMBOLS.indexOf(val) === -1      });      // enhanced literal objects, example: {key() {}}      if(nonReservedSymbols.length > 0) {          return (def.indexOf('async') > -1 ? 'async ' : '') + 'function'            + (def.join('').indexOf('*') > -1 ? '*' : '')            + serializedFn.substr(argsStartsAt);      }      // arrow functions      return serializedFn;    }    // Check if the parameter is function    if (options.ignoreFunction && typeof obj === "function") {        obj = undefined;    }    // Protects against `JSON.stringify()` returning `undefined`, by serializing    // to the literal string: "undefined".    if (obj === undefined) {        return String(obj);    }    var str;    // Creates a JSON string representation of the value.    // NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args.    if (options.isJSON && !options.space) {        str = JSON.stringify(obj);    } else {        str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space);    }    // Protects against `JSON.stringify()` returning `undefined`, by serializing    // to the literal string: "undefined".    if (typeof str !== 'string') {        return String(str);    }    // Replace unsafe HTML and invalid JavaScript line terminator chars with    // their safe Unicode char counterpart. This _must_ happen before the    // regexps and functions are serialized and added back to the string.    if (options.unsafe !== true) {        str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);    }    if (functions.length === 0 && regexps.length === 0 && dates.length === 0 && maps.length === 0 && sets.length === 0 && undefs.length === 0 && infinities.length === 0 && bigInts.length === 0) {        return str;    }    // Replaces all occurrences of function, regexp, date, map and set placeholders in the    // JSON string with their string representations. If the original value can    // not be found, then `undefined` is used.    return str.replace(PLACE_HOLDER_REGEXP, function (match, backSlash, type, valueIndex) {        // The placeholder may not be preceded by a backslash. This is to prevent        // replacing things like `"a\"@__R-<UID>-0__@"` and thus outputting        // invalid JS.        if (backSlash) {            return match;        }        if (type === 'D') {            return "new Date(\"" + dates[valueIndex].toISOString() + "\")";        }        if (type === 'R') {            return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";        }        if (type === 'M') {            return "new Map(" + serialize(Array.from(maps[valueIndex].entries()), options) + ")";        }        if (type === 'S') {            return "new Set(" + serialize(Array.from(sets[valueIndex].values()), options) + ")";        }        if (type === 'U') {            return 'undefined'        }        if (type === 'I') {            return infinities[valueIndex];        }        if (type === 'B') {            return "BigInt(\"" + bigInts[valueIndex] + "\")";        }        var fn = functions[valueIndex];        return serializeFunc(fn);    });}
 |