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