serialize-javascript.js 9.1 KB

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