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;
 | 
						|
	}
 | 
						|
}
 |