You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			269 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			269 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 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|A|U|I|B|L)-' + 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 arrays    = []; | ||
|  |     var undefs    = []; | ||
|  |     var infinities= []; | ||
|  |     var bigInts = []; | ||
|  |     var urls = []; | ||
|  | 
 | ||
|  |     // 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 && value !== BigInt(0)) { | ||
|  |             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(origValue instanceof Array) { | ||
|  |                 var isSparse = origValue.filter(function(){return true}).length !== origValue.length; | ||
|  |                 if (isSparse) { | ||
|  |                     return '@__A-' + UID + '-' + (arrays.push(origValue) - 1) + '__@'; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if(origValue instanceof URL) { | ||
|  |                 return '@__L-' + UID + '-' + (urls.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 && arrays.length === 0 && undefs.length === 0 && infinities.length === 0 && bigInts.length === 0 && urls.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 === 'A') { | ||
|  |             return "Array.prototype.slice.call(" + serialize(Object.assign({ length: arrays[valueIndex].length }, arrays[valueIndex]), options) + ")"; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (type === 'U') { | ||
|  |             return 'undefined' | ||
|  |         } | ||
|  | 
 | ||
|  |         if (type === 'I') { | ||
|  |             return infinities[valueIndex]; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (type === 'B') { | ||
|  |             return "BigInt(\"" + bigInts[valueIndex] + "\")"; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (type === 'L') { | ||
|  |             return "new URL(\"" + urls[valueIndex].toString() + "\")";  | ||
|  |         } | ||
|  | 
 | ||
|  |         var fn = functions[valueIndex]; | ||
|  | 
 | ||
|  |         return serializeFunc(fn); | ||
|  |     }); | ||
|  | } |