/* eslint-disable */ /** * Original code taken from https://github.com/kvz/locutus */ function initCache () { const store: any[] = [] // cache only first element, second is length to jump ahead for the parser const cache = function cache (value) { store.push(value[0]) return value } cache.get = (index) => { if (index >= store.length) { throw RangeError(`Can't resolve reference ${index + 1}`) } return store[index] } return cache } function expectType (str, cache) { const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g const type = (types.exec(str) || [])[0] if (!type) { throw SyntaxError('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: throw SyntaxError(`Invalid or unsupported data type: ${type}`) } } function expectBool (str) { const reBool = /^b:([01]);/ const [ match, boolMatch ] = reBool.exec(str) || [] if (!boolMatch) { throw SyntaxError('Invalid bool value, expected 0 or 1') } return [ boolMatch === '1', match.length ] } function expectInt (str) { const reInt = /^i:([+-]?\d+);/ const [ match, intMatch ] = reInt.exec(str) || [] if (!intMatch) { throw SyntaxError('Expected an integer value') } return [ parseInt(intMatch, 10), match.length ] } function expectFloat (str) { const reFloat = /^d:(NAN|-?INF|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]\d+)?);/ const [ match, floatMatch ] = reFloat.exec(str) || [] if (!floatMatch) { throw SyntaxError('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 ] } function readBytes (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.substring(c + 1, c + 3), 16)) escapedChars++ // each escaped sequence is 3 characters. Go 2 chars ahead. // third character will be jumped over a few lines later c += 2 } c++ bytes += isHighSurrogate || (isLowSurrogate && wasHighSurrogate) // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate // if low surrogate preceded by high surrogate, add 2 bytes ? 2 : code > 0x7ff // otherwise low surrogate falls into this part ? 3 : code > 0x7f ? 2 : 1 // if high surrogate is not followed by low surrogate, add 1 more byte bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0 out += chr wasHighSurrogate = isHighSurrogate } return [ out, bytes, escapedChars ] } function expectString (str) { // PHP strings consist of one-byte characters. // JS uses 2 bytes with possible surrogate pairs. // Serialized length of 2 is still 1 JS string character const reStrLength = /^s:(\d+):"/g // also match the opening " char const [ match, byteLenMatch ] = reStrLength.exec(str) || [] if (!match) { throw SyntaxError('Expected a string value') } const len = parseInt(byteLenMatch, 10) str = str.substring(match.length) let [ strMatch, bytes ] = readBytes(str, len) if (bytes !== len) { throw SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`) } str = str.substring((strMatch as string).length) // strict parsing, match closing "; chars if (!str.startsWith('";')) { throw SyntaxError('Expected ";') } return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last "; } function expectEscapedString (str) { const reStrLength = /^S:(\d+):"/g // also match the opening " char const [ match, strLenMatch ] = reStrLength.exec(str) || [] if (!match) { throw SyntaxError('Expected an escaped string value') } const len = parseInt(strLenMatch, 10) str = str.substring(match.length) let [ strMatch, bytes, escapedChars ] = readBytes(str, len, true) if (bytes !== len) { throw SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`) } str = str.substring((strMatch as string).length + (escapedChars as number) * 2) // strict parsing, match closing "; chars if (!str.startsWith('";')) { throw SyntaxError('Expected ";') } return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last "; } function expectKeyOrIndex (str) { try { return expectString(str) } catch (err) {} try { return expectEscapedString(str) } catch (err) {} try { return expectInt(str) } catch (err) { throw SyntaxError('Expected key or index') } } function expectObject (str, cache) { // O::"class name"::{} // O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"baz";} const reObjectLiteral = /^O:(\d+):"([^"]+)":(\d+):\{/ const [ objectLiteralBeginMatch, /* classNameLengthMatch */, className, propCountMatch ] = reObjectLiteral.exec(str) || [] if (!objectLiteralBeginMatch) { throw SyntaxError('Invalid input') } if (className !== 'stdClass') { throw SyntaxError(`Unsupported object type: ${className}`) } let totalOffset = objectLiteralBeginMatch.length const propCount = parseInt(propCountMatch, 10) const obj = {} cache([obj]) str = str.substring(totalOffset) for (let i = 0; i < propCount; i++) { const prop = expectKeyOrIndex(str) str = str.substring(prop[1]) totalOffset += prop[1] as number const value = expectType(str, cache) str = str.substring(value[1]) totalOffset += value[1] obj[prop[0]] = value[0] } // strict parsing, expect } after object literal if (str.charAt(0) !== '}') { throw SyntaxError('Expected }') } return [ obj, totalOffset + 1 ] // skip final } } function expectClass (str, cache) { // can't be well supported, because requires calling eval (or similar) // in order to call serialized constructor name // which is unsafe // or assume that constructor is defined in global scope // but this is too much limiting throw Error('Not yet implemented') } function expectReference (str, cache) { const reRef = /^[rR]:([1-9]\d*);/ const [ match, refIndex ] = reRef.exec(str) || [] if (!match) { throw SyntaxError('Expected reference value') } return [ cache.get(parseInt(refIndex, 10) - 1), match.length ] } function expectArray (str, cache) { const reArrayLength = /^a:(\d+):{/ const [ arrayLiteralBeginMatch, arrayLengthMatch ] = reArrayLength.exec(str) || [] if (!arrayLengthMatch) { throw SyntaxError('Expected array length annotation') } str = str.substring(arrayLiteralBeginMatch.length) const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache) // strict parsing, expect closing } brace after array literal if (str.charAt(array[1]) !== '}') { throw SyntaxError('Expected }') } return [ array[0], arrayLiteralBeginMatch.length + (array[1] as number) + 1 ] // jump over } } function expectArrayItems (str, expectedItems = 0, cache) { let key let hasStringKeys = false let item let totalOffset = 0 let items: any[] = [] cache([items]) for (let i = 0; i < expectedItems; i++) { key = expectKeyOrIndex(str) // this is for backward compatibility with previous implementation if (!hasStringKeys) { hasStringKeys = (typeof key[0] === 'string') } str = str.substring(key[1]) totalOffset += key[1] // references are resolved immediately, so if duplicate key overwrites previous array index // the old value is anyway resolved // fixme: but next time the same reference should point to the new value item = expectType(str, cache) str = str.substring(item[1]) totalOffset += item[1] items[key[0]] = item[0] } // this is for backward compatibility with previous implementation if (hasStringKeys) { items = Object.assign({}, items) } return [ items, totalOffset ] } function unserialize (str) { // discuss at: https://locutus.io/php/unserialize/ // original by: Arpad Ray (mailto:arpad@php.net) // improved by: Pedro Tainha (https://www.pedrotainha.com) // improved by: Kevin van Zonneveld (https://kvz.io) // improved by: Kevin van Zonneveld (https://kvz.io) // improved by: Chris // improved by: James // improved by: Le Torbi // improved by: Eli Skeggs // bugfixed by: dptr1988 // bugfixed by: Kevin van Zonneveld (https://kvz.io) // bugfixed by: Brett Zamir (https://brett-zamir.me) // bugfixed by: philippsimon (https://github.com/philippsimon/) // revised by: d3x // input by: Brett Zamir (https://brett-zamir.me) // input by: Martin (https://www.erlenwiese.de/) // input by: kilops // input by: Jaroslaw Czarniak // input by: lovasoa (https://github.com/lovasoa/) // improved by: Rafał Kukawski // reimplemented by: Rafał Kukawski // note 1: We feel the main purpose of this function should be // note 1: to ease the transport of data between php & js // note 1: Aiming for PHP-compatibility, we have to translate objects to arrays // example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}') // returns 1: ['Kevin', 'van', 'Zonneveld'] // example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}') // returns 2: {firstName: 'Kevin', midName: 'van'} // example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}') // returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'} // example 4: unserialize(undefined) // returns 4: false // example 5: unserialize('O:8:"stdClass":1:{s:3:"foo";b:1;}') // returns 5: { foo: true } // example 6: unserialize('a:2:{i:0;N;i:1;s:0:"";}') // returns 6: [null, ""] // example 7: unserialize('S:7:"\\65\\73\\63\\61\\70\\65\\64";') // returns 7: 'escaped' try { if (typeof str !== 'string') { return false } return expectType(str, initCache())[0] } catch (err) { console.error(err) return false } } function substr_replace (str, replace, start, length) { // discuss at: https://locutus.io/php/substr_replace/ // original by: Brett Zamir (https://brett-zamir.me) // example 1: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0) // returns 1: 'bob' // example 2: var $var = 'ABCDEFGH:/MNRPQR/' // example 2: substr_replace($var, 'bob', 0, $var.length) // returns 2: 'bob' // example 3: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0, 0) // returns 3: 'bobABCDEFGH:/MNRPQR/' // example 4: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 10, -1) // returns 4: 'ABCDEFGH:/bob/' // example 5: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', -7, -1) // returns 5: 'ABCDEFGH:/bob/' // example 6: substr_replace('ABCDEFGH:/MNRPQR/', '', 10, -1) // returns 6: 'ABCDEFGH://' if (start < 0) { // start position in str start = start + str.length } length = length !== undefined ? length : str.length if (length < 0) { length = length + str.length - start } return [ str.slice(0, start), replace.substring(0, length), replace.slice(length), str.slice(start + length) ].join('') } export class Locutus { static unserialize(data: string): T { return unserialize(data); } static substrReplace(str: string, replace: string, start: number, length?: number): string { return substr_replace(str, replace, start, length); } };