/* * 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; import haxe.ds.List; /** The Serializer class can be used to encode values and objects into a `String`, from which the `Unserializer` class can recreate the original representation. This class can be used in two ways: - create a `new Serializer()` instance, call its `serialize()` method with any argument and finally retrieve the String representation from `toString()` - call `Serializer.run()` to obtain the serialized representation of a single argument Serialization is guaranteed to work for all haxe-defined classes, but may or may not work for instances of external/native classes. The specification of the serialization format can be found here: **/ class Serializer { /** If the values you are serializing can contain circular references or objects repetitions, you should set `USE_CACHE` to true to prevent infinite loops. This may also reduce the size of serialization Strings at the expense of performance. This value can be changed for individual instances of `Serializer` by setting their `useCache` field. **/ public static var USE_CACHE = false; /** Use constructor indexes for enums instead of names. This may reduce the size of serialization Strings, but makes them less suited for long-term storage: If constructors are removed or added from the enum, the indices may no longer match. This value can be changed for individual instances of `Serializer` by setting their `useEnumIndex` field. **/ public static var USE_ENUM_INDEX = false; static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:"; static var BASE64_CODES = null; var buf:StringBuf; var cache:Array; var shash:haxe.ds.StringMap; var scount:Int; /** The individual cache setting for `this` Serializer instance. See `USE_CACHE` for a complete description. **/ public var useCache:Bool; /** The individual enum index setting for `this` Serializer instance. See `USE_ENUM_INDEX` for a complete description. **/ public var useEnumIndex:Bool; /** Creates a new Serializer instance. Subsequent calls to `this.serialize` will append values to the internal buffer of this String. Once complete, the contents can be retrieved through a call to `this.toString`. Each `Serializer` instance maintains its own cache if `this.useCache` is `true`. **/ public function new() { buf = new StringBuf(); cache = new Array(); useCache = USE_CACHE; useEnumIndex = USE_ENUM_INDEX; shash = new haxe.ds.StringMap(); scount = 0; } /** Return the String representation of `this` Serializer. The exact format specification can be found here: https://haxe.org/manual/serialization/format **/ public function toString() { return buf.toString(); } /* prefixes : a : array b : hash c : class d : Float e : reserved (float exp) f : false g : object end h : array/list/hash end i : Int j : enum (by index) k : NaN l : list m : -Inf n : null o : object p : +Inf q : haxe.ds.IntMap r : reference s : bytes (base64) t : true u : array nulls v : date w : enum x : exception y : urlencoded string z : zero A : Class B : Enum M : haxe.ds.ObjectMap C : custom */ function serializeString(s:String) { var x = shash.get(s); if (x != null) { buf.add("R"); buf.add(x); return; } shash.set(s, scount++); #if old_serialize // no more support for -D old_serialize due to 'j' reuse #if error #end #end buf.add("y"); s = StringTools.urlEncode(s); buf.add(s.length); buf.add(":"); buf.add(s); } function serializeRef(v:Dynamic) { #if js var vt = js.Syntax.typeof(v); #end for (i in 0...cache.length) { #if js var ci = cache[i]; if (js.Syntax.typeof(ci) == vt && ci == v) { #else if (cache[i] == v) { #end buf.add("r"); buf.add(i); return true; } } cache.push(v); return false; } #if flash // only the instance variables function serializeClassFields(v:Dynamic, c:Dynamic) { var xml:flash.xml.XML = untyped __global__["flash.utils.describeType"](c); var vars = xml.factory[0].child("variable"); for (i in 0...vars.length()) { var f = vars[i].attribute("name").toString(); if (!v.hasOwnProperty(f)) continue; serializeString(f); serialize(Reflect.field(v, f)); } buf.add("g"); } #end function serializeFields(v:{}) { for (f in Reflect.fields(v)) { serializeString(f); serialize(Reflect.field(v, f)); } buf.add("g"); } /** Serializes `v`. All haxe-defined values and objects with the exception of functions can be serialized. Serialization of external/native objects is not guaranteed to work. The values of `this.useCache` and `this.useEnumIndex` may affect serialization output. **/ public function serialize(v:Dynamic) { switch (Type.typeof(v)) { case TNull: buf.add("n"); case TInt: var v:Int = v; if (v == 0) { buf.add("z"); return; } buf.add("i"); buf.add(v); case TFloat: var v:Float = v; if (Math.isNaN(v)) buf.add("k"); else if (!Math.isFinite(v)) buf.add(if (v < 0) "m" else "p"); else { buf.add("d"); buf.add(v); } case TBool: buf.add(if (v) "t" else "f"); case TClass(c): if (#if neko untyped c.__is_String #else c == String #end) { serializeString(v); return; } if (useCache && serializeRef(v)) return; switch (#if (neko || cs || python) Type.getClassName(c) #else c #end) { case #if (neko || cs || python) "Array" #else cast Array #end: var ucount = 0; buf.add("a"); #if (flash || python || hl) var v:Array = v; #end var l = #if (neko || flash || php || cs || java || python || hl || lua || eval) v.length #elseif cpp v.__length() #else __getField(v, "length") #end; for (i in 0...l) { if (v[i] == null) ucount++; else { if (ucount > 0) { if (ucount == 1) buf.add("n"); else { buf.add("u"); buf.add(ucount); } ucount = 0; } serialize(v[i]); } } if (ucount > 0) { if (ucount == 1) buf.add("n"); else { buf.add("u"); buf.add(ucount); } } buf.add("h"); case #if (neko || cs || python) "haxe.ds.List" #else cast List #end: buf.add("l"); var v:List = v; for (i in v) serialize(i); buf.add("h"); case #if (neko || cs || python) "Date" #else cast Date #end: var d:Date = v; buf.add("v"); buf.add(d.getTime()); case #if (neko || cs || python) "haxe.ds.StringMap" #else cast haxe.ds.StringMap #end: buf.add("b"); var v:haxe.ds.StringMap = v; for (k in v.keys()) { serializeString(k); serialize(v.get(k)); } buf.add("h"); case #if (neko || cs || python) "haxe.ds.IntMap" #else cast haxe.ds.IntMap #end: buf.add("q"); var v:haxe.ds.IntMap = v; for (k in v.keys()) { buf.add(":"); buf.add(k); serialize(v.get(k)); } buf.add("h"); case #if (neko || cs || python) "haxe.ds.ObjectMap" #else cast haxe.ds.ObjectMap #end: buf.add("M"); var v:haxe.ds.ObjectMap = v; for (k in v.keys()) { #if (js || neko) var id = Reflect.field(k, "__id__"); Reflect.deleteField(k, "__id__"); serialize(k); Reflect.setField(k, "__id__", id); #else serialize(k); #end serialize(v.get(k)); } buf.add("h"); case #if (neko || cs || python) "haxe.io.Bytes" #else cast haxe.io.Bytes #end: var v:haxe.io.Bytes = v; #if neko var chars = new String(base_encode(v.getData(), untyped BASE64.__s)); buf.add("s"); buf.add(chars.length); buf.add(":"); buf.add(chars); #elseif php var chars = new String(php.Global.base64_encode(v.getData())); chars = php.Global.strtr(chars, '+/', '%:'); buf.add("s"); buf.add(chars.length); buf.add(":"); buf.add(chars); #else buf.add("s"); buf.add(Math.ceil((v.length * 8) / 6)); buf.add(":"); var i = 0; var max = v.length - 2; var b64 = BASE64_CODES; if (b64 == null) { b64 = new haxe.ds.Vector(BASE64.length); for (i in 0...BASE64.length) b64[i] = BASE64.charCodeAt(i); BASE64_CODES = b64; } while (i < max) { var b1 = v.get(i++); var b2 = v.get(i++); var b3 = v.get(i++); buf.addChar(b64[b1 >> 2]); buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); buf.addChar(b64[((b2 << 2) | (b3 >> 6)) & 63]); buf.addChar(b64[b3 & 63]); } if (i == max) { var b1 = v.get(i++); var b2 = v.get(i++); buf.addChar(b64[b1 >> 2]); buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); buf.addChar(b64[(b2 << 2) & 63]); } else if (i == max + 1) { var b1 = v.get(i++); buf.addChar(b64[b1 >> 2]); buf.addChar(b64[(b1 << 4) & 63]); } #end default: if (useCache) cache.pop(); if (#if flash try v.hxSerialize != null catch (e:Dynamic) false #elseif (cs || java || python) Reflect.hasField(v, "hxSerialize") #elseif php php.Global.method_exists(v, 'hxSerialize') #else v.hxSerialize != null #end) { buf.add("C"); serializeString(Type.getClassName(c)); if (useCache) cache.push(v); v.hxSerialize(this); buf.add("g"); } else { buf.add("c"); serializeString(Type.getClassName(c)); if (useCache) cache.push(v); #if flash serializeClassFields(v, c); #else serializeFields(v); #end } } case TObject: if (Std.isOfType(v, Class)) { var className = Type.getClassName(v); #if (flash || cpp) // Currently, Enum and Class are the same for flash and cpp. // use resolveEnum to test if it is actually an enum if (Type.resolveEnum(className) != null) buf.add("B") else #end buf.add("A"); serializeString(className); } else if (Std.isOfType(v, Enum)) { buf.add("B"); serializeString(Type.getEnumName(v)); } else { if (useCache && serializeRef(v)) return; buf.add("o"); serializeFields(v); } case TEnum(e): if (useCache) { if (serializeRef(v)) return; cache.pop(); } buf.add(useEnumIndex ? "j" : "w"); serializeString(Type.getEnumName(e)); #if neko if (useEnumIndex) { buf.add(":"); buf.add(v.index); } else serializeString(new String(v.tag)); buf.add(":"); if (v.args == null) buf.add(0); else { var l:Int = untyped __dollar__asize(v.args); buf.add(l); for (i in 0...l) serialize(v.args[i]); } #elseif flash if (useEnumIndex) { buf.add(":"); var i:Int = v.index; buf.add(i); } else serializeString(v.tag); buf.add(":"); var pl:Array = v.params; if (pl == null) buf.add(0); else { buf.add(pl.length); for (p in pl) serialize(p); } #elseif cpp var enumBase:cpp.EnumBase = v; if (useEnumIndex) { buf.add(":"); buf.add(enumBase.getIndex()); } else serializeString(enumBase.getTag()); buf.add(":"); var len = enumBase.getParamCount(); buf.add(len); for (p in 0...len) serialize(enumBase.getParamI(p)); #elseif php if (useEnumIndex) { buf.add(":"); buf.add(v.index); } else serializeString(v.tag); buf.add(":"); var l:Int = php.Syntax.code("count({0})", v.params); if (l == 0 || v.params == null) buf.add(0); else { buf.add(l); for (i in 0...l) { #if php serialize(v.params[i]); #end } } #elseif (java || cs || python || hl || eval) if (useEnumIndex) { buf.add(":"); buf.add(Type.enumIndex(v)); } else serializeString(Type.enumConstructor(v)); buf.add(":"); var arr:Array = Type.enumParameters(v); if (arr != null) { buf.add(arr.length); for (v in arr) serialize(v); } else { buf.add("0"); } #elseif (js && !js_enums_as_arrays) if (useEnumIndex) { buf.add(":"); buf.add(v._hx_index); } else serializeString(Type.enumConstructor(v)); buf.add(":"); var params = Type.enumParameters(v); buf.add(params.length); for (p in params) serialize(p); #else if (useEnumIndex) { buf.add(":"); buf.add(v[1]); } else serializeString(v[0]); buf.add(":"); var l = __getField(v, "length"); buf.add(l - 2); for (i in 2...l) serialize(v[i]); #end if (useCache) cache.push(v); case TFunction: throw "Cannot serialize function"; default: #if neko if (untyped (__i32__kind != null && __dollar__iskind(v, __i32__kind))) { buf.add("i"); buf.add(v); return; } #end throw "Cannot serialize " + Std.string(v); } } extern inline function __getField(o:Dynamic, f:String):Dynamic return o[cast f]; public function serializeException(e:Dynamic) { buf.add("x"); #if flash if (untyped __is__(e, __global__["Error"])) { var e:flash.errors.Error = e; var s = e.getStackTrace(); if (s == null) serialize(e.message); else serialize(s); return; } #end serialize(e); } /** Serializes `v` and returns the String representation. This is a convenience function for creating a new instance of Serializer, serialize `v` into it and obtain the result through a call to `toString()`. **/ public static function run(v:Dynamic) { var s = new Serializer(); s.serialize(v); return s.toString(); } #if neko static var base_encode = neko.Lib.load("std", "base_encode", 2); #end }