wallet-core/node_modules/structured-clone/serialize.js
2017-08-14 05:02:09 +02:00

223 lines
5.7 KiB
JavaScript

// Implements serialize/deserialize of structured clones.
// Also resolves cycles (code based on cycle.js by Douglas Crockford)
function encodeRegExp (regexp)
{
var flags = '';
if (regexp.global) flags += 'g';
if (regexp.multiline) flags += 'm';
if (regexp.ignoreCase) flags += 'i';
return [flags, regexp.source].join(',');
}
function decodeRegExp (str)
{
var flags = str.match(/^[^,]*/)[0];
var source = str.substr(flags.length + 1);
return new RegExp(source, flags);
}
// The derez recurses through the object, producing the deep copy.
function derez(value, path, objects, paths, buffers)
{
if (Buffer.isBuffer(value)) {
var start = Buffer.concat(buffers).length;
buffers.push(value);
return '\x10b' + [start, value.length].join(',')
}
if (value instanceof Date) {
return '\x10d' + value.toJSON();
}
if (value instanceof RegExp) {
return '\x10r' + encodeRegExp(value);
}
if (value instanceof Error) {
return '\x10e' + value.message
}
if (typeof value == 'string') {
return value.charAt(0) == '\x10' ? '\x10s' + value : value;
}
var i, // The loop counter
name, // Property name
nu; // The new object or array
// typeof null === 'object', so go on if this value is really an object but not
// one of the weird builtin objects.
if (typeof value === 'object' && value !== null &&
!(value instanceof Boolean) &&
!(value instanceof Number) &&
!(value instanceof String)) {
// If the value is an object or array, look to see if we have already
// encountered it. If so, return a $ref/path object. This is a hard way,
// linear search that will get slower as the number of unique objects grows.
i = objects.indexOf(value);
if (i !== -1) {
return '\x10j' + paths[i];
}
// Otherwise, accumulate the unique value and its path.
objects.push(value);
paths.push(path);
// If it is an array, replicate the array.
if (Array.isArray(value)) {
nu = [];
for (i = 0; i < value.length; i += 1) {
nu[i] = derez(value[i],
path + '[' + i + ']',
objects, paths, buffers);
}
} else {
// If it is an object, replicate the object.
nu = {};
for (name in value) {
if (Object.prototype.hasOwnProperty.call(value, name) && value != '__proto__') {
nu[name] = derez(value[name],
path + '[' + JSON.stringify(name) + ']',
objects, paths, buffers);
}
}
}
return nu;
}
return value;
}
function rerez($, $$)
{
// Restore an object that was reduced by decycle. Members whose values are
// objects of the form
// {$ref: PATH}
// are replaced with references to the value found by the PATH. This will
// restore cycles. The object will be mutated.
// The eval function is used to locate the values described by a PATH. The
// root object is kept in a $ variable. A regular expression is used to
// assure that the PATH is extremely well formed. The regexp contains nested
// * quantifiers. That has been known to have extremely bad performance
// problems on some browsers for very long strings. A PATH is expected to be
// reasonably short. A PATH is allowed to belong to a very restricted subset of
// Goessner's JSONPath.
// So,
// var s = '[{"$ref":"$"}]';
// return JSON.retrocycle(JSON.parse(s));
// produces an array containing a single element which is the array itself.
var px =
/^\${1,4}(?:\[(?:\d+|\"(?:[^\\\"\u0000-\u001f]|\\([\\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*\")\])*$/;
function redo (item) {
if (typeof item == 'string' && item.charAt(0) == '\x10') {
switch (item.charAt(1)) {
case 's':
return item.substr(2);
case 'b':
var bounds = item.substr(2).split(',', 2);
return $$.slice(bounds[0] || 0, (bounds[0] || 0) + (bounds[1] || [0]));
case 'd':
return new Date(item.substr(2));
case 'r':
return decodeRegExp(item.substr(2));
case 'e':
return new Error(item.substr(2));
case 'j':
var path = item.substr(2);
if (px.test(path)) {
return eval(path);
}
default: return null;
}
}
if (item && typeof item === 'object') {
rez(item, $$);
}
return item;
}
function rez(value) {
// The rez function walks recursively through the object looking for $ref
// properties. When it finds one that has a value that is a path, then it
// replaces the $ref object with a reference to the value that is found by
// the path.
var i, name;
if (value && typeof value === 'object') {
if (Array.isArray(value)) {
for (i = 0; i < value.length; i += 1) {
value[i] = redo(value[i]);
}
} else {
for (name in value) {
value[name] = redo(value[name]);
}
}
}
}
$ = redo($)
return $;
};
// Public API
exports.serialize = function (object) {
var objects = [], // Keep a reference to each unique object or array
paths = [], // Keep the path to each unique object or array
buffers = []; // Returned buffers
var nilbyte = new Buffer([0x00]);
if (Buffer.isBuffer(object)) {
return Buffer.concat([
nilbyte,
object
]);
}
var json = new Buffer(JSON.stringify(derez(object, '$', objects, paths, buffers)));
if (buffers.length == 0) {
return json;
}
return Buffer.concat([
json,
nilbyte,
Buffer.concat(buffers)
]);
}
exports.deserialize = function (buf) {
for (var i = 0; i <= buf.length; i++) {
if (buf[i] == 0x00 || buf[i] == null) {
break;
}
}
var jsonbuf = buf.slice(0, i);
var bufbuf = buf.slice(i + 1);
// Shortcut for only encoding a root buffer.
if (jsonbuf.length == 0) {
return bufbuf;
}
return rerez(JSON.parse(jsonbuf.toString('utf-8')), bufbuf);
}