'use strict';

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

function initCache() {
  var store = [];
  // cache only first element, second is length to jump ahead for the parser
  var cache = function cache(value) {
    store.push(value[0]);
    return value;
  };

  cache.get = function (index) {
    if (index >= store.length) {
      throw RangeError('Can\'t resolve reference ' + (index + 1));
    }

    return store[index];
  };

  return cache;
}

function expectType(str, cache) {
  var types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g;
  var 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) {
  var reBool = /^b:([01]);/;

  var _ref = reBool.exec(str) || [],
      _ref2 = _slicedToArray(_ref, 2),
      match = _ref2[0],
      boolMatch = _ref2[1];

  if (!boolMatch) {
    throw SyntaxError('Invalid bool value, expected 0 or 1');
  }

  return [boolMatch === '1', match.length];
}

function expectInt(str) {
  var reInt = /^i:([+-]?\d+);/;

  var _ref3 = reInt.exec(str) || [],
      _ref4 = _slicedToArray(_ref3, 2),
      match = _ref4[0],
      intMatch = _ref4[1];

  if (!intMatch) {
    throw SyntaxError('Expected an integer value');
  }

  return [parseInt(intMatch, 10), match.length];
}

function expectFloat(str) {
  var reFloat = /^d:(NAN|-?INF|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]\d+)?);/;

  var _ref5 = reFloat.exec(str) || [],
      _ref6 = _slicedToArray(_ref5, 2),
      match = _ref6[0],
      floatMatch = _ref6[1];

  if (!floatMatch) {
    throw SyntaxError('Expected a float value');
  }

  var floatValue = void 0;

  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) {
  var escapedString = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

  var bytes = 0;
  var out = '';
  var c = 0;
  var strLen = str.length;
  var wasHighSurrogate = false;
  var escapedChars = 0;

  while (bytes < len && c < strLen) {
    var chr = str.charAt(c);
    var code = chr.charCodeAt(0);
    var isHighSurrogate = code >= 0xd800 && code <= 0xdbff;
    var isLowSurrogate = code >= 0xdc00 && code <= 0xdfff;

    if (escapedString && chr === '\\') {
      chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 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
  var reStrLength = /^s:(\d+):"/g; // also match the opening " char

  var _ref7 = reStrLength.exec(str) || [],
      _ref8 = _slicedToArray(_ref7, 2),
      match = _ref8[0],
      byteLenMatch = _ref8[1];

  if (!match) {
    throw SyntaxError('Expected a string value');
  }

  var len = parseInt(byteLenMatch, 10);

  str = str.substr(match.length);

  var _readBytes = readBytes(str, len),
      _readBytes2 = _slicedToArray(_readBytes, 2),
      strMatch = _readBytes2[0],
      bytes = _readBytes2[1];

  if (bytes !== len) {
    throw SyntaxError('Expected string of ' + len + ' bytes, but got ' + bytes);
  }

  str = str.substr(strMatch.length);

  // strict parsing, match closing "; chars
  if (!str.startsWith('";')) {
    throw SyntaxError('Expected ";');
  }

  return [strMatch, match.length + strMatch.length + 2]; // skip last ";
}

function expectEscapedString(str) {
  var reStrLength = /^S:(\d+):"/g; // also match the opening " char

  var _ref9 = reStrLength.exec(str) || [],
      _ref10 = _slicedToArray(_ref9, 2),
      match = _ref10[0],
      strLenMatch = _ref10[1];

  if (!match) {
    throw SyntaxError('Expected an escaped string value');
  }

  var len = parseInt(strLenMatch, 10);

  str = str.substr(match.length);

  var _readBytes3 = readBytes(str, len, true),
      _readBytes4 = _slicedToArray(_readBytes3, 3),
      strMatch = _readBytes4[0],
      bytes = _readBytes4[1],
      escapedChars = _readBytes4[2];

  if (bytes !== len) {
    throw SyntaxError('Expected escaped string of ' + len + ' bytes, but got ' + bytes);
  }

  str = str.substr(strMatch.length + escapedChars * 2);

  // strict parsing, match closing "; chars
  if (!str.startsWith('";')) {
    throw SyntaxError('Expected ";');
  }

  return [strMatch, match.length + strMatch.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 length>:"class name":<prop count>:{<props and values>}
  // O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"baz";}
  var reObjectLiteral = /^O:(\d+):"([^"]+)":(\d+):\{/;

  var _ref11 = reObjectLiteral.exec(str) || [],
      _ref12 = _slicedToArray(_ref11, 4),
      objectLiteralBeginMatch = _ref12[0],
      /* classNameLengthMatch */className = _ref12[2],
      propCountMatch = _ref12[3];

  if (!objectLiteralBeginMatch) {
    throw SyntaxError('Invalid input');
  }

  if (className !== 'stdClass') {
    throw SyntaxError('Unsupported object type: ' + className);
  }

  var totalOffset = objectLiteralBeginMatch.length;

  var propCount = parseInt(propCountMatch, 10);
  var obj = {};
  cache([obj]);

  str = str.substr(totalOffset);

  for (var i = 0; i < propCount; i++) {
    var prop = expectKeyOrIndex(str);
    str = str.substr(prop[1]);
    totalOffset += prop[1];

    var value = expectType(str, cache);
    str = str.substr(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) {
  var reRef = /^[rR]:([1-9]\d*);/;

  var _ref13 = reRef.exec(str) || [],
      _ref14 = _slicedToArray(_ref13, 2),
      match = _ref14[0],
      refIndex = _ref14[1];

  if (!match) {
    throw SyntaxError('Expected reference value');
  }

  return [cache.get(parseInt(refIndex, 10) - 1), match.length];
}

function expectArray(str, cache) {
  var reArrayLength = /^a:(\d+):{/;

  var _ref15 = reArrayLength.exec(str) || [],
      _ref16 = _slicedToArray(_ref15, 2),
      arrayLiteralBeginMatch = _ref16[0],
      arrayLengthMatch = _ref16[1];

  if (!arrayLengthMatch) {
    throw SyntaxError('Expected array length annotation');
  }

  str = str.substr(arrayLiteralBeginMatch.length);

  var 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] + 1]; // jump over }
}

function expectArrayItems(str) {
  var expectedItems = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  var cache = arguments[2];

  var key = void 0;
  var hasStringKeys = false;
  var item = void 0;
  var totalOffset = 0;
  var items = [];
  cache([items]);

  for (var 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.substr(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.substr(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];
}

module.exports = 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;
  }
};
//# sourceMappingURL=unserialize.js.map