aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/Rest/Inventory
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs1465
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml11
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs551
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs860
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs383
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs448
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs662
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs2343
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs246
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs46
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs204
-rw-r--r--OpenSim/Framework/Console/ConsoleDisplayUtil.cs (renamed from OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs)27
-rw-r--r--OpenSim/Server/Handlers/AgentPreferences/AgentPreferencesServiceConnector.cs (renamed from OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs)47
13 files changed, 42 insertions, 7251 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
deleted file mode 100644
index 10f1a6e..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
+++ /dev/null
@@ -1,1465 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Security.Cryptography;
32using System.Text;
33using System.Text.RegularExpressions;
34using System.Xml;
35using OpenSim.Framework;
36using OpenSim.Framework.Servers;
37using OpenSim.Framework.Servers.HttpServer;
38using OpenSim.Services.Interfaces;
39
40using OpenMetaverse;
41
42namespace OpenSim.ApplicationPlugins.Rest.Inventory
43{
44
45 /// <summary>
46 /// This class represents the current REST request. It
47 /// encapsulates the request/response state and takes care
48 /// of response generation without exposing the REST handler
49 /// to the actual mechanisms involved.
50 ///
51 /// This structure is created on entry to the Handler
52 /// method and is disposed of upon return. It is part of
53 /// the plug-in infrastructure, rather than the functionally
54 /// specific REST handler, and fundamental changes to
55 /// this should be reflected in the Rest HandlerVersion. The
56 /// object is instantiated, and may be extended by, any
57 /// given handler. See the inventory handler for an example
58 /// of this.
59 ///
60 /// If possible, the underlying request/response state is not
61 /// changed until the handler explicitly issues a Respond call.
62 /// This ensures that the request/response pair can be safely
63 /// processed by subsequent, unrelated, handlers even id the
64 /// agent handler had completed much of its processing. Think
65 /// of it as a transactional req/resp capability.
66 /// </summary>
67
68 public class RequestData
69 {
70
71 // HTTP Server interface data (Received values)
72
73 internal OSHttpRequest request = null;
74 internal OSHttpResponse response = null;
75 internal string qprefix = null;
76
77 // Request lifetime values
78 // buffer is global because it is referenced by the handler
79 // in supported of streamed requests.
80 // If a service provider wants to construct the message
81 // body explicitly it can use body to do this. The value
82 // in body is used if the buffer is still null when a response
83 // is generated.
84 // Storing information in body will suppress the return of
85 // statusBody which is only intended to report status on
86 // requests which do not themselves ordinarily generate
87 // an informational response. All of this is handled in
88 // Respond().
89
90 internal byte[] buffer = null;
91 internal string body = null;
92 internal string bodyType = "text/html";
93
94 // The encoding in effect is set to a server default. It may
95 // subsequently be overridden by a Content header. This
96 // value is established during construction and is used
97 // wherever encoding services are needed.
98
99 internal Encoding encoding = Rest.Encoding;
100
101 // These values are derived from the supplied URL. They
102 // are initialized during construction.
103
104 internal string path = null;
105 internal string method = null;
106 internal Uri uri = null;
107 internal string query = null;
108 internal string hostname = "localhost";
109 internal int port = 80;
110
111 // The path part of the URI is decomposed. pathNodes
112 // is an array of every element in the URI. Parameters
113 // is an array that contains only those nodes that
114 // are not a part of the authority prefix
115
116 private string[] pathNodes = null;
117 private string[] parameters = null;
118 private static readonly string[] EmptyPath = { String.Empty };
119
120 // The status code gets set during the course of processing
121 // and is the HTTP completion code. The status body is
122 // initialized during construction, is appended to during the
123 // course of execution, and is finalized during Respond
124 // processing.
125 //
126 // Fail processing marks the request as failed and this is
127 // then used to inhibit processing during Response processing.
128
129 internal int statusCode = 0;
130 internal string statusBody = String.Empty;
131 internal bool fail = false;
132
133 // This carries the URL to which the client should be redirected.
134 // It is set by the service provider using the Redirect call.
135
136 internal string redirectLocation = null;
137
138 // These values influence response processing. They can be set by
139 // service providers according to need. The defaults are generally
140 // good.
141
142 internal bool keepAlive = false;
143 internal bool chunked = false;
144
145 // XML related state
146
147 internal XmlWriter writer = null;
148 internal XmlReader reader = null;
149
150 // Internal working state
151
152 private StringBuilder sbuilder = new StringBuilder(1024);
153 private MemoryStream xmldata = null;
154
155 // This is used to make the response mechanism idempotent.
156
157 internal bool handled = false;
158
159 // Authentication related state
160 //
161 // Two supported authentication mechanisms are:
162 // scheme = Rest.AS_BASIC;
163 // scheme = Rest.AS_DIGEST;
164 // Presented in that order (as required by spec)
165 // A service provider can set the scheme variable to
166 // force selection of a particular authentication model
167 // (choosing from amongst those supported of course)
168 //
169
170 internal bool authenticated = false;
171 internal string scheme = Rest.Scheme;
172 internal string realm = Rest.Realm;
173 internal string domain = null;
174 internal string nonce = null;
175 internal string cnonce = null;
176 internal string qop = Rest.Qop_Auth;
177 internal string opaque = null;
178 internal string stale = null;
179 internal string algorithm = Rest.Digest_MD5;
180 internal string authParms = null;
181 internal string authPrefix = null;
182 internal string userName = String.Empty;
183 internal string userPass = String.Empty;
184
185 // Session related tables. These are only needed if QOP is set to "auth-sess"
186 // and for now at least, it is not. Session related authentication is of
187 // questionable merit in the context of REST anyway, but it is, arguably, more
188 // secure.
189
190 private static Dictionary<string,string> cntable = new Dictionary<string,string>();
191 private static Dictionary<string,string> sktable = new Dictionary<string,string>();
192
193 // This dictionary is used to keep track fo all of the parameters discovered
194 // when the authorisation header is anaylsed.
195
196 private Dictionary<string,string> authparms = new Dictionary<string,string>();
197
198 // These regular expressions are used to decipher the various header entries.
199
200 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
201 RegexOptions.Compiled | RegexOptions.IgnoreCase);
202
203 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
204 RegexOptions.Compiled | RegexOptions.IgnoreCase);
205
206 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"",
207 RegexOptions.Compiled | RegexOptions.IgnoreCase);
208
209 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
210 RegexOptions.Compiled | RegexOptions.IgnoreCase);
211
212 private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)",
213 RegexOptions.Compiled | RegexOptions.IgnoreCase);
214
215 // For efficiency, we create static instances of these objects
216
217 private static MD5 md5hash = MD5.Create();
218
219 private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
220
221#region properties
222
223 // Just for convenience...
224
225 internal string MsgId
226 {
227 get { return Rest.MsgId; }
228 }
229
230 /// <summary>
231 /// Return a boolean indication of whether or no an authenticated user is
232 /// associated with this request. This could be wholly integrated, but
233 /// that would make authentication mandatory.
234 /// </summary>
235
236 internal bool IsAuthenticated
237 {
238 get
239 {
240 if (Rest.Authenticate)
241 {
242 if (!authenticated)
243 {
244 authenticate();
245 }
246
247 return authenticated;
248 }
249 else return true;
250 }
251 }
252
253 /// <summary>
254 /// Access to all 'nodes' in the supplied URI as an
255 /// array of strings.
256 /// </summary>
257
258 internal string[] PathNodes
259 {
260 get
261 {
262 return pathNodes;
263 }
264 }
265
266 /// <summary>
267 /// Access to all non-prefix 'nodes' in the supplied URI as an
268 /// array of strings. These identify a specific resource that
269 /// is managed by the authority (the prefix).
270 /// </summary>
271
272 internal string[] Parameters
273 {
274 get
275 {
276 return parameters;
277 }
278 }
279
280#endregion properties
281
282#region constructors
283
284 // Constructor
285
286 internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
287 {
288
289 request = p_request;
290 response = p_response;
291 qprefix = p_qprefix;
292
293 sbuilder.Length = 0;
294
295 encoding = request.ContentEncoding;
296
297 if (encoding == null)
298 {
299 encoding = Rest.Encoding;
300 }
301
302 method = request.HttpMethod.ToLower();
303 initUrl();
304
305 initParameters(p_qprefix.Length);
306
307 }
308
309#endregion constructors
310
311#region authentication_common
312
313 /// <summary>
314 /// The REST handler has requested authentication. Authentication
315 /// is considered to be with respect to the current values for
316 /// Realm, domain, etc.
317 ///
318 /// This method checks to see if the current request is already
319 /// authenticated for this domain. If it is, then it returns
320 /// true. If it is not, then it issues a challenge to the client
321 /// and responds negatively to the request.
322 ///
323 /// As soon as authentication failure is detected the method calls
324 /// DoChallenge() which terminates the request with REST exception
325 /// for unauthroized access.
326 /// </summary>
327
328 private void authenticate()
329 {
330
331 string authdata = request.Headers.Get("Authorization");
332 string reqscheme = String.Empty;
333
334 // If we don't have an authorization header, then this
335 // user is certainly not authorized. This is the typical
336 // pivot for the 1st request by a client.
337
338 if (authdata == null)
339 {
340 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
341 DoChallenge();
342 }
343
344 // So, we have authentication data, now we have to check to
345 // see what we got and whether or not it is valid for the
346 // current domain. To do this we need to interpret the data
347 // provided in the Authorization header. First we need to
348 // identify the scheme being used and route accordingly.
349
350 MatchCollection matches = schema.Matches(authdata);
351
352 foreach (Match m in matches)
353 {
354 Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value);
355 reqscheme = m.Groups["scheme"].Value.ToLower();
356 }
357
358 // If we want a specific authentication mechanism, make sure
359 // we get it. null indicates we don't care. non-null indicates
360 // a specific scheme requirement.
361
362 if (scheme != null && scheme.ToLower() != reqscheme)
363 {
364 Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId);
365 DoChallenge();
366 }
367
368 // In the future, these could be made into plug-ins...
369 // But for now at least we have no reason to use anything other
370 // then MD5. TLS/SSL are taken care of elsewhere.
371
372 switch (reqscheme)
373 {
374 case "digest" :
375 Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
376 DoDigest(authdata);
377 break;
378
379 case "basic" :
380 Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
381 DoBasic(authdata);
382 break;
383 }
384
385 // If the current header is invalid, then a challenge is still needed.
386
387 if (!authenticated)
388 {
389 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
390 DoChallenge();
391 }
392
393 }
394
395 /// <summary>
396 /// Construct the necessary WWW-Authenticate headers and fail the request
397 /// with a NOT AUTHORIZED response. The parameters are the union of values
398 /// required by the supported schemes.
399 /// </summary>
400
401 private void DoChallenge()
402 {
403 Flush();
404 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
405 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
406 Fail(Rest.HttpStatusCodeNotAuthorized);
407 }
408
409 /// <summary>
410 /// The Flush() call is here to support a problem encountered with the
411 /// client where an authentication rejection was lost because the rejection
412 /// may flow before the clienthas finished sending us the inbound data stream,
413 /// in which case the client responds to the socket error on out put, and
414 /// never sees the authentication challenge. The client should be fixed,
415 /// because this solution leaves the server prone to DOS attacks. A message
416 /// will be issued whenever flushing occurs. It can be enabled/disabled from
417 /// the configuration file.
418 /// </summary>
419
420 private void Flush()
421 {
422 if (Rest.FlushEnabled)
423 {
424 byte[] dbuffer = new byte[8192];
425 Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId);
426 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
427 }
428 return;
429 }
430
431 // Indicate that authentication is required
432
433 private void Challenge(string scheme, string realm, string domain, string nonce,
434 string opaque, string stale, string alg,
435 string qop, string auth)
436 {
437
438 sbuilder.Length = 0;
439
440 // The service provider can force a particular scheme by
441 // assigning a value to scheme.
442
443 // Basic authentication is pretty simple.
444 // Just specify the realm in question.
445
446 if (scheme == null || scheme == Rest.AS_BASIC)
447 {
448
449 sbuilder.Append(Rest.AS_BASIC);
450
451 if (realm != null)
452 {
453 sbuilder.Append(" realm=");
454 sbuilder.Append(Rest.CS_DQUOTE);
455 sbuilder.Append(realm);
456 sbuilder.Append(Rest.CS_DQUOTE);
457 }
458 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
459 }
460
461 sbuilder.Length = 0;
462
463 // Digest authentication takes somewhat more
464 // to express.
465
466 if (scheme == null || scheme == Rest.AS_DIGEST)
467 {
468
469 sbuilder.Append(Rest.AS_DIGEST);
470 sbuilder.Append(" ");
471
472 // Specify the effective realm. This should
473 // never be null if we are uthenticating, as it is required for all
474 // authentication schemes. It defines, in conjunction with the
475 // absolute URI information, the domain to which the authentication
476 // applies. It is an arbitrary string. I *believe* this allows an
477 // authentication to apply to disjoint resources within the same
478 // server.
479
480 if (realm != null)
481 {
482 sbuilder.Append("realm=");
483 sbuilder.Append(Rest.CS_DQUOTE);
484 sbuilder.Append(realm);
485 sbuilder.Append(Rest.CS_DQUOTE);
486 sbuilder.Append(Rest.CS_COMMA);
487 }
488
489 // Share our nonce. This is *uniquely* generated each time a 401 is
490 // returned. We do not generate a very sophisticated nonce at the
491 // moment (it's simply a base64 encoded UUID).
492
493 if (nonce != null)
494 {
495 sbuilder.Append("nonce=");
496 sbuilder.Append(Rest.CS_DQUOTE);
497 sbuilder.Append(nonce);
498 sbuilder.Append(Rest.CS_DQUOTE);
499 sbuilder.Append(Rest.CS_COMMA);
500 }
501
502 // The opaque string should be returned by the client unchanged in all
503 // subsequent requests.
504
505 if (opaque != null)
506 {
507 sbuilder.Append("opaque=");
508 sbuilder.Append(Rest.CS_DQUOTE);
509 sbuilder.Append(opaque);
510 sbuilder.Append(Rest.CS_DQUOTE);
511 sbuilder.Append(Rest.CS_COMMA);
512 }
513
514 // This flag indicates that the authentication was rejected because the
515 // included nonce was stale. The server might use timestamp information
516 // in the nonce to determine this. We do not.
517
518 if (stale != null)
519 {
520 sbuilder.Append("stale=");
521 sbuilder.Append(Rest.CS_DQUOTE);
522 sbuilder.Append(stale);
523 sbuilder.Append(Rest.CS_DQUOTE);
524 sbuilder.Append(Rest.CS_COMMA);
525 }
526
527 // Identifies the algorithm used to produce the digest and checksum.
528 // The default is MD5.
529
530 if (alg != null)
531 {
532 sbuilder.Append("algorithm=");
533 sbuilder.Append(alg);
534 sbuilder.Append(Rest.CS_COMMA);
535 }
536
537 // Theoretically QOP is optional, but it is required by a compliant
538 // with current versions of the scheme. In fact IE requires that QOP
539 // be specified and will refuse to authenticate otherwise.
540
541 if (qop != String.Empty)
542 {
543 sbuilder.Append("qop=");
544 sbuilder.Append(Rest.CS_DQUOTE);
545 sbuilder.Append(qop);
546 sbuilder.Append(Rest.CS_DQUOTE);
547 sbuilder.Append(Rest.CS_COMMA);
548 }
549
550 // This parameter allows for arbitrary extensions to the protocol.
551 // Unrecognized values should be simply ignored.
552
553 if (auth != null)
554 {
555 sbuilder.Append(auth);
556 sbuilder.Append(Rest.CS_COMMA);
557 }
558
559 // We don't know the userid that will be used
560 // so we cannot make any authentication domain
561 // assumptions. So the prefix will determine
562 // this.
563
564 sbuilder.Append("domain=");
565 sbuilder.Append(Rest.CS_DQUOTE);
566 sbuilder.Append(qprefix);
567 sbuilder.Append(Rest.CS_DQUOTE);
568
569 // Generate the authenticate header and we're basically
570 // done.
571
572 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
573
574 }
575
576 }
577
578#endregion authentication_common
579
580#region authentication_basic
581
582 /// <summary>
583 /// Interpret a BASIC authorization claim. Some clients can only
584 /// understand this and also expect it to be the first one
585 /// offered. So we do.
586 /// OpenSim also needs this, as it is the only scheme that allows
587 /// authentication using the hashed passwords stored in the
588 /// user database.
589 /// </summary>
590
591 private void DoBasic(string authdata)
592 {
593
594 string response = null;
595
596 MatchCollection matches = basicParms.Matches(authdata);
597
598 // In the case of basic authentication there is
599 // only expected to be a single argument.
600
601 foreach (Match m in matches)
602 {
603 authparms.Add("response",m.Groups["pval"].Value);
604 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
605 MsgId, "response", m.Groups["pval"].Value);
606 }
607
608 // Did we get a valid response?
609
610 if (authparms.TryGetValue("response", out response))
611 {
612 // Decode
613 response = Rest.Base64ToString(response);
614 Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response);
615
616 // Extract user & password
617 Match m = reuserPass.Match(response);
618 userName = m.Groups["user"].Value;
619 userPass = m.Groups["pass"].Value;
620
621 // Validate against user database
622 authenticated = Validate(userName,userPass);
623 }
624
625 }
626
627 /// <summary>
628 /// This method provides validation in support of the BASIC
629 /// authentication method. This is not normaly expected to be
630 /// used, but is included for completeness (and because I tried
631 /// it first).
632 /// </summary>
633
634 private bool Validate(string user, string pass)
635 {
636
637 Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
638
639 // Both values are required
640
641 if (user == null || pass == null)
642 return false;
643
644 // Eliminate any leading or trailing spaces
645 user = user.Trim();
646
647 return vetPassword(user, pass);
648
649 }
650
651 /// <summary>
652 /// This is used by the BASIC authentication scheme to calculate
653 /// the double hash used by OpenSim to encode user's passwords.
654 /// It returns true, if the supplied password is actually correct.
655 /// If the specified user-id is not recognized, but the password
656 /// matches the God password, then this is accepted as an admin
657 /// session.
658 /// </summary>
659
660 private bool vetPassword(string user, string pass)
661 {
662
663 int x;
664 string first;
665 string last;
666
667 // Distinguish the parts, if necessary
668
669 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
670 {
671 first = user.Substring(0,x);
672 last = user.Substring(x+1);
673 }
674 else
675 {
676 first = user;
677 last = String.Empty;
678 }
679
680 UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last);
681
682 // If we don't recognize the user id, perhaps it is god?
683 if (account == null)
684 return pass == Rest.GodKey;
685
686 return (Rest.AuthServices.Authenticate(account.PrincipalID, pass, 1) != string.Empty);
687
688 }
689
690#endregion authentication_basic
691
692#region authentication_digest
693
694 /// <summary>
695 /// This is an RFC2617 compliant HTTP MD5 Digest authentication
696 /// implementation. It has been tested with Firefox, Java HTTP client,
697 /// and Microsoft's Internet Explorer V7.
698 /// </summary>
699
700 private void DoDigest(string authdata)
701 {
702
703 string response = null;
704
705 // Find all of the values of the for x = "y"
706
707 MatchCollection matches = digestParm1.Matches(authdata);
708
709 foreach (Match m in matches)
710 {
711 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
712 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
713 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
714 }
715
716 // Find all of the values of the for x = y
717
718 matches = digestParm2.Matches(authdata);
719
720 foreach (Match m in matches)
721 {
722 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
723 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
724 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
725 }
726
727 // A response string MUST be returned, otherwise we are
728 // NOT authenticated.
729
730 Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId);
731
732 if (authparms.TryGetValue("response", out response))
733 {
734
735 string temp = null;
736
737 do
738 {
739
740 string nck = null;
741 string ncl = null;
742
743 // The userid is sent in clear text. Needed for the
744 // verification.
745
746 authparms.TryGetValue("username", out userName);
747
748 // All URI's of which this is a prefix are
749 // optimistically considered to be authenticated by the
750 // client. This is also needed to verify the response.
751
752 authparms.TryGetValue("uri", out authPrefix);
753
754 // There MUST be a nonce string present. We're not preserving any server
755 // side state and we can't validate the MD5 unless the client returns it
756 // to us, as it should.
757
758 if (!authparms.TryGetValue("nonce", out nonce) || nonce == null)
759 {
760 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
761 break;
762 }
763
764 // If there is an opaque string present, it had better
765 // match what we sent.
766
767 if (authparms.TryGetValue("opaque", out temp))
768 {
769 if (temp != opaque)
770 {
771 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
772 break;
773 }
774 }
775
776 // If an algorithm string is present, it had better
777 // match what we sent.
778
779 if (authparms.TryGetValue("algorithm", out temp))
780 {
781 if (temp != algorithm)
782 {
783 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
784 break;
785 }
786 }
787
788 // Quality of protection considerations...
789
790 if (authparms.TryGetValue("qop", out temp))
791 {
792
793 qop = temp.ToLower(); // replace with actual value used
794
795 // if QOP was specified then
796 // these MUST be present.
797
798 if (!authparms.ContainsKey("cnonce"))
799 {
800 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
801 Fail(Rest.HttpStatusCodeBadRequest);
802 break;
803 }
804
805 cnonce = authparms["cnonce"];
806
807 if (!authparms.TryGetValue("nc", out nck) || nck == null)
808 {
809 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
810 Fail(Rest.HttpStatusCodeBadRequest);
811 break;
812 }
813
814 Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId);
815
816 if (cntable.TryGetValue(nonce, out ncl))
817 {
818 Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl);
819
820 if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
821 {
822 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
823 Fail(Rest.HttpStatusCodeBadRequest);
824 break;
825 }
826 cntable[nonce] = nck;
827 }
828 else
829 {
830 lock (cntable) cntable.Add(nonce, nck);
831 }
832
833 }
834 else
835 {
836
837 qop = String.Empty;
838
839 // if QOP was not specified then
840 // these MUST NOT be present.
841 if (authparms.ContainsKey("cnonce"))
842 {
843 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
844 Fail(Rest.HttpStatusCodeBadRequest);
845 break;
846 }
847 if (authparms.ContainsKey("nc"))
848 {
849 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
850 Fail(Rest.HttpStatusCodeBadRequest);
851 break;
852 }
853 }
854
855 // Validate the supplied userid/password info
856
857 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
858
859 }
860 while (false);
861
862 }
863 else
864 Fail(Rest.HttpStatusCodeBadRequest);
865
866 }
867
868 /// <summary>
869 /// This mechanism is used by the digest authentication mechanism
870 /// to return the user's password. In fact, because the OpenSim
871 /// user's passwords are already hashed, and the HTTP mechanism
872 /// does not supply an open password, the hashed passwords cannot
873 /// be used unless the client has used the same salting mechanism
874 /// to has the password before using it in the authentication
875 /// algorithn. This is not inconceivable...
876 /// </summary>
877
878 private string getPassword(string user)
879 {
880
881 int x;
882 string first;
883 string last;
884
885 // Distinguish the parts, if necessary
886
887 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
888 {
889 first = user.Substring(0,x);
890 last = user.Substring(x+1);
891 }
892 else
893 {
894 first = user;
895 last = String.Empty;
896 }
897
898 UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last);
899 // If we don;t recognize the user id, perhaps it is god?
900
901 if (account == null)
902 {
903 Rest.Log.DebugFormat("{0} Administrator", MsgId);
904 return Rest.GodKey;
905 }
906 else
907 {
908 Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user);
909
910 // !!! REFACTORING PROBLEM
911 // This is what it was. It doesn't work in 0.7
912 // Nothing retrieves the password from the authentication service, there's only authentication.
913 //return udata.PasswordHash;
914 return string.Empty;
915 }
916
917 }
918
919 // Validate the request-digest
920
921 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
922 {
923
924 string patt = null;
925 string payl = String.Empty;
926 string KDS = null;
927 string HA1 = null;
928 string HA2 = null;
929 string pass = getPassword(user);
930
931 // Generate H(A1)
932
933 if (algorithm == Rest.Digest_MD5Sess)
934 {
935 if (!sktable.ContainsKey(cnonce))
936 {
937 patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce);
938 HA1 = HashToString(patt);
939 sktable.Add(cnonce, HA1);
940 }
941 else
942 {
943 HA1 = sktable[cnonce];
944 }
945 }
946 else
947 {
948 patt = String.Format("{0}:{1}:{2}", user, realm, pass);
949 HA1 = HashToString(patt);
950 }
951
952 // Generate H(A2)
953
954 if (qop == "auth-int")
955 {
956 patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl));
957 }
958 else
959 {
960 patt = String.Format("{0}:{1}", request.HttpMethod, uri);
961 }
962
963 HA2 = HashToString(patt);
964
965 // Generate Digest
966
967 if (qop != String.Empty)
968 {
969 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
970 }
971 else
972 {
973 patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2);
974 }
975
976 KDS = HashToString(patt);
977
978 // Compare the generated sequence with the original
979
980 return (0 == sc.Compare(KDS, response));
981
982 }
983
984 private string HashToString(string pattern)
985 {
986
987 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
988
989 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
990
991 sbuilder.Length = 0;
992
993 for (int i = 0; i < hash.Length; i++)
994 {
995 sbuilder.Append(hash[i].ToString("x2"));
996 }
997
998 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
999
1000 return sbuilder.ToString();
1001
1002 }
1003
1004#endregion authentication_digest
1005
1006#region service_interface
1007
1008 /// <summary>
1009 /// Conditionally set a normal completion code. This allows a normal
1010 /// execution path to default.
1011 /// </summary>
1012
1013 internal void Complete()
1014 {
1015 if (statusCode == 0)
1016 {
1017 statusCode = Rest.HttpStatusCodeOK;
1018 }
1019 }
1020
1021 /// <summary>
1022 /// Indicate a functionally-dependent conclusion to the
1023 /// request. See Rest.cs for a list of possible values.
1024 /// </summary>
1025
1026 internal void Complete(int code)
1027 {
1028 statusCode = code;
1029 }
1030
1031 /// <summary>
1032 /// Indicate that a request should be redirected, using
1033 /// the HTTP completion codes. Permanent and temporary
1034 /// redirections may be indicated. The supplied URL is
1035 /// the new location of the resource.
1036 /// </summary>
1037
1038 internal void Redirect(string Url, bool temp)
1039 {
1040
1041 redirectLocation = Url;
1042
1043 if (temp)
1044 {
1045 statusCode = Rest.HttpStatusCodeTemporaryRedirect;
1046 }
1047 else
1048 {
1049 statusCode = Rest.HttpStatusCodePermanentRedirect;
1050 }
1051
1052 Fail(statusCode, String.Empty, true);
1053
1054 }
1055
1056 /// <summary>
1057 /// Fail for an arbitrary reason. Just a failure with
1058 /// headers. The supplied message will be returned in the
1059 /// message body.
1060 /// </summary>
1061
1062 internal void Fail(int code)
1063 {
1064 Fail(code, String.Empty, false);
1065 }
1066
1067 /// <summary>
1068 /// For the more adventurous. This failure also includes a
1069 /// specified entity to be appended to the code-related
1070 /// status string.
1071 /// </summary>
1072
1073 internal void Fail(int code, string addendum)
1074 {
1075 Fail(code, addendum, false);
1076 }
1077
1078 internal void Fail(int code, string addendum, bool reset)
1079 {
1080
1081 statusCode = code;
1082 appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code]));
1083
1084 // Add any final addendum to the status information
1085
1086 if (addendum != String.Empty)
1087 {
1088 appendStatus(String.Format(addendum));
1089 }
1090
1091 // Help us understand why the request is being rejected
1092
1093 if (Rest.DEBUG)
1094 {
1095 Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
1096 Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
1097 Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
1098 Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
1099 Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce);
1100 Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce);
1101 Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque);
1102 Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale);
1103 Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm);
1104 Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop);
1105 Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix);
1106 Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName);
1107 Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass);
1108 }
1109
1110 fail = true;
1111
1112 // Respond to the client's request, tag the response (for the
1113 // benefit of trace) to indicate the reason.
1114
1115 Respond(String.Format("Failure response: ({0}) : {1} ",
1116 code, Rest.HttpStatusDesc[code]));
1117
1118 // Finally initialize and the throw a RestException. All of the
1119 // handler's infrastructure knows that this is a "normal"
1120 // completion from a code point-of-view.
1121
1122 RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">");
1123
1124 re.statusCode = code;
1125 re.statusDesc = Rest.HttpStatusDesc[code];
1126 re.httpmethod = method;
1127 re.httppath = path;
1128
1129 throw re;
1130
1131 }
1132
1133 // Reject this request
1134
1135 internal void Reject()
1136 {
1137 Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)");
1138 }
1139
1140 // This MUST be called by an agent handler before it returns
1141 // control to Handle, otherwise the request will be ignored.
1142 // This is called implciitly for the REST stream handlers and
1143 // is harmless if it is called twice.
1144
1145 internal virtual bool Respond(string reason)
1146 {
1147
1148
1149 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
1150
1151 // We do this to try and make multiple Respond requests harmless,
1152 // as it is sometimes convenient to isse a response without
1153 // certain knowledge that it has not previously been done.
1154
1155 if (!handled)
1156 {
1157
1158 Rest.Log.DebugFormat("{0} Generating Response", MsgId);
1159 Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);
1160
1161 // A Head request can NOT have a body! So don't waste time on
1162 // formatting if we're going to reject it anyway!
1163
1164 if (method != Rest.HEAD)
1165 {
1166
1167 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
1168
1169 // If the writer is non-null then we know that an XML
1170 // data component exists. Flush and close the writer and
1171 // then convert the result to the expected buffer format
1172 // unless the request has already been failed for some
1173 // reason.
1174
1175 if (writer != null)
1176 {
1177 Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
1178 Rest.Log.DebugFormat("{0} XML Response exists", MsgId);
1179 writer.Flush();
1180 writer.Close();
1181 if (!fail)
1182 {
1183 buffer = xmldata.ToArray();
1184 AddHeader("Content-Type","application/xml");
1185 }
1186 xmldata.Close();
1187 Rest.Log.DebugFormat("{0} XML Response encoded", MsgId);
1188 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
1189 }
1190
1191 if (buffer == null && body != null)
1192 {
1193 buffer = encoding.GetBytes(body);
1194 AddHeader("Content-Type",bodyType);
1195 }
1196
1197 // OK, if the buffer contains something, regardless of how
1198 // it got there, set various response headers accordingly.
1199
1200 if (buffer != null)
1201 {
1202 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
1203 }
1204 else
1205 {
1206 if (statusBody != String.Empty)
1207 {
1208 statusBody += Rest.statusTail;
1209 buffer = encoding.GetBytes(statusBody);
1210 AddHeader("Content-Type","text/html");
1211 }
1212 else
1213 {
1214 statusBody = Rest.statusHead;
1215 appendStatus(String.Format(": ({0}) {1}",
1216 statusCode, Rest.HttpStatusDesc[statusCode]));
1217 statusBody += Rest.statusTail;
1218 buffer = encoding.GetBytes(statusBody);
1219 AddHeader("Content-Type","text/html");
1220 }
1221 }
1222
1223 response.ContentLength64 = buffer.Length;
1224
1225 if (response.ContentEncoding == null)
1226 response.ContentEncoding = encoding;
1227
1228 response.SendChunked = chunked;
1229 response.KeepAlive = keepAlive;
1230
1231 }
1232
1233 // Set the status code & description. If nothing has been stored,
1234 // we consider that a success.
1235
1236 if (statusCode == 0)
1237 {
1238 Complete();
1239 }
1240
1241 // Set the response code in the actual carrier
1242
1243 response.StatusCode = statusCode;
1244
1245 // For a redirect we need to set the relocation header accordingly
1246
1247 if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect ||
1248 response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect)
1249 {
1250 Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation);
1251 response.RedirectLocation = redirectLocation;
1252 }
1253
1254 // And include the status description if provided.
1255
1256 response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode];
1257
1258 // Finally we send back our response.
1259
1260 // We've left the setting of handled' until the
1261 // last minute because the header settings included
1262 // above are pretty harmless. But everything from
1263 // here on down probably leaves the response
1264 // element unusable by anyone else.
1265
1266 handled = true;
1267
1268 // DumpHeaders();
1269
1270 // if (request.InputStream != null)
1271 // {
1272 // Rest.Log.DebugFormat("{0} Closing input stream", MsgId);
1273 // request.InputStream.Close();
1274 // }
1275
1276 if (buffer != null && buffer.Length != 0)
1277 {
1278 Rest.Log.DebugFormat("{0} Entity buffer, length = {1}", MsgId, buffer.Length);
1279 // Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
1280 // MsgId, buffer.Length, encoding.GetString(buffer));
1281 response.OutputStream.Write(buffer, 0, buffer.Length);
1282 }
1283
1284 // Closing the outputstream should complete the transmission process
1285
1286 Rest.Log.DebugFormat("{0} Sending response", MsgId);
1287 // response.OutputStream.Close();
1288 response.Send();
1289
1290 }
1291
1292 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
1293
1294 return handled;
1295
1296 }
1297
1298 /// <summary>
1299 /// These methods allow a service provider to manipulate the
1300 /// request/response headers. The DumpHeaders method is intended
1301 /// for problem diagnosis.
1302 /// </summary>
1303
1304 internal void AddHeader(string hdr, string data)
1305 {
1306 if (Rest.DEBUG) Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", MsgId, hdr, data);
1307 response.AddHeader(hdr, data);
1308 }
1309
1310 // internal void RemoveHeader(string hdr)
1311 // {
1312 // if (Rest.DEBUG)
1313 // {
1314 // Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
1315 // if (response.Headers.Get(hdr) == null)
1316 // {
1317 // Rest.Log.DebugFormat("{0} No such header existed",
1318 // MsgId, hdr);
1319 // }
1320 // }
1321 // response.Headers.Remove(hdr);
1322 // }
1323
1324 // internal void DumpHeaders()
1325 // {
1326 // if (Rest.DEBUG)
1327 // {
1328 // for (int i=0;i<response.Headers.Count;i++)
1329 // {
1330 // Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
1331 // response.Headers.Get(i));
1332 // }
1333 // }
1334 // }
1335
1336 // Setup the XML writer for output
1337
1338 internal void initXmlWriter()
1339 {
1340 XmlWriterSettings settings = new XmlWriterSettings();
1341 xmldata = new MemoryStream();
1342 settings.Indent = true;
1343 settings.IndentChars = " ";
1344 settings.Encoding = encoding;
1345 settings.CloseOutput = false;
1346 settings.OmitXmlDeclaration = true;
1347 settings.ConformanceLevel = ConformanceLevel.Fragment;
1348 writer = XmlWriter.Create(xmldata, settings);
1349 }
1350
1351 internal void initXmlReader()
1352 {
1353
1354 XmlReaderSettings settings = new XmlReaderSettings();
1355
1356 settings.ConformanceLevel = ConformanceLevel.Fragment;
1357 settings.IgnoreComments = true;
1358 settings.IgnoreWhitespace = true;
1359 settings.IgnoreProcessingInstructions = true;
1360 settings.ValidationType = ValidationType.None;
1361
1362 reader = XmlReader.Create(request.InputStream,settings);
1363
1364 }
1365
1366 internal void appendStatus(string msg)
1367 {
1368 if (statusBody == String.Empty)
1369 {
1370 statusBody = String.Format(Rest.statusHead, request.HttpMethod);
1371 }
1372
1373 statusBody = String.Format("{0} {1}", statusBody, msg);
1374 }
1375
1376#endregion service_interface
1377
1378#region internal_methods
1379
1380 /// <summary>
1381 /// Helper methods for deconstructing and reconstructing
1382 /// URI path data.
1383 /// </summary>
1384
1385 private void initUrl()
1386 {
1387
1388 uri = request.Url;
1389
1390 if (query == null)
1391 {
1392 query = uri.Query;
1393 }
1394
1395 // If the path has not been previously initialized,
1396 // do so now.
1397
1398 if (path == null)
1399 {
1400 path = uri.AbsolutePath;
1401 if (path.EndsWith(Rest.UrlPathSeparator))
1402 path = path.Substring(0,path.Length-1);
1403 }
1404
1405 // If we succeeded in getting a path, perform any
1406 // additional pre-processing required.
1407
1408 if (path != null)
1409 {
1410 if (Rest.ExtendedEscape)
1411 {
1412 // Handle "+". Not a standard substitution, but
1413 // common enough...
1414 path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
1415 }
1416 pathNodes = path.Split(Rest.CA_PATHSEP);
1417 }
1418 else
1419 {
1420 pathNodes = EmptyPath;
1421 }
1422
1423 // Elimiate any %-escaped values. This is left until here
1424 // so that escaped "+' are not mistakenly replaced.
1425
1426 path = Uri.UnescapeDataString(path);
1427
1428 // Request server context info
1429
1430 hostname = uri.Host;
1431 port = uri.Port;
1432
1433 }
1434
1435 private int initParameters(int prfxlen)
1436 {
1437
1438 if (prfxlen < path.Length-1)
1439 {
1440 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
1441 }
1442 else
1443 {
1444 parameters = new string[0];
1445 }
1446
1447 // Generate a debug list of the decoded parameters
1448
1449 if (Rest.DEBUG && prfxlen < path.Length-1)
1450 {
1451 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
1452 for (int i = 0; i < parameters.Length; i++)
1453 {
1454 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
1455 }
1456 }
1457
1458 return parameters.Length;
1459
1460 }
1461
1462#endregion internal_methods
1463
1464 }
1465}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml b/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml
deleted file mode 100644
index 777a2dc..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml
+++ /dev/null
@@ -1,11 +0,0 @@
1<Addin id="OpenSim.ApplicationPlugins.Rest.Inventory" version="0.1">
2 <Runtime>
3 <Import assembly="OpenSim.ApplicationPlugins.Rest.Inventory.dll"/>
4 </Runtime>
5 <Dependencies>
6 <Addin id="OpenSim" version="0.5" />
7 </Dependencies>
8 <Extension path = "/OpenSim/Startup">
9 <Plugin id="RestInventory" type="OpenSim.ApplicationPlugins.Rest.Inventory.RestHandler" />
10 </Extension>
11</Addin>
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
deleted file mode 100644
index 9755e73..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
+++ /dev/null
@@ -1,551 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 */
28
29using System;
30using System.Collections.Generic;
31using System.Reflection;
32using System.Text;
33using log4net;
34using Nini.Config;
35using OpenSim.Framework;
36using OpenSim.Framework.Communications;
37using OpenSim.Services.Interfaces;
38using IAvatarService = OpenSim.Services.Interfaces.IAvatarService;
39
40namespace OpenSim.ApplicationPlugins.Rest.Inventory
41{
42 public class Rest
43 {
44 internal static readonly ILog Log =
45 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
46
47 internal static bool DEBUG = Log.IsDebugEnabled;
48
49 /// <summary>
50 /// Supported authentication schemes
51 /// </summary>
52
53 public const string AS_BASIC = "Basic"; // simple user/password verification
54 public const string AS_DIGEST = "Digest"; // password safe authentication
55
56 /// Supported Digest algorithms
57
58 public const string Digest_MD5 = "MD5"; // assumed default if omitted
59 public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST?
60
61 public const string Qop_Auth = "auth"; // authentication only
62 public const string Qop_Int = "auth-int"; // TODO
63
64 /// <summary>
65 /// These values have a single value for the whole
66 /// domain and lifetime of the plugin handler. We
67 /// make them static for ease of reference within
68 /// the assembly. These are initialized by the
69 /// RestHandler class during start-up.
70 /// </summary>
71
72 internal static IRestHandler Plugin = null;
73 internal static OpenSimBase main = null;
74 internal static string Prefix = null;
75 internal static IConfig Config = null;
76 internal static string GodKey = null;
77 internal static bool Authenticate = true;
78 internal static bool Secure = true;
79 internal static bool ExtendedEscape = true;
80 internal static bool DumpAsset = false;
81 internal static bool Fill = true;
82 internal static bool FlushEnabled = true;
83 internal static string Realm = "OpenSim REST";
84 internal static string Scheme = AS_BASIC;
85 internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
86
87 /// <summary>
88 /// These are all dependent upon the Comms manager
89 /// being initialized. So they have to be properties
90 /// because the comms manager is now a module and is
91 /// not guaranteed to be there when the rest handler
92 /// initializes.
93 /// </summary>
94
95 internal static IInventoryService InventoryServices
96 {
97 get { return main.SceneManager.CurrentOrFirstScene.InventoryService; }
98 }
99
100 internal static IUserAccountService UserServices
101 {
102 get { return main.SceneManager.CurrentOrFirstScene.UserAccountService; }
103 }
104
105 internal static IAuthenticationService AuthServices
106 {
107 get { return main.SceneManager.CurrentOrFirstScene.AuthenticationService; }
108 }
109
110 internal static IAvatarService AvatarServices
111 {
112 get { return main.SceneManager.CurrentOrFirstScene.AvatarService; }
113 }
114
115 internal static IAssetService AssetServices
116 {
117 get { return main.SceneManager.CurrentOrFirstScene.AssetService; }
118 }
119
120 /// <summary>
121 /// HTTP requires that status information be generated for PUT
122 /// and POST opertaions. This is in support of that. The
123 /// operation verb gets substituted into the first string,
124 /// and the completion code is inserted into the tail. The
125 /// strings are put here to encourage consistency.
126 /// </summary>
127
128 internal static string statusHead = "<html><body><title>{0} status</title><break>";
129 internal static string statusTail = "</body></html>";
130
131 internal static Dictionary<int,string> HttpStatusDesc;
132
133 static Rest()
134 {
135 HttpStatusDesc = new Dictionary<int,string>();
136 if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length)
137 {
138 Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match");
139 throw new Exception("HTTP Status array discrepancy");
140 }
141
142 // Repackage the data into something more tractable. The sparse
143 // nature of HTTP return codes makes an array a bad choice.
144
145 for (int i=0; i<HttpStatusCodeArray.Length; i++)
146 {
147 HttpStatusDesc.Add(HttpStatusCodeArray[i], HttpStatusDescArray[i]);
148 }
149 }
150
151 internal static int CreationDate
152 {
153 get { return (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; }
154 }
155
156 internal static string MsgId
157 {
158 get { return Plugin.MsgId; }
159 }
160
161 internal static string RequestId
162 {
163 get { return Plugin.RequestId; }
164 }
165
166 internal static Encoding Encoding = Util.UTF8;
167
168 /// <summary>
169 /// Version control for REST implementation. This
170 /// refers to the overall infrastructure represented
171 /// by the following classes
172 /// RequestData
173 /// RequestInventoryPlugin
174 /// Rest
175 /// It does no describe implementation classes such as
176 /// RestInventoryServices, which may morph much more
177 /// often. Such classes ARE dependent upon this however
178 /// and should check it in their Initialize method.
179 /// </summary>
180
181 public static readonly float Version = 1.0F;
182 public const string Name = "REST 1.0";
183
184 /// <summary>
185 /// Currently defined HTTP methods.
186 /// Only GET and HEAD are required to be
187 /// supported by all servers. See Respond
188 /// to see how these are handled.
189 /// </summary>
190
191 // REST AGENT 1.0 interpretations
192 public const string GET = "get"; // information retrieval - server state unchanged
193 public const string HEAD = "head"; // same as get except only the headers are returned.
194 public const string POST = "post"; // Replace the URI designated resource with the entity.
195 public const string PUT = "put"; // Add the entity to the context represented by the URI
196 public const string DELETE = "delete"; // Remove the URI designated resource from the server.
197
198 public const string OPTIONS = "options"; //
199 public const string TRACE = "trace"; //
200 public const string CONNECT = "connect"; //
201
202 // Define this in one place...
203
204 public const string UrlPathSeparator = "/";
205 public const string UrlMethodSeparator = ":";
206
207 // Redirection qualifications
208
209 public const bool PERMANENT = false;
210 public const bool TEMPORARY = true;
211
212 // Constant arrays used by String.Split
213
214 public static readonly char C_SPACE = ' ';
215 public static readonly char C_SLASH = '/';
216 public static readonly char C_PATHSEP = '/';
217 public static readonly char C_COLON = ':';
218 public static readonly char C_PLUS = '+';
219 public static readonly char C_PERIOD = '.';
220 public static readonly char C_COMMA = ',';
221 public static readonly char C_DQUOTE = '"';
222
223 public static readonly string CS_SPACE = " ";
224 public static readonly string CS_SLASH = "/";
225 public static readonly string CS_PATHSEP = "/";
226 public static readonly string CS_COLON = ":";
227 public static readonly string CS_PLUS = "+";
228 public static readonly string CS_PERIOD = ".";
229 public static readonly string CS_COMMA = ",";
230 public static readonly string CS_DQUOTE = "\"";
231
232 public static readonly char[] CA_SPACE = { C_SPACE };
233 public static readonly char[] CA_SLASH = { C_SLASH };
234 public static readonly char[] CA_PATHSEP = { C_PATHSEP };
235 public static readonly char[] CA_COLON = { C_COLON };
236 public static readonly char[] CA_PERIOD = { C_PERIOD };
237 public static readonly char[] CA_PLUS = { C_PLUS };
238 public static readonly char[] CA_COMMA = { C_COMMA };
239 public static readonly char[] CA_DQUOTE = { C_DQUOTE };
240
241 // HTTP Code Values (in value order)
242
243 public const int HttpStatusCodeContinue = 100;
244 public const int HttpStatusCodeSwitchingProtocols = 101;
245
246 public const int HttpStatusCodeOK = 200;
247 public const int HttpStatusCodeCreated = 201;
248 public const int HttpStatusCodeAccepted = 202;
249 public const int HttpStatusCodeNonAuthoritative = 203;
250 public const int HttpStatusCodeNoContent = 204;
251 public const int HttpStatusCodeResetContent = 205;
252 public const int HttpStatusCodePartialContent = 206;
253
254 public const int HttpStatusCodeMultipleChoices = 300;
255 public const int HttpStatusCodePermanentRedirect = 301;
256 public const int HttpStatusCodeFound = 302;
257 public const int HttpStatusCodeSeeOther = 303;
258 public const int HttpStatusCodeNotModified = 304;
259 public const int HttpStatusCodeUseProxy = 305;
260 public const int HttpStatusCodeReserved306 = 306;
261 public const int HttpStatusCodeTemporaryRedirect = 307;
262
263 public const int HttpStatusCodeBadRequest = 400;
264 public const int HttpStatusCodeNotAuthorized = 401;
265 public const int HttpStatusCodePaymentRequired = 402;
266 public const int HttpStatusCodeForbidden = 403;
267 public const int HttpStatusCodeNotFound = 404;
268 public const int HttpStatusCodeMethodNotAllowed = 405;
269 public const int HttpStatusCodeNotAcceptable = 406;
270 public const int HttpStatusCodeProxyAuthenticate = 407;
271 public const int HttpStatusCodeTimeOut = 408;
272 public const int HttpStatusCodeConflict = 409;
273 public const int HttpStatusCodeGone = 410;
274 public const int HttpStatusCodeLengthRequired = 411;
275 public const int HttpStatusCodePreconditionFailed = 412;
276 public const int HttpStatusCodeEntityTooLarge = 413;
277 public const int HttpStatusCodeUriTooLarge = 414;
278 public const int HttpStatusCodeUnsupportedMedia = 415;
279 public const int HttpStatusCodeRangeNotSatsified = 416;
280 public const int HttpStatusCodeExpectationFailed = 417;
281
282 public const int HttpStatusCodeServerError = 500;
283 public const int HttpStatusCodeNotImplemented = 501;
284 public const int HttpStatusCodeBadGateway = 502;
285 public const int HttpStatusCodeServiceUnavailable = 503;
286 public const int HttpStatusCodeGatewayTimeout = 504;
287 public const int HttpStatusCodeHttpVersionError = 505;
288
289 public static readonly int[] HttpStatusCodeArray = {
290 HttpStatusCodeContinue,
291 HttpStatusCodeSwitchingProtocols,
292 HttpStatusCodeOK,
293 HttpStatusCodeCreated,
294 HttpStatusCodeAccepted,
295 HttpStatusCodeNonAuthoritative,
296 HttpStatusCodeNoContent,
297 HttpStatusCodeResetContent,
298 HttpStatusCodePartialContent,
299 HttpStatusCodeMultipleChoices,
300 HttpStatusCodePermanentRedirect,
301 HttpStatusCodeFound,
302 HttpStatusCodeSeeOther,
303 HttpStatusCodeNotModified,
304 HttpStatusCodeUseProxy,
305 HttpStatusCodeReserved306,
306 HttpStatusCodeTemporaryRedirect,
307 HttpStatusCodeBadRequest,
308 HttpStatusCodeNotAuthorized,
309 HttpStatusCodePaymentRequired,
310 HttpStatusCodeForbidden,
311 HttpStatusCodeNotFound,
312 HttpStatusCodeMethodNotAllowed,
313 HttpStatusCodeNotAcceptable,
314 HttpStatusCodeProxyAuthenticate,
315 HttpStatusCodeTimeOut,
316 HttpStatusCodeConflict,
317 HttpStatusCodeGone,
318 HttpStatusCodeLengthRequired,
319 HttpStatusCodePreconditionFailed,
320 HttpStatusCodeEntityTooLarge,
321 HttpStatusCodeUriTooLarge,
322 HttpStatusCodeUnsupportedMedia,
323 HttpStatusCodeRangeNotSatsified,
324 HttpStatusCodeExpectationFailed,
325 HttpStatusCodeServerError,
326 HttpStatusCodeNotImplemented,
327 HttpStatusCodeBadGateway,
328 HttpStatusCodeServiceUnavailable,
329 HttpStatusCodeGatewayTimeout,
330 HttpStatusCodeHttpVersionError
331 };
332
333 // HTTP Status Descriptions (in status code order)
334 // This array must be kept strictly consistent with respect
335 // to the status code array above.
336
337 public static readonly string[] HttpStatusDescArray = {
338 "Continue Request",
339 "Switching Protocols",
340 "OK",
341 "CREATED",
342 "ACCEPTED",
343 "NON-AUTHORITATIVE INFORMATION",
344 "NO CONTENT",
345 "RESET CONTENT",
346 "PARTIAL CONTENT",
347 "MULTIPLE CHOICES",
348 "PERMANENT REDIRECT",
349 "FOUND",
350 "SEE OTHER",
351 "NOT MODIFIED",
352 "USE PROXY",
353 "RESERVED CODE 306",
354 "TEMPORARY REDIRECT",
355 "BAD REQUEST",
356 "NOT AUTHORIZED",
357 "PAYMENT REQUIRED",
358 "FORBIDDEN",
359 "NOT FOUND",
360 "METHOD NOT ALLOWED",
361 "NOT ACCEPTABLE",
362 "PROXY AUTHENTICATION REQUIRED",
363 "TIMEOUT",
364 "CONFLICT",
365 "GONE",
366 "LENGTH REQUIRED",
367 "PRECONDITION FAILED",
368 "ENTITY TOO LARGE",
369 "URI TOO LARGE",
370 "UNSUPPORTED MEDIA",
371 "RANGE NOT SATISFIED",
372 "EXPECTATION FAILED",
373 "SERVER ERROR",
374 "NOT IMPLEMENTED",
375 "BAD GATEWAY",
376 "SERVICE UNAVAILABLE",
377 "GATEWAY TIMEOUT",
378 "HTTP VERSION NOT SUPPORTED"
379 };
380
381 // HTTP Headers
382
383 public const string HttpHeaderAccept = "Accept";
384 public const string HttpHeaderAcceptCharset = "Accept-Charset";
385 public const string HttpHeaderAcceptEncoding = "Accept-Encoding";
386 public const string HttpHeaderAcceptLanguage = "Accept-Language";
387 public const string HttpHeaderAcceptRanges = "Accept-Ranges";
388 public const string HttpHeaderAge = "Age";
389 public const string HttpHeaderAllow = "Allow";
390 public const string HttpHeaderAuthorization = "Authorization";
391 public const string HttpHeaderCacheControl = "Cache-Control";
392 public const string HttpHeaderConnection = "Connection";
393 public const string HttpHeaderContentEncoding = "Content-Encoding";
394 public const string HttpHeaderContentLanguage = "Content-Language";
395 public const string HttpHeaderContentLength = "Content-Length";
396 public const string HttpHeaderContentLocation = "Content-Location";
397 public const string HttpHeaderContentMD5 = "Content-MD5";
398 public const string HttpHeaderContentRange = "Content-Range";
399 public const string HttpHeaderContentType = "Content-Type";
400 public const string HttpHeaderDate = "Date";
401 public const string HttpHeaderETag = "ETag";
402 public const string HttpHeaderExpect = "Expect";
403 public const string HttpHeaderExpires = "Expires";
404 public const string HttpHeaderFrom = "From";
405 public const string HttpHeaderHost = "Host";
406 public const string HttpHeaderIfMatch = "If-Match";
407 public const string HttpHeaderIfModifiedSince = "If-Modified-Since";
408 public const string HttpHeaderIfNoneMatch = "If-None-Match";
409 public const string HttpHeaderIfRange = "If-Range";
410 public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since";
411 public const string HttpHeaderLastModified = "Last-Modified";
412 public const string HttpHeaderLocation = "Location";
413 public const string HttpHeaderMaxForwards = "Max-Forwards";
414 public const string HttpHeaderPragma = "Pragma";
415 public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate";
416 public const string HttpHeaderProxyAuthorization = "Proxy-Authorization";
417 public const string HttpHeaderRange = "Range";
418 public const string HttpHeaderReferer = "Referer";
419 public const string HttpHeaderRetryAfter = "Retry-After";
420 public const string HttpHeaderServer = "Server";
421 public const string HttpHeaderTE = "TE";
422 public const string HttpHeaderTrailer = "Trailer";
423 public const string HttpHeaderTransferEncoding = "Transfer-Encoding";
424 public const string HttpHeaderUpgrade = "Upgrade";
425 public const string HttpHeaderUserAgent = "User-Agent";
426 public const string HttpHeaderVary = "Vary";
427 public const string HttpHeaderVia = "Via";
428 public const string HttpHeaderWarning = "Warning";
429 public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate";
430
431 /// Utility routines
432
433 public static string StringToBase64(string str)
434 {
435 try
436 {
437 byte[] encData_byte = new byte[str.Length];
438 encData_byte = Util.UTF8.GetBytes(str);
439 return Convert.ToBase64String(encData_byte);
440 }
441 catch
442 {
443 return String.Empty;
444 }
445 }
446
447 public static string Base64ToString(string str)
448 {
449 try
450 {
451 return Util.Base64ToString(str);
452 }
453 catch
454 {
455 return String.Empty;
456 }
457 }
458
459 private const string hvals = "0123456789abcdef";
460
461 public static int Hex2Int(string hex)
462 {
463 int val = 0;
464 int sum = 0;
465 string tmp = null;
466
467 if (hex != null)
468 {
469 tmp = hex.ToLower();
470 for (int i = 0; i < tmp.Length; i++)
471 {
472 val = hvals.IndexOf(tmp[i]);
473 if (val == -1)
474 break;
475 sum *= 16;
476 sum += val;
477 }
478 }
479
480 return sum;
481 }
482
483 // Nonce management
484
485 public static string NonceGenerator()
486 {
487 return StringToBase64(CreationDate + Guid.NewGuid().ToString());
488 }
489
490 // Dump the specified data stream
491
492 public static void Dump(byte[] data)
493 {
494 char[] buffer = new char[DumpLineSize];
495 int cc = 0;
496
497 for (int i = 0; i < data.Length; i++)
498 {
499 if (i % DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8"));
500
501 if (i % 4 == 0) Console.Write(" ");
502
503 Console.Write("{0}",data[i].ToString("x2"));
504
505 if (data[i] < 127 && data[i] > 31)
506 buffer[i % DumpLineSize] = (char) data[i];
507 else
508 buffer[i % DumpLineSize] = '.';
509
510 cc++;
511
512 if (i != 0 && (i + 1) % DumpLineSize == 0)
513 {
514 Console.Write(" |"+(new String(buffer))+"|");
515 cc = 0;
516 }
517 }
518
519 // Finish off any incomplete line
520
521 if (cc != 0)
522 {
523 for (int i = cc ; i < DumpLineSize; i++)
524 {
525 if (i % 4 == 0) Console.Write(" ");
526 Console.Write(" ");
527 buffer[i % DumpLineSize] = ' ';
528 }
529 Console.WriteLine(" |"+(new String(buffer))+"|");
530 }
531 else
532 {
533 Console.Write("\n");
534 }
535 }
536 }
537
538 // Local exception type
539
540 public class RestException : Exception
541 {
542 internal int statusCode;
543 internal string statusDesc;
544 internal string httpmethod;
545 internal string httppath;
546
547 public RestException(string msg) : base(msg)
548 {
549 }
550 }
551}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs
deleted file mode 100644
index 3cda984..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs
+++ /dev/null
@@ -1,860 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections;
30using System.Collections.Generic;
31using System.Xml;
32using OpenMetaverse;
33using OpenSim.Framework;
34using OpenSim.Framework.Servers;
35using OpenSim.Framework.Servers.HttpServer;
36using OpenSim.Services.Interfaces;
37
38namespace OpenSim.ApplicationPlugins.Rest.Inventory
39{
40
41 public class RestAppearanceServices : IRest
42 {
43// private static readonly int PARM_USERID = 0;
44
45 // private static readonly int PARM_PATH = 1;
46
47// private bool enabled = false;
48 private string qPrefix = "appearance";
49
50 /// <summary>
51 /// The constructor makes sure that the service prefix is absolute
52 /// and the registers the service handler and the allocator.
53 /// </summary>
54
55 public RestAppearanceServices()
56 {
57 Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId);
58 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
59
60 // If a relative path was specified for the handler's domain,
61 // add the standard prefix to make it absolute, e.g. /admin
62
63 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
64 {
65 Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
66 qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
67 qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
68 Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
69 }
70
71 // Register interface using the absolute URI.
72
73 Rest.Plugin.AddPathHandler(DoAppearance,qPrefix,Allocate);
74
75 // Activate if everything went OK
76
77// enabled = true;
78
79 Rest.Log.InfoFormat("{0} User appearance services initialization complete", MsgId);
80 }
81
82 /// <summary>
83 /// Post-construction, pre-enabled initialization opportunity
84 /// Not currently exploited.
85 /// </summary>
86
87 public void Initialize()
88 {
89 }
90
91 /// <summary>
92 /// Called by the plug-in to halt service processing. Local processing is
93 /// disabled.
94 /// </summary>
95
96 public void Close()
97 {
98// enabled = false;
99 Rest.Log.InfoFormat("{0} User appearance services closing down", MsgId);
100 }
101
102 /// <summary>
103 /// This property is declared locally because it is used a lot and
104 /// brevity is nice.
105 /// </summary>
106
107 internal string MsgId
108 {
109 get { return Rest.MsgId; }
110 }
111
112 #region Interface
113
114 /// <summary>
115 /// The plugin (RestHandler) calls this method to allocate the request
116 /// state carrier for a new request. It is destroyed when the request
117 /// completes. All request-instance specific state is kept here. This
118 /// is registered when this service provider is registered.
119 /// </summary>
120 /// <param name=request>Inbound HTTP request information</param>
121 /// <param name=response>Outbound HTTP request information</param>
122 /// <param name=qPrefix>REST service domain prefix</param>
123 /// <returns>A RequestData instance suitable for this service</returns>
124
125 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
126 {
127 return (RequestData) new AppearanceRequestData(request, response, prefix);
128 }
129
130 /// <summary>
131 /// This method is registered with the handler when this service provider
132 /// is initialized. It is called whenever the plug-in identifies this service
133 /// provider as the best match for a given request.
134 /// It handles all aspects of inventory REST processing, i.e. /admin/inventory
135 /// </summary>
136 /// <param name=hdata>A consolidated HTTP request work area</param>
137
138 private void DoAppearance(RequestData hdata)
139 {
140 // !!! REFACTORIMG PROBLEM. This needs rewriting for 0.7
141
142 //AppearanceRequestData rdata = (AppearanceRequestData) hdata;
143
144 //Rest.Log.DebugFormat("{0} DoAppearance ENTRY", MsgId);
145
146 //// If we're disabled, do nothing.
147
148 //if (!enabled)
149 //{
150 // return;
151 //}
152
153 //// Now that we know this is a serious attempt to
154 //// access inventory data, we should find out who
155 //// is asking, and make sure they are authorized
156 //// to do so. We need to validate the caller's
157 //// identity before revealing anything about the
158 //// status quo. Authenticate throws an exception
159 //// via Fail if no identity information is present.
160 ////
161 //// With the present HTTP server we can't use the
162 //// builtin authentication mechanisms because they
163 //// would be enforced for all in-bound requests.
164 //// Instead we look at the headers ourselves and
165 //// handle authentication directly.
166
167 //try
168 //{
169 // if (!rdata.IsAuthenticated)
170 // {
171 // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
172 // }
173 //}
174 //catch (RestException e)
175 //{
176 // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
177 // {
178 // Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
179 // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
180 // }
181 // else
182 // {
183 // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
184 // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
185 // }
186 // throw (e);
187 //}
188
189 //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
190
191 //// We can only get here if we are authorized
192 ////
193 //// The requestor may have specified an UUID or
194 //// a conjoined FirstName LastName string. We'll
195 //// try both. If we fail with the first, UUID,
196 //// attempt, we try the other. As an example, the
197 //// URI for a valid inventory request might be:
198 ////
199 //// http://<host>:<port>/admin/inventory/Arthur Dent
200 ////
201 //// Indicating that this is an inventory request for
202 //// an avatar named Arthur Dent. This is ALL that is
203 //// required to designate a GET for an entire
204 //// inventory.
205 ////
206
207 //// Do we have at least a user agent name?
208
209 //if (rdata.Parameters.Length < 1)
210 //{
211 // Rest.Log.WarnFormat("{0} Appearance: No user agent identifier specified", MsgId);
212 // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified");
213 //}
214
215 //// The first parameter MUST be the agent identification, either an UUID
216 //// or a space-separated First-name Last-Name specification. We check for
217 //// an UUID first, if anyone names their character using a valid UUID
218 //// that identifies another existing avatar will cause this a problem...
219
220 //try
221 //{
222 // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]);
223 // Rest.Log.DebugFormat("{0} UUID supplied", MsgId);
224 // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
225 //}
226 //catch
227 //{
228 // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE);
229 // if (names.Length == 2)
230 // {
231 // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
232 // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]);
233 // }
234 // else
235 // {
236 // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
237 // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity");
238 // }
239 //}
240
241 //// If the user profile is null then either the server is broken, or the
242 //// user is not known. We always assume the latter case.
243
244 //if (rdata.userProfile != null)
245 //{
246 // Rest.Log.DebugFormat("{0} User profile obtained for agent {1} {2}",
247 // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
248 //}
249 //else
250 //{
251 // Rest.Log.WarnFormat("{0} No user profile for {1}", MsgId, rdata.path);
252 // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity");
253 //}
254
255 //// If we get to here, then we have effectively validated the user's
256
257 //switch (rdata.method)
258 //{
259 // case Rest.HEAD : // Do the processing, set the status code, suppress entity
260 // DoGet(rdata);
261 // rdata.buffer = null;
262 // break;
263
264 // case Rest.GET : // Do the processing, set the status code, return entity
265 // DoGet(rdata);
266 // break;
267
268 // case Rest.PUT : // Update named element
269 // DoUpdate(rdata);
270 // break;
271
272 // case Rest.POST : // Add new information to identified context.
273 // DoExtend(rdata);
274 // break;
275
276 // case Rest.DELETE : // Delete information
277 // DoDelete(rdata);
278 // break;
279
280 // default :
281 // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}",
282 // MsgId, rdata.method, rdata.path);
283 // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
284 // String.Format("{0} not supported", rdata.method));
285 // break;
286 //}
287 }
288
289 #endregion Interface
290
291 #region method-specific processing
292
293 /// <summary>
294 /// This method implements GET processing for user's appearance.
295 /// </summary>
296 /// <param name=rdata>HTTP service request work area</param>
297
298// private void DoGet(AppearanceRequestData rdata)
299// {
300// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID);
301//
302// if (adata == null)
303// {
304// rdata.Fail(Rest.HttpStatusCodeNoContent,
305// String.Format("appearance data not found for user {0} {1}",
306// rdata.userProfile.FirstName, rdata.userProfile.SurName));
307// }
308// rdata.userAppearance = adata.ToAvatarAppearance(rdata.userProfile.ID);
309//
310// rdata.initXmlWriter();
311//
312// FormatUserAppearance(rdata);
313//
314// // Indicate a successful request
315//
316// rdata.Complete();
317//
318// // Send the response to the user. The body will be implicitly
319// // constructed from the result of the XML writer.
320//
321// rdata.Respond(String.Format("Appearance {0} Normal completion", rdata.method));
322// }
323
324 /// <summary>
325 /// POST adds NEW information to the user profile database.
326 /// This effectively resets the appearance before applying those
327 /// characteristics supplied in the request.
328 /// </summary>
329
330// private void DoExtend(AppearanceRequestData rdata)
331// {
332//
333// bool created = false;
334// bool modified = false;
335// string newnode = String.Empty;
336//
337// Rest.Log.DebugFormat("{0} POST ENTRY", MsgId);
338//
339// //AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);
340//
341// rdata.userAppearance = new AvatarAppearance();
342//
343// // Although the following behavior is admitted by HTTP I am becoming
344// // increasingly doubtful that it is appropriate for REST. If I attempt to
345// // add a new record, and it already exists, then it seems to me that the
346// // attempt should fail, rather than update the existing record.
347// AvatarData adata = null;
348// if (GetUserAppearance(rdata))
349// {
350// modified = rdata.userAppearance != null;
351// created = !modified;
352// adata = new AvatarData(rdata.userAppearance);
353// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
354// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
355// }
356// else
357// {
358// created = true;
359// adata = new AvatarData(rdata.userAppearance);
360// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
361// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
362// }
363//
364// if (created)
365// {
366// newnode = String.Format("{0} {1}", rdata.userProfile.FirstName,
367// rdata.userProfile.SurName);
368// // Must include a location header with a URI that identifies the new resource.
369//
370// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}{3}{4}",
371// rdata.hostname,rdata.port,rdata.path,Rest.UrlPathSeparator, newnode));
372// rdata.Complete(Rest.HttpStatusCodeCreated);
373//
374// }
375// else
376// {
377// if (modified)
378// {
379// rdata.Complete(Rest.HttpStatusCodeOK);
380// }
381// else
382// {
383// rdata.Complete(Rest.HttpStatusCodeNoContent);
384// }
385// }
386//
387// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
388//
389// }
390
391 /// <summary>
392 /// This updates the user's appearance. not all aspects need to be provided,
393 /// only those supplied will be changed.
394 /// </summary>
395
396// private void DoUpdate(AppearanceRequestData rdata)
397// {
398//
399// // REFACTORING PROBLEM This was commented out. It doesn't work for 0.7
400//
401// //bool created = false;
402// //bool modified = false;
403//
404//
405// //rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);
406//
407// //// If the user exists then this is considered a modification regardless
408// //// of what may, or may not be, specified in the payload.
409//
410// //if (rdata.userAppearance != null)
411// //{
412// // modified = true;
413// // Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance);
414// // Rest.UserServices.UpdateUserProfile(rdata.userProfile);
415// //}
416//
417// //if (created)
418// //{
419// // rdata.Complete(Rest.HttpStatusCodeCreated);
420// //}
421// //else
422// //{
423// // if (modified)
424// // {
425// // rdata.Complete(Rest.HttpStatusCodeOK);
426// // }
427// // else
428// // {
429// // rdata.Complete(Rest.HttpStatusCodeNoContent);
430// // }
431// //}
432//
433// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
434//
435// }
436
437 /// <summary>
438 /// Delete the specified user's appearance. This actually performs a reset
439 /// to the default avatar appearance, if the info is already there.
440 /// Existing ownership is preserved. All prior updates are lost and can not
441 /// be recovered.
442 /// </summary>
443// private void DoDelete(AppearanceRequestData rdata)
444// {
445// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID);
446//
447// if (adata != null)
448// {
449// AvatarAppearance old = adata.ToAvatarAppearance(rdata.userProfile.ID);
450// rdata.userAppearance = new AvatarAppearance();
451// rdata.userAppearance.Owner = old.Owner;
452// adata = new AvatarData(rdata.userAppearance);
453//
454// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata);
455//
456// rdata.Complete();
457// }
458// else
459// {
460//
461// rdata.Complete(Rest.HttpStatusCodeNoContent);
462// }
463//
464// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
465// }
466
467#endregion method-specific processing
468
469 private bool GetUserAppearance(AppearanceRequestData rdata)
470 {
471
472 XmlReader xml;
473 bool indata = false;
474
475 rdata.initXmlReader();
476 xml = rdata.reader;
477
478 while (xml.Read())
479 {
480 switch (xml.NodeType)
481 {
482 case XmlNodeType.Element :
483 switch (xml.Name)
484 {
485 case "Appearance" :
486 if (xml.MoveToAttribute("Height"))
487 {
488 rdata.userAppearance.AvatarHeight = (float) Convert.ToDouble(xml.Value);
489 indata = true;
490 }
491// if (xml.MoveToAttribute("Owner"))
492// {
493// rdata.userAppearance.Owner = (UUID)xml.Value;
494// indata = true;
495// }
496 if (xml.MoveToAttribute("Serial"))
497 {
498 rdata.userAppearance.Serial = Convert.ToInt32(xml.Value);
499 indata = true;
500 }
501 break;
502/*
503 case "Body" :
504 if (xml.MoveToAttribute("Item"))
505 {
506 rdata.userAppearance.BodyItem = (UUID)xml.Value;
507 indata = true;
508 }
509 if (xml.MoveToAttribute("Asset"))
510 {
511 rdata.userAppearance.BodyAsset = (UUID)xml.Value;
512 indata = true;
513 }
514 break;
515 case "Skin" :
516 if (xml.MoveToAttribute("Item"))
517 {
518 rdata.userAppearance.SkinItem = (UUID)xml.Value;
519 indata = true;
520 }
521 if (xml.MoveToAttribute("Asset"))
522 {
523 rdata.userAppearance.SkinAsset = (UUID)xml.Value;
524 indata = true;
525 }
526 break;
527 case "Hair" :
528 if (xml.MoveToAttribute("Item"))
529 {
530 rdata.userAppearance.HairItem = (UUID)xml.Value;
531 indata = true;
532 }
533 if (xml.MoveToAttribute("Asset"))
534 {
535 rdata.userAppearance.HairAsset = (UUID)xml.Value;
536 indata = true;
537 }
538 break;
539 case "Eyes" :
540 if (xml.MoveToAttribute("Item"))
541 {
542 rdata.userAppearance.EyesItem = (UUID)xml.Value;
543 indata = true;
544 }
545 if (xml.MoveToAttribute("Asset"))
546 {
547 rdata.userAppearance.EyesAsset = (UUID)xml.Value;
548 indata = true;
549 }
550 break;
551 case "Shirt" :
552 if (xml.MoveToAttribute("Item"))
553 {
554 rdata.userAppearance.ShirtItem = (UUID)xml.Value;
555 indata = true;
556 }
557 if (xml.MoveToAttribute("Asset"))
558 {
559 rdata.userAppearance.ShirtAsset = (UUID)xml.Value;
560 indata = true;
561 }
562 break;
563 case "Pants" :
564 if (xml.MoveToAttribute("Item"))
565 {
566 rdata.userAppearance.PantsItem = (UUID)xml.Value;
567 indata = true;
568 }
569 if (xml.MoveToAttribute("Asset"))
570 {
571 rdata.userAppearance.PantsAsset = (UUID)xml.Value;
572 indata = true;
573 }
574 break;
575 case "Shoes" :
576 if (xml.MoveToAttribute("Item"))
577 {
578 rdata.userAppearance.ShoesItem = (UUID)xml.Value;
579 indata = true;
580 }
581 if (xml.MoveToAttribute("Asset"))
582 {
583 rdata.userAppearance.ShoesAsset = (UUID)xml.Value;
584 indata = true;
585 }
586 break;
587 case "Socks" :
588 if (xml.MoveToAttribute("Item"))
589 {
590 rdata.userAppearance.SocksItem = (UUID)xml.Value;
591 indata = true;
592 }
593 if (xml.MoveToAttribute("Asset"))
594 {
595 rdata.userAppearance.SocksAsset = (UUID)xml.Value;
596 indata = true;
597 }
598 break;
599 case "Jacket" :
600 if (xml.MoveToAttribute("Item"))
601 {
602 rdata.userAppearance.JacketItem = (UUID)xml.Value;
603 indata = true;
604 }
605 if (xml.MoveToAttribute("Asset"))
606 {
607 rdata.userAppearance.JacketAsset = (UUID)xml.Value;
608 indata = true;
609 }
610 break;
611 case "Gloves" :
612 if (xml.MoveToAttribute("Item"))
613 {
614 rdata.userAppearance.GlovesItem = (UUID)xml.Value;
615 indata = true;
616 }
617 if (xml.MoveToAttribute("Asset"))
618 {
619 rdata.userAppearance.GlovesAsset = (UUID)xml.Value;
620 indata = true;
621 }
622 break;
623 case "UnderShirt" :
624 if (xml.MoveToAttribute("Item"))
625 {
626 rdata.userAppearance.UnderShirtItem = (UUID)xml.Value;
627 indata = true;
628 }
629 if (xml.MoveToAttribute("Asset"))
630 {
631 rdata.userAppearance.UnderShirtAsset = (UUID)xml.Value;
632 indata = true;
633 }
634 break;
635 case "UnderPants" :
636 if (xml.MoveToAttribute("Item"))
637 {
638 rdata.userAppearance.UnderPantsItem = (UUID)xml.Value;
639 indata = true;
640 }
641 if (xml.MoveToAttribute("Asset"))
642 {
643 rdata.userAppearance.UnderPantsAsset = (UUID)xml.Value;
644 indata = true;
645 }
646 break;
647 case "Skirt" :
648 if (xml.MoveToAttribute("Item"))
649 {
650 rdata.userAppearance.SkirtItem = (UUID)xml.Value;
651 indata = true;
652 }
653 if (xml.MoveToAttribute("Asset"))
654 {
655 rdata.userAppearance.SkirtAsset = (UUID)xml.Value;
656 indata = true;
657 }
658 break;
659*/
660 case "Attachment" :
661 {
662
663 int ap;
664 UUID asset;
665 UUID item;
666
667 if (xml.MoveToAttribute("AtPoint"))
668 {
669 ap = Convert.ToInt32(xml.Value);
670 if (xml.MoveToAttribute("Asset"))
671 {
672 asset = new UUID(xml.Value);
673 if (xml.MoveToAttribute("Asset"))
674 {
675 item = new UUID(xml.Value);
676 rdata.userAppearance.SetAttachment(ap, item, asset);
677 indata = true;
678 }
679 }
680 }
681 }
682 break;
683 case "Texture" :
684 if (xml.MoveToAttribute("Default"))
685 {
686 rdata.userAppearance.Texture = new Primitive.TextureEntry(new UUID(xml.Value));
687 indata = true;
688 }
689 break;
690 case "Face" :
691 {
692 uint index;
693 if (xml.MoveToAttribute("Index"))
694 {
695 index = Convert.ToUInt32(xml.Value);
696 if (xml.MoveToAttribute("Id"))
697 {
698 rdata.userAppearance.Texture.CreateFace(index).TextureID = new UUID(xml.Value);
699 indata = true;
700 }
701 }
702 }
703 break;
704 case "VisualParameters" :
705 {
706 xml.ReadContentAsBase64(rdata.userAppearance.VisualParams,
707 0, rdata.userAppearance.VisualParams.Length);
708 indata = true;
709 }
710 break;
711 }
712 break;
713 }
714 }
715
716 return indata;
717
718 }
719
720 private void FormatPart(AppearanceRequestData rdata, string part, UUID item, UUID asset)
721 {
722 if (item != UUID.Zero || asset != UUID.Zero)
723 {
724 rdata.writer.WriteStartElement(part);
725 if (item != UUID.Zero)
726 {
727 rdata.writer.WriteAttributeString("Item",item.ToString());
728 }
729
730 if (asset != UUID.Zero)
731 {
732 rdata.writer.WriteAttributeString("Asset",asset.ToString());
733 }
734 rdata.writer.WriteEndElement();
735 }
736 }
737
738 private void FormatUserAppearance(AppearanceRequestData rdata)
739 {
740
741 Rest.Log.DebugFormat("{0} FormatUserAppearance", MsgId);
742
743 if (rdata.userAppearance != null)
744 {
745
746 Rest.Log.DebugFormat("{0} FormatUserAppearance: appearance object exists", MsgId);
747 rdata.writer.WriteStartElement("Appearance");
748
749 rdata.writer.WriteAttributeString("Height", rdata.userAppearance.AvatarHeight.ToString());
750// if (rdata.userAppearance.Owner != UUID.Zero)
751// rdata.writer.WriteAttributeString("Owner", rdata.userAppearance.Owner.ToString());
752 rdata.writer.WriteAttributeString("Serial", rdata.userAppearance.Serial.ToString());
753
754/*
755 FormatPart(rdata, "Body", rdata.userAppearance.BodyItem, rdata.userAppearance.BodyAsset);
756 FormatPart(rdata, "Skin", rdata.userAppearance.SkinItem, rdata.userAppearance.SkinAsset);
757 FormatPart(rdata, "Hair", rdata.userAppearance.HairItem, rdata.userAppearance.HairAsset);
758 FormatPart(rdata, "Eyes", rdata.userAppearance.EyesItem, rdata.userAppearance.EyesAsset);
759
760 FormatPart(rdata, "Shirt", rdata.userAppearance.ShirtItem, rdata.userAppearance.ShirtAsset);
761 FormatPart(rdata, "Pants", rdata.userAppearance.PantsItem, rdata.userAppearance.PantsAsset);
762 FormatPart(rdata, "Skirt", rdata.userAppearance.SkirtItem, rdata.userAppearance.SkirtAsset);
763 FormatPart(rdata, "Shoes", rdata.userAppearance.ShoesItem, rdata.userAppearance.ShoesAsset);
764 FormatPart(rdata, "Socks", rdata.userAppearance.SocksItem, rdata.userAppearance.SocksAsset);
765
766 FormatPart(rdata, "Jacket", rdata.userAppearance.JacketItem, rdata.userAppearance.JacketAsset);
767 FormatPart(rdata, "Gloves", rdata.userAppearance.GlovesItem, rdata.userAppearance.GlovesAsset);
768
769 FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset);
770 FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset);
771*/
772 Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId);
773
774 rdata.writer.WriteStartElement("Attachments");
775 List<AvatarAttachment> attachments = rdata.userAppearance.GetAttachments();
776 foreach (AvatarAttachment attach in attachments)
777 {
778 rdata.writer.WriteStartElement("Attachment");
779 rdata.writer.WriteAttributeString("AtPoint", attach.AttachPoint.ToString());
780 rdata.writer.WriteAttributeString("Item", attach.ItemID.ToString());
781 rdata.writer.WriteAttributeString("Asset", attach.AssetID.ToString());
782 rdata.writer.WriteEndElement();
783 }
784 rdata.writer.WriteEndElement();
785
786 Primitive.TextureEntry texture = rdata.userAppearance.Texture;
787
788 if (texture != null && (texture.DefaultTexture != null || texture.FaceTextures != null))
789 {
790 Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting textures", MsgId);
791
792 rdata.writer.WriteStartElement("Texture");
793
794 if (texture.DefaultTexture != null)
795 {
796 Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting default texture", MsgId);
797 rdata.writer.WriteAttributeString("Default",
798 texture.DefaultTexture.TextureID.ToString());
799 }
800
801 if (texture.FaceTextures != null)
802 {
803
804 Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting face textures", MsgId);
805
806 for (int i=0; i<texture.FaceTextures.Length;i++)
807 {
808 if (texture.FaceTextures[i] != null)
809 {
810 rdata.writer.WriteStartElement("Face");
811 rdata.writer.WriteAttributeString("Index", i.ToString());
812 rdata.writer.WriteAttributeString("Id",
813 texture.FaceTextures[i].TextureID.ToString());
814 rdata.writer.WriteEndElement();
815 }
816 }
817 }
818
819 rdata.writer.WriteEndElement();
820 }
821
822 Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting visual parameters", MsgId);
823
824 rdata.writer.WriteStartElement("VisualParameters");
825 rdata.writer.WriteBase64(rdata.userAppearance.VisualParams,0,
826 rdata.userAppearance.VisualParams.Length);
827 rdata.writer.WriteEndElement();
828 rdata.writer.WriteFullEndElement();
829 }
830
831 Rest.Log.DebugFormat("{0} FormatUserAppearance: completed", MsgId);
832
833 return;
834 }
835
836 #region appearance RequestData extension
837
838 internal class AppearanceRequestData : RequestData
839 {
840
841 /// <summary>
842 /// These are the inventory specific request/response state
843 /// extensions.
844 /// </summary>
845
846 internal UUID uuid = UUID.Zero;
847 internal UserProfileData userProfile = null;
848 internal AvatarAppearance userAppearance = null;
849
850 internal AppearanceRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
851 : base(request, response, prefix)
852 {
853 }
854
855 }
856
857 #endregion Appearance RequestData extension
858
859 }
860}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
deleted file mode 100644
index 4ba3d77..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
+++ /dev/null
@@ -1,383 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Xml;
30using OpenMetaverse;
31using OpenSim.Framework;
32using OpenSim.Framework.Servers;
33using OpenSim.Framework.Servers.HttpServer;
34
35namespace OpenSim.ApplicationPlugins.Rest.Inventory
36{
37 public class RestAssetServices : IRest
38 {
39 private bool enabled = false;
40 private string qPrefix = "assets";
41
42 // A simple constructor is used to handle any once-only
43 // initialization of working classes.
44
45 public RestAssetServices()
46 {
47 Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
48 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
49
50 // If the handler specifies a relative path for its domain
51 // then we must add the standard absolute prefix, e.g. /admin
52
53 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
54 {
55 Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix);
56 qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
57 Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix);
58 }
59
60 // Register interface using the fully-qualified prefix
61
62 Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate);
63
64 // Activate if all went OK
65
66 enabled = true;
67
68 Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId);
69 }
70
71 // Post-construction, pre-enabled initialization opportunity
72 // Not currently exploited.
73
74 public void Initialize()
75 {
76 }
77
78 // Called by the plug-in to halt REST processing. Local processing is
79 // disabled, and control blocks until all current processing has
80 // completed. No new processing will be started
81
82 public void Close()
83 {
84 enabled = false;
85 Rest.Log.InfoFormat("{0} Asset services ({1}) closing down", MsgId, qPrefix);
86 }
87
88 // Properties
89
90 internal string MsgId
91 {
92 get { return Rest.MsgId; }
93 }
94
95 #region Interface
96
97 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
98 {
99 return (RequestData) new AssetRequestData(request, response, prefix);
100 }
101
102 // Asset Handler
103
104 private void DoAsset(RequestData rparm)
105 {
106 if (!enabled) return;
107
108 AssetRequestData rdata = (AssetRequestData) rparm;
109
110 Rest.Log.DebugFormat("{0} REST Asset handler ({1}) ENTRY", MsgId, qPrefix);
111
112 // Now that we know this is a serious attempt to
113 // access inventory data, we should find out who
114 // is asking, and make sure they are authorized
115 // to do so. We need to validate the caller's
116 // identity before revealing anything about the
117 // status quo. Authenticate throws an exception
118 // via Fail if no identity information is present.
119 //
120 // With the present HTTP server we can't use the
121 // builtin authentication mechanisms because they
122 // would be enforced for all in-bound requests.
123 // Instead we look at the headers ourselves and
124 // handle authentication directly.
125
126 try
127 {
128 if (!rdata.IsAuthenticated)
129 {
130 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
131 }
132 }
133 catch (RestException e)
134 {
135 if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
136 {
137 Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
138 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
139 rdata.request.Headers.Get("Authorization"));
140 }
141 else
142 {
143 Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
144 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
145 rdata.request.Headers.Get("Authorization"));
146 }
147 throw (e);
148 }
149
150 // Remove the prefix and what's left are the parameters. If we don't have
151 // the parameters we need, fail the request. Parameters do NOT include
152 // any supplied query values.
153
154 if (rdata.Parameters.Length > 0)
155 {
156 switch (rdata.method)
157 {
158 case "get" :
159 DoGet(rdata);
160 break;
161 case "put" :
162 DoPut(rdata);
163 break;
164 case "post" :
165 DoPost(rdata);
166 break;
167 case "delete" :
168 default :
169 Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}",
170 MsgId, rdata.method);
171 rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
172 break;
173 }
174 }
175 else
176 {
177 Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId);
178 rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
179 }
180
181 Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId);
182 }
183
184 #endregion Interface
185
186 /// <summary>
187 /// The only parameter we recognize is a UUID.If an asset with this identification is
188 /// found, it's content, base-64 encoded, is returned to the client.
189 /// </summary>
190
191 private void DoGet(AssetRequestData rdata)
192 {
193 Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
194
195 if (rdata.Parameters.Length == 1)
196 {
197 UUID uuid = new UUID(rdata.Parameters[0]);
198 AssetBase asset = Rest.AssetServices.Get(uuid.ToString());
199
200 if (asset != null)
201 {
202 Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]);
203
204 rdata.initXmlWriter();
205
206 rdata.writer.WriteStartElement(String.Empty,"Asset",String.Empty);
207
208 rdata.writer.WriteAttributeString("id", asset.ID);
209 rdata.writer.WriteAttributeString("name", asset.Name);
210 rdata.writer.WriteAttributeString("desc", asset.Description);
211 rdata.writer.WriteAttributeString("type", asset.Type.ToString());
212 rdata.writer.WriteAttributeString("local", asset.Local.ToString());
213 rdata.writer.WriteAttributeString("temporary", asset.Temporary.ToString());
214
215 rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length);
216
217 rdata.writer.WriteFullEndElement();
218
219 }
220 else
221 {
222 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
223 rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
224 }
225 }
226
227 rdata.Complete();
228 rdata.Respond(String.Format("Asset <{0}> : Normal completion", rdata.method));
229
230 }
231
232 /// <summary>
233 /// UPDATE existing item, if it exists. URI identifies the item in question.
234 /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded)
235 /// is decoded and stored in the database, identified by the supplied UUID.
236 /// </summary>
237 private void DoPut(AssetRequestData rdata)
238 {
239 bool modified = false;
240 bool created = false;
241
242 AssetBase asset = null;
243
244 Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
245
246 if (rdata.Parameters.Length == 1)
247 {
248
249 rdata.initXmlReader();
250 XmlReader xml = rdata.reader;
251
252 if (!xml.ReadToFollowing("Asset"))
253 {
254 Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
255 rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
256 }
257
258 UUID uuid = new UUID(rdata.Parameters[0]);
259 asset = Rest.AssetServices.Get(uuid.ToString());
260
261 modified = (asset != null);
262 created = !modified;
263
264 asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString());
265 asset.Description = xml.GetAttribute("desc");
266 asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0;
267 asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0;
268 asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", ""));
269
270 if (asset.ID != rdata.Parameters[0])
271 {
272 Rest.Log.WarnFormat("{0} URI and payload disagree on UUID U:{1} vs P:{2}",
273 MsgId, rdata.Parameters[0], asset.ID);
274 }
275
276 Rest.AssetServices.Store(asset);
277
278 }
279 else
280 {
281 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
282 rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
283 }
284
285 if (created)
286 {
287 rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
288 rdata.Complete(Rest.HttpStatusCodeCreated);
289 }
290 else
291 {
292 if (modified)
293 {
294 rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
295 rdata.Complete(Rest.HttpStatusCodeOK);
296 }
297 else
298 {
299 rdata.Complete(Rest.HttpStatusCodeNoContent);
300 }
301 }
302
303 rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method));
304
305 }
306
307 /// <summary>
308 /// CREATE new item, replace if it exists. URI identifies the context for the item in question.
309 /// No parameters are required for POST, just thepayload.
310 /// </summary>
311
312 private void DoPost(AssetRequestData rdata)
313 {
314
315 bool modified = false;
316 bool created = false;
317
318 Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
319
320 if (rdata.Parameters.Length != 0)
321 {
322 Rest.Log.WarnFormat("{0} Parameters ignored <{1}>", MsgId, rdata.path);
323 Rest.Log.InfoFormat("{0} POST of an asset has no parameters", MsgId, rdata.path);
324 }
325
326 rdata.initXmlReader();
327 XmlReader xml = rdata.reader;
328
329 if (!xml.ReadToFollowing("Asset"))
330 {
331 Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
332 rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
333 }
334
335 UUID uuid = new UUID(xml.GetAttribute("id"));
336 AssetBase asset = Rest.AssetServices.Get(uuid.ToString());
337
338 modified = (asset != null);
339 created = !modified;
340
341 asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString());
342 asset.Description = xml.GetAttribute("desc");
343 asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0;
344 asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0;
345 asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", ""));
346
347 Rest.AssetServices.Store(asset);
348
349 if (created)
350 {
351 rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
352 rdata.Complete(Rest.HttpStatusCodeCreated);
353 }
354 else
355 {
356 if (modified)
357 {
358 rdata.appendStatus(String.Format("<p> Modified asset {0}, UUID {1} <p>", asset.Name, asset.FullID));
359 rdata.Complete(Rest.HttpStatusCodeOK);
360 }
361 else
362 {
363 rdata.Complete(Rest.HttpStatusCodeNoContent);
364 }
365 }
366
367 rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method));
368
369 }
370
371 /// <summary>
372 /// Asset processing has no special data area requirements.
373 /// </summary>
374
375 internal class AssetRequestData : RequestData
376 {
377 internal AssetRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
378 : base(request, response, prefix)
379 {
380 }
381 }
382 }
383}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs
deleted file mode 100644
index e79d2bd..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs
+++ /dev/null
@@ -1,448 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Xml;
30using System.IO;
31using OpenMetaverse;
32using OpenSim.Framework;
33using OpenSim.Framework.Servers;
34using OpenSim.Framework.Servers.HttpServer;
35
36namespace OpenSim.ApplicationPlugins.Rest.Inventory
37{
38 public class RestFileServices : IRest
39 {
40 private bool enabled = false;
41 private string qPrefix = "files";
42
43 // A simple constructor is used to handle any once-only
44 // initialization of working classes.
45
46 public RestFileServices()
47 {
48 Rest.Log.InfoFormat("{0} File services initializing", MsgId);
49 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
50
51 // If the handler specifies a relative path for its domain
52 // then we must add the standard absolute prefix, e.g. /admin
53
54 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
55 {
56 Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix);
57 qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
58 Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix);
59 }
60
61 // Register interface using the fully-qualified prefix
62
63 Rest.Plugin.AddPathHandler(DoFile, qPrefix, Allocate);
64
65 // Activate if all went OK
66
67 enabled = true;
68
69 Rest.Log.InfoFormat("{0} File services initialization complete", MsgId);
70 }
71
72 // Post-construction, pre-enabled initialization opportunity
73 // Not currently exploited.
74
75 public void Initialize()
76 {
77 }
78
79 // Called by the plug-in to halt REST processing. Local processing is
80 // disabled, and control blocks until all current processing has
81 // completed. No new processing will be started
82
83 public void Close()
84 {
85 enabled = false;
86 Rest.Log.InfoFormat("{0} File services ({1}) closing down", MsgId, qPrefix);
87 }
88
89 // Properties
90
91 internal string MsgId
92 {
93 get { return Rest.MsgId; }
94 }
95
96 #region Interface
97
98 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
99 {
100 return (RequestData) new FileRequestData(request, response, prefix);
101 }
102
103 // Asset Handler
104
105 private void DoFile(RequestData rparm)
106 {
107 if (!enabled) return;
108
109 FileRequestData rdata = (FileRequestData) rparm;
110
111 Rest.Log.DebugFormat("{0} REST File handler ({1}) ENTRY", MsgId, qPrefix);
112
113 // Now that we know this is a serious attempt to
114 // access file data, we should find out who
115 // is asking, and make sure they are authorized
116 // to do so. We need to validate the caller's
117 // identity before revealing anything about the
118 // status quo. Authenticate throws an exception
119 // via Fail if no identity information is present.
120 //
121 // With the present HTTP server we can't use the
122 // builtin authentication mechanisms because they
123 // would be enforced for all in-bound requests.
124 // Instead we look at the headers ourselves and
125 // handle authentication directly.
126
127 try
128 {
129 if (!rdata.IsAuthenticated)
130 {
131 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
132 }
133 }
134 catch (RestException e)
135 {
136 if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
137 {
138 Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
139 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
140 rdata.request.Headers.Get("Authorization"));
141 }
142 else
143 {
144 Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
145 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
146 rdata.request.Headers.Get("Authorization"));
147 }
148 throw (e);
149 }
150
151 // Remove the prefix and what's left are the parameters. If we don't have
152 // the parameters we need, fail the request. Parameters do NOT include
153 // any supplied query values.
154
155 if (rdata.Parameters.Length > 0)
156 {
157 switch (rdata.method)
158 {
159 case "get" :
160 DoGet(rdata);
161 break;
162 case "put" :
163 DoPut(rdata);
164 break;
165 case "post" :
166 DoPost(rdata);
167 break;
168 case "delete" :
169 DoDelete(rdata);
170 break;
171 default :
172 Rest.Log.WarnFormat("{0} File: Method not supported: {1}",
173 MsgId, rdata.method);
174 rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
175 break;
176 }
177 }
178 else
179 {
180 Rest.Log.WarnFormat("{0} File: No agent information provided", MsgId);
181 rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
182 }
183
184 Rest.Log.DebugFormat("{0} REST File handler EXIT", MsgId);
185
186 }
187
188 #endregion Interface
189
190 /// <summary>
191 /// The only parameter we recognize is a UUID.If an asset with this identification is
192 /// found, it's content, base-64 encoded, is returned to the client.
193 /// </summary>
194
195 private void DoGet(FileRequestData rdata)
196 {
197
198 string path = String.Empty;
199
200 Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
201
202 if (rdata.Parameters.Length > 1)
203 {
204 try
205 {
206 path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
207 if (File.Exists(path))
208 {
209 Rest.Log.DebugFormat("{0} File located <{1}>", MsgId, path);
210 Byte[] data = File.ReadAllBytes(path);
211 rdata.initXmlWriter();
212 rdata.writer.WriteStartElement(String.Empty,"File",String.Empty);
213 rdata.writer.WriteAttributeString("name", path);
214 rdata.writer.WriteBase64(data,0,data.Length);
215 rdata.writer.WriteFullEndElement();
216 }
217 else
218 {
219 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, path);
220 rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0}", path));
221 }
222 }
223 catch (Exception e)
224 {
225 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, e.Message);
226 rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
227 path, e.Message));
228 }
229 }
230
231 rdata.Complete();
232 rdata.Respond(String.Format("File <{0}> : Normal completion", rdata.method));
233
234 }
235
236 /// <summary>
237 /// UPDATE existing item, if it exists. URI identifies the item in question.
238 /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded)
239 /// is decoded and stored in the database, identified by the supplied UUID.
240 /// </summary>
241 private void DoPut(FileRequestData rdata)
242 {
243 bool modified = false;
244 bool created = false;
245 string path = String.Empty;
246
247 Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
248
249 if (rdata.Parameters.Length > 1)
250 {
251 try
252 {
253 path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
254 bool maymod = File.Exists(path);
255
256 rdata.initXmlReader();
257 XmlReader xml = rdata.reader;
258
259 if (!xml.ReadToFollowing("File"))
260 {
261 Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
262 rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
263 }
264
265 Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
266
267 File.WriteAllBytes(path,data);
268 modified = maymod;
269 created = ! maymod;
270 }
271 catch (Exception e)
272 {
273 Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
274 e.Message);
275 }
276 }
277 else
278 {
279 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
280 rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
281 }
282
283 if (created)
284 {
285 rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
286 rdata.Complete(Rest.HttpStatusCodeCreated);
287 }
288 else
289 {
290 if (modified)
291 {
292 rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
293 rdata.Complete(Rest.HttpStatusCodeOK);
294 }
295 else
296 {
297 rdata.Complete(Rest.HttpStatusCodeNoContent);
298 }
299 }
300
301 rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
302
303 }
304
305 /// <summary>
306 /// CREATE new item, replace if it exists. URI identifies the context for the item in question.
307 /// No parameters are required for POST, just thepayload.
308 /// </summary>
309
310 private void DoPost(FileRequestData rdata)
311 {
312
313 bool modified = false;
314 bool created = false;
315 string path = String.Empty;
316
317 Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
318
319 if (rdata.Parameters.Length > 1)
320 {
321 try
322 {
323 path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
324 bool maymod = File.Exists(path);
325
326 rdata.initXmlReader();
327 XmlReader xml = rdata.reader;
328
329 if (!xml.ReadToFollowing("File"))
330 {
331 Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
332 rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
333 }
334
335 Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
336
337 File.WriteAllBytes(path,data);
338 modified = maymod;
339 created = ! maymod;
340 }
341 catch (Exception e)
342 {
343 Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
344 e.Message);
345 }
346 }
347 else
348 {
349 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
350 rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
351 }
352
353 if (created)
354 {
355 rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
356 rdata.Complete(Rest.HttpStatusCodeCreated);
357 }
358 else
359 {
360 if (modified)
361 {
362 rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
363 rdata.Complete(Rest.HttpStatusCodeOK);
364 }
365 else
366 {
367 rdata.Complete(Rest.HttpStatusCodeNoContent);
368 }
369 }
370
371 rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
372
373 }
374
375 /// <summary>
376 /// CREATE new item, replace if it exists. URI identifies the context for the item in question.
377 /// No parameters are required for POST, just thepayload.
378 /// </summary>
379
380 private void DoDelete(FileRequestData rdata)
381 {
382
383 bool modified = false;
384 bool created = false;
385 string path = String.Empty;
386
387 Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
388
389 if (rdata.Parameters.Length > 1)
390 {
391 try
392 {
393 path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
394
395 if (File.Exists(path))
396 {
397 File.Delete(path);
398 }
399 }
400 catch (Exception e)
401 {
402 Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
403 e.Message);
404 rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
405 path, e.Message));
406 }
407 }
408 else
409 {
410 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
411 rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
412 }
413
414 if (created)
415 {
416 rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
417 rdata.Complete(Rest.HttpStatusCodeCreated);
418 }
419 else
420 {
421 if (modified)
422 {
423 rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
424 rdata.Complete(Rest.HttpStatusCodeOK);
425 }
426 else
427 {
428 rdata.Complete(Rest.HttpStatusCodeNoContent);
429 }
430 }
431
432 rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
433
434 }
435
436 /// <summary>
437 /// File processing has no special data area requirements.
438 /// </summary>
439
440 internal class FileRequestData : RequestData
441 {
442 internal FileRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
443 : base(request, response, prefix)
444 {
445 }
446 }
447 }
448}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
deleted file mode 100644
index 072bd6f..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
+++ /dev/null
@@ -1,662 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Reflection;
31using OpenSim.Framework.Servers;
32using OpenSim.Framework.Servers.HttpServer;
33
34namespace OpenSim.ApplicationPlugins.Rest.Inventory
35{
36 /// <remarks>
37 /// The class signature reveals the roles that RestHandler plays.
38 ///
39 /// [1] It is a sub-class of RestPlugin. It inherits and extends
40 /// the functionality of this class, constraining it to the
41 /// specific needs of this REST implementation. This relates
42 /// to the plug-in mechanism supported by OpenSim, the specifics
43 /// of which are mostly hidden by RestPlugin.
44 /// [2] IRestHandler describes the interface that this class
45 /// exports to service implementations. This is the services
46 /// management interface.
47 /// [3] IHttpAgentHandler describes the interface that is exported
48 /// to the BaseHttpServer in support of this particular HTTP
49 /// processing model. This is the request interface of the
50 /// handler.
51 /// </remarks>
52
53 public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler
54 {
55 // Handler tables: both stream and REST are supported. The path handlers and their
56 // respective allocators are stored in separate tables.
57
58 internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
59 internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
60 internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
61
62 #region local static state
63
64 private static bool handlersLoaded = false;
65 private static List<Type> classes = new List<Type>();
66 private static List<IRest> handlers = new List<IRest>();
67 private static Type[] parms = new Type[0];
68 private static Object[] args = new Object[0];
69
70 /// <summary>
71 /// This static initializer scans the ASSEMBLY for classes that
72 /// export the IRest interface and builds a list of them. These
73 /// are later activated by the handler. To add a new handler it
74 /// is only necessary to create a new services class that implements
75 /// the IRest interface, and recompile the handler. This gives
76 /// all of the build-time flexibility of a modular approach
77 /// while not introducing yet-another module loader. Note that
78 /// multiple assembles can still be built, each with its own set
79 /// of handlers. Examples of services classes are RestInventoryServices
80 /// and RestSkeleton.
81 /// </summary>
82
83 static RestHandler()
84 {
85 Module[] mods = Assembly.GetExecutingAssembly().GetModules();
86
87 foreach (Module m in mods)
88 {
89 Type[] types = m.GetTypes();
90 foreach (Type t in types)
91 {
92 try
93 {
94 if (t.GetInterface("IRest") != null)
95 {
96 classes.Add(t);
97 }
98 }
99 catch (Exception)
100 {
101 Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t);
102 Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t);
103 }
104 }
105 }
106 }
107
108 #endregion local static state
109
110 #region local instance state
111
112 /// <summary>
113 /// This routine loads all of the handlers discovered during
114 /// instance initialization.
115 /// A table of all loaded and successfully constructed handlers
116 /// is built, and this table is then used by the constructor to
117 /// initialize each of the handlers in turn.
118 /// NOTE: The loading process does not automatically imply that
119 /// the handler has registered any kind of an interface, that
120 /// may be (optionally) done by the handler either during
121 /// construction, or during initialization.
122 ///
123 /// I was not able to make this code work within a constructor
124 /// so it is isolated within this method.
125 /// </summary>
126
127 private void LoadHandlers()
128 {
129 lock (handlers)
130 {
131 if (!handlersLoaded)
132 {
133 ConstructorInfo ci;
134 Object ht;
135
136 foreach (Type t in classes)
137 {
138 try
139 {
140 ci = t.GetConstructor(parms);
141 ht = ci.Invoke(args);
142 handlers.Add((IRest)ht);
143 }
144 catch (Exception e)
145 {
146 Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message);
147 }
148 }
149 handlersLoaded = true;
150 }
151 }
152 }
153
154 #endregion local instance state
155
156 #region overriding properties
157
158 // These properties override definitions
159 // in the base class.
160
161 // Name is used to differentiate the message header.
162
163 public override string Name
164 {
165 get { return "HANDLER"; }
166 }
167
168 // Used to partition the .ini configuration space.
169
170 public override string ConfigName
171 {
172 get { return "RestHandler"; }
173 }
174
175 // We have to rename these because we want
176 // to be able to share the values with other
177 // classes in our assembly and the base
178 // names are protected.
179
180 public string MsgId
181 {
182 get { return base.MsgID; }
183 }
184
185 public string RequestId
186 {
187 get { return base.RequestID; }
188 }
189
190 #endregion overriding properties
191
192 #region overriding methods
193
194 /// <summary>
195 /// This method is called by OpenSimMain immediately after loading the
196 /// plugin and after basic server setup, but before running any server commands.
197 /// </summary>
198 /// <remarks>
199 /// Note that entries MUST be added to the active configuration files before
200 /// the plugin can be enabled.
201 /// </remarks>
202
203 public override void Initialise(OpenSimBase openSim)
204 {
205 try
206 {
207 // This plugin will only be enabled if the broader
208 // REST plugin mechanism is enabled.
209
210 //Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId);
211
212 base.Initialise(openSim);
213
214 // IsEnabled is implemented by the base class and
215 // reflects an overall RestPlugin status
216
217 if (!IsEnabled)
218 {
219 //Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
220 return;
221 }
222
223 Rest.Log.InfoFormat("{0} Rest <{1}> plugin will be enabled", MsgId, Name);
224 Rest.Log.InfoFormat("{0} Configuration parameters read from <{1}>", MsgId, ConfigName);
225
226 // These are stored in static variables to make
227 // them easy to reach from anywhere in the assembly.
228
229 Rest.main = openSim;
230 if (Rest.main == null)
231 throw new Exception("OpenSim base pointer is null");
232
233 Rest.Plugin = this;
234 Rest.Config = Config;
235 Rest.Prefix = Prefix;
236 Rest.GodKey = GodKey;
237 Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate);
238 Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme);
239 Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure);
240 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape);
241 Rest.Realm = Rest.Config.GetString("realm", Rest.Realm);
242 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset);
243 Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill);
244 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize);
245 Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled);
246
247 // Note: Odd spacing is required in the following strings
248
249 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
250 (Rest.Authenticate ? "" : "not "));
251
252 Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId,
253 (Rest.Secure ? "" : "not "));
254
255 Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId,
256 (Rest.ExtendedEscape ? "" : "not "));
257
258 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
259 (Rest.DumpAsset ? "" : "not "));
260
261 // The supplied prefix MUST be absolute
262
263 if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator)
264 {
265 Rest.Log.WarnFormat("{0} Prefix <{1}> is not absolute and must be", MsgId, Rest.Prefix);
266 Rest.Log.InfoFormat("{0} Prefix changed to </{1}>", MsgId, Rest.Prefix);
267 Rest.Prefix = String.Format("{0}{1}", Rest.UrlPathSeparator, Rest.Prefix);
268 }
269
270 // If data dumping is requested, report on the chosen line
271 // length.
272
273 if (Rest.DumpAsset)
274 {
275 Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.DumpLineSize);
276 }
277
278 // Load all of the handlers present in the
279 // assembly
280
281 // In principle, as we're an application plug-in,
282 // most of what needs to be done could be done using
283 // static resources, however the Open Sim plug-in
284 // model makes this an instance, so that's what we
285 // need to be.
286 // There is only one Communications manager per
287 // server, and by inference, only one each of the
288 // user, asset, and inventory servers. So we can cache
289 // those using a static initializer.
290 // We move all of this processing off to another
291 // services class to minimize overlap between function
292 // and infrastructure.
293
294 LoadHandlers();
295
296 // The intention of a post construction initializer
297 // is to allow for setup that is dependent upon other
298 // activities outside of the agency.
299
300 foreach (IRest handler in handlers)
301 {
302 try
303 {
304 handler.Initialize();
305 }
306 catch (Exception e)
307 {
308 Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message);
309 }
310 }
311
312 // Now that everything is setup we can proceed to
313 // add THIS agent to the HTTP server's handler list
314
315 // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will
316 // have to be handled through the AddHttpHandler interface.
317// if (!AddAgentHandler(Rest.Name,this))
318// {
319// Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId);
320// foreach (IRest handler in handlers)
321// {
322// handler.Close();
323// }
324// }
325
326 }
327 catch (Exception e)
328 {
329 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message);
330 }
331 }
332
333 /// <summary>
334 /// In the interests of efficiency, and because we cannot determine whether
335 /// or not this instance will actually be harvested, we clobber the only
336 /// anchoring reference to the working state for this plug-in. What the
337 /// call to close does is irrelevant to this class beyond knowing that it
338 /// can nullify the reference when it returns.
339 /// To make sure everything is copacetic we make sure the primary interface
340 /// is disabled by deleting the handler from the HTTP server tables.
341 /// </summary>
342
343 public override void Close()
344 {
345 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
346
347 // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will
348 // have to be handled through the AddHttpHandler interface.
349// try
350// {
351// RemoveAgentHandler(Rest.Name, this);
352// }
353// catch (KeyNotFoundException){}
354
355 foreach (IRest handler in handlers)
356 {
357 handler.Close();
358 }
359 }
360
361 #endregion overriding methods
362
363 #region interface methods
364
365 /// <summary>
366 /// This method is called by the HTTP server to match an incoming
367 /// request. It scans all of the strings registered by the
368 /// underlying handlers and looks for the best match. It returns
369 /// true if a match is found.
370 /// The matching process could be made arbitrarily complex.
371 /// Note: The match is case-insensitive.
372 /// </summary>
373
374 public bool Match(OSHttpRequest request, OSHttpResponse response)
375 {
376
377 string path = request.RawUrl.ToLower();
378
379 // Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
380
381 try
382 {
383 foreach (string key in pathHandlers.Keys)
384 {
385 // Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
386
387 // Note that Match will not necessarily find the handler that will
388 // actually be used - it does no test for the "closest" fit. It
389 // simply reflects that at least one possible handler exists.
390
391 if (path.StartsWith(key))
392 {
393 // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
394
395 // This apparently odd evaluation is needed to prevent a match
396 // on anything other than a URI token boundary. Otherwise we
397 // may match on URL's that were not intended for this handler.
398
399 return (path.Length == key.Length ||
400 path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
401 }
402 }
403
404 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
405
406 foreach (string key in streamHandlers.Keys)
407 {
408 // Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
409
410 // Note that Match will not necessarily find the handler that will
411 // actually be used - it does no test for the "closest" fit. It
412 // simply reflects that at least one possible handler exists.
413
414 if (path.StartsWith(key))
415 {
416 // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
417
418 // This apparently odd evaluation is needed to prevent a match
419 // on anything other than a URI token boundary. Otherwise we
420 // may match on URL's that were not intended for this handler.
421
422 return (path.Length == key.Length ||
423 path.Substring(key.Length, 1) == Rest.UrlPathSeparator);
424 }
425 }
426 }
427 catch (Exception e)
428 {
429 Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message);
430 }
431
432 return false;
433 }
434
435 /// <summary>
436 /// This is called by the HTTP server once the handler has indicated
437 /// that it is able to handle the request.
438 /// Preconditions:
439 /// [1] request != null and is a valid request object
440 /// [2] response != null and is a valid response object
441 /// Behavior is undefined if preconditions are not satisfied.
442 /// </summary>
443
444 public bool Handle(OSHttpRequest request, OSHttpResponse response)
445 {
446 bool handled;
447 base.MsgID = base.RequestID;
448
449 // Debug only
450
451 if (Rest.DEBUG)
452 {
453 Rest.Log.DebugFormat("{0} ENTRY", MsgId);
454 Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent);
455 Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod);
456
457 for (int i = 0; i < request.Headers.Count; i++)
458 {
459 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
460 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
461 }
462 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
463 }
464
465 // If a path handler worked we're done, otherwise try any
466 // available stream handlers too.
467
468 try
469 {
470 handled = (FindPathHandler(request, response) ||
471 FindStreamHandler(request, response));
472 }
473 catch (Exception e)
474 {
475 // A raw exception indicates that something we weren't expecting has
476 // happened. This should always reflect a shortcoming in the plugin,
477 // or a failure to satisfy the preconditions. It should not reflect
478 // an error in the request itself. Under such circumstances the state
479 // of the request cannot be determined and we are obliged to mark it
480 // as 'handled'.
481
482 Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message);
483 handled = true;
484 }
485
486 Rest.Log.DebugFormat("{0} EXIT", MsgId);
487
488 return handled;
489 }
490
491 #endregion interface methods
492
493 /// <summary>
494 /// If there is a stream handler registered that can handle the
495 /// request, then fine. If the request is not matched, do
496 /// nothing.
497 /// Note: The selection is case-insensitive
498 /// </summary>
499
500 private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response)
501 {
502 RequestData rdata = new RequestData(request, response, String.Empty);
503
504 string bestMatch = String.Empty;
505 string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower();
506
507 Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
508
509 if (!IsEnabled)
510 {
511 return false;
512 }
513
514 foreach (string pattern in streamHandlers.Keys)
515 {
516 if (path.StartsWith(pattern))
517 {
518 if (pattern.Length > bestMatch.Length)
519 {
520 bestMatch = pattern;
521 }
522 }
523 }
524
525 // Handle using the best match available
526
527 if (bestMatch.Length > 0)
528 {
529 Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch);
530 RestStreamHandler handler = streamHandlers[bestMatch];
531 rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response);
532 rdata.AddHeader(rdata.response.ContentType,handler.ContentType);
533 rdata.Respond("FindStreamHandler Completion");
534 }
535
536 return rdata.handled;
537 }
538
539 /// <summary>
540 /// Add a stream handler for the designated HTTP method and path prefix.
541 /// If the handler is not enabled, the request is ignored. If the path
542 /// does not start with the REST prefix, it is added. If method-qualified
543 /// path has not already been registered, the method is added to the active
544 /// handler table.
545 /// </summary>
546 public void AddStreamHandler(string httpMethod, string path, RestMethod method)
547 {
548 if (!IsEnabled)
549 {
550 return;
551 }
552
553 if (!path.StartsWith(Rest.Prefix))
554 {
555 path = String.Format("{0}{1}", Rest.Prefix, path);
556 }
557
558 path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path);
559
560 // Conditionally add to the list
561
562 if (!streamHandlers.ContainsKey(path))
563 {
564 streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
565 Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path);
566 }
567 else
568 {
569 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path);
570 }
571 }
572
573 /// <summary>
574 /// Given the supplied request/response, if the handler is enabled, the inbound
575 /// information is used to match an entry in the active path handler tables, using
576 /// the method-qualified path information. If a match is found, then the handler is
577 /// invoked. The result is the boolean result of the handler, or false if no
578 /// handler was located. The boolean indicates whether or not the request has been
579 /// handled, not whether or not the request was successful - that information is in
580 /// the response.
581 /// Note: The selection process is case-insensitive
582 /// </summary>
583
584 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
585 {
586 RequestData rdata = null;
587 string bestMatch = null;
588
589 if (!IsEnabled)
590 {
591 return false;
592 }
593
594 // Conditionally add to the list
595
596 Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl);
597
598 foreach (string pattern in pathHandlers.Keys)
599 {
600 if (request.RawUrl.ToLower().StartsWith(pattern))
601 {
602 if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length)
603 {
604 bestMatch = pattern;
605 }
606 }
607 }
608
609 if (!String.IsNullOrEmpty(bestMatch))
610 {
611 rdata = pathAllocators[bestMatch](request, response, bestMatch);
612
613 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
614
615 try
616 {
617 pathHandlers[bestMatch](rdata);
618 }
619
620 // A plugin generated error indicates a request-related error
621 // that has been handled by the plugin.
622
623 catch (RestException r)
624 {
625 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
626 }
627 }
628
629 return (rdata == null) ? false : rdata.handled;
630 }
631
632 /// <summary>
633 /// A method handler and a request allocator are stored using the designated
634 /// path as a key. If an entry already exists, it is replaced by the new one.
635 /// </summary>
636
637 public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
638 {
639 if (!IsEnabled)
640 {
641 return;
642 }
643
644 if (pathHandlers.ContainsKey(path))
645 {
646 Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
647 pathHandlers.Remove(path);
648 }
649
650 if (pathAllocators.ContainsKey(path))
651 {
652 Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path);
653 pathAllocators.Remove(path);
654 }
655
656 Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path);
657
658 pathHandlers.Add(path, mh);
659 pathAllocators.Add(path, ra);
660 }
661 }
662}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
deleted file mode 100644
index 536f167..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
+++ /dev/null
@@ -1,2343 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Drawing;
31using System.Globalization;
32using System.IO;
33using System.Threading;
34using System.Timers;
35using System.Xml;
36using OpenMetaverse;
37using OpenMetaverse.Imaging;
38using OpenSim.Framework;
39
40using OpenSim.Framework.Servers;
41using OpenSim.Framework.Servers.HttpServer;
42using Timer=System.Timers.Timer;
43
44namespace OpenSim.ApplicationPlugins.Rest.Inventory
45{
46 public class RestInventoryServices : IRest
47 {
48// private static readonly int PARM_USERID = 0;
49// private static readonly int PARM_PATH = 1;
50
51// private bool enabled = false;
52 private string qPrefix = "inventory";
53
54// private static readonly string PRIVATE_ROOT_NAME = "My Inventory";
55
56 /// <summary>
57 /// The constructor makes sure that the service prefix is absolute
58 /// and the registers the service handler and the allocator.
59 /// </summary>
60
61 public RestInventoryServices()
62 {
63 Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId);
64 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
65
66 // If a relative path was specified for the handler's domain,
67 // add the standard prefix to make it absolute, e.g. /admin
68
69 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
70 {
71 Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
72 qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
73 Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
74 }
75
76 // Register interface using the absolute URI.
77
78 Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate);
79
80 // Activate if everything went OK
81
82// enabled = true;
83
84 Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId);
85 }
86
87 /// <summary>
88 /// Post-construction, pre-enabled initialization opportunity
89 /// Not currently exploited.
90 /// </summary>
91
92 public void Initialize()
93 {
94 }
95
96 /// <summary>
97 /// Called by the plug-in to halt service processing. Local processing is
98 /// disabled.
99 /// </summary>
100
101 public void Close()
102 {
103// enabled = false;
104 Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId);
105 }
106
107 /// <summary>
108 /// This property is declared locally because it is used a lot and
109 /// brevity is nice.
110 /// </summary>
111 internal string MsgId
112 {
113 get { return Rest.MsgId; }
114 }
115
116 #region Interface
117
118 /// <summary>
119 /// The plugin (RestHandler) calls this method to allocate the request
120 /// state carrier for a new request. It is destroyed when the request
121 /// completes. All request-instance specific state is kept here. This
122 /// is registered when this service provider is registered.
123 /// </summary>
124 /// <param name=request>Inbound HTTP request information</param>
125 /// <param name=response>Outbound HTTP request information</param>
126 /// <param name=qPrefix>REST service domain prefix</param>
127 /// <returns>A RequestData instance suitable for this service</returns>
128 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
129 {
130 return (RequestData) new InventoryRequestData(request, response, prefix);
131 }
132
133 /// <summary>
134 /// This method is registered with the handler when this service provider
135 /// is initialized. It is called whenever the plug-in identifies this service
136 /// provider as the best match for a given request.
137 /// It handles all aspects of inventory REST processing, i.e. /admin/inventory
138 /// </summary>
139 /// <param name=hdata>A consolidated HTTP request work area</param>
140 private void DoInventory(RequestData hdata)
141 {
142// InventoryRequestData rdata = (InventoryRequestData) hdata;
143
144 Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId);
145
146 // !!! REFACTORING PROBLEM
147
148 //// If we're disabled, do nothing.
149
150 //if (!enabled)
151 //{
152 // return;
153 //}
154
155 //// Now that we know this is a serious attempt to
156 //// access inventory data, we should find out who
157 //// is asking, and make sure they are authorized
158 //// to do so. We need to validate the caller's
159 //// identity before revealing anything about the
160 //// status quo. Authenticate throws an exception
161 //// via Fail if no identity information is present.
162 ////
163 //// With the present HTTP server we can't use the
164 //// builtin authentication mechanisms because they
165 //// would be enforced for all in-bound requests.
166 //// Instead we look at the headers ourselves and
167 //// handle authentication directly.
168
169 //try
170 //{
171 // if (!rdata.IsAuthenticated)
172 // {
173 // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
174 // }
175 //}
176 //catch (RestException e)
177 //{
178 // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
179 // {
180 // Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
181 // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
182 // }
183 // else
184 // {
185 // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
186 // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
187 // }
188 // throw (e);
189 //}
190
191 //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
192
193 //// We can only get here if we are authorized
194 ////
195 //// The requestor may have specified an UUID or
196 //// a conjoined FirstName LastName string. We'll
197 //// try both. If we fail with the first, UUID,
198 //// attempt, we try the other. As an example, the
199 //// URI for a valid inventory request might be:
200 ////
201 //// http://<host>:<port>/admin/inventory/Arthur Dent
202 ////
203 //// Indicating that this is an inventory request for
204 //// an avatar named Arthur Dent. This is ALL that is
205 //// required to designate a GET for an entire
206 //// inventory.
207 ////
208
209
210 //// Do we have at least a user agent name?
211
212 //if (rdata.Parameters.Length < 1)
213 //{
214 // Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId);
215 // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified");
216 //}
217
218 //// The first parameter MUST be the agent identification, either an UUID
219 //// or a space-separated First-name Last-Name specification. We check for
220 //// an UUID first, if anyone names their character using a valid UUID
221 //// that identifies another existing avatar will cause this a problem...
222
223 //try
224 //{
225 // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]);
226 // Rest.Log.DebugFormat("{0} UUID supplied", MsgId);
227 // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
228 //}
229 //catch
230 //{
231 // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE);
232 // if (names.Length == 2)
233 // {
234 // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
235 // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]);
236 // }
237 // else
238 // {
239 // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
240 // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity");
241 // }
242 //}
243
244 //// If the user profile is null then either the server is broken, or the
245 //// user is not known. We always assume the latter case.
246
247 //if (rdata.userProfile != null)
248 //{
249 // Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}",
250 // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
251 //}
252 //else
253 //{
254 // Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path);
255 // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity");
256 //}
257
258 //// If we get to here, then we have effectively validated the user's
259 //// identity. Now we need to get the inventory. If the server does not
260 //// have the inventory, we reject the request with an appropriate explanation.
261 ////
262 //// Note that inventory retrieval is an asynchronous event, we use the rdata
263 //// class instance as the basis for our synchronization.
264 ////
265
266 //rdata.uuid = rdata.userProfile.ID;
267
268 //if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid))
269 //{
270 // rdata.root = Rest.InventoryServices.GetRootFolder(rdata.uuid);
271
272 // Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}",
273 // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
274
275 // Rest.InventoryServices.GetUserInventory(rdata.uuid, rdata.GetUserInventory);
276
277 // Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}",
278 // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
279
280 // lock (rdata)
281 // {
282 // if (!rdata.HaveInventory)
283 // {
284 // rdata.startWD(1000);
285 // rdata.timeout = false;
286 // Monitor.Wait(rdata);
287 // }
288 // }
289
290 // if (rdata.timeout)
291 // {
292 // Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.",
293 // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
294 // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding");
295 // }
296
297 // if (rdata.root == null)
298 // {
299 // Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}",
300 // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
301 // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed");
302 // }
303
304 //}
305 //else
306 //{
307 // Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}",
308 // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
309 // rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user");
310 //}
311
312 //// If we get here, then we have successfully retrieved the user's information
313 //// and inventory information is now available locally.
314
315 //switch (rdata.method)
316 //{
317 // case Rest.HEAD : // Do the processing, set the status code, suppress entity
318 // DoGet(rdata);
319 // rdata.buffer = null;
320 // break;
321
322 // case Rest.GET : // Do the processing, set the status code, return entity
323 // DoGet(rdata);
324 // break;
325
326 // case Rest.PUT : // Update named element
327 // DoUpdate(rdata);
328 // break;
329
330 // case Rest.POST : // Add new information to identified context.
331 // DoExtend(rdata);
332 // break;
333
334 // case Rest.DELETE : // Delete information
335 // DoDelete(rdata);
336 // break;
337
338 // default :
339 // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}",
340 // MsgId, rdata.method, rdata.path);
341 // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
342 // String.Format("{0} not supported", rdata.method));
343 // break;
344 //}
345 }
346
347 #endregion Interface
348
349 #region method-specific processing
350
351 /// <summary>
352 /// This method implements GET processing for inventory.
353 /// Any remaining parameters are used to locate the
354 /// corresponding subtree based upon node name.
355 /// </summary>
356 /// <param name=rdata>HTTP service request work area</param>
357// private void DoGet(InventoryRequestData rdata)
358// {
359// rdata.initXmlWriter();
360//
361// rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty);
362//
363// // If there are additional parameters, then these represent
364// // a path relative to the root of the inventory. This path
365// // must be traversed before we format the sub-tree thus
366// // identified.
367//
368// traverse(rdata, rdata.root, PARM_PATH);
369//
370// // Close all open elements
371//
372// rdata.writer.WriteFullEndElement();
373//
374// // Indicate a successful request
375//
376// rdata.Complete();
377//
378// // Send the response to the user. The body will be implicitly
379// // constructed from the result of the XML writer.
380//
381// rdata.Respond(String.Format("Inventory {0} Normal completion", rdata.method));
382// }
383
384 /// <summary>
385 /// In the case of the inventory, and probably in general,
386 /// the distinction between PUT and POST is not always
387 /// easy to discern. The standard is badly worded in places,
388 /// and adding a node to a hierarchy can be viewed as
389 /// an addition, or as a modification to the inventory as
390 /// a whole. This is exacerbated by an unjustified lack of
391 /// consistency across different implementations.
392 ///
393 /// For OpenSim PUT is an update and POST is an addition. This
394 /// is the behavior required by the HTTP specification and
395 /// therefore as required by REST.
396 ///
397 /// The best way to explain the distinction is to
398 /// consider the relationship between the URI and the
399 /// enclosed entity. For PUT, the URI identifies the
400 /// actual entity to be modified or replaced, i.e. the
401 /// enclosed entity.
402 ///
403 /// If the operation is POST,then the URI describes the
404 /// context into which the new entity will be added.
405 ///
406 /// As an example, suppose the URI contains:
407 /// /admin/inventory/Clothing
408 ///
409 /// A PUT request will normally result in some modification of
410 /// the folder or item named "Clothing". Whereas a POST
411 /// request will normally add some new information into the
412 /// content identified by Clothing. It follows from this
413 /// that for POST, the element identified by the URI MUST
414 /// be a folder.
415 /// </summary>
416
417 /// <summary>
418 /// POST adds new information to the inventory in the
419 /// context identified by the URI.
420 /// </summary>
421 /// <param name=rdata>HTTP service request work area</param>
422// private void DoExtend(InventoryRequestData rdata)
423// {
424// bool created = false;
425// bool modified = false;
426// string newnode = String.Empty;
427//
428// // Resolve the context node specified in the URI. Entity
429// // data will be ADDED beneath this node. rdata already contains
430// // information about the current content of the user's
431// // inventory.
432//
433// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill);
434//
435// // Processing depends upon the type of inventory node
436// // identified in the URI. This is the CONTEXT for the
437// // change. We either got a context or we threw an
438// // exception.
439//
440// // It follows that we can only add information if the URI
441// // has identified a folder. So only a type of folder is supported
442// // in this case.
443//
444// if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
445// typeof(InventoryFolderImpl) == InventoryNode.GetType())
446// {
447// // Cast the context node appropriately.
448//
449// InventoryFolderBase context = (InventoryFolderBase) InventoryNode;
450//
451// Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}",
452// MsgId, rdata.method, rdata.path);
453//
454// // Reconstitute the inventory sub-tree from the XML supplied in the entity.
455// // The result is a stand-alone inventory subtree, not yet integrated into the
456// // existing tree. An inventory collection consists of three components:
457// // [1] A (possibly empty) set of folders.
458// // [2] A (possibly empty) set of items.
459// // [3] A (possibly empty) set of assets.
460// // If all of these are empty, then the POST is a harmless no-operation.
461//
462// XmlInventoryCollection entity = ReconstituteEntity(rdata);
463//
464// // Inlined assets can be included in entity. These must be incorporated into
465// // the asset database before we attempt to update the inventory. If anything
466// // fails, return a failure to requestor.
467//
468// if (entity.Assets.Count > 0)
469// {
470// Rest.Log.DebugFormat("{0} Adding {1} assets to server",
471// MsgId, entity.Assets.Count);
472//
473// foreach (AssetBase asset in entity.Assets)
474// {
475// Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
476// MsgId, asset.ID, asset.Type, asset.Name);
477// Rest.AssetServices.Store(asset);
478//
479// created = true;
480// rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>",
481// asset.Name, asset.ID));
482//
483// if (Rest.DEBUG && Rest.DumpAsset)
484// {
485// Rest.Dump(asset.Data);
486// }
487// }
488// }
489//
490// // Modify the context using the collection of folders and items
491// // returned in the XmlInventoryCollection.
492//
493// foreach (InventoryFolderBase folder in entity.Folders)
494// {
495// InventoryFolderBase found;
496//
497// // If the parentID is zero, then this folder is going
498// // into the root folder identified by the URI. The requestor
499// // may have already set the parent ID explicitly, in which
500// // case we don't have to do it here.
501//
502// if (folder.ParentID == UUID.Zero || folder.ParentID == context.ID)
503// {
504// if (newnode != String.Empty)
505// {
506// Rest.Log.DebugFormat("{0} Too many resources", MsgId);
507// rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed");
508// }
509// folder.ParentID = context.ID;
510// newnode = folder.Name;
511// }
512//
513// // Search the existing inventory for an existing entry. If
514// // we have one, we need to decide if it has really changed.
515// // It could just be present as (unnecessary) context, and we
516// // don't want to waste time updating the database in that
517// // case, OR, it could be being moved from another location
518// // in which case an update is most certainly necessary.
519//
520// found = null;
521//
522// foreach (InventoryFolderBase xf in rdata.folders)
523// {
524// // Compare identifying attribute
525// if (xf.ID == folder.ID)
526// {
527// found = xf;
528// break;
529// }
530// }
531//
532// if (found != null && FolderHasChanged(folder,found))
533// {
534// Rest.Log.DebugFormat("{0} Updating existing folder", MsgId);
535// Rest.InventoryServices.MoveFolder(folder);
536//
537// modified = true;
538// rdata.appendStatus(String.Format("<p> Created folder {0}, UUID {1} <p>",
539// folder.Name, folder.ID));
540// }
541// else
542// {
543// Rest.Log.DebugFormat("{0} Adding new folder", MsgId);
544// Rest.InventoryServices.AddFolder(folder);
545//
546// created = true;
547// rdata.appendStatus(String.Format("<p> Modified folder {0}, UUID {1} <p>",
548// folder.Name, folder.ID));
549// }
550// }
551//
552// // Now we repeat a similar process for the items included
553// // in the entity.
554//
555// foreach (InventoryItemBase item in entity.Items)
556// {
557// InventoryItemBase found = null;
558//
559// // If the parentID is zero, then this is going
560// // directly into the root identified by the URI.
561//
562// if (item.Folder == UUID.Zero)
563// {
564// item.Folder = context.ID;
565// }
566//
567// // Determine whether this is a new item or a
568// // replacement definition.
569//
570// foreach (InventoryItemBase xi in rdata.items)
571// {
572// // Compare identifying attribute
573// if (xi.ID == item.ID)
574// {
575// found = xi;
576// break;
577// }
578// }
579//
580// if (found != null && ItemHasChanged(item, found))
581// {
582// Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}",
583// MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
584// Rest.InventoryServices.UpdateItem(item);
585// modified = true;
586// rdata.appendStatus(String.Format("<p> Modified item {0}, UUID {1} <p>", item.Name, item.ID));
587// }
588// else
589// {
590// Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}",
591// MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
592// Rest.InventoryServices.AddItem(item);
593// created = true;
594// rdata.appendStatus(String.Format("<p> Created item {0}, UUID {1} <p>", item.Name, item.ID));
595// }
596// }
597//
598// if (created)
599// {
600// // Must include a location header with a URI that identifies the new resource.
601// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}/{3}",
602// rdata.hostname, rdata.port,rdata.path,newnode));
603// rdata.Complete(Rest.HttpStatusCodeCreated);
604// }
605// else
606// {
607// if (modified)
608// {
609// rdata.Complete(Rest.HttpStatusCodeOK);
610// }
611// else
612// {
613// rdata.Complete(Rest.HttpStatusCodeNoContent);
614// }
615// }
616//
617// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method));
618// }
619// else
620// {
621// Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}",
622// MsgId, rdata.method, rdata.path, InventoryNode.GetType());
623// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context");
624// }
625// }
626
627 /// <summary>
628 /// PUT updates the URI-identified element in the inventory. This
629 /// is actually far more flexible than it might at first sound. For
630 /// PUT the URI serves two purposes:
631 /// [1] It identifies the user whose inventory is to be
632 /// processed.
633 /// [2] It optionally specifies a subtree of the inventory
634 /// that is to be used to resolve any relative subtree
635 /// specifications in the entity. If nothing is specified
636 /// then the whole of the private inventory is implied.
637 /// Please note that the subtree specified by the URI is only relevant
638 /// to an entity containing a URI relative specification, i.e. one or
639 /// more elements do not specify parent folder information. These
640 /// elements will be implicitly referenced within the context identified
641 /// by the URI.
642 /// If an element in the entity specifies an explicit parent folder, then
643 /// that parent is effective, regardless of any value specified in the
644 /// URI. If the parent does not exist, then the element, and any dependent
645 /// elements, are ignored. This case is actually detected and handled
646 /// during the reconstitution process.
647 /// </summary>
648 /// <param name=rdata>HTTP service request work area</param>
649// private void DoUpdate(InventoryRequestData rdata)
650// {
651// int count = 0;
652// bool created = false;
653// bool modified = false;
654//
655// // Resolve the inventory node that is to be modified.
656// // rdata already contains information about the current
657// // content of the user's inventory.
658//
659// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill);
660//
661// // As long as we have a node, then we have something
662// // meaningful to do, unlike POST. So we reconstitute the
663// // subtree before doing anything else. Note that we
664// // etiher got a valid node or we threw an exception.
665//
666// XmlInventoryCollection entity = ReconstituteEntity(rdata);
667//
668// // Incorporate any inlined assets first. Any failures
669// // will terminate the request.
670//
671// if (entity.Assets.Count > 0)
672// {
673// Rest.Log.DebugFormat("{0} Adding {1} assets to server",
674// MsgId, entity.Assets.Count);
675//
676// foreach (AssetBase asset in entity.Assets)
677// {
678// Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
679// MsgId, asset.ID, asset.Type, asset.Name);
680//
681// // The asset was validated during the collection process
682//
683// Rest.AssetServices.Store(asset);
684//
685// created = true;
686// rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.ID));
687//
688// if (Rest.DEBUG && Rest.DumpAsset)
689// {
690// Rest.Dump(asset.Data);
691// }
692// }
693// }
694//
695// // The URI specifies either a folder or an item to be updated.
696// //
697// // The root node in the entity will replace the node identified
698// // by the URI. This means the parent will remain the same, but
699// // any or all attributes associated with the named element
700// // will change.
701// //
702// // If the inventory collection contains an element with a zero
703// // parent ID, then this is taken to be the replacement for the
704// // named node. The collection MAY also specify an explicit
705// // parent ID, in this case it MAY identify the same parent as
706// // the current node, or it MAY specify a different parent,
707// // indicating that the folder is being moved in addition to any
708// // other modifications being made.
709//
710// if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
711// typeof(InventoryFolderImpl) == InventoryNode.GetType())
712// {
713// bool rfound = false;
714// InventoryFolderBase uri = (InventoryFolderBase) InventoryNode;
715// InventoryFolderBase xml = null;
716//
717// // If the entity to be replaced resolved to be the root
718// // directory itself (My Inventory), then make sure that
719// // the supplied data include as appropriately typed and
720// // named folder. Note that we can;t rule out the possibility
721// // of a sub-directory being called "My Inventory", so that
722// // is anticipated.
723//
724// if (uri == rdata.root)
725// {
726// foreach (InventoryFolderBase folder in entity.Folders)
727// {
728// if ((rfound = (folder.Name == PRIVATE_ROOT_NAME)))
729// {
730// if ((rfound = (folder.ParentID == UUID.Zero)))
731// break;
732// }
733// }
734//
735// if (!rfound)
736// {
737// Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory",
738// MsgId, rdata.method, rdata.path);
739// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure");
740// }
741// }
742//
743// // Scan the set of folders in the entity collection for an
744// // entry that matches the context folder. It is assumed that
745// // the only reliable indicator of this is a zero UUID (using
746// // implicit context), or the parent's UUID matches that of the
747// // URI designated node (explicit context). We don't allow
748// // ambiguity in this case because this is POST and we are
749// // supposed to be modifying a specific node.
750// // We assign any element IDs required as an economy; we don't
751// // want to iterate over the fodler set again if it can be
752// // helped.
753//
754// foreach (InventoryFolderBase folder in entity.Folders)
755// {
756// if (folder.ParentID == uri.ParentID ||
757// folder.ParentID == UUID.Zero)
758// {
759// folder.ParentID = uri.ParentID;
760// xml = folder;
761// count++;
762// }
763// }
764//
765// // More than one entry is ambiguous. Other folders should be
766// // added using the POST verb.
767//
768// if (count > 1)
769// {
770// Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous",
771// MsgId, rdata.method, rdata.path);
772// rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous");
773// }
774//
775// // Exactly one entry means we ARE replacing the node
776// // identified by the URI. So we delete the old folder
777// // by moving it to the trash and then purging it.
778// // We then add all of the folders and items we
779// // included in the entity. The subtree has been
780// // modified.
781//
782// if (count == 1)
783// {
784// InventoryFolderBase TrashCan = GetTrashCan(rdata);
785//
786// // All went well, so we generate a UUID is one is
787// // needed.
788//
789// if (xml.ID == UUID.Zero)
790// {
791// xml.ID = UUID.Random();
792// }
793//
794// uri.ParentID = TrashCan.ID;
795// Rest.InventoryServices.MoveFolder(uri);
796// Rest.InventoryServices.PurgeFolder(TrashCan);
797// modified = true;
798// }
799//
800// // Now, regardelss of what they represent, we
801// // integrate all of the elements in the entity.
802//
803// foreach (InventoryFolderBase f in entity.Folders)
804// {
805// rdata.appendStatus(String.Format("<p>Moving folder {0} UUID {1} <p>", f.Name, f.ID));
806// Rest.InventoryServices.MoveFolder(f);
807// }
808//
809// foreach (InventoryItemBase it in entity.Items)
810// {
811// rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", it.Name, it.ID));
812// Rest.InventoryServices.AddItem(it);
813// }
814// }
815//
816// /// <summary>
817// /// URI specifies an item to be updated
818// /// </summary>
819// /// <remarks>
820// /// The entity must contain a single item node to be
821// /// updated. ID and Folder ID must be correct.
822// /// </remarks>
823//
824// else
825// {
826// InventoryItemBase uri = (InventoryItemBase) InventoryNode;
827// InventoryItemBase xml = null;
828//
829// if (entity.Folders.Count != 0)
830// {
831// Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>",
832// MsgId, rdata.method, rdata.path);
833// rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed");
834// }
835//
836// if (entity.Items.Count > 1)
837// {
838// Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>",
839// MsgId, rdata.method, rdata.path);
840// rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items");
841// }
842//
843// xml = entity.Items[0];
844//
845// if (xml.ID == UUID.Zero)
846// {
847// xml.ID = UUID.Random();
848// }
849//
850// // If the folder reference has changed, then this item is
851// // being moved. Otherwise we'll just delete the old, and
852// // add in the new.
853//
854// // Delete the old item
855//
856// List<UUID> uuids = new List<UUID>();
857// uuids.Add(uri.ID);
858// Rest.InventoryServices.DeleteItems(uri.Owner, uuids);
859//
860// // Add the new item to the inventory
861//
862// Rest.InventoryServices.AddItem(xml);
863//
864// rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", xml.Name, xml.ID));
865// }
866//
867// if (created)
868// {
869// rdata.Complete(Rest.HttpStatusCodeCreated);
870// }
871// else
872// {
873// if (modified)
874// {
875// rdata.Complete(Rest.HttpStatusCodeOK);
876// }
877// else
878// {
879// rdata.Complete(Rest.HttpStatusCodeNoContent);
880// }
881// }
882//
883// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method));
884// }
885
886 /// <summary>
887 /// Arguably the most damaging REST interface. It deletes the inventory
888 /// item or folder identified by the URI.
889 ///
890 /// We only process if the URI identified node appears to exist
891 /// We do not test for success because we either get a context,
892 /// or an exception is thrown.
893 ///
894 /// Folders are deleted by moving them to another folder and then
895 /// purging that folder. We'll do that by creating a temporary
896 /// sub-folder in the TrashCan and purging that folder's
897 /// contents. If we can't can it, we don't delete it...
898 /// So, if no trashcan is available, the request does nothing.
899 /// Items are summarily deleted.
900 ///
901 /// In the interests of safety, a delete request should normally
902 /// be performed using UUID, as a name might identify several
903 /// elements.
904 /// </summary>
905 /// <param name=rdata>HTTP service request work area</param>
906// private void DoDelete(InventoryRequestData rdata)
907// {
908// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false);
909//
910// if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
911// typeof(InventoryFolderImpl) == InventoryNode.GetType())
912// {
913// InventoryFolderBase TrashCan = GetTrashCan(rdata);
914//
915// InventoryFolderBase folder = (InventoryFolderBase) InventoryNode;
916// Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted",
917// MsgId, rdata.method, rdata.path);
918// folder.ParentID = TrashCan.ID;
919// Rest.InventoryServices.MoveFolder(folder);
920// Rest.InventoryServices.PurgeFolder(TrashCan);
921//
922// rdata.appendStatus(String.Format("<p>Deleted folder {0} UUID {1} <p>", folder.Name, folder.ID));
923// }
924//
925// // Deleting items is much more straight forward.
926//
927// else
928// {
929// InventoryItemBase item = (InventoryItemBase) InventoryNode;
930// Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted",
931// MsgId, rdata.method, rdata.path);
932// List<UUID> uuids = new List<UUID>();
933// uuids.Add(item.ID);
934// Rest.InventoryServices.DeleteItems(item.Owner, uuids);
935// rdata.appendStatus(String.Format("<p>Deleted item {0} UUID {1} <p>", item.Name, item.ID));
936// }
937//
938// rdata.Complete();
939// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method));
940// }
941
942#endregion method-specific processing
943
944 /// <summary>
945 /// This method is called to obtain the OpenSim inventory object identified
946 /// by the supplied URI. This may be either an Item or a Folder, so a suitably
947 /// ambiguous return type is employed (Object). This method recurses as
948 /// necessary to process the designated hierarchy.
949 ///
950 /// If we reach the end of the URI then we return the contextual folder to
951 /// our caller.
952 ///
953 /// If we are not yet at the end of the URI we attempt to find a child folder
954 /// and if we succeed we recurse.
955 ///
956 /// If this is the last node, then we look to see if this is an item. If it is,
957 /// we return that item.
958 ///
959 /// If we reach the end of an inventory path and the URI si not yet exhausted,
960 /// then if 'fill' is specified, we create the intermediate nodes.
961 ///
962 /// Otherwise we fail the request on the ground of an invalid URI.
963 ///
964 /// An ambiguous request causes the request to fail.
965 ///
966 /// </summary>
967 /// <param name=rdata>HTTP service request work area</param>
968 /// <param name=folder>The folder to be searched (parent)</param>
969 /// <param name=pi>URI parameter index</param>
970 /// <param name=fill>Should missing path members be created?</param>
971
972 private Object getInventoryNode(InventoryRequestData rdata,
973 InventoryFolderBase folder,
974 int pi, bool fill)
975 {
976 InventoryFolderBase foundf = null;
977 int fk = 0;
978
979 Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
980
981 // We have just run off the end of the parameter sequence
982
983 if (pi >= rdata.Parameters.Length)
984 {
985 return folder;
986 }
987
988 // There are more names in the parameter sequence,
989 // look for the folder named by param[pi] as a
990 // child of the folder supplied as an argument.
991 // Note that a UUID may have been supplied as the
992 // identifier (it is the ONLY guaranteed unambiguous
993 // option.
994
995 if (rdata.folders != null)
996 {
997 foreach (InventoryFolderBase f in rdata.folders)
998 {
999 // Look for the present node in the directory list
1000 if (f.ParentID == folder.ID &&
1001 (f.Name == rdata.Parameters[pi] ||
1002 f.ID.ToString() == rdata.Parameters[pi]))
1003 {
1004 foundf = f;
1005 fk++;
1006 }
1007 }
1008 }
1009
1010 // If more than one node matched, then the path, as specified
1011 // is ambiguous.
1012
1013 if (fk > 1)
1014 {
1015 Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
1016 MsgId, rdata.method, rdata.path);
1017 rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous");
1018 }
1019
1020 // If we find a match, then the method
1021 // increment the parameter index, and calls itself
1022 // passing the found folder as the new context.
1023
1024 if (foundf != null)
1025 {
1026 return getInventoryNode(rdata, foundf, pi+1, fill);
1027 }
1028
1029 // No folders that match. Perhaps this parameter identifies an item? If
1030 // it does, then it MUST also be the last name in the sequence.
1031
1032 if (pi == rdata.Parameters.Length-1)
1033 {
1034 if (rdata.items != null)
1035 {
1036 int k = 0;
1037 InventoryItemBase li = null;
1038 foreach (InventoryItemBase i in rdata.items)
1039 {
1040 if (i.Folder == folder.ID &&
1041 (i.Name == rdata.Parameters[pi] ||
1042 i.ID.ToString() == rdata.Parameters[pi]))
1043 {
1044 li = i;
1045 k++;
1046 }
1047 }
1048 if (k == 1)
1049 {
1050 return li;
1051 }
1052 else if (k > 1)
1053 {
1054 Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
1055 MsgId, rdata.method, rdata.path);
1056 rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous");
1057 }
1058 }
1059 }
1060
1061 // If fill is enabled, then we must create the missing intermediate nodes.
1062 // And of course, even this is not straightforward. All intermediate nodes
1063 // are obviously folders, but the last node may be a folder or an item.
1064
1065 if (fill)
1066 {
1067 }
1068
1069 // No fill, so abandon the request
1070
1071 Rest.Log.DebugFormat("{0} {1}: Resource {2} not found",
1072 MsgId, rdata.method, rdata.path);
1073 rdata.Fail(Rest.HttpStatusCodeNotFound,
1074 String.Format("resource {0}:{1} not found", rdata.method, rdata.path));
1075
1076 return null; /* Never reached */
1077 }
1078
1079 /// <summary>
1080 /// This routine traverse the inventory's structure until the end-point identified
1081 /// in the URI is reached, the remainder of the inventory (if any) is then formatted
1082 /// and returned to the requestor.
1083 ///
1084 /// Note that this method is only interested in those folder that match elements of
1085 /// the URI supplied by the requestor, so once a match is fund, the processing does
1086 /// not need to consider any further elements.
1087 ///
1088 /// Only the last element in the URI should identify an item.
1089 /// </summary>
1090 /// <param name=rdata>HTTP service request work area</param>
1091 /// <param name=folder>The folder to be searched (parent)</param>
1092 /// <param name=pi>URI parameter index</param>
1093
1094 private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
1095 {
1096 Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
1097
1098 if (rdata.folders != null)
1099 {
1100 // If there was only one parameter (avatar name), then the entire
1101 // inventory is being requested.
1102
1103 if (rdata.Parameters.Length == 1)
1104 {
1105 formatInventory(rdata, rdata.root, String.Empty);
1106 }
1107
1108 // Has the client specified the root directory name explicitly?
1109 // if yes, then we just absorb the reference, because the folder
1110 // we start looking in for a match *is* the root directory. If there
1111 // are more parameters remaining we tarverse, otehrwise it's time
1112 // to format. Otherwise,we consider the "My Inventory" to be implied
1113 // and we just traverse normally.
1114
1115 else if (folder.ID.ToString() == rdata.Parameters[pi] ||
1116 folder.Name == rdata.Parameters[pi])
1117 {
1118 // Length is -1 because the avatar name is a parameter
1119 if (pi<(rdata.Parameters.Length-1))
1120 {
1121 traverseInventory(rdata, folder, pi+1);
1122 }
1123 else
1124 {
1125 formatInventory(rdata, folder, String.Empty);
1126 }
1127 }
1128 else
1129 {
1130 traverseInventory(rdata, folder, pi);
1131 }
1132
1133 return;
1134 }
1135 }
1136
1137 /// <summary>
1138 /// This is the recursive method. I've separated them in this way so that
1139 /// we do not have to waste cycles on any first-case-only processing.
1140 /// </summary>
1141
1142 private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
1143 {
1144 int fk = 0;
1145 InventoryFolderBase ffound = null;
1146 InventoryItemBase ifound = null;
1147
1148 Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
1149
1150 foreach (InventoryFolderBase f in rdata.folders)
1151 {
1152 if (f.ParentID == folder.ID &&
1153 (f.Name == rdata.Parameters[pi] ||
1154 f.ID.ToString() == rdata.Parameters[pi]))
1155 {
1156 fk++;
1157 ffound = f;
1158 }
1159 }
1160
1161 // If this is the last element in the parameter sequence, then
1162 // it is reasonable to check for an item. All intermediate nodes
1163 // MUST be folders.
1164
1165 if (pi == rdata.Parameters.Length-1)
1166 {
1167 // Only if there are any items, and there pretty much always are.
1168
1169 if (rdata.items != null)
1170 {
1171 foreach (InventoryItemBase i in rdata.items)
1172 {
1173 if (i.Folder == folder.ID &&
1174 (i.Name == rdata.Parameters[pi] ||
1175 i.ID.ToString() == rdata.Parameters[pi]))
1176 {
1177 fk++;
1178 ifound = i;
1179 }
1180 }
1181 }
1182 }
1183
1184 if (fk == 1)
1185 {
1186 if (ffound != null)
1187 {
1188 if (pi < rdata.Parameters.Length-1)
1189 {
1190 traverseInventory(rdata, ffound, pi+1);
1191 }
1192 else
1193 {
1194 formatInventory(rdata, ffound, String.Empty);
1195 }
1196 return;
1197 }
1198 else
1199 {
1200 // Fetching an Item has a special significance. In this
1201 // case we also want to fetch the associated asset.
1202 // To make it interesting, we'll do this via redirection.
1203 string asseturl = String.Format("http://{0}:{1}/{2}{3}{4}", rdata.hostname, rdata.port,
1204 "admin/assets",Rest.UrlPathSeparator,ifound.AssetID.ToString());
1205 rdata.Redirect(asseturl,Rest.PERMANENT);
1206 Rest.Log.DebugFormat("{0} Never Reached", MsgId);
1207 }
1208 }
1209 else if (fk > 1)
1210 {
1211 rdata.Fail(Rest.HttpStatusCodeConflict,
1212 String.Format("ambiguous element ({0}) in path specified: <{1}>",
1213 pi, rdata.path));
1214 }
1215
1216 Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>",
1217 MsgId, rdata.path);
1218 rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}",
1219 rdata.Parameters[pi]));
1220
1221 }
1222
1223 /// <summary>
1224 /// This method generates XML that describes an instance of InventoryFolderBase.
1225 /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem
1226 /// to generate XML for any items encountered along the way.
1227 /// The indentation parameter is solely for the benefit of trace record
1228 /// formatting.
1229 /// </summary>
1230 /// <param name=rdata>HTTP service request work area</param>
1231 /// <param name=folder>The folder to be searched (parent)</param>
1232 /// <param name=indent>pretty print indentation</param>
1233 private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent)
1234 {
1235 if (Rest.DEBUG)
1236 {
1237 Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}",
1238 MsgId, folder.ID, indent, folder.Name, folder.Type);
1239 indent += "\t";
1240 }
1241
1242 // Start folder item
1243
1244 rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty);
1245 rdata.writer.WriteAttributeString("name",String.Empty,folder.Name);
1246 rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString());
1247 rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString());
1248 rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString());
1249 rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString());
1250 rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString());
1251
1252 if (rdata.folders != null)
1253 {
1254 foreach (InventoryFolderBase f in rdata.folders)
1255 {
1256 if (f.ParentID == folder.ID)
1257 {
1258 formatInventory(rdata, f, indent);
1259 }
1260 }
1261 }
1262
1263 if (rdata.items != null)
1264 {
1265 foreach (InventoryItemBase i in rdata.items)
1266 {
1267 if (i.Folder == folder.ID)
1268 {
1269 formatItem(rdata, i, indent);
1270 }
1271 }
1272 }
1273
1274 // End folder item
1275
1276 rdata.writer.WriteEndElement();
1277 }
1278
1279 /// <summary>
1280 /// This method generates XML that describes an instance of InventoryItemBase.
1281 /// </summary>
1282 /// <param name="rdata">HTTP service request work area</param>
1283 /// <param name="i">The item to be formatted</param>
1284 /// <param name="indent">Pretty print indentation</param>
1285 private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent)
1286 {
1287 Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}",
1288 MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType);
1289
1290 rdata.writer.WriteStartElement(String.Empty, "Item", String.Empty);
1291
1292 rdata.writer.WriteAttributeString("name", String.Empty, i.Name);
1293 rdata.writer.WriteAttributeString("desc", String.Empty, i.Description);
1294 rdata.writer.WriteAttributeString("uuid", String.Empty, i.ID.ToString());
1295 rdata.writer.WriteAttributeString("folder", String.Empty, i.Folder.ToString());
1296 rdata.writer.WriteAttributeString("owner", String.Empty, i.Owner.ToString());
1297 rdata.writer.WriteAttributeString("creator", String.Empty, i.CreatorId);
1298 rdata.writer.WriteAttributeString("creatordata", String.Empty, i.CreatorData);
1299 rdata.writer.WriteAttributeString("creationdate", String.Empty, i.CreationDate.ToString());
1300 rdata.writer.WriteAttributeString("invtype", String.Empty, i.InvType.ToString());
1301 rdata.writer.WriteAttributeString("assettype", String.Empty, i.AssetType.ToString());
1302 rdata.writer.WriteAttributeString("groupowned", String.Empty, i.GroupOwned.ToString());
1303 rdata.writer.WriteAttributeString("groupid", String.Empty, i.GroupID.ToString());
1304 rdata.writer.WriteAttributeString("saletype", String.Empty, i.SaleType.ToString());
1305 rdata.writer.WriteAttributeString("saleprice", String.Empty, i.SalePrice.ToString());
1306 rdata.writer.WriteAttributeString("flags", String.Empty, i.Flags.ToString());
1307
1308 rdata.writer.WriteStartElement(String.Empty, "Permissions", String.Empty);
1309 rdata.writer.WriteAttributeString("current", String.Empty, i.CurrentPermissions.ToString("X"));
1310 rdata.writer.WriteAttributeString("next", String.Empty, i.NextPermissions.ToString("X"));
1311 rdata.writer.WriteAttributeString("group", String.Empty, i.GroupPermissions.ToString("X"));
1312 rdata.writer.WriteAttributeString("everyone", String.Empty, i.EveryOnePermissions.ToString("X"));
1313 rdata.writer.WriteAttributeString("base", String.Empty, i.BasePermissions.ToString("X"));
1314 rdata.writer.WriteEndElement();
1315
1316 rdata.writer.WriteElementString("Asset", i.AssetID.ToString());
1317
1318 rdata.writer.WriteEndElement();
1319 }
1320
1321 /// <summary>
1322 /// This method creates a "trashcan" folder to support folder and item
1323 /// deletions by this interface. The xisting trash folder is found and
1324 /// this folder is created within it. It is called "tmp" to indicate to
1325 /// the client that it is OK to delete this folder. The REST interface
1326 /// will recreate the folder on an as-required basis.
1327 /// If the trash can cannot be created, then by implication the request
1328 /// that required it cannot be completed, and it fails accordingly.
1329 /// </summary>
1330 /// <param name=rdata>HTTP service request work area</param>
1331 private InventoryFolderBase GetTrashCan(InventoryRequestData rdata)
1332 {
1333 InventoryFolderBase TrashCan = null;
1334
1335 foreach (InventoryFolderBase f in rdata.folders)
1336 {
1337 if (f.Name == "Trash")
1338 {
1339 foreach (InventoryFolderBase t in rdata.folders)
1340 {
1341 if (t.Name == "tmp")
1342 {
1343 TrashCan = t;
1344 }
1345 }
1346 if (TrashCan == null)
1347 {
1348 TrashCan = new InventoryFolderBase();
1349 TrashCan.Name = "tmp";
1350 TrashCan.ID = UUID.Random();
1351 TrashCan.Version = 1;
1352 TrashCan.Type = (short) AssetType.TrashFolder;
1353 TrashCan.ParentID = f.ID;
1354 TrashCan.Owner = f.Owner;
1355 Rest.InventoryServices.AddFolder(TrashCan);
1356 }
1357 }
1358 }
1359
1360 if (TrashCan == null)
1361 {
1362 Rest.Log.DebugFormat("{0} No Trash Can available", MsgId);
1363 rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can");
1364 }
1365
1366 return TrashCan;
1367 }
1368
1369 /// <summary>
1370 /// Make sure that an unchanged folder is not unnecessarily
1371 /// processed.
1372 /// </summary>
1373 /// <param name=newf>Folder obtained from enclosed entity</param>
1374 /// <param name=oldf>Folder obtained from the user's inventory</param>
1375 private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf)
1376 {
1377 return (newf.Name != oldf.Name
1378 || newf.ParentID != oldf.ParentID
1379 || newf.Owner != oldf.Owner
1380 || newf.Type != oldf.Type
1381 || newf.Version != oldf.Version
1382 );
1383 }
1384
1385 /// <summary>
1386 /// Make sure that an unchanged item is not unnecessarily
1387 /// processed.
1388 /// </summary>
1389 /// <param name=newf>Item obtained from enclosed entity</param>
1390 /// <param name=oldf>Item obtained from the user's inventory</param>
1391 private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf)
1392 {
1393 return (newf.Name != oldf.Name
1394 || newf.Folder != oldf.Folder
1395 || newf.Description != oldf.Description
1396 || newf.Owner != oldf.Owner
1397 || newf.CreatorId != oldf.CreatorId
1398 || newf.AssetID != oldf.AssetID
1399 || newf.GroupID != oldf.GroupID
1400 || newf.GroupOwned != oldf.GroupOwned
1401 || newf.InvType != oldf.InvType
1402 || newf.AssetType != oldf.AssetType
1403 );
1404 }
1405
1406 /// <summary>
1407 /// This method is called by PUT and POST to create an XmlInventoryCollection
1408 /// instance that reflects the content of the entity supplied on the request.
1409 /// Any elements in the completed collection whose UUID is zero, are
1410 /// considered to be located relative to the end-point identified int he
1411 /// URI. In this way, an entire sub-tree can be conveyed in a single REST
1412 /// PUT or POST request.
1413 ///
1414 /// A new instance of XmlInventoryCollection is created and, if the request
1415 /// has an entity, it is more completely initialized. thus, if no entity was
1416 /// provided the collection is valid, but empty.
1417 ///
1418 /// The entity is then scanned and each tag is processed to produce the
1419 /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection
1420 /// will reflect the subtree described by the entity.
1421 ///
1422 /// This is a very flexible mechanism, the entity may contain arbitrary,
1423 /// discontiguous tree fragments, or may contain single element. The caller is
1424 /// responsible for integrating this collection (and ensuring that any
1425 /// missing parent IDs are resolved).
1426 /// </summary>
1427 /// <param name=rdata>HTTP service request work area</param>
1428 internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata)
1429 {
1430 Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId);
1431
1432 XmlInventoryCollection ic = new XmlInventoryCollection();
1433
1434 if (rdata.request.HasEntityBody)
1435 {
1436 Rest.Log.DebugFormat("{0} Entity present", MsgId);
1437
1438 ic.init(rdata);
1439
1440 try
1441 {
1442 while (ic.xml.Read())
1443 {
1444 switch (ic.xml.NodeType)
1445 {
1446 case XmlNodeType.Element:
1447 Rest.Log.DebugFormat("{0} StartElement: <{1}>",
1448 MsgId, ic.xml.Name);
1449
1450 switch (ic.xml.Name)
1451 {
1452 case "Folder":
1453 Rest.Log.DebugFormat("{0} Processing {1} element",
1454 MsgId, ic.xml.Name);
1455 CollectFolder(ic);
1456 break;
1457 case "Item":
1458 Rest.Log.DebugFormat("{0} Processing {1} element",
1459 MsgId, ic.xml.Name);
1460 CollectItem(ic);
1461 break;
1462 case "Asset":
1463 Rest.Log.DebugFormat("{0} Processing {1} element",
1464 MsgId, ic.xml.Name);
1465 CollectAsset(ic);
1466 break;
1467 case "Permissions":
1468 Rest.Log.DebugFormat("{0} Processing {1} element",
1469 MsgId, ic.xml.Name);
1470 CollectPermissions(ic);
1471 break;
1472 default:
1473 Rest.Log.DebugFormat("{0} Ignoring {1} element",
1474 MsgId, ic.xml.Name);
1475 break;
1476 }
1477
1478 // This stinks, but the ReadElement call above not only reads
1479 // the imbedded data, but also consumes the end tag for Asset
1480 // and moves the element pointer on to the containing Item's
1481 // element-end, however, if there was a permissions element
1482 // following, it would get us to the start of that..
1483 if (ic.xml.NodeType == XmlNodeType.EndElement &&
1484 ic.xml.Name == "Item")
1485 {
1486 Validate(ic);
1487 }
1488 break;
1489
1490 case XmlNodeType.EndElement :
1491 switch (ic.xml.Name)
1492 {
1493 case "Folder":
1494 Rest.Log.DebugFormat("{0} Completing {1} element",
1495 MsgId, ic.xml.Name);
1496 ic.Pop();
1497 break;
1498 case "Item":
1499 Rest.Log.DebugFormat("{0} Completing {1} element",
1500 MsgId, ic.xml.Name);
1501 Validate(ic);
1502 break;
1503 case "Asset":
1504 Rest.Log.DebugFormat("{0} Completing {1} element",
1505 MsgId, ic.xml.Name);
1506 break;
1507 case "Permissions":
1508 Rest.Log.DebugFormat("{0} Completing {1} element",
1509 MsgId, ic.xml.Name);
1510 break;
1511 default:
1512 Rest.Log.DebugFormat("{0} Ignoring {1} element",
1513 MsgId, ic.xml.Name);
1514 break;
1515 }
1516 break;
1517
1518 default:
1519 Rest.Log.DebugFormat("{0} Ignoring: <{1}>:<{2}>",
1520 MsgId, ic.xml.NodeType, ic.xml.Value);
1521 break;
1522 }
1523 }
1524 }
1525 catch (XmlException e)
1526 {
1527 Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message);
1528 throw e;
1529 }
1530 catch (Exception e)
1531 {
1532 Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message);
1533 throw e;
1534 }
1535 }
1536 else
1537 {
1538 Rest.Log.DebugFormat("{0} Entity absent", MsgId);
1539 }
1540
1541 if (Rest.DEBUG)
1542 {
1543 Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId);
1544 Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count);
1545 Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count);
1546 Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count);
1547 }
1548
1549 return ic;
1550 }
1551
1552 /// <summary>
1553 /// This method creates an inventory Folder from the
1554 /// information supplied in the request's entity.
1555 /// A folder instance is created and initialized to reflect
1556 /// default values. These values are then overridden
1557 /// by information supplied in the entity.
1558 /// If context was not explicitly provided, then the
1559 /// appropriate ID values are determined.
1560 /// </summary>
1561
1562 private void CollectFolder(XmlInventoryCollection ic)
1563 {
1564 Rest.Log.DebugFormat("{0} Interpret folder element", MsgId);
1565
1566 InventoryFolderBase result = new InventoryFolderBase();
1567
1568 // Default values
1569
1570 result.Name = String.Empty;
1571 result.ID = UUID.Zero;
1572 result.Owner = ic.UserID;
1573 result.ParentID = UUID.Zero; // Context
1574 result.Type = (short) AssetType.Folder;
1575 result.Version = 1;
1576
1577 if (ic.xml.HasAttributes)
1578 {
1579 for (int i = 0; i < ic.xml.AttributeCount; i++)
1580 {
1581 ic.xml.MoveToAttribute(i);
1582 switch (ic.xml.Name)
1583 {
1584 case "name":
1585 result.Name = ic.xml.Value;
1586 break;
1587 case "uuid":
1588 result.ID = new UUID(ic.xml.Value);
1589 break;
1590 case "parent":
1591 result.ParentID = new UUID(ic.xml.Value);
1592 break;
1593 case "owner":
1594 result.Owner = new UUID(ic.xml.Value);
1595 break;
1596 case "type":
1597 result.Type = Int16.Parse(ic.xml.Value);
1598 break;
1599 case "version":
1600 result.Version = UInt16.Parse(ic.xml.Value);
1601 break;
1602 default:
1603 Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}",
1604 MsgId, ic.xml.Name, ic.xml.Value);
1605 ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>",
1606 ic.xml.Name));
1607 break;
1608 }
1609 }
1610 }
1611
1612 ic.xml.MoveToElement();
1613
1614 // The client is relying upon the reconstitution process
1615 // to determine the parent's UUID based upon context. This
1616 // is necessary where a new folder may have been
1617 // introduced.
1618
1619 if (result.ParentID == UUID.Zero)
1620 {
1621 result.ParentID = ic.Parent();
1622 }
1623 else
1624 {
1625 bool found = false;
1626
1627 foreach (InventoryFolderBase parent in ic.rdata.folders)
1628 {
1629 if (parent.ID == result.ParentID)
1630 {
1631 found = true;
1632 break;
1633 }
1634 }
1635
1636 if (!found)
1637 {
1638 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}",
1639 MsgId, ic.Item.Folder, result.ID);
1640 ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent");
1641 }
1642 }
1643
1644 // This is a new folder, so no existing UUID is available
1645 // or appropriate
1646
1647 if (result.ID == UUID.Zero)
1648 {
1649 result.ID = UUID.Random();
1650 }
1651
1652 // Treat this as a new context. Any other information is
1653 // obsolete as a consequence.
1654
1655 ic.Push(result);
1656 }
1657
1658 /// <summary>
1659 /// This method is called to handle the construction of an Item
1660 /// instance from the supplied request entity. It is called
1661 /// whenever an Item start tag is detected.
1662 /// An instance of an Item is created and initialized to default
1663 /// values. These values are then overridden from values supplied
1664 /// as attributes to the Item element.
1665 /// This item is then stored in the XmlInventoryCollection and
1666 /// will be verified by Validate.
1667 /// All context is reset whenever the effective folder changes
1668 /// or an item is successfully validated.
1669 /// </summary>
1670 private void CollectItem(XmlInventoryCollection ic)
1671 {
1672 Rest.Log.DebugFormat("{0} Interpret item element", MsgId);
1673
1674 InventoryItemBase result = new InventoryItemBase();
1675
1676 result.Name = String.Empty;
1677 result.Description = String.Empty;
1678 result.ID = UUID.Zero;
1679 result.Folder = UUID.Zero;
1680 result.Owner = ic.UserID;
1681 result.CreatorId = ic.UserID.ToString();
1682 result.AssetID = UUID.Zero;
1683 result.GroupID = UUID.Zero;
1684 result.GroupOwned = false;
1685 result.InvType = (int) InventoryType.Unknown;
1686 result.AssetType = (int) AssetType.Unknown;
1687
1688 if (ic.xml.HasAttributes)
1689 {
1690 for (int i = 0; i < ic.xml.AttributeCount; i++)
1691 {
1692 ic.xml.MoveToAttribute(i);
1693
1694 switch (ic.xml.Name)
1695 {
1696 case "name":
1697 result.Name = ic.xml.Value;
1698 break;
1699 case "desc":
1700 result.Description = ic.xml.Value;
1701 break;
1702 case "uuid":
1703 result.ID = new UUID(ic.xml.Value);
1704 break;
1705 case "folder":
1706 result.Folder = new UUID(ic.xml.Value);
1707 break;
1708 case "owner":
1709 result.Owner = new UUID(ic.xml.Value);
1710 break;
1711 case "invtype":
1712 result.InvType = Int32.Parse(ic.xml.Value);
1713 break;
1714 case "creator":
1715 result.CreatorId = ic.xml.Value;
1716 break;
1717 case "assettype":
1718 result.AssetType = Int32.Parse(ic.xml.Value);
1719 break;
1720 case "groupowned":
1721 result.GroupOwned = Boolean.Parse(ic.xml.Value);
1722 break;
1723 case "groupid":
1724 result.GroupID = new UUID(ic.xml.Value);
1725 break;
1726 case "flags":
1727 result.Flags = UInt32.Parse(ic.xml.Value);
1728 break;
1729 case "creationdate":
1730 result.CreationDate = Int32.Parse(ic.xml.Value);
1731 break;
1732 case "saletype":
1733 result.SaleType = Byte.Parse(ic.xml.Value);
1734 break;
1735 case "saleprice":
1736 result.SalePrice = Int32.Parse(ic.xml.Value);
1737 break;
1738
1739 default:
1740 Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}",
1741 MsgId, ic.xml.Name, ic.xml.Value);
1742 ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute",
1743 ic.xml.Name));
1744 break;
1745 }
1746 }
1747 }
1748
1749 ic.xml.MoveToElement();
1750
1751 ic.Push(result);
1752 }
1753
1754 /// <summary>
1755 /// This method assembles an asset instance from the
1756 /// information supplied in the request's entity. It is
1757 /// called as a result of detecting a start tag for a
1758 /// type of Asset.
1759 /// The information is collected locally, and an asset
1760 /// instance is created only if the basic XML parsing
1761 /// completes successfully.
1762 /// Default values for all parts of the asset are
1763 /// established before overriding them from the supplied
1764 /// XML.
1765 /// If an asset has inline=true as an attribute, then
1766 /// the element contains the data representing the
1767 /// asset. This is saved as the data component.
1768 /// inline=false means that the element's payload is
1769 /// simply the UUID of the asset referenced by the
1770 /// item being constructed.
1771 /// An asset, if created is stored in the
1772 /// XmlInventoryCollection
1773 /// </summary>
1774 private void CollectAsset(XmlInventoryCollection ic)
1775 {
1776 Rest.Log.DebugFormat("{0} Interpret asset element", MsgId);
1777
1778 string name = String.Empty;
1779 string desc = String.Empty;
1780 sbyte type = (sbyte) AssetType.Unknown;
1781 bool temp = false;
1782 bool local = false;
1783
1784 // This is not a persistent attribute
1785 bool inline = false;
1786
1787 UUID uuid = UUID.Zero;
1788
1789 // Attribute is optional
1790 if (ic.xml.HasAttributes)
1791 {
1792 for (int i = 0; i < ic.xml.AttributeCount; i++)
1793 {
1794 ic.xml.MoveToAttribute(i);
1795 switch (ic.xml.Name)
1796 {
1797 case "name" :
1798 name = ic.xml.Value;
1799 break;
1800
1801 case "type" :
1802 type = SByte.Parse(ic.xml.Value);
1803 break;
1804
1805 case "description" :
1806 desc = ic.xml.Value;
1807 break;
1808
1809 case "temporary" :
1810 temp = Boolean.Parse(ic.xml.Value);
1811 break;
1812
1813 case "uuid" :
1814 uuid = new UUID(ic.xml.Value);
1815 break;
1816
1817 case "inline" :
1818 inline = Boolean.Parse(ic.xml.Value);
1819 break;
1820
1821 case "local" :
1822 local = Boolean.Parse(ic.xml.Value);
1823 break;
1824
1825 default :
1826 Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}",
1827 MsgId, ic.xml.Name, ic.xml.Value);
1828 ic.Fail(Rest.HttpStatusCodeBadRequest,
1829 String.Format("unrecognized attribute <{0}>", ic.xml.Name));
1830 break;
1831 }
1832 }
1833 }
1834
1835 ic.xml.MoveToElement();
1836
1837 // If this is a reference to an existing asset, just store the
1838 // asset ID into the item.
1839
1840 if (!inline)
1841 {
1842 if (ic.Item != null)
1843 {
1844 ic.Item.AssetID = new UUID(ic.xml.ReadElementContentAsString());
1845 Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID);
1846 }
1847 else
1848 {
1849 Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId);
1850 ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset");
1851 }
1852 }
1853
1854 // Otherwise, generate an asset ID, store that into the item, and
1855 // create an entry in the asset list for the inlined asset. But
1856 // only if the size is non-zero.
1857
1858 else
1859 {
1860 AssetBase asset = null;
1861 string b64string = null;
1862
1863 // Generate a UUID if none were given, and generally none should
1864 // be. Ever.
1865
1866 if (uuid == UUID.Zero)
1867 {
1868 uuid = UUID.Random();
1869 }
1870
1871 // Create AssetBase entity to hold the inlined asset
1872
1873 asset = new AssetBase(uuid, name, type, UUID.Zero.ToString());
1874
1875 asset.Description = desc;
1876 asset.Local = local;
1877 asset.Temporary = temp;
1878
1879 b64string = ic.xml.ReadElementContentAsString();
1880
1881 Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length);
1882 Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId,
1883 b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length));
1884
1885 asset.Data = Convert.FromBase64String(b64string);
1886
1887 // Ensure the asset always has some kind of data component
1888
1889 if (asset.Data == null)
1890 {
1891 asset.Data = new byte[1];
1892 }
1893
1894 // If this is in the context of an item, establish
1895 // a link with the item in context.
1896
1897 if (ic.Item != null && ic.Item.AssetID == UUID.Zero)
1898 {
1899 ic.Item.AssetID = uuid;
1900 }
1901
1902 ic.Push(asset);
1903 }
1904 }
1905
1906 /// <summary>
1907 /// Store any permissions information provided by the request.
1908 /// This overrides the default permissions set when the
1909 /// XmlInventoryCollection object was created.
1910 /// </summary>
1911 private void CollectPermissions(XmlInventoryCollection ic)
1912 {
1913 if (ic.xml.HasAttributes)
1914 {
1915 for (int i = 0; i < ic.xml.AttributeCount; i++)
1916 {
1917 ic.xml.MoveToAttribute(i);
1918 switch (ic.xml.Name)
1919 {
1920 case "current":
1921 ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber);
1922 break;
1923 case "next":
1924 ic.NextPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber);
1925 break;
1926 case "group":
1927 ic.GroupPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber);
1928 break;
1929 case "everyone":
1930 ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber);
1931 break;
1932 case "base":
1933 ic.BasePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber);
1934 break;
1935 default:
1936 Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}",
1937 MsgId,ic.xml.Name, ic.xml.Value);
1938 ic.Fail(Rest.HttpStatusCodeBadRequest,
1939 String.Format("invalid attribute <{0}>", ic.xml.Name));
1940 break;
1941 }
1942 }
1943 }
1944
1945 ic.xml.MoveToElement();
1946 }
1947
1948 /// <summary>
1949 /// This method is called whenever an Item has been successfully
1950 /// reconstituted from the request's entity.
1951 /// It uses the information curren tin the XmlInventoryCollection
1952 /// to complete the item's specification, including any implied
1953 /// context and asset associations.
1954 /// It fails the request if any necessary item or asset information
1955 /// is missing.
1956 /// </summary>
1957
1958 private void Validate(XmlInventoryCollection ic)
1959 {
1960 // There really should be an item present if we've
1961 // called validate. So fail if there is not.
1962
1963 if (ic.Item == null)
1964 {
1965 Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId);
1966 ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error");
1967 }
1968
1969 // Every item is required to have a name (via REST anyway)
1970
1971 if (ic.Item.Name == String.Empty)
1972 {
1973 Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId);
1974 ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required");
1975 }
1976
1977 // An item MUST have an asset ID. AssetID should never be zero
1978 // here. It should always get set from the information stored
1979 // when the Asset element was processed.
1980
1981 if (ic.Item.AssetID == UUID.Zero)
1982 {
1983 Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId);
1984 Rest.Log.InfoFormat("{0} Asset information is missing", MsgId);
1985 ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required");
1986 }
1987
1988 // If the item is new, then assign it an ID
1989
1990 if (ic.Item.ID == UUID.Zero)
1991 {
1992 ic.Item.ID = UUID.Random();
1993 }
1994
1995 // If the context is being implied, obtain the current
1996 // folder item's ID. If it was specified explicitly, make
1997 // sure that theparent folder exists.
1998
1999 if (ic.Item.Folder == UUID.Zero)
2000 {
2001 ic.Item.Folder = ic.Parent();
2002 }
2003 else
2004 {
2005 bool found = false;
2006
2007 foreach (InventoryFolderBase parent in ic.rdata.folders)
2008 {
2009 if (parent.ID == ic.Item.Folder)
2010 {
2011 found = true;
2012 break;
2013 }
2014 }
2015
2016 if (!found)
2017 {
2018 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}",
2019 MsgId, ic.Item.Folder, ic.Item.ID);
2020 ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required");
2021 }
2022 }
2023
2024 // If this is an inline asset being constructed in the context
2025 // of a new Item, then use the itm's name here too.
2026
2027 if (ic.Asset != null)
2028 {
2029 if (ic.Asset.Name == String.Empty)
2030 ic.Asset.Name = ic.Item.Name;
2031 if (ic.Asset.Description == String.Empty)
2032 ic.Asset.Description = ic.Item.Description;
2033 }
2034
2035 // Assign permissions
2036
2037 ic.Item.CurrentPermissions = ic.CurrentPermissions;
2038 ic.Item.EveryOnePermissions = ic.EveryOnePermissions;
2039 ic.Item.BasePermissions = ic.BasePermissions;
2040 ic.Item.GroupPermissions = ic.GroupPermissions;
2041 ic.Item.NextPermissions = ic.NextPermissions;
2042
2043 // If no type was specified for this item, we can attempt to
2044 // infer something from the file type maybe. This is NOT as
2045 // good as having type be specified in the XML.
2046
2047 if (ic.Item.AssetType == (int) AssetType.Unknown ||
2048 ic.Item.InvType == (int) InventoryType.Unknown)
2049 {
2050 Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId);
2051
2052 string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD);
2053
2054 if (Rest.DEBUG)
2055 {
2056 for (int i = 0; i < parts.Length; i++)
2057 {
2058 Rest.Log.DebugFormat("{0} Name part {1} : {2}",
2059 MsgId, i, parts[i]);
2060 }
2061 }
2062
2063 // If the associated item name is multi-part, then maybe
2064 // the last part will indicate the item type - if we're
2065 // lucky.
2066
2067 if (parts.Length > 1)
2068 {
2069 Rest.Log.DebugFormat("{0} File type is {1}",
2070 MsgId, parts[parts.Length - 1]);
2071 switch (parts[parts.Length - 1])
2072 {
2073 case "jpeg2000" :
2074 case "jpeg-2000" :
2075 case "jpg2000" :
2076 case "jpg-2000" :
2077 Rest.Log.DebugFormat("{0} Type {1} inferred",
2078 MsgId, parts[parts.Length-1]);
2079 if (ic.Item.AssetType == (int) AssetType.Unknown)
2080 ic.Item.AssetType = (int) AssetType.ImageJPEG;
2081 if (ic.Item.InvType == (int) InventoryType.Unknown)
2082 ic.Item.InvType = (int) InventoryType.Texture;
2083 break;
2084 case "jpg" :
2085 case "jpeg" :
2086 Rest.Log.DebugFormat("{0} Type {1} inferred",
2087 MsgId, parts[parts.Length - 1]);
2088 if (ic.Item.AssetType == (int) AssetType.Unknown)
2089 ic.Item.AssetType = (int) AssetType.ImageJPEG;
2090 if (ic.Item.InvType == (int) InventoryType.Unknown)
2091 ic.Item.InvType = (int) InventoryType.Texture;
2092 break;
2093 case "tga" :
2094 if (parts[parts.Length - 2].IndexOf("_texture") != -1)
2095 {
2096 if (ic.Item.AssetType == (int) AssetType.Unknown)
2097 ic.Item.AssetType = (int) AssetType.TextureTGA;
2098 if (ic.Item.InvType == (int) AssetType.Unknown)
2099 ic.Item.InvType = (int) InventoryType.Texture;
2100 }
2101 else
2102 {
2103 if (ic.Item.AssetType == (int) AssetType.Unknown)
2104 ic.Item.AssetType = (int) AssetType.ImageTGA;
2105 if (ic.Item.InvType == (int) InventoryType.Unknown)
2106 ic.Item.InvType = (int) InventoryType.Snapshot;
2107 }
2108 break;
2109 default :
2110 Rest.Log.DebugFormat("{0} Asset/Inventory type could not be inferred for {1}",
2111 MsgId,ic.Item.Name);
2112 break;
2113 }
2114 }
2115 }
2116
2117 /// If this is a TGA remember the fact
2118
2119 if (ic.Item.AssetType == (int) AssetType.TextureTGA ||
2120 ic.Item.AssetType == (int) AssetType.ImageTGA)
2121 {
2122 Bitmap temp;
2123 Stream tgadata = new MemoryStream(ic.Asset.Data);
2124
2125 temp = LoadTGAClass.LoadTGA(tgadata);
2126 try
2127 {
2128 ic.Asset.Data = OpenJPEG.EncodeFromImage(temp, true);
2129 }
2130 catch (DllNotFoundException)
2131 {
2132 Rest.Log.ErrorFormat("OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", ic.Item.Name);
2133 ic.Asset.Data = new Byte[0];
2134 }
2135 catch (IndexOutOfRangeException)
2136 {
2137 Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name);
2138 ic.Asset.Data = new Byte[0];
2139 }
2140 catch (Exception)
2141 {
2142 Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name);
2143 ic.Asset.Data = new Byte[0];
2144 }
2145 }
2146
2147 ic.reset();
2148 }
2149
2150 #region Inventory RequestData extension
2151
2152 internal class InventoryRequestData : RequestData
2153 {
2154 /// <summary>
2155 /// These are the inventory specific request/response state
2156 /// extensions.
2157 /// </summary>
2158
2159 internal UUID uuid = UUID.Zero;
2160 internal bool HaveInventory = false;
2161 internal ICollection<InventoryFolderImpl> folders = null;
2162 internal ICollection<InventoryItemBase> items = null;
2163 internal UserProfileData userProfile = null;
2164 internal InventoryFolderBase root = null;
2165 internal bool timeout = false;
2166 internal Timer watchDog = new Timer();
2167
2168 internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
2169 : base(request, response, prefix)
2170 {
2171 }
2172
2173 internal void startWD(double interval)
2174 {
2175 Rest.Log.DebugFormat("{0} Setting watchdog", MsgId);
2176 watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut);
2177 watchDog.Interval = interval;
2178 watchDog.AutoReset = false;
2179 watchDog.Enabled = true;
2180 lock (watchDog)
2181 watchDog.Start();
2182
2183 }
2184
2185 internal void stopWD()
2186 {
2187 Rest.Log.DebugFormat("{0} Reset watchdog", MsgId);
2188 lock (watchDog)
2189 watchDog.Stop();
2190 }
2191
2192 /// <summary>
2193 /// This is the callback method required by the inventory watchdog. The
2194 /// requestor issues an inventory request and then blocks until the
2195 /// request completes, or this method signals the monitor.
2196 /// </summary>
2197
2198 private void OnTimeOut(object sender, ElapsedEventArgs args)
2199 {
2200 Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId);
2201 // InventoryRequestData rdata = (InventoryRequestData) sender;
2202 lock (this)
2203 {
2204 this.folders = null;
2205 this.items = null;
2206 this.HaveInventory = false;
2207 this.timeout = true;
2208 Monitor.Pulse(this);
2209 }
2210 }
2211
2212 /// <summary>
2213 /// This is the callback method required by inventory services. The
2214 /// requestor issues an inventory request and then blocks until this
2215 /// method signals the monitor.
2216 /// </summary>
2217
2218 internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items)
2219 {
2220 Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId);
2221 lock (this)
2222 {
2223 if (watchDog.Enabled)
2224 {
2225 this.stopWD();
2226 }
2227 this.folders = folders;
2228 this.items = items;
2229 this.HaveInventory = true;
2230 this.timeout = false;
2231 Monitor.Pulse(this);
2232 }
2233 }
2234 }
2235
2236 #endregion Inventory RequestData extension
2237
2238 /// <summary>
2239 /// This class is used to record and manage the hierarchy
2240 /// constructed from the entity supplied in the request for
2241 /// PUT and POST.
2242 /// </summary>
2243
2244 internal class XmlInventoryCollection : InventoryCollection
2245 {
2246 internal InventoryRequestData rdata;
2247 private Stack<InventoryFolderBase> stk;
2248
2249 internal List<AssetBase> Assets;
2250
2251 internal InventoryItemBase Item;
2252 internal AssetBase Asset;
2253 internal XmlReader xml;
2254
2255 internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF;
2256 internal /*static*/ const uint DefaultNext = 0x82000;
2257 internal /*static*/ const uint DefaultBase = 0x7FFFFFFF;
2258 internal /*static*/ const uint DefaultEveryOne = 0x0;
2259 internal /*static*/ const uint DefaultGroup = 0x0;
2260
2261 internal uint CurrentPermissions = 0x00;
2262 internal uint NextPermissions = 0x00;
2263 internal uint BasePermissions = 0x00;
2264 internal uint EveryOnePermissions = 0x00;
2265 internal uint GroupPermissions = 0x00;
2266
2267 internal XmlInventoryCollection()
2268 {
2269 Folders = new List<InventoryFolderBase>();
2270 Items = new List<InventoryItemBase>();
2271 Assets = new List<AssetBase>();
2272 }
2273
2274 internal void init(InventoryRequestData p_rdata)
2275 {
2276 rdata = p_rdata;
2277 UserID = rdata.uuid;
2278 stk = new Stack<InventoryFolderBase>();
2279 rdata.initXmlReader();
2280 xml = rdata.reader;
2281 initPermissions();
2282 }
2283
2284 internal void initPermissions()
2285 {
2286 CurrentPermissions = DefaultCurrent;
2287 NextPermissions = DefaultNext;
2288 BasePermissions = DefaultBase;
2289 GroupPermissions = DefaultGroup;
2290 EveryOnePermissions = DefaultEveryOne;
2291 }
2292
2293 internal UUID Parent()
2294 {
2295 if (stk.Count != 0)
2296 {
2297 return stk.Peek().ID;
2298 }
2299 else
2300 {
2301 return UUID.Zero;
2302 }
2303 }
2304
2305 internal void Push(InventoryFolderBase folder)
2306 {
2307 stk.Push(folder);
2308 Folders.Add(folder);
2309 reset();
2310 }
2311
2312 internal void Push(InventoryItemBase item)
2313 {
2314 Item = item;
2315 Items.Add(item);
2316 }
2317
2318 internal void Push(AssetBase asset)
2319 {
2320 Asset = asset;
2321 Assets.Add(asset);
2322 }
2323
2324 internal void Pop()
2325 {
2326 stk.Pop();
2327 reset();
2328 }
2329
2330 internal void reset()
2331 {
2332 Item = null;
2333 Asset = null;
2334 initPermissions();
2335 }
2336
2337 internal void Fail(int code, string addendum)
2338 {
2339 rdata.Fail(code, addendum);
2340 }
2341 }
2342 }
2343}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs
deleted file mode 100644
index 81596a3..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs
+++ /dev/null
@@ -1,246 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 */
28
29using System;
30using System.Collections.Generic;
31using System.Reflection;
32using OpenSim.Framework.Servers;
33using OpenSim.Framework.Servers.HttpServer;
34
35namespace OpenSim.ApplicationPlugins.Rest.Inventory
36{
37 public class RestTestServices : IRest
38 {
39 private bool enabled = false;
40 private string qPrefix = "test";
41
42 // A simple constructor is used to handle any once-only
43 // initialization of working classes.
44
45 public RestTestServices()
46 {
47 Rest.Log.InfoFormat("{0} Test services initializing", MsgId);
48 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
49
50 // If a relative path was specified, make it absolute by adding
51 // the standard prefix, e.g. /admin
52
53 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
54 {
55 Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
56 qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
57 Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
58 }
59
60 // Load test cases
61
62 loadTests();
63 foreach (ITest test in tests)
64 {
65 test.Initialize();
66 }
67
68 // Register interface
69
70 Rest.Plugin.AddPathHandler(DoTests,qPrefix,Allocate);
71
72 // Activate
73
74 enabled = true;
75
76 Rest.Log.InfoFormat("{0} Test services initialization complete", MsgId);
77 }
78
79 // Post-construction, pre-enabled initialization opportunity
80 // Not currently exploited.
81
82 public void Initialize()
83 {
84 }
85
86 // Called by the plug-in to halt REST processing. Local processing is
87 // disabled, and control blocks until all current processing has
88 // completed. No new processing will be started
89
90 public void Close()
91 {
92 enabled = false;
93 foreach (ITest test in tests)
94 {
95 test.Close();
96 }
97 Rest.Log.InfoFormat("{0} Test 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, string prefix)
110 {
111 return new RequestData(request, response, prefix);
112 }
113
114 // Inventory Handler
115
116 private void DoTests(RequestData rdata)
117 {
118 if (!enabled)
119 return;
120
121 // Now that we know this is a serious attempt to
122 // access inventory data, we should find out who
123 // is asking, and make sure they are authorized
124 // to do so. We need to validate the caller's
125 // identity before revealing anything about the
126 // status quo. Authenticate throws an exception
127 // via Fail if no identity information is present.
128 //
129 // With the present HTTP server we can't use the
130 // builtin authentication mechanisms because they
131 // would be enforced for all in-bound requests.
132 // Instead we look at the headers ourselves and
133 // handle authentication directly.
134
135 try
136 {
137 if (!rdata.IsAuthenticated)
138 {
139 rdata.Fail(Rest.HttpStatusCodeNotAuthorized,
140 String.Format("user \"{0}\" could not be authenticated", rdata.userName));
141 }
142 }
143 catch (RestException e)
144 {
145 if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
146 {
147 Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
148 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
149 }
150 else
151 {
152 Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
153 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
154 }
155 throw (e);
156 }
157
158 // Check that a test was specified
159
160 if (rdata.Parameters.Length < 1)
161 {
162 Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId);
163 rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters");
164 }
165
166 // Select the test
167
168 foreach (ITest test in tests)
169 {
170 if (!rdata.handled)
171 test.Execute(rdata);
172 }
173 }
174
175 #endregion Interface
176
177 private static bool testsLoaded = false;
178 private static List<Type> classes = new List<Type>();
179 private static List<ITest> tests = new List<ITest>();
180 private static Type[] parms = new Type[0];
181 private static Object[] args = new Object[0];
182
183 static RestTestServices()
184 {
185 Module[] mods = Assembly.GetExecutingAssembly().GetModules();
186 foreach (Module m in mods)
187 {
188 Type[] types = m.GetTypes();
189 foreach (Type t in types)
190 {
191 try
192 {
193 if (t.GetInterface("ITest") != null)
194 {
195 classes.Add(t);
196 }
197 }
198 catch (Exception e)
199 {
200 Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message);
201 }
202 }
203 }
204 }
205
206 /// <summary>
207 /// This routine loads all of the handlers discovered during
208 /// instance initialization. Each handler is responsible for
209 /// registering itself with this handler.
210 /// I was not able to make this code work in a constructor.
211 /// </summary>
212
213 private void loadTests()
214 {
215 lock (tests)
216 {
217 if (!testsLoaded)
218 {
219
220 ConstructorInfo ci;
221 Object ht;
222
223 foreach (Type t in classes)
224 {
225 try
226 {
227 if (t.GetInterface("ITest") != null)
228 {
229 ci = t.GetConstructor(parms);
230 ht = ci.Invoke(args);
231 tests.Add((ITest)ht);
232 Rest.Log.InfoFormat("{0} Test {1} added", MsgId, t);
233 }
234 }
235 catch (Exception e)
236 {
237 Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message);
238 }
239 }
240 testsLoaded = true;
241 }
242 }
243 }
244
245 }
246}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs
deleted file mode 100644
index eafc154..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs
+++ /dev/null
@@ -1,46 +0,0 @@
1/*
2* Copyright (c) Contributors, http://opensimulator.org/
3* See CONTRIBUTORS.TXT for a full list of copyright holders.
4*
5* Redistribution and use in source and binary forms, with or without
6* modification, are permitted provided that the following conditions are met:
7* * Redistributions of source code must retain the above copyright
8* notice, this list of conditions and the following disclaimer.
9* * Redistributions in binary form must reproduce the above copyright
10* notice, this list of conditions and the following disclaimer in the
11* documentation and/or other materials provided with the distribution.
12* * Neither the name of the OpenSimulator Project nor the
13* names of its contributors may be used to endorse or promote products
14* derived from this software without specific prior written permission.
15*
16* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*
27*/
28
29namespace OpenSim.ApplicationPlugins.Rest.Inventory
30{
31
32 /// <summary>
33 /// This interface represents the boundary between the general purpose
34 /// REST plugin handling, and the functionally specific handlers. The
35 /// handler knows only to initialzie and terminate all such handlers
36 /// that it finds.
37 /// </summary>
38
39 internal interface ITest
40 {
41 void Initialize();
42 void Execute(RequestData rdata);
43 void Close();
44 }
45
46}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs
deleted file mode 100644
index 1c1afd0..0000000
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs
+++ /dev/null
@@ -1,204 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using OpenMetaverse;
30using OpenSim.Region.Framework.Scenes;
31
32namespace OpenSim.ApplicationPlugins.Rest.Inventory.Tests
33{
34 public class Remote : ITest
35 {
36 private static readonly int PARM_TESTID = 0;
37 private static readonly int PARM_COMMAND = 1;
38
39 private static readonly int PARM_MOVE_AVATAR = 2;
40 private static readonly int PARM_MOVE_X = 3;
41 private static readonly int PARM_MOVE_Y = 4;
42 private static readonly int PARM_MOVE_Z = 5;
43
44 private bool enabled = false;
45
46 // No constructor code is required.
47
48 public Remote()
49 {
50 Rest.Log.InfoFormat("{0} Remote services constructor", MsgId);
51 }
52
53 // Post-construction, pre-enabled initialization opportunity
54 // Not currently exploited.
55
56 public void Initialize()
57 {
58 enabled = true;
59 Rest.Log.InfoFormat("{0} Remote services initialized", MsgId);
60 }
61
62 // Called by the plug-in to halt REST processing. Local processing is
63 // disabled, and control blocks until all current processing has
64 // completed. No new processing will be started
65
66 public void Close()
67 {
68 enabled = false;
69 Rest.Log.InfoFormat("{0} Remote services closing down", MsgId);
70 }
71
72 // Properties
73
74 internal string MsgId
75 {
76 get { return Rest.MsgId; }
77 }
78
79 // Remote Handler
80 // Key information of interest here is the Parameters array, each
81 // entry represents an element of the URI, with element zero being
82 // the
83
84 public void Execute(RequestData rdata)
85 {
86 if (!enabled) return;
87
88 // If we can't relate to what's there, leave it for others.
89
90 if (rdata.Parameters.Length == 0 || rdata.Parameters[PARM_TESTID] != "remote")
91 return;
92
93 Rest.Log.DebugFormat("{0} REST Remote handler ENTRY", MsgId);
94
95 // Remove the prefix and what's left are the parameters. If we don't have
96 // the parameters we need, fail the request. Parameters do NOT include
97 // any supplied query values.
98
99 if (rdata.Parameters.Length > 1)
100 {
101 switch (rdata.Parameters[PARM_COMMAND].ToLower())
102 {
103 case "move" :
104 DoMove(rdata);
105 break;
106 default :
107 DoHelp(rdata);
108 break;
109 }
110 }
111 else
112 {
113 DoHelp(rdata);
114 }
115 }
116
117 private void DoHelp(RequestData rdata)
118 {
119 rdata.body = Help;
120 rdata.Complete();
121 rdata.Respond("Help");
122 }
123
124 private void DoMove(RequestData rdata)
125 {
126 if (rdata.Parameters.Length < 6)
127 {
128 Rest.Log.WarnFormat("{0} Move: No movement information provided", MsgId);
129 rdata.Fail(Rest.HttpStatusCodeBadRequest, "no movement information provided");
130 }
131 else
132 {
133 string[] names = rdata.Parameters[PARM_MOVE_AVATAR].Split(Rest.CA_SPACE);
134 ScenePresence presence = null;
135 Scene scene = null;
136
137 if (names.Length != 2)
138 {
139 rdata.Fail(Rest.HttpStatusCodeBadRequest,
140 String.Format("invalid avatar name: <{0}>",rdata.Parameters[PARM_MOVE_AVATAR]));
141 }
142
143 Rest.Log.WarnFormat("{0} '{1}' command received for {2} {3}",
144 MsgId, rdata.Parameters[0], names[0], names[1]);
145
146 // The first parameter should be an avatar name, look for the
147 // avatar in the known regions first.
148
149 Rest.main.SceneManager.ForEachScene(delegate(Scene s)
150 {
151 s.ForEachRootScenePresence(delegate(ScenePresence sp)
152 {
153 if (sp.Firstname == names[0] && sp.Lastname == names[1])
154 {
155 scene = s;
156 presence = sp;
157 }
158 });
159 });
160
161 if (presence != null)
162 {
163 Rest.Log.DebugFormat("{0} Move : Avatar {1} located in region {2}",
164 MsgId, rdata.Parameters[PARM_MOVE_AVATAR], scene.RegionInfo.RegionName);
165
166 try
167 {
168 float x = Convert.ToSingle(rdata.Parameters[PARM_MOVE_X]);
169 float y = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Y]);
170 float z = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Z]);
171 Vector3 vector = new Vector3(x, y, z);
172 presence.MoveToTarget(vector, false, false);
173 }
174 catch (Exception e)
175 {
176 rdata.Fail(Rest.HttpStatusCodeBadRequest,
177 String.Format("invalid parameters: {0}", e.Message));
178 }
179 }
180 else
181 {
182 rdata.Fail(Rest.HttpStatusCodeBadRequest,
183 String.Format("avatar {0} not present", rdata.Parameters[PARM_MOVE_AVATAR]));
184 }
185
186 rdata.Complete();
187 rdata.Respond("OK");
188 }
189 }
190
191 private static readonly string Help =
192 "<html>"
193 + "<head><title>Remote Command Usage</title></head>"
194 + "<body>"
195 + "<p>Supported commands are:</p>"
196 + "<dl>"
197 + "<dt>move/avatar-name/x/y/z</dt>"
198 + "<dd>moves the specified avatar to another location</dd>"
199 + "</dl>"
200 + "</body>"
201 + "</html>"
202 ;
203 }
204}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs b/OpenSim/Framework/Console/ConsoleDisplayUtil.cs
index 8b43d42..6417663 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs
+++ b/OpenSim/Framework/Console/ConsoleDisplayUtil.cs
@@ -25,19 +25,24 @@
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */ 26 */
27 27
28namespace OpenSim.ApplicationPlugins.Rest.Inventory 28using System;
29
30namespace OpenSim.Framework.Console
29{ 31{
30 /// <summary> 32 /// <summary>
31 /// This interface represents the boundary between the general purpose 33 /// This will be a set of typical column sizes to allow greater consistency between console commands.
32 /// REST plugin handling, and the functionally specific handlers. The
33 /// handler knows only to initialize and terminate all such handlers
34 /// that it finds. Implementing this interface identifies the class as
35 /// a REST handler implementation.
36 /// </summary> 34 /// </summary>
37 35 public static class ConsoleDisplayUtil
38 internal interface IRest
39 { 36 {
40 void Initialize(); 37 public const int CoordTupleSize = 11;
41 void Close(); 38 public const int PortSize = 5;
39
40 public const int EstateNameSize = 20;
41 public const int ParcelNameSize = 40;
42 public const int RegionNameSize = 20;
43 public const int UserNameSize = 35;
44
45 public const int UuidSize = 36;
46 public const int VectorSize = 15;
42 } 47 }
43} 48} \ No newline at end of file
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs b/OpenSim/Server/Handlers/AgentPreferences/AgentPreferencesServiceConnector.cs
index a88fe88..a581ea2 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs
+++ b/OpenSim/Server/Handlers/AgentPreferences/AgentPreferencesServiceConnector.cs
@@ -25,35 +25,40 @@
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */ 26 */
27 27
28
29using System;
30using Nini.Config;
31using OpenSim.Server.Base;
32using OpenSim.Services.Interfaces;
33using OpenSim.Framework.ServiceAuth;
28using OpenSim.Framework.Servers.HttpServer; 34using OpenSim.Framework.Servers.HttpServer;
35using OpenSim.Server.Handlers.Base;
29 36
30namespace OpenSim.ApplicationPlugins.Rest.Inventory 37namespace OpenSim.Server.Handlers.AgentPreferences
31{ 38{
39 public class AgentPreferencesServiceConnector : ServiceConnector
40 {
41 private IAgentPreferencesService m_AgentPreferencesService;
42 private string m_ConfigName = "AgentPreferencesService";
32 43
33 /// <remarks> 44 public AgentPreferencesServiceConnector(IConfigSource config, IHttpServer server, string configName) :
34 /// The handler delegates are not noteworthy. The allocator allows 45 base(config, server, configName)
35 /// a given handler to optionally subclass the base RequestData 46 {
36 /// structure to carry any locally required per-request state 47 IConfig serverConfig = config.Configs[m_ConfigName];
37 /// needed. 48 if (serverConfig == null)
38 /// </remarks> 49 throw new Exception(String.Format("No section {0} in config file", m_ConfigName));
39 50
40 public delegate void RestMethodHandler(RequestData rdata); 51 string service = serverConfig.GetString("LocalServiceModule", String.Empty);
41 public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path);
42 52
43 /// <summary> 53 if (String.IsNullOrWhiteSpace(service))
44 /// This interface exports the generic plugin-handling services 54 throw new Exception("No LocalServiceModule in config file");
45 /// available to each loaded REST services module (IRest implementation)
46 /// </summary>
47 55
48 internal interface IRestHandler 56 Object[] args = new Object[] { config };
49 { 57 m_AgentPreferencesService = ServerUtils.LoadPlugin<IAgentPreferencesService>(service, args);
50 58
51 string MsgId { get; } 59 IServiceAuth auth = ServiceAuth.Create(config, m_ConfigName); ;
52 string RequestId { get; }
53
54 void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ma);
55 void AddStreamHandler(string httpMethod, string path, RestMethod method);
56 60
61 server.AddStreamHandler(new AgentPreferencesServerPostHandler(m_AgentPreferencesService, auth));
62 }
57 } 63 }
58
59} 64}