You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
6.4 KiB
280 lines
6.4 KiB
/*! |
|
* php-unserialize-js JavaScript Library |
|
* https://github.com/bd808/php-unserialize-js |
|
* |
|
* Copyright 2013 Bryan Davis and contributors |
|
* Released under the MIT license |
|
* http://www.opensource.org/licenses/MIT |
|
*/ |
|
|
|
export default function (phpstr) { |
|
var idx = 0 |
|
, refStack = [] |
|
, ridx = 0 |
|
, parseNext // forward declaraton for "use strict" |
|
|
|
, readLength = function () { |
|
var del = phpstr.indexOf(':', idx) |
|
, val = phpstr.substring(idx, del); |
|
idx = del + 2; |
|
return parseInt(val, 10); |
|
} //end readLength |
|
|
|
, readInt = function () { |
|
var del = phpstr.indexOf(';', idx) |
|
, val = phpstr.substring(idx, del); |
|
idx = del + 1; |
|
return parseInt(val, 10); |
|
} //end readInt |
|
|
|
, parseAsInt = function () { |
|
var val = readInt(); |
|
refStack[ridx++] = val; |
|
return val; |
|
} //end parseAsInt |
|
|
|
, parseAsFloat = function () { |
|
var del = phpstr.indexOf(';', idx) |
|
, val = phpstr.substring(idx, del); |
|
idx = del + 1; |
|
val = parseFloat(val); |
|
refStack[ridx++] = val; |
|
return val; |
|
} //end parseAsFloat |
|
|
|
, parseAsBoolean = function () { |
|
var del = phpstr.indexOf(';', idx) |
|
, val = phpstr.substring(idx, del); |
|
idx = del + 1; |
|
val = ("1" === val) ? true : false; |
|
refStack[ridx++] = val; |
|
return val; |
|
} //end parseAsBoolean |
|
|
|
, readString = function () { |
|
var len = readLength() |
|
, utfLen = 0 |
|
, bytes = 0 |
|
, ch |
|
, val; |
|
while (bytes < len) { |
|
ch = phpstr.charCodeAt(idx + utfLen++); |
|
if (ch <= 0x007F) { |
|
bytes++; |
|
} else if (ch > 0x07FF) { |
|
bytes += 3; |
|
} else { |
|
bytes += 2; |
|
} |
|
} |
|
val = phpstr.substring(idx, idx + utfLen); |
|
idx += utfLen + 2; |
|
return val; |
|
} //end readString |
|
|
|
, parseAsString = function () { |
|
var val = readString(); |
|
refStack[ridx++] = val; |
|
return val; |
|
} //end parseAsString |
|
|
|
, readType = function () { |
|
var type = phpstr.charAt(idx); |
|
idx += 2; |
|
return type; |
|
} //end readType |
|
|
|
, readKey = function () { |
|
var type = readType(); |
|
switch (type) { |
|
case 'i': |
|
return readInt(); |
|
case 's': |
|
var key = readString(); |
|
if (key[key.length - 2] === '"') { // missing null bytes gives invalid length |
|
key = key.substr(0, key.length - 2); |
|
idx -= 2; |
|
} |
|
return key; |
|
default: |
|
throw { |
|
name: "Parse Error", |
|
message: "Unknown key type '" + type + "' at position " + |
|
(idx - 2) |
|
}; |
|
} //end switch |
|
} |
|
|
|
, parseAsArray = function () { |
|
var len = readLength() |
|
, resultArray = [] |
|
, resultHash = {} |
|
, keep = resultArray |
|
, lref = ridx++ |
|
, key |
|
, val |
|
, i |
|
, j |
|
, alen; |
|
|
|
refStack[lref] = keep; |
|
for (i = 0; i < len; i++) { |
|
key = readKey(); |
|
val = parseNext(); |
|
if (keep === resultArray && parseInt(key, 10) === i) { |
|
// store in array version |
|
resultArray.push(val); |
|
|
|
} else { |
|
if (keep !== resultHash) { |
|
// found first non-sequential numeric key |
|
// convert existing data to hash |
|
for (j = 0, alen = resultArray.length; j < alen; j++) { |
|
resultHash[j] = resultArray[j]; |
|
} |
|
keep = resultHash; |
|
refStack[lref] = keep; |
|
} |
|
resultHash[key] = val; |
|
} //end if |
|
} //end for |
|
|
|
idx++; |
|
return keep; |
|
} //end parseAsArray |
|
|
|
, fixPropertyName = function (parsedName, baseClassName) { |
|
var class_name |
|
, prop_name |
|
, pos; |
|
if ("\u0000" === parsedName.charAt(0)) { |
|
// "<NUL>*<NUL>property" |
|
// "<NUL>class<NUL>property" |
|
pos = parsedName.indexOf("\u0000", 1); |
|
if (pos > 0) { |
|
class_name = parsedName.substring(1, pos); |
|
prop_name = parsedName.substr(pos + 1); |
|
|
|
if ("*" === class_name) { |
|
// protected |
|
return prop_name; |
|
} else if (baseClassName === class_name) { |
|
// own private |
|
return prop_name; |
|
} else { |
|
// private of a descendant |
|
return class_name + "::" + prop_name; |
|
|
|
// On the one hand, we need to prefix property name with |
|
// class name, because parent and child classes both may |
|
// have private property with same name. We don't want |
|
// just to overwrite it and lose something. |
|
// |
|
// On the other hand, property name can be "foo::bar" |
|
// |
|
// $obj = new stdClass(); |
|
// $obj->{"foo::bar"} = 42; |
|
// // any user-defined class can do this by default |
|
// |
|
// and such property also can overwrite something. |
|
// |
|
// So, we can to lose something in any way. |
|
} |
|
} |
|
} else if (parsedName.substr(0, baseClassName.length) === baseClassName) { // private property with missing null bytes |
|
return baseClassName + '::' + parsedName.substr(baseClassName.length); |
|
} else { |
|
// public "property" |
|
return parsedName; |
|
} |
|
} |
|
|
|
, parseAsObject = function () { |
|
var len |
|
, obj = {} |
|
, lref = ridx++ |
|
// HACK last char after closing quote is ':', |
|
// but not ';' as for normal string |
|
, clazzname = readString() |
|
, key |
|
, val |
|
, i; |
|
|
|
refStack[lref] = obj; |
|
len = readLength(); |
|
for (i = 0; i < len; i++) { |
|
key = fixPropertyName(readKey(), clazzname); |
|
val = parseNext(); |
|
obj[key] = val; |
|
} |
|
idx++; |
|
return {'class': clazzname, 'properties': obj}; |
|
} //end parseAsObject |
|
|
|
, parseAsCustom = function () { |
|
var clazzname = readString() |
|
, content = readString(); |
|
return { |
|
"__PHP_Incomplete_Class_Name": clazzname, |
|
"serialized": content |
|
}; |
|
} //end parseAsCustom |
|
|
|
, parseAsRefValue = function () { |
|
var ref = readInt() |
|
// php's ref counter is 1-based; our stack is 0-based. |
|
, val = refStack[ref - 1]; |
|
refStack[ridx++] = val; |
|
return val; |
|
} //end parseAsRefValue |
|
|
|
, parseAsRef = function () { |
|
var ref = readInt(); |
|
// php's ref counter is 1-based; our stack is 0-based. |
|
return refStack[ref - 1]; |
|
} //end parseAsRef |
|
|
|
, parseAsNull = function () { |
|
var val = null; |
|
refStack[ridx++] = val; |
|
return val; |
|
}; //end parseAsNull |
|
|
|
parseNext = function () { |
|
var type = readType(); |
|
switch (type) { |
|
case 'i': |
|
return parseAsInt(); |
|
case 'd': |
|
return parseAsFloat(); |
|
case 'b': |
|
return parseAsBoolean(); |
|
case 's': |
|
return parseAsString(); |
|
case 'a': |
|
return parseAsArray(); |
|
case 'O': |
|
return parseAsObject(); |
|
case 'C': |
|
return parseAsCustom(); |
|
|
|
// link to object, which is a value - affects refStack |
|
case 'r': |
|
return parseAsRefValue(); |
|
|
|
// PHP's reference - DOES NOT affect refStack |
|
case 'R': |
|
return parseAsRef(); |
|
|
|
case 'N': |
|
return parseAsNull(); |
|
default: |
|
throw { |
|
name: "Parse Error", |
|
message: "Unknown type '" + type + "' at position " + (idx - 2) |
|
}; |
|
} //end switch |
|
}; //end parseNext |
|
|
|
return parseNext(); |
|
}
|
|
|