@ssense/php-serialization
Version:
A library used to serialize and unserialize like it was in php (especially useful for manipulating laravel sessions stored with redis server)
279 lines (260 loc) • 10.1 kB
JavaScript
(function(window, exports) {
var Class=require("./Class").Class;
var util=require("util")
var bigdecimal=require("bigdecimal");
exports.unserialize = unserialize;
exports.serialize=serialize;
exports.Class=Class;
function getCharCodeLen(c) {
var len = c < (1 << 7) ? 1 :
c < (1 << 11) ? 2 :
c < (1 << 16) ? 3 :
c < (1 << 21) ? 4 :
c < (1 << 26) ? 5 :
c < (1 << 31) ? 6 : Number.NaN;
if (Number.isNaN(len))
throw new Error("CharCode is NaN");
else
return len;
}
function read(string,length) {
var counter=0;
var buf='';
var scope='public';
var i=0;
if (string.charCodeAt(0)===0) {
if (string.charAt(1).charCodeAt(0)===42) {
scope="protected";
length-=3;
string=string.substr(3);
} else {
scope="private";
}
}
for (;counter<length;i++,counter++) {
var c=string.charCodeAt(i);
if (c===0 && scope==="private") {
continue;
}
counter += getCharCodeLen(c) - 1;
buf+=String.fromCharCode(c);
}
return {result:buf,scope:scope,length:counter};
}
function readUntil(string, delimiter,delimiter2) {
var buf = "";
if (delimiter2==undefined) {
delimiter2=delimiter;
}
var counter = 0;
for (; ; counter++) {
var rawc = string.charCodeAt(counter);
var c;
if (rawc==0) {
continue;
}
c=String.fromCharCode(rawc);
if (c == delimiter || c==delimiter2) {
break;
}
buf += c;
}
string = string.substr(buf.length + 1);
return {result: buf, buffer: string};
}
function unserialize_item(string) {
var result = "";
var buf = string;
var copy=buf;
var typeResult = readUntil(buf, ':',';');
buf = typeResult.buffer;
var type = typeResult.result;
var buf2, buf2Result;
switch (type.toLowerCase()) {
case 's':
case 'a':
case 'o':
case 'c':
buf2Result = readUntil(buf, ':');
buf = buf2Result.buffer;
buf2 = buf2Result.result;
break;
case 'd':
case 'i':
case 'b':
case 'r':
buf2Result = readUntil(buf, ';');
buf = buf2Result.buffer;
buf2 = buf2Result.result;
break;
}
var length,val;
switch (type.toLowerCase()) {
case 's': //s:len<string>:"<string>";
length = parseInt(buf2); //string length;
var valResult=read(buf.substr(1),length);
val = valResult.result;
var valScope=valResult.scope;
if (valScope==="protected") {
buf=buf.substr(1+val.length+3+2);
} else if (valScope==="private") {
buf=buf.substr(1+val.length+2+2);
} else {
buf=buf.substr(1+val.length+2);
}
result={result:val,type:"string",scope:valScope};
break;
case 'i': //i:<integer>;
val = parseInt(buf2);
result={result:val,type:"integer"};
break;
case 'd': //d:<float>;
val = new bigdecimal.BigDecimal(buf2);
result={result:val,type:'float'};
break;
case 'r': //r:<integer>;
val=parseInt(buf2);
result={result:val,type:'resource'};
break;
case 'a': //a:len<array>:{<key>;<val>.....}
var tmpResult = unserialize_array(buf, parseInt(buf2));
buf = tmpResult.buffer;
result = {result:tmpResult.result,type:'array'};
break;
case 'o': //o:len<object_class_name>:<object_class_name>:len<object>:{<key>;<val>....}
length = parseInt(buf2); // object name length
var objectNameResult=read(buf.substr(1),length);
var objectName=objectNameResult.result;
buf=buf.substr(1+objectNameResult.length+2);
var buf3Result=readUntil(buf,':'); //properties size
var buf3=buf3Result.result;
buf=buf3Result.buffer;
var tmpResult=unserialize_object(buf,parseInt(buf3),objectName);
buf=tmpResult.buffer;
result={result:tmpResult.result,type:'object'};
break;
case 'c': //c:len<class_name>:"<class_name>":len<val>:{<val>}
length=parseInt(buf2); //class name length
var className=buf.substr(1,length);
buf=buf.substr(1+length+2);
var valueLengthBuffer=readUntil(buf,':');
var valueLength=parseInt(valueLengthBuffer.result);
buf=valueLengthBuffer.buffer;
var value=buf.substr(1,valueLength);
buf=buf.substr(1+valueLength+1);
result={result:value,type:'class'+className};
break;
case 'n': //n;
result={result:null,type:'null'};
break;
case 'b': //b:<digit>; digit is either 1 or 0
result={result:buf2==="1", type:'boolean'};
break;
default:
console.log(copy);
throw new Error("unknown token:"+type);
break;
}
return {result: result, buffer: buf};
}
function unserialize_object(string,size,namespace) {
if (size===0) {
return {result:{},buffer:string.substr(2)};
}
var buf = string.substr(1);
var result=new Class(namespace);
for (var i = 0; i < size; i++) {
var keyResult = unserialize_item(buf);
var key=keyResult.result.result;
if (keyResult.result.scope==="private") {
key=key.substr(namespace.length);
}
buf=keyResult.buffer;
var valResult = unserialize_item(buf);
var val=valResult.result;
buf=valResult.buffer;
result.__addAttr__(key,keyResult.result.type,val.result,val.type,keyResult.result.scope);
}
buf=buf.substr(1);
return {result:result,buffer:buf};
}
function unserialize_array(string, size) {
if (size===0) {
return {result:{},buffer:string.substr(2)};
}
var buf = string.substr(1);
var result=new Class("");
for (var i = 0; i < size; i++) {
var keyResult = unserialize_item(buf);
var key=keyResult.result.result;
buf=keyResult.buffer;
var valResult = unserialize_item(buf);
var val=valResult.result;
buf=valResult.buffer;
result.__addAttr__(key,keyResult.result.type,val.result,val.type);
}
buf=buf.substr(1);
return {result:result,buffer:buf};
}
function unserialize(body) {
return unserialize_item(body).result.result;
}
///serialize;
function lenString(string) {
return Buffer.byteLength(string, 'utf8');
}
function serialize_item(body,type,namespace) {
switch (type) {
case "string":
return util.format("s:%d:\"%s\";",lenString(body.toString()),body.toString());
case "integer":
return util.format("i:%d;",parseInt(body));
case "float":
return util.format("d:%s;",body.toString());
case "resource":
return util.format("r:%d;",parseInt(body));
case "boolean":
return util.format("b:%d;",body===true?1:0);
case "null":
return "N;";
case "array":
var buf="";
var count=0;
for (k in body.__attr__) {
if (k==undefined) continue;
buf+=serialize_item(k,body.__keyTypeOf__(k));
buf+=serialize_item(body[k],body.__typeOf__(k));
count++;
}
return util.format("a:%d:{%s}",count,buf);
case "object":
var buf="";
var count=0;
for (k in body.__attr__) {
if (k==undefined) continue;
var key=k;
switch (body.__scopeOf__(k)) {
case "private":
key=String.fromCharCode(0)+body.__name__+String.fromCharCode(0)+key;
break;
case "protected":
key=String.fromCharCode(0)+"*"+String.fromCharCode(0)+key;
break;
}
buf+=serialize_item(key,body.__keyTypeOf__(k));
buf+=serialize_item(body[k],body.__typeOf__(k));
count++;
}
return util.format("O:%d:\"%s\":%d:{%s}",lenString(body.__name__),body.__name__,count,buf);
default:
if (type.substr(0,5)=="class") {
return util.format("C:%d:\"%s\":%d:{%s}",lenString(type)-5,type.substr(5),lenString(body),body);
}
throw new Error("Unknown type:"+type);
break;
}
}
function serialize(body,type) {
return serialize_item(body,type);
}
})((typeof window === 'undefined') ? global : window, (typeof window === 'undefined') ? exports : (window.PhpSerialization= {}));