aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Capabilities/LLSD.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Capabilities/LLSD.cs')
-rw-r--r--OpenSim/Capabilities/LLSD.cs679
1 files changed, 679 insertions, 0 deletions
diff --git a/OpenSim/Capabilities/LLSD.cs b/OpenSim/Capabilities/LLSD.cs
new file mode 100644
index 0000000..eec9e61
--- /dev/null
+++ b/OpenSim/Capabilities/LLSD.cs
@@ -0,0 +1,679 @@
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
28using System;
29using System.Collections;
30using System.Globalization;
31using System.IO;
32using System.Security.Cryptography;
33using System.Text;
34using System.Xml;
35using OpenMetaverse;
36
37namespace OpenSim.Framework.Capabilities
38{
39 /// <summary>
40 /// Borrowed from (a older version of) libsl for now, as their new llsd code doesn't work we our decoding code.
41 /// </summary>
42 public static class LLSD
43 {
44 /// <summary>
45 ///
46 /// </summary>
47 public class LLSDParseException : Exception
48 {
49 public LLSDParseException(string message) : base(message)
50 {
51 }
52 }
53
54 /// <summary>
55 ///
56 /// </summary>
57 public class LLSDSerializeException : Exception
58 {
59 public LLSDSerializeException(string message) : base(message)
60 {
61 }
62 }
63
64 /// <summary>
65 ///
66 /// </summary>
67 /// <param name="b"></param>
68 /// <returns></returns>
69 public static object LLSDDeserialize(byte[] b)
70 {
71 return LLSDDeserialize(new MemoryStream(b, false));
72 }
73
74 /// <summary>
75 ///
76 /// </summary>
77 /// <param name="st"></param>
78 /// <returns></returns>
79 public static object LLSDDeserialize(Stream st)
80 {
81 XmlTextReader reader = new XmlTextReader(st);
82 reader.Read();
83 SkipWS(reader);
84
85 if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd")
86 throw new LLSDParseException("Expected <llsd>");
87
88 reader.Read();
89 object ret = LLSDParseOne(reader);
90 SkipWS(reader);
91
92 if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd")
93 throw new LLSDParseException("Expected </llsd>");
94
95 return ret;
96 }
97
98 /// <summary>
99 ///
100 /// </summary>
101 /// <param name="obj"></param>
102 /// <returns></returns>
103 public static byte[] LLSDSerialize(object obj)
104 {
105 StringWriter sw = new StringWriter();
106 XmlTextWriter writer = new XmlTextWriter(sw);
107 writer.Formatting = Formatting.None;
108
109 writer.WriteStartElement(String.Empty, "llsd", String.Empty);
110 LLSDWriteOne(writer, obj);
111 writer.WriteEndElement();
112
113 writer.Close();
114
115 return Util.UTF8.GetBytes(sw.ToString());
116 }
117
118 /// <summary>
119 ///
120 /// </summary>
121 /// <param name="writer"></param>
122 /// <param name="obj"></param>
123 public static void LLSDWriteOne(XmlTextWriter writer, object obj)
124 {
125 if (obj == null)
126 {
127 writer.WriteStartElement(String.Empty, "undef", String.Empty);
128 writer.WriteEndElement();
129 return;
130 }
131
132 if (obj is string)
133 {
134 writer.WriteStartElement(String.Empty, "string", String.Empty);
135 writer.WriteString((string) obj);
136 writer.WriteEndElement();
137 }
138 else if (obj is int)
139 {
140 writer.WriteStartElement(String.Empty, "integer", String.Empty);
141 writer.WriteString(obj.ToString());
142 writer.WriteEndElement();
143 }
144 else if (obj is double)
145 {
146 writer.WriteStartElement(String.Empty, "real", String.Empty);
147 writer.WriteString(obj.ToString());
148 writer.WriteEndElement();
149 }
150 else if (obj is bool)
151 {
152 bool b = (bool) obj;
153 writer.WriteStartElement(String.Empty, "boolean", String.Empty);
154 writer.WriteString(b ? "1" : "0");
155 writer.WriteEndElement();
156 }
157 else if (obj is ulong)
158 {
159 throw new Exception("ulong in LLSD is currently not implemented, fix me!");
160 }
161 else if (obj is UUID)
162 {
163 UUID u = (UUID) obj;
164 writer.WriteStartElement(String.Empty, "uuid", String.Empty);
165 writer.WriteString(u.ToString());
166 writer.WriteEndElement();
167 }
168 else if (obj is Hashtable)
169 {
170 Hashtable h = obj as Hashtable;
171 writer.WriteStartElement(String.Empty, "map", String.Empty);
172 foreach (string key in h.Keys)
173 {
174 writer.WriteStartElement(String.Empty, "key", String.Empty);
175 writer.WriteString(key);
176 writer.WriteEndElement();
177 LLSDWriteOne(writer, h[key]);
178 }
179 writer.WriteEndElement();
180 }
181 else if (obj is ArrayList)
182 {
183 ArrayList a = obj as ArrayList;
184 writer.WriteStartElement(String.Empty, "array", String.Empty);
185 foreach (object item in a)
186 {
187 LLSDWriteOne(writer, item);
188 }
189 writer.WriteEndElement();
190 }
191 else if (obj is byte[])
192 {
193 byte[] b = obj as byte[];
194 writer.WriteStartElement(String.Empty, "binary", String.Empty);
195
196 writer.WriteStartAttribute(String.Empty, "encoding", String.Empty);
197 writer.WriteString("base64");
198 writer.WriteEndAttribute();
199
200 //// Calculate the length of the base64 output
201 //long length = (long)(4.0d * b.Length / 3.0d);
202 //if (length % 4 != 0) length += 4 - (length % 4);
203
204 //// Create the char[] for base64 output and fill it
205 //char[] tmp = new char[length];
206 //int i = Convert.ToBase64CharArray(b, 0, b.Length, tmp, 0);
207
208 //writer.WriteString(new String(tmp));
209
210 writer.WriteString(Convert.ToBase64String(b));
211 writer.WriteEndElement();
212 }
213 else
214 {
215 throw new LLSDSerializeException("Unknown type " + obj.GetType().Name);
216 }
217 }
218
219 /// <summary>
220 ///
221 /// </summary>
222 /// <param name="reader"></param>
223 /// <returns></returns>
224 public static object LLSDParseOne(XmlTextReader reader)
225 {
226 SkipWS(reader);
227 if (reader.NodeType != XmlNodeType.Element)
228 throw new LLSDParseException("Expected an element");
229
230 string dtype = reader.LocalName;
231 object ret = null;
232
233 switch (dtype)
234 {
235 case "undef":
236 {
237 if (reader.IsEmptyElement)
238 {
239 reader.Read();
240 return null;
241 }
242
243 reader.Read();
244 SkipWS(reader);
245 ret = null;
246 break;
247 }
248 case "boolean":
249 {
250 if (reader.IsEmptyElement)
251 {
252 reader.Read();
253 return false;
254 }
255
256 reader.Read();
257 string s = reader.ReadString().Trim();
258
259 if (s == String.Empty || s == "false" || s == "0")
260 ret = false;
261 else if (s == "true" || s == "1")
262 ret = true;
263 else
264 throw new LLSDParseException("Bad boolean value " + s);
265
266 break;
267 }
268 case "integer":
269 {
270 if (reader.IsEmptyElement)
271 {
272 reader.Read();
273 return 0;
274 }
275
276 reader.Read();
277 ret = Convert.ToInt32(reader.ReadString().Trim());
278 break;
279 }
280 case "real":
281 {
282 if (reader.IsEmptyElement)
283 {
284 reader.Read();
285 return 0.0f;
286 }
287
288 reader.Read();
289 ret = Convert.ToDouble(reader.ReadString().Trim());
290 break;
291 }
292 case "uuid":
293 {
294 if (reader.IsEmptyElement)
295 {
296 reader.Read();
297 return UUID.Zero;
298 }
299
300 reader.Read();
301 ret = new UUID(reader.ReadString().Trim());
302 break;
303 }
304 case "string":
305 {
306 if (reader.IsEmptyElement)
307 {
308 reader.Read();
309 return String.Empty;
310 }
311
312 reader.Read();
313 ret = reader.ReadString();
314 break;
315 }
316 case "binary":
317 {
318 if (reader.IsEmptyElement)
319 {
320 reader.Read();
321 return new byte[0];
322 }
323
324 if (reader.GetAttribute("encoding") != null &&
325 reader.GetAttribute("encoding") != "base64")
326 {
327 throw new LLSDParseException("Unknown encoding: " + reader.GetAttribute("encoding"));
328 }
329
330 reader.Read();
331 FromBase64Transform b64 = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
332 byte[] inp = Util.UTF8.GetBytes(reader.ReadString());
333 ret = b64.TransformFinalBlock(inp, 0, inp.Length);
334 break;
335 }
336 case "date":
337 {
338 reader.Read();
339 throw new Exception("LLSD TODO: date");
340 }
341 case "map":
342 {
343 return LLSDParseMap(reader);
344 }
345 case "array":
346 {
347 return LLSDParseArray(reader);
348 }
349 default:
350 throw new LLSDParseException("Unknown element <" + dtype + ">");
351 }
352
353 if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != dtype)
354 {
355 throw new LLSDParseException("Expected </" + dtype + ">");
356 }
357
358 reader.Read();
359 return ret;
360 }
361
362 /// <summary>
363 ///
364 /// </summary>
365 /// <param name="reader"></param>
366 /// <returns></returns>
367 public static Hashtable LLSDParseMap(XmlTextReader reader)
368 {
369 Hashtable ret = new Hashtable();
370
371 if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map")
372 throw new LLSDParseException("Expected <map>");
373
374 if (reader.IsEmptyElement)
375 {
376 reader.Read();
377 return ret;
378 }
379
380 reader.Read();
381
382 while (true)
383 {
384 SkipWS(reader);
385 if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map")
386 {
387 reader.Read();
388 break;
389 }
390
391 if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key")
392 throw new LLSDParseException("Expected <key>");
393
394 string key = reader.ReadString();
395
396 if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key")
397 throw new LLSDParseException("Expected </key>");
398
399 reader.Read();
400 object val = LLSDParseOne(reader);
401 ret[key] = val;
402 }
403
404 return ret; // TODO
405 }
406
407 /// <summary>
408 ///
409 /// </summary>
410 /// <param name="reader"></param>
411 /// <returns></returns>
412 public static ArrayList LLSDParseArray(XmlTextReader reader)
413 {
414 ArrayList ret = new ArrayList();
415
416 if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array")
417 throw new LLSDParseException("Expected <array>");
418
419 if (reader.IsEmptyElement)
420 {
421 reader.Read();
422 return ret;
423 }
424
425 reader.Read();
426
427 while (true)
428 {
429 SkipWS(reader);
430
431 if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array")
432 {
433 reader.Read();
434 break;
435 }
436
437 ret.Insert(ret.Count, LLSDParseOne(reader));
438 }
439
440 return ret; // TODO
441 }
442
443 /// <summary>
444 ///
445 /// </summary>
446 /// <param name="count"></param>
447 /// <returns></returns>
448 private static string GetSpaces(int count)
449 {
450 StringBuilder b = new StringBuilder();
451 for (int i = 0; i < count; i++) b.Append(" ");
452 return b.ToString();
453 }
454
455 /// <summary>
456 ///
457 /// </summary>
458 /// <param name="obj"></param>
459 /// <param name="indent"></param>
460 /// <returns></returns>
461 public static String LLSDDump(object obj, int indent)
462 {
463 if (obj == null)
464 {
465 return GetSpaces(indent) + "- undef\n";
466 }
467 else if (obj is string)
468 {
469 return GetSpaces(indent) + "- string \"" + (string) obj + "\"\n";
470 }
471 else if (obj is int)
472 {
473 return GetSpaces(indent) + "- integer " + obj.ToString() + "\n";
474 }
475 else if (obj is double)
476 {
477 return GetSpaces(indent) + "- float " + obj.ToString() + "\n";
478 }
479 else if (obj is UUID)
480 {
481 return GetSpaces(indent) + "- uuid " + ((UUID) obj).ToString() + Environment.NewLine;
482 }
483 else if (obj is Hashtable)
484 {
485 StringBuilder ret = new StringBuilder();
486 ret.Append(GetSpaces(indent) + "- map" + Environment.NewLine);
487 Hashtable map = (Hashtable) obj;
488
489 foreach (string key in map.Keys)
490 {
491 ret.Append(GetSpaces(indent + 2) + "- key \"" + key + "\"" + Environment.NewLine);
492 ret.Append(LLSDDump(map[key], indent + 3));
493 }
494
495 return ret.ToString();
496 }
497 else if (obj is ArrayList)
498 {
499 StringBuilder ret = new StringBuilder();
500 ret.Append(GetSpaces(indent) + "- array\n");
501 ArrayList list = (ArrayList) obj;
502
503 foreach (object item in list)
504 {
505 ret.Append(LLSDDump(item, indent + 2));
506 }
507
508 return ret.ToString();
509 }
510 else if (obj is byte[])
511 {
512 return GetSpaces(indent) + "- binary\n" + Utils.BytesToHexString((byte[]) obj, GetSpaces(indent)) +
513 Environment.NewLine;
514 }
515 else
516 {
517 return GetSpaces(indent) + "- unknown type " + obj.GetType().Name + Environment.NewLine;
518 }
519 }
520
521 public static object ParseTerseLLSD(string llsd)
522 {
523 int notused;
524 return ParseTerseLLSD(llsd, out notused);
525 }
526
527 public static object ParseTerseLLSD(string llsd, out int endPos)
528 {
529 if (llsd.Length == 0)
530 {
531 endPos = 0;
532 return null;
533 }
534
535 // Identify what type of object this is
536 switch (llsd[0])
537 {
538 case '!':
539 throw new LLSDParseException("Undefined value type encountered");
540 case '1':
541 endPos = 1;
542 return true;
543 case '0':
544 endPos = 1;
545 return false;
546 case 'i':
547 {
548 if (llsd.Length < 2) throw new LLSDParseException("Integer value type with no value");
549 int value;
550 endPos = FindEnd(llsd, 1);
551
552 if (Int32.TryParse(llsd.Substring(1, endPos - 1), out value))
553 return value;
554 else
555 throw new LLSDParseException("Failed to parse integer value type");
556 }
557 case 'r':
558 {
559 if (llsd.Length < 2) throw new LLSDParseException("Real value type with no value");
560 double value;
561 endPos = FindEnd(llsd, 1);
562
563 if (Double.TryParse(llsd.Substring(1, endPos - 1), NumberStyles.Float,
564 Utils.EnUsCulture.NumberFormat, out value))
565 return value;
566 else
567 throw new LLSDParseException("Failed to parse double value type");
568 }
569 case 'u':
570 {
571 if (llsd.Length < 17) throw new LLSDParseException("UUID value type with no value");
572 UUID value;
573 endPos = FindEnd(llsd, 1);
574
575 if (UUID.TryParse(llsd.Substring(1, endPos - 1), out value))
576 return value;
577 else
578 throw new LLSDParseException("Failed to parse UUID value type");
579 }
580 case 'b':
581 //byte[] value = new byte[llsd.Length - 1];
582 // This isn't the actual binary LLSD format, just the terse format sent
583 // at login so I don't even know if there is a binary type
584 throw new LLSDParseException("Binary value type is unimplemented");
585 case 's':
586 case 'l':
587 if (llsd.Length < 2) throw new LLSDParseException("String value type with no value");
588 endPos = FindEnd(llsd, 1);
589 return llsd.Substring(1, endPos - 1);
590 case 'd':
591 // Never seen one before, don't know what the format is
592 throw new LLSDParseException("Date value type is unimplemented");
593 case '[':
594 {
595 if (llsd.IndexOf(']') == -1) throw new LLSDParseException("Invalid array");
596
597 int pos = 0;
598 ArrayList array = new ArrayList();
599
600 while (llsd[pos] != ']')
601 {
602 ++pos;
603
604 // Advance past comma if need be
605 if (llsd[pos] == ',') ++pos;
606
607 // Allow a single whitespace character
608 if (pos < llsd.Length && llsd[pos] == ' ') ++pos;
609
610 int end;
611 array.Add(ParseTerseLLSD(llsd.Substring(pos), out end));
612 pos += end;
613 }
614
615 endPos = pos + 1;
616 return array;
617 }
618 case '{':
619 {
620 if (llsd.IndexOf('}') == -1) throw new LLSDParseException("Invalid map");
621
622 int pos = 0;
623 Hashtable hashtable = new Hashtable();
624
625 while (llsd[pos] != '}')
626 {
627 ++pos;
628
629 // Advance past comma if need be
630 if (llsd[pos] == ',') ++pos;
631
632 // Allow a single whitespace character
633 if (pos < llsd.Length && llsd[pos] == ' ') ++pos;
634
635 if (llsd[pos] != '\'') throw new LLSDParseException("Expected a map key");
636 int endquote = llsd.IndexOf('\'', pos + 1);
637 if (endquote == -1 || (endquote + 1) >= llsd.Length || llsd[endquote + 1] != ':')
638 throw new LLSDParseException("Invalid map format");
639 string key = llsd.Substring(pos, endquote - pos);
640 key = key.Replace("'", String.Empty);
641 pos += (endquote - pos) + 2;
642
643 int end;
644 hashtable.Add(key, ParseTerseLLSD(llsd.Substring(pos), out end));
645 pos += end;
646 }
647
648 endPos = pos + 1;
649 return hashtable;
650 }
651 default:
652 throw new Exception("Unknown value type");
653 }
654 }
655
656 private static int FindEnd(string llsd, int start)
657 {
658 int end = llsd.IndexOfAny(new char[] {',', ']', '}'});
659 if (end == -1) end = llsd.Length - 1;
660 return end;
661 }
662
663 /// <summary>
664 ///
665 /// </summary>
666 /// <param name="reader"></param>
667 private static void SkipWS(XmlTextReader reader)
668 {
669 while (
670 reader.NodeType == XmlNodeType.Comment ||
671 reader.NodeType == XmlNodeType.Whitespace ||
672 reader.NodeType == XmlNodeType.SignificantWhitespace ||
673 reader.NodeType == XmlNodeType.XmlDeclaration)
674 {
675 reader.Read();
676 }
677 }
678 }
679}