forked from LeenkxTeam/LNXSDK
538 lines
14 KiB
Haxe
538 lines
14 KiB
Haxe
/*
|
|
* Copyright (C)2005-2019 Haxe Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
package haxe;
|
|
|
|
using haxe.Unserializer;
|
|
|
|
import haxe.ds.List;
|
|
|
|
@:noDoc
|
|
typedef TypeResolver = {
|
|
function resolveClass(name:String):Class<Dynamic>;
|
|
function resolveEnum(name:String):Enum<Dynamic>;
|
|
}
|
|
|
|
/**
|
|
The `Unserializer` class is the complement to the `Serializer` class. It parses
|
|
a serialization `String` and creates objects from the contained data.
|
|
|
|
This class can be used in two ways:
|
|
|
|
- create a `new Unserializer()` instance with a given serialization
|
|
String, then call its `unserialize()` method until all values are
|
|
extracted
|
|
- call `Unserializer.run()` to unserialize a single value from a given
|
|
String
|
|
|
|
The specification of the serialization format can be found here:
|
|
<https://haxe.org/manual/serialization/format>
|
|
**/
|
|
class Unserializer {
|
|
/**
|
|
This value can be set to use custom type resolvers.
|
|
|
|
A type resolver finds a `Class` or `Enum` instance from a given `String`.
|
|
By default, the Haxe `Type` Api is used.
|
|
|
|
A type resolver must provide two methods:
|
|
|
|
1. `resolveClass(name:String):Class<Dynamic>` is called to determine a
|
|
`Class` from a class name
|
|
2. `resolveEnum(name:String):Enum<Dynamic>` is called to determine an
|
|
`Enum` from an enum name
|
|
|
|
This value is applied when a new `Unserializer` instance is created.
|
|
Changing it afterwards has no effect on previously created instances.
|
|
**/
|
|
public static var DEFAULT_RESOLVER:TypeResolver = new DefaultResolver();
|
|
|
|
static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:";
|
|
|
|
#if !neko
|
|
static var CODES = null;
|
|
|
|
static function initCodes() {
|
|
var codes = #if flash new flash.utils.ByteArray(); #else new Array(); #end
|
|
for (i in 0...BASE64.length)
|
|
codes[StringTools.fastCodeAt(BASE64, i)] = i;
|
|
return codes;
|
|
}
|
|
#end
|
|
|
|
var buf:String;
|
|
var pos:Int;
|
|
var length:Int;
|
|
var cache:Array<Dynamic>;
|
|
var scache:Array<String>;
|
|
var resolver:TypeResolver;
|
|
#if neko
|
|
var upos:Int;
|
|
#end
|
|
|
|
/**
|
|
Creates a new Unserializer instance, with its internal buffer
|
|
initialized to `buf`.
|
|
|
|
This does not parse `buf` immediately. It is parsed only when calls to
|
|
`this.unserialize` are made.
|
|
|
|
Each Unserializer instance maintains its own cache.
|
|
**/
|
|
public function new(buf:String) {
|
|
this.buf = buf;
|
|
length = this.buf.fastLength();
|
|
pos = 0;
|
|
#if neko
|
|
upos = 0;
|
|
#end
|
|
scache = new Array();
|
|
cache = new Array();
|
|
var r = DEFAULT_RESOLVER;
|
|
if (r == null) {
|
|
r = new DefaultResolver();
|
|
DEFAULT_RESOLVER = r;
|
|
}
|
|
resolver = r;
|
|
}
|
|
|
|
/**
|
|
Sets the type resolver of `this` Unserializer instance to `r`.
|
|
|
|
If `r` is `null`, a special resolver is used which returns `null` for all
|
|
input values.
|
|
|
|
See `DEFAULT_RESOLVER` for more information on type resolvers.
|
|
**/
|
|
public function setResolver(r) {
|
|
if (r == null)
|
|
resolver = NullResolver.instance;
|
|
else
|
|
resolver = r;
|
|
}
|
|
|
|
/**
|
|
Gets the type resolver of `this` Unserializer instance.
|
|
|
|
See `DEFAULT_RESOLVER` for more information on type resolvers.
|
|
**/
|
|
public function getResolver() {
|
|
return resolver;
|
|
}
|
|
|
|
inline function get(p:Int):Int {
|
|
#if php
|
|
return p >= length ? 0 : buf.fastCharCodeAt(p);
|
|
#else
|
|
return StringTools.fastCodeAt(buf, p);
|
|
#end
|
|
}
|
|
|
|
function readDigits() {
|
|
var k = 0;
|
|
var s = false;
|
|
var fpos = pos;
|
|
while (true) {
|
|
var c = get(pos);
|
|
if (StringTools.isEof(c))
|
|
break;
|
|
if (c == "-".code) {
|
|
if (pos != fpos)
|
|
break;
|
|
s = true;
|
|
pos++;
|
|
continue;
|
|
}
|
|
if (c < "0".code || c > "9".code)
|
|
break;
|
|
k = k * 10 + (c - "0".code);
|
|
pos++;
|
|
}
|
|
if (s)
|
|
k *= -1;
|
|
return k;
|
|
}
|
|
|
|
function readFloat() {
|
|
var p1 = pos;
|
|
while (true) {
|
|
var c = get(pos);
|
|
if (StringTools.isEof(c))
|
|
break;
|
|
// + - . , 0-9
|
|
if ((c >= 43 && c < 58) || c == "e".code || c == "E".code)
|
|
pos++;
|
|
else
|
|
break;
|
|
}
|
|
return Std.parseFloat(buf.fastSubstr(p1, pos - p1));
|
|
}
|
|
|
|
function unserializeObject(o:{}) {
|
|
while (true) {
|
|
if (pos >= length)
|
|
throw "Invalid object";
|
|
if (get(pos) == "g".code)
|
|
break;
|
|
var k:Dynamic = unserialize();
|
|
if (!Std.isOfType(k, String))
|
|
throw "Invalid object key";
|
|
var v = unserialize();
|
|
Reflect.setField(o, k, v);
|
|
}
|
|
pos++;
|
|
}
|
|
|
|
function unserializeEnum<T>(edecl:Enum<T>, tag:String) {
|
|
if (get(pos++) != ":".code)
|
|
throw "Invalid enum format";
|
|
var nargs = readDigits();
|
|
if (nargs == 0)
|
|
return Type.createEnum(edecl, tag);
|
|
var args = new Array();
|
|
while (nargs-- > 0)
|
|
args.push(unserialize());
|
|
return Type.createEnum(edecl, tag, args);
|
|
}
|
|
|
|
/**
|
|
Unserializes the next part of `this` Unserializer instance and returns
|
|
the according value.
|
|
|
|
This function may call `this.resolver.resolveClass` to determine a
|
|
Class from a String, and `this.resolver.resolveEnum` to determine an
|
|
Enum from a String.
|
|
|
|
If `this` Unserializer instance contains no more or invalid data, an
|
|
exception is thrown.
|
|
|
|
This operation may fail on structurally valid data if a type cannot be
|
|
resolved or if a field cannot be set. This can happen when unserializing
|
|
Strings that were serialized on a different Haxe target, in which the
|
|
serialization side has to make sure not to include platform-specific
|
|
data.
|
|
|
|
Classes are created from `Type.createEmptyInstance`, which means their
|
|
constructors are not called.
|
|
**/
|
|
public function unserialize():Dynamic {
|
|
switch (get(pos++)) {
|
|
case "n".code:
|
|
return null;
|
|
case "t".code:
|
|
return true;
|
|
case "f".code:
|
|
return false;
|
|
case "z".code:
|
|
return 0;
|
|
case "i".code:
|
|
return readDigits();
|
|
case "d".code:
|
|
return readFloat();
|
|
case "y".code:
|
|
var len = readDigits();
|
|
if (get(pos++) != ":".code || length - pos < len)
|
|
throw "Invalid string length";
|
|
var s = buf.fastSubstr(pos, len);
|
|
pos += len;
|
|
s = StringTools.urlDecode(s);
|
|
scache.push(s);
|
|
return s;
|
|
case "k".code:
|
|
return Math.NaN;
|
|
case "m".code:
|
|
return Math.NEGATIVE_INFINITY;
|
|
case "p".code:
|
|
return Math.POSITIVE_INFINITY;
|
|
case "a".code:
|
|
var buf = buf;
|
|
var a = new Array<Dynamic>();
|
|
#if cpp
|
|
var cachePos = cache.length;
|
|
#end
|
|
cache.push(a);
|
|
while (true) {
|
|
var c = get(pos);
|
|
if (c == "h".code) {
|
|
pos++;
|
|
break;
|
|
}
|
|
if (c == "u".code) {
|
|
pos++;
|
|
var n = readDigits();
|
|
a[a.length + n - 1] = null;
|
|
} else
|
|
a.push(unserialize());
|
|
}
|
|
#if cpp
|
|
return cache[cachePos] = cpp.NativeArray.resolveVirtualArray(a);
|
|
#else
|
|
return a;
|
|
#end
|
|
case "o".code:
|
|
var o = {};
|
|
cache.push(o);
|
|
unserializeObject(o);
|
|
return o;
|
|
case "r".code:
|
|
var n = readDigits();
|
|
if (n < 0 || n >= cache.length)
|
|
throw "Invalid reference";
|
|
return cache[n];
|
|
case "R".code:
|
|
var n = readDigits();
|
|
if (n < 0 || n >= scache.length)
|
|
throw "Invalid string reference";
|
|
return scache[n];
|
|
case "x".code:
|
|
throw unserialize();
|
|
case "c".code:
|
|
var name = unserialize();
|
|
var cl = resolver.resolveClass(name);
|
|
if (cl == null)
|
|
throw "Class not found " + name;
|
|
var o = Type.createEmptyInstance(cl);
|
|
cache.push(o);
|
|
unserializeObject(o);
|
|
return o;
|
|
case "w".code:
|
|
var name = unserialize();
|
|
var edecl = resolver.resolveEnum(name);
|
|
if (edecl == null)
|
|
throw "Enum not found " + name;
|
|
var e = unserializeEnum(edecl, unserialize());
|
|
cache.push(e);
|
|
return e;
|
|
case "j".code:
|
|
var name = unserialize();
|
|
var edecl = resolver.resolveEnum(name);
|
|
if (edecl == null)
|
|
throw "Enum not found " + name;
|
|
pos++; /* skip ':' */
|
|
var index = readDigits();
|
|
var tag = Type.getEnumConstructs(edecl)[index];
|
|
if (tag == null)
|
|
throw "Unknown enum index " + name + "@" + index;
|
|
var e = unserializeEnum(edecl, tag);
|
|
cache.push(e);
|
|
return e;
|
|
case "l".code:
|
|
var l = new List();
|
|
cache.push(l);
|
|
var buf = buf;
|
|
while (get(pos) != "h".code)
|
|
l.add(unserialize());
|
|
pos++;
|
|
return l;
|
|
case "b".code:
|
|
var h = new haxe.ds.StringMap();
|
|
cache.push(h);
|
|
var buf = buf;
|
|
while (get(pos) != "h".code) {
|
|
var s = unserialize();
|
|
h.set(s, unserialize());
|
|
}
|
|
pos++;
|
|
return h;
|
|
case "q".code:
|
|
var h = new haxe.ds.IntMap();
|
|
cache.push(h);
|
|
var buf = buf;
|
|
var c = get(pos++);
|
|
while (c == ":".code) {
|
|
var i = readDigits();
|
|
h.set(i, unserialize());
|
|
c = get(pos++);
|
|
}
|
|
if (c != "h".code)
|
|
throw "Invalid IntMap format";
|
|
return h;
|
|
case "M".code:
|
|
var h = new haxe.ds.ObjectMap();
|
|
cache.push(h);
|
|
var buf = buf;
|
|
while (get(pos) != "h".code) {
|
|
var s = unserialize();
|
|
h.set(s, unserialize());
|
|
}
|
|
pos++;
|
|
return h;
|
|
case "v".code:
|
|
var d;
|
|
if (get(pos) >= '0'.code && get(pos) <= '9'.code && get(pos + 1) >= '0'.code && get(pos + 1) <= '9'.code && get(pos + 2) >= '0'.code
|
|
&& get(pos + 2) <= '9'.code && get(pos + 3) >= '0'.code && get(pos + 3) <= '9'.code && get(pos + 4) == '-'.code) {
|
|
// Included for backwards compatibility
|
|
d = Date.fromString(buf.fastSubstr(pos, 19));
|
|
pos += 19;
|
|
} else
|
|
d = Date.fromTime(readFloat());
|
|
cache.push(d);
|
|
return d;
|
|
case "s".code:
|
|
var len = readDigits();
|
|
var buf = buf;
|
|
if (get(pos++) != ":".code || length - pos < len)
|
|
throw "Invalid bytes length";
|
|
#if neko
|
|
var bytes = haxe.io.Bytes.ofData(base_decode(untyped buf.fastSubstr(pos, len).__s, untyped BASE64.__s));
|
|
#elseif php
|
|
var phpEncoded = php.Global.strtr(buf.fastSubstr(pos, len), '%:', '+/');
|
|
var bytes = haxe.io.Bytes.ofData(php.Global.base64_decode(phpEncoded));
|
|
#else
|
|
var codes = CODES;
|
|
if (codes == null) {
|
|
codes = initCodes();
|
|
CODES = codes;
|
|
}
|
|
var i = pos;
|
|
var rest = len & 3;
|
|
var size = (len >> 2) * 3 + ((rest >= 2) ? rest - 1 : 0);
|
|
var max = i + (len - rest);
|
|
var bytes = haxe.io.Bytes.alloc(size);
|
|
var bpos = 0;
|
|
while (i < max) {
|
|
var c1 = codes[StringTools.fastCodeAt(buf, i++)];
|
|
var c2 = codes[StringTools.fastCodeAt(buf, i++)];
|
|
bytes.set(bpos++, (c1 << 2) | (c2 >> 4));
|
|
var c3 = codes[StringTools.fastCodeAt(buf, i++)];
|
|
bytes.set(bpos++, (c2 << 4) | (c3 >> 2));
|
|
var c4 = codes[StringTools.fastCodeAt(buf, i++)];
|
|
bytes.set(bpos++, (c3 << 6) | c4);
|
|
}
|
|
if (rest >= 2) {
|
|
var c1 = codes[StringTools.fastCodeAt(buf, i++)];
|
|
var c2 = codes[StringTools.fastCodeAt(buf, i++)];
|
|
bytes.set(bpos++, (c1 << 2) | (c2 >> 4));
|
|
if (rest == 3) {
|
|
var c3 = codes[StringTools.fastCodeAt(buf, i++)];
|
|
bytes.set(bpos++, (c2 << 4) | (c3 >> 2));
|
|
}
|
|
}
|
|
#end
|
|
pos += len;
|
|
cache.push(bytes);
|
|
return bytes;
|
|
case "C".code:
|
|
var name = unserialize();
|
|
var cl = resolver.resolveClass(name);
|
|
if (cl == null)
|
|
throw "Class not found " + name;
|
|
var o:Dynamic = Type.createEmptyInstance(cl);
|
|
cache.push(o);
|
|
o.hxUnserialize(this);
|
|
if (get(pos++) != "g".code)
|
|
throw "Invalid custom data";
|
|
return o;
|
|
case "A".code:
|
|
var name = unserialize();
|
|
var cl = resolver.resolveClass(name);
|
|
if (cl == null)
|
|
throw "Class not found " + name;
|
|
return cl;
|
|
case "B".code:
|
|
var name = unserialize();
|
|
var e = resolver.resolveEnum(name);
|
|
if (e == null)
|
|
throw "Enum not found " + name;
|
|
return e;
|
|
default:
|
|
}
|
|
pos--;
|
|
throw("Invalid char " + buf.fastCharAt(pos) + " at position " + pos);
|
|
}
|
|
|
|
/**
|
|
Unserializes `v` and returns the according value.
|
|
|
|
This is a convenience function for creating a new instance of
|
|
Unserializer with `v` as buffer and calling its `unserialize()` method
|
|
once.
|
|
**/
|
|
public static function run(v:String):Dynamic {
|
|
return new Unserializer(v).unserialize();
|
|
}
|
|
|
|
#if neko
|
|
static var base_decode = neko.Lib.load("std", "base_decode", 2);
|
|
#end
|
|
|
|
static inline function fastLength(s:String):Int {
|
|
#if php
|
|
return php.Global.strlen(s);
|
|
#else
|
|
return s.length;
|
|
#end
|
|
}
|
|
|
|
static inline function fastCharCodeAt(s:String, pos:Int):Int {
|
|
#if php
|
|
return php.Global.ord((s:php.NativeString)[pos]);
|
|
#else
|
|
return s.charCodeAt(pos);
|
|
#end
|
|
}
|
|
|
|
static inline function fastCharAt(s:String, pos:Int):String {
|
|
#if php
|
|
return (s:php.NativeString)[pos];
|
|
#else
|
|
return s.charAt(pos);
|
|
#end
|
|
}
|
|
|
|
static inline function fastSubstr(s:String, pos:Int, length:Int):String {
|
|
#if php
|
|
return php.Global.substr(s, pos, length);
|
|
#else
|
|
return s.substr(pos, length);
|
|
#end
|
|
}
|
|
}
|
|
|
|
private class DefaultResolver {
|
|
public function new() {}
|
|
|
|
public inline function resolveClass(name:String):Class<Dynamic>
|
|
return Type.resolveClass(name);
|
|
|
|
public inline function resolveEnum(name:String):Enum<Dynamic>
|
|
return Type.resolveEnum(name);
|
|
}
|
|
|
|
private class NullResolver {
|
|
function new() {}
|
|
|
|
public inline function resolveClass(name:String):Class<Dynamic>
|
|
return null;
|
|
|
|
public inline function resolveEnum(name:String):Enum<Dynamic>
|
|
return null;
|
|
|
|
public static var instance(get, null):NullResolver;
|
|
|
|
inline static function get_instance():NullResolver {
|
|
if (instance == null)
|
|
instance = new NullResolver();
|
|
return instance;
|
|
}
|
|
}
|