functioninitCache () {
const store = []
const cache = functioncache (value) {
store.push(value[0])
return value
}
cache.get = (index) => {
if (index >= store.length) {
throwRangeError(`Can't resolve reference ${index + 1}`)
}
return store[index]
}
return cache
}
functionexpectType (str, cache) {
const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g
const type = (types.exec(str) || [])[0]
if (!type) {
throwSyntaxError('Invalid input: ' + str)
}
switch (type) {
case'N':
return cache([null, 2])
case'b':
return cache(expectBool(str))
case'i':
return cache(expectInt(str))
case'd':
return cache(expectFloat(str))
case's':
return cache(expectString(str))
case'S':
return cache(expectEscapedString(str))
case'a':
return expectArray(str, cache)
case'O':
return expectObject(str, cache)
case'C':
return expectClass(str, cache)
case'r':
case'R':
return expectReference(str, cache)
default:
throwSyntaxError(`Invalid or unsupported data type: ${type}`)
}
}
functionexpectBool (str) {
const reBool = /^b:([01]);/
const [match, boolMatch] = reBool.exec(str) || []
if (!boolMatch) {
throwSyntaxError('Invalid bool value, expected 0 or 1')
}
return [boolMatch === '1', match.length]
}
functionexpectInt (str) {
const reInt = /^i:([+-]?\d+);/
const [match, intMatch] = reInt.exec(str) || []
if (!intMatch) {
throwSyntaxError('Expected an integer value')
}
return [parseInt(intMatch, 10), match.length]
}
functionexpectFloat (str) {
const reFloat = /^d:(NAN|-?INF|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]\d+)?);/
const [match, floatMatch] = reFloat.exec(str) || []
if (!floatMatch) {
throwSyntaxError('Expected a float value')
}
let floatValue
switch (floatMatch) {
case'NAN':
floatValue = Number.NaN
break
case'-INF':
floatValue = Number.NEGATIVE_INFINITY
break
case'INF':
floatValue = Number.POSITIVE_INFINITY
break
default:
floatValue = parseFloat(floatMatch)
break
}
return [floatValue, match.length]
}
functionreadBytes (str, len, escapedString = false) {
let bytes = 0
let out = ''
let c = 0
const strLen = str.length
let wasHighSurrogate = false
let escapedChars = 0
while (bytes < len && c < strLen) {
let chr = str.charAt(c)
const code = chr.charCodeAt(0)
const isHighSurrogate = code >= 0xd800 && code <= 0xdbff
const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff
if (escapedString && chr === '\\') {
chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16))
escapedChars++
c += 2
}
c++
bytes += isHighSurrogate || (isLowSurrogate && wasHighSurrogate)
? 2
: code > 0x7ff
? 3
: code > 0x7f
? 2
: 1
bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0
out += chr
wasHighSurrogate = isHighSurrogate
}
return [out, bytes, escapedChars]
}
functionexpectString (str) {
const reStrLength = /^s:(\d+):"/g
const [match, byteLenMatch] = reStrLength.exec(str) || []
if (!match) {
throwSyntaxError('Expected a string value')
}
const len = parseInt(byteLenMatch, 10)
str = str.substr(match.length)
const [strMatch, bytes] = readBytes(str, len)
if (bytes !== len) {
throwSyntaxError(`Expected string of ${len} bytes, but got ${bytes}`)
}
str = str.substr(strMatch.length)
if (!str.startsWith('";')) {
throwSyntaxError('Expected ";')
}
return [strMatch, match.length + strMatch.length + 2]
}
functionexpectEscapedString (str) {
const reStrLength = /^S:(\d+):"/g
const [match, strLenMatch] = reStrLength.exec(str) || []
if (!match) {
throwSyntaxError('Expected an escaped string value')
}
const len = parseInt(strLenMatch, 10)
str = str.substr(match.length)
const [strMatch, bytes, escapedChars] = readBytes(str, len, true)
if (bytes !== len) {
throwSyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`)
}
str = str.substr(strMatch.length + escapedChars * 2)
if (!str.startsWith('";')) {
throwSyntaxError('Expected ";')
}
return [strMatch, match.length + strMatch.length + 2]
}
functionexpectKeyOrIndex (str) {
try {
return expectString(str)
} catch (err) {}
try {
return expectEscapedString(str)
} catch (err) {}
try {
return expectInt(str)
} catch (err) {
throwSyntaxError('Expected key or index')
}
}
functionexpectObject (str, cache) {
const reObjectLiteral = /^O:(\d+):"([^"]+)":(\d+):\{/
const [objectLiteralBeginMatch, , className, propCountMatch] = reObjectLiteral.exec(str) || []
if (!objectLiteralBeginMatch) {
throwSyntaxError('Invalid input')
}
if (className !== 'stdClass') {
throwSyntaxError(`Unsupported object type: ${className}`)
}
let totalOffset = objectLiteralBeginMatch.length
const propCount = parseInt(propCountMatch, 10)
const obj = {}
cache([obj])
str = str.substr(totalOffset)
for (let i = 0; i < propCount; i++) {
const prop = expectKeyOrIndex(str)
str = str.substr(prop[1])
totalOffset += prop[1]
const value = expectType(str, cache)
str = str.substr(value[1])
totalOffset += value[1]
obj[prop[0]] = value[0]
}
if (str.charAt(0) !== '}') {
throwSyntaxError('Expected }')
}
return [obj, totalOffset + 1]
}
functionexpectClass (str, cache) {
throwError('Not yet implemented')
}
functionexpectReference (str, cache) {
const reRef = /^[rR]:([1-9]\d*);/
const [match, refIndex] = reRef.exec(str) || []
if (!match) {
throwSyntaxError('Expected reference value')
}
return [cache.get(parseInt(refIndex, 10) - 1), match.length]
}
functionexpectArray (str, cache) {
const reArrayLength = /^a:(\d+):{/
const [arrayLiteralBeginMatch, arrayLengthMatch] = reArrayLength.exec(str) || []
if (!arrayLengthMatch) {
throwSyntaxError('Expected array length annotation')
}
str = str.substr(arrayLiteralBeginMatch.length)
const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache)
if (str.charAt(array[1]) !== '}') {
throwSyntaxError('Expected }')
}
return [array[0], arrayLiteralBeginMatch.length + array[1] + 1]
}
functionexpectArrayItems (str, expectedItems = 0, cache) {
let key
let item
let totalOffset = 0
let hasContinousIndexes = true
let lastIndex = -1
let items = {}
cache([items])
for (let i = 0; i < expectedItems; i++) {
key = expectKeyOrIndex(str)
hasContinousIndexes = hasContinousIndexes && typeof key[0] === 'number' && key[0] === lastIndex + 1
lastIndex = key[0]
str = str.substr(key[1])
totalOffset += key[1]
item = expectType(str, cache)
str = str.substr(item[1])
totalOffset += item[1]
items[key[0]] = item[0]
}
if (hasContinousIndexes) {
items = Object.values(items)
}
return [items, totalOffset]
}
module.exports = functionunserialize (str) {
try {
if (typeof str !== 'string') {
returnfalse
}
return expectType(str, initCache())[0]
} catch (err) {
console.error(err)
returnfalse
}
}