LLSD.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Globalization;
  31. using System.IO;
  32. using System.Security.Cryptography;
  33. using System.Text;
  34. using System.Xml;
  35. using OpenMetaverse;
  36. namespace OpenSim.Framework.Capabilities
  37. {
  38. /// <summary>
  39. /// Borrowed from (a older version of) libsl for now, as their new llsd code doesn't work we our decoding code.
  40. /// </summary>
  41. public static class LLSD
  42. {
  43. /// <summary>
  44. ///
  45. /// </summary>
  46. public class LLSDParseException : Exception
  47. {
  48. public LLSDParseException(string message) : base(message)
  49. {
  50. }
  51. }
  52. /// <summary>
  53. ///
  54. /// </summary>
  55. public class LLSDSerializeException : Exception
  56. {
  57. public LLSDSerializeException(string message) : base(message)
  58. {
  59. }
  60. }
  61. /// <summary>
  62. ///
  63. /// </summary>
  64. /// <param name="b"></param>
  65. /// <returns></returns>
  66. public static object LLSDDeserialize(byte[] b)
  67. {
  68. using (MemoryStream ms = new MemoryStream(b, false))
  69. {
  70. return LLSDDeserialize(ms);
  71. }
  72. }
  73. /// <summary>
  74. ///
  75. /// </summary>
  76. /// <param name="st"></param>
  77. /// <returns></returns>
  78. public static object LLSDDeserialize(Stream st)
  79. {
  80. using (XmlTextReader reader = new XmlTextReader(st))
  81. {
  82. reader.DtdProcessing = DtdProcessing.Ignore;
  83. reader.Read();
  84. SkipWS(reader);
  85. if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd")
  86. throw new LLSDParseException("Expected <llsd>");
  87. reader.Read();
  88. object ret = LLSDParseOne(reader);
  89. SkipWS(reader);
  90. if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd")
  91. throw new LLSDParseException("Expected </llsd>");
  92. return ret;
  93. }
  94. }
  95. /// <summary>
  96. ///
  97. /// </summary>
  98. /// <param name="obj"></param>
  99. /// <returns></returns>
  100. public static byte[] LLSDSerialize(object obj)
  101. {
  102. using(StringWriter sw = new StringWriter())
  103. using(XmlTextWriter writer = new XmlTextWriter(sw))
  104. {
  105. writer.Formatting = Formatting.None;
  106. writer.WriteStartElement(String.Empty, "llsd", String.Empty);
  107. LLSDWriteOne(writer, obj);
  108. writer.WriteEndElement();
  109. writer.Flush();
  110. return Util.UTF8.GetBytes(sw.ToString());
  111. }
  112. }
  113. /// <summary>
  114. ///
  115. /// </summary>
  116. /// <param name="writer"></param>
  117. /// <param name="obj"></param>
  118. public static void LLSDWriteOne(XmlTextWriter writer, object obj)
  119. {
  120. if (obj == null)
  121. {
  122. writer.WriteStartElement(String.Empty, "undef", String.Empty);
  123. writer.WriteEndElement();
  124. return;
  125. }
  126. if (obj is string s)
  127. {
  128. writer.WriteStartElement(String.Empty, "string", String.Empty);
  129. writer.WriteString(s);
  130. writer.WriteEndElement();
  131. }
  132. else if (obj is int)
  133. {
  134. writer.WriteStartElement(String.Empty, "integer", String.Empty);
  135. writer.WriteString(obj.ToString());
  136. writer.WriteEndElement();
  137. }
  138. else if (obj is double || obj is float)
  139. {
  140. writer.WriteStartElement(String.Empty, "real", String.Empty);
  141. writer.WriteString(obj.ToString());
  142. writer.WriteEndElement();
  143. }
  144. else if (obj is bool b)
  145. {
  146. writer.WriteStartElement(String.Empty, "boolean", String.Empty);
  147. writer.WriteString(b ? "1" : "0");
  148. writer.WriteEndElement();
  149. }
  150. else if (obj is ulong)
  151. {
  152. throw new Exception("ulong in LLSD is currently not implemented, fix me!");
  153. }
  154. else if (obj is UUID u)
  155. {
  156. writer.WriteStartElement(String.Empty, "uuid", String.Empty);
  157. writer.WriteString(u.ToString());
  158. writer.WriteEndElement();
  159. }
  160. else if (obj is Hashtable h)
  161. {
  162. writer.WriteStartElement(string.Empty, "map", string.Empty);
  163. foreach (DictionaryEntry de in h)
  164. {
  165. writer.WriteStartElement(string.Empty, "key", string.Empty);
  166. writer.WriteString((string)de.Key);
  167. writer.WriteEndElement();
  168. LLSDWriteOne(writer, de.Value);
  169. }
  170. writer.WriteEndElement();
  171. }
  172. else if (obj is Dictionary<string,object> dict)
  173. {
  174. writer.WriteStartElement(String.Empty, "map", String.Empty);
  175. foreach (KeyValuePair<string,object> kvp in dict)
  176. {
  177. writer.WriteStartElement(String.Empty, "key", String.Empty);
  178. writer.WriteString(kvp.Key);
  179. writer.WriteEndElement();
  180. LLSDWriteOne(writer, kvp.Value);
  181. }
  182. writer.WriteEndElement();
  183. }
  184. else if (obj is ArrayList a)
  185. {
  186. writer.WriteStartElement(String.Empty, "array", String.Empty);
  187. foreach (object item in a)
  188. {
  189. LLSDWriteOne(writer, item);
  190. }
  191. writer.WriteEndElement();
  192. }
  193. else if (obj is List<object> lsto)
  194. {
  195. writer.WriteStartElement(string.Empty, "array", string.Empty);
  196. foreach (object item in lsto)
  197. {
  198. LLSDWriteOne(writer, item);
  199. }
  200. writer.WriteEndElement();
  201. }
  202. else if (obj is byte[] bytes)
  203. {
  204. writer.WriteStartElement(string.Empty, "binary", string.Empty);
  205. writer.WriteStartAttribute(String.Empty, "encoding", String.Empty);
  206. writer.WriteString("base64");
  207. writer.WriteEndAttribute();
  208. writer.WriteString(Convert.ToBase64String(bytes));
  209. writer.WriteEndElement();
  210. }
  211. else
  212. {
  213. throw new LLSDSerializeException("Unknown type " + obj.GetType().Name);
  214. }
  215. }
  216. /// <summary>
  217. ///
  218. /// </summary>
  219. /// <param name="reader"></param>
  220. /// <returns></returns>
  221. public static object LLSDParseOne(XmlTextReader reader)
  222. {
  223. SkipWS(reader);
  224. if (reader.NodeType != XmlNodeType.Element)
  225. throw new LLSDParseException("Expected an element");
  226. string dtype = reader.LocalName;
  227. object ret = null;
  228. switch (dtype)
  229. {
  230. case "undef":
  231. {
  232. if (reader.IsEmptyElement)
  233. {
  234. reader.Read();
  235. return null;
  236. }
  237. reader.Read();
  238. SkipWS(reader);
  239. ret = null;
  240. break;
  241. }
  242. case "boolean":
  243. {
  244. if (reader.IsEmptyElement)
  245. {
  246. reader.Read();
  247. return false;
  248. }
  249. reader.Read();
  250. string s = reader.ReadString().Trim();
  251. if (s == String.Empty || s == "false" || s == "0")
  252. ret = false;
  253. else if (s == "true" || s == "1")
  254. ret = true;
  255. else
  256. throw new LLSDParseException("Bad boolean value " + s);
  257. break;
  258. }
  259. case "integer":
  260. {
  261. if (reader.IsEmptyElement)
  262. {
  263. reader.Read();
  264. return 0;
  265. }
  266. reader.Read();
  267. ret = Convert.ToInt32(reader.ReadString().Trim());
  268. break;
  269. }
  270. case "real":
  271. {
  272. if (reader.IsEmptyElement)
  273. {
  274. reader.Read();
  275. return 0.0f;
  276. }
  277. reader.Read();
  278. ret = Convert.ToDouble(reader.ReadString().Trim());
  279. break;
  280. }
  281. case "uuid":
  282. {
  283. if (reader.IsEmptyElement)
  284. {
  285. reader.Read();
  286. return UUID.Zero;
  287. }
  288. reader.Read();
  289. ret = new UUID(reader.ReadString().Trim());
  290. break;
  291. }
  292. case "string":
  293. {
  294. if (reader.IsEmptyElement)
  295. {
  296. reader.Read();
  297. return String.Empty;
  298. }
  299. reader.Read();
  300. ret = reader.ReadString();
  301. break;
  302. }
  303. case "binary":
  304. {
  305. if (reader.IsEmptyElement)
  306. {
  307. reader.Read();
  308. return new byte[0];
  309. }
  310. if (reader.GetAttribute("encoding") != null &&
  311. reader.GetAttribute("encoding") != "base64")
  312. {
  313. throw new LLSDParseException("Unknown encoding: " + reader.GetAttribute("encoding"));
  314. }
  315. reader.Read();
  316. FromBase64Transform b64 = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
  317. byte[] inp = Util.UTF8.GetBytes(reader.ReadString());
  318. ret = b64.TransformFinalBlock(inp, 0, inp.Length);
  319. break;
  320. }
  321. case "date":
  322. {
  323. reader.Read();
  324. throw new Exception("LLSD TODO: date");
  325. }
  326. case "map":
  327. {
  328. return LLSDParseMap(reader);
  329. }
  330. case "array":
  331. {
  332. return LLSDParseArray(reader);
  333. }
  334. default:
  335. throw new LLSDParseException("Unknown element <" + dtype + ">");
  336. }
  337. if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != dtype)
  338. {
  339. throw new LLSDParseException("Expected </" + dtype + ">");
  340. }
  341. reader.Read();
  342. return ret;
  343. }
  344. /// <summary>
  345. ///
  346. /// </summary>
  347. /// <param name="reader"></param>
  348. /// <returns></returns>
  349. public static Hashtable LLSDParseMap(XmlTextReader reader)
  350. {
  351. Hashtable ret = new Hashtable();
  352. if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map")
  353. throw new LLSDParseException("Expected <map>");
  354. if (reader.IsEmptyElement)
  355. {
  356. reader.Read();
  357. return ret;
  358. }
  359. reader.Read();
  360. while (true)
  361. {
  362. SkipWS(reader);
  363. if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map")
  364. {
  365. reader.Read();
  366. break;
  367. }
  368. if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key")
  369. throw new LLSDParseException("Expected <key>");
  370. string key = reader.ReadString();
  371. if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key")
  372. throw new LLSDParseException("Expected </key>");
  373. reader.Read();
  374. object val = LLSDParseOne(reader);
  375. ret[key] = val;
  376. }
  377. return ret; // TODO
  378. }
  379. /// <summary>
  380. ///
  381. /// </summary>
  382. /// <param name="reader"></param>
  383. /// <returns></returns>
  384. public static ArrayList LLSDParseArray(XmlTextReader reader)
  385. {
  386. ArrayList ret = new ArrayList();
  387. if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array")
  388. throw new LLSDParseException("Expected <array>");
  389. if (reader.IsEmptyElement)
  390. {
  391. reader.Read();
  392. return ret;
  393. }
  394. reader.Read();
  395. while (true)
  396. {
  397. SkipWS(reader);
  398. if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array")
  399. {
  400. reader.Read();
  401. break;
  402. }
  403. ret.Insert(ret.Count, LLSDParseOne(reader));
  404. }
  405. return ret; // TODO
  406. }
  407. /// <summary>
  408. ///
  409. /// </summary>
  410. /// <param name="count"></param>
  411. /// <returns></returns>
  412. private static string GetSpaces(int count)
  413. {
  414. StringBuilder b = new StringBuilder();
  415. for (int i = 0; i < count; i++) b.Append(' ');
  416. return b.ToString();
  417. }
  418. /// <summary>
  419. ///
  420. /// </summary>
  421. /// <param name="obj"></param>
  422. /// <param name="indent"></param>
  423. /// <returns></returns>
  424. /*
  425. public static String LLSDDump(object obj, int indent)
  426. {
  427. if (obj == null)
  428. {
  429. return GetSpaces(indent) + "- undef\n";
  430. }
  431. else if (obj is string)
  432. {
  433. return GetSpaces(indent) + "- string \"" + (string) obj + "\"\n";
  434. }
  435. else if (obj is int)
  436. {
  437. return GetSpaces(indent) + "- integer " + obj.ToString() + "\n";
  438. }
  439. else if (obj is double)
  440. {
  441. return GetSpaces(indent) + "- float " + obj.ToString() + "\n";
  442. }
  443. else if (obj is UUID)
  444. {
  445. return GetSpaces(indent) + "- uuid " + ((UUID) obj).ToString() + Environment.NewLine;
  446. }
  447. else if (obj is Hashtable)
  448. {
  449. StringBuilder ret = new StringBuilder();
  450. ret.Append(GetSpaces(indent) + "- map" + Environment.NewLine);
  451. Hashtable map = (Hashtable) obj;
  452. foreach (string key in map.Keys)
  453. {
  454. ret.Append(GetSpaces(indent + 2) + "- key \"" + key + "\"" + Environment.NewLine);
  455. ret.Append(LLSDDump(map[key], indent + 3));
  456. }
  457. return ret.ToString();
  458. }
  459. else if (obj is ArrayList)
  460. {
  461. StringBuilder ret = new StringBuilder();
  462. ret.Append(GetSpaces(indent) + "- array\n");
  463. ArrayList list = (ArrayList) obj;
  464. foreach (object item in list)
  465. {
  466. ret.Append(LLSDDump(item, indent + 2));
  467. }
  468. return ret.ToString();
  469. }
  470. else if (obj is byte[])
  471. {
  472. return GetSpaces(indent) + "- binary\n" + Utils.BytesToHexString((byte[]) obj, GetSpaces(indent)) +
  473. Environment.NewLine;
  474. }
  475. else
  476. {
  477. return GetSpaces(indent) + "- unknown type " + obj.GetType().Name + Environment.NewLine;
  478. }
  479. }
  480. public static object ParseTerseLLSD(string llsd)
  481. {
  482. int notused;
  483. return ParseTerseLLSD(llsd, out notused);
  484. }
  485. public static object ParseTerseLLSD(string llsd, out int endPos)
  486. {
  487. if (llsd.Length == 0)
  488. {
  489. endPos = 0;
  490. return null;
  491. }
  492. // Identify what type of object this is
  493. switch (llsd[0])
  494. {
  495. case '!':
  496. throw new LLSDParseException("Undefined value type encountered");
  497. case '1':
  498. endPos = 1;
  499. return true;
  500. case '0':
  501. endPos = 1;
  502. return false;
  503. case 'i':
  504. {
  505. if (llsd.Length < 2) throw new LLSDParseException("Integer value type with no value");
  506. int value;
  507. endPos = FindEnd(llsd, 1);
  508. if (Int32.TryParse(llsd.Substring(1, endPos - 1), out value))
  509. return value;
  510. else
  511. throw new LLSDParseException("Failed to parse integer value type");
  512. }
  513. case 'r':
  514. {
  515. if (llsd.Length < 2) throw new LLSDParseException("Real value type with no value");
  516. double value;
  517. endPos = FindEnd(llsd, 1);
  518. if (Double.TryParse(llsd.Substring(1, endPos - 1), NumberStyles.Float,
  519. Culture.NumberFormatInfo, out value))
  520. return value;
  521. else
  522. throw new LLSDParseException("Failed to parse double value type");
  523. }
  524. case 'u':
  525. {
  526. if (llsd.Length < 17) throw new LLSDParseException("UUID value type with no value");
  527. UUID value;
  528. endPos = FindEnd(llsd, 1);
  529. if (UUID.TryParse(llsd.Substring(1, endPos - 1), out value))
  530. return value;
  531. else
  532. throw new LLSDParseException("Failed to parse UUID value type");
  533. }
  534. case 'b':
  535. //byte[] value = new byte[llsd.Length - 1];
  536. // This isn't the actual binary LLSD format, just the terse format sent
  537. // at login so I don't even know if there is a binary type
  538. throw new LLSDParseException("Binary value type is unimplemented");
  539. case 's':
  540. case 'l':
  541. if (llsd.Length < 2) throw new LLSDParseException("String value type with no value");
  542. endPos = FindEnd(llsd, 1);
  543. return llsd.Substring(1, endPos - 1);
  544. case 'd':
  545. // Never seen one before, don't know what the format is
  546. throw new LLSDParseException("Date value type is unimplemented");
  547. case '[':
  548. {
  549. if (llsd.IndexOf(']') == -1) throw new LLSDParseException("Invalid array");
  550. int pos = 0;
  551. ArrayList array = new ArrayList();
  552. while (llsd[pos] != ']')
  553. {
  554. ++pos;
  555. // Advance past comma if need be
  556. if (llsd[pos] == ',') ++pos;
  557. // Allow a single whitespace character
  558. if (pos < llsd.Length && llsd[pos] == ' ') ++pos;
  559. int end;
  560. array.Add(ParseTerseLLSD(llsd.Substring(pos), out end));
  561. pos += end;
  562. }
  563. endPos = pos + 1;
  564. return array;
  565. }
  566. case '{':
  567. {
  568. if (llsd.IndexOf('}') == -1) throw new LLSDParseException("Invalid map");
  569. int pos = 0;
  570. Hashtable hashtable = new Hashtable();
  571. while (llsd[pos] != '}')
  572. {
  573. ++pos;
  574. // Advance past comma if need be
  575. if (llsd[pos] == ',') ++pos;
  576. // Allow a single whitespace character
  577. if (pos < llsd.Length && llsd[pos] == ' ') ++pos;
  578. if (llsd[pos] != '\'') throw new LLSDParseException("Expected a map key");
  579. int endquote = llsd.IndexOf('\'', pos + 1);
  580. if (endquote == -1 || (endquote + 1) >= llsd.Length || llsd[endquote + 1] != ':')
  581. throw new LLSDParseException("Invalid map format");
  582. string key = llsd.Substring(pos, endquote - pos);
  583. key = key.Replace("'", String.Empty);
  584. pos += (endquote - pos) + 2;
  585. int end;
  586. hashtable.Add(key, ParseTerseLLSD(llsd.Substring(pos), out end));
  587. pos += end;
  588. }
  589. endPos = pos + 1;
  590. return hashtable;
  591. }
  592. default:
  593. throw new Exception("Unknown value type");
  594. }
  595. }
  596. */
  597. private static int FindEnd(string llsd, int start)
  598. {
  599. int end = llsd.IndexOfAny(new char[] {',', ']', '}'});
  600. if (end == -1) end = llsd.Length - 1;
  601. return end;
  602. }
  603. /// <summary>
  604. ///
  605. /// </summary>
  606. /// <param name="reader"></param>
  607. private static void SkipWS(XmlTextReader reader)
  608. {
  609. while (
  610. reader.NodeType == XmlNodeType.Comment ||
  611. reader.NodeType == XmlNodeType.Whitespace ||
  612. reader.NodeType == XmlNodeType.SignificantWhitespace ||
  613. reader.NodeType == XmlNodeType.XmlDeclaration)
  614. {
  615. reader.Read();
  616. }
  617. }
  618. }
  619. }