aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorDr Scofield2008-07-02 09:02:30 +0000
committerDr Scofield2008-07-02 09:02:30 +0000
commitd40bea4a8e09be1f8e87cf41405aaa60fa8826cb (patch)
tree91ef9356d8b284ac6fa5f0d588fedebe723b69ad
parentMantis#1643. Thank you Melanie for a patch that: (diff)
downloadopensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.zip
opensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.gz
opensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.bz2
opensim-SC-d40bea4a8e09be1f8e87cf41405aaa60fa8826cb.tar.xz
From: Alan M Webb <awebb@vnet.ibm.com>
This adds REST services for inventory access. It also allows inventory uploads.
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs47
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs1201
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs479
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs257
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs547
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs1993
-rw-r--r--prebuild.xml38
7 files changed, 4562 insertions, 0 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs
new file mode 100644
index 0000000..6f52582
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs
@@ -0,0 +1,47 @@
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 OpenSim 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 */
28
29using System;
30
31namespace OpenSim.ApplicationPlugins.Rest.Inventory
32{
33
34 /// <summary>
35 /// This interface represents the boundary between the general purpose
36 /// REST plugin handling, and the functionally specific handlers. The
37 /// handler knows only to initialzie and terminate all such handlers
38 /// that it finds.
39 /// </summary>
40
41 internal interface IRest
42 {
43 void Initialize();
44 void Close();
45 }
46
47}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
new file mode 100644
index 0000000..3de9f36
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
@@ -0,0 +1,1201 @@
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 OpenSim 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 */
28
29using System;
30using System.IO;
31using System.Reflection;
32using System.Text;
33using System.Security.Cryptography;
34using System.Text.RegularExpressions;
35using System.Collections.Generic;
36using System.Collections.Specialized;
37using OpenSim.Framework.Servers;
38using libsecondlife;
39using System.Xml;
40
41namespace OpenSim.ApplicationPlugins.Rest.Inventory
42{
43
44 /// <summary>
45 /// This class represents the current REST request. It
46 /// encapsulates the request/response state and takes care
47 /// of response generation without exposing the REST handler
48 /// to the actual mechanisms involved.
49 ///
50 /// This structure is created on entry to the Handler
51 /// method and is disposed of upon return. It is part of
52 /// the plug-in infrastructure, rather than the functionally
53 /// specifici REST handler, and fundamental changes to
54 /// this should be reflected in the Rest HandlerVersion. The
55 /// object is instantiated, and may be extended by, any
56 /// given handler. See the inventory handler for an example
57 /// of this.
58 ///
59 /// If possible, the underlying request/response state is not
60 /// changed until the handler explicitly issues a Respond call.
61 /// This ensures that the request/response pair can be safely
62 /// processed by subsequent, unrelated, handlers even id the
63 /// agent handler had completed much of its processing. Think
64 /// of it as a transactional req/resp capability.
65 /// </summary>
66
67 internal class RequestData
68 {
69
70 // HTTP Server interface data
71
72 internal OSHttpRequest request = null;
73 internal OSHttpResponse response = null;
74
75 // Request lifetime values
76
77 internal NameValueCollection headers = null;
78 internal List<string> removed_headers = null;
79 internal byte[] buffer = null;
80 internal string body = null;
81 internal string html = null;
82 internal string entity = null;
83 internal string path = null;
84 internal string method = null;
85 internal string statusDescription = null;
86 internal string redirectLocation = null;
87 internal string[] pathNodes = null;
88 internal string[] parameters = null;
89 internal int statusCode = 0;
90 internal bool handled = false;
91 internal LLUUID uuid = LLUUID.Zero;
92 internal Encoding encoding = Rest.Encoding;
93 internal Uri uri = null;
94 internal string query = null;
95 internal bool fail = false;
96 internal string hostname = "localhost";
97 internal int port = 80;
98 internal string prefix = Rest.UrlPathSeparator;
99
100 // Authentication related state
101
102 internal bool authenticated = false;
103 internal string scheme = Rest.AS_DIGEST;
104 internal string realm = Rest.Realm;
105 internal string domain = null;
106 internal string nonce = null;
107 internal string cnonce = null;
108 internal string qop = Rest.Qop_Auth;
109 internal string opaque = null;
110 internal string stale = null;
111 internal string algorithm = Rest.Digest_MD5;
112 internal string authParms = null;
113 internal string authPrefix = null;
114 internal string userName = String.Empty;
115 internal string userPass = String.Empty;
116 internal LLUUID client = LLUUID.Zero;
117
118 // XML related state
119
120 internal XmlWriter writer = null;
121 internal XmlReader reader = null;
122
123 // Internal working state
124
125 private StringBuilder sbuilder = new StringBuilder(1024);
126 private MemoryStream xmldata = null;
127
128 private static readonly string[] EmptyPath = { String.Empty };
129
130 // Session related tables. These are only needed if QOP is set to "auth-sess"
131 // and for now at least, it is not. Session related authentication is of
132 // questionable merit in the context of REST anyway, but it is, arguably, more
133 // secure.
134
135 private static Dictionary<string,string> cntable = new Dictionary<string,string>();
136 private static Dictionary<string,string> sktable = new Dictionary<string,string>();
137
138 // This dictionary is used to keep track fo all of the parameters discovered
139 // when the authorisation header is anaylsed.
140
141 private Dictionary<string,string> authparms = new Dictionary<string,string>();
142
143 // These regular expressions are used to decipher the various header entries.
144
145 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
146 RegexOptions.Compiled | RegexOptions.IgnoreCase);
147
148 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
149 RegexOptions.Compiled | RegexOptions.IgnoreCase);
150
151 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>\\S+)\"",
152 RegexOptions.Compiled | RegexOptions.IgnoreCase);
153
154 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
155 RegexOptions.Compiled | RegexOptions.IgnoreCase);
156
157 private static Regex reuserPass = new Regex("\\s*(?<user>\\w+)\\s*:\\s*(?<pass>\\S*)",
158 RegexOptions.Compiled | RegexOptions.IgnoreCase);
159
160 // For efficiency, we create static instances of these objects
161
162 private static MD5 md5hash = MD5.Create();
163
164 private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
165
166 // Constructor
167
168 internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string qprefix)
169 {
170
171 request = p_request;
172 response = p_response;
173
174 sbuilder.Length = 0;
175
176 encoding = request.ContentEncoding;
177 if (encoding == null)
178 {
179 encoding = Rest.Encoding;
180 }
181
182 method = request.HttpMethod.ToLower();
183 initUrl();
184
185 initParameters(qprefix.Length);
186
187 }
188
189 // Just for convenience...
190
191 internal string MsgId
192 {
193 get { return Rest.MsgId; }
194 }
195
196 // Defer authentication check until requested
197
198 internal bool IsAuthenticated
199 {
200 get
201 {
202 if (Rest.Authenticate)
203 {
204 if (!authenticated)
205 {
206 authenticate();
207 }
208
209 return authenticated;
210 }
211 else return true;
212 }
213 }
214
215 /// <summary>
216 /// The REST handler has requested authentication. Authentication
217 /// is considered to be with respect to the current values for
218 /// Realm, domain, etc.
219 ///
220 /// This method checks to see if the current request is already
221 /// authenticated for this domain. If it is, then it returns
222 /// true. If it is not, then it issues a challenge to the client
223 /// and responds negatively to the request.
224 /// </summary>
225
226 private void authenticate()
227 {
228
229 string authdata = request.Headers.Get("Authorization");
230 string reqscheme = String.Empty;
231
232 // If we don't have an authorization header, then this
233 // user is certainly not authorized. This is the typical
234 // pivot for the 1st request by a client.
235
236 if (authdata == null)
237 {
238 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
239 DoChallenge();
240 }
241
242 // So, we have authentication data, now we have to check to
243 // see what we got and whether or not it is valid for the
244 // current domain. To do this we need to interpret the data
245 // provided in the Authorization header. First we need to
246 // identify the scheme being used and route accordingly.
247
248 MatchCollection matches = schema.Matches(authdata);
249
250 foreach (Match m in matches)
251 {
252 Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value);
253 reqscheme = m.Groups["scheme"].Value.ToLower();
254 }
255
256 // If we want a specific authentication mechanism, make sure
257 // we get it.
258
259 if (scheme != null && scheme.ToLower() != reqscheme)
260 {
261 Rest.Log.DebugFormat("{0} Challenge reason: Required scheme not accepted", MsgId);
262 DoChallenge();
263 }
264
265 // In the future, these could be made into plug-ins...
266 // But for now at least we have no reason to use anything other
267 // then MD5. TLS/SSL are taken care of elsewhere.
268
269 switch (reqscheme)
270 {
271 case "digest" :
272 Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
273 DoDigest(authdata);
274 break;
275
276 case "basic" :
277 Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
278 DoBasic(authdata);
279 break;
280 }
281
282 // If the current header is invalid, then a challenge is still needed.
283
284 if (!authenticated)
285 {
286 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
287 DoChallenge();
288 }
289
290 }
291
292 /// <summary>
293 /// Construct the necessary WWW-Authenticate headers and fail the request
294 /// with a NOT AUTHORIZED response. The parameters are the union of values
295 /// required by the supported schemes.
296 /// </summary>
297
298 private void DoChallenge()
299 {
300 Flush();
301 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
302 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
303 Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized);
304 }
305
306 /// <summary>
307 /// Interpret a BASIC authorization claim
308 /// This is here for completeness, it is not used.
309 /// </summary>
310
311 private void DoBasic(string authdata)
312 {
313
314 string response = null;
315
316 MatchCollection matches = basicParms.Matches(authdata);
317
318 // In the case of basic authentication there is
319 // only expected to be a single argument.
320
321 foreach (Match m in matches)
322 {
323 authparms.Add("response",m.Groups["pval"].Value);
324 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
325 MsgId, "response", m.Groups["pval"].Value);
326 }
327
328 // Did we get a valid response?
329
330 if (authparms.TryGetValue("response", out response))
331 {
332 // Decode
333 response = Rest.Base64ToString(response);
334 Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response);
335
336 // Extract user & password
337 Match m = reuserPass.Match(response);
338 userName = m.Groups["user"].Value;
339 userPass = m.Groups["pass"].Value;
340
341 // Validate against user database
342 authenticated = Validate(userName,userPass);
343 }
344
345 }
346
347 /// <summary>
348 /// This is an RFC2617 compliant HTTP MD5 Digest authentication
349 /// implementation. It has been tested with Firefox, Java HTTP client,
350 /// and Miscrosoft's Internet Explorer V7.
351 /// </summary>
352
353 private void DoDigest(string authdata)
354 {
355
356 string response = null;
357
358 MatchCollection matches = digestParm1.Matches(authdata);
359
360 // Collect all of the supplied parameters and store them
361 // in a dictionary (for ease of access)
362
363 foreach (Match m in matches)
364 {
365 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
366 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
367 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
368 }
369
370 // And pick up any tokens too
371
372 matches = digestParm2.Matches(authdata);
373
374 foreach (Match m in matches)
375 {
376 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
377 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
378 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
379 }
380
381 // A response string MUST be returned, otherwise we are
382 // NOT authenticated.
383
384 Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId);
385
386 if (authparms.TryGetValue("response", out response))
387 {
388
389 string temp = null;
390
391 do
392 {
393
394 string nck = null;
395 string ncl = null;
396
397 // The userid is sent in clear text. Needed for the
398 // verification.
399
400 authparms.TryGetValue("username", out userName);
401
402 // All URI's of which this is a prefix are
403 // optimistically considered to be authenticated by the
404 // client. This is also needed to verify the response.
405
406 authparms.TryGetValue("uri", out authPrefix);
407
408 // There MUST be a nonce string present. We're not preserving any server
409 // side state and we can;t validate the MD5 unless the lcient returns it
410 // to us, as it should.
411
412 if (!authparms.TryGetValue("nonce", out nonce))
413 {
414 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
415 break;
416 }
417
418 // If there is an opaque string present, it had better
419 // match what we sent.
420
421 if (authparms.TryGetValue("opaque", out temp))
422 {
423 if (temp != opaque)
424 {
425 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
426 break;
427 }
428 }
429
430 // If an algorithm string is present, it had better
431 // match what we sent.
432
433 if (authparms.TryGetValue("algorithm", out temp))
434 {
435 if (temp != algorithm)
436 {
437 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
438 break;
439 }
440 }
441
442 // Quality of protection considerations...
443
444 if (authparms.TryGetValue("qop", out temp))
445 {
446
447 qop = temp.ToLower(); // replace with actual value used
448
449 // if QOP was specified then
450 // these MUST be present.
451
452 if (!authparms.ContainsKey("cnonce"))
453 {
454 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
455 break;
456 }
457
458 cnonce = authparms["cnonce"];
459
460 if (!authparms.ContainsKey("nc"))
461 {
462 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
463 break;
464 }
465
466 nck = authparms["nc"];
467
468 if (cntable.TryGetValue(cnonce, out ncl))
469 {
470 if (Rest.Hex2Int(ncl) <= Rest.Hex2Int(nck))
471 {
472 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
473 break;
474 }
475 cntable[cnonce] = nck;
476 }
477 else
478 {
479 lock(cntable) cntable.Add(cnonce, nck);
480 }
481
482 }
483 else
484 {
485
486 qop = String.Empty;
487
488 // if QOP was not specified then
489 // these MUST NOT be present.
490 if (authparms.ContainsKey("cnonce"))
491 {
492 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
493 break;
494 }
495 if (authparms.ContainsKey("nc"))
496 {
497 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
498 break;
499 }
500 }
501
502 // Validate the supplied userid/password info
503
504 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
505
506 }
507 while (false);
508
509 }
510
511 }
512
513 // Indicate that authentication is required
514
515 internal void Challenge(string scheme, string realm, string domain, string nonce,
516 string opaque, string stale, string alg,
517 string qop, string auth)
518 {
519
520 sbuilder.Length = 0;
521
522 if (scheme == null || scheme == Rest.AS_DIGEST)
523 {
524
525 sbuilder.Append(Rest.AS_DIGEST);
526 sbuilder.Append(" ");
527
528 if (realm != null)
529 {
530 sbuilder.Append("realm=");
531 sbuilder.Append(Rest.CS_DQUOTE);
532 sbuilder.Append(realm);
533 sbuilder.Append(Rest.CS_DQUOTE);
534 sbuilder.Append(Rest.CS_COMMA);
535 }
536
537 if (nonce != null)
538 {
539 sbuilder.Append("nonce=");
540 sbuilder.Append(Rest.CS_DQUOTE);
541 sbuilder.Append(nonce);
542 sbuilder.Append(Rest.CS_DQUOTE);
543 sbuilder.Append(Rest.CS_COMMA);
544 }
545
546 if (opaque != null)
547 {
548 sbuilder.Append("opaque=");
549 sbuilder.Append(Rest.CS_DQUOTE);
550 sbuilder.Append(opaque);
551 sbuilder.Append(Rest.CS_DQUOTE);
552 sbuilder.Append(Rest.CS_COMMA);
553 }
554
555 if (stale != null)
556 {
557 sbuilder.Append("stale=");
558 sbuilder.Append(Rest.CS_DQUOTE);
559 sbuilder.Append(stale);
560 sbuilder.Append(Rest.CS_DQUOTE);
561 sbuilder.Append(Rest.CS_COMMA);
562 }
563
564 if (alg != null)
565 {
566 sbuilder.Append("algorithm=");
567 sbuilder.Append(alg);
568 sbuilder.Append(Rest.CS_COMMA);
569 }
570
571 if (qop != String.Empty)
572 {
573 sbuilder.Append("qop=");
574 sbuilder.Append(Rest.CS_DQUOTE);
575 sbuilder.Append(qop);
576 sbuilder.Append(Rest.CS_DQUOTE);
577 sbuilder.Append(Rest.CS_COMMA);
578 }
579
580 if (auth != null)
581 {
582 sbuilder.Append(auth);
583 sbuilder.Append(Rest.CS_COMMA);
584 }
585
586 if (Rest.Domains.Count != 0)
587 {
588 sbuilder.Append("domain=");
589 sbuilder.Append(Rest.CS_DQUOTE);
590 foreach (string dom in Rest.Domains.Values)
591 {
592 sbuilder.Append(dom);
593 sbuilder.Append(Rest.CS_SPACE);
594 }
595 if (sbuilder[sbuilder.Length-1] == Rest.C_SPACE)
596 {
597 sbuilder.Length = sbuilder.Length-1;
598 }
599 sbuilder.Append(Rest.CS_DQUOTE);
600 sbuilder.Append(Rest.CS_COMMA);
601 }
602
603 if (sbuilder[sbuilder.Length-1] == Rest.C_COMMA)
604 {
605 sbuilder.Length = sbuilder.Length-1;
606 }
607
608 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
609
610 }
611
612 if (scheme == null || scheme == Rest.AS_BASIC)
613 {
614
615 sbuilder.Append(Rest.AS_BASIC);
616
617 if (realm != null)
618 {
619 sbuilder.Append(" realm=\"");
620 sbuilder.Append(realm);
621 sbuilder.Append("\"");
622 }
623 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
624 }
625
626 }
627
628 private bool Validate(string user, string pass)
629 {
630 Rest.Log.DebugFormat("{0} Validating {1}:{2}", MsgId, user, pass);
631 return user == "awebb" && pass == getPassword(user);
632 }
633
634 private string getPassword(string user)
635 {
636 return Rest.GodKey;
637 }
638
639 // Validate the request-digest
640 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
641 {
642
643 string patt = null;
644 string payl = String.Empty;
645 string KDS = null;
646 string HA1 = null;
647 string HA2 = null;
648 string pass = getPassword(user);
649
650 // Generate H(A1)
651
652 if (algorithm == Rest.Digest_MD5Sess)
653 {
654 if (!sktable.ContainsKey(cnonce))
655 {
656 patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce);
657 HA1 = HashToString(patt);
658 sktable.Add(cnonce, HA1);
659 }
660 else
661 {
662 HA1 = sktable[cnonce];
663 }
664 }
665 else
666 {
667 patt = String.Format("{0}:{1}:{2}", user, realm, pass);
668 HA1 = HashToString(patt);
669 }
670
671 // Generate H(A2)
672
673 if (qop == "auth-int")
674 {
675 patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl));
676 }
677 else
678 {
679 patt = String.Format("{0}:{1}", request.HttpMethod, uri);
680 }
681
682 HA2 = HashToString(patt);
683
684 // Generate Digest
685
686 if (qop != String.Empty)
687 {
688 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
689 }
690 else
691 {
692 patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2);
693 }
694
695 KDS = HashToString(patt);
696
697 // Compare the generated sequence with the original
698
699 return (0 == sc.Compare(KDS, response));
700
701 }
702
703 private string HashToString(string pattern)
704 {
705
706 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
707
708 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
709
710 sbuilder.Length = 0;
711
712 for (int i = 0; i < hash.Length; i++)
713 {
714 sbuilder.Append(hash[i].ToString("x2"));
715 }
716
717 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
718
719 return sbuilder.ToString();
720
721 }
722
723 internal void Complete()
724 {
725 statusCode = Rest.HttpStatusCodeOK;
726 statusDescription = Rest.HttpStatusDescOK;
727 }
728
729 internal void Redirect(string Url, bool temp)
730 {
731
732 redirectLocation = Url;
733
734 if (temp)
735 {
736 statusCode = Rest.HttpStatusCodeTemporaryRedirect;
737 statusDescription = Rest.HttpStatusDescTemporaryRedirect;
738 }
739 else
740 {
741 statusCode = Rest.HttpStatusCodePermanentRedirect;
742 statusDescription = Rest.HttpStatusDescPermanentRedirect;
743 }
744
745 Fail(statusCode, statusDescription, true);
746
747 }
748
749 // Fail for an arbitrary reason. Just a failure with
750 // headers.
751
752 internal void Fail(int code, string message)
753 {
754 Fail(code, message, true);
755 }
756
757 // More adventurous. This failure also includes a
758 // specified entity.
759
760 internal void Fail(int code, string message, string data)
761 {
762 buffer = null;
763 body = data;
764 Fail(code, message, false);
765 }
766
767 internal void Fail(int code, string message, bool reset)
768 {
769
770 statusCode = code;
771 statusDescription = message;
772
773 if (reset)
774 {
775 buffer = null;
776 body = null;
777 }
778
779 if (Rest.DEBUG)
780 {
781 Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
782 Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
783 Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
784 Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce);
785 Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce);
786 Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque);
787 Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale);
788 Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm);
789 Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop);
790 Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix);
791 Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName);
792 Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass);
793 }
794
795 fail = true;
796
797 Respond("Failure response");
798
799 RestException re = new RestException(message+" <"+code+">");
800
801 re.statusCode = code;
802 re.statusDesc = message;
803 re.httpmethod = method;
804 re.httppath = path;
805
806 throw re;
807
808 }
809
810 // Reject this request
811
812 internal void Reject()
813 {
814 Fail(Rest.HttpStatusCodeNotImplemented, Rest.HttpStatusDescNotImplemented);
815 }
816
817 // This MUST be called by an agent handler before it returns
818 // control to Handle, otherwise the request will be ignored.
819 // This is called implciitly for the REST stream handlers and
820 // is harmless if it is called twice.
821
822 internal virtual bool Respond(string reason)
823 {
824
825 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
826
827 if (!handled)
828 {
829
830 Rest.Log.DebugFormat("{0} Generating Response", MsgId);
831
832 // Process any arbitrary headers collected
833
834 BuildHeaders();
835
836 // A Head request can NOT have a body!
837 if (method != Rest.HEAD)
838 {
839
840 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
841
842 if (writer != null)
843 {
844 Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
845 Rest.Log.DebugFormat("{0} XML Response exists", MsgId);
846 writer.Flush();
847 writer.Close();
848 if (!fail)
849 {
850 buffer = xmldata.ToArray();
851 AddHeader("Content-Type","application/xml");
852 }
853 xmldata.Close();
854 Rest.Log.DebugFormat("{0} XML Response encoded", MsgId);
855 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
856 }
857
858 // If buffer != null, then we assume that
859 // this has already been done some other
860 // way. For example, transfer encoding might
861 // have been done.
862
863 if (buffer == null)
864 {
865 if (body != null && body.Length > 0)
866 {
867 Rest.Log.DebugFormat("{0} String-based entity", MsgId);
868 buffer = encoding.GetBytes(body);
869 }
870 }
871
872 if (buffer != null)
873 {
874 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
875 if (response.Headers.Get("Content-Encoding") == null)
876 response.ContentEncoding = encoding;
877 response.ContentLength64 = buffer.Length;
878 response.SendChunked = false;
879 response.KeepAlive = false;
880 }
881
882 }
883
884 // Set the status code & description. If nothing
885 // has been stored, we consider that a success
886
887 if (statusCode == 0)
888 {
889 Complete();
890 }
891
892 response.StatusCode = statusCode;
893
894 if (response.StatusCode == (int)OSHttpStatusCode.RedirectMovedTemporarily ||
895 response.StatusCode == (int)OSHttpStatusCode.RedirectMovedPermanently)
896 {
897 response.RedirectLocation = redirectLocation;
898 }
899
900 if (statusDescription != null)
901 {
902 response.StatusDescription = statusDescription;
903 }
904
905 // Finally we send back our response, consuming
906 // any exceptions that doing so might produce.
907
908 // We've left the setting of handled' until the
909 // last minute because the header settings included
910 // above are pretty harmless. But everything from
911 // here on down probably leaves the response
912 // element unusable by anyone else.
913
914 handled = true;
915
916 if (buffer != null && buffer.Length != 0)
917 {
918 Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
919 MsgId, buffer.Length, encoding.GetString(buffer));
920 response.OutputStream.Write(buffer, 0, buffer.Length);
921 }
922
923 response.OutputStream.Close();
924
925 if (request.InputStream != null)
926 {
927 request.InputStream.Close();
928 }
929
930 }
931
932 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
933
934 return handled;
935
936 }
937
938 // Add a header to the table. If the header
939 // already exists, it is replaced.
940
941 internal void AddHeader(string hdr, string data)
942 {
943
944 if (headers == null)
945 {
946 headers = new NameValueCollection();
947 }
948
949 headers[hdr] = data;
950
951 }
952
953 // Keep explicit track of any headers which
954 // are to be removed.
955
956 internal void RemoveHeader(string hdr)
957 {
958
959 if (removed_headers == null)
960 {
961 removed_headers = new List<string>();
962 }
963
964 removed_headers.Add(hdr);
965
966 if (headers != null)
967 {
968 headers.Remove(hdr);
969 }
970
971 }
972
973 // Should it prove necessary, we could always
974 // restore the header collection from a cloned
975 // copy, but for now we'll assume that that is
976 // not necessary.
977
978 private void BuildHeaders()
979 {
980 if (removed_headers != null)
981 {
982 foreach (string h in removed_headers)
983 {
984 Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, h);
985 response.Headers.Remove(h);
986 }
987 }
988 if (headers!= null)
989 {
990 for (int i = 0; i < headers.Count; i++)
991 {
992 Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
993 MsgId, headers.GetKey(i), headers.Get(i));
994 response.Headers.Add(headers.GetKey(i), headers.Get(i));
995 }
996 }
997 }
998
999 /// <summary>
1000 /// Helper methods for deconstructing and reconstructing
1001 /// URI path data.
1002 /// </summary>
1003
1004 private void initUrl()
1005 {
1006
1007 uri = request.Url;
1008
1009 if (query == null)
1010 {
1011 query = uri.Query;
1012 }
1013
1014 // If the path has not been previously initialized,
1015 // do so now.
1016
1017 if (path == null)
1018 {
1019 path = uri.AbsolutePath;
1020 if (path.EndsWith(Rest.UrlPathSeparator))
1021 path = path.Substring(0,path.Length-1);
1022 path = Uri.UnescapeDataString(path);
1023 }
1024
1025 // If we succeeded in getting a path, perform any
1026 // additional pre-processing required.
1027
1028 if (path != null)
1029 {
1030 if (Rest.ExtendedEscape)
1031 {
1032 // Handle "+". Not a standard substitution, but
1033 // common enough...
1034 path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
1035 }
1036 pathNodes = path.Split(Rest.CA_PATHSEP);
1037 }
1038 else
1039 {
1040 pathNodes = EmptyPath;
1041 }
1042
1043 // Request server context info
1044
1045 hostname = uri.Host;
1046 port = uri.Port;
1047
1048 }
1049
1050 internal int initParameters(int prfxlen)
1051 {
1052
1053 if (prfxlen < path.Length-1)
1054 {
1055 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
1056 }
1057 else
1058 {
1059 parameters = new string[0];
1060 }
1061
1062 // Generate a debug list of the decoded parameters
1063
1064 if (Rest.DEBUG && prfxlen < path.Length-1)
1065 {
1066 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
1067 for (int i = 0; i < parameters.Length; i++)
1068 {
1069 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
1070 }
1071 }
1072
1073 return parameters.Length;
1074
1075 }
1076
1077 internal string[] PathNodes
1078 {
1079 get
1080 {
1081 if (pathNodes == null)
1082 {
1083 initUrl();
1084 }
1085 return pathNodes;
1086 }
1087 }
1088
1089 internal string BuildUrl(int first, int last)
1090 {
1091
1092 if (pathNodes == null)
1093 {
1094 initUrl();
1095 }
1096
1097 if (first < 0)
1098 {
1099 first = first + pathNodes.Length;
1100 }
1101
1102 if (last < 0)
1103 {
1104 last = last + pathNodes.Length;
1105 if (last < 0)
1106 {
1107 return Rest.UrlPathSeparator;
1108 }
1109 }
1110
1111 sbuilder.Length = 0;
1112 sbuilder.Append(Rest.UrlPathSeparator);
1113
1114 if (first <= last)
1115 {
1116 for (int i = first; i <= last; i++)
1117 {
1118 sbuilder.Append(pathNodes[i]);
1119 sbuilder.Append(Rest.UrlPathSeparator);
1120 }
1121 }
1122 else
1123 {
1124 for (int i = last; i >= first; i--)
1125 {
1126 sbuilder.Append(pathNodes[i]);
1127 sbuilder.Append(Rest.UrlPathSeparator);
1128 }
1129 }
1130
1131 return sbuilder.ToString();
1132
1133 }
1134
1135 // Setup the XML writer for output
1136
1137 internal void initXmlWriter()
1138 {
1139 XmlWriterSettings settings = new XmlWriterSettings();
1140 xmldata = new MemoryStream();
1141 settings.Indent = true;
1142 settings.IndentChars = " ";
1143 settings.Encoding = encoding;
1144 settings.CloseOutput = false;
1145 settings.OmitXmlDeclaration = true;
1146 settings.ConformanceLevel = ConformanceLevel.Fragment;
1147 writer = XmlWriter.Create(xmldata, settings);
1148 }
1149
1150 internal void initXmlReader()
1151 {
1152 XmlReaderSettings settings = new XmlReaderSettings();
1153 settings.ConformanceLevel = ConformanceLevel.Fragment;
1154 settings.IgnoreComments = true;
1155 settings.IgnoreWhitespace = true;
1156 settings.IgnoreProcessingInstructions = true;
1157 settings.ValidationType = ValidationType.None;
1158 // reader = XmlReader.Create(new StringReader(entity),settings);
1159 reader = XmlReader.Create(request.InputStream,settings);
1160 }
1161
1162 private void Flush()
1163 {
1164 byte[] dbuffer = new byte[8192];
1165 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
1166 return;
1167 }
1168
1169 // This allows us to make errors a bit more apparent in REST
1170
1171 internal void SendHtml(string text)
1172 {
1173 SendHtml("OpenSim REST Interface 1.0", text);
1174 }
1175
1176 internal void SendHtml(string title, string text)
1177 {
1178
1179 AddHeader(Rest.HttpHeaderContentType, "text/html");
1180 sbuilder.Length = 0;
1181
1182 sbuilder.Append("<html>");
1183 sbuilder.Append("<head>");
1184 sbuilder.Append("<title>");
1185 sbuilder.Append(title);
1186 sbuilder.Append("</title>");
1187 sbuilder.Append("</head>");
1188
1189 sbuilder.Append("<body>");
1190 sbuilder.Append("<br />");
1191 sbuilder.Append("<p>");
1192 sbuilder.Append(text);
1193 sbuilder.Append("</p>");
1194 sbuilder.Append("</body>");
1195 sbuilder.Append("</html>");
1196
1197 html = sbuilder.ToString();
1198
1199 }
1200 }
1201}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
new file mode 100644
index 0000000..e88c54d
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
@@ -0,0 +1,479 @@
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 OpenSim 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 */
28
29using System;
30using System.Collections.Generic;
31using System.Reflection;
32using System.Text;
33using OpenSim.Framework;
34using OpenSim.Framework.Servers;
35using OpenSim.Framework.Communications;
36using OpenSim.Framework.Communications.Cache;
37using Nini.Config;
38
39namespace OpenSim.ApplicationPlugins.Rest.Inventory
40{
41
42 public class Rest
43 {
44
45 internal static readonly log4net.ILog Log =
46 log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47
48 internal static bool DEBUG = Log.IsDebugEnabled;
49
50 /// <summary>
51 /// These values have a single value for the whole
52 /// domain and lifetime of the plugin handler. We
53 /// make them static for ease of reference within
54 /// the assembly. These are initialized by the
55 /// RestHandler class during start-up.
56 /// </summary>
57
58 internal static RestHandler Plugin = null;
59 internal static OpenSimBase main = null;
60 internal static CommunicationsManager Comms = null;
61 internal static IInventoryServices InventoryServices = null;
62 internal static IUserService UserServices = null;
63 internal static AssetCache AssetServices = null;
64 internal static string Prefix = null;
65 internal static IConfig Config = null;
66 internal static string GodKey = null;
67 internal static bool Authenticate = true;
68 internal static bool Secure = true;
69 internal static bool ExtendedEscape = true;
70 internal static bool DumpAsset = false;
71 internal static string Realm = "REST";
72 internal static Dictionary<string,string> Domains = new Dictionary<string,string>();
73 internal static int CreationDate = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
74 internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
75
76 internal static string MsgId
77 {
78 get { return Plugin.MsgId; }
79 }
80
81 internal static string RequestId
82 {
83 get { return Plugin.RequestId; }
84 }
85
86 internal static Encoding Encoding = Encoding.UTF8;
87
88 /// <summary>
89 /// Version control for REST implementation. This
90 /// refers to the overall infrastructure represented
91 /// by the following classes
92 /// RequestData
93 /// RequestInventoryPlugin
94 /// Rest
95 /// It does no describe implementation classes such as
96 /// RestInventoryServices, which may morph much more
97 /// often. Such classes ARE dependent upon this however
98 /// and should check it in their Initialize method.
99 /// </summary>
100
101 public static readonly float Version = 1.0F;
102 public const string Name = "REST 1.0";
103
104 /// <summary>
105 /// Currently defined HTTP methods.
106 /// Only GET and HEAD are required to be
107 /// supported by all servers. See Respond
108 /// to see how these are handled.
109 /// </summary>
110
111 // REST AGENT 1.0 interpretations
112 public const string GET = "get"; // information retrieval - server state unchanged
113 public const string HEAD = "head"; // same as get except only the headers are returned.
114 public const string POST = "post"; // Replace the URI designated resource with the entity.
115 public const string PUT = "put"; // Add the entity to the context represented by the URI
116 public const string DELETE = "delete"; // Remove the URI designated resource from the server.
117
118 public const string OPTIONS = "options"; //
119 public const string TRACE = "trace"; //
120 public const string CONNECT = "connect"; //
121
122 // Define this in one place...
123
124 public const string UrlPathSeparator = "/";
125 public const string UrlMethodSeparator = ":";
126
127 // Redirection qualifications
128
129 public const bool PERMANENT = false;
130 public const bool TEMPORARY = true;
131
132 // Constant arrays used by String.Split
133
134 public static readonly char C_SPACE = ' ';
135 public static readonly char C_SLASH = '/';
136 public static readonly char C_PATHSEP = '/';
137 public static readonly char C_COLON = ':';
138 public static readonly char C_PLUS = '+';
139 public static readonly char C_PERIOD = '.';
140 public static readonly char C_COMMA = ',';
141 public static readonly char C_DQUOTE = '"';
142
143 public static readonly string CS_SPACE = " ";
144 public static readonly string CS_SLASH = "/";
145 public static readonly string CS_PATHSEP = "/";
146 public static readonly string CS_COLON = ":";
147 public static readonly string CS_PLUS = "+";
148 public static readonly string CS_PERIOD = ".";
149 public static readonly string CS_COMMA = ",";
150 public static readonly string CS_DQUOTE = "\"";
151
152 public static readonly char[] CA_SPACE = { C_SPACE };
153 public static readonly char[] CA_SLASH = { C_SLASH };
154 public static readonly char[] CA_PATHSEP = { C_PATHSEP };
155 public static readonly char[] CA_COLON = { C_COLON };
156 public static readonly char[] CA_PERIOD = { C_PERIOD };
157 public static readonly char[] CA_PLUS = { C_PLUS };
158 public static readonly char[] CA_COMMA = { C_COMMA };
159 public static readonly char[] CA_DQUOTE = { C_DQUOTE };
160
161 // HTTP Code Values (in value order)
162
163 public const int HttpStatusCodeContinue = 100;
164 public const int HttpStatusCodeSwitchingProtocols = 101;
165
166 public const int HttpStatusCodeOK = 200;
167 public const int HttpStatusCodeCreated = 201;
168 public const int HttpStatusCodeAccepted = 202;
169 public const int HttpStatusCodeNonAuthoritative = 203;
170 public const int HttpStatusCodeNoContent = 204;
171 public const int HttpStatusCodeResetContent = 205;
172 public const int HttpStatusCodePartialContent = 206;
173
174 public const int HttpStatusCodeMultipleChoices = 300;
175 public const int HttpStatusCodePermanentRedirect = 301;
176 public const int HttpStatusCodeFound = 302;
177 public const int HttpStatusCodeSeeOther = 303;
178 public const int HttpStatusCodeNotModified = 304;
179 public const int HttpStatusCodeUseProxy = 305;
180 public const int HttpStatusCodeReserved306 = 306;
181 public const int HttpStatusCodeTemporaryRedirect = 307;
182
183 public const int HttpStatusCodeBadRequest = 400;
184 public const int HttpStatusCodeNotAuthorized = 401;
185 public const int HttpStatusCodePaymentRequired = 402;
186 public const int HttpStatusCodeForbidden = 403;
187 public const int HttpStatusCodeNotFound = 404;
188 public const int HttpStatusCodeMethodNotAllowed = 405;
189 public const int HttpStatusCodeNotAcceptable = 406;
190 public const int HttpStatusCodeProxyAuthenticate = 407;
191 public const int HttpStatusCodeTimeOut = 408;
192 public const int HttpStatusCodeConflict = 409;
193 public const int HttpStatusCodeGone = 410;
194 public const int HttpStatusCodeLengthRequired = 411;
195 public const int HttpStatusCodePreconditionFailed = 412;
196 public const int HttpStatusCodeEntityTooLarge = 413;
197 public const int HttpStatusCodeUriTooLarge = 414;
198 public const int HttpStatusCodeUnsupportedMedia = 415;
199 public const int HttpStatusCodeRangeNotSatsified = 416;
200 public const int HttpStatusCodeExpectationFailed = 417;
201
202 public const int HttpStatusCodeServerError = 500;
203 public const int HttpStatusCodeNotImplemented = 501;
204 public const int HttpStatusCodeBadGateway = 502;
205 public const int HttpStatusCodeServiceUnavailable = 503;
206 public const int HttpStatusCodeGatewayTimeout = 504;
207 public const int HttpStatusCodeHttpVersionError = 505;
208
209 // HTTP Status Descriptions (in status code order)
210
211 public const string HttpStatusDescContinue = "Continue Request"; // 100
212 public const string HttpStatusDescSwitchingProtocols = "Switching Protocols"; // 101
213
214 public const string HttpStatusDescOK = "OK";
215 public const string HttpStatusDescCreated = "CREATED";
216 public const string HttpStatusDescAccepted = "ACCEPTED";
217 public const string HttpStatusDescNonAuthoritative = "NON-AUTHORITATIVE INFORMATION";
218 public const string HttpStatusDescNoContent = "NO CONTENT";
219 public const string HttpStatusDescResetContent = "RESET CONTENT";
220 public const string HttpStatusDescPartialContent = "PARTIAL CONTENT";
221
222 public const string HttpStatusDescMultipleChoices = "MULTIPLE CHOICES";
223 public const string HttpStatusDescPermanentRedirect = "PERMANENT REDIRECT";
224 public const string HttpStatusDescFound = "FOUND";
225 public const string HttpStatusDescSeeOther = "SEE OTHER";
226 public const string HttpStatusDescNotModified = "NOT MODIFIED";
227 public const string HttpStatusDescUseProxy = "USE PROXY";
228 public const string HttpStatusDescReserved306 = "RESERVED CODE 306";
229 public const string HttpStatusDescTemporaryRedirect = "TEMPORARY REDIRECT";
230
231 public const string HttpStatusDescBadRequest = "BAD REQUEST";
232 public const string HttpStatusDescNotAuthorized = "NOT AUTHORIZED";
233 public const string HttpStatusDescPaymentRequired = "PAYMENT REQUIRED";
234 public const string HttpStatusDescForbidden = "FORBIDDEN";
235 public const string HttpStatusDescNotFound = "NOT FOUND";
236 public const string HttpStatusDescMethodNotAllowed = "METHOD NOT ALLOWED";
237 public const string HttpStatusDescNotAcceptable = "NOT ACCEPTABLE";
238 public const string HttpStatusDescProxyAuthenticate = "PROXY AUTHENTICATION REQUIRED";
239 public const string HttpStatusDescTimeOut = "TIMEOUT";
240 public const string HttpStatusDescConflict = "CONFLICT";
241 public const string HttpStatusDescGone = "GONE";
242 public const string HttpStatusDescLengthRequired = "LENGTH REQUIRED";
243 public const string HttpStatusDescPreconditionFailed = "PRECONDITION FAILED";
244 public const string HttpStatusDescEntityTooLarge = "ENTITY TOO LARGE";
245 public const string HttpStatusDescUriTooLarge = "URI TOO LARGE";
246 public const string HttpStatusDescUnsupportedMedia = "UNSUPPORTED MEDIA";
247 public const string HttpStatusDescRangeNotSatisfied = "RANGE NOT SATISFIED";
248 public const string HttpStatusDescExpectationFailed = "EXPECTATION FAILED";
249
250 public const string HttpStatusDescServerError = "SERVER ERROR";
251 public const string HttpStatusDescNotImplemented = "NOT IMPLEMENTED";
252 public const string HttpStatusDescBadGateway = "BAD GATEWAY";
253 public const string HttpStatusDescServiceUnavailable = "SERVICE UNAVAILABLE";
254 public const string HttpStatusDescGatewayTimeout = "GATEWAY TIMEOUT";
255 public const string HttpStatusDescHttpVersionError = "HTTP VERSION NOT SUPPORTED";
256
257 // HTTP Headers
258
259 public const string HttpHeaderAccept = "Accept";
260 public const string HttpHeaderAcceptCharset = "Accept-Charset";
261 public const string HttpHeaderAcceptEncoding = "Accept-Encoding";
262 public const string HttpHeaderAcceptLanguage = "Accept-Language";
263 public const string HttpHeaderAcceptRanges = "Accept-Ranges";
264 public const string HttpHeaderAge = "Age";
265 public const string HttpHeaderAllow = "Allow";
266 public const string HttpHeaderAuthorization = "Authorization";
267 public const string HttpHeaderCacheControl = "Cache-Control";
268 public const string HttpHeaderConnection = "Connection";
269 public const string HttpHeaderContentEncoding = "Content-Encoding";
270 public const string HttpHeaderContentLanguage = "Content-Language";
271 public const string HttpHeaderContentLength = "Content-Length";
272 public const string HttpHeaderContentLocation = "Content-Location";
273 public const string HttpHeaderContentMD5 = "Content-MD5";
274 public const string HttpHeaderContentRange = "Content-Range";
275 public const string HttpHeaderContentType = "Content-Type";
276 public const string HttpHeaderDate = "Date";
277 public const string HttpHeaderETag = "ETag";
278 public const string HttpHeaderExpect = "Expect";
279 public const string HttpHeaderExpires = "Expires";
280 public const string HttpHeaderFrom = "From";
281 public const string HttpHeaderHost = "Host";
282 public const string HttpHeaderIfMatch = "If-Match";
283 public const string HttpHeaderIfModifiedSince = "If-Modified-Since";
284 public const string HttpHeaderIfNoneMatch = "If-None-Match";
285 public const string HttpHeaderIfRange = "If-Range";
286 public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since";
287 public const string HttpHeaderLastModified = "Last-Modified";
288 public const string HttpHeaderLocation = "Location";
289 public const string HttpHeaderMaxForwards = "Max-Forwards";
290 public const string HttpHeaderPragma = "Pragma";
291 public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate";
292 public const string HttpHeaderProxyAuthorization = "Proxy-Authorization";
293 public const string HttpHeaderRange = "Range";
294 public const string HttpHeaderReferer = "Referer";
295 public const string HttpHeaderRetryAfter = "Retry-After";
296 public const string HttpHeaderServer = "Server";
297 public const string HttpHeaderTE = "TE";
298 public const string HttpHeaderTrailer = "Trailer";
299 public const string HttpHeaderTransferEncoding = "Transfer-Encoding";
300 public const string HttpHeaderUpgrade = "Upgrade";
301 public const string HttpHeaderUserAgent = "User-Agent";
302 public const string HttpHeaderVary = "Vary";
303 public const string HttpHeaderVia = "Via";
304 public const string HttpHeaderWarning = "Warning";
305 public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate";
306
307 /// <summary>
308 /// Supported authentication schemes
309 /// </summary>
310
311 public const string AS_BASIC = "Basic";
312 public const string AS_DIGEST = "Digest";
313
314 /// Supported Digest algorithms
315
316 public const string Digest_MD5 = "MD5"; // assumedd efault if omitted
317 public const string Digest_MD5Sess = "MD5-sess";
318
319 public const string Qop_Auth = "auth";
320 public const string Qop_Int = "auth-int";
321
322 /// Utility routines
323
324 public static string StringToBase64(string str)
325 {
326 try
327 {
328 byte[] encData_byte = new byte[str.Length];
329 encData_byte = Encoding.UTF8.GetBytes(str);
330 return Convert.ToBase64String(encData_byte);
331 }
332 catch
333 {
334 return String.Empty;
335 }
336 }
337
338 public static string Base64ToString(string str)
339 {
340 UTF8Encoding encoder = new UTF8Encoding();
341 Decoder utf8Decode = encoder.GetDecoder();
342 try
343 {
344 byte[] todecode_byte = Convert.FromBase64String(str);
345 int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
346 char[] decoded_char = new char[charCount];
347 utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
348 return new String(decoded_char);
349 }
350 catch
351 {
352 return String.Empty;
353 }
354 }
355
356 private const string hvals = "0123456789abcdef";
357
358 public static int Hex2Int(string hex)
359 {
360 int val = 0;
361 int sum = 0;
362 string tmp = null;
363
364 if (hex != null)
365 {
366 tmp = hex.ToLower();
367 for (int i = 0; i < tmp.Length; i++)
368 {
369 val = hvals.IndexOf(tmp[i]);
370 if (val == -1)
371 break;
372 sum *= 16;
373 sum += val;
374 }
375 }
376
377 return sum;
378
379 }
380
381 public static string Int2Hex8(int val)
382 {
383 string res = String.Empty;
384 for (int i = 0; i < 8; i++)
385 {
386 res = (val % 16) + res;
387 val = val / 16;
388 }
389 return res;
390 }
391
392 public static string ToHex32(int val)
393 {
394 return String.Empty;
395 }
396
397 public static string ToHex32(string val)
398 {
399 return String.Empty;
400 }
401
402 // Nonce management
403
404 public static string NonceGenerator()
405 {
406 return StringToBase64(Guid.NewGuid().ToString());
407 }
408
409 // Dump he specified data stream;
410
411 public static void Dump(byte[] data)
412 {
413
414 char[] buffer = new char[Rest.DumpLineSize];
415 int cc = 0;
416
417 for (int i = 0; i < data.Length; i++)
418 {
419
420 if (i % Rest.DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8"));
421
422 if (i % 4 == 0) Console.Write(" ");
423// if (i%16 == 0) Console.Write(" ");
424
425 Console.Write("{0}",data[i].ToString("x2"));
426
427 if (data[i] < 127 && data[i] > 31)
428 buffer[i % Rest.DumpLineSize] = (char) data[i];
429 else
430 buffer[i % Rest.DumpLineSize] = '.';
431
432 cc++;
433
434 if (i != 0 && (i + 1) % Rest.DumpLineSize == 0)
435 {
436 Console.Write(" |"+(new String(buffer))+"|");
437 cc = 0;
438 }
439
440 }
441
442 // Finish off any incomplete line
443
444 if (cc != 0)
445 {
446 for (int i = cc ; i < Rest.DumpLineSize; i++)
447 {
448 if (i % 4 == 0) Console.Write(" ");
449 // if (i%16 == 0) Console.Write(" ");
450 Console.Write(" ");
451 buffer[i % Rest.DumpLineSize] = ' ';
452 }
453 Console.WriteLine(" |"+(new String(buffer))+"|");
454 }
455 else
456 {
457 Console.Write("\n");
458 }
459
460 }
461
462 }
463
464 // Local exception type
465
466 public class RestException : Exception
467 {
468
469 internal int statusCode;
470 internal string statusDesc;
471 internal string httpmethod;
472 internal string httppath;
473
474 public RestException(string msg) : base(msg)
475 {
476 }
477 }
478
479}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
new file mode 100644
index 0000000..839e0f2
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
@@ -0,0 +1,257 @@
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 OpenSim 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 */
28
29using libsecondlife;
30using Nini.Config;
31using System;
32using System.Collections.Generic;
33using System.IO;
34using System.Threading;
35using System.Xml;
36using OpenSim.Framework;
37using OpenSim.Framework.Servers;
38using OpenSim.Framework.Communications;
39using OpenSim.Framework.Communications.Cache;
40
41namespace OpenSim.ApplicationPlugins.Rest.Inventory
42{
43
44 public class RestAssetServices : IRest
45 {
46
47 private string key = "assets";
48 private bool enabled = false;
49 private string qPrefix = "assets";
50
51 // A simple constructor is used to handle any once-only
52 // initialization of working classes.
53
54 public RestAssetServices(RestHandler p_rest)
55 {
56
57 Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
58 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
59
60 // Integrate domain
61
62 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
63 {
64 qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix;
65 }
66
67 // Authentication domain
68
69 Rest.Domains.Add(key,Rest.Config.GetString("asset-domain",qPrefix));
70
71 // Register interface
72
73 Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate);
74
75 // Activate
76
77 enabled = true;
78
79 Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId);
80
81 }
82
83 // Post-construction, pre-enabled initialization opportunity
84 // Not currently exploited.
85
86 public void Initialize()
87 {
88 }
89
90 // Called by the plug-in to halt REST processing. Local processing is
91 // disabled, and control blocks until all current processing has
92 // completed. No new processing will be started
93
94 public void Close()
95 {
96 enabled = false;
97 Rest.Log.InfoFormat("{0} Asset services closing down", MsgId);
98 }
99
100 // Properties
101
102 internal string MsgId
103 {
104 get { return Rest.MsgId; }
105 }
106
107 #region Interface
108
109 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response)
110 {
111 return (RequestData) new AssetRequestData(request, response, qPrefix);
112 }
113
114 // Asset Handler
115
116 private void DoAsset(RequestData rparm)
117 {
118
119 if (!enabled) return;
120
121 AssetRequestData rdata = (AssetRequestData) rparm;
122
123 Rest.Log.DebugFormat("{0} REST Asset handler ENTRY", MsgId);
124
125 // Now that we know this is a serious attempt to
126 // access inventory data, we should find out who
127 // is asking, and make sure they are authorized
128 // to do so. We need to validate the caller's
129 // identity before revealing anything about the
130 // status quo. Authenticate throws an exception
131 // via Fail if no identity information is present.
132 //
133 // With the present HTTP server we can't use the
134 // builtin authentication mechanisms because they
135 // would be enforced for all in-bound requests.
136 // Instead we look at the headers ourselves and
137 // handle authentication directly.
138
139 try
140 {
141 if (!rdata.IsAuthenticated)
142 {
143 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized);
144 }
145 }
146 catch (RestException e)
147 {
148 if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
149 {
150 Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
151 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
152 rdata.request.Headers.Get("Authorization"));
153 }
154 else
155 {
156 Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
157 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
158 rdata.request.Headers.Get("Authorization"));
159 }
160 throw (e);
161 }
162
163 // Remove the prefix and what's left are the parameters. If we don't have
164 // the parameters we need, fail the request. Parameters do NOT include
165 // any supplied query values.
166
167 if (rdata.parameters.Length > 0)
168 {
169 switch (rdata.method)
170 {
171 case "get" :
172 DoGet(rdata);
173 break;
174 case "put" :
175 case "post" :
176 case "delete" :
177 default :
178 Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}",
179 MsgId, rdata.method);
180 rdata.Fail(Rest.HttpStatusCodeBadRequest,
181 Rest.HttpStatusDescBadRequest);
182 break;
183 }
184 }
185 else
186 {
187 Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId);
188 rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest);
189 }
190
191 Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId);
192
193 }
194
195 #endregion Interface
196
197 private void DoGet(AssetRequestData rdata)
198 {
199
200 bool istexture = false;
201
202 Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
203
204 // The only parameter we accept is an LLUUID for
205 // the asset
206
207 if (rdata.parameters.Length == 1)
208 {
209
210 LLUUID uuid = new LLUUID(rdata.parameters[0]);
211 AssetBase asset = Rest.AssetServices.GetAsset(uuid, istexture);
212
213 if (asset != null)
214 {
215
216 Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.parameters[0]);
217
218 rdata.initXmlWriter();
219
220 rdata.writer.WriteStartElement(String.Empty,"Asset",String.Empty);
221
222 rdata.writer.WriteAttributeString("id", asset.ID.ToString());
223 rdata.writer.WriteAttributeString("name", asset.Name);
224 rdata.writer.WriteAttributeString("desc", asset.Description);
225 rdata.writer.WriteAttributeString("type", asset.Type.ToString());
226 rdata.writer.WriteAttributeString("invtype", asset.InvType.ToString());
227 rdata.writer.WriteAttributeString("local", asset.Local.ToString());
228 rdata.writer.WriteAttributeString("temporary", asset.Temporary.ToString());
229
230 rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length);
231
232 rdata.writer.WriteFullEndElement();
233
234 }
235 else
236 {
237 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
238 rdata.Fail(Rest.HttpStatusCodeNotFound,
239 Rest.HttpStatusDescNotFound);
240 }
241 }
242
243 rdata.Complete();
244 rdata.Respond("Asset " + rdata.method + ": Normal completion");
245
246 }
247
248 internal class AssetRequestData : RequestData
249 {
250 internal AssetRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
251 : base(request, response, prefix)
252 {
253 }
254 }
255
256 }
257}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
new file mode 100644
index 0000000..0a0bf3f
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
@@ -0,0 +1,547 @@
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 OpenSim 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 */
28
29using System;
30using System.Collections.Generic;
31using System.Reflection;
32using OpenSim.Framework;
33using OpenSim.Framework.Servers;
34using OpenSim.ApplicationPlugins.Rest;
35using Mono.Addins;
36
37[assembly : Addin]
38[assembly : AddinDependency("OpenSim", "0.5")]
39
40namespace OpenSim.ApplicationPlugins.Rest.Inventory
41{
42
43 [Extension("/OpenSim/Startup")]
44
45 public class RestHandler : RestPlugin, IHttpAgentHandler
46 {
47
48 #region local static state
49
50 /// <summary>
51 /// This static initializer scans the assembly for classes that
52 /// export the IRest interface and builds a list of them. These
53 /// are later activated by the handler. To add a new handler it
54 /// is only necessary to create a new services class that implements
55 /// the IRest interface, and recompile the handler. This gives
56 /// all of the build-time flexibility of a modular approach
57 /// while not introducing yet-another module loader. Note that
58 /// multiple assembles can still be built, each with its own set
59 /// of handlers.
60 /// </summary>
61
62 private static bool handlersLoaded = false;
63 private static List<Type> classes = new List<Type>();
64 private static List<IRest> handlers = new List<IRest>();
65 private static Type[] parms = new Type[1];
66 private static Object[] args = new Object[1];
67
68 static RestHandler()
69 {
70 Module[] mods = Assembly.GetExecutingAssembly().GetModules();
71 foreach (Module m in mods)
72 {
73 Type[] types = m.GetTypes();
74 foreach (Type t in types)
75 {
76 if (t.GetInterface("IRest") != null)
77 {
78 classes.Add(t);
79 }
80 }
81 }
82 }
83
84 #endregion local static state
85
86 #region local instance state
87
88 /// <remarks>
89 /// The handler delegate is not noteworthy. The allocator allows
90 /// a given handler to optionally subclass the base RequestData
91 /// structure to carry any locally required per-request state
92 /// needed.
93 /// </remarks>
94 internal delegate void RestMethodHandler(RequestData rdata);
95 internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
96
97 // Handler tables: both stream and REST are supported
98
99 internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
100 internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
101 internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
102
103 /// <summary>
104 /// This routine loads all of the handlers discovered during
105 /// instance initialization. Each handler is responsible for
106 /// registering itself with this handler.
107 /// I was not able to make this code work in a constructor.
108 /// </summary>
109 private void LoadHandlers()
110 {
111 lock(handlers)
112 {
113 if (!handlersLoaded)
114 {
115 parms[0] = this.GetType();
116 args[0] = this;
117
118 ConstructorInfo ci;
119 Object ht;
120
121 foreach (Type t in classes)
122 {
123 ci = t.GetConstructor(parms);
124 ht = ci.Invoke(args);
125 handlers.Add((IRest)ht);
126 }
127 handlersLoaded = true;
128 }
129 }
130 }
131
132 #endregion local instance state
133
134 #region overriding properties
135
136 // Used to differentiate the message header.
137
138 public override string Name
139 {
140 get { return "HANDLER"; }
141 }
142
143 // Used to partition the configuration space.
144
145 public override string ConfigName
146 {
147 get { return "RestHandler"; }
148 }
149
150 // We have to rename these because we want
151 // to be able to share the values with other
152 // classes in our assembly and the base
153 // names are protected.
154
155 internal string MsgId
156 {
157 get { return base.MsgID; }
158 }
159
160 internal string RequestId
161 {
162 get { return base.RequestID; }
163 }
164
165 #endregion overriding properties
166
167 #region overriding methods
168
169 /// <summary>
170 /// This method is called by OpenSimMain immediately after loading the
171 /// plugin and after basic server setup, but before running any server commands.
172 /// </summary>
173 /// <remarks>
174 /// Note that entries MUST be added to the active configuration files before
175 /// the plugin can be enabled.
176 /// </remarks>
177 public override void Initialise(OpenSimBase openSim)
178 {
179 try
180 {
181
182 /// <remarks>
183 /// This plugin will only be enabled if the broader
184 /// REST plugin mechanism is enabled.
185 /// </remarks>
186
187 Rest.Log.InfoFormat("{0} Plugin is initializing", MsgID);
188
189 base.Initialise(openSim);
190
191 if (!IsEnabled)
192 {
193 Rest.Log.WarnFormat("{0} Plugins are disabled", MsgID);
194 return;
195 }
196
197 Rest.Log.InfoFormat("{0} Plugin will be enabled", MsgID);
198
199 /// <remarks>
200 /// These are stored in static variables to make
201 /// them easy to reach from anywhere in the assembly.
202 /// </remarks>
203
204 Rest.main = openSim;
205 Rest.Plugin = this;
206 Rest.Comms = App.CommunicationsManager;
207 Rest.UserServices = Rest.Comms.UserService;
208 Rest.InventoryServices = Rest.Comms.InventoryService;
209 Rest.AssetServices = Rest.Comms.AssetCache;
210 Rest.Config = Config;
211 Rest.Prefix = Prefix;
212 Rest.GodKey = GodKey;
213
214 Rest.Authenticate = Rest.Config.GetBoolean("authenticate",true);
215 Rest.Secure = Rest.Config.GetBoolean("secured",true);
216 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true);
217 Rest.Realm = Rest.Config.GetString("realm","OpenSim REST");
218 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false);
219 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32);
220
221 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
222 (Rest.Authenticate ? "" : "not "));
223
224 Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId,
225 (Rest.Authenticate ? "" : "not "));
226
227 Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId,
228 (Rest.ExtendedEscape ? "" : "not "));
229
230 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
231 (Rest.DumpAsset ? "" : "not "));
232
233 if (Rest.DumpAsset)
234 {
235 Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId,
236 Rest.DumpLineSize);
237 }
238
239 // Load all of the handlers present in the
240 // assembly
241
242 // In principle, as we're an application plug-in,
243 // most of what needs to be done could be done using
244 // static resources, however the Open Sim plug-in
245 // model makes this an instance, so that's what we
246 // need to be.
247 // There is only one Communications manager per
248 // server, and by inference, only one each of the
249 // user, asset, and inventory servers. So we can cache
250 // those using a static initializer.
251 // We move all of this processing off to another
252 // services class to minimize overlap between function
253 // and infrastructure.
254
255 LoadHandlers();
256
257 /// <remarks>
258 /// The intention of a post construction initializer
259 /// is to allow for setup that is dependent upon other
260 /// activities outside of the agency. We don't currently
261 /// have any, but the design allows for it.
262 /// </remarks>
263
264 foreach (IRest handler in handlers)
265 {
266 handler.Initialize();
267 }
268
269 /// <remarks>
270 /// Now that everything is setup we can proceed and
271 /// add this agent to the HTTP server's handler list
272 /// </remarks>
273
274 if (!AddAgentHandler(Rest.Name,this))
275 {
276 Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId);
277 foreach (IRest handler in handlers)
278 {
279 handler.Close();
280 }
281 }
282
283 }
284 catch (Exception e)
285 {
286 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgID, e.Message);
287 }
288
289 }
290
291 /// <summary>
292 /// In the interests of efficiency, and because we cannot determine whether
293 /// or not this instance will actually be harvested, we clobber the only
294 /// anchoring reference to the working state for this plug-in. What the
295 /// call to close does is irrelevant to this class beyond knowing that it
296 /// can nullify the reference when it returns.
297 /// To make sure everything is copacetic we make sure the primary interface
298 /// is disabled by deleting the handler from the HTTP server tables.
299 /// </summary>
300 public override void Close()
301 {
302
303 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgID);
304
305 try
306 {
307 RemoveAgentHandler(Rest.Name, this);
308 }
309 catch (KeyNotFoundException){}
310
311 foreach (IRest handler in handlers)
312 {
313 handler.Close();
314 }
315
316 }
317
318 #endregion overriding methods
319
320 #region interface methods
321
322 /// <summary>
323 /// This method is called by the server to match the client, it could
324 /// just return true if we only want one such handler. For now we
325 /// match any explicitly specified client.
326 /// </summary>
327 public bool Match(OSHttpRequest request, OSHttpResponse response)
328 {
329 string path = request.RawUrl;
330 foreach (string key in pathHandlers.Keys)
331 {
332 if (path.StartsWith(key))
333 {
334 return ( path.Length == key.Length ||
335 path.Substring(key.Length,1) == Rest.UrlPathSeparator);
336 }
337 }
338
339 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
340 foreach (string key in streamHandlers.Keys)
341 {
342 if (path.StartsWith(key))
343 {
344 return true;
345 }
346 }
347
348 return false;
349 }
350
351 /// <summary>
352 /// Preconditions:
353 /// [1] request != null and is a valid request object
354 /// [2] response != null and is a valid response object
355 /// Behavior is undefined if preconditions are not satisfied.
356 /// </summary>
357 public bool Handle(OSHttpRequest request, OSHttpResponse response)
358 {
359 bool handled;
360 base.MsgID = base.RequestID;
361
362 if (Rest.DEBUG)
363 {
364 Rest.Log.DebugFormat("{0} ENTRY", MsgId);
365 Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent);
366 Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod);
367
368 for (int i = 0; i < request.Headers.Count; i++)
369 {
370 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
371 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
372 }
373 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
374 }
375
376 // If a path handler worked we're done, otherwise try any
377 // available stream handlers too.
378
379 try
380 {
381 handled = FindPathHandler(request, response) ||
382 FindStreamHandler(request, response);
383 }
384 catch (Exception e)
385 {
386 // A raw exception indicates that something we weren't expecting has
387 // happened. This should always reflect a shortcoming in the plugin,
388 // or a failure to satisfy the preconditions.
389 Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
390 handled = true;
391 }
392
393 Rest.Log.DebugFormat("{0} EXIT", MsgId);
394
395 return handled;
396
397 }
398
399 #endregion interface methods
400
401 /// <summary>
402 /// If there is a stream handler registered that can handle the
403 /// request, then fine. If the request is not matched, do
404 /// nothing.
405 /// </summary>
406
407 private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response)
408 {
409 RequestData rdata = new RequestData(request, response, String.Empty);
410
411 string bestMatch = null;
412 string path = String.Format("{0}:{1}", rdata.method, rdata.path);
413
414 Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
415
416 foreach (string pattern in streamHandlers.Keys)
417 {
418 if (path.StartsWith(pattern))
419 {
420 if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
421 {
422 bestMatch = pattern;
423 }
424 }
425 }
426
427 // Handle using the best match available
428
429 if (!String.IsNullOrEmpty(bestMatch))
430 {
431 Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
432 RestStreamHandler handler = streamHandlers[bestMatch];
433 rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response);
434 rdata.AddHeader(rdata.response.ContentType,handler.ContentType);
435 rdata.Respond("FindStreamHandler Completion");
436 }
437
438 return rdata.handled;
439
440 }
441
442 // Preserves the original handler's semantics
443
444 public new void AddStreamHandler(string httpMethod, string path, RestMethod method)
445 {
446
447 if (!IsEnabled)
448 {
449 return;
450 }
451
452 if (!path.StartsWith(Rest.Prefix))
453 {
454 path = String.Format("{0}{1}", Rest.Prefix, path);
455 }
456
457 path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path);
458
459 // Conditionally add to the list
460
461 if (!streamHandlers.ContainsKey(path))
462 {
463 streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
464 Rest.Log.DebugFormat("{0} Added handler for {1}", MsgID, path);
465 }
466 else
467 {
468 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgID, path);
469 }
470
471 }
472
473
474 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
475 {
476
477 RequestData rdata = null;
478 string bestMatch = null;
479
480 if (!IsEnabled)
481 {
482 return false;
483 }
484
485 // Conditionally add to the list
486
487 Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl);
488
489 foreach (string pattern in pathHandlers.Keys)
490 {
491 if (request.RawUrl.StartsWith(pattern))
492 {
493 if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
494 {
495 bestMatch = pattern;
496 }
497 }
498 }
499
500 if (!String.IsNullOrEmpty(bestMatch))
501 {
502
503 rdata = pathAllocators[bestMatch](request, response);
504
505 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
506
507 try
508 {
509 pathHandlers[bestMatch](rdata);
510 }
511
512 // A plugin generated error indicates a request-related error
513 // that has been handled by the plugin.
514
515 catch (RestException r)
516 {
517 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
518 }
519
520 }
521
522 return (rdata == null) ? false : rdata.handled;
523
524 }
525
526 internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
527 {
528 if (pathHandlers.ContainsKey(path))
529 {
530 Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
531 pathHandlers.Remove(path);
532 }
533
534 if (pathAllocators.ContainsKey(path))
535 {
536 Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path);
537 pathAllocators.Remove(path);
538 }
539
540 Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path);
541
542 pathHandlers.Add(path, mh);
543 pathAllocators.Add(path, ra);
544
545 }
546 }
547}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
new file mode 100644
index 0000000..0fc10f9
--- /dev/null
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
@@ -0,0 +1,1993 @@
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 OpenSim 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 */
28
29using System;
30using System.Collections.Generic;
31using System.IO;
32using System.Threading;
33using System.Xml;
34using OpenSim.Framework;
35using OpenSim.Framework.Servers;
36using OpenSim.Framework.Communications;
37using OpenSim.Framework.Communications.Cache;
38using libsecondlife;
39using Nini.Config;
40
41namespace OpenSim.ApplicationPlugins.Rest.Inventory
42{
43
44 public class RestInventoryServices : IRest
45 {
46
47 private string key = "inventory";
48 private bool enabled = false;
49 private string qPrefix = "inventory";
50
51 // A simple constructor is used to handle any once-only
52 // initialization of working classes.
53
54 public RestInventoryServices(RestHandler p_rest)
55 {
56
57 Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId);
58 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
59
60 // Update to reflect the full prefix if not absolute
61
62 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
63 {
64 qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix;
65 }
66
67 // Authentication domain
68
69 Rest.Domains.Add(key, Rest.Config.GetString("inventory-domain",qPrefix));
70
71 // Register interface
72
73 Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate);
74
75 // Activate
76
77 enabled = true;
78
79 Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId);
80
81 }
82
83 // Post-construction, pre-enabled initialization opportunity
84 // Not currently exploited.
85
86 public void Initialize()
87 {
88 }
89
90 // Called by the plug-in to halt REST processing. Local processing is
91 // disabled, and control blocks until all current processing has
92 // completed. No new processing will be started
93
94 public void Close()
95 {
96 enabled = false;
97 Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId);
98 }
99
100 // Convenient properties
101
102 internal string MsgId
103 {
104 get { return Rest.MsgId; }
105 }
106
107 #region Interface
108
109 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response)
110 {
111 return (RequestData) new InventoryRequestData(request, response, qPrefix);
112 }
113
114 /// <summary>
115 /// This method is registered with the handler when this class is
116 /// initialized. It is called whenever the URI includes this handler's
117 /// prefix string.
118 /// It handles all aspects of inventory REST processing.
119 /// </summary>
120
121 private void DoInventory(RequestData hdata)
122 {
123
124 InventoryRequestData rdata = (InventoryRequestData) hdata;
125
126 Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId);
127
128 // We're disabled
129 if (!enabled)
130 {
131 return;
132 }
133
134 // Now that we know this is a serious attempt to
135 // access inventory data, we should find out who
136 // is asking, and make sure they are authorized
137 // to do so. We need to validate the caller's
138 // identity before revealing anything about the
139 // status quo. Authenticate throws an exception
140 // via Fail if no identity information is present.
141 //
142 // With the present HTTP server we can't use the
143 // builtin authentication mechanisms because they
144 // would be enforced for all in-bound requests.
145 // Instead we look at the headers ourselves and
146 // handle authentication directly.
147
148 try
149 {
150 if (!rdata.IsAuthenticated)
151 {
152 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized);
153 }
154 }
155 catch (RestException e)
156 {
157 if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
158 {
159 Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
160 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
161 }
162 else
163 {
164 Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
165 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
166 }
167 throw (e);
168 }
169
170 Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
171
172 // We can only get here if we're authorized
173 //
174 // The requestor may have specified an LLUUID or
175 // a conjoined FirstNameLastName string. We'll
176 // try both. If we fail with the first, UUID,
177 // attempt, then we need two nodes to construct
178 // a valid avatar name.
179
180 // Do we have at least a user agent name?
181
182 if (rdata.parameters.Length < 1)
183 {
184 Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId);
185 rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest);
186 }
187
188 // The next parameter MUST be the agent identification, either an LLUUID
189 // or a space-separated First-name Last-Name specification.
190
191 try
192 {
193 rdata.uuid = new LLUUID(rdata.parameters[0]);
194 Rest.Log.DebugFormat("{0} LLUUID supplied", MsgId);
195 rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
196 }
197 catch
198 {
199 string[] names = rdata.parameters[0].Split(Rest.CA_SPACE);
200 if (names.Length == 2)
201 {
202 Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
203 rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]);
204 }
205 else
206 {
207 Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
208 rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest);
209 }
210 }
211
212 if (rdata.userProfile != null)
213 {
214 Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}",
215 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
216 }
217 else
218 {
219 Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path);
220 rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound);
221 }
222
223 // If we get to here, then we have successfully obtained an inventory
224 // for the specified user.
225
226 rdata.uuid = rdata.userProfile.ID;
227
228 if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid))
229 {
230
231 rdata.root = Rest.InventoryServices.RequestRootFolder(rdata.uuid);
232
233 Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}",
234 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
235
236 Rest.InventoryServices.RequestInventoryForUser(rdata.uuid, rdata.GetUserInventory);
237
238 Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}",
239 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
240
241 lock(rdata)
242 {
243 if (!rdata.HaveInventory)
244 {
245 Monitor.Wait(rdata);
246 }
247 }
248
249 if (rdata.root == null)
250 {
251 Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}",
252 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
253 rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError);
254 }
255
256 }
257 else
258 {
259 Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}",
260 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
261 rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound);
262 }
263
264 // If we get here, then we have successfully retrieved the user's information
265 // and inventory information is now available locally.
266
267 switch (rdata.method)
268 {
269
270 case Rest.HEAD : // Do the processing, set the status code, suppress entity
271 DoGet(rdata);
272 rdata.buffer = null;
273 break;
274
275 case Rest.GET : // Do the processing, set the status code, return entity
276 DoGet(rdata);
277 break;
278
279 case Rest.PUT : // Add new information
280 DoPut(rdata);
281 break;
282
283 case Rest.POST : // Update (replace)
284 DoPost(rdata);
285 break;
286
287 case Rest.DELETE : // Delete information
288 DoDelete(rdata);
289 break;
290
291 default :
292 Rest.Log.DebugFormat("{0} Method {1} not supported for {2}",
293 MsgId, rdata.method, rdata.path);
294 rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
295 Rest.HttpStatusDescMethodNotAllowed);
296 break;
297 }
298
299 }
300
301 #endregion Interface
302
303 #region method-specific processing
304
305 /// <summary>
306 /// This method implements GET processing for inventory.
307 /// Any remaining parameters are used to locate the
308 /// corresponding subtree based upon node name.
309 /// </summary>
310
311 private void DoGet(InventoryRequestData rdata)
312 {
313
314 rdata.initXmlWriter();
315
316 rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty);
317
318 if (rdata.parameters.Length == 1)
319 {
320 formatInventory(rdata, rdata.root, String.Empty);
321 }
322 else
323 {
324 traverseInventory(rdata, rdata.root, 1);
325 }
326
327 rdata.writer.WriteFullEndElement();
328
329 rdata.Complete();
330 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
331
332 }
333
334 /// <summary>
335 /// In the case of the inventory, and probably much else
336 /// the distinction between PUT and POST is not always
337 /// easy to discern. Adding a directory can be viewed as
338 /// an addition, or as a modification to the inventory as
339 /// a whole.
340 ///
341 /// The best distinction may be the relationship between
342 /// the entity and the URI. If we view POST as an update,
343 /// then the enity represents a replacement for the
344 /// element named by the URI. If the operation is PUT,
345 /// then the URI describes the context into which the
346 /// entity will be added.
347 ///
348 /// As an example, suppose the URI contains:
349 /// /admin/inventory/Clothing
350 /// Suppose the entity represents a Folder, called
351 /// "Clothes".
352 ///
353 /// A POST request will result in the replacement of
354 /// "Clothing" by "Clothes". Whereas a PUT request
355 /// would add Clothes as a sub-directory of Clothing.
356 ///
357 /// This is the model followed by this implementation.
358 /// </summary>
359
360 /// <summary>
361 /// PUT adds new information to the inventory at the
362 /// context identified by the URI.
363 /// </summary>
364
365 private void DoPut(InventoryRequestData rdata)
366 {
367
368 // Resolve the context node specified in the URI. Entity
369 // data will be ADDED beneath this node.
370
371 Object InventoryNode = getInventoryNode(rdata, rdata.root, 1);
372
373 // Processing depends upon the type of inventory node
374 // identified in the URI. This is the CONTEXT for the
375 // change. We either got a context or we threw an
376 // exception.
377
378 // It follows that we can only add information if the URI
379 // has identified a folder. So only folder is supported
380 // in this case.
381
382 if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
383 typeof(InventoryFolderImpl) == InventoryNode.GetType())
384 {
385
386 // Cast the context node appropriately.
387
388 InventoryFolderBase context = (InventoryFolderBase) InventoryNode;
389
390 Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}",
391 MsgId, rdata.method, rdata.path);
392
393 // Reconstitute inventory sub-tree from the XML supplied in the entity.
394 // This is a stand-alone inventory subtree, not yet integrated into the
395 // existing tree.
396
397 XmlInventoryCollection entity = ReconstituteEntity(rdata);
398
399 // Inlined assest included in entity. If anything fails,
400 // return failure to requestor.
401
402 if (entity.Assets.Count > 0)
403 {
404
405 Rest.Log.DebugFormat("{0} Adding {1} assets to server",
406 MsgId, entity.Assets.Count);
407
408 foreach (AssetBase asset in entity.Assets)
409 {
410 Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
411 MsgId, asset.ID, asset.Type, asset.Name);
412 Rest.AssetServices.AddAsset(asset);
413 if (Rest.DumpAsset)
414 {
415 Rest.Dump(asset.Data);
416 }
417 }
418
419 }
420
421 // Modify the context using the collection of folders and items
422 // returned in the XmlInventoryCollection.
423
424 foreach (InventoryFolderBase folder in entity.Folders)
425 {
426
427 InventoryFolderBase found = null;
428
429 // If the parentID is zero, then this is going
430 // into the root identified by the URI. The requestor
431 // may have already set the parent ID correctly, in which
432 // case we don't have to do it here.
433
434 if (folder.ParentID == LLUUID.Zero)
435 {
436 folder.ParentID = context.ID;
437 }
438
439 // Search the existing inventory for an existing entry. If
440 // we have once, we need to decide if it has really changed.
441 // It could just be present as (unnecessary) context, and we
442 // don't want to waste time updating the database in that
443 // case, OR, it could be being moved from another location
444 // in which case an update is most certainly necessary.
445
446 found = null;
447
448 foreach (InventoryFolderBase xf in rdata.folders)
449 {
450 // Compare identifying attribute
451 if (xf.ID == folder.ID)
452 {
453 found = xf;
454 }
455 }
456
457 if (found != null && FolderHasChanged(folder,found))
458 {
459 Rest.Log.DebugFormat("{0} Updating existing folder", MsgId);
460 Rest.InventoryServices.MoveFolder(folder);
461 }
462 else
463 {
464 Rest.Log.DebugFormat("{0} Adding new folder", MsgId);
465 Rest.InventoryServices.AddFolder(folder);
466 }
467
468 }
469
470 // Now we repeat a similar process for the items included
471 // in the entity.
472
473 foreach (InventoryItemBase item in entity.Items)
474 {
475
476 InventoryItemBase found = null;
477
478 // If the parentID is zero, then this is going
479 // directly into the root identified by the URI.
480
481 if (item.Folder == LLUUID.Zero)
482 {
483 item.Folder = context.ID;
484 }
485
486 // Determine whether this is a new item or a
487 // replacement definition.
488
489 foreach (InventoryItemBase xi in rdata.items)
490 {
491 // Compare identifying attribute
492 if (xi.ID == item.ID)
493 {
494 found = xi;
495 }
496 }
497
498 if (found != null && ItemHasChanged(item, found))
499 {
500 Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}",
501 MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
502 Rest.InventoryServices.UpdateItem(item);
503 }
504 else
505 {
506 Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}",
507 MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
508 Rest.InventoryServices.AddItem(item);
509 }
510
511 }
512
513 }
514 else
515 {
516 Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}",
517 MsgId, rdata.method, rdata.path, InventoryNode.GetType());
518 rdata.Fail(Rest.HttpStatusCodeBadRequest,
519 Rest.HttpStatusDescBadRequest);
520 }
521
522 rdata.Complete();
523 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
524
525 }
526
527 /// <summary>
528 /// POST updates the URI-identified element in the inventory. This
529 /// is actually far more flexible than it might at first sound. For
530 /// POST the URI serves two purposes:
531 /// [1] It identifies the user whose inventory is to be
532 /// processed.
533 /// [2] It optionally specifies a subtree of the inventory
534 /// that is to be used to resolve an relative subtree
535 /// specifications in the entity. If nothing is specified
536 /// then the whole inventory is implied.
537 /// Please note that the subtree specified by the URI is only relevant
538 /// to an entity containing a URI relative specification, i.e. one or
539 /// more elements do not specify parent folder information. These
540 /// elements will be implicitly referenced within the context identified
541 /// by the URI.
542 /// If an element in the entity specifies an explicit parent folder, then
543 /// that parent is effective, regardless of nay value specified in the
544 /// URI. If the parent does not exist, then the element, and any dependent
545 /// elements, are ignored. This case is actually detected and handled
546 /// during the reconstitution process.
547 /// </summary>
548
549 private void DoPost(InventoryRequestData rdata)
550 {
551
552 int count = 0;
553
554 // Resolve the inventory node that is to be modified.
555
556 Object InventoryNode = getInventoryNode(rdata, rdata.root, 1);
557
558 // As long as we have a context, then we have something
559 // meaningful to do, unlike PUT. So reconstitute the
560 // subtree before doing anything else. Note that we
561 // etiher got a context or we threw an exception.
562
563 XmlInventoryCollection entity = ReconstituteEntity(rdata);
564
565 // Incorporate any inlined assets first
566
567 if (entity.Assets.Count != 0)
568 {
569 foreach (AssetBase asset in entity.Assets)
570 {
571 // Asset was validated during the collection
572 // process
573 Rest.AssetServices.AddAsset(asset);
574 }
575 }
576
577 /// <summary>
578 /// URI specifies a folder to be updated.
579 /// </summary>
580 /// <remarks>
581 /// The root node in the entity must have the same
582 /// UUID as the node identified by the URI. The
583 /// parentID if different indicates that the updated
584 /// folder is actually being moved too.
585 /// </remarks>
586
587 if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
588 typeof(InventoryFolderImpl) == InventoryNode.GetType())
589 {
590
591 InventoryFolderBase uri = (InventoryFolderBase) InventoryNode;
592 InventoryFolderBase xml = null;
593
594 // Scan the set of folders in the entity collection for an
595 // entry that macthes the context folder. It is assumed that
596 // the only reliable indicator of this is a zero UUID ( using
597 // implicit context), or the parent's UUID matches that of the
598 // URI designated node (explicit context). We don't allow
599 // ambiguity in this case because this is POST and we are
600 // supposed to be modifying a specific node.
601 // We assign any element IDs required as an economy; we don't
602 // want to iterate over the fodler set again if it can be
603 // helped.
604
605 foreach (InventoryFolderBase folder in entity.Folders)
606 {
607 if (folder.ParentID == uri.ParentID ||
608 folder.ParentID == LLUUID.Zero)
609 {
610 folder.ParentID = uri.ParentID;
611 xml = folder;
612 count++;
613 }
614 if (xml.ID == LLUUID.Zero)
615 {
616 xml.ID = LLUUID.Random();
617 }
618 }
619
620 // More than one entry is ambiguous
621
622 if (count > 1)
623 {
624 Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous",
625 MsgId, rdata.method, rdata.path);
626 rdata.Fail(Rest.HttpStatusCodeBadRequest,
627 Rest.HttpStatusDescBadRequest);
628 }
629
630 // Exactly one entry means we ARE replacing the node
631 // identified by the URI. So we delete the old folder
632 // by moving it to the trash and then purging it.
633 // We then add all of the folders and items we
634 // included in the entity. The subtree has been
635 // modified.
636
637 if (count == 1)
638 {
639
640 InventoryFolderBase TrashCan = GetTrashCan(rdata);
641
642 uri.ParentID = TrashCan.ID;
643 Rest.InventoryServices.MoveFolder(uri);
644 Rest.InventoryServices.PurgeFolder(TrashCan);
645
646 }
647
648 // Now, regardelss of what they represent, we
649 // integrate all of the elements in the entity.
650
651 foreach (InventoryFolderBase f in entity.Folders)
652 {
653 Rest.InventoryServices.MoveFolder(f);
654 }
655
656 foreach (InventoryItemBase it in entity.Items)
657 {
658 Rest.InventoryServices.AddItem(it);
659 }
660
661 }
662
663 /// <summary>
664 /// URI specifies an item to be updated
665 /// </summary>
666 /// <remarks>
667 /// The entity must contain a single item node to be
668 /// updated. ID and Folder ID must be correct.
669 /// </remarks>
670
671 else
672 {
673
674 InventoryItemBase uri = (InventoryItemBase) InventoryNode;
675 InventoryItemBase xml = null;
676
677 if (entity.Folders.Count != 0)
678 {
679 Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>",
680 MsgId, rdata.method, rdata.path);
681 rdata.Fail(Rest.HttpStatusCodeBadRequest,
682 Rest.HttpStatusDescBadRequest);
683 }
684
685 if (entity.Items.Count > 1)
686 {
687 Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>",
688 MsgId, rdata.method, rdata.path);
689 rdata.Fail(Rest.HttpStatusCodeBadRequest,
690 Rest.HttpStatusDescBadRequest);
691 }
692
693 xml = entity.Items[0];
694
695 if (xml.ID == LLUUID.Zero)
696 {
697 xml.ID = LLUUID.Random();
698 }
699
700 // If the folder reference has changed, then this item is
701 // being moved. Otherwise we'll just delete the old, and
702 // add in the new.
703
704 // Delete the old item
705
706 Rest.InventoryServices.DeleteItem(uri);
707
708 // Add the new item to the inventory
709
710 Rest.InventoryServices.AddItem(xml);
711
712 }
713
714 rdata.Complete();
715 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
716
717 }
718
719 /// <summary>
720 /// Arguably the most damaging REST interface. It deletes the inventory
721 /// item or folder identified by the URI.
722 ///
723 /// We only process if the URI identified node appears to exist
724 /// We do not test for success because we either get a context,
725 /// or an exception is thrown.
726 ///
727 /// Folders are deleted by moving them to another folder and then
728 /// purging that folder. We'll do that by creating a temporary
729 /// sub-folder in the TrashCan and purging that folder's
730 /// contents. If we can't can it, we don't delete it...
731 /// So, if no trashcan is available, the request does nothing.
732 /// Items are summarily deleted.
733 ///
734 /// In the interests of safety, a delete request should normally
735 /// be performed using UUID, as a name might identify several
736 /// elements.
737 /// </summary>
738
739 private void DoDelete(InventoryRequestData rdata)
740 {
741
742 Object InventoryNode = getInventoryNode(rdata, rdata.root, 1);
743
744 if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
745 typeof(InventoryFolderImpl) == InventoryNode.GetType())
746 {
747
748 InventoryFolderBase TrashCan = GetTrashCan(rdata);
749
750 InventoryFolderBase folder = (InventoryFolderBase) InventoryNode;
751 Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted",
752 MsgId, rdata.method, rdata.path);
753 folder.ParentID = TrashCan.ID;
754 Rest.InventoryServices.MoveFolder(folder);
755 Rest.InventoryServices.PurgeFolder(TrashCan);
756
757 }
758
759 // Deleting items is much more straight forward.
760
761 else
762 {
763 InventoryItemBase item = (InventoryItemBase) InventoryNode;
764 Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted",
765 MsgId, rdata.method, rdata.path);
766 Rest.InventoryServices.DeleteItem(item);
767 }
768
769 rdata.Complete();
770 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
771
772 }
773
774#endregion method-specific processing
775
776 /// <summary>
777 /// This method is called to obtain the OpenSim inventory object identified
778 /// by the supplied URI. This may be either an Item or a Folder, so a suitably
779 /// ambiguous return type is employed (Object). This method recurses as
780 /// necessary to process the designated hierarchy.
781 ///
782 /// If we reach the end of the URI then we return the contextural folder to
783 /// our caller.
784 ///
785 /// If we are not yet at the end of the URI we attempt to find a child folder
786 /// and if we succeed we recurse.
787 ///
788 /// If this is the last node, then we look to see if this is an item. If it is,
789 /// we return that item.
790 ///
791 /// Otherwise we fail the request on the ground of an invalid URI.
792 ///
793 /// <note>
794 /// This mechanism cannot detect the case where duplicate subtrees satisfy a
795 /// request. In such a case the 1st element gets processed. If this is a
796 /// problem, then UUID should be used to identify the end-node. This is basic
797 /// premise of normal inventory processing. The name is an informational, and
798 /// not a defining, attribute.
799 /// </note>
800 ///
801 /// </summary>
802
803 private Object getInventoryNode(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
804 {
805
806 Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
807
808 // We have just run off the end of the parameter sequence
809
810 if (pi >= rdata.parameters.Length)
811 {
812 return folder;
813 }
814
815 // More names in the sequence, look for a folder that might
816 // get us there.
817
818 if (rdata.folders != null)
819 foreach (InventoryFolderBase f in rdata.folders)
820 {
821 // Look for the present node in the directory list
822 if (f.ParentID == folder.ID &&
823 (f.Name == rdata.parameters[pi] ||
824 f.ID.ToString() == rdata.parameters[pi]))
825 {
826 return getInventoryNode(rdata, f, pi+1);
827 }
828 }
829
830 // No folders that match. Perhaps this parameter identifies an item? If
831 // it does, then it MUST also be the last name in the sequence.
832
833 if (pi == rdata.parameters.Length-1)
834 {
835 if (rdata.items != null)
836 {
837 int k = 0;
838 InventoryItemBase li = null;
839 foreach (InventoryItemBase i in rdata.items)
840 {
841 if (i.Folder == folder.ID &&
842 (i.Name == rdata.parameters[pi] ||
843 i.ID.ToString() == rdata.parameters[pi]))
844 {
845 li = i;
846 k++;
847 }
848 }
849 if (k == 1)
850 {
851 return li;
852 }
853 else
854 {
855 Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
856 MsgId, rdata.method, rdata.path);
857 rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound);
858 }
859 }
860 }
861
862 // No, so abandon the request
863
864 Rest.Log.DebugFormat("{0} {1}: Resource {2} not found",
865 MsgId, rdata.method, rdata.path);
866 rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound);
867
868 return null; /* Never reached */
869
870 }
871
872 /// <summary>
873 /// This routine traverse the inventory's structure until the end-point identified
874 /// in the URI is reached, the remainder of the inventory (if any) is then formatted
875 /// and returned to the requestor.
876 ///
877 /// Note that this method is only interested in those folder that match elements of
878 /// the URI supplied by the requestor, so once a match is fund, the processing does
879 /// not need to consider any further elements.
880 ///
881 /// Only the last element in the URI should identify an item.
882 /// </summary>
883
884 private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
885 {
886
887 Rest.Log.DebugFormat("{0} Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
888
889 if (rdata.folders != null)
890 {
891 foreach (InventoryFolderBase f in rdata.folders)
892 {
893 if (f.ParentID == folder.ID &&
894 (f.Name == rdata.parameters[pi] ||
895 f.ID.ToString() == rdata.parameters[pi]))
896 {
897 if (pi < rdata.parameters.Length-1)
898 {
899 traverseInventory(rdata, f, pi+1);
900 }
901 else
902 {
903 formatInventory(rdata, f, String.Empty);
904 }
905 return;
906 }
907 }
908 }
909
910 if (pi == rdata.parameters.Length-1)
911 {
912 if (rdata.items != null)
913 {
914 foreach (InventoryItemBase i in rdata.items)
915 {
916 if (i.Folder == folder.ID &&
917 (i.Name == rdata.parameters[pi] ||
918 i.ID.ToString() == rdata.parameters[pi]))
919 {
920 // Fetching an Item has a special significance. In this
921 // case we also want to fetch the associated asset.
922 // To make it interesting, we'll d this via redirection.
923 string asseturl = "http://" + rdata.hostname + ":" + rdata.port +
924 "/admin/assets" + Rest.UrlPathSeparator + i.AssetID.ToString();
925 rdata.Redirect(asseturl,Rest.PERMANENT);
926 Rest.Log.DebugFormat("{0} Never Reached");
927 }
928 }
929 }
930 }
931
932 Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>",
933 MsgId, rdata.path);
934 rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound);
935
936 }
937
938 /// <summary>
939 /// This method generates XML that describes an instance of InventoryFolderBase.
940 /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem
941 /// to generate XML for any items encountered along the way.
942 /// The indentation parameter is solely for the benefit of trace record
943 /// formatting.
944 /// </summary>
945
946 private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent)
947 {
948
949 if (Rest.DEBUG)
950 {
951 Rest.Log.DebugFormat("{0} Folder : {1} {2} {3}", MsgId, folder.ID, indent, folder.Name);
952 indent += "\t";
953 }
954
955 // Start folder item
956
957 rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty);
958 rdata.writer.WriteAttributeString("name",String.Empty,folder.Name);
959 rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString());
960 rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString());
961 rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString());
962 rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString());
963
964 if (rdata.folders != null)
965 {
966 foreach (InventoryFolderBase f in rdata.folders)
967 {
968 if (f.ParentID == folder.ID)
969 {
970 formatInventory(rdata, f, indent);
971 }
972 }
973 }
974
975 if (rdata.items != null)
976 {
977 foreach (InventoryItemBase i in rdata.items)
978 {
979 if (i.Folder == folder.ID)
980 {
981 formatItem(rdata, i, indent);
982 }
983 }
984 }
985
986 // End folder item
987
988 rdata.writer.WriteEndElement();
989
990 }
991
992 /// <summary>
993 /// This method generates XML that describes an instance of InventoryItemBase.
994 /// </summary>
995
996 private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent)
997 {
998
999 Rest.Log.DebugFormat("{0} Item : {1} {2} {3}", MsgId, i.ID, indent, i.Name);
1000
1001 rdata.writer.WriteStartElement(String.Empty,"Item",String.Empty);
1002
1003 rdata.writer.WriteAttributeString("name",String.Empty,i.Name);
1004 rdata.writer.WriteAttributeString("desc",String.Empty,i.Description);
1005 rdata.writer.WriteAttributeString("uuid",String.Empty,i.ID.ToString());
1006 rdata.writer.WriteAttributeString("owner",String.Empty,i.Owner.ToString());
1007 rdata.writer.WriteAttributeString("creator",String.Empty,i.Creator.ToString());
1008 rdata.writer.WriteAttributeString("creationdate",String.Empty,i.CreationDate.ToString());
1009 rdata.writer.WriteAttributeString("type",String.Empty,i.InvType.ToString());
1010 rdata.writer.WriteAttributeString("assettype",String.Empty,i.AssetType.ToString());
1011 rdata.writer.WriteAttributeString("groupowned",String.Empty,i.GroupOwned.ToString());
1012 rdata.writer.WriteAttributeString("groupid",String.Empty,i.GroupID.ToString());
1013 rdata.writer.WriteAttributeString("saletype",String.Empty,i.SaleType.ToString());
1014 rdata.writer.WriteAttributeString("saleprice",String.Empty,i.SalePrice.ToString());
1015 rdata.writer.WriteAttributeString("flags",String.Empty,i.Flags.ToString("X"));
1016
1017 rdata.writer.WriteStartElement(String.Empty,"Permissions",String.Empty);
1018 rdata.writer.WriteAttributeString("current",String.Empty,i.CurrentPermissions.ToString("X"));
1019 rdata.writer.WriteAttributeString("next",String.Empty,i.NextPermissions.ToString("X"));
1020 rdata.writer.WriteAttributeString("everyone",String.Empty,i.EveryOnePermissions.ToString("X"));
1021 rdata.writer.WriteAttributeString("base",String.Empty,i.BasePermissions.ToString("X"));
1022 rdata.writer.WriteEndElement();
1023
1024 rdata.writer.WriteElementString("Asset",i.AssetID.ToString());
1025
1026 rdata.writer.WriteEndElement();
1027
1028 }
1029
1030 /// <summary>
1031 /// This method creates a "trashcan" folder to support folder and item
1032 /// deletions by this interface. The xisting trash folder is found and
1033 /// this folder is created within it. It is called "tmp" to indicate to
1034 /// the client that it is OK to delete this folder. The REST interface
1035 /// will recreate the folder on an as-required basis.
1036 /// If the trash can cannot be created, then by implication the request
1037 /// that required it cannot be completed, and it fails accordingly.
1038 /// </summary>
1039
1040 private InventoryFolderBase GetTrashCan(InventoryRequestData rdata)
1041 {
1042
1043 InventoryFolderBase TrashCan = null;
1044
1045 foreach (InventoryFolderBase f in rdata.folders)
1046 {
1047 if (f.Name == "Trash")
1048 {
1049 foreach (InventoryFolderBase t in rdata.folders)
1050 {
1051 if (t.Name == "tmp")
1052 {
1053 TrashCan = t;
1054 }
1055 }
1056 if (TrashCan == null)
1057 {
1058 TrashCan = new InventoryFolderBase();
1059 TrashCan.Name = "tmp";
1060 TrashCan.ID = LLUUID.Random();
1061 TrashCan.Version = 1;
1062 TrashCan.Type = (short) AssetType.TrashFolder;
1063 TrashCan.ParentID = f.ID;
1064 Rest.InventoryServices.AddFolder(TrashCan);
1065 }
1066 }
1067 }
1068
1069 if (TrashCan == null)
1070 {
1071 Rest.Log.DebugFormat("{0} No Trash Can available", MsgId);
1072 rdata.Fail(Rest.HttpStatusCodeServerError,
1073 Rest.HttpStatusDescServerError);
1074 }
1075
1076 return TrashCan;
1077
1078 }
1079
1080 /// <summary>
1081 /// Make sure that an unchanged folder is not unnecessarily
1082 /// processed.
1083 /// </summary>
1084
1085 private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf)
1086 {
1087 return ( newf.Name != oldf.Name
1088 || newf.ParentID != oldf.ParentID
1089 || newf.Owner != oldf.Owner
1090 || newf.Type != oldf.Type
1091 || newf.Version != oldf.Version
1092 );
1093 }
1094
1095 /// <summary>
1096 /// Make sure that an unchanged item is not unnecessarily
1097 /// processed.
1098 /// </summary>
1099
1100 private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf)
1101 {
1102 return ( newf.Name != oldf.Name
1103 || newf.Folder != oldf.Description
1104 || newf.Description != oldf.Description
1105 || newf.Owner != oldf.Owner
1106 || newf.Creator != oldf.Creator
1107 || newf.AssetID != oldf.AssetID
1108 || newf.GroupID != oldf.GroupID
1109 || newf.GroupOwned != oldf.GroupOwned
1110 || newf.InvType != oldf.InvType
1111 || newf.AssetType != oldf.AssetType
1112 );
1113 }
1114
1115 /// <summary>
1116 /// This method is called by PUT and POST to create an XmlInventoryCollection
1117 /// instance that reflects the content of the entity supplied on the request.
1118 /// Any elements in the completed collection whose UUID is zero, are
1119 /// considered to be located relative to the end-point identified int he
1120 /// URI. In this way, an entire sub-tree can be conveyed in a single REST
1121 /// PUT or POST request.
1122 ///
1123 /// A new instance of XmlInventoryCollection is created and, if the request
1124 /// has an entity, it is more completely initialized. thus, if no entity was
1125 /// provided the collection is valid, but empty.
1126 ///
1127 /// The entity is then scanned and each tag is processed to produce the
1128 /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection
1129 /// will reflect the subtree described by the entity.
1130 ///
1131 /// This is a very flexible mechanism, the entity may contain arbitrary,
1132 /// discontiguous tree fragments, or may contain single element. The caller is
1133 /// responsible for integrating this collection (and ensuring that any
1134 /// missing parent IDs are resolved).
1135 /// </summary>
1136
1137 internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata)
1138 {
1139
1140 Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId);
1141
1142 XmlInventoryCollection ic = new XmlInventoryCollection();
1143
1144 if (rdata.request.HasEntityBody)
1145 {
1146
1147 Rest.Log.DebugFormat("{0} Entity present", MsgId);
1148
1149 ic.init(rdata);
1150
1151 try
1152 {
1153 while (ic.xml.Read())
1154 {
1155 switch (ic.xml.NodeType)
1156 {
1157 case XmlNodeType.Element :
1158 Rest.Log.DebugFormat("{0} StartElement: <{1}>",
1159 MsgId, ic.xml.Name);
1160 switch (ic.xml.Name)
1161 {
1162 case "Folder" :
1163 Rest.Log.DebugFormat("{0} Processing {1} element",
1164 MsgId, ic.xml.Name);
1165 CollectFolder(ic);
1166 break;
1167 case "Item" :
1168 Rest.Log.DebugFormat("{0} Processing {1} element",
1169 MsgId, ic.xml.Name);
1170 CollectItem(ic);
1171 break;
1172 case "Asset" :
1173 Rest.Log.DebugFormat("{0} Processing {1} element",
1174 MsgId, ic.xml.Name);
1175 CollectAsset(ic);
1176 break;
1177 case "Permissions" :
1178 Rest.Log.DebugFormat("{0} Processing {1} element",
1179 MsgId, ic.xml.Name);
1180 CollectPermissions(ic);
1181 break;
1182 default :
1183 Rest.Log.DebugFormat("{0} Ignoring {1} element",
1184 MsgId, ic.xml.Name);
1185 break;
1186 }
1187 // This stinks, but the ReadElement call above not only reads
1188 // the imbedded data, but also consumes the end tag for Asset
1189 // and moves the element pointer on to the containing Item's
1190 // element-end, however, if there was a permissions element
1191 // following, it would get us to the start of that..
1192 if (ic.xml.NodeType == XmlNodeType.EndElement &&
1193 ic.xml.Name == "Item")
1194 {
1195 Validate(ic);
1196 }
1197 break;
1198 case XmlNodeType.EndElement :
1199 switch (ic.xml.Name)
1200 {
1201 case "Folder" :
1202 Rest.Log.DebugFormat("{0} Completing {1} element",
1203 MsgId, ic.xml.Name);
1204 ic.Pop();
1205 break;
1206 case "Item" :
1207 Rest.Log.DebugFormat("{0} Completing {1} element",
1208 MsgId, ic.xml.Name);
1209 Validate(ic);
1210 break;
1211 case "Asset" :
1212 Rest.Log.DebugFormat("{0} Completing {1} element",
1213 MsgId, ic.xml.Name);
1214 break;
1215 case "Permissions" :
1216 Rest.Log.DebugFormat("{0} Completing {1} element",
1217 MsgId, ic.xml.Name);
1218 break;
1219 default :
1220 Rest.Log.DebugFormat("{0} Ignoring {1} element",
1221 MsgId, ic.xml.Name);
1222 break;
1223 }
1224 break;
1225 default :
1226 Rest.Log.DebugFormat("{0} [0] Ignoring: <{1}>:<2>",
1227 MsgId, ic.xml.NodeType, ic.xml.Value);
1228 break;
1229 }
1230 }
1231 }
1232 catch (XmlException e)
1233 {
1234 Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message);
1235 throw e;
1236 }
1237 catch (Exception e)
1238 {
1239 Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message);
1240 throw e;
1241 }
1242
1243 }
1244 else
1245 {
1246 Rest.Log.DebugFormat("{0} Entity absent", MsgId);
1247 }
1248
1249 if (Rest.DEBUG)
1250 {
1251 Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId);
1252 Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count);
1253 Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count);
1254 Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count);
1255 }
1256
1257 return ic;
1258
1259 }
1260
1261 /// <summary>
1262 /// This method creates an inventory Folder from the
1263 /// information supplied in the request's entity.
1264 /// A folder instance is created and initialized to reflect
1265 /// default values. These values are then overridden
1266 /// by information supplied in the entity.
1267 /// If context was not explicitly provided, then the
1268 /// appropriate ID values are determined.
1269 /// </summary>
1270
1271 private void CollectFolder(XmlInventoryCollection ic)
1272 {
1273
1274 Rest.Log.DebugFormat("{0} Interpret folder element", MsgId);
1275
1276 InventoryFolderBase result = new InventoryFolderBase();
1277
1278 // Default values
1279
1280 result.Name = String.Empty;
1281 result.ID = LLUUID.Zero;
1282 result.Owner = ic.UserID;
1283 result.ParentID = LLUUID.Zero; // Context
1284 result.Type = (short) AssetType.Folder;
1285 result.Version = 1;
1286
1287 if (ic.xml.HasAttributes)
1288 {
1289 for (int i = 0; i < ic.xml.AttributeCount; i++)
1290 {
1291 ic.xml.MoveToAttribute(i);
1292 switch (ic.xml.Name)
1293 {
1294 case "name" :
1295 result.Name = ic.xml.Value;
1296 break;
1297 case "uuid" :
1298 result.ID = new LLUUID(ic.xml.Value);
1299 break;
1300 case "parent" :
1301 result.ParentID = new LLUUID(ic.xml.Value);
1302 break;
1303 case "owner" :
1304 result.Owner = new LLUUID(ic.xml.Value);
1305 break;
1306 case "type" :
1307 result.Type = Int16.Parse(ic.xml.Value);
1308 break;
1309 case "version" :
1310 result.Version = UInt16.Parse(ic.xml.Value);
1311 break;
1312 default :
1313 Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}",
1314 MsgId, ic.xml.Name, ic.xml.Value);
1315 ic.Fail(Rest.HttpStatusCodeBadRequest,
1316 Rest.HttpStatusDescBadRequest);
1317 break;
1318 }
1319 }
1320 }
1321
1322 ic.xml.MoveToElement();
1323
1324 // The client is relying upon the reconstitution process
1325 // to determine the parent's UUID based upon context. This
1326 // is necessary where a new folder may have been
1327 // introduced.
1328
1329 if (result.ParentID == LLUUID.Zero)
1330 {
1331 result.ParentID = ic.Parent();
1332 }
1333 else
1334 {
1335
1336 bool found = false;
1337
1338 foreach (InventoryFolderBase parent in ic.rdata.folders)
1339 {
1340 if ( parent.ID == result.ParentID )
1341 {
1342 found = true;
1343 break;
1344 }
1345 }
1346
1347 if (!found)
1348 {
1349 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}",
1350 MsgId, ic.Item.Folder, result.ID);
1351 ic.Fail(Rest.HttpStatusCodeBadRequest,
1352 Rest.HttpStatusDescBadRequest);
1353 }
1354
1355 }
1356
1357 // This is a new folder, so no existing UUID is available
1358 // or appropriate
1359
1360 if (result.ID == LLUUID.Zero)
1361 {
1362 result.ID = LLUUID.Random();
1363 }
1364
1365 // Treat this as a new context. Any other information is
1366 // obsolete as a consequence.
1367
1368 ic.Push(result);
1369
1370 }
1371
1372 /// <summary>
1373 /// This method is called to handle the construction of an Item
1374 /// instance from the supplied request entity. It is called
1375 /// whenever an Item start tag is detected.
1376 /// An instance of an Item is created and initialized to default
1377 /// values. These values are then overridden from values supplied
1378 /// as attributes to the Item element.
1379 /// This item is then stored in the XmlInventoryCollection and
1380 /// will be verified by Validate.
1381 /// All context is reset whenever the effective folder changes
1382 /// or an item is successfully validated.
1383 /// </summary>
1384
1385 private void CollectItem(XmlInventoryCollection ic)
1386 {
1387
1388 Rest.Log.DebugFormat("{0} Interpret item element", MsgId);
1389
1390 InventoryItemBase result = new InventoryItemBase();
1391
1392 result.Name = String.Empty;
1393 result.Description = String.Empty;
1394 result.ID = LLUUID.Zero;
1395 result.Folder = LLUUID.Zero;
1396 result.Owner = ic.UserID;
1397 result.Creator = ic.UserID;
1398 result.AssetID = LLUUID.Zero;
1399 result.GroupID = LLUUID.Zero;
1400 result.GroupOwned = false;
1401 result.InvType = (int) InventoryType.Unknown;
1402 result.AssetType = (int) AssetType.Unknown;
1403
1404 if (ic.xml.HasAttributes)
1405 {
1406 for (int i = 0; i < ic.xml.AttributeCount; i++)
1407 {
1408
1409 ic.xml.MoveToAttribute(i);
1410
1411 switch (ic.xml.Name)
1412 {
1413 case "name" :
1414 result.Name = ic.xml.Value;
1415 break;
1416 case "desc" :
1417 result.Description = ic.xml.Value;
1418 break;
1419 case "uuid" :
1420 result.ID = new LLUUID(ic.xml.Value);
1421 break;
1422 case "folder" :
1423 result.Folder = new LLUUID(ic.xml.Value);
1424 break;
1425 case "owner" :
1426 result.Owner = new LLUUID(ic.xml.Value);
1427 break;
1428 case "invtype" :
1429 result.InvType = Int32.Parse(ic.xml.Value);
1430 break;
1431 case "creator" :
1432 result.Creator = new LLUUID(ic.xml.Value);
1433 break;
1434 case "assettype" :
1435 result.AssetType = Int32.Parse(ic.xml.Value);
1436 break;
1437 case "groupowned" :
1438 result.GroupOwned = Boolean.Parse(ic.xml.Value);
1439 break;
1440 case "groupid" :
1441 result.GroupID = new LLUUID(ic.xml.Value);
1442 break;
1443 case "flags" :
1444 result.Flags = UInt32.Parse(ic.xml.Value);
1445 break;
1446 case "creationdate" :
1447 result.CreationDate = Int32.Parse(ic.xml.Value);
1448 break;
1449 case "saletype" :
1450 result.SaleType = Byte.Parse(ic.xml.Value);
1451 break;
1452 case "saleprice" :
1453 result.SalePrice = Int32.Parse(ic.xml.Value);
1454 break;
1455
1456 default :
1457 Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}",
1458 MsgId, ic.xml.Name, ic.xml.Value);
1459 ic.Fail(Rest.HttpStatusCodeBadRequest,
1460 Rest.HttpStatusDescBadRequest);
1461 break;
1462 }
1463 }
1464 }
1465
1466 ic.xml.MoveToElement();
1467
1468 ic.Push(result);
1469
1470 }
1471
1472 /// <summary>
1473 /// This method assembles an asset instance from the
1474 /// information supplied in the request's entity. It is
1475 /// called as a result of detecting a start tag for a
1476 /// type of Asset.
1477 /// The information is collected locally, and an asset
1478 /// instance is created only if the basic XML parsing
1479 /// completes successfully.
1480 /// Default values for all parts of the asset are
1481 /// established before overriding them from the supplied
1482 /// XML.
1483 /// If an asset has inline=true as an attribute, then
1484 /// the element contains the data representing the
1485 /// asset. This is saved as the data component.
1486 /// inline=false means that the element's payload is
1487 /// simply the UUID of the asset referenced by the
1488 /// item being constructed.
1489 /// An asset, if created is stored in the
1490 /// XmlInventoryCollection
1491 /// </summary>
1492
1493 private void CollectAsset(XmlInventoryCollection ic)
1494 {
1495
1496 Rest.Log.DebugFormat("{0} Interpret asset element", MsgId);
1497
1498 AssetBase asset = null;
1499
1500 string name = String.Empty;
1501 string desc = String.Empty;
1502 sbyte type = (sbyte) AssetType.Unknown;
1503 sbyte itype = (sbyte) AssetType.Unknown;
1504 bool temp = false;
1505 bool local = false;
1506
1507 // This is not a persistent attribute
1508 bool inline = true;
1509
1510 LLUUID uuid = LLUUID.Zero;
1511
1512 // Attribute is optional
1513 if (ic.xml.HasAttributes)
1514 {
1515 for (int i = 0; i < ic.xml.AttributeCount; i++)
1516 {
1517 ic.xml.MoveToAttribute(i);
1518 switch (ic.xml.Name)
1519 {
1520
1521 case "name" :
1522 name = ic.xml.Value;
1523 break;
1524
1525 case "type" :
1526 type = SByte.Parse(ic.xml.Value);
1527 break;
1528
1529 case "description" :
1530 desc = ic.xml.Value;
1531 break;
1532
1533 case "temporary" :
1534 temp = Boolean.Parse(ic.xml.Value);
1535 break;
1536
1537 case "invtype" :
1538 itype = SByte.Parse(ic.xml.Value);
1539 break;
1540
1541 case "uuid" :
1542 uuid = new LLUUID(ic.xml.Value);
1543 break;
1544
1545 case "inline" :
1546 inline = Boolean.Parse(ic.xml.Value);
1547 break;
1548
1549 case "local" :
1550 local = Boolean.Parse(ic.xml.Value);
1551 break;
1552
1553 default :
1554 Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}",
1555 MsgId, ic.xml.Name, ic.xml.Value);
1556 ic.Fail(Rest.HttpStatusCodeBadRequest,
1557 Rest.HttpStatusDescBadRequest);
1558 break;
1559 }
1560 }
1561 }
1562
1563 ic.xml.MoveToElement();
1564
1565 // If this is a reference to an existing asset, just store the
1566 // asset ID into the item.
1567
1568 if (!inline)
1569 {
1570 if (ic.Item != null)
1571 {
1572 ic.Item.AssetID = new LLUUID(ic.xml.ReadElementContentAsString());
1573 Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID);
1574 }
1575 else
1576 {
1577 Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId);
1578 ic.Fail(Rest.HttpStatusCodeBadRequest,
1579 Rest.HttpStatusDescBadRequest);
1580 }
1581 }
1582
1583 // Otherwise, generate an asset ID, store that into the item, and
1584 // create an entry in the asset list for the inlined asset. But
1585 // only if the size is non-zero.
1586
1587 else
1588 {
1589
1590 string b64string = null;
1591
1592 // Generate a UUID of none were given, and generally none should
1593 // be. Ever.
1594
1595 if (uuid == LLUUID.Zero)
1596 {
1597 uuid = LLUUID.Random();
1598 }
1599
1600 // Create AssetBase entity to hold the inlined asset
1601
1602 asset = new AssetBase(uuid, name);
1603
1604 asset.Description = desc;
1605 asset.Type = type; // type == 0 == texture
1606 asset.InvType = itype;
1607 asset.Local = local;
1608 asset.Temporary = temp;
1609
1610 b64string = ic.xml.ReadElementContentAsString();
1611
1612 Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length);
1613 Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId,
1614 b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length));
1615
1616 asset.Data = Convert.FromBase64String(b64string);
1617
1618 // Ensure the asset always has some kind of data component
1619
1620 if (asset.Data == null)
1621 {
1622 asset.Data = new byte[1];
1623 }
1624
1625 // If this is in the context of an item, establish
1626 // a link with the item in context.
1627
1628 if (ic.Item != null && ic.Item.AssetID == LLUUID.Zero)
1629 {
1630 ic.Item.AssetID = uuid;
1631 }
1632
1633 }
1634
1635 ic.Push(asset);
1636
1637 }
1638
1639 /// <summary>
1640 /// Store any permissions information provided by the request.
1641 /// This overrides the default permissions set when the
1642 /// XmlInventoryCollection object was created.
1643 /// </summary>
1644
1645 private void CollectPermissions(XmlInventoryCollection ic)
1646 {
1647
1648 if (ic.xml.HasAttributes)
1649 {
1650 for (int i = 0; i < ic.xml.AttributeCount; i++)
1651 {
1652 ic.xml.MoveToAttribute(i);
1653 switch (ic.xml.Name)
1654 {
1655 case "current" :
1656 ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
1657 break;
1658 case "next" :
1659 ic.NextPermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
1660 break;
1661 case "everyone" :
1662 ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
1663 break;
1664 case "base" :
1665 ic.BasePermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
1666 break;
1667 default :
1668 Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}",
1669 MsgId,ic.xml.Name, ic.xml.Value);
1670 ic.Fail(Rest.HttpStatusCodeBadRequest,
1671 Rest.HttpStatusDescBadRequest);
1672 break;
1673 }
1674 }
1675 }
1676
1677 ic.xml.MoveToElement();
1678
1679 }
1680
1681 /// <summary>
1682 /// This method is called whenever an Item has been successfully
1683 /// reconstituted from the request's entity.
1684 /// It uses the information curren tin the XmlInventoryCollection
1685 /// to complete the item's specification, including any implied
1686 /// context and asset associations.
1687 /// It fails the request if any necessary item or asset information
1688 /// is missing.
1689 /// </summary>
1690
1691 private void Validate(XmlInventoryCollection ic)
1692 {
1693
1694 // There really should be an item present if we've
1695 // called validate. So fail if there is not.
1696
1697 if (ic.Item == null)
1698 {
1699 Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId);
1700 ic.Fail(Rest.HttpStatusCodeBadRequest,
1701 Rest.HttpStatusDescBadRequest);
1702 }
1703
1704 // Every item is required to have a name (via REST anyway)
1705
1706 if (ic.Item.Name == String.Empty)
1707 {
1708 Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId);
1709 ic.Fail(Rest.HttpStatusCodeBadRequest,
1710 Rest.HttpStatusDescBadRequest);
1711 }
1712
1713 // An item MUST have an asset ID. AssetID should never be zero
1714 // here. It should always get set from the information stored
1715 // when the Asset element was processed.
1716
1717 if (ic.Item.AssetID == LLUUID.Zero)
1718 {
1719
1720 Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId);
1721 Rest.Log.InfoFormat("{0} Asset information is missing", MsgId);
1722 ic.Fail(Rest.HttpStatusCodeBadRequest,
1723 Rest.HttpStatusDescBadRequest);
1724
1725 }
1726
1727 // If the item is new, then assign it an ID
1728
1729 if (ic.Item.ID == LLUUID.Zero)
1730 {
1731 ic.Item.ID = LLUUID.Random();
1732 }
1733
1734 // If the context is being implied, obtain the current
1735 // folder item's ID. If it was specified explicitly, make
1736 // sure that theparent folder exists.
1737
1738 if (ic.Item.Folder == LLUUID.Zero)
1739 {
1740 ic.Item.Folder = ic.Parent();
1741 }
1742 else
1743 {
1744
1745 bool found = false;
1746
1747 foreach (InventoryFolderBase parent in ic.rdata.folders)
1748 {
1749 if ( parent.ID == ic.Item.Folder )
1750 {
1751 found = true;
1752 break;
1753 }
1754 }
1755
1756 if (!found)
1757 {
1758 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}",
1759 MsgId, ic.Item.Folder, ic.Item.ID);
1760 ic.Fail(Rest.HttpStatusCodeBadRequest,
1761 Rest.HttpStatusDescBadRequest);
1762 }
1763
1764 }
1765
1766 // If this is an inline asset being constructed in the context
1767 // of a new Item, then use the itm's name here too.
1768
1769 if (ic.Asset != null)
1770 {
1771 if (ic.Asset.Name == String.Empty)
1772 ic.Asset.Name = ic.Item.Name;
1773 if (ic.Asset.Description == String.Empty)
1774 ic.Asset.Description = ic.Item.Description;
1775 }
1776
1777 // Assign permissions
1778
1779 ic.Item.CurrentPermissions = ic.CurrentPermissions;
1780 ic.Item.EveryOnePermissions = ic.EveryOnePermissions;
1781 ic.Item.BasePermissions = ic.BasePermissions;
1782 ic.Item.NextPermissions = ic.NextPermissions;
1783
1784 // If no type was specified for this item, we can attempt to
1785 // infer something from the file type maybe. This is NOT as
1786 // good as having type be specified in the XML.
1787
1788 if (ic.Item.AssetType == (int) AssetType.Unknown ||
1789 ic.Item.InvType == (int) AssetType.Unknown)
1790 {
1791
1792 Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId);
1793
1794 string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD);
1795
1796 if (Rest.DEBUG)
1797 {
1798 for (int i = 0; i < parts.Length; i++)
1799 {
1800 Rest.Log.DebugFormat("{0} Name part {1} : {2}",
1801 MsgId, i, parts[i]);
1802 }
1803 }
1804
1805 // If the associated item name is multi-part, then maybe
1806 // the last part will indicate the item type - if we're
1807 // lucky.
1808
1809 if (parts.Length > 1)
1810 {
1811 Rest.Log.DebugFormat("{0} File type is {1}",
1812 MsgId, parts[parts.Length - 1]);
1813 switch (parts[parts.Length - 1])
1814 {
1815 case "jpeg2000" :
1816 case "jpeg-2000" :
1817 case "jpg2000" :
1818 case "jpg-2000" :
1819 Rest.Log.DebugFormat("{0} Type {1} inferred",
1820 MsgId, parts[parts.Length-1]);
1821 if (ic.Item.AssetType == (int) AssetType.Unknown)
1822 ic.Item.AssetType = (int) AssetType.ImageJPEG;
1823 if (ic.Item.InvType == (int) AssetType.Unknown)
1824 ic.Item.InvType = (int) AssetType.ImageJPEG;
1825 break;
1826 case "jpg" :
1827 case "jpeg" :
1828 Rest.Log.DebugFormat("{0} Type {1} inferred",
1829 MsgId, parts[parts.Length - 1]);
1830 if (ic.Item.AssetType == (int) AssetType.Unknown)
1831 ic.Item.AssetType = (int) AssetType.ImageJPEG;
1832 if (ic.Item.InvType == (int) AssetType.Unknown)
1833 ic.Item.InvType = (int) AssetType.ImageJPEG;
1834 break;
1835 default :
1836 Rest.Log.DebugFormat("{0} Type was not inferred", MsgId);
1837 break;
1838 }
1839 }
1840 }
1841
1842 ic.reset();
1843
1844 }
1845
1846 #region Inventory RequestData extension
1847
1848 internal class InventoryRequestData : RequestData
1849 {
1850
1851 /// <summary>
1852 /// These are the inventory specific request/response state
1853 /// extensions.
1854 /// </summary>
1855
1856 internal bool HaveInventory = false;
1857 internal ICollection<InventoryFolderImpl> folders = null;
1858 internal ICollection<InventoryItemBase> items = null;
1859 internal UserProfileData userProfile = null;
1860 internal InventoryFolderBase root = null;
1861
1862 internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
1863 : base(request, response, prefix)
1864 {
1865 }
1866
1867 /// <summary>
1868 /// This is the callback method required by inventory services. The
1869 /// requestor issues an inventory request and then blocks until this
1870 /// method signals the monitor.
1871 /// </summary>
1872
1873 internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items)
1874 {
1875 Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId);
1876 this.folders = folders;
1877 this.items = items;
1878 this.HaveInventory = true;
1879 lock(this)
1880 {
1881 Monitor.Pulse(this);
1882 }
1883 }
1884
1885 }
1886
1887 #endregion Inventory RequestData extension
1888
1889 /// <summary>
1890 /// This class is used to record and manage the hierarchy
1891 /// constructed from the entity supplied in the request for
1892 /// PUT and POST.
1893 /// </summary>
1894
1895 internal class XmlInventoryCollection : InventoryCollection
1896 {
1897
1898 internal InventoryRequestData rdata;
1899 private Stack<InventoryFolderBase> stk;
1900
1901 internal List<AssetBase> Assets;
1902
1903 internal InventoryItemBase Item;
1904 internal AssetBase Asset;
1905 internal XmlReader xml;
1906
1907 internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF;
1908 internal /*static*/ const uint DefaultNext = 0x82000;
1909 internal /*static*/ const uint DefaultBase = 0x7FFFFFFF;
1910 internal /*static*/ const uint DefaultEveryOne = 0x0;
1911
1912 internal uint CurrentPermissions = 0x00;
1913 internal uint NextPermissions = 0x00;
1914 internal uint BasePermissions = 0x00;
1915 internal uint EveryOnePermissions = 0x00;
1916
1917 internal XmlInventoryCollection()
1918 {
1919 Folders = new List<InventoryFolderBase>();
1920 Items = new List<InventoryItemBase>();
1921 Assets = new List<AssetBase>();
1922 }
1923
1924 internal void init(InventoryRequestData p_rdata)
1925 {
1926 rdata = p_rdata;
1927 UserID = rdata.uuid;
1928 stk = new Stack<InventoryFolderBase>();
1929 rdata.initXmlReader();
1930 xml = rdata.reader;
1931 initPermissions();
1932 }
1933
1934 internal void initPermissions()
1935 {
1936 CurrentPermissions = DefaultCurrent;
1937 NextPermissions = DefaultNext;
1938 BasePermissions = DefaultBase;
1939 EveryOnePermissions = DefaultEveryOne;
1940 }
1941
1942 internal LLUUID Parent()
1943 {
1944 if (stk.Count != 0)
1945 {
1946 return stk.Peek().ID;
1947 }
1948 else
1949 {
1950 return LLUUID.Zero;
1951 }
1952 }
1953
1954 internal void Push(InventoryFolderBase folder)
1955 {
1956 stk.Push(folder);
1957 Folders.Add(folder);
1958 reset();
1959 }
1960
1961 internal void Push(InventoryItemBase item)
1962 {
1963 Item = item;
1964 Items.Add(item);
1965 }
1966
1967 internal void Push(AssetBase asset)
1968 {
1969 Asset = asset;
1970 Assets.Add(asset);
1971 }
1972
1973 internal void Pop()
1974 {
1975 stk.Pop();
1976 reset();
1977 }
1978
1979 internal void reset()
1980 {
1981 Item = null;
1982 Asset = null;
1983 initPermissions();
1984 }
1985
1986 internal void Fail(int code, string desc)
1987 {
1988 rdata.Fail(code, desc);
1989 }
1990
1991 }
1992 }
1993}
diff --git a/prebuild.xml b/prebuild.xml
index fdc9eee..29c8a23 100644
--- a/prebuild.xml
+++ b/prebuild.xml
@@ -1198,6 +1198,44 @@
1198 <Match pattern="*.cs" recurse="true"/> 1198 <Match pattern="*.cs" recurse="true"/>
1199 </Files> 1199 </Files>
1200 </Project> 1200 </Project>
1201
1202 <Project name="OpenSim.ApplicationPlugins.Rest.Inventory"
1203 path="OpenSim/ApplicationPlugins/Rest/Inventory" type="Library">
1204 <Configuration name="Debug">
1205 <Options>
1206 <OutputPath>../../../../bin/</OutputPath>
1207 </Options>
1208 </Configuration>
1209 <Configuration name="Release">
1210 <Options>
1211 <OutputPath>../../../../bin/</OutputPath>
1212 </Options>
1213 </Configuration>
1214
1215 <ReferencePath>../../../../bin/</ReferencePath>
1216 <Reference name="Mono.Addins.dll" />
1217 <Reference name="System"/>
1218 <Reference name="System.IO"/>
1219 <Reference name="System.Xml"/>
1220 <Reference name="System.Xml.Serialization"/>
1221 <Reference name="libsecondlife.dll" />
1222 <Reference name="Nini.dll" />
1223 <Reference name="XMLRPC.dll" />
1224 <Reference name="OpenSim"/>
1225 <Reference name="OpenSim.Region.ClientStack"/>
1226 <Reference name="OpenSim.Region.Environment"/>
1227 <Reference name="OpenSim.Framework.Communications"/>
1228 <Reference name="OpenSim.Framework"/>
1229 <Reference name="OpenSim.Framework.Servers"/>
1230 <Reference name="OpenSim.Framework.Console"/>
1231 <Reference name="OpenSim.ApplicationPlugins.Rest"/>
1232 <Reference name="log4net"/>
1233
1234 <Files>
1235 <Match pattern="*.cs" recurse="true"/>
1236 </Files>
1237 </Project>
1238
1201 <!-- /REST plugins --> 1239 <!-- /REST plugins -->
1202 1240
1203 <!-- Scene Server API Example Apps --> 1241 <!-- Scene Server API Example Apps -->