Unserializer.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. /*
  2. * Copyright (C)2005-2016 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package haxe;
  23. @:noDoc
  24. typedef TypeResolver = {
  25. function resolveClass( name : String ) : Class<Dynamic>;
  26. function resolveEnum( name : String ) : Enum<Dynamic>;
  27. }
  28. /**
  29. The `Unserializer` class is the complement to the `Serializer` class. It parses
  30. a serialization `String` and creates objects from the contained data.
  31. This class can be used in two ways:
  32. - create a `new Unserializer()` instance with a given serialization
  33. String, then call its `unserialize()` method until all values are
  34. extracted
  35. - call `Unserializer.run()` to unserialize a single value from a given
  36. String
  37. The specification of the serialization format can be found here:
  38. <https://haxe.org/manual/serialization/format>
  39. **/
  40. class Unserializer {
  41. /**
  42. This value can be set to use custom type resolvers.
  43. A type resolver finds a `Class` or `Enum` instance from a given `String`.
  44. By default, the Haxe `Type` Api is used.
  45. A type resolver must provide two methods:
  46. 1. `resolveClass(name:String):Class<Dynamic>` is called to determine a
  47. `Class` from a class name
  48. 2. `resolveEnum(name:String):Enum<Dynamic>` is called to determine an
  49. `Enum` from an enum name
  50. This value is applied when a new `Unserializer` instance is created.
  51. Changing it afterwards has no effect on previously created instances.
  52. **/
  53. public static var DEFAULT_RESOLVER : TypeResolver = new DefaultResolver();
  54. static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:";
  55. #if !neko
  56. static var CODES = null;
  57. static function initCodes() {
  58. var codes =
  59. #if flash
  60. new flash.utils.ByteArray();
  61. #else
  62. new Array();
  63. #end
  64. for( i in 0...BASE64.length )
  65. codes[StringTools.fastCodeAt(BASE64,i)] = i;
  66. return codes;
  67. }
  68. #end
  69. var buf : String;
  70. var pos : Int;
  71. var length : Int;
  72. var cache : Array<Dynamic>;
  73. var scache : Array<String>;
  74. var resolver : TypeResolver;
  75. #if neko
  76. var upos : Int;
  77. #end
  78. /**
  79. Creates a new Unserializer instance, with its internal buffer
  80. initialized to `buf`.
  81. This does not parse `buf` immediately. It is parsed only when calls to
  82. `this.unserialize` are made.
  83. Each Unserializer instance maintains its own cache.
  84. **/
  85. public function new( buf : String ) {
  86. this.buf = buf;
  87. length = buf.length;
  88. pos = 0;
  89. #if neko
  90. upos = 0;
  91. #end
  92. scache = new Array();
  93. cache = new Array();
  94. var r = DEFAULT_RESOLVER;
  95. if( r == null ) {
  96. r = new DefaultResolver();
  97. DEFAULT_RESOLVER = r;
  98. }
  99. resolver = r;
  100. }
  101. /**
  102. Sets the type resolver of `this` Unserializer instance to `r`.
  103. If `r` is null, a special resolver is used which returns null for all
  104. input values.
  105. See `DEFAULT_RESOLVER` for more information on type resolvers.
  106. **/
  107. public function setResolver( r ) {
  108. if( r == null )
  109. resolver = NullResolver.instance;
  110. else
  111. resolver = r;
  112. }
  113. /**
  114. Gets the type resolver of `this` Unserializer instance.
  115. See `DEFAULT_RESOLVER` for more information on type resolvers.
  116. **/
  117. public function getResolver() {
  118. return resolver;
  119. }
  120. inline function get(p) : Int {
  121. return StringTools.fastCodeAt(buf, p);
  122. }
  123. function readDigits() {
  124. var k = 0;
  125. var s = false;
  126. var fpos = pos;
  127. while( true ) {
  128. var c = get(pos);
  129. if( StringTools.isEof(c) )
  130. break;
  131. if( c == "-".code ) {
  132. if( pos != fpos )
  133. break;
  134. s = true;
  135. pos++;
  136. continue;
  137. }
  138. if( c < "0".code || c > "9".code )
  139. break;
  140. k = k * 10 + (c - "0".code);
  141. pos++;
  142. }
  143. if( s )
  144. k *= -1;
  145. return k;
  146. }
  147. function readFloat() {
  148. var p1 = pos;
  149. while( true ) {
  150. var c = get(pos);
  151. if( StringTools.isEof(c)) break;
  152. // + - . , 0-9
  153. if( (c >= 43 && c < 58) || c == "e".code || c == "E".code )
  154. pos++;
  155. else
  156. break;
  157. }
  158. return Std.parseFloat(buf.substr(p1,pos-p1));
  159. }
  160. function unserializeObject(o) {
  161. while( true ) {
  162. if( pos >= length )
  163. throw "Invalid object";
  164. if( get(pos) == "g".code )
  165. break;
  166. var k = unserialize();
  167. if( !Std.is(k,String) )
  168. throw "Invalid object key";
  169. var v = unserialize();
  170. Reflect.setField(o,k,v);
  171. }
  172. pos++;
  173. }
  174. function unserializeEnum( edecl, tag ) {
  175. if( get(pos++) != ":".code )
  176. throw "Invalid enum format";
  177. var nargs = readDigits();
  178. if( nargs == 0 )
  179. return Type.createEnum(edecl,tag);
  180. var args = new Array();
  181. while( nargs-- > 0 )
  182. args.push(unserialize());
  183. return Type.createEnum(edecl,tag,args);
  184. }
  185. /**
  186. Unserializes the next part of `this` Unserializer instance and returns
  187. the according value.
  188. This function may call `this.resolver.resolveClass` to determine a
  189. Class from a String, and `this.resolver.resolveEnum` to determine an
  190. Enum from a String.
  191. If `this` Unserializer instance contains no more or invalid data, an
  192. exception is thrown.
  193. This operation may fail on structurally valid data if a type cannot be
  194. resolved or if a field cannot be set. This can happen when unserializing
  195. Strings that were serialized on a different Haxe target, in which the
  196. serialization side has to make sure not to include platform-specific
  197. data.
  198. Classes are created from `Type.createEmptyInstance`, which means their
  199. constructors are not called.
  200. **/
  201. public function unserialize() : Dynamic {
  202. switch( get(pos++) ) {
  203. case "n".code:
  204. return null;
  205. case "t".code:
  206. return true;
  207. case "f".code:
  208. return false;
  209. case "z".code:
  210. return 0;
  211. case "i".code:
  212. return readDigits();
  213. case "d".code:
  214. return readFloat();
  215. case "y".code:
  216. var len = readDigits();
  217. if( get(pos++) != ":".code || length - pos < len )
  218. throw "Invalid string length";
  219. var s = buf.substr(pos,len);
  220. pos += len;
  221. s = StringTools.urlDecode(s);
  222. scache.push(s);
  223. return s;
  224. case "k".code:
  225. return Math.NaN;
  226. case "m".code:
  227. return Math.NEGATIVE_INFINITY;
  228. case "p".code:
  229. return Math.POSITIVE_INFINITY;
  230. case "a".code:
  231. var buf = buf;
  232. var a = new Array<Dynamic>();
  233. cache.push(a);
  234. while( true ) {
  235. var c = get(pos);
  236. if( c == "h".code ) {
  237. pos++;
  238. break;
  239. }
  240. if( c == "u".code ) {
  241. pos++;
  242. var n = readDigits();
  243. a[a.length+n-1] = null;
  244. } else
  245. a.push(unserialize());
  246. }
  247. return a;
  248. case "o".code:
  249. var o = {};
  250. cache.push(o);
  251. unserializeObject(o);
  252. return o;
  253. case "r".code:
  254. var n = readDigits();
  255. if( n < 0 || n >= cache.length )
  256. throw "Invalid reference";
  257. return cache[n];
  258. case "R".code:
  259. var n = readDigits();
  260. if( n < 0 || n >= scache.length )
  261. throw "Invalid string reference";
  262. return scache[n];
  263. case "x".code:
  264. throw unserialize();
  265. case "c".code:
  266. var name = unserialize();
  267. var cl = resolver.resolveClass(name);
  268. if( cl == null )
  269. throw "Class not found " + name;
  270. var o = Type.createEmptyInstance(cl);
  271. cache.push(o);
  272. unserializeObject(o);
  273. return o;
  274. case "w".code:
  275. var name = unserialize();
  276. var edecl = resolver.resolveEnum(name);
  277. if( edecl == null )
  278. throw "Enum not found " + name;
  279. var e = unserializeEnum(edecl, unserialize());
  280. cache.push(e);
  281. return e;
  282. case "j".code:
  283. var name = unserialize();
  284. var edecl = resolver.resolveEnum(name);
  285. if( edecl == null )
  286. throw "Enum not found " + name;
  287. pos++; /* skip ':' */
  288. var index = readDigits();
  289. var tag = Type.getEnumConstructs(edecl)[index];
  290. if( tag == null )
  291. throw "Unknown enum index "+name+"@"+index;
  292. var e = unserializeEnum(edecl, tag);
  293. cache.push(e);
  294. return e;
  295. case "l".code:
  296. var l = new List();
  297. cache.push(l);
  298. var buf = buf;
  299. while( get(pos) != "h".code )
  300. l.add(unserialize());
  301. pos++;
  302. return l;
  303. case "b".code:
  304. var h = new haxe.ds.StringMap();
  305. cache.push(h);
  306. var buf = buf;
  307. while( get(pos) != "h".code ) {
  308. var s = unserialize();
  309. h.set(s,unserialize());
  310. }
  311. pos++;
  312. return h;
  313. case "q".code:
  314. var h = new haxe.ds.IntMap();
  315. cache.push(h);
  316. var buf = buf;
  317. var c = get(pos++);
  318. while( c == ":".code ) {
  319. var i = readDigits();
  320. h.set(i,unserialize());
  321. c = get(pos++);
  322. }
  323. if( c != "h".code )
  324. throw "Invalid IntMap format";
  325. return h;
  326. case "M".code:
  327. var h = new haxe.ds.ObjectMap();
  328. cache.push(h);
  329. var buf = buf;
  330. while( get(pos) != "h".code ) {
  331. var s = unserialize();
  332. h.set(s,unserialize());
  333. }
  334. pos++;
  335. return h;
  336. case "v".code:
  337. var d;
  338. if( get(pos) >= '0'.code && get(pos) <= '9'.code &&
  339. get(pos + 1) >= '0'.code && get(pos + 1) <= '9'.code &&
  340. get(pos + 2) >= '0'.code && get(pos + 2) <= '9'.code &&
  341. get(pos + 3) >= '0'.code && get(pos + 3) <= '9'.code &&
  342. get(pos + 4) == '-'.code
  343. ) {
  344. // Included for backwards compatibility
  345. d = Date.fromString(buf.substr(pos,19));
  346. pos += 19;
  347. } else
  348. d = Date.fromTime(readFloat());
  349. cache.push(d);
  350. return d;
  351. case "s".code:
  352. var len = readDigits();
  353. var buf = buf;
  354. if( get(pos++) != ":".code || length - pos < len )
  355. throw "Invalid bytes length";
  356. #if neko
  357. var bytes = haxe.io.Bytes.ofData( base_decode(untyped buf.substr(pos,len).__s,untyped BASE64.__s) );
  358. #else
  359. var codes = CODES;
  360. if( codes == null ) {
  361. codes = initCodes();
  362. CODES = codes;
  363. }
  364. var i = pos;
  365. var rest = len & 3;
  366. var size = (len >> 2) * 3 + ((rest >= 2) ? rest - 1 : 0);
  367. var max = i + (len - rest);
  368. var bytes = haxe.io.Bytes.alloc(size);
  369. var bpos = 0;
  370. while( i < max ) {
  371. var c1 = codes[StringTools.fastCodeAt(buf,i++)];
  372. var c2 = codes[StringTools.fastCodeAt(buf,i++)];
  373. bytes.set(bpos++,(c1 << 2) | (c2 >> 4));
  374. var c3 = codes[StringTools.fastCodeAt(buf,i++)];
  375. bytes.set(bpos++,(c2 << 4) | (c3 >> 2));
  376. var c4 = codes[StringTools.fastCodeAt(buf,i++)];
  377. bytes.set(bpos++,(c3 << 6) | c4);
  378. }
  379. if( rest >= 2 ) {
  380. var c1 = codes[StringTools.fastCodeAt(buf,i++)];
  381. var c2 = codes[StringTools.fastCodeAt(buf,i++)];
  382. bytes.set(bpos++,(c1 << 2) | (c2 >> 4));
  383. if( rest == 3 ) {
  384. var c3 = codes[StringTools.fastCodeAt(buf,i++)];
  385. bytes.set(bpos++,(c2 << 4) | (c3 >> 2));
  386. }
  387. }
  388. #end
  389. pos += len;
  390. cache.push(bytes);
  391. return bytes;
  392. case "C".code:
  393. var name = unserialize();
  394. var cl = resolver.resolveClass(name);
  395. if( cl == null )
  396. throw "Class not found " + name;
  397. var o : Dynamic = Type.createEmptyInstance(cl);
  398. cache.push(o);
  399. o.hxUnserialize(this);
  400. if( get(pos++) != "g".code )
  401. throw "Invalid custom data";
  402. return o;
  403. case "A".code:
  404. var name = unserialize();
  405. var cl = resolver.resolveClass(name);
  406. if( cl == null )
  407. throw "Class not found " + name;
  408. return cl;
  409. case "B".code:
  410. var name = unserialize();
  411. var e = resolver.resolveEnum(name);
  412. if( e == null )
  413. throw "Enum not found " + name;
  414. return e;
  415. default:
  416. }
  417. pos--;
  418. throw ("Invalid char "+buf.charAt(pos)+" at position "+pos);
  419. }
  420. /**
  421. Unserializes `v` and returns the according value.
  422. This is a convenience function for creating a new instance of
  423. Unserializer with `v` as buffer and calling its unserialize() method
  424. once.
  425. **/
  426. public static function run( v : String ) : Dynamic {
  427. return new Unserializer(v).unserialize();
  428. }
  429. #if neko
  430. static var base_decode = neko.Lib.load("std","base_decode",2);
  431. #end
  432. }
  433. private class DefaultResolver {
  434. public function new() {}
  435. @:final public inline function resolveClass(name:String):Class<Dynamic> return Type.resolveClass(name);
  436. @:final public inline function resolveEnum(name:String):Enum<Dynamic> return Type.resolveEnum(name);
  437. }
  438. private class NullResolver {
  439. function new() {}
  440. @:final public inline function resolveClass(name:String):Class<Dynamic> return null;
  441. @:final public inline function resolveEnum(name:String):Enum<Dynamic> return null;
  442. public static var instance(get,null):NullResolver;
  443. inline static function get_instance():NullResolver {
  444. if (instance == null) instance = new NullResolver();
  445. return instance;
  446. }
  447. }