1+ /*
2+ Copyright (c) 2014, Yahoo! Inc. All rights reserved.
3+ Copyrights licensed under the New BSD License.
4+ See the accompanying LICENSE file for terms.
5+ */
6+
7+ 'use strict' ;
8+
9+ var randomBytes = require ( 'randombytes' ) ;
10+
11+ // Generate an internal UID to make the regexp pattern harder to guess.
12+ var UID_LENGTH = 16 ;
13+ var UID = generateUID ( ) ;
14+ var PLACE_HOLDER_REGEXP = new RegExp ( '(\\\\)?"@__(F|R|D|M|S|A|U|I|B|L)-' + UID + '-(\\d+)__@"' , 'g' ) ;
15+
16+ var IS_NATIVE_CODE_REGEXP = / \{ \s * \[ n a t i v e c o d e \] \s * \} / g;
17+ var IS_PURE_FUNCTION = / f u n c t i o n .* ?\( / ;
18+ var IS_ARROW_FUNCTION = / .* ?= > .* ?/ ;
19+ var UNSAFE_CHARS_REGEXP = / [ < > \/ \u2028 \u2029 ] / g;
20+
21+ var RESERVED_SYMBOLS = [ '*' , 'async' ] ;
22+
23+ // Mapping of unsafe HTML and invalid JavaScript line terminator chars to their
24+ // Unicode char counterparts which are safe to use in JavaScript strings.
25+ var ESCAPED_CHARS = {
26+ '<' : '\\u003C' ,
27+ '>' : '\\u003E' ,
28+ '/' : '\\u002F' ,
29+ '\u2028' : '\\u2028' ,
30+ '\u2029' : '\\u2029'
31+ } ;
32+
33+ function escapeUnsafeChars ( unsafeChar ) {
34+ return ESCAPED_CHARS [ unsafeChar ] ;
35+ }
36+
37+ function generateUID ( ) {
38+ var bytes = randomBytes ( UID_LENGTH ) ;
39+ var result = '' ;
40+ for ( var i = 0 ; i < UID_LENGTH ; ++ i ) {
41+ result += bytes [ i ] . toString ( 16 ) ;
42+ }
43+ return result ;
44+ }
45+
46+ function deleteFunctions ( obj ) {
47+ var functionKeys = [ ] ;
48+ for ( var key in obj ) {
49+ if ( typeof obj [ key ] === "function" ) {
50+ functionKeys . push ( key ) ;
51+ }
52+ }
53+ for ( var i = 0 ; i < functionKeys . length ; i ++ ) {
54+ delete obj [ functionKeys [ i ] ] ;
55+ }
56+ }
57+
58+ function serialize ( obj , options ) {
59+ options || ( options = { } ) ;
60+
61+ // Backwards-compatibility for `space` as the second argument.
62+ if ( typeof options === 'number' || typeof options === 'string' ) {
63+ options = { space : options } ;
64+ }
65+
66+ var functions = [ ] ;
67+ var regexps = [ ] ;
68+ var dates = [ ] ;
69+ var maps = [ ] ;
70+ var sets = [ ] ;
71+ var arrays = [ ] ;
72+ var undefs = [ ] ;
73+ var infinities = [ ] ;
74+ var bigInts = [ ] ;
75+ var urls = [ ] ;
76+
77+ // Returns placeholders for functions and regexps (identified by index)
78+ // which are later replaced by their string representation.
79+ function replacer ( key , value ) {
80+
81+ // For nested function
82+ if ( options . ignoreFunction ) {
83+ deleteFunctions ( value ) ;
84+ }
85+
86+ if ( ! value && value !== undefined ) {
87+ return value ;
88+ }
89+
90+ // If the value is an object w/ a toJSON method, toJSON is called before
91+ // the replacer runs, so we use this[key] to get the non-toJSONed value.
92+ var origValue = this [ key ] ;
93+ var type = typeof origValue ;
94+
95+ if ( type === 'object' ) {
96+ if ( origValue instanceof RegExp ) {
97+ return '@__R-' + UID + '-' + ( regexps . push ( origValue ) - 1 ) + '__@' ;
98+ }
99+
100+ if ( origValue instanceof Date ) {
101+ return '@__D-' + UID + '-' + ( dates . push ( origValue ) - 1 ) + '__@' ;
102+ }
103+
104+ if ( origValue instanceof Map ) {
105+ return '@__M-' + UID + '-' + ( maps . push ( origValue ) - 1 ) + '__@' ;
106+ }
107+
108+ if ( origValue instanceof Set ) {
109+ return '@__S-' + UID + '-' + ( sets . push ( origValue ) - 1 ) + '__@' ;
110+ }
111+
112+ if ( origValue instanceof Array ) {
113+ var isSparse = origValue . filter ( function ( ) { return true } ) . length !== origValue . length ;
114+ if ( isSparse ) {
115+ return '@__A-' + UID + '-' + ( arrays . push ( origValue ) - 1 ) + '__@' ;
116+ }
117+ }
118+
119+ if ( origValue instanceof URL ) {
120+ return '@__L-' + UID + '-' + ( urls . push ( origValue ) - 1 ) + '__@' ;
121+ }
122+ }
123+
124+ if ( type === 'function' ) {
125+ return '@__F-' + UID + '-' + ( functions . push ( origValue ) - 1 ) + '__@' ;
126+ }
127+
128+ if ( type === 'undefined' ) {
129+ return '@__U-' + UID + '-' + ( undefs . push ( origValue ) - 1 ) + '__@' ;
130+ }
131+
132+ if ( type === 'number' && ! isNaN ( origValue ) && ! isFinite ( origValue ) ) {
133+ return '@__I-' + UID + '-' + ( infinities . push ( origValue ) - 1 ) + '__@' ;
134+ }
135+
136+ if ( type === 'bigint' ) {
137+ return '@__B-' + UID + '-' + ( bigInts . push ( origValue ) - 1 ) + '__@' ;
138+ }
139+
140+ return value ;
141+ }
142+
143+ function serializeFunc ( fn ) {
144+ var serializedFn = fn . toString ( ) ;
145+ if ( IS_NATIVE_CODE_REGEXP . test ( serializedFn ) ) {
146+ throw new TypeError ( 'Serializing native function: ' + fn . name ) ;
147+ }
148+
149+ // pure functions, example: {key: function() {}}
150+ if ( IS_PURE_FUNCTION . test ( serializedFn ) ) {
151+ return serializedFn ;
152+ }
153+
154+ // arrow functions, example: arg1 => arg1+5
155+ if ( IS_ARROW_FUNCTION . test ( serializedFn ) ) {
156+ return serializedFn ;
157+ }
158+
159+ var argsStartsAt = serializedFn . indexOf ( '(' ) ;
160+ var def = serializedFn . substr ( 0 , argsStartsAt )
161+ . trim ( )
162+ . split ( ' ' )
163+ . filter ( function ( val ) { return val . length > 0 } ) ;
164+
165+ var nonReservedSymbols = def . filter ( function ( val ) {
166+ return RESERVED_SYMBOLS . indexOf ( val ) === - 1
167+ } ) ;
168+
169+ // enhanced literal objects, example: {key() {}}
170+ if ( nonReservedSymbols . length > 0 ) {
171+ return ( def . indexOf ( 'async' ) > - 1 ? 'async ' : '' ) + 'function'
172+ + ( def . join ( '' ) . indexOf ( '*' ) > - 1 ? '*' : '' )
173+ + serializedFn . substr ( argsStartsAt ) ;
174+ }
175+
176+ // arrow functions
177+ return serializedFn ;
178+ }
179+
180+ // Check if the parameter is function
181+ if ( options . ignoreFunction && typeof obj === "function" ) {
182+ obj = undefined ;
183+ }
184+ // Protects against `JSON.stringify()` returning `undefined`, by serializing
185+ // to the literal string: "undefined".
186+ if ( obj === undefined ) {
187+ return String ( obj ) ;
188+ }
189+
190+ var str ;
191+
192+ // Creates a JSON string representation of the value.
193+ // NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args.
194+ if ( options . isJSON && ! options . space ) {
195+ str = JSON . stringify ( obj ) ;
196+ } else {
197+ str = JSON . stringify ( obj , options . isJSON ? null : replacer , options . space ) ;
198+ }
199+
200+ // Protects against `JSON.stringify()` returning `undefined`, by serializing
201+ // to the literal string: "undefined".
202+ if ( typeof str !== 'string' ) {
203+ return String ( str ) ;
204+ }
205+
206+ // Replace unsafe HTML and invalid JavaScript line terminator chars with
207+ // their safe Unicode char counterpart. This _must_ happen before the
208+ // regexps and functions are serialized and added back to the string.
209+ if ( options . unsafe !== true ) {
210+ str = str . replace ( UNSAFE_CHARS_REGEXP , escapeUnsafeChars ) ;
211+ }
212+
213+ 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 ) {
214+ return str ;
215+ }
216+
217+ // Replaces all occurrences of function, regexp, date, map and set placeholders in the
218+ // JSON string with their string representations. If the original value can
219+ // not be found, then `undefined` is used.
220+ return str . replace ( PLACE_HOLDER_REGEXP , function ( match , backSlash , type , valueIndex ) {
221+ // The placeholder may not be preceded by a backslash. This is to prevent
222+ // replacing things like `"a\"@__R-<UID>-0__@"` and thus outputting
223+ // invalid JS.
224+ if ( backSlash ) {
225+ return match ;
226+ }
227+
228+ if ( type === 'D' ) {
229+ return "new Date(\"" + dates [ valueIndex ] . toISOString ( ) + "\")" ;
230+ }
231+
232+ if ( type === 'R' ) {
233+ return "new RegExp(" + serialize ( regexps [ valueIndex ] . source ) + ", \"" + regexps [ valueIndex ] . flags + "\")" ;
234+ }
235+
236+ if ( type === 'M' ) {
237+ return "new Map(" + serialize ( Array . from ( maps [ valueIndex ] . entries ( ) ) , options ) + ")" ;
238+ }
239+
240+ if ( type === 'S' ) {
241+ return "new Set(" + serialize ( Array . from ( sets [ valueIndex ] . values ( ) ) , options ) + ")" ;
242+ }
243+
244+ if ( type === 'A' ) {
245+ return "Array.prototype.slice.call(" + serialize ( Object . assign ( { length : arrays [ valueIndex ] . length } , arrays [ valueIndex ] ) , options ) + ")" ;
246+ }
247+
248+ if ( type === 'U' ) {
249+ return 'undefined'
250+ }
251+
252+ if ( type === 'I' ) {
253+ return infinities [ valueIndex ] ;
254+ }
255+
256+ if ( type === 'B' ) {
257+ return "BigInt(\"" + bigInts [ valueIndex ] + "\")" ;
258+ }
259+
260+ if ( type === 'L' ) {
261+ return "new URL(\"" + urls [ valueIndex ] . toString ( ) + "\")" ;
262+ }
263+
264+ var fn = functions [ valueIndex ] ;
265+
266+ return serializeFunc ( fn ) ;
267+ } ) ;
268+ }
269+
270+ export {
271+ serialize
272+ }
0 commit comments