2
0

Unserializer.hx 14 KB

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