aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs1076
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs233
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs59
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs141
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs916
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs43
6 files changed, 1573 insertions, 895 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
index d08f830..6742402 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs
@@ -23,6 +23,7 @@
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
26 */ 27 */
27 28
28using System; 29using System;
@@ -40,16 +41,17 @@ using System.Xml;
40 41
41namespace OpenSim.ApplicationPlugins.Rest.Inventory 42namespace OpenSim.ApplicationPlugins.Rest.Inventory
42{ 43{
44
43 /// <summary> 45 /// <summary>
44 /// This class represents the current REST request. It 46 /// This class represents the current REST request. It
45 /// encapsulates the request/response state and takes care 47 /// encapsulates the request/response state and takes care
46 /// of response generation without exposing the REST handler 48 /// of response generation without exposing the REST handler
47 /// to the actual mechanisms involved. 49 /// to the actual mechanisms involved.
48 /// 50 ///
49 /// This structure is created on entry to the Handler 51 /// This structure is created on entry to the Handler
50 /// method and is disposed of upon return. It is part of 52 /// method and is disposed of upon return. It is part of
51 /// the plug-in infrastructure, rather than the functionally 53 /// the plug-in infrastructure, rather than the functionally
52 /// specific REST handler, and fundamental changes to 54 /// specific REST handler, and fundamental changes to
53 /// this should be reflected in the Rest HandlerVersion. The 55 /// this should be reflected in the Rest HandlerVersion. The
54 /// object is instantiated, and may be extended by, any 56 /// object is instantiated, and may be extended by, any
55 /// given handler. See the inventory handler for an example 57 /// given handler. See the inventory handler for an example
@@ -63,44 +65,109 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
63 /// of it as a transactional req/resp capability. 65 /// of it as a transactional req/resp capability.
64 /// </summary> 66 /// </summary>
65 67
66 internal class RequestData 68 public class RequestData
67 { 69 {
68 // HTTP Server interface data 70
71 // HTTP Server interface data (Received values)
69 72
70 internal OSHttpRequest request = null; 73 internal OSHttpRequest request = null;
71 internal OSHttpResponse response = null; 74 internal OSHttpResponse response = null;
72 internal string qprefix = null; 75 internal string qprefix = null;
73 76
74 // Request lifetime values 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().
75 89
76 internal byte[] buffer = null; 90 internal byte[] buffer = null;
77 internal string body = null; 91 internal string body = null;
78 internal string html = null; 92 internal string bodyType = "text/html";
79 internal string entity = null; 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
80 internal string path = null; 104 internal string path = null;
81 internal string method = null; 105 internal string method = null;
82 internal string statusDescription = null;
83 internal string redirectLocation = null;
84 internal string[] pathNodes = null;
85 internal string[] parameters = null;
86 internal int statusCode = 0;
87 internal bool handled = false;
88 internal LLUUID uuid = LLUUID.Zero;
89 internal Encoding encoding = Rest.Encoding;
90 internal Uri uri = null; 106 internal Uri uri = null;
91 internal string query = null; 107 internal string query = null;
92 internal bool fail = false;
93 internal string hostname = "localhost"; 108 internal string hostname = "localhost";
94 internal int port = 80; 109 internal int port = 80;
95 internal string prefix = Rest.UrlPathSeparator; 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
96 internal bool keepAlive = false; 142 internal bool keepAlive = false;
97 internal bool chunked = false; 143 internal bool chunked = false;
98 144
99 // Authentication related state 145 // XML related state
100 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
101 internal bool authenticated = false; 170 internal bool authenticated = false;
102 // internal string scheme = Rest.AS_DIGEST;
103 // internal string scheme = Rest.AS_BASIC;
104 internal string scheme = null; 171 internal string scheme = null;
105 internal string realm = Rest.Realm; 172 internal string realm = Rest.Realm;
106 internal string domain = null; 173 internal string domain = null;
@@ -114,22 +181,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
114 internal string authPrefix = null; 181 internal string authPrefix = null;
115 internal string userName = String.Empty; 182 internal string userName = String.Empty;
116 internal string userPass = String.Empty; 183 internal string userPass = String.Empty;
117 internal LLUUID client = LLUUID.Zero;
118
119 // XML related state
120
121 internal XmlWriter writer = null;
122 internal XmlReader reader = null;
123
124 // Internal working state
125
126 private StringBuilder sbuilder = new StringBuilder(1024);
127 private MemoryStream xmldata = null;
128
129 private static readonly string[] EmptyPath = { String.Empty };
130 184
131 // Session related tables. These are only needed if QOP is set to "auth-sess" 185 // Session related tables. These are only needed if QOP is set to "auth-sess"
132 // and for now at least, it is not. Session related authentication is of 186 // and for now at least, it is not. Session related authentication is of
133 // questionable merit in the context of REST anyway, but it is, arguably, more 187 // questionable merit in the context of REST anyway, but it is, arguably, more
134 // secure. 188 // secure.
135 189
@@ -145,46 +199,26 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
145 199
146 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*", 200 private static Regex schema = new Regex("^\\s*(?<scheme>\\w+)\\s*.*",
147 RegexOptions.Compiled | RegexOptions.IgnoreCase); 201 RegexOptions.Compiled | RegexOptions.IgnoreCase);
148 202
149 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*", 203 private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
150 RegexOptions.Compiled | RegexOptions.IgnoreCase); 204 RegexOptions.Compiled | RegexOptions.IgnoreCase);
151 205
152 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"", 206 private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"",
153 RegexOptions.Compiled | RegexOptions.IgnoreCase); 207 RegexOptions.Compiled | RegexOptions.IgnoreCase);
154 208
155 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", 209 private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
156 RegexOptions.Compiled | RegexOptions.IgnoreCase); 210 RegexOptions.Compiled | RegexOptions.IgnoreCase);
157 211
158 private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)", 212 private static Regex reuserPass = new Regex("(?<user>[^:]+):(?<pass>[\\S\\s]*)",
159 RegexOptions.Compiled | RegexOptions.IgnoreCase); 213 RegexOptions.Compiled | RegexOptions.IgnoreCase);
160 214
161 // For efficiency, we create static instances of these objects 215 // For efficiency, we create static instances of these objects
162 216
163 private static MD5 md5hash = MD5.Create(); 217 private static MD5 md5hash = MD5.Create();
164 218
165 private static StringComparer sc = StringComparer.OrdinalIgnoreCase; 219 private static StringComparer sc = StringComparer.OrdinalIgnoreCase;
166 220
167 // Constructor 221#region properties
168
169 internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
170 {
171 request = p_request;
172 response = p_response;
173 qprefix = p_qprefix;
174
175 sbuilder.Length = 0;
176
177 encoding = request.ContentEncoding;
178 if (encoding == null)
179 {
180 encoding = Rest.Encoding;
181 }
182
183 method = request.HttpMethod.ToLower();
184 initUrl();
185
186 initParameters(p_qprefix.Length);
187 }
188 222
189 // Just for convenience... 223 // Just for convenience...
190 224
@@ -193,12 +227,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
193 get { return Rest.MsgId; } 227 get { return Rest.MsgId; }
194 } 228 }
195 229
196 // Defer authentication check until requested 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>
197 235
198 internal bool IsAuthenticated 236 internal bool IsAuthenticated
199 { 237 {
200 get 238 get
201 { 239 {
202 if (Rest.Authenticate) 240 if (Rest.Authenticate)
203 { 241 {
204 if (!authenticated) 242 if (!authenticated)
@@ -208,24 +246,87 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
208 246
209 return authenticated; 247 return authenticated;
210 } 248 }
211 else 249 else return true;
212 return true;
213 } 250 }
214 } 251 }
215 252
216 /// <summary> 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 if (encoding == null)
297 {
298 encoding = Rest.Encoding;
299 }
300
301 method = request.HttpMethod.ToLower();
302 initUrl();
303
304 initParameters(p_qprefix.Length);
305
306 }
307
308#endregion constructors
309
310#region authentication_common
311
312 /// <summary>
217 /// The REST handler has requested authentication. Authentication 313 /// The REST handler has requested authentication. Authentication
218 /// is considered to be with respect to the current values for 314 /// is considered to be with respect to the current values for
219 /// Realm, domain, etc. 315 /// Realm, domain, etc.
220 /// 316 ///
221 /// This method checks to see if the current request is already 317 /// This method checks to see if the current request is already
222 /// authenticated for this domain. If it is, then it returns 318 /// authenticated for this domain. If it is, then it returns
223 /// true. If it is not, then it issues a challenge to the client 319 /// true. If it is not, then it issues a challenge to the client
224 /// and responds negatively to the request. 320 /// and responds negatively to the request.
321 ///
322 /// As soon as authentication failure is detected the method calls
323 /// DoChallenge() which terminates the request with REST exception
324 /// for unauthroized access.
225 /// </summary> 325 /// </summary>
226 326
227 private void authenticate() 327 private void authenticate()
228 { 328 {
329
229 string authdata = request.Headers.Get("Authorization"); 330 string authdata = request.Headers.Get("Authorization");
230 string reqscheme = String.Empty; 331 string reqscheme = String.Empty;
231 332
@@ -238,7 +339,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
238 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId); 339 Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId);
239 DoChallenge(); 340 DoChallenge();
240 } 341 }
241 342
242 // So, we have authentication data, now we have to check to 343 // So, we have authentication data, now we have to check to
243 // see what we got and whether or not it is valid for the 344 // see what we got and whether or not it is valid for the
244 // current domain. To do this we need to interpret the data 345 // current domain. To do this we need to interpret the data
@@ -287,6 +388,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
287 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId); 388 Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId);
288 DoChallenge(); 389 DoChallenge();
289 } 390 }
391
290 } 392 }
291 393
292 /// <summary> 394 /// <summary>
@@ -300,16 +402,193 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
300 Flush(); 402 Flush();
301 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is) 403 nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is)
302 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms); 404 Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms);
303 Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); 405 Fail(Rest.HttpStatusCodeNotAuthorized);
304 } 406 }
305 407
306 /// <summary> 408 /// <summary>
307 /// Interpret a BASIC authorization claim 409 /// The Flush() call is here to support a problem encountered with the
308 /// This is here for completeness, it is not used. 410 /// client where an authentication rejection was lost because the rejection
411 /// may flow before the clienthas finished sending us the inbound data stream,
412 /// in which case the client responds to the socket error on out put, and
413 /// never sees the authentication challenge. The client should be fixed,
414 /// because this solution leaves the server prone to DOS attacks. A message
415 /// will be issued whenever flushing occurs. It can be enabled/disabled from
416 /// the configuration file.
417 /// </summary>
418
419 private void Flush()
420 {
421 if (Rest.FlushEnabled)
422 {
423 byte[] dbuffer = new byte[8192];
424 Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId);
425 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
426 }
427 return;
428 }
429
430 // Indicate that authentication is required
431
432 private void Challenge(string scheme, string realm, string domain, string nonce,
433 string opaque, string stale, string alg,
434 string qop, string auth)
435 {
436
437 sbuilder.Length = 0;
438
439 // The service provider can force a particular scheme by
440 // assigning a value to scheme.
441
442 // Basic authentication is pretty simple.
443 // Just specify the realm in question.
444
445 if (scheme == null || scheme == Rest.AS_BASIC)
446 {
447
448 sbuilder.Append(Rest.AS_BASIC);
449
450 if (realm != null)
451 {
452 sbuilder.Append(" realm=\"");
453 sbuilder.Append(realm);
454 sbuilder.Append("\"");
455 }
456 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
457 }
458
459 sbuilder.Length = 0;
460
461 // Digest authentication takes somewhat more
462 // to express.
463
464 if (scheme == null || scheme == Rest.AS_DIGEST)
465 {
466
467 sbuilder.Append(Rest.AS_DIGEST);
468 sbuilder.Append(" ");
469
470 // Specify the effective realm. This should
471 // never be null if we are uthenticating, as it is required for all
472 // authentication schemes. It defines, in conjunction with the
473 // absolute URI information, the domain to which the authentication
474 // applies. It is an arbitrary string. I *believe* this allows an
475 // authentication to apply to disjoint resources within the same
476 // server.
477
478 if (realm != null)
479 {
480 sbuilder.Append("realm=");
481 sbuilder.Append(Rest.CS_DQUOTE);
482 sbuilder.Append(realm);
483 sbuilder.Append(Rest.CS_DQUOTE);
484 sbuilder.Append(Rest.CS_COMMA);
485 }
486
487 // Share our nonce. This is *uniquely* generated each time a 401 is
488 // returned. We do not generate a very sophisticated nonce at the
489 // moment (it's simply a base64 encoded UUID).
490
491 if (nonce != null)
492 {
493 sbuilder.Append("nonce=");
494 sbuilder.Append(Rest.CS_DQUOTE);
495 sbuilder.Append(nonce);
496 sbuilder.Append(Rest.CS_DQUOTE);
497 sbuilder.Append(Rest.CS_COMMA);
498 }
499
500 // The opaque string should be returned by the client unchanged in all
501 // subsequent requests.
502
503 if (opaque != null)
504 {
505 sbuilder.Append("opaque=");
506 sbuilder.Append(Rest.CS_DQUOTE);
507 sbuilder.Append(opaque);
508 sbuilder.Append(Rest.CS_DQUOTE);
509 sbuilder.Append(Rest.CS_COMMA);
510 }
511
512 // This flag indicates that the authentication was rejected because the
513 // included nonce was stale. The server might use timestamp information
514 // in the nonce to determine this. We do not.
515
516 if (stale != null)
517 {
518 sbuilder.Append("stale=");
519 sbuilder.Append(Rest.CS_DQUOTE);
520 sbuilder.Append(stale);
521 sbuilder.Append(Rest.CS_DQUOTE);
522 sbuilder.Append(Rest.CS_COMMA);
523 }
524
525 // Identifies the algorithm used to produce the digest and checksum.
526 // The default is MD5.
527
528 if (alg != null)
529 {
530 sbuilder.Append("algorithm=");
531 sbuilder.Append(alg);
532 sbuilder.Append(Rest.CS_COMMA);
533 }
534
535 // Theoretically QOP is optional, but it is required by a compliant
536 // with current versions of the scheme. In fact IE requires that QOP
537 // be specified and will refuse to authenticate otherwise.
538
539 if (qop != String.Empty)
540 {
541 sbuilder.Append("qop=");
542 sbuilder.Append(Rest.CS_DQUOTE);
543 sbuilder.Append(qop);
544 sbuilder.Append(Rest.CS_DQUOTE);
545 sbuilder.Append(Rest.CS_COMMA);
546 }
547
548 // This parameter allows for arbitrary extensions to the protocol.
549 // Unrecognized values should be simply ignored.
550
551 if (auth != null)
552 {
553 sbuilder.Append(auth);
554 sbuilder.Append(Rest.CS_COMMA);
555 }
556
557 // We don't know the userid that will be used
558 // so we cannot make any authentication domain
559 // assumptions. So the prefix will determine
560 // this.
561
562 sbuilder.Append("domain=");
563 sbuilder.Append(Rest.CS_DQUOTE);
564 sbuilder.Append(qprefix);
565 sbuilder.Append(Rest.CS_DQUOTE);
566
567 // Generate the authenticate header and we're basically
568 // done.
569
570 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
571
572 }
573
574 }
575
576#endregion authentication_common
577
578#region authentication_basic
579
580 /// <summary>
581 /// Interpret a BASIC authorization claim. Some clients can only
582 /// understand this and also expect it to be the first one
583 /// offered. So we do.
584 /// OpenSim also needs this, as it is the only scheme that allows
585 /// authentication using the hashed passwords stored in the
586 /// user database.
309 /// </summary> 587 /// </summary>
310 588
311 private void DoBasic(string authdata) 589 private void DoBasic(string authdata)
312 { 590 {
591
313 string response = null; 592 string response = null;
314 593
315 MatchCollection matches = basicParms.Matches(authdata); 594 MatchCollection matches = basicParms.Matches(authdata);
@@ -320,7 +599,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
320 foreach (Match m in matches) 599 foreach (Match m in matches)
321 { 600 {
322 authparms.Add("response",m.Groups["pval"].Value); 601 authparms.Add("response",m.Groups["pval"].Value);
323 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}", 602 Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}",
324 MsgId, "response", m.Groups["pval"].Value); 603 MsgId, "response", m.Groups["pval"].Value);
325 } 604 }
326 605
@@ -340,38 +619,111 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
340 // Validate against user database 619 // Validate against user database
341 authenticated = Validate(userName,userPass); 620 authenticated = Validate(userName,userPass);
342 } 621 }
622
623 }
624
625 /// <summary>
626 /// This method provides validation in support of the BASIC
627 /// authentication method. This is not normaly expected to be
628 /// used, but is included for completeness (and because I tried
629 /// it first).
630 /// </summary>
631
632 private bool Validate(string user, string pass)
633 {
634
635 Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
636
637 // Both values are required
638
639 if (user == null || pass == null)
640 return false;
641
642 // Eliminate any leading or trailing spaces
643 user = user.Trim();
644
645 return vetPassword(user, pass);
646
343 } 647 }
344 648
345 /// <summary> 649 /// <summary>
650 /// This is used by the BASIC authentication scheme to calculate
651 /// the double hash used by OpenSim to encode user's passwords.
652 /// It returns true, if the supplied password is actually correct.
653 /// If the specified user-id is not recognized, but the password
654 /// matches the God password, then this is accepted as an admin
655 /// session.
656 /// </summary>
657
658 private bool vetPassword(string user, string pass)
659 {
660
661 int x;
662 string HA1;
663 string first;
664 string last;
665
666 // Distinguish the parts, if necessary
667
668 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
669 {
670 first = user.Substring(0,x);
671 last = user.Substring(x+1);
672 }
673 else
674 {
675 first = user;
676 last = String.Empty;
677 }
678
679 UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
680
681 // If we don;t recognize the user id, perhaps it is god?
682
683 if (udata == null)
684 return pass == Rest.GodKey;
685
686 HA1 = HashToString(pass);
687 HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt));
688
689 return (0 == sc.Compare(HA1, udata.PasswordHash));
690
691 }
692
693#endregion authentication_basic
694
695#region authentication_digest
696
697 /// <summary>
346 /// This is an RFC2617 compliant HTTP MD5 Digest authentication 698 /// This is an RFC2617 compliant HTTP MD5 Digest authentication
347 /// implementation. It has been tested with Firefox, Java HTTP client, 699 /// implementation. It has been tested with Firefox, Java HTTP client,
348 /// and Miscrosoft's Internet Explorer V7. 700 /// and Microsoft's Internet Explorer V7.
349 /// </summary> 701 /// </summary>
350 702
351 private void DoDigest(string authdata) 703 private void DoDigest(string authdata)
352 { 704 {
705
353 string response = null; 706 string response = null;
354 707
355 MatchCollection matches = digestParm1.Matches(authdata); 708 // Find all of the values of the for x = "y"
356 709
357 // Collect all of the supplied parameters and store them 710 MatchCollection matches = digestParm1.Matches(authdata);
358 // in a dictionary (for ease of access)
359 711
360 foreach (Match m in matches) 712 foreach (Match m in matches)
361 { 713 {
362 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); 714 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
363 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}", 715 Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}",
364 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); 716 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
365 } 717 }
366 718
367 // And pick up any tokens too 719 // Find all of the values of the for x = y
368 720
369 matches = digestParm2.Matches(authdata); 721 matches = digestParm2.Matches(authdata);
370 722
371 foreach (Match m in matches) 723 foreach (Match m in matches)
372 { 724 {
373 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); 725 authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value);
374 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}", 726 Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}",
375 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); 727 MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value);
376 } 728 }
377 729
@@ -382,10 +734,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
382 734
383 if (authparms.TryGetValue("response", out response)) 735 if (authparms.TryGetValue("response", out response))
384 { 736 {
737
385 string temp = null; 738 string temp = null;
386 739
387 do 740 do
388 { 741 {
742
389 string nck = null; 743 string nck = null;
390 string ncl = null; 744 string ncl = null;
391 745
@@ -406,7 +760,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
406 760
407 if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) 761 if (!authparms.TryGetValue("nonce", out nonce) || nonce == null)
408 { 762 {
409 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); 763 Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
410 break; 764 break;
411 } 765 }
412 766
@@ -417,7 +771,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
417 { 771 {
418 if (temp != opaque) 772 if (temp != opaque)
419 { 773 {
420 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId); 774 Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId);
421 break; 775 break;
422 } 776 }
423 } 777 }
@@ -429,7 +783,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
429 { 783 {
430 if (temp != algorithm) 784 if (temp != algorithm)
431 { 785 {
432 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId); 786 Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId);
433 break; 787 break;
434 } 788 }
435 } 789 }
@@ -438,6 +792,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
438 792
439 if (authparms.TryGetValue("qop", out temp)) 793 if (authparms.TryGetValue("qop", out temp))
440 { 794 {
795
441 qop = temp.ToLower(); // replace with actual value used 796 qop = temp.ToLower(); // replace with actual value used
442 797
443 // if QOP was specified then 798 // if QOP was specified then
@@ -445,7 +800,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
445 800
446 if (!authparms.ContainsKey("cnonce")) 801 if (!authparms.ContainsKey("cnonce"))
447 { 802 {
448 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); 803 Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId);
449 break; 804 break;
450 } 805 }
451 806
@@ -453,7 +808,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
453 808
454 if (!authparms.TryGetValue("nc", out nck) || nck == null) 809 if (!authparms.TryGetValue("nc", out nck) || nck == null)
455 { 810 {
456 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); 811 Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
457 break; 812 break;
458 } 813 }
459 814
@@ -465,7 +820,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
465 820
466 if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) 821 if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
467 { 822 {
468 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); 823 Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
469 break; 824 break;
470 } 825 }
471 cntable[nonce] = nck; 826 cntable[nonce] = nck;
@@ -474,21 +829,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
474 { 829 {
475 lock (cntable) cntable.Add(nonce, nck); 830 lock (cntable) cntable.Add(nonce, nck);
476 } 831 }
832
477 } 833 }
478 else 834 else
479 { 835 {
836
480 qop = String.Empty; 837 qop = String.Empty;
481 838
482 // if QOP was not specified then 839 // if QOP was not specified then
483 // these MUST NOT be present. 840 // these MUST NOT be present.
484 if (authparms.ContainsKey("cnonce")) 841 if (authparms.ContainsKey("cnonce"))
485 { 842 {
486 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); 843 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId);
487 break; 844 break;
488 } 845 }
489 if (authparms.ContainsKey("nc")) 846 if (authparms.ContainsKey("nc"))
490 { 847 {
491 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); 848 Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId);
492 break; 849 break;
493 } 850 }
494 } 851 }
@@ -496,157 +853,38 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
496 // Validate the supplied userid/password info 853 // Validate the supplied userid/password info
497 854
498 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response); 855 authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response);
499 }
500 while (false);
501 }
502 }
503
504 // Indicate that authentication is required
505
506 internal void Challenge(string scheme, string realm, string domain, string nonce,
507 string opaque, string stale, string alg,
508 string qop, string auth)
509 {
510 sbuilder.Length = 0;
511
512 if (scheme == null || scheme == Rest.AS_BASIC)
513 {
514
515 sbuilder.Append(Rest.AS_BASIC);
516
517 if (realm != null)
518 {
519 sbuilder.Append(" realm=\"");
520 sbuilder.Append(realm);
521 sbuilder.Append("\"");
522 }
523 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
524 }
525
526 sbuilder.Length = 0;
527
528 if (scheme == null || scheme == Rest.AS_DIGEST)
529 {
530 sbuilder.Append(Rest.AS_DIGEST);
531 sbuilder.Append(" ");
532
533 if (realm != null)
534 {
535 sbuilder.Append("realm=");
536 sbuilder.Append(Rest.CS_DQUOTE);
537 sbuilder.Append(realm);
538 sbuilder.Append(Rest.CS_DQUOTE);
539 sbuilder.Append(Rest.CS_COMMA);
540 }
541
542 if (nonce != null)
543 {
544 sbuilder.Append("nonce=");
545 sbuilder.Append(Rest.CS_DQUOTE);
546 sbuilder.Append(nonce);
547 sbuilder.Append(Rest.CS_DQUOTE);
548 sbuilder.Append(Rest.CS_COMMA);
549 }
550
551 if (opaque != null)
552 {
553 sbuilder.Append("opaque=");
554 sbuilder.Append(Rest.CS_DQUOTE);
555 sbuilder.Append(opaque);
556 sbuilder.Append(Rest.CS_DQUOTE);
557 sbuilder.Append(Rest.CS_COMMA);
558 }
559
560 if (stale != null)
561 {
562 sbuilder.Append("stale=");
563 sbuilder.Append(Rest.CS_DQUOTE);
564 sbuilder.Append(stale);
565 sbuilder.Append(Rest.CS_DQUOTE);
566 sbuilder.Append(Rest.CS_COMMA);
567 }
568
569 if (alg != null)
570 {
571 sbuilder.Append("algorithm=");
572 sbuilder.Append(alg);
573 sbuilder.Append(Rest.CS_COMMA);
574 }
575 856
576 if (qop != String.Empty) 857 }
577 { 858 while (false);
578 sbuilder.Append("qop=");
579 sbuilder.Append(Rest.CS_DQUOTE);
580 sbuilder.Append(qop);
581 sbuilder.Append(Rest.CS_DQUOTE);
582 sbuilder.Append(Rest.CS_COMMA);
583 }
584
585 if (auth != null)
586 {
587 sbuilder.Append(auth);
588 sbuilder.Append(Rest.CS_COMMA);
589 }
590
591 // We don;t know the userid that will be used
592 // so we cannot make any authentication domain
593 // assumptions. So the prefix will determine
594 // this.
595
596 sbuilder.Append("domain=");
597 sbuilder.Append(Rest.CS_DQUOTE);
598 sbuilder.Append(qprefix);
599 sbuilder.Append(Rest.CS_DQUOTE);
600 859
601 AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
602 } 860 }
603 }
604
605 /// <summary>
606 /// This method provides validation in support of the BASIC
607 /// authentication method. This is not normaly expected to be
608 /// used, but is included for completeness (and because I tried
609 /// it first).
610 /// </summary>
611
612 private bool Validate(string user, string pass)
613 {
614 Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
615
616 // Both values are required
617 861
618 if (user == null || pass == null)
619 return false;
620
621 // Eliminate any leading or trailing spaces
622 user = user.Trim();
623
624 return vetPassword(user, pass);
625 } 862 }
626 863
627 /// <summary> 864 /// <summary>
628 /// This mechanism is used by the digest authetnication mechanism 865 /// This mechanism is used by the digest authentication mechanism
629 /// to return the user's password. In fact, because the OpenSim 866 /// to return the user's password. In fact, because the OpenSim
630 /// user's passwords are already hashed, and the HTTP mechanism 867 /// user's passwords are already hashed, and the HTTP mechanism
631 /// does not supply an open password, the hashed passwords cannot 868 /// does not supply an open password, the hashed passwords cannot
632 /// be used unless the cliemt has used the same salting mechanism 869 /// be used unless the client has used the same salting mechanism
633 /// to has the password before using it in the authentication 870 /// to has the password before using it in the authentication
634 /// algorithm. This is not inconceivable... 871 /// algorithn. This is not inconceivable...
635 /// </summary> 872 /// </summary>
636 873
637 private string getPassword(string user) 874 private string getPassword(string user)
638 { 875 {
876
639 int x; 877 int x;
640 string first; 878 string first;
641 string last; 879 string last;
642 880
643 // Distinguish the parts, if necessary 881 // Distinguish the parts, if necessary
644 882
645 if ((x=user.IndexOf(Rest.C_SPACE)) != -1) 883 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
646 { 884 {
647 first = user.Substring(0,x); 885 first = user.Substring(0,x);
648 last = user.Substring(x+1); 886 last = user.Substring(x+1);
649 } 887 }
650 else 888 else
651 { 889 {
652 first = user; 890 first = user;
@@ -667,53 +905,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
667 Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); 905 Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user);
668 return udata.PasswordHash; 906 return udata.PasswordHash;
669 } 907 }
670 }
671 908
672 /// <summary>
673 /// This is used by the BASIC authentication scheme to calculate
674 /// the double hash used by OpenSim to encode user's passwords.
675 /// It returns true, if the supplied password is actually correct.
676 /// If the specified user-id is not recognized, but the password
677 /// matches the God password, then this is accepted as an admin
678 /// session.
679 /// </summary>
680
681 private bool vetPassword(string user, string pass)
682 {
683 int x;
684 string HA1;
685 string first;
686 string last;
687
688 // Distinguish the parts, if necessary
689
690 if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
691 {
692 first = user.Substring(0,x);
693 last = user.Substring(x+1);
694 }
695 else
696 {
697 first = user;
698 last = String.Empty;
699 }
700
701 UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
702
703 // If we don;t recognize the user id, perhaps it is god?
704
705 if (udata == null)
706 return pass == Rest.GodKey;
707
708 HA1 = HashToString(pass);
709 HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt));
710
711 return (0 == sc.Compare(HA1, udata.PasswordHash));
712 } 909 }
713 910
714 // Validate the request-digest 911 // Validate the request-digest
912
715 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response) 913 private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response)
716 { 914 {
915
717 string patt = null; 916 string patt = null;
718 string payl = String.Empty; 917 string payl = String.Empty;
719 string KDS = null; 918 string KDS = null;
@@ -756,7 +955,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
756 HA2 = HashToString(patt); 955 HA2 = HashToString(patt);
757 956
758 // Generate Digest 957 // Generate Digest
759 958
760 if (qop != String.Empty) 959 if (qop != String.Empty)
761 { 960 {
762 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2); 961 patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2);
@@ -771,10 +970,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
771 // Compare the generated sequence with the original 970 // Compare the generated sequence with the original
772 971
773 return (0 == sc.Compare(KDS, response)); 972 return (0 == sc.Compare(KDS, response));
973
774 } 974 }
775 975
776 private string HashToString(string pattern) 976 private string HashToString(string pattern)
777 { 977 {
978
778 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern); 979 Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern);
779 980
780 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern)); 981 byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern));
@@ -789,62 +990,98 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
789 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString()); 990 Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString());
790 991
791 return sbuilder.ToString(); 992 return sbuilder.ToString();
993
792 } 994 }
793 995
996#endregion authentication_digest
997
998#region service_interface
999
1000 /// <summary>
1001 /// Conditionally set a normal completion code. This allows a normal
1002 /// execution path to default.
1003 /// </summary>
1004
794 internal void Complete() 1005 internal void Complete()
795 { 1006 {
796 statusCode = Rest.HttpStatusCodeOK; 1007 if (statusCode == 0)
797 statusDescription = Rest.HttpStatusDescOK; 1008 {
1009 statusCode = Rest.HttpStatusCodeOK;
1010 }
798 } 1011 }
799 1012
1013 /// <summary>
1014 /// Indicate a functionally-dependent conclusion to the
1015 /// request. See Rest.cs for a list of possible values.
1016 /// </summary>
1017
1018 internal void Complete(int code)
1019 {
1020 statusCode = code;
1021 }
1022
1023 /// <summary>
1024 /// Indicate that a request should be redirected, using
1025 /// the HTTP completion codes. Permanent and temporary
1026 /// redirections may be indicated. The supplied URL is
1027 /// the new location of the resource.
1028 /// </summary>
1029
800 internal void Redirect(string Url, bool temp) 1030 internal void Redirect(string Url, bool temp)
801 { 1031 {
1032
802 redirectLocation = Url; 1033 redirectLocation = Url;
803 1034
804 if (temp) 1035 if (temp)
805 { 1036 {
806 statusCode = Rest.HttpStatusCodeTemporaryRedirect; 1037 statusCode = Rest.HttpStatusCodeTemporaryRedirect;
807 statusDescription = Rest.HttpStatusDescTemporaryRedirect;
808 } 1038 }
809 else 1039 else
810 { 1040 {
811 statusCode = Rest.HttpStatusCodePermanentRedirect; 1041 statusCode = Rest.HttpStatusCodePermanentRedirect;
812 statusDescription = Rest.HttpStatusDescPermanentRedirect;
813 } 1042 }
814 1043
815 Fail(statusCode, statusDescription, true); 1044 Fail(statusCode, String.Empty, true);
1045
816 } 1046 }
817 1047
818 // Fail for an arbitrary reason. Just a failure with 1048 /// <summary>
819 // headers. 1049 /// Fail for an arbitrary reason. Just a failure with
1050 /// headers. The supplied message will be returned in the
1051 /// message body.
1052 /// </summary>
820 1053
821 internal void Fail(int code, string message) 1054 internal void Fail(int code)
822 { 1055 {
823 Fail(code, message, true); 1056 Fail(code, String.Empty, false);
824 } 1057 }
825 1058
826 // More adventurous. This failure also includes a 1059 /// <summary>
827 // specified entity. 1060 /// For the more adventurous. This failure also includes a
1061 /// specified entity to be appended to the code-related
1062 /// status string.
1063 /// </summary>
828 1064
829 internal void Fail(int code, string message, string data) 1065 internal void Fail(int code, string addendum)
830 { 1066 {
831 buffer = null; 1067 Fail(code, addendum, false);
832 body = data;
833 Fail(code, message, false);
834 } 1068 }
835 1069
836 internal void Fail(int code, string message, bool reset) 1070 internal void Fail(int code, string addendum, bool reset)
837 { 1071 {
1072
838 statusCode = code; 1073 statusCode = code;
839 statusDescription = message; 1074 appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code]));
1075
1076 // Add any final addendum to the status information
840 1077
841 if (reset) 1078 if (addendum != String.Empty)
842 { 1079 {
843 buffer = null; 1080 appendStatus(String.Format(addendum));
844 SendHtml(message);
845 body = html;
846 } 1081 }
847 1082
1083 // Help us understand why the request is being rejected
1084
848 if (Rest.DEBUG) 1085 if (Rest.DEBUG)
849 { 1086 {
850 Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId); 1087 Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
@@ -864,42 +1101,61 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
864 1101
865 fail = true; 1102 fail = true;
866 1103
867 Respond("Failure response"); 1104 // Respond to the client's request, tag the response (for the
1105 // benefit of trace) to indicate the reason.
1106
1107 Respond(String.Format("Failure response: ({0}) : {1}",
1108 code, Rest.HttpStatusDesc[code]));
1109
1110 // Finally initialize and the throw a RestException. All of the
1111 // handler's infrastructure knows that this is a "normal"
1112 // completion from a code point-of-view.
868 1113
869 RestException re = new RestException(message+" <"+code+">"); 1114 RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">");
870 1115
871 re.statusCode = code; 1116 re.statusCode = code;
872 re.statusDesc = message; 1117 re.statusDesc = Rest.HttpStatusDesc[code];
873 re.httpmethod = method; 1118 re.httpmethod = method;
874 re.httppath = path; 1119 re.httppath = path;
875 1120
876 throw re; 1121 throw re;
1122
877 } 1123 }
878 1124
879 // Reject this request 1125 // Reject this request
880 1126
881 internal void Reject() 1127 internal void Reject()
882 { 1128 {
883 Fail(Rest.HttpStatusCodeNotImplemented, Rest.HttpStatusDescNotImplemented); 1129 Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)");
884 } 1130 }
885 1131
886 // This MUST be called by an agent handler before it returns 1132 // This MUST be called by an agent handler before it returns
887 // control to Handle, otherwise the request will be ignored. 1133 // control to Handle, otherwise the request will be ignored.
888 // This is called implciitly for the REST stream handlers and 1134 // This is called implciitly for the REST stream handlers and
889 // is harmless if it is called twice. 1135 // is harmless if it is called twice.
890 1136
891 internal virtual bool Respond(string reason) 1137 internal virtual bool Respond(string reason)
892 { 1138 {
1139
1140
893 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason); 1141 Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason);
894 1142
1143 // We do this to try and make multiple Respond requests harmless,
1144 // as it is sometimes convenient to isse a response without
1145 // certain knowledge that it has not previously been done.
1146
895 if (!handled) 1147 if (!handled)
896 { 1148 {
1149
897 Rest.Log.DebugFormat("{0} Generating Response", MsgId); 1150 Rest.Log.DebugFormat("{0} Generating Response", MsgId);
898 Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); 1151 Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);
899 1152
900 // A Head request can NOT have a body! 1153 // A Head request can NOT have a body! So don't waste time on
1154 // formatting if we're going to reject it anyway!
1155
901 if (method != Rest.HEAD) 1156 if (method != Rest.HEAD)
902 { 1157 {
1158
903 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); 1159 Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
904 1160
905 // If the writer is non-null then we know that an XML 1161 // If the writer is non-null then we know that an XML
@@ -924,18 +1180,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
924 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId); 1180 Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId);
925 } 1181 }
926 1182
927 // If buffer != null, then we assume that 1183 if (buffer == null && body != null)
928 // this has already been done some other
929 // way. For example, transfer encoding might
930 // have been done.
931
932 if (buffer == null)
933 { 1184 {
934 if (body != null && body.Length > 0) 1185 buffer = encoding.GetBytes(body);
935 { 1186 AddHeader("Content-Type",bodyType);
936 Rest.Log.DebugFormat("{0} String-based entity", MsgId);
937 buffer = encoding.GetBytes(body);
938 }
939 } 1187 }
940 1188
941 // OK, if the buffer contains something, regardless of how 1189 // OK, if the buffer contains something, regardless of how
@@ -944,21 +1192,37 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
944 if (buffer != null) 1192 if (buffer != null)
945 { 1193 {
946 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); 1194 Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
947 response.ContentLength64 = buffer.Length;
948 } 1195 }
949 else 1196 else
950 { 1197 {
951 response.ContentLength64 = 0; 1198 if (statusBody != String.Empty)
1199 {
1200 statusBody += Rest.statusTail;
1201 buffer = encoding.GetBytes(statusBody);
1202 AddHeader("Content-Type","text/html");
1203 }
1204 else
1205 {
1206 statusBody = Rest.statusHead;
1207 appendStatus(String.Format(": ({0}) {1}",
1208 statusCode, Rest.HttpStatusDesc[statusCode]));
1209 statusBody += Rest.statusTail;
1210 buffer = encoding.GetBytes(statusBody);
1211 AddHeader("Content-Type","text/html");
1212 }
952 } 1213 }
953 1214
1215 response.ContentLength64 = buffer.Length;
1216
954 if (response.Headers.Get("Content-Encoding") == null) 1217 if (response.Headers.Get("Content-Encoding") == null)
955 response.ContentEncoding = encoding; 1218 response.ContentEncoding = encoding;
956 1219
957 response.SendChunked = chunked; 1220 response.SendChunked = chunked;
958 response.KeepAlive = keepAlive; 1221 response.KeepAlive = keepAlive;
1222
959 } 1223 }
960 1224
961 // Set the status code & description. If nothing has been stored, 1225 // Set the status code & description. If nothing has been stored,
962 // we consider that a success. 1226 // we consider that a success.
963 1227
964 if (statusCode == 0) 1228 if (statusCode == 0)
@@ -972,7 +1236,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
972 1236
973 // For a redirect we need to set the relocation header accordingly 1237 // For a redirect we need to set the relocation header accordingly
974 1238
975 if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect || 1239 if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect ||
976 response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect) 1240 response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect)
977 { 1241 {
978 Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation); 1242 Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation);
@@ -981,18 +1245,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
981 1245
982 // And include the status description if provided. 1246 // And include the status description if provided.
983 1247
984 if (statusDescription != null) 1248 response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode];
985 {
986 Rest.Log.DebugFormat("{0} Status description is {1}", MsgId, statusDescription);
987 response.StatusDescription = statusDescription;
988 }
989 1249
990 // Finally we send back our response. 1250 // Finally we send back our response.
991 1251
992 // We've left the setting of handled' until the 1252 // We've left the setting of handled' until the
993 // last minute because the header settings included 1253 // last minute because the header settings included
994 // above are pretty harmless. But everything from 1254 // above are pretty harmless. But everything from
995 // here on down probably leaves the response 1255 // here on down probably leaves the response
996 // element unusable by anyone else. 1256 // element unusable by anyone else.
997 1257
998 handled = true; 1258 handled = true;
@@ -1007,7 +1267,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1007 1267
1008 if (buffer != null && buffer.Length != 0) 1268 if (buffer != null && buffer.Length != 0)
1009 { 1269 {
1010 Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", 1270 Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
1011 MsgId, buffer.Length, encoding.GetString(buffer)); 1271 MsgId, buffer.Length, encoding.GetString(buffer));
1012 response.OutputStream.Write(buffer, 0, buffer.Length); 1272 response.OutputStream.Write(buffer, 0, buffer.Length);
1013 } 1273 }
@@ -1016,35 +1276,36 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1016 1276
1017 Rest.Log.DebugFormat("{0} Closing output stream", MsgId); 1277 Rest.Log.DebugFormat("{0} Closing output stream", MsgId);
1018 response.OutputStream.Close(); 1278 response.OutputStream.Close();
1279
1019 } 1280 }
1020 1281
1021 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason); 1282 Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason);
1022 1283
1023 return handled; 1284 return handled;
1285
1024 } 1286 }
1025 1287
1026 // Add a header to the table. We need to allow 1288 /// <summary>
1027 // multiple instances of many of the headers. 1289 /// These methods allow a service provider to manipulate the
1028 // If the 1290 /// request/response headers. The DumpHeaders method is intended
1291 /// for problem diagnosis.
1292 /// </summary>
1029 1293
1030 internal void AddHeader(string hdr, string data) 1294 internal void AddHeader(string hdr, string data)
1031 { 1295 {
1032 if (Rest.DEBUG) 1296 if (Rest.DEBUG)
1033 { 1297 {
1034 Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", 1298 Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
1035 MsgId, hdr, data); 1299 MsgId, hdr, data);
1036 if (response.Headers.Get(hdr) != null) 1300 if (response.Headers.Get(hdr) != null)
1037 { 1301 {
1038 Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>", 1302 Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>",
1039 MsgId, hdr); 1303 MsgId, hdr);
1040 } 1304 }
1041 } 1305 }
1042 response.Headers.Add(hdr, data); 1306 response.Headers.Add(hdr, data);
1043 } 1307 }
1044 1308
1045 // Keep explicit track of any headers which
1046 // are to be removed.
1047
1048 internal void RemoveHeader(string hdr) 1309 internal void RemoveHeader(string hdr)
1049 { 1310 {
1050 if (Rest.DEBUG) 1311 if (Rest.DEBUG)
@@ -1052,29 +1313,69 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1052 Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); 1313 Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
1053 if (response.Headers.Get(hdr) == null) 1314 if (response.Headers.Get(hdr) == null)
1054 { 1315 {
1055 Rest.Log.DebugFormat("{0} No such header existed", 1316 Rest.Log.DebugFormat("{0} No such header existed",
1056 MsgId, hdr); 1317 MsgId, hdr);
1057 } 1318 }
1058 } 1319 }
1059 response.Headers.Remove(hdr); 1320 response.Headers.Remove(hdr);
1060 } 1321 }
1061 1322
1062 /// <summary>
1063 /// Dump headers that will be generated in the response
1064 /// </summary>
1065
1066 internal void DumpHeaders() 1323 internal void DumpHeaders()
1067 { 1324 {
1068 if (Rest.DEBUG) 1325 if (Rest.DEBUG)
1069 { 1326 {
1070 for (int i=0;i<response.Headers.Count;i++) 1327 for (int i=0;i<response.Headers.Count;i++)
1071 { 1328 {
1072 Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i, 1329 Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
1073 response.Headers.Get(i)); 1330 response.Headers.Get(i));
1074 } 1331 }
1075 } 1332 }
1076 } 1333 }
1077 1334
1335 // Setup the XML writer for output
1336
1337 internal void initXmlWriter()
1338 {
1339 XmlWriterSettings settings = new XmlWriterSettings();
1340 xmldata = new MemoryStream();
1341 settings.Indent = true;
1342 settings.IndentChars = " ";
1343 settings.Encoding = encoding;
1344 settings.CloseOutput = false;
1345 settings.OmitXmlDeclaration = true;
1346 settings.ConformanceLevel = ConformanceLevel.Fragment;
1347 writer = XmlWriter.Create(xmldata, settings);
1348 }
1349
1350 internal void initXmlReader()
1351 {
1352
1353 XmlReaderSettings settings = new XmlReaderSettings();
1354
1355 settings.ConformanceLevel = ConformanceLevel.Fragment;
1356 settings.IgnoreComments = true;
1357 settings.IgnoreWhitespace = true;
1358 settings.IgnoreProcessingInstructions = true;
1359 settings.ValidationType = ValidationType.None;
1360
1361 reader = XmlReader.Create(request.InputStream,settings);
1362
1363 }
1364
1365 internal void appendStatus(string msg)
1366 {
1367 if (statusBody == String.Empty)
1368 {
1369 statusBody = String.Format(Rest.statusHead, request.HttpMethod);
1370 }
1371
1372 statusBody = String.Format("{0} {1}", statusBody, msg);
1373 }
1374
1375#endregion service_interface
1376
1377#region internal_methods
1378
1078 /// <summary> 1379 /// <summary>
1079 /// Helper methods for deconstructing and reconstructing 1380 /// Helper methods for deconstructing and reconstructing
1080 /// URI path data. 1381 /// URI path data.
@@ -1082,6 +1383,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1082 1383
1083 private void initUrl() 1384 private void initUrl()
1084 { 1385 {
1386
1085 uri = request.Url; 1387 uri = request.Url;
1086 1388
1087 if (query == null) 1389 if (query == null)
@@ -1096,19 +1398,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1096 { 1398 {
1097 path = uri.AbsolutePath; 1399 path = uri.AbsolutePath;
1098 if (path.EndsWith(Rest.UrlPathSeparator)) 1400 if (path.EndsWith(Rest.UrlPathSeparator))
1099 path = path.Substring(0, path.Length-1); 1401 path = path.Substring(0,path.Length-1);
1100 } 1402 }
1101 1403
1102 // If we succeeded in getting a path, perform any 1404 // If we succeeded in getting a path, perform any
1103 // additional pre-processing required. 1405 // additional pre-processing required.
1104 1406
1105 if (path != null) 1407 if (path != null)
1106 { 1408 {
1107 if (Rest.ExtendedEscape) 1409 if (Rest.ExtendedEscape)
1108 { 1410 {
1109 // Handle "+". Not a standard substitution, but 1411 // Handle "+". Not a standard substitution, but
1110 // common enough... 1412 // common enough...
1111 path = path.Replace(Rest.C_PLUS, Rest.C_SPACE); 1413 path = path.Replace(Rest.C_PLUS,Rest.C_SPACE);
1112 } 1414 }
1113 pathNodes = path.Split(Rest.CA_PATHSEP); 1415 pathNodes = path.Split(Rest.CA_PATHSEP);
1114 } 1416 }
@@ -1126,10 +1428,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1126 1428
1127 hostname = uri.Host; 1429 hostname = uri.Host;
1128 port = uri.Port; 1430 port = uri.Port;
1431
1129 } 1432 }
1130 1433
1131 internal int initParameters(int prfxlen) 1434 private int initParameters(int prfxlen)
1132 { 1435 {
1436
1133 if (prfxlen < path.Length-1) 1437 if (prfxlen < path.Length-1)
1134 { 1438 {
1135 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP); 1439 parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP);
@@ -1138,139 +1442,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1138 { 1442 {
1139 parameters = new string[0]; 1443 parameters = new string[0];
1140 } 1444 }
1141 1445
1142 // Generate a debug list of the decoded parameters 1446 // Generate a debug list of the decoded parameters
1143 1447
1144 if (Rest.DEBUG && prfxlen < path.Length-1) 1448 if (Rest.DEBUG && prfxlen < path.Length-1)
1145 { 1449 {
1146 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen)); 1450 Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen));
1147 for (int i = 0; i < parameters.Length; i++) 1451 for (int i = 0; i < parameters.Length; i++)
1148 { 1452 {
1149 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]); 1453 Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]);
1150 } 1454 }
1151 } 1455 }
1152 1456
1153 return parameters.Length; 1457 return parameters.Length;
1154 }
1155 1458
1156 internal string[] PathNodes
1157 {
1158 get
1159 {
1160 if (pathNodes == null)
1161 {
1162 initUrl();
1163 }
1164 return pathNodes;
1165 }
1166 } 1459 }
1167 1460
1168 internal string BuildUrl(int first, int last) 1461#endregion internal_methods
1169 {
1170 if (pathNodes == null)
1171 {
1172 initUrl();
1173 }
1174
1175 if (first < 0)
1176 {
1177 first = first + pathNodes.Length;
1178 }
1179 1462
1180 if (last < 0)
1181 {
1182 last = last + pathNodes.Length;
1183 if (last < 0)
1184 {
1185 return Rest.UrlPathSeparator;
1186 }
1187 }
1188
1189 sbuilder.Length = 0;
1190 sbuilder.Append(Rest.UrlPathSeparator);
1191
1192 if (first <= last)
1193 {
1194 for (int i = first; i <= last; i++)
1195 {
1196 sbuilder.Append(pathNodes[i]);
1197 sbuilder.Append(Rest.UrlPathSeparator);
1198 }
1199 }
1200 else
1201 {
1202 for (int i = last; i >= first; i--)
1203 {
1204 sbuilder.Append(pathNodes[i]);
1205 sbuilder.Append(Rest.UrlPathSeparator);
1206 }
1207 }
1208
1209 return sbuilder.ToString();
1210 }
1211
1212 // Setup the XML writer for output
1213
1214 internal void initXmlWriter()
1215 {
1216 XmlWriterSettings settings = new XmlWriterSettings();
1217 xmldata = new MemoryStream();
1218 settings.Indent = true;
1219 settings.IndentChars = " ";
1220 settings.Encoding = encoding;
1221 settings.CloseOutput = false;
1222 settings.OmitXmlDeclaration = true;
1223 settings.ConformanceLevel = ConformanceLevel.Fragment;
1224 writer = XmlWriter.Create(xmldata, settings);
1225 }
1226
1227 internal void initXmlReader()
1228 {
1229 XmlReaderSettings settings = new XmlReaderSettings();
1230
1231 settings.ConformanceLevel = ConformanceLevel.Fragment;
1232 settings.IgnoreComments = true;
1233 settings.IgnoreWhitespace = true;
1234 settings.IgnoreProcessingInstructions = true;
1235 settings.ValidationType = ValidationType.None;
1236
1237 reader = XmlReader.Create(request.InputStream,settings);
1238 }
1239
1240 private void Flush()
1241 {
1242 byte[] dbuffer = new byte[8192];
1243 while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0);
1244 }
1245
1246 // This allows us to make errors a bit more apparent in REST
1247
1248 internal void SendHtml(string text)
1249 {
1250 SendHtml("OpenSim REST Interface 1.0", text);
1251 }
1252
1253 internal void SendHtml(string title, string text)
1254 {
1255 AddHeader(Rest.HttpHeaderContentType, "text/html");
1256 sbuilder.Length = 0;
1257
1258 sbuilder.Append("<html>");
1259 sbuilder.Append("<head>");
1260 sbuilder.Append("<title>");
1261 sbuilder.Append(title);
1262 sbuilder.Append("</title>");
1263 sbuilder.Append("</head>");
1264
1265 sbuilder.Append("<body>");
1266 sbuilder.Append("<br />");
1267 sbuilder.Append("<p>");
1268 sbuilder.Append(text);
1269 sbuilder.Append("</p>");
1270 sbuilder.Append("</body>");
1271 sbuilder.Append("</html>");
1272
1273 html = sbuilder.ToString();
1274 }
1275 } 1463 }
1276} 1464}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
index e8c0ee8..fa22481 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs
@@ -23,7 +23,7 @@
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 * 26 *
27 */ 27 */
28 28
29using System; 29using System;
@@ -38,9 +38,11 @@ using Nini.Config;
38 38
39namespace OpenSim.ApplicationPlugins.Rest.Inventory 39namespace OpenSim.ApplicationPlugins.Rest.Inventory
40{ 40{
41
41 public class Rest 42 public class Rest
42 { 43 {
43 internal static readonly log4net.ILog Log = 44
45 internal static readonly log4net.ILog Log =
44 log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 46 log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
45 47
46 internal static bool DEBUG = Log.IsDebugEnabled; 48 internal static bool DEBUG = Log.IsDebugEnabled;
@@ -53,7 +55,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
53 /// RestHandler class during start-up. 55 /// RestHandler class during start-up.
54 /// </summary> 56 /// </summary>
55 57
56 internal static RestHandler Plugin = null; 58 internal static IRestHandler Plugin = null;
57 internal static OpenSimBase main = null; 59 internal static OpenSimBase main = null;
58 internal static CommunicationsManager Comms = null; 60 internal static CommunicationsManager Comms = null;
59 internal static IInventoryServices InventoryServices = null; 61 internal static IInventoryServices InventoryServices = null;
@@ -66,10 +68,47 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
66 internal static bool Secure = true; 68 internal static bool Secure = true;
67 internal static bool ExtendedEscape = true; 69 internal static bool ExtendedEscape = true;
68 internal static bool DumpAsset = false; 70 internal static bool DumpAsset = false;
71 internal static bool Fill = true;
72 internal static bool FlushEnabled = true;
69 internal static string Realm = "REST"; 73 internal static string Realm = "REST";
70 internal static int CreationDate = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
71 internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 74 internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
72 75
76 /// <summary>
77 /// HTTP requires that status information be generated for PUT
78 /// and POST opertaions. This is in support of that. The
79 /// operation verb gets substituted into the first string,
80 /// and the completion code is inserted into the tail. The
81 /// strings are put here to encourage consistency.
82 /// </summary>
83
84 internal static string statusHead = "<html><body><title>{0} status</title><break>";
85 internal static string statusTail = "</body></html>";
86
87 internal static Dictionary<int,string> HttpStatusDesc;
88
89 static Rest()
90 {
91 HttpStatusDesc = new Dictionary<int,string>();
92 if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length)
93 {
94 Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match");
95 throw new Exception("HTTP Status array discrepancy");
96 }
97
98 // Repackage the data into something more tractable. The sparse
99 // nature of HTTP return codes makes an array a bad choice.
100
101 for (int i=0; i<HttpStatusCodeArray.Length; i++)
102 {
103 HttpStatusDesc.Add(HttpStatusCodeArray[i], HttpStatusDescArray[i]);
104 }
105 }
106
107 internal static int CreationDate
108 {
109 get { return (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; }
110 }
111
73 internal static string MsgId 112 internal static string MsgId
74 { 113 {
75 get { return Plugin.MsgId; } 114 get { return Plugin.MsgId; }
@@ -104,7 +143,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
104 /// supported by all servers. See Respond 143 /// supported by all servers. See Respond
105 /// to see how these are handled. 144 /// to see how these are handled.
106 /// </summary> 145 /// </summary>
107 146
108 // REST AGENT 1.0 interpretations 147 // REST AGENT 1.0 interpretations
109 public const string GET = "get"; // information retrieval - server state unchanged 148 public const string GET = "get"; // information retrieval - server state unchanged
110 public const string HEAD = "head"; // same as get except only the headers are returned. 149 public const string HEAD = "head"; // same as get except only the headers are returned.
@@ -136,7 +175,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
136 public static readonly char C_PERIOD = '.'; 175 public static readonly char C_PERIOD = '.';
137 public static readonly char C_COMMA = ','; 176 public static readonly char C_COMMA = ',';
138 public static readonly char C_DQUOTE = '"'; 177 public static readonly char C_DQUOTE = '"';
139 178
140 public static readonly string CS_SPACE = " "; 179 public static readonly string CS_SPACE = " ";
141 public static readonly string CS_SLASH = "/"; 180 public static readonly string CS_SLASH = "/";
142 public static readonly string CS_PATHSEP = "/"; 181 public static readonly string CS_PATHSEP = "/";
@@ -145,7 +184,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
145 public static readonly string CS_PERIOD = "."; 184 public static readonly string CS_PERIOD = ".";
146 public static readonly string CS_COMMA = ","; 185 public static readonly string CS_COMMA = ",";
147 public static readonly string CS_DQUOTE = "\""; 186 public static readonly string CS_DQUOTE = "\"";
148 187
149 public static readonly char[] CA_SPACE = { C_SPACE }; 188 public static readonly char[] CA_SPACE = { C_SPACE };
150 public static readonly char[] CA_SLASH = { C_SLASH }; 189 public static readonly char[] CA_SLASH = { C_SLASH };
151 public static readonly char[] CA_PATHSEP = { C_PATHSEP }; 190 public static readonly char[] CA_PATHSEP = { C_PATHSEP };
@@ -203,53 +242,97 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
203 public const int HttpStatusCodeGatewayTimeout = 504; 242 public const int HttpStatusCodeGatewayTimeout = 504;
204 public const int HttpStatusCodeHttpVersionError = 505; 243 public const int HttpStatusCodeHttpVersionError = 505;
205 244
206 // HTTP Status Descriptions (in status code order) 245 public static readonly int[] HttpStatusCodeArray = {
246 HttpStatusCodeContinue,
247 HttpStatusCodeSwitchingProtocols,
248 HttpStatusCodeOK,
249 HttpStatusCodeCreated,
250 HttpStatusCodeAccepted,
251 HttpStatusCodeNonAuthoritative,
252 HttpStatusCodeNoContent,
253 HttpStatusCodeResetContent,
254 HttpStatusCodePartialContent,
255 HttpStatusCodeMultipleChoices,
256 HttpStatusCodePermanentRedirect,
257 HttpStatusCodeFound,
258 HttpStatusCodeSeeOther,
259 HttpStatusCodeNotModified,
260 HttpStatusCodeUseProxy,
261 HttpStatusCodeReserved306,
262 HttpStatusCodeTemporaryRedirect,
263 HttpStatusCodeBadRequest,
264 HttpStatusCodeNotAuthorized,
265 HttpStatusCodePaymentRequired,
266 HttpStatusCodeForbidden,
267 HttpStatusCodeNotFound,
268 HttpStatusCodeMethodNotAllowed,
269 HttpStatusCodeNotAcceptable,
270 HttpStatusCodeProxyAuthenticate,
271 HttpStatusCodeTimeOut,
272 HttpStatusCodeConflict,
273 HttpStatusCodeGone,
274 HttpStatusCodeLengthRequired,
275 HttpStatusCodePreconditionFailed,
276 HttpStatusCodeEntityTooLarge,
277 HttpStatusCodeUriTooLarge,
278 HttpStatusCodeUnsupportedMedia,
279 HttpStatusCodeRangeNotSatsified,
280 HttpStatusCodeExpectationFailed,
281 HttpStatusCodeServerError,
282 HttpStatusCodeNotImplemented,
283 HttpStatusCodeBadGateway,
284 HttpStatusCodeServiceUnavailable,
285 HttpStatusCodeGatewayTimeout,
286 HttpStatusCodeHttpVersionError
287 };
207 288
208 public const string HttpStatusDescContinue = "Continue Request"; // 100 289 // HTTP Status Descriptions (in status code order)
209 public const string HttpStatusDescSwitchingProtocols = "Switching Protocols"; // 101 290 // This array must be kept strictly consistent with respect
210 291 // to the status code array above.
211 public const string HttpStatusDescOK = "OK"; 292
212 public const string HttpStatusDescCreated = "CREATED"; 293 public static readonly string[] HttpStatusDescArray = {
213 public const string HttpStatusDescAccepted = "ACCEPTED"; 294 "Continue Request",
214 public const string HttpStatusDescNonAuthoritative = "NON-AUTHORITATIVE INFORMATION"; 295 "Switching Protocols",
215 public const string HttpStatusDescNoContent = "NO CONTENT"; 296 "OK",
216 public const string HttpStatusDescResetContent = "RESET CONTENT"; 297 "CREATED",
217 public const string HttpStatusDescPartialContent = "PARTIAL CONTENT"; 298 "ACCEPTED",
218 299 "NON-AUTHORITATIVE INFORMATION",
219 public const string HttpStatusDescMultipleChoices = "MULTIPLE CHOICES"; 300 "NO CONTENT",
220 public const string HttpStatusDescPermanentRedirect = "PERMANENT REDIRECT"; 301 "RESET CONTENT",
221 public const string HttpStatusDescFound = "FOUND"; 302 "PARTIAL CONTENT",
222 public const string HttpStatusDescSeeOther = "SEE OTHER"; 303 "MULTIPLE CHOICES",
223 public const string HttpStatusDescNotModified = "NOT MODIFIED"; 304 "PERMANENT REDIRECT",
224 public const string HttpStatusDescUseProxy = "USE PROXY"; 305 "FOUND",
225 public const string HttpStatusDescReserved306 = "RESERVED CODE 306"; 306 "SEE OTHER",
226 public const string HttpStatusDescTemporaryRedirect = "TEMPORARY REDIRECT"; 307 "NOT MODIFIED",
227 308 "USE PROXY",
228 public const string HttpStatusDescBadRequest = "BAD REQUEST"; 309 "RESERVED CODE 306",
229 public const string HttpStatusDescNotAuthorized = "NOT AUTHORIZED"; 310 "TEMPORARY REDIRECT",
230 public const string HttpStatusDescPaymentRequired = "PAYMENT REQUIRED"; 311 "BAD REQUEST",
231 public const string HttpStatusDescForbidden = "FORBIDDEN"; 312 "NOT AUTHORIZED",
232 public const string HttpStatusDescNotFound = "NOT FOUND"; 313 "PAYMENT REQUIRED",
233 public const string HttpStatusDescMethodNotAllowed = "METHOD NOT ALLOWED"; 314 "FORBIDDEN",
234 public const string HttpStatusDescNotAcceptable = "NOT ACCEPTABLE"; 315 "NOT FOUND",
235 public const string HttpStatusDescProxyAuthenticate = "PROXY AUTHENTICATION REQUIRED"; 316 "METHOD NOT ALLOWED",
236 public const string HttpStatusDescTimeOut = "TIMEOUT"; 317 "NOT ACCEPTABLE",
237 public const string HttpStatusDescConflict = "CONFLICT"; 318 "PROXY AUTHENTICATION REQUIRED",
238 public const string HttpStatusDescGone = "GONE"; 319 "TIMEOUT",
239 public const string HttpStatusDescLengthRequired = "LENGTH REQUIRED"; 320 "CONFLICT",
240 public const string HttpStatusDescPreconditionFailed = "PRECONDITION FAILED"; 321 "GONE",
241 public const string HttpStatusDescEntityTooLarge = "ENTITY TOO LARGE"; 322 "LENGTH REQUIRED",
242 public const string HttpStatusDescUriTooLarge = "URI TOO LARGE"; 323 "PRECONDITION FAILED",
243 public const string HttpStatusDescUnsupportedMedia = "UNSUPPORTED MEDIA"; 324 "ENTITY TOO LARGE",
244 public const string HttpStatusDescRangeNotSatisfied = "RANGE NOT SATISFIED"; 325 "URI TOO LARGE",
245 public const string HttpStatusDescExpectationFailed = "EXPECTATION FAILED"; 326 "UNSUPPORTED MEDIA",
246 327 "RANGE NOT SATISFIED",
247 public const string HttpStatusDescServerError = "SERVER ERROR"; 328 "EXPECTATION FAILED",
248 public const string HttpStatusDescNotImplemented = "NOT IMPLEMENTED"; 329 "SERVER ERROR",
249 public const string HttpStatusDescBadGateway = "BAD GATEWAY"; 330 "NOT IMPLEMENTED",
250 public const string HttpStatusDescServiceUnavailable = "SERVICE UNAVAILABLE"; 331 "BAD GATEWAY",
251 public const string HttpStatusDescGatewayTimeout = "GATEWAY TIMEOUT"; 332 "SERVICE UNAVAILABLE",
252 public const string HttpStatusDescHttpVersionError = "HTTP VERSION NOT SUPPORTED"; 333 "GATEWAY TIMEOUT",
334 "HTTP VERSION NOT SUPPORTED"
335 };
253 336
254 // HTTP Headers 337 // HTTP Headers
255 338
@@ -309,7 +392,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
309 public const string AS_DIGEST = "Digest"; 392 public const string AS_DIGEST = "Digest";
310 393
311 /// Supported Digest algorithms 394 /// Supported Digest algorithms
312 395
313 public const string Digest_MD5 = "MD5"; // assumedd efault if omitted 396 public const string Digest_MD5 = "MD5"; // assumedd efault if omitted
314 public const string Digest_MD5Sess = "MD5-sess"; 397 public const string Digest_MD5Sess = "MD5-sess";
315 398
@@ -357,7 +440,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
357 int val = 0; 440 int val = 0;
358 int sum = 0; 441 int sum = 0;
359 string tmp = null; 442 string tmp = null;
360 443
361 if (hex != null) 444 if (hex != null)
362 { 445 {
363 tmp = hex.ToLower(); 446 tmp = hex.ToLower();
@@ -372,40 +455,21 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
372 } 455 }
373 456
374 return sum; 457 return sum;
375 }
376
377 public static string Int2Hex8(int val)
378 {
379 string res = String.Empty;
380 for (int i = 0; i < 8; i++)
381 {
382 res = (val % 16) + res;
383 val = val / 16;
384 }
385 return res;
386 }
387
388 public static string ToHex32(int val)
389 {
390 return String.Empty;
391 }
392 458
393 public static string ToHex32(string val)
394 {
395 return String.Empty;
396 } 459 }
397 460
398 // Nonce management 461 // Nonce management
399 462
400 public static string NonceGenerator() 463 public static string NonceGenerator()
401 { 464 {
402 return StringToBase64(Guid.NewGuid().ToString()); 465 return StringToBase64(CreationDate + Guid.NewGuid().ToString());
403 } 466 }
404 467
405 // Dump he specified data stream; 468 // Dump he specified data stream;
406 469
407 public static void Dump(byte[] data) 470 public static void Dump(byte[] data)
408 { 471 {
472
409 char[] buffer = new char[Rest.DumpLineSize]; 473 char[] buffer = new char[Rest.DumpLineSize];
410 int cc = 0; 474 int cc = 0;
411 475
@@ -415,7 +479,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
415 if (i % Rest.DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8")); 479 if (i % Rest.DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8"));
416 480
417 if (i % 4 == 0) Console.Write(" "); 481 if (i % 4 == 0) Console.Write(" ");
418// if (i%16 == 0) Console.Write(" ");
419 482
420 Console.Write("{0}",data[i].ToString("x2")); 483 Console.Write("{0}",data[i].ToString("x2"));
421 484
@@ -431,6 +494,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
431 Console.Write(" |"+(new String(buffer))+"|"); 494 Console.Write(" |"+(new String(buffer))+"|");
432 cc = 0; 495 cc = 0;
433 } 496 }
497
434 } 498 }
435 499
436 // Finish off any incomplete line 500 // Finish off any incomplete line
@@ -440,30 +504,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
440 for (int i = cc ; i < Rest.DumpLineSize; i++) 504 for (int i = cc ; i < Rest.DumpLineSize; i++)
441 { 505 {
442 if (i % 4 == 0) Console.Write(" "); 506 if (i % 4 == 0) Console.Write(" ");
443 // if (i%16 == 0) Console.Write(" "); 507 Console.Write(" ");
444 Console.Write(" ");
445 buffer[i % Rest.DumpLineSize] = ' '; 508 buffer[i % Rest.DumpLineSize] = ' ';
446 } 509 }
447 Console.WriteLine(" |"+(new String(buffer))+"|"); 510 Console.WriteLine(" |"+(new String(buffer))+"|");
448 } 511 }
449 else 512 else
450 { 513 {
451 Console.Write("\n"); 514 Console.Write("\n");
452 } 515 }
516
453 } 517 }
454 }
455 518
519 }
520
456 // Local exception type 521 // Local exception type
457 522
458 public class RestException : Exception 523 public class RestException : Exception
459 { 524 {
525
460 internal int statusCode; 526 internal int statusCode;
461 internal string statusDesc; 527 internal string statusDesc;
462 internal string httpmethod; 528 internal string httpmethod;
463 internal string httppath; 529 internal string httppath;
464 530
465 public RestException(string msg) : base(msg) 531 public RestException(string msg) : base(msg)
466 { 532 {
467 } 533 }
468 } 534 }
535
469} 536}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
index 85748fa..a40d197 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs
@@ -23,6 +23,7 @@
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
26 */ 27 */
27 28
28using libsecondlife; 29using libsecondlife;
@@ -39,8 +40,10 @@ using OpenSim.Framework.Communications.Cache;
39 40
40namespace OpenSim.ApplicationPlugins.Rest.Inventory 41namespace OpenSim.ApplicationPlugins.Rest.Inventory
41{ 42{
43
42 public class RestAssetServices : IRest 44 public class RestAssetServices : IRest
43 { 45 {
46
44 private bool enabled = false; 47 private bool enabled = false;
45 private string qPrefix = "assets"; 48 private string qPrefix = "assets";
46 49
@@ -49,6 +52,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
49 52
50 public RestAssetServices() 53 public RestAssetServices()
51 { 54 {
55
52 Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); 56 Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
53 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); 57 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
54 58
@@ -69,6 +73,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
69 enabled = true; 73 enabled = true;
70 74
71 Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId); 75 Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId);
76
72 } 77 }
73 78
74 // Post-construction, pre-enabled initialization opportunity 79 // Post-construction, pre-enabled initialization opportunity
@@ -79,7 +84,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
79 } 84 }
80 85
81 // Called by the plug-in to halt REST processing. Local processing is 86 // Called by the plug-in to halt REST processing. Local processing is
82 // disabled, and control blocks until all current processing has 87 // disabled, and control blocks until all current processing has
83 // completed. No new processing will be started 88 // completed. No new processing will be started
84 89
85 public void Close() 90 public void Close()
@@ -106,14 +111,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
106 111
107 private void DoAsset(RequestData rparm) 112 private void DoAsset(RequestData rparm)
108 { 113 {
109 if (!enabled) 114
110 return; 115 if (!enabled) return;
111 116
112 AssetRequestData rdata = (AssetRequestData) rparm; 117 AssetRequestData rdata = (AssetRequestData) rparm;
113 118
114 Rest.Log.DebugFormat("{0} REST Asset handler ENTRY", MsgId); 119 Rest.Log.DebugFormat("{0} REST Asset handler ENTRY", MsgId);
115 120
116 // Now that we know this is a serious attempt to 121 // Now that we know this is a serious attempt to
117 // access inventory data, we should find out who 122 // access inventory data, we should find out who
118 // is asking, and make sure they are authorized 123 // is asking, and make sure they are authorized
119 // to do so. We need to validate the caller's 124 // to do so. We need to validate the caller's
@@ -124,14 +129,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
124 // With the present HTTP server we can't use the 129 // With the present HTTP server we can't use the
125 // builtin authentication mechanisms because they 130 // builtin authentication mechanisms because they
126 // would be enforced for all in-bound requests. 131 // would be enforced for all in-bound requests.
127 // Instead we look at the headers ourselves and 132 // Instead we look at the headers ourselves and
128 // handle authentication directly. 133 // handle authentication directly.
129 134
130 try 135 try
131 { 136 {
132 if (!rdata.IsAuthenticated) 137 if (!rdata.IsAuthenticated)
133 { 138 {
134 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); 139 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
135 } 140 }
136 } 141 }
137 catch (RestException e) 142 catch (RestException e)
@@ -139,13 +144,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
139 if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) 144 if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
140 { 145 {
141 Rest.Log.WarnFormat("{0} User not authenticated", MsgId); 146 Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
142 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, 147 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
143 rdata.request.Headers.Get("Authorization")); 148 rdata.request.Headers.Get("Authorization"));
144 } 149 }
145 else 150 else
146 { 151 {
147 Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); 152 Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
148 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, 153 Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
149 rdata.request.Headers.Get("Authorization")); 154 rdata.request.Headers.Get("Authorization"));
150 } 155 }
151 throw (e); 156 throw (e);
@@ -155,7 +160,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
155 // the parameters we need, fail the request. Parameters do NOT include 160 // the parameters we need, fail the request. Parameters do NOT include
156 // any supplied query values. 161 // any supplied query values.
157 162
158 if (rdata.parameters.Length > 0) 163 if (rdata.Parameters.Length > 0)
159 { 164 {
160 switch (rdata.method) 165 switch (rdata.method)
161 { 166 {
@@ -168,26 +173,27 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
168 case "post" : 173 case "post" :
169 case "delete" : 174 case "delete" :
170 default : 175 default :
171 Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}", 176 Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}",
172 MsgId, rdata.method); 177 MsgId, rdata.method);
173 rdata.Fail(Rest.HttpStatusCodeBadRequest, 178 rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
174 Rest.HttpStatusDescBadRequest);
175 break; 179 break;
176 } 180 }
177 } 181 }
178 else 182 else
179 { 183 {
180 Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId); 184 Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId);
181 rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); 185 rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
182 } 186 }
183 187
184 Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId); 188 Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId);
189
185 } 190 }
186 191
187 #endregion Interface 192 #endregion Interface
188 193
189 private void DoGet(AssetRequestData rdata) 194 private void DoGet(AssetRequestData rdata)
190 { 195 {
196
191 bool istexture = false; 197 bool istexture = false;
192 198
193 Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); 199 Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method);
@@ -195,14 +201,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
195 // The only parameter we accept is an LLUUID for 201 // The only parameter we accept is an LLUUID for
196 // the asset 202 // the asset
197 203
198 if (rdata.parameters.Length == 1) 204 if (rdata.Parameters.Length == 1)
199 { 205 {
200 LLUUID uuid = new LLUUID(rdata.parameters[0]); 206
207 LLUUID uuid = new LLUUID(rdata.Parameters[0]);
201 AssetBase asset = Rest.AssetServices.GetAsset(uuid, istexture); 208 AssetBase asset = Rest.AssetServices.GetAsset(uuid, istexture);
202 209
203 if (asset != null) 210 if (asset != null)
204 { 211 {
205 Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.parameters[0]); 212
213 Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]);
206 214
207 rdata.initXmlWriter(); 215 rdata.initXmlWriter();
208 216
@@ -218,17 +226,18 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
218 rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length); 226 rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length);
219 227
220 rdata.writer.WriteFullEndElement(); 228 rdata.writer.WriteFullEndElement();
229
221 } 230 }
222 else 231 else
223 { 232 {
224 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); 233 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
225 rdata.Fail(Rest.HttpStatusCodeNotFound, 234 rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
226 Rest.HttpStatusDescNotFound);
227 } 235 }
228 } 236 }
229 237
230 rdata.Complete(); 238 rdata.Complete();
231 rdata.Respond("Asset " + rdata.method + ": Normal completion"); 239 rdata.Respond("Asset " + rdata.method + ": Normal completion");
240
232 } 241 }
233 242
234 private void DoPut(AssetRequestData rdata) 243 private void DoPut(AssetRequestData rdata)
@@ -238,7 +247,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
238 // The only parameter we accept is an LLUUID for 247 // The only parameter we accept is an LLUUID for
239 // the asset 248 // the asset
240 249
241 if (rdata.parameters.Length == 1) 250 if (rdata.Parameters.Length == 1)
242 { 251 {
243 rdata.initXmlReader(); 252 rdata.initXmlReader();
244 XmlReader xml = rdata.reader; 253 XmlReader xml = rdata.reader;
@@ -246,12 +255,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
246 if (!xml.ReadToFollowing("Asset")) 255 if (!xml.ReadToFollowing("Asset"))
247 { 256 {
248 Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); 257 Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
249 rdata.Fail(Rest.HttpStatusCodeBadRequest, 258 rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
250 Rest.HttpStatusDescBadRequest);
251 } 259 }
252 260
253 AssetBase asset = new AssetBase(); 261 AssetBase asset = new AssetBase();
254 asset.ID = rdata.parameters[0]; 262 asset.ID = rdata.Parameters[0];
255 asset.Name = xml.GetAttribute("name"); 263 asset.Name = xml.GetAttribute("name");
256 asset.Description = xml.GetAttribute("desc"); 264 asset.Description = xml.GetAttribute("desc");
257 asset.Type = SByte.Parse(xml.GetAttribute("type")); 265 asset.Type = SByte.Parse(xml.GetAttribute("type"));
@@ -264,12 +272,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
264 else 272 else
265 { 273 {
266 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); 274 Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
267 rdata.Fail(Rest.HttpStatusCodeNotFound, 275 rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
268 Rest.HttpStatusDescNotFound);
269 } 276 }
270 277
271 rdata.Complete(); 278 rdata.Complete();
272 rdata.Respond("Asset " + rdata.method + ": Normal completion"); 279 rdata.Respond("Asset " + rdata.method + ": Normal completion");
280
273 } 281 }
274 282
275 internal class AssetRequestData : RequestData 283 internal class AssetRequestData : RequestData
@@ -279,5 +287,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
279 { 287 {
280 } 288 }
281 } 289 }
290
282 } 291 }
283} 292}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
index 9853f16..cb80846 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs
@@ -23,6 +23,7 @@
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
26 */ 27 */
27 28
28using System; 29using System;
@@ -34,8 +35,27 @@ using OpenSim.ApplicationPlugins.Rest;
34 35
35namespace OpenSim.ApplicationPlugins.Rest.Inventory 36namespace OpenSim.ApplicationPlugins.Rest.Inventory
36{ 37{
37 public class RestHandler : RestPlugin, IHttpAgentHandler 38
39 /// <remarks>
40 /// The class signature reveals the roles that RestHandler plays.
41 ///
42 /// [1] It is a sub-class of RestPlugin. It inherits and extends
43 /// the functionality of this class, constraining it to the
44 /// specific needs of this REST implementation. This relates
45 /// to the plug-in mechanism supported by OpenSim, the specifics
46 /// of which are mostly hidden by RestPlugin.
47 /// [2] IRestHandler describes the interface that this class
48 /// exports to service implementations. This is the services
49 /// management interface.
50 /// [3] IHttpAgentHandler describes the interface that is exported
51 /// to the BaseHttpServer in support of this particular HTTP
52 /// processing model. This is the request interface of the
53 /// handler.
54 /// </remarks>
55
56 public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler
38 { 57 {
58
39 /// <remarks> 59 /// <remarks>
40 /// The handler delegates are not noteworthy. The allocator allows 60 /// The handler delegates are not noteworthy. The allocator allows
41 /// a given handler to optionally subclass the base RequestData 61 /// a given handler to optionally subclass the base RequestData
@@ -43,8 +63,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
43 /// needed. 63 /// needed.
44 /// </remarks> 64 /// </remarks>
45 65
46 internal delegate void RestMethodHandler(RequestData rdata); 66 // internal delegate void RestMethodHandler(RequestData rdata);
47 internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response); 67 // internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
48 68
49 // Handler tables: both stream and REST are supported. The path handlers and their 69 // Handler tables: both stream and REST are supported. The path handlers and their
50 // respective allocators are stored in separate tables. 70 // respective allocators are stored in separate tables.
@@ -64,10 +84,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
64 /// <summary> 84 /// <summary>
65 /// This static initializer scans the ASSEMBLY for classes that 85 /// This static initializer scans the ASSEMBLY for classes that
66 /// export the IRest interface and builds a list of them. These 86 /// export the IRest interface and builds a list of them. These
67 /// are later activated by the handler. To add a new handler it 87 /// are later activated by the handler. To add a new handler it
68 /// is only necessary to create a new services class that implements 88 /// is only necessary to create a new services class that implements
69 /// the IRest interface, and recompile the handler. This gives 89 /// the IRest interface, and recompile the handler. This gives
70 /// all of the build-time flexibility of a modular approach 90 /// all of the build-time flexibility of a modular approach
71 /// while not introducing yet-another module loader. Note that 91 /// while not introducing yet-another module loader. Note that
72 /// multiple assembles can still be built, each with its own set 92 /// multiple assembles can still be built, each with its own set
73 /// of handlers. Examples of services classes are RestInventoryServices 93 /// of handlers. Examples of services classes are RestInventoryServices
@@ -76,12 +96,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
76 96
77 static RestHandler() 97 static RestHandler()
78 { 98 {
99
79 Module[] mods = Assembly.GetExecutingAssembly().GetModules(); 100 Module[] mods = Assembly.GetExecutingAssembly().GetModules();
80 101
81 foreach (Module m in mods) 102 foreach (Module m in mods)
82 { 103 {
83 Type[] types = m.GetTypes(); 104 Type[] types = m.GetTypes();
84 foreach (Type t in types) 105 foreach (Type t in types)
85 { 106 {
86 try 107 try
87 { 108 {
@@ -97,6 +118,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
97 } 118 }
98 } 119 }
99 } 120 }
121
100 } 122 }
101 123
102 #endregion local static state 124 #endregion local static state
@@ -105,13 +127,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
105 127
106 /// <summary> 128 /// <summary>
107 /// This routine loads all of the handlers discovered during 129 /// This routine loads all of the handlers discovered during
108 /// instance initialization. 130 /// instance initialization.
109 /// A table of all loaded and successfully constructed handlers 131 /// A table of all loaded and successfully constructed handlers
110 /// is built, and this table is then used by the constructor to 132 /// is built, and this table is then used by the constructor to
111 /// initialize each of the handlers in turn. 133 /// initialize each of the handlers in turn.
112 /// NOTE: The loading process does not automatically imply that 134 /// NOTE: The loading process does not automatically imply that
113 /// the handler has registered any kind of an interface, that 135 /// the handler has registered any kind of an interface, that
114 /// may be (optionally) done by the handler either during 136 /// may be (optionally) done by the handler either during
115 /// construction, or during initialization. 137 /// construction, or during initialization.
116 /// 138 ///
117 /// I was not able to make this code work within a constructor 139 /// I was not able to make this code work within a constructor
@@ -124,6 +146,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
124 { 146 {
125 if (!handlersLoaded) 147 if (!handlersLoaded)
126 { 148 {
149
127 ConstructorInfo ci; 150 ConstructorInfo ci;
128 Object ht; 151 Object ht;
129 152
@@ -154,8 +177,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
154 177
155 // Name is used to differentiate the message header. 178 // Name is used to differentiate the message header.
156 179
157 public override string Name 180 public override string Name
158 { 181 {
159 get { return "HANDLER"; } 182 get { return "HANDLER"; }
160 } 183 }
161 184
@@ -168,15 +191,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
168 191
169 // We have to rename these because we want 192 // We have to rename these because we want
170 // to be able to share the values with other 193 // to be able to share the values with other
171 // classes in our assembly and the base 194 // classes in our assembly and the base
172 // names are protected. 195 // names are protected.
173 196
174 internal string MsgId 197 public string MsgId
175 { 198 {
176 get { return base.MsgID; } 199 get { return base.MsgID; }
177 } 200 }
178 201
179 internal string RequestId 202 public string RequestId
180 { 203 {
181 get { return base.RequestID; } 204 get { return base.RequestID; }
182 } 205 }
@@ -198,6 +221,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
198 { 221 {
199 try 222 try
200 { 223 {
224
201 // This plugin will only be enabled if the broader 225 // This plugin will only be enabled if the broader
202 // REST plugin mechanism is enabled. 226 // REST plugin mechanism is enabled.
203 227
@@ -208,7 +232,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
208 // IsEnabled is implemented by the base class and 232 // IsEnabled is implemented by the base class and
209 // reflects an overall RestPlugin status 233 // reflects an overall RestPlugin status
210 234
211 if (!IsEnabled) 235 if (!IsEnabled)
212 { 236 {
213 Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId); 237 Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
214 return; 238 return;
@@ -221,7 +245,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
221 245
222 Rest.main = openSim; 246 Rest.main = openSim;
223 Rest.Plugin = this; 247 Rest.Plugin = this;
224 Rest.Comms = App.CommunicationsManager; 248 Rest.Comms = Rest.main.CommunicationsManager;
225 Rest.UserServices = Rest.Comms.UserService; 249 Rest.UserServices = Rest.Comms.UserService;
226 Rest.InventoryServices = Rest.Comms.InventoryService; 250 Rest.InventoryServices = Rest.Comms.InventoryService;
227 Rest.AssetServices = Rest.Comms.AssetCache; 251 Rest.AssetServices = Rest.Comms.AssetCache;
@@ -234,7 +258,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
234 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true); 258 Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true);
235 Rest.Realm = Rest.Config.GetString("realm","OpenSim REST"); 259 Rest.Realm = Rest.Config.GetString("realm","OpenSim REST");
236 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false); 260 Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false);
261 Rest.Fill = Rest.Config.GetBoolean("path-fill",true);
237 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32); 262 Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32);
263 Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error",true);
238 264
239 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, 265 Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId,
240 (Rest.Authenticate ? "" : "not ")); 266 (Rest.Authenticate ? "" : "not "));
@@ -248,6 +274,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
248 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, 274 Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
249 (Rest.DumpAsset ? "" : "not ")); 275 (Rest.DumpAsset ? "" : "not "));
250 276
277 // The supplied prefix MUST be absolute
278
279 if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator)
280 Rest.Prefix = Rest.UrlPathSeparator+Rest.Prefix;
281
251 // If data dumping is requested, report on the chosen line 282 // If data dumping is requested, report on the chosen line
252 // length. 283 // length.
253 284
@@ -257,15 +288,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
257 Rest.DumpLineSize); 288 Rest.DumpLineSize);
258 } 289 }
259 290
260 // Load all of the handlers present in the 291 // Load all of the handlers present in the
261 // assembly 292 // assembly
262 293
263 // In principle, as we're an application plug-in, 294 // In principle, as we're an application plug-in,
264 // most of what needs to be done could be done using 295 // most of what needs to be done could be done using
265 // static resources, however the Open Sim plug-in 296 // static resources, however the Open Sim plug-in
266 // model makes this an instance, so that's what we 297 // model makes this an instance, so that's what we
267 // need to be. 298 // need to be.
268 // There is only one Communications manager per 299 // There is only one Communications manager per
269 // server, and by inference, only one each of the 300 // server, and by inference, only one each of the
270 // user, asset, and inventory servers. So we can cache 301 // user, asset, and inventory servers. So we can cache
271 // those using a static initializer. 302 // those using a static initializer.
@@ -308,12 +339,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
308 { 339 {
309 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); 340 Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message);
310 } 341 }
342
311 } 343 }
312 344
313 /// <summary> 345 /// <summary>
314 /// In the interests of efficiency, and because we cannot determine whether 346 /// In the interests of efficiency, and because we cannot determine whether
315 /// or not this instance will actually be harvested, we clobber the only 347 /// or not this instance will actually be harvested, we clobber the only
316 /// anchoring reference to the working state for this plug-in. What the 348 /// anchoring reference to the working state for this plug-in. What the
317 /// call to close does is irrelevant to this class beyond knowing that it 349 /// call to close does is irrelevant to this class beyond knowing that it
318 /// can nullify the reference when it returns. 350 /// can nullify the reference when it returns.
319 /// To make sure everything is copacetic we make sure the primary interface 351 /// To make sure everything is copacetic we make sure the primary interface
@@ -322,6 +354,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
322 354
323 public override void Close() 355 public override void Close()
324 { 356 {
357
325 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); 358 Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
326 359
327 try 360 try
@@ -329,11 +362,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
329 RemoveAgentHandler(Rest.Name, this); 362 RemoveAgentHandler(Rest.Name, this);
330 } 363 }
331 catch (KeyNotFoundException){} 364 catch (KeyNotFoundException){}
332 365
333 foreach (IRest handler in handlers) 366 foreach (IRest handler in handlers)
334 { 367 {
335 handler.Close(); 368 handler.Close();
336 } 369 }
370
337 } 371 }
338 372
339 #endregion overriding methods 373 #endregion overriding methods
@@ -352,25 +386,57 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
352 { 386 {
353 string path = request.RawUrl; 387 string path = request.RawUrl;
354 388
389 Rest.Log.DebugFormat("{0} Match ENTRY", MsgId);
390
355 try 391 try
356 { 392 {
357 foreach (string key in pathHandlers.Keys) 393 foreach (string key in pathHandlers.Keys)
358 { 394 {
395 Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key);
396
397 // Note that Match will not necessarily find the handler that will
398 // actually be used - it does no test for the "closest" fit. It
399 // simply reflects that at least one possible handler exists.
400
359 if (path.StartsWith(key)) 401 if (path.StartsWith(key))
360 { 402 {
361 return (path.Length == key.Length || 403 Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
362 path.Substring(key.Length, 1) == Rest.UrlPathSeparator); 404
405 // This apparently odd evaluation is needed to prevent a match
406 // on anything other than a URI token boundary. Otherwise we
407 // may match on URL's that were not intended for this handler.
408
409 return ( path.Length == key.Length ||
410 path.Substring(key.Length,1) == Rest.UrlPathSeparator);
411
363 } 412 }
364 } 413 }
365 414
366 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); 415 path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
416
367 foreach (string key in streamHandlers.Keys) 417 foreach (string key in streamHandlers.Keys)
368 { 418 {
419
420 Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key);
421
422 // Note that Match will not necessarily find the handler that will
423 // actually be used - it does no test for the "closest" fit. It
424 // simply reflects that at least one possible handler exists.
425
369 if (path.StartsWith(key)) 426 if (path.StartsWith(key))
370 { 427 {
371 return true; 428 Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key);
429
430 // This apparently odd evaluation is needed to prevent a match
431 // on anything other than a URI token boundary. Otherwise we
432 // may match on URL's that were not intended for this handler.
433
434 return ( path.Length == key.Length ||
435 path.Substring(key.Length,1) == Rest.UrlPathSeparator);
436
372 } 437 }
373 } 438 }
439
374 } 440 }
375 catch (Exception e) 441 catch (Exception e)
376 { 442 {
@@ -404,7 +470,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
404 470
405 for (int i = 0; i < request.Headers.Count; i++) 471 for (int i = 0; i < request.Headers.Count; i++)
406 { 472 {
407 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>", 473 Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>",
408 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i)); 474 MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i));
409 } 475 }
410 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl); 476 Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl);
@@ -415,8 +481,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
415 481
416 try 482 try
417 { 483 {
418 handled = FindPathHandler(request, response) || 484 handled = ( FindPathHandler(request, response) ||
419 FindStreamHandler(request, response); 485 FindStreamHandler(request, response) );
420 } 486 }
421 catch (Exception e) 487 catch (Exception e)
422 { 488 {
@@ -430,6 +496,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
430 Rest.Log.DebugFormat("{0} EXIT", MsgId); 496 Rest.Log.DebugFormat("{0} EXIT", MsgId);
431 497
432 return handled; 498 return handled;
499
433 } 500 }
434 501
435 #endregion interface methods 502 #endregion interface methods
@@ -477,6 +544,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
477 } 544 }
478 545
479 return rdata.handled; 546 return rdata.handled;
547
480 } 548 }
481 549
482 /// <summary> 550 /// <summary>
@@ -489,12 +557,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
489 557
490 public void AddStreamHandler(string httpMethod, string path, RestMethod method) 558 public void AddStreamHandler(string httpMethod, string path, RestMethod method)
491 { 559 {
560
492 if (!IsEnabled) 561 if (!IsEnabled)
493 { 562 {
494 return; 563 return;
495 } 564 }
496 565
497 if (!path.StartsWith(Rest.Prefix)) 566 if (!path.StartsWith(Rest.Prefix))
498 { 567 {
499 path = String.Format("{0}{1}", Rest.Prefix, path); 568 path = String.Format("{0}{1}", Rest.Prefix, path);
500 } 569 }
@@ -512,6 +581,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
512 { 581 {
513 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); 582 Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path);
514 } 583 }
584
515 } 585 }
516 586
517 /// <summary> 587 /// <summary>
@@ -526,9 +596,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
526 596
527 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) 597 internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
528 { 598 {
599
529 RequestData rdata = null; 600 RequestData rdata = null;
530 string bestMatch = null; 601 string bestMatch = null;
531 602
532 if (!IsEnabled) 603 if (!IsEnabled)
533 { 604 {
534 return false; 605 return false;
@@ -551,6 +622,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
551 622
552 if (!String.IsNullOrEmpty(bestMatch)) 623 if (!String.IsNullOrEmpty(bestMatch))
553 { 624 {
625
554 rdata = pathAllocators[bestMatch](request, response); 626 rdata = pathAllocators[bestMatch](request, response);
555 627
556 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch); 628 Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch);
@@ -559,7 +631,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
559 { 631 {
560 pathHandlers[bestMatch](rdata); 632 pathHandlers[bestMatch](rdata);
561 } 633 }
562 634
563 // A plugin generated error indicates a request-related error 635 // A plugin generated error indicates a request-related error
564 // that has been handled by the plugin. 636 // that has been handled by the plugin.
565 637
@@ -567,9 +639,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
567 { 639 {
568 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message); 640 Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message);
569 } 641 }
642
570 } 643 }
571 644
572 return (rdata == null) ? false : rdata.handled; 645 return (rdata == null) ? false : rdata.handled;
646
573 } 647 }
574 648
575 /// <summary> 649 /// <summary>
@@ -577,8 +651,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
577 /// path as a key. If an entry already exists, it is replaced by the new one. 651 /// path as a key. If an entry already exists, it is replaced by the new one.
578 /// </summary> 652 /// </summary>
579 653
580 internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) 654 public void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
581 { 655 {
656
582 if (!IsEnabled) 657 if (!IsEnabled)
583 { 658 {
584 return; 659 return;
@@ -600,6 +675,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
600 675
601 pathHandlers.Add(path, mh); 676 pathHandlers.Add(path, mh);
602 pathAllocators.Add(path, ra); 677 pathAllocators.Add(path, ra);
678
603 } 679 }
604 } 680 }
681
605} 682}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
index 8a0eba5..6a0fdf2 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs
@@ -23,6 +23,7 @@
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
26 */ 27 */
27 28
28using System; 29using System;
@@ -41,22 +42,30 @@ using Nini.Config;
41 42
42namespace OpenSim.ApplicationPlugins.Rest.Inventory 43namespace OpenSim.ApplicationPlugins.Rest.Inventory
43{ 44{
45
44 public class RestInventoryServices : IRest 46 public class RestInventoryServices : IRest
45 { 47 {
48
49 private static readonly int PARM_USERID = 0;
50 private static readonly int PARM_PATH = 1;
51
46 private bool enabled = false; 52 private bool enabled = false;
47 private string qPrefix = "inventory"; 53 private string qPrefix = "inventory";
48 54
55 private static readonly string PRIVATE_ROOT_NAME = "My Inventory";
56
49 /// <summary> 57 /// <summary>
50 /// A simple constructor is used to handle any once-only 58 /// The constructor makes sure that the service prefix is absolute
51 /// initialization of working classes. 59 /// and the registers the service handler and the allocator.
52 /// </summary> 60 /// </summary>
53 61
54 public RestInventoryServices() 62 public RestInventoryServices()
55 { 63 {
64
56 Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); 65 Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId);
57 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); 66 Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
58 67
59 // If a relative path was specified for the handler's domain, 68 // If a relative path was specified for the handler's domain,
60 // add the standard prefix to make it absolute, e.g. /admin 69 // add the standard prefix to make it absolute, e.g. /admin
61 70
62 if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) 71 if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
@@ -73,6 +82,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
73 enabled = true; 82 enabled = true;
74 83
75 Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); 84 Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId);
85
76 } 86 }
77 87
78 /// <summary> 88 /// <summary>
@@ -85,9 +95,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
85 } 95 }
86 96
87 /// <summary> 97 /// <summary>
88 /// Called by the plug-in to halt REST processing. Local processing is 98 /// Called by the plug-in to halt service processing. Local processing is
89 /// disabled, and control blocks until all current processing has 99 /// disabled.
90 /// completed. No new processing will be started
91 /// </summary> 100 /// </summary>
92 101
93 public void Close() 102 public void Close()
@@ -114,6 +123,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
114 /// completes. All request-instance specific state is kept here. This 123 /// completes. All request-instance specific state is kept here. This
115 /// is registered when this service provider is registered. 124 /// is registered when this service provider is registered.
116 /// </summary> 125 /// </summary>
126 /// <param name=request>Inbound HTTP request information</param>
127 /// <param name=response>Outbound HTTP request information</param>
128 /// <param name=qPrefix>REST service domain prefix</param>
129 /// <returns>A RequestData instance suitable for this service</returns>
117 130
118 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response) 131 private RequestData Allocate(OSHttpRequest request, OSHttpResponse response)
119 { 132 {
@@ -123,12 +136,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
123 /// <summary> 136 /// <summary>
124 /// This method is registered with the handler when this service provider 137 /// This method is registered with the handler when this service provider
125 /// is initialized. It is called whenever the plug-in identifies this service 138 /// is initialized. It is called whenever the plug-in identifies this service
126 /// provider as the best match. 139 /// provider as the best match for a given request.
127 /// It handles all aspects of inventory REST processing. 140 /// It handles all aspects of inventory REST processing, i.e. /admin/inventory
128 /// </summary> 141 /// </summary>
142 /// <param name=hdata>A consolidated HTTP request work area</param>
129 143
130 private void DoInventory(RequestData hdata) 144 private void DoInventory(RequestData hdata)
131 { 145 {
146
132 InventoryRequestData rdata = (InventoryRequestData) hdata; 147 InventoryRequestData rdata = (InventoryRequestData) hdata;
133 148
134 Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); 149 Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId);
@@ -140,7 +155,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
140 return; 155 return;
141 } 156 }
142 157
143 // Now that we know this is a serious attempt to 158 // Now that we know this is a serious attempt to
144 // access inventory data, we should find out who 159 // access inventory data, we should find out who
145 // is asking, and make sure they are authorized 160 // is asking, and make sure they are authorized
146 // to do so. We need to validate the caller's 161 // to do so. We need to validate the caller's
@@ -151,14 +166,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
151 // With the present HTTP server we can't use the 166 // With the present HTTP server we can't use the
152 // builtin authentication mechanisms because they 167 // builtin authentication mechanisms because they
153 // would be enforced for all in-bound requests. 168 // would be enforced for all in-bound requests.
154 // Instead we look at the headers ourselves and 169 // Instead we look at the headers ourselves and
155 // handle authentication directly. 170 // handle authentication directly.
156 171
157 try 172 try
158 { 173 {
159 if (!rdata.IsAuthenticated) 174 if (!rdata.IsAuthenticated)
160 { 175 {
161 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); 176 rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
162 } 177 }
163 } 178 }
164 catch (RestException e) 179 catch (RestException e)
@@ -178,43 +193,43 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
178 193
179 Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); 194 Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
180 195
181 /// <remarks> 196 // We can only get here if we are authorized
182 /// We can only get here if we are authorized 197 //
183 /// 198 // The requestor may have specified an LLUUID or
184 /// The requestor may have specified an LLUUID or 199 // a conjoined FirstName LastName string. We'll
185 /// a conjoined FirstName LastName string. We'll 200 // try both. If we fail with the first, UUID,
186 /// try both. If we fail with the first, UUID, 201 // attempt, we try the other. As an example, the
187 /// attempt, we try the other. As an example, the 202 // URI for a valid inventory request might be:
188 /// URI for a valid inventory request might be: 203 //
189 /// 204 // http://<host>:<port>/admin/inventory/Arthur Dent
190 /// http://<host>:<port>/admin/inventory/Arthur Dent 205 //
191 /// 206 // Indicating that this is an inventory request for
192 /// Indicating that this is an inventory request for 207 // an avatar named Arthur Dent. This is ALL that is
193 /// an avatar named Arthur Dent. This is ALl that is 208 // required to designate a GET for an entire
194 /// required to designate a GET for an entire 209 // inventory.
195 /// inventory. 210 //
196 /// </remarks>
197
198 // Do we have at least a user agent name? 211 // Do we have at least a user agent name?
199 212
200 if (rdata.parameters.Length < 1) 213 if (rdata.Parameters.Length < 1)
201 { 214 {
202 Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); 215 Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId);
203 rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": No user identity specified"); 216 rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified");
204 } 217 }
205 218
206 // The first parameter MUST be the agent identification, either an LLUUID 219 // The first parameter MUST be the agent identification, either an LLUUID
207 // or a space-separated First-name Last-Name specification. 220 // or a space-separated First-name Last-Name specification. We check for
221 // an LLUUID first, if anyone names their character using a valid LLUUID
222 // that identifies another existing avatar will cause this a problem...
208 223
209 try 224 try
210 { 225 {
211 rdata.uuid = new LLUUID(rdata.parameters[0]); 226 rdata.uuid = new LLUUID(rdata.Parameters[PARM_USERID]);
212 Rest.Log.DebugFormat("{0} LLUUID supplied", MsgId); 227 Rest.Log.DebugFormat("{0} LLUUID supplied", MsgId);
213 rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); 228 rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
214 } 229 }
215 catch 230 catch
216 { 231 {
217 string[] names = rdata.parameters[0].Split(Rest.CA_SPACE); 232 string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE);
218 if (names.Length == 2) 233 if (names.Length == 2)
219 { 234 {
220 Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); 235 Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
@@ -222,23 +237,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
222 } 237 }
223 else 238 else
224 { 239 {
225 Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); 240 Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
226 rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": invalid user identity"); 241 rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity");
227 } 242 }
228 } 243 }
229 244
230 // If the user rpofile is null then either the server is broken, or the 245 // If the user profile is null then either the server is broken, or the
231 // user is not known. We always assume the latter case. 246 // user is not known. We always assume the latter case.
232 247
233 if (rdata.userProfile != null) 248 if (rdata.userProfile != null)
234 { 249 {
235 Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", 250 Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}",
236 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); 251 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
237 } 252 }
238 else 253 else
239 { 254 {
240 Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path); 255 Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path);
241 rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": unrecognized user identity"); 256 rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity");
242 } 257 }
243 258
244 // If we get to here, then we have effectively validated the user's 259 // If we get to here, then we have effectively validated the user's
@@ -254,17 +269,18 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
254 // response is not recieved in a timely fashion. 269 // response is not recieved in a timely fashion.
255 270
256 rdata.uuid = rdata.userProfile.ID; 271 rdata.uuid = rdata.userProfile.ID;
257 272
258 if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) 273 if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid))
259 { 274 {
275
260 rdata.root = Rest.InventoryServices.RequestRootFolder(rdata.uuid); 276 rdata.root = Rest.InventoryServices.RequestRootFolder(rdata.uuid);
261 277
262 Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", 278 Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}",
263 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); 279 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
264 280
265 Rest.InventoryServices.RequestInventoryForUser(rdata.uuid, rdata.GetUserInventory); 281 Rest.InventoryServices.RequestInventoryForUser(rdata.uuid, rdata.GetUserInventory);
266 282
267 Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", 283 Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}",
268 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); 284 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
269 285
270 lock (rdata) 286 lock (rdata)
@@ -277,16 +293,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
277 293
278 if (rdata.root == null) 294 if (rdata.root == null)
279 { 295 {
280 Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}", 296 Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}",
281 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); 297 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
282 rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError+": inventory retrieval failed"); 298 rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed");
283 } 299 }
300
284 } 301 }
285 else 302 else
286 { 303 {
287 Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}", 304 Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}",
288 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); 305 MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
289 rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no inventory for user"); 306 rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user");
290 } 307 }
291 308
292 // If we get here, then we have successfully retrieved the user's information 309 // If we get here, then we have successfully retrieved the user's information
@@ -294,34 +311,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
294 311
295 switch (rdata.method) 312 switch (rdata.method)
296 { 313 {
297 case Rest.HEAD : // Do the processing, set the status code, suppress entity
298 DoGet(rdata);
299 rdata.buffer = null;
300 break;
301 314
302 case Rest.GET : // Do the processing, set the status code, return entity 315 case Rest.HEAD : // Do the processing, set the status code, suppress entity
303 DoGet(rdata); 316 DoGet(rdata);
304 break; 317 rdata.buffer = null;
318 break;
305 319
306 case Rest.PUT : // Add new information 320 case Rest.GET : // Do the processing, set the status code, return entity
307 DoPut(rdata); 321 DoGet(rdata);
308 break; 322 break;
309 323
310 case Rest.POST : // Update (replace) 324 case Rest.PUT : // Update named element
311 DoPost(rdata); 325 DoUpdate(rdata);
312 break; 326 break;
313 327
314 case Rest.DELETE : // Delete information 328 case Rest.POST : // Add new information to identified context.
315 DoDelete(rdata); 329 DoExtend(rdata);
316 break; 330 break;
317 331
318 default : 332 case Rest.DELETE : // Delete information
319 Rest.Log.DebugFormat("{0} Method {1} not supported for {2}", 333 DoDelete(rdata);
320 MsgId, rdata.method, rdata.path); 334 break;
321 rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, 335
322 Rest.HttpStatusDescMethodNotAllowed+": "+rdata.method+" not supported"); 336 default :
323 break; 337 Rest.Log.WarnFormat("{0} Method {1} not supported for {2}",
338 MsgId, rdata.method, rdata.path);
339 rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, rdata.method+" not supported");
340 break;
324 } 341 }
342
325 } 343 }
326 344
327 #endregion Interface 345 #endregion Interface
@@ -333,84 +351,97 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
333 /// Any remaining parameters are used to locate the 351 /// Any remaining parameters are used to locate the
334 /// corresponding subtree based upon node name. 352 /// corresponding subtree based upon node name.
335 /// </summary> 353 /// </summary>
354 /// <param name=rdata>HTTP service request work area</param>
336 355
337 private void DoGet(InventoryRequestData rdata) 356 private void DoGet(InventoryRequestData rdata)
338 { 357 {
358
339 rdata.initXmlWriter(); 359 rdata.initXmlWriter();
340 360
341 rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); 361 rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty);
342 362
343 // If there was only one parameter, then the entire
344 // inventory is being requested.
345
346 if (rdata.parameters.Length == 1)
347 {
348 formatInventory(rdata, rdata.root, String.Empty);
349 }
350
351 // If there are additional parameters, then these represent 363 // If there are additional parameters, then these represent
352 // a path relative to the root of the inventory. This path 364 // a path relative to the root of the inventory. This path
353 // must be traversed before we format the sub-tree thus 365 // must be traversed before we format the sub-tree thus
354 // identified. 366 // identified.
355 367
356 else 368 traverse(rdata, rdata.root, PARM_PATH);
357 { 369
358 traverseInventory(rdata, rdata.root, 1); 370 // Close all open elements
359 }
360 371
361 rdata.writer.WriteFullEndElement(); 372 rdata.writer.WriteFullEndElement();
362 373
374 // Indicate a successful request
375
363 rdata.Complete(); 376 rdata.Complete();
364 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
365 }
366 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
367 /// <summary> 385 /// <summary>
368 /// In the case of the inventory, and probably in general, 386 /// In the case of the inventory, and probably in general,
369 /// the distinction between PUT and POST is not always 387 /// the distinction between PUT and POST is not always
370 /// easy to discern. Adding a directory can be viewed as 388 /// easy to discern. The standard is badly worded in places,
389 /// and adding a node to a hierarchy can be viewed as
371 /// an addition, or as a modification to the inventory as 390 /// an addition, or as a modification to the inventory as
372 /// a whole. This is exacerbated by a lack of consistency 391 /// a whole. This is exacerbated by an unjustified lack of
373 /// across different implementations. 392 /// consistency across different implementations.
374 /// 393 ///
375 /// For OpenSim POST is an update and PUT is an addition. 394 /// For OpenSim PUT is an update and POST is an addition. This
395 /// is the behavior required by the HTTP specification and
396 /// therefore as required by REST.
376 /// 397 ///
377 /// The best way to exaplain the distinction is to 398 /// The best way to explain the distinction is to
378 /// consider the relationship between the URI and the 399 /// consider the relationship between the URI and the
379 /// entity in question. For POST, the URI identifies the 400 /// enclosed entity. For PUT, the URI identifies the
380 /// entity to be modified or replaced. 401 /// actual entity to be modified or replaced, i.e. the
381 /// If the operation is PUT,then the URI describes the 402 /// enclosed entity.
403 ///
404 /// If the operation is POST,then the URI describes the
382 /// context into which the new entity will be added. 405 /// context into which the new entity will be added.
383 /// 406 ///
384 /// As an example, suppose the URI contains: 407 /// As an example, suppose the URI contains:
385 /// /admin/inventory/Clothing 408 /// /admin/inventory/Clothing
386 /// 409 ///
387 /// A POST request will result in some modification of 410 /// A PUT request will normally result in some modification of
388 /// the folder or item named "Clothing". Whereas a PUT 411 /// the folder or item named "Clothing". Whereas a POST
389 /// request will add some new information into the 412 /// request will normally add some new information into the
390 /// content identified by Clothing. It follows from this 413 /// content identified by Clothing. It follows from this
391 /// that for PUT, the element identified by the URI must 414 /// that for POST, the element identified by the URI MUST
392 /// be a folder. 415 /// be a folder.
393 /// </summary> 416 /// </summary>
394 417
395 /// <summary> 418 /// <summary>
396 /// PUT adds new information to the inventory in the 419 /// POST adds new information to the inventory in the
397 /// context identified by the URI. 420 /// context identified by the URI.
398 /// </summary> 421 /// </summary>
422 /// <param name=rdata>HTTP service request work area</param>
399 423
400 private void DoPut(InventoryRequestData rdata) 424 private void DoExtend(InventoryRequestData rdata)
401 { 425 {
426
427 bool created = false;
428 bool modified = false;
429 string newnode = String.Empty;
430
402 // Resolve the context node specified in the URI. Entity 431 // Resolve the context node specified in the URI. Entity
403 // data will be ADDED beneath this node. 432 // data will be ADDED beneath this node. rdata already contains
433 // information about the current content of the user's
434 // inventory.
404 435
405 Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); 436 Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill);
406 437
407 // Processing depends upon the type of inventory node 438 // Processing depends upon the type of inventory node
408 // identified in the URI. This is the CONTEXT for the 439 // identified in the URI. This is the CONTEXT for the
409 // change. We either got a context or we threw an 440 // change. We either got a context or we threw an
410 // exception. 441 // exception.
411 442
412 // It follows that we can only add information if the URI 443 // It follows that we can only add information if the URI
413 // has identified a folder. So only a type of folder is supported 444 // has identified a folder. So only a type of folder is supported
414 // in this case. 445 // in this case.
415 446
416 if (typeof(InventoryFolderBase) == InventoryNode.GetType() || 447 if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
@@ -430,16 +461,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
430 // [1] A (possibly empty) set of folders. 461 // [1] A (possibly empty) set of folders.
431 // [2] A (possibly empty) set of items. 462 // [2] A (possibly empty) set of items.
432 // [3] A (possibly empty) set of assets. 463 // [3] A (possibly empty) set of assets.
433 // If all of these are empty, then the PUT is a harmless no-operation. 464 // If all of these are empty, then the POST is a harmless no-operation.
434 465
435 XmlInventoryCollection entity = ReconstituteEntity(rdata); 466 XmlInventoryCollection entity = ReconstituteEntity(rdata);
436 467
437 // Inlined assets can be included in entity. These must be incorporated into 468 // Inlined assets can be included in entity. These must be incorporated into
438 // the asset database before we attempt to update the inventory. If anything 469 // the asset database before we attempt to update the inventory. If anything
439 // fails, return a failure to requestor. 470 // fails, return a failure to requestor.
440 471
441 if (entity.Assets.Count > 0) 472 if (entity.Assets.Count > 0)
442 { 473 {
474
443 Rest.Log.DebugFormat("{0} Adding {1} assets to server", 475 Rest.Log.DebugFormat("{0} Adding {1} assets to server",
444 MsgId, entity.Assets.Count); 476 MsgId, entity.Assets.Count);
445 477
@@ -449,11 +481,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
449 MsgId, asset.ID, asset.Type, asset.Name); 481 MsgId, asset.ID, asset.Type, asset.Name);
450 Rest.AssetServices.AddAsset(asset); 482 Rest.AssetServices.AddAsset(asset);
451 483
452 if (Rest.DumpAsset) 484 created = true;
485 rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1}<p>",
486 asset.Name, asset.ID));
487
488 if (Rest.DEBUG && Rest.DumpAsset)
453 { 489 {
454 Rest.Dump(asset.Data); 490 Rest.Dump(asset.Data);
455 } 491 }
492
456 } 493 }
494
457 } 495 }
458 496
459 // Modify the context using the collection of folders and items 497 // Modify the context using the collection of folders and items
@@ -461,6 +499,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
461 499
462 foreach (InventoryFolderBase folder in entity.Folders) 500 foreach (InventoryFolderBase folder in entity.Folders)
463 { 501 {
502
464 InventoryFolderBase found; 503 InventoryFolderBase found;
465 504
466 // If the parentID is zero, then this folder is going 505 // If the parentID is zero, then this folder is going
@@ -468,9 +507,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
468 // may have already set the parent ID explicitly, in which 507 // may have already set the parent ID explicitly, in which
469 // case we don't have to do it here. 508 // case we don't have to do it here.
470 509
471 if (folder.ParentID == LLUUID.Zero) 510 if (folder.ParentID == LLUUID.Zero || folder.ParentID == context.ID)
472 { 511 {
512 if (newnode != String.Empty)
513 {
514 Rest.Log.DebugFormat("{0} Too many resources", MsgId);
515 rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed");
516 }
473 folder.ParentID = context.ID; 517 folder.ParentID = context.ID;
518 newnode = folder.Name;
474 } 519 }
475 520
476 // Search the existing inventory for an existing entry. If 521 // Search the existing inventory for an existing entry. If
@@ -496,12 +541,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
496 { 541 {
497 Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); 542 Rest.Log.DebugFormat("{0} Updating existing folder", MsgId);
498 Rest.InventoryServices.MoveFolder(folder); 543 Rest.InventoryServices.MoveFolder(folder);
544
545 modified = true;
546 rdata.appendStatus(String.Format("<p> Created folder {0}, UUID {1}<p>",
547 folder.Name, folder.ID));
499 } 548 }
500 else 549 else
501 { 550 {
502 Rest.Log.DebugFormat("{0} Adding new folder", MsgId); 551 Rest.Log.DebugFormat("{0} Adding new folder", MsgId);
503 Rest.InventoryServices.AddFolder(folder); 552 Rest.InventoryServices.AddFolder(folder);
553
554 created = true;
555 rdata.appendStatus(String.Format("<p> Modified folder {0}, UUID {1}<p>",
556 folder.Name, folder.ID));
557
504 } 558 }
559
505 } 560 }
506 561
507 // Now we repeat a similar process for the items included 562 // Now we repeat a similar process for the items included
@@ -509,6 +564,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
509 564
510 foreach (InventoryItemBase item in entity.Items) 565 foreach (InventoryItemBase item in entity.Items)
511 { 566 {
567
512 InventoryItemBase found = null; 568 InventoryItemBase found = null;
513 569
514 // If the parentID is zero, then this is going 570 // If the parentID is zero, then this is going
@@ -519,7 +575,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
519 item.Folder = context.ID; 575 item.Folder = context.ID;
520 } 576 }
521 577
522 // Determine whether this is a new item or a 578 // Determine whether this is a new item or a
523 // replacement definition. 579 // replacement definition.
524 580
525 foreach (InventoryItemBase xi in rdata.items) 581 foreach (InventoryItemBase xi in rdata.items)
@@ -537,60 +593,90 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
537 Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", 593 Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}",
538 MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); 594 MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
539 Rest.InventoryServices.UpdateItem(item); 595 Rest.InventoryServices.UpdateItem(item);
596 modified = true;
597 rdata.appendStatus(String.Format("<p> Modified item {0}, UUID {1}<p>", item.Name, item.ID));
540 } 598 }
541 else 599 else
542 { 600 {
543 Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", 601 Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}",
544 MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); 602 MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
545 Rest.InventoryServices.AddItem(item); 603 Rest.InventoryServices.AddItem(item);
604 created = true;
605 rdata.appendStatus(String.Format("<p> Created item {2}, UUID {3}<p>", item.Name, item.ID));
546 } 606 }
607
547 } 608 }
609
610 if (created)
611 {
612 // Must include a location header with a URI that identifies the new resource.
613 rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}/{2}",
614 rdata.hostname+":"+rdata.port,rdata.path,newnode));
615 rdata.Complete(Rest.HttpStatusCodeCreated);
616 }
617 else
618 {
619 if (modified)
620 {
621 rdata.Complete(Rest.HttpStatusCodeOK);
622 }
623 else
624 {
625 rdata.Complete(Rest.HttpStatusCodeNoContent);
626 }
627 }
628
629 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
630
548 } 631 }
549 else 632 else
550 { 633 {
551 Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", 634 Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}",
552 MsgId, rdata.method, rdata.path, InventoryNode.GetType()); 635 MsgId, rdata.method, rdata.path, InventoryNode.GetType());
553 rdata.Fail(Rest.HttpStatusCodeBadRequest, 636 rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context");
554 Rest.HttpStatusDescBadRequest+": invalid resource context");
555 } 637 }
556 638
557 rdata.Complete();
558 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
559 } 639 }
560 640
561 /// <summary> 641 /// <summary>
562 /// POST updates the URI-identified element in the inventory. This 642 /// PUT updates the URI-identified element in the inventory. This
563 /// is actually far more flexible than it might at first sound. For 643 /// is actually far more flexible than it might at first sound. For
564 /// POST the URI serves two purposes: 644 /// PUT the URI serves two purposes:
565 /// [1] It identifies the user whose inventory is to be 645 /// [1] It identifies the user whose inventory is to be
566 /// processed. 646 /// processed.
567 /// [2] It optionally specifies a subtree of the inventory 647 /// [2] It optionally specifies a subtree of the inventory
568 /// that is to be used to resolve any relative subtree 648 /// that is to be used to resolve any relative subtree
569 /// specifications in the entity. If nothing is specified 649 /// specifications in the entity. If nothing is specified
570 /// then the whole inventory is implied. 650 /// then the whole of the private inventory is implied.
571 /// Please note that the subtree specified by the URI is only relevant 651 /// Please note that the subtree specified by the URI is only relevant
572 /// to an entity containing a URI relative specification, i.e. one or 652 /// to an entity containing a URI relative specification, i.e. one or
573 /// more elements do not specify parent folder information. These 653 /// more elements do not specify parent folder information. These
574 /// elements will be implicitly referenced within the context identified 654 /// elements will be implicitly referenced within the context identified
575 /// by the URI. 655 /// by the URI.
576 /// If an element in the entity specifies an explicit parent folder, then 656 /// If an element in the entity specifies an explicit parent folder, then
577 /// that parent is effective, regardless of any value specified in the 657 /// that parent is effective, regardless of any value specified in the
578 /// URI. If the parent does not exist, then the element, and any dependent 658 /// URI. If the parent does not exist, then the element, and any dependent
579 /// elements, are ignored. This case is actually detected and handled 659 /// elements, are ignored. This case is actually detected and handled
580 /// during the reconstitution process. 660 /// during the reconstitution process.
581 /// </summary> 661 /// </summary>
662 /// <param name=rdata>HTTP service request work area</param>
582 663
583 private void DoPost(InventoryRequestData rdata) 664 private void DoUpdate(InventoryRequestData rdata)
584 { 665 {
585 int count = 0; 666
667 int count = 0;
668 bool created = false;
669 bool modified = false;
586 670
587 // Resolve the inventory node that is to be modified. 671 // Resolve the inventory node that is to be modified.
672 // rdata already contains information about the current
673 // content of the user's inventory.
588 674
589 Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); 675 Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill);
590 676
591 // As long as we have a node, then we have something 677 // As long as we have a node, then we have something
592 // meaningful to do, unlike PUT. So we reconstitute the 678 // meaningful to do, unlike POST. So we reconstitute the
593 // subtree before doing anything else. Note that we 679 // subtree before doing anything else. Note that we
594 // etiher got a valid node or we threw an exception. 680 // etiher got a valid node or we threw an exception.
595 681
596 XmlInventoryCollection entity = ReconstituteEntity(rdata); 682 XmlInventoryCollection entity = ReconstituteEntity(rdata);
@@ -612,46 +698,77 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
612 698
613 Rest.AssetServices.AddAsset(asset); 699 Rest.AssetServices.AddAsset(asset);
614 700
615 if (Rest.DumpAsset) 701 created = true;
702 rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1}<p>", asset.Name, asset.ID));
703
704 if (Rest.DEBUG && Rest.DumpAsset)
616 { 705 {
617 Rest.Dump(asset.Data); 706 Rest.Dump(asset.Data);
618 } 707 }
708
619 } 709 }
620 } 710 }
621 711
622 /// <summary> 712 // The URI specifies either a folder or an item to be updated.
623 /// The URI specifies either a folder or an item to be updated. 713 //
624 /// </summary> 714 // The root node in the entity will replace the node identified
625 /// <remarks> 715 // by the URI. This means the parent will remain the same, but
626 /// The root node in the entity will replace the node identified 716 // any or all attributes associated with the named element
627 /// by the URI. This means the parent will remain the same, but 717 // will change.
628 /// any or all attributes associated with the named element 718 //
629 /// will change. 719 // If the inventory collection contains an element with a zero
630 /// 720 // parent ID, then this is taken to be the replacement for the
631 /// If the inventory collection contains an element with a zero 721 // named node. The collection MAY also specify an explicit
632 /// parent ID, then this is taken to be the replacement for the 722 // parent ID, in this case it MAY identify the same parent as
633 /// named node. The collection MAY also specify an explicit 723 // the current node, or it MAY specify a different parent,
634 /// parent ID, in this case it MAY identify the same parent as 724 // indicating that the folder is being moved in addition to any
635 /// the current node, or it MAY specify a different parent, 725 // other modifications being made.
636 /// indicating that the folder is being moved in addition to any
637 /// other modifications being made.
638 /// </remarks>
639 726
640 if (typeof(InventoryFolderBase) == InventoryNode.GetType() || 727 if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
641 typeof(InventoryFolderImpl) == InventoryNode.GetType()) 728 typeof(InventoryFolderImpl) == InventoryNode.GetType())
642 { 729 {
730
731 bool rfound = false;
643 InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; 732 InventoryFolderBase uri = (InventoryFolderBase) InventoryNode;
644 InventoryFolderBase xml = null; 733 InventoryFolderBase xml = null;
734
735 // If the entity to be replaced resolved to be the root
736 // directory itself (My Inventory), then make sure that
737 // the supplied data include as appropriately typed and
738 // named folder. Note that we can;t rule out the possibility
739 // of a sub-directory being called "My Inventory", so that
740 // is anticipated.
741
742 if (uri == rdata.root)
743 {
744
745 foreach (InventoryFolderBase folder in entity.Folders)
746 {
747 if ((rfound = (folder.Name == PRIVATE_ROOT_NAME)))
748 {
749 if ((rfound = (folder.ParentID == LLUUID.Zero)))
750 break;
751 }
752 }
753
754 if (!rfound)
755 {
756 Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory",
757 MsgId, rdata.method, rdata.path);
758 rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure");
759 }
760
761 }
645 762
646 // Scan the set of folders in the entity collection for an 763 // Scan the set of folders in the entity collection for an
647 // entry that matches the context folder. It is assumed that 764 // entry that matches the context folder. It is assumed that
648 // the only reliable indicator of this is a zero UUID (using 765 // the only reliable indicator of this is a zero UUID ( using
649 // implicit context), or the parent's UUID matches that of the 766 // implicit context), or the parent's UUID matches that of the
650 // URI designated node (explicit context). We don't allow 767 // URI designated node (explicit context). We don't allow
651 // ambiguity in this case because this is POST and we are 768 // ambiguity in this case because this is POST and we are
652 // supposed to be modifying a specific node. 769 // supposed to be modifying a specific node.
653 // We assign any element IDs required as an economy; we don't 770 // We assign any element IDs required as an economy; we don't
654 // want to iterate over the fodler set again if it can be 771 // want to iterate over the fodler set again if it can be
655 // helped. 772 // helped.
656 773
657 foreach (InventoryFolderBase folder in entity.Folders) 774 foreach (InventoryFolderBase folder in entity.Folders)
@@ -663,51 +780,60 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
663 xml = folder; 780 xml = folder;
664 count++; 781 count++;
665 } 782 }
666 if (xml.ID == LLUUID.Zero)
667 {
668 xml.ID = LLUUID.Random();
669 }
670 } 783 }
671 784
672 // More than one entry is ambiguous. Other folders should be 785 // More than one entry is ambiguous. Other folders should be
673 // added using the PUT verb. 786 // added using the POST verb.
674 787
675 if (count > 1) 788 if (count > 1)
676 { 789 {
677 Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", 790 Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous",
678 MsgId, rdata.method, rdata.path); 791 MsgId, rdata.method, rdata.path);
679 rdata.Fail(Rest.HttpStatusCodeBadRequest, 792 rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous");
680 Rest.HttpStatusDescBadRequest+": context is ambiguous");
681 } 793 }
682 794
683 // Exactly one entry means we ARE replacing the node 795 // Exactly one entry means we ARE replacing the node
684 // identified by the URI. So we delete the old folder 796 // identified by the URI. So we delete the old folder
685 // by moving it to the trash and then purging it. 797 // by moving it to the trash and then purging it.
686 // We then add all of the folders and items we 798 // We then add all of the folders and items we
687 // included in the entity. The subtree has been 799 // included in the entity. The subtree has been
688 // modified. 800 // modified.
689 801
690 if (count == 1) 802 if (count == 1)
691 { 803 {
804
692 InventoryFolderBase TrashCan = GetTrashCan(rdata); 805 InventoryFolderBase TrashCan = GetTrashCan(rdata);
693 806
807 // All went well, so we generate a UUID is one is
808 // needed.
809
810 if (xml.ID == LLUUID.Zero)
811 {
812 xml.ID = LLUUID.Random();
813 }
814
694 uri.ParentID = TrashCan.ID; 815 uri.ParentID = TrashCan.ID;
695 Rest.InventoryServices.MoveFolder(uri); 816 Rest.InventoryServices.MoveFolder(uri);
696 Rest.InventoryServices.PurgeFolder(TrashCan); 817 Rest.InventoryServices.PurgeFolder(TrashCan);
818 modified = true;
819
697 } 820 }
698 821
699 // Now, regardelss of what they represent, we 822 // Now, regardelss of what they represent, we
700 // integrate all of the elements in the entity. 823 // integrate all of the elements in the entity.
701 824
702 foreach (InventoryFolderBase f in entity.Folders) 825 foreach (InventoryFolderBase f in entity.Folders)
703 { 826 {
827 rdata.appendStatus(String.Format("<p>Moving folder {0} UUID {1}<p>", f.Name, f.ID));
704 Rest.InventoryServices.MoveFolder(f); 828 Rest.InventoryServices.MoveFolder(f);
705 } 829 }
706 830
707 foreach (InventoryItemBase it in entity.Items) 831 foreach (InventoryItemBase it in entity.Items)
708 { 832 {
833 rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1}<p>", it.Name, it.ID));
709 Rest.InventoryServices.AddItem(it); 834 Rest.InventoryServices.AddItem(it);
710 } 835 }
836
711 } 837 }
712 838
713 /// <summary> 839 /// <summary>
@@ -720,6 +846,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
720 846
721 else 847 else
722 { 848 {
849
723 InventoryItemBase uri = (InventoryItemBase) InventoryNode; 850 InventoryItemBase uri = (InventoryItemBase) InventoryNode;
724 InventoryItemBase xml = null; 851 InventoryItemBase xml = null;
725 852
@@ -727,20 +854,18 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
727 { 854 {
728 Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", 855 Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>",
729 MsgId, rdata.method, rdata.path); 856 MsgId, rdata.method, rdata.path);
730 rdata.Fail(Rest.HttpStatusCodeBadRequest, 857 rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed");
731 Rest.HttpStatusDescBadRequest+": folder is not allowed");
732 } 858 }
733 859
734 if (entity.Items.Count > 1) 860 if (entity.Items.Count > 1)
735 { 861 {
736 Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", 862 Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>",
737 MsgId, rdata.method, rdata.path); 863 MsgId, rdata.method, rdata.path);
738 rdata.Fail(Rest.HttpStatusCodeBadRequest, 864 rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items");
739 Rest.HttpStatusDescBadRequest+": too may items");
740 } 865 }
741 866
742 xml = entity.Items[0]; 867 xml = entity.Items[0];
743 868
744 if (xml.ID == LLUUID.Zero) 869 if (xml.ID == LLUUID.Zero)
745 { 870 {
746 xml.ID = LLUUID.Random(); 871 xml.ID = LLUUID.Random();
@@ -757,10 +882,29 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
757 // Add the new item to the inventory 882 // Add the new item to the inventory
758 883
759 Rest.InventoryServices.AddItem(xml); 884 Rest.InventoryServices.AddItem(xml);
885
886 rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1}<p>", xml.Name, xml.ID));
887
888 }
889
890 if (created)
891 {
892 rdata.Complete(Rest.HttpStatusCodeCreated);
893 }
894 else
895 {
896 if (modified)
897 {
898 rdata.Complete(Rest.HttpStatusCodeOK);
899 }
900 else
901 {
902 rdata.Complete(Rest.HttpStatusCodeNoContent);
903 }
760 } 904 }
761 905
762 rdata.Complete();
763 rdata.Respond("Inventory " + rdata.method + ": Normal completion"); 906 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
907
764 } 908 }
765 909
766 /// <summary> 910 /// <summary>
@@ -773,7 +917,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
773 /// 917 ///
774 /// Folders are deleted by moving them to another folder and then 918 /// Folders are deleted by moving them to another folder and then
775 /// purging that folder. We'll do that by creating a temporary 919 /// purging that folder. We'll do that by creating a temporary
776 /// sub-folder in the TrashCan and purging that folder's 920 /// sub-folder in the TrashCan and purging that folder's
777 /// contents. If we can't can it, we don't delete it... 921 /// contents. If we can't can it, we don't delete it...
778 /// So, if no trashcan is available, the request does nothing. 922 /// So, if no trashcan is available, the request does nothing.
779 /// Items are summarily deleted. 923 /// Items are summarily deleted.
@@ -782,14 +926,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
782 /// be performed using UUID, as a name might identify several 926 /// be performed using UUID, as a name might identify several
783 /// elements. 927 /// elements.
784 /// </summary> 928 /// </summary>
929 /// <param name=rdata>HTTP service request work area</param>
785 930
786 private void DoDelete(InventoryRequestData rdata) 931 private void DoDelete(InventoryRequestData rdata)
787 { 932 {
788 Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); 933
934 Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false);
789 935
790 if (typeof(InventoryFolderBase) == InventoryNode.GetType() || 936 if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
791 typeof(InventoryFolderImpl) == InventoryNode.GetType()) 937 typeof(InventoryFolderImpl) == InventoryNode.GetType())
792 { 938 {
939
793 InventoryFolderBase TrashCan = GetTrashCan(rdata); 940 InventoryFolderBase TrashCan = GetTrashCan(rdata);
794 941
795 InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; 942 InventoryFolderBase folder = (InventoryFolderBase) InventoryNode;
@@ -798,6 +945,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
798 folder.ParentID = TrashCan.ID; 945 folder.ParentID = TrashCan.ID;
799 Rest.InventoryServices.MoveFolder(folder); 946 Rest.InventoryServices.MoveFolder(folder);
800 Rest.InventoryServices.PurgeFolder(TrashCan); 947 Rest.InventoryServices.PurgeFolder(TrashCan);
948
949 rdata.appendStatus(String.Format("<p>Deleted folder {0} UUID {1}<p>", folder.Name, folder.ID));
950
801 } 951 }
802 952
803 // Deleting items is much more straight forward. 953 // Deleting items is much more straight forward.
@@ -808,21 +958,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
808 Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", 958 Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted",
809 MsgId, rdata.method, rdata.path); 959 MsgId, rdata.method, rdata.path);
810 Rest.InventoryServices.DeleteItem(item); 960 Rest.InventoryServices.DeleteItem(item);
961 rdata.appendStatus(String.Format("<p>Deleted item {0} UUID {1}<p>", item.Name, item.ID));
811 } 962 }
812 963
813 rdata.Complete(); 964 rdata.Complete();
814 rdata.Respond("Inventory " + rdata.method + ": Normal completion"); 965 rdata.Respond("Inventory " + rdata.method + ": Normal completion");
966
815 } 967 }
816 968
817 #endregion method-specific processing 969#endregion method-specific processing
818 970
819 /// <summary> 971 /// <summary>
820 /// This method is called to obtain the OpenSim inventory object identified 972 /// This method is called to obtain the OpenSim inventory object identified
821 /// by the supplied URI. This may be either an Item or a Folder, so a suitably 973 /// by the supplied URI. This may be either an Item or a Folder, so a suitably
822 /// ambiguous return type is employed (Object). This method recurses as 974 /// ambiguous return type is employed (Object). This method recurses as
823 /// necessary to process the designated hierarchy. 975 /// necessary to process the designated hierarchy.
824 /// 976 ///
825 /// If we reach the end of the URI then we return the contextural folder to 977 /// If we reach the end of the URI then we return the contextual folder to
826 /// our caller. 978 /// our caller.
827 /// 979 ///
828 /// If we are not yet at the end of the URI we attempt to find a child folder 980 /// If we are not yet at the end of the URI we attempt to find a child folder
@@ -831,50 +983,79 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
831 /// If this is the last node, then we look to see if this is an item. If it is, 983 /// If this is the last node, then we look to see if this is an item. If it is,
832 /// we return that item. 984 /// we return that item.
833 /// 985 ///
986 /// If we reach the end of an inventory path and the URI si not yet exhausted,
987 /// then if 'fill' is specified, we create the intermediate nodes.
988 ///
834 /// Otherwise we fail the request on the ground of an invalid URI. 989 /// Otherwise we fail the request on the ground of an invalid URI.
835 /// 990 ///
836 /// <note> 991 /// An ambiguous request causes the request to fail.
837 /// This mechanism cannot detect the case where duplicate subtrees satisfy a
838 /// request. In such a case the 1st element gets processed. If this is a
839 /// problem, then UUID should be used to identify the end-node. This is basic
840 /// premise of normal inventory processing. The name is an informational, and
841 /// not a defining, attribute.
842 /// </note>
843 /// 992 ///
844 /// </summary> 993 /// </summary>
845 994 /// <param name=rdata>HTTP service request work area</param>
846 private Object getInventoryNode(InventoryRequestData rdata, InventoryFolderBase folder, int pi) 995 /// <param name=folder>The folder to be searched (parent)</param>
996 /// <param name=pi>URI parameter index</param>
997 /// <param name=fill>Should missing path members be created?</param>
998
999 private Object getInventoryNode(InventoryRequestData rdata,
1000 InventoryFolderBase folder,
1001 int pi, bool fill)
847 { 1002 {
1003
1004 InventoryFolderBase foundf = null;
1005 int fk = 0;
1006
848 Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); 1007 Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
849 1008
850 // We have just run off the end of the parameter sequence 1009 // We have just run off the end of the parameter sequence
851 1010
852 if (pi >= rdata.parameters.Length) 1011 if (pi >= rdata.Parameters.Length)
853 { 1012 {
854 return folder; 1013 return folder;
855 } 1014 }
856 1015
857 // More names in the sequence, look for a folder that might 1016 // There are more names in the parameter sequence,
858 // get us there. 1017 // look for the folder named by param[pi] as a
1018 // child of the folder supplied as an argument.
1019 // Note that a UUID may have been supplied as the
1020 // identifier (it is the ONLY guaranteed unambiguous
1021 // option.
859 1022
860 if (rdata.folders != null) 1023 if (rdata.folders != null)
861 {
862 foreach (InventoryFolderBase f in rdata.folders) 1024 foreach (InventoryFolderBase f in rdata.folders)
863 { 1025 {
864 // Look for the present node in the directory list 1026 // Look for the present node in the directory list
865 if (f.ParentID == folder.ID && 1027 if (f.ParentID == folder.ID &&
866 (f.Name == rdata.parameters[pi] || 1028 (f.Name == rdata.Parameters[pi] ||
867 f.ID.ToString() == rdata.parameters[pi])) 1029 f.ID.ToString() == rdata.Parameters[pi]))
868 { 1030 {
869 return getInventoryNode(rdata, f, pi+1); 1031 foundf = f;
1032 fk++;
870 } 1033 }
871 } 1034 }
1035
1036 // If more than one node matched, then the path, as specified
1037 // is ambiguous.
1038
1039 if (fk > 1)
1040 {
1041 Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
1042 MsgId, rdata.method, rdata.path);
1043 rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous");
1044 }
1045
1046 // If we find a match, then the method
1047 // increment the parameter index, and calls itself
1048 // passing the found folder as the new context.
1049
1050 if (foundf != null)
1051 {
1052 return getInventoryNode(rdata, foundf, pi+1, fill);
872 } 1053 }
873 1054
874 // No folders that match. Perhaps this parameter identifies an item? If 1055 // No folders that match. Perhaps this parameter identifies an item? If
875 // it does, then it MUST also be the last name in the sequence. 1056 // it does, then it MUST also be the last name in the sequence.
876 1057
877 if (pi == rdata.parameters.Length-1) 1058 if (pi == rdata.Parameters.Length-1)
878 { 1059 {
879 if (rdata.items != null) 1060 if (rdata.items != null)
880 { 1061 {
@@ -882,9 +1063,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
882 InventoryItemBase li = null; 1063 InventoryItemBase li = null;
883 foreach (InventoryItemBase i in rdata.items) 1064 foreach (InventoryItemBase i in rdata.items)
884 { 1065 {
885 if (i.Folder == folder.ID && 1066 if (i.Folder == folder.ID &&
886 (i.Name == rdata.parameters[pi] || 1067 (i.Name == rdata.Parameters[pi] ||
887 i.ID.ToString() == rdata.parameters[pi])) 1068 i.ID.ToString() == rdata.Parameters[pi]))
888 { 1069 {
889 li = i; 1070 li = i;
890 k++; 1071 k++;
@@ -894,26 +1075,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
894 { 1075 {
895 return li; 1076 return li;
896 } 1077 }
897 else 1078 else if (k > 1)
898 { 1079 {
899 Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", 1080 Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
900 MsgId, rdata.method, rdata.path); 1081 MsgId, rdata.method, rdata.path);
901 rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": request is ambiguous"); 1082 rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous");
902 } 1083 }
903 } 1084 }
904 } 1085 }
905 1086
906 // No, so abandon the request 1087 // If fill is enabled, then we must create the missing intermediate nodes.
1088 // And of course, even this is not straightforward. All intermediate nodes
1089 // are obviously folders, but the last node may be a folder or an item.
1090
1091 if (fill)
1092 {
1093 }
1094
1095 // No fill, so abandon the request
907 1096
908 Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", 1097 Rest.Log.DebugFormat("{0} {1}: Resource {2} not found",
909 MsgId, rdata.method, rdata.path); 1098 MsgId, rdata.method, rdata.path);
910 rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": resource "+rdata.path+" not found"); 1099 rdata.Fail(Rest.HttpStatusCodeNotFound, "resource "+rdata.path+" not found");
911 1100
912 return null; /* Never reached */ 1101 return null; /* Never reached */
1102
913 } 1103 }
914 1104
915 /// <summary> 1105 /// <summary>
916 /// This routine traverse the inventory's structure until the end-point identified 1106 /// This routine traverse the inventory's structure until the end-point identified
917 /// in the URI is reached, the remainder of the inventory (if any) is then formatted 1107 /// in the URI is reached, the remainder of the inventory (if any) is then formatted
918 /// and returned to the requestor. 1108 /// and returned to the requestor.
919 /// 1109 ///
@@ -923,57 +1113,142 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
923 /// 1113 ///
924 /// Only the last element in the URI should identify an item. 1114 /// Only the last element in the URI should identify an item.
925 /// </summary> 1115 /// </summary>
1116 /// <param name=rdata>HTTP service request work area</param>
1117 /// <param name=folder>The folder to be searched (parent)</param>
1118 /// <param name=pi>URI parameter index</param>
926 1119
927 private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) 1120 private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
928 { 1121 {
929 Rest.Log.DebugFormat("{0} Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); 1122
1123 Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
930 1124
931 if (rdata.folders != null) 1125 if (rdata.folders != null)
932 { 1126 {
933 foreach (InventoryFolderBase f in rdata.folders) 1127
1128 // If there was only one parameter (avatar name), then the entire
1129 // inventory is being requested.
1130
1131 if (rdata.Parameters.Length == 1)
934 { 1132 {
935 if (f.ParentID == folder.ID && 1133 formatInventory(rdata, rdata.root, String.Empty);
936 (f.Name == rdata.parameters[pi] || 1134 }
937 f.ID.ToString() == rdata.parameters[pi])) 1135
1136 // Has the client specified the root directory name explicitly?
1137 // if yes, then we just absorb the reference, because the folder
1138 // we start looking in for a match *is* the root directory. If there
1139 // are more parameters remaining we tarverse, otehrwise it's time
1140 // to format. Otherwise,we consider the "My Inventory" to be implied
1141 // and we just traverse normally.
1142
1143 else if (folder.ID.ToString() == rdata.Parameters[pi] ||
1144 folder.Name == rdata.Parameters[pi])
1145 {
1146 // Length is -1 because the avatar name is a parameter
1147 if (pi<(rdata.Parameters.Length-1))
938 { 1148 {
939 if (pi < rdata.parameters.Length-1) 1149 traverseInventory(rdata, folder, pi+1);
940 {
941 traverseInventory(rdata, f, pi+1);
942 }
943 else
944 {
945 formatInventory(rdata, f, String.Empty);
946 }
947 return;
948 } 1150 }
1151 else
1152 {
1153 formatInventory(rdata, folder, String.Empty);
1154 }
1155 }
1156 else
1157 {
1158 traverseInventory(rdata, folder, pi);
949 } 1159 }
1160
1161 return;
1162
950 } 1163 }
1164 }
1165
1166 /// <summary>
1167 /// This is the recursive method. I've separated them in this way so that
1168 /// we do not have to waste cycles on any first-case-only processing.
1169 /// </summary>
1170
1171 private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
1172 {
1173
1174 int fk = 0;
1175 InventoryFolderBase ffound = null;
1176 InventoryItemBase ifound = null;
951 1177
952 if (pi == rdata.parameters.Length-1) 1178 Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
1179
1180 foreach (InventoryFolderBase f in rdata.folders)
953 { 1181 {
1182 if (f.ParentID == folder.ID &&
1183 (f.Name == rdata.Parameters[pi] ||
1184 f.ID.ToString() == rdata.Parameters[pi]))
1185 {
1186 fk++;
1187 ffound = f;
1188 }
1189 }
1190
1191 // If this is the last element in the parameter sequence, then
1192 // it is reasonable to check for an item. All intermediate nodes
1193 // MUST be folders.
1194
1195 if (pi == rdata.Parameters.Length-1)
1196 {
1197
1198 // Only if there are any items, and there pretty much always are.
1199
954 if (rdata.items != null) 1200 if (rdata.items != null)
955 { 1201 {
956 foreach (InventoryItemBase i in rdata.items) 1202 foreach (InventoryItemBase i in rdata.items)
957 { 1203 {
958 if (i.Folder == folder.ID && 1204 if (i.Folder == folder.ID &&
959 (i.Name == rdata.parameters[pi] || 1205 (i.Name == rdata.Parameters[pi] ||
960 i.ID.ToString() == rdata.parameters[pi])) 1206 i.ID.ToString() == rdata.Parameters[pi]))
961 { 1207 {
962 // Fetching an Item has a special significance. In this 1208 fk++;
963 // case we also want to fetch the associated asset. 1209 ifound = i;
964 // To make it interesting, we'll d this via redirection.
965 string asseturl = "http://" + rdata.hostname + ":" + rdata.port +
966 "/admin/assets" + Rest.UrlPathSeparator + i.AssetID.ToString();
967 rdata.Redirect(asseturl,Rest.PERMANENT);
968 Rest.Log.DebugFormat("{0} Never Reached");
969 } 1210 }
970 } 1211 }
971 } 1212 }
972 } 1213 }
973 1214
974 Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", 1215 if (fk == 1)
1216 {
1217 if (ffound != null)
1218 {
1219 if (pi < rdata.Parameters.Length-1)
1220 {
1221 traverseInventory(rdata, ffound, pi+1);
1222 }
1223 else
1224 {
1225 formatInventory(rdata, ffound, String.Empty);
1226 }
1227 return;
1228 }
1229 else
1230 {
1231 // Fetching an Item has a special significance. In this
1232 // case we also want to fetch the associated asset.
1233 // To make it interesting, we'll d this via redirection.
1234 string asseturl = "http://" + rdata.hostname + ":" + rdata.port +
1235 "/admin/assets" + Rest.UrlPathSeparator + ifound.AssetID.ToString();
1236 rdata.Redirect(asseturl,Rest.PERMANENT);
1237 Rest.Log.DebugFormat("{0} Never Reached", MsgId);
1238 }
1239 }
1240 else if (fk > 1)
1241 {
1242 rdata.Fail(Rest.HttpStatusCodeConflict,
1243 String.Format("ambiguous element ({0}) in path specified: <{1}>",
1244 pi, rdata.path));
1245 }
1246
1247 Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>",
975 MsgId, rdata.path); 1248 MsgId, rdata.path);
976 rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no such item/folder"); 1249 rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}",
1250 rdata.Parameters[pi]));
1251
977 } 1252 }
978 1253
979 /// <summary> 1254 /// <summary>
@@ -983,12 +1258,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
983 /// The indentation parameter is solely for the benefit of trace record 1258 /// The indentation parameter is solely for the benefit of trace record
984 /// formatting. 1259 /// formatting.
985 /// </summary> 1260 /// </summary>
1261 /// <param name=rdata>HTTP service request work area</param>
1262 /// <param name=folder>The folder to be searched (parent)</param>
1263 /// <param name=indent>pretty print indentation</param>
986 1264
987 private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) 1265 private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent)
988 { 1266 {
1267
989 if (Rest.DEBUG) 1268 if (Rest.DEBUG)
990 { 1269 {
991 Rest.Log.DebugFormat("{0} Folder : {1} {2} {3}", MsgId, folder.ID, indent, folder.Name); 1270 Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}",
1271 MsgId, folder.ID, indent, folder.Name, folder.Type);
992 indent += "\t"; 1272 indent += "\t";
993 } 1273 }
994 1274
@@ -997,6 +1277,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
997 rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); 1277 rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty);
998 rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); 1278 rdata.writer.WriteAttributeString("name",String.Empty,folder.Name);
999 rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); 1279 rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString());
1280 rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString());
1000 rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); 1281 rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString());
1001 rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); 1282 rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString());
1002 rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); 1283 rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString());
@@ -1026,21 +1307,28 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1026 // End folder item 1307 // End folder item
1027 1308
1028 rdata.writer.WriteEndElement(); 1309 rdata.writer.WriteEndElement();
1310
1029 } 1311 }
1030 1312
1031 /// <summary> 1313 /// <summary>
1032 /// This method generates XML that describes an instance of InventoryItemBase. 1314 /// This method generates XML that describes an instance of InventoryItemBase.
1033 /// </summary> 1315 /// </summary>
1316 /// <param name=rdata>HTTP service request work area</param>
1317 /// <param name=i>The item to be formatted</param>
1318 /// <param name=indent>Pretty print indentation</param>
1034 1319
1035 private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) 1320 private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent)
1036 { 1321 {
1037 Rest.Log.DebugFormat("{0} Item : {1} {2} {3}", MsgId, i.ID, indent, i.Name); 1322
1323 Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}",
1324 MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType);
1038 1325
1039 rdata.writer.WriteStartElement(String.Empty,"Item",String.Empty); 1326 rdata.writer.WriteStartElement(String.Empty,"Item",String.Empty);
1040 1327
1041 rdata.writer.WriteAttributeString("name",String.Empty,i.Name); 1328 rdata.writer.WriteAttributeString("name",String.Empty,i.Name);
1042 rdata.writer.WriteAttributeString("desc",String.Empty,i.Description); 1329 rdata.writer.WriteAttributeString("desc",String.Empty,i.Description);
1043 rdata.writer.WriteAttributeString("uuid",String.Empty,i.ID.ToString()); 1330 rdata.writer.WriteAttributeString("uuid",String.Empty,i.ID.ToString());
1331 rdata.writer.WriteAttributeString("folder",String.Empty,i.Folder.ToString());
1044 rdata.writer.WriteAttributeString("owner",String.Empty,i.Owner.ToString()); 1332 rdata.writer.WriteAttributeString("owner",String.Empty,i.Owner.ToString());
1045 rdata.writer.WriteAttributeString("creator",String.Empty,i.Creator.ToString()); 1333 rdata.writer.WriteAttributeString("creator",String.Empty,i.Creator.ToString());
1046 rdata.writer.WriteAttributeString("creationdate",String.Empty,i.CreationDate.ToString()); 1334 rdata.writer.WriteAttributeString("creationdate",String.Empty,i.CreationDate.ToString());
@@ -1062,10 +1350,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1062 rdata.writer.WriteElementString("Asset",i.AssetID.ToString()); 1350 rdata.writer.WriteElementString("Asset",i.AssetID.ToString());
1063 1351
1064 rdata.writer.WriteEndElement(); 1352 rdata.writer.WriteEndElement();
1353
1065 } 1354 }
1066 1355
1067 /// <summary> 1356 /// <summary>
1068 /// This method creates a "trashcan" folder to support folder and item 1357 /// This method creates a "trashcan" folder to support folder and item
1069 /// deletions by this interface. The xisting trash folder is found and 1358 /// deletions by this interface. The xisting trash folder is found and
1070 /// this folder is created within it. It is called "tmp" to indicate to 1359 /// this folder is created within it. It is called "tmp" to indicate to
1071 /// the client that it is OK to delete this folder. The REST interface 1360 /// the client that it is OK to delete this folder. The REST interface
@@ -1073,9 +1362,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1073 /// If the trash can cannot be created, then by implication the request 1362 /// If the trash can cannot be created, then by implication the request
1074 /// that required it cannot be completed, and it fails accordingly. 1363 /// that required it cannot be completed, and it fails accordingly.
1075 /// </summary> 1364 /// </summary>
1365 /// <param name=rdata>HTTP service request work area</param>
1076 1366
1077 private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) 1367 private InventoryFolderBase GetTrashCan(InventoryRequestData rdata)
1078 { 1368 {
1369
1079 InventoryFolderBase TrashCan = null; 1370 InventoryFolderBase TrashCan = null;
1080 1371
1081 foreach (InventoryFolderBase f in rdata.folders) 1372 foreach (InventoryFolderBase f in rdata.folders)
@@ -1102,29 +1393,31 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1102 } 1393 }
1103 } 1394 }
1104 } 1395 }
1105 1396
1106 if (TrashCan == null) 1397 if (TrashCan == null)
1107 { 1398 {
1108 Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); 1399 Rest.Log.DebugFormat("{0} No Trash Can available", MsgId);
1109 rdata.Fail(Rest.HttpStatusCodeServerError, 1400 rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can");
1110 Rest.HttpStatusDescServerError+": unable to create trash can");
1111 } 1401 }
1112 1402
1113 return TrashCan; 1403 return TrashCan;
1404
1114 } 1405 }
1115 1406
1116 /// <summary> 1407 /// <summary>
1117 /// Make sure that an unchanged folder is not unnecessarily 1408 /// Make sure that an unchanged folder is not unnecessarily
1118 /// processed. 1409 /// processed.
1119 /// </summary> 1410 /// </summary>
1411 /// <param name=newf>Folder obtained from enclosed entity</param>
1412 /// <param name=oldf>Folder obtained from the user's inventory</param>
1120 1413
1121 private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) 1414 private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf)
1122 { 1415 {
1123 return (newf.Name != oldf.Name 1416 return ( newf.Name != oldf.Name
1124 || newf.ParentID != oldf.ParentID 1417 || newf.ParentID != oldf.ParentID
1125 || newf.Owner != oldf.Owner 1418 || newf.Owner != oldf.Owner
1126 || newf.Type != oldf.Type 1419 || newf.Type != oldf.Type
1127 || newf.Version != oldf.Version 1420 || newf.Version != oldf.Version
1128 ); 1421 );
1129 } 1422 }
1130 1423
@@ -1132,27 +1425,29 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1132 /// Make sure that an unchanged item is not unnecessarily 1425 /// Make sure that an unchanged item is not unnecessarily
1133 /// processed. 1426 /// processed.
1134 /// </summary> 1427 /// </summary>
1428 /// <param name=newf>Item obtained from enclosed entity</param>
1429 /// <param name=oldf>Item obtained from the user's inventory</param>
1135 1430
1136 private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) 1431 private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf)
1137 { 1432 {
1138 return (newf.Name != oldf.Name 1433 return ( newf.Name != oldf.Name
1139 || newf.Folder != oldf.Description 1434 || newf.Folder != oldf.Description
1140 || newf.Description != oldf.Description 1435 || newf.Description != oldf.Description
1141 || newf.Owner != oldf.Owner 1436 || newf.Owner != oldf.Owner
1142 || newf.Creator != oldf.Creator 1437 || newf.Creator != oldf.Creator
1143 || newf.AssetID != oldf.AssetID 1438 || newf.AssetID != oldf.AssetID
1144 || newf.GroupID != oldf.GroupID 1439 || newf.GroupID != oldf.GroupID
1145 || newf.GroupOwned != oldf.GroupOwned 1440 || newf.GroupOwned != oldf.GroupOwned
1146 || newf.InvType != oldf.InvType 1441 || newf.InvType != oldf.InvType
1147 || newf.AssetType != oldf.AssetType 1442 || newf.AssetType != oldf.AssetType
1148 ); 1443 );
1149 } 1444 }
1150 1445
1151 /// <summary> 1446 /// <summary>
1152 /// This method is called by PUT and POST to create an XmlInventoryCollection 1447 /// This method is called by PUT and POST to create an XmlInventoryCollection
1153 /// instance that reflects the content of the entity supplied on the request. 1448 /// instance that reflects the content of the entity supplied on the request.
1154 /// Any elements in the completed collection whose UUID is zero, are 1449 /// Any elements in the completed collection whose UUID is zero, are
1155 /// considered to be located relative to the end-point identified int he 1450 /// considered to be located relative to the end-point identified int he
1156 /// URI. In this way, an entire sub-tree can be conveyed in a single REST 1451 /// URI. In this way, an entire sub-tree can be conveyed in a single REST
1157 /// PUT or POST request. 1452 /// PUT or POST request.
1158 /// 1453 ///
@@ -1160,24 +1455,27 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1160 /// has an entity, it is more completely initialized. thus, if no entity was 1455 /// has an entity, it is more completely initialized. thus, if no entity was
1161 /// provided the collection is valid, but empty. 1456 /// provided the collection is valid, but empty.
1162 /// 1457 ///
1163 /// The entity is then scanned and each tag is processed to produce the 1458 /// The entity is then scanned and each tag is processed to produce the
1164 /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection 1459 /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection
1165 /// will reflect the subtree described by the entity. 1460 /// will reflect the subtree described by the entity.
1166 /// 1461 ///
1167 /// This is a very flexible mechanism, the entity may contain arbitrary, 1462 /// This is a very flexible mechanism, the entity may contain arbitrary,
1168 /// discontiguous tree fragments, or may contain single element. The caller is 1463 /// discontiguous tree fragments, or may contain single element. The caller is
1169 /// responsible for integrating this collection (and ensuring that any 1464 /// responsible for integrating this collection (and ensuring that any
1170 /// missing parent IDs are resolved). 1465 /// missing parent IDs are resolved).
1171 /// </summary> 1466 /// </summary>
1467 /// <param name=rdata>HTTP service request work area</param>
1172 1468
1173 internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) 1469 internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata)
1174 { 1470 {
1471
1175 Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); 1472 Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId);
1176 1473
1177 XmlInventoryCollection ic = new XmlInventoryCollection(); 1474 XmlInventoryCollection ic = new XmlInventoryCollection();
1178 1475
1179 if (rdata.request.HasEntityBody) 1476 if (rdata.request.HasEntityBody)
1180 { 1477 {
1478
1181 Rest.Log.DebugFormat("{0} Entity present", MsgId); 1479 Rest.Log.DebugFormat("{0} Entity present", MsgId);
1182 1480
1183 ic.init(rdata); 1481 ic.init(rdata);
@@ -1273,6 +1571,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1273 Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); 1571 Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message);
1274 throw e; 1572 throw e;
1275 } 1573 }
1574
1276 } 1575 }
1277 else 1576 else
1278 { 1577 {
@@ -1288,13 +1587,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1288 } 1587 }
1289 1588
1290 return ic; 1589 return ic;
1590
1291 } 1591 }
1292 1592
1293 /// <summary> 1593 /// <summary>
1294 /// This method creates an inventory Folder from the 1594 /// This method creates an inventory Folder from the
1295 /// information supplied in the request's entity. 1595 /// information supplied in the request's entity.
1296 /// A folder instance is created and initialized to reflect 1596 /// A folder instance is created and initialized to reflect
1297 /// default values. These values are then overridden 1597 /// default values. These values are then overridden
1298 /// by information supplied in the entity. 1598 /// by information supplied in the entity.
1299 /// If context was not explicitly provided, then the 1599 /// If context was not explicitly provided, then the
1300 /// appropriate ID values are determined. 1600 /// appropriate ID values are determined.
@@ -1302,6 +1602,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1302 1602
1303 private void CollectFolder(XmlInventoryCollection ic) 1603 private void CollectFolder(XmlInventoryCollection ic)
1304 { 1604 {
1605
1305 Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); 1606 Rest.Log.DebugFormat("{0} Interpret folder element", MsgId);
1306 1607
1307 InventoryFolderBase result = new InventoryFolderBase(); 1608 InventoryFolderBase result = new InventoryFolderBase();
@@ -1341,10 +1642,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1341 result.Version = UInt16.Parse(ic.xml.Value); 1642 result.Version = UInt16.Parse(ic.xml.Value);
1342 break; 1643 break;
1343 default : 1644 default :
1344 Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", 1645 Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}",
1345 MsgId, ic.xml.Name, ic.xml.Value); 1646 MsgId, ic.xml.Name, ic.xml.Value);
1346 ic.Fail(Rest.HttpStatusCodeBadRequest, 1647 ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>",
1347 Rest.HttpStatusDescBadRequest+": unrecognized attribute"); 1648 ic.xml.Name));
1348 break; 1649 break;
1349 } 1650 }
1350 } 1651 }
@@ -1363,11 +1664,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1363 } 1664 }
1364 else 1665 else
1365 { 1666 {
1667
1366 bool found = false; 1668 bool found = false;
1367 1669
1368 foreach (InventoryFolderBase parent in ic.rdata.folders) 1670 foreach (InventoryFolderBase parent in ic.rdata.folders)
1369 { 1671 {
1370 if (parent.ID == result.ParentID) 1672 if ( parent.ID == result.ParentID )
1371 { 1673 {
1372 found = true; 1674 found = true;
1373 break; 1675 break;
@@ -1376,11 +1678,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1376 1678
1377 if (!found) 1679 if (!found)
1378 { 1680 {
1379 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", 1681 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}",
1380 MsgId, ic.Item.Folder, result.ID); 1682 MsgId, ic.Item.Folder, result.ID);
1381 ic.Fail(Rest.HttpStatusCodeBadRequest, 1683 ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent");
1382 Rest.HttpStatusDescBadRequest+": invalid parent");
1383 } 1684 }
1685
1384 } 1686 }
1385 1687
1386 // This is a new folder, so no existing UUID is available 1688 // This is a new folder, so no existing UUID is available
@@ -1395,14 +1697,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1395 // obsolete as a consequence. 1697 // obsolete as a consequence.
1396 1698
1397 ic.Push(result); 1699 ic.Push(result);
1700
1398 } 1701 }
1399 1702
1400 /// <summary> 1703 /// <summary>
1401 /// This method is called to handle the construction of an Item 1704 /// This method is called to handle the construction of an Item
1402 /// instance from the supplied request entity. It is called 1705 /// instance from the supplied request entity. It is called
1403 /// whenever an Item start tag is detected. 1706 /// whenever an Item start tag is detected.
1404 /// An instance of an Item is created and initialized to default 1707 /// An instance of an Item is created and initialized to default
1405 /// values. These values are then overridden from values supplied 1708 /// values. These values are then overridden from values supplied
1406 /// as attributes to the Item element. 1709 /// as attributes to the Item element.
1407 /// This item is then stored in the XmlInventoryCollection and 1710 /// This item is then stored in the XmlInventoryCollection and
1408 /// will be verified by Validate. 1711 /// will be verified by Validate.
@@ -1412,6 +1715,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1412 1715
1413 private void CollectItem(XmlInventoryCollection ic) 1716 private void CollectItem(XmlInventoryCollection ic)
1414 { 1717 {
1718
1415 Rest.Log.DebugFormat("{0} Interpret item element", MsgId); 1719 Rest.Log.DebugFormat("{0} Interpret item element", MsgId);
1416 1720
1417 InventoryItemBase result = new InventoryItemBase(); 1721 InventoryItemBase result = new InventoryItemBase();
@@ -1432,6 +1736,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1432 { 1736 {
1433 for (int i = 0; i < ic.xml.AttributeCount; i++) 1737 for (int i = 0; i < ic.xml.AttributeCount; i++)
1434 { 1738 {
1739
1435 ic.xml.MoveToAttribute(i); 1740 ic.xml.MoveToAttribute(i);
1436 1741
1437 switch (ic.xml.Name) 1742 switch (ic.xml.Name)
@@ -1480,36 +1785,37 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1480 break; 1785 break;
1481 1786
1482 default : 1787 default :
1483 Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", 1788 Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}",
1484 MsgId, ic.xml.Name, ic.xml.Value); 1789 MsgId, ic.xml.Name, ic.xml.Value);
1485 ic.Fail(Rest.HttpStatusCodeBadRequest, 1790 ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute",
1486 Rest.HttpStatusDescBadRequest+": unrecognized attribute"); 1791 ic.xml.Name));
1487 break; 1792 break;
1488 } 1793 }
1489 } 1794 }
1490 } 1795 }
1491 1796
1492 ic.xml.MoveToElement(); 1797 ic.xml.MoveToElement();
1493 1798
1494 ic.Push(result); 1799 ic.Push(result);
1800
1495 } 1801 }
1496 1802
1497 /// <summary> 1803 /// <summary>
1498 /// This method assembles an asset instance from the 1804 /// This method assembles an asset instance from the
1499 /// information supplied in the request's entity. It is 1805 /// information supplied in the request's entity. It is
1500 /// called as a result of detecting a start tag for a 1806 /// called as a result of detecting a start tag for a
1501 /// type of Asset. 1807 /// type of Asset.
1502 /// The information is collected locally, and an asset 1808 /// The information is collected locally, and an asset
1503 /// instance is created only if the basic XML parsing 1809 /// instance is created only if the basic XML parsing
1504 /// completes successfully. 1810 /// completes successfully.
1505 /// Default values for all parts of the asset are 1811 /// Default values for all parts of the asset are
1506 /// established before overriding them from the supplied 1812 /// established before overriding them from the supplied
1507 /// XML. 1813 /// XML.
1508 /// If an asset has inline=true as an attribute, then 1814 /// If an asset has inline=true as an attribute, then
1509 /// the element contains the data representing the 1815 /// the element contains the data representing the
1510 /// asset. This is saved as the data component. 1816 /// asset. This is saved as the data component.
1511 /// inline=false means that the element's payload is 1817 /// inline=false means that the element's payload is
1512 /// simply the UUID of the asset referenced by the 1818 /// simply the UUID of the asset referenced by the
1513 /// item being constructed. 1819 /// item being constructed.
1514 /// An asset, if created is stored in the 1820 /// An asset, if created is stored in the
1515 /// XmlInventoryCollection 1821 /// XmlInventoryCollection
@@ -1570,10 +1876,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1570 break; 1876 break;
1571 1877
1572 default : 1878 default :
1573 Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", 1879 Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}",
1574 MsgId, ic.xml.Name, ic.xml.Value); 1880 MsgId, ic.xml.Name, ic.xml.Value);
1575 ic.Fail(Rest.HttpStatusCodeBadRequest, 1881 ic.Fail(Rest.HttpStatusCodeBadRequest,
1576 Rest.HttpStatusDescBadRequest); 1882 String.Format("unrecognized attribute <{0}>", ic.xml.Name));
1577 break; 1883 break;
1578 } 1884 }
1579 } 1885 }
@@ -1583,7 +1889,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1583 1889
1584 // If this is a reference to an existing asset, just store the 1890 // If this is a reference to an existing asset, just store the
1585 // asset ID into the item. 1891 // asset ID into the item.
1586 1892
1587 if (!inline) 1893 if (!inline)
1588 { 1894 {
1589 if (ic.Item != null) 1895 if (ic.Item != null)
@@ -1594,17 +1900,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1594 else 1900 else
1595 { 1901 {
1596 Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); 1902 Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId);
1597 ic.Fail(Rest.HttpStatusCodeBadRequest, 1903 ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset");
1598 Rest.HttpStatusDescBadRequest+": no context for asset");
1599 } 1904 }
1600 } 1905 }
1601 1906
1602 // Otherwise, generate an asset ID, store that into the item, and 1907 // Otherwise, generate an asset ID, store that into the item, and
1603 // create an entry in the asset list for the inlined asset. But 1908 // create an entry in the asset list for the inlined asset. But
1604 // only if the size is non-zero. 1909 // only if the size is non-zero.
1605 1910
1606 else 1911 else
1607 { 1912 {
1913
1608 string b64string = null; 1914 string b64string = null;
1609 1915
1610 // Generate a UUID of none were given, and generally none should 1916 // Generate a UUID of none were given, and generally none should
@@ -1617,17 +1923,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1617 1923
1618 // Create AssetBase entity to hold the inlined asset 1924 // Create AssetBase entity to hold the inlined asset
1619 1925
1620 asset = new AssetBase(uuid, name); 1926 asset = new AssetBase(uuid, name);
1621 1927
1622 asset.Description = desc; 1928 asset.Description = desc;
1623 asset.Type = type; // type == 0 == texture 1929 asset.Type = type; // type == 0 == texture
1624 asset.Local = local; 1930 asset.Local = local;
1625 asset.Temporary = temp; 1931 asset.Temporary = temp;
1626 1932
1627 b64string = ic.xml.ReadElementContentAsString(); 1933 b64string = ic.xml.ReadElementContentAsString();
1628 1934
1629 Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); 1935 Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length);
1630 Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, 1936 Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId,
1631 b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); 1937 b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length));
1632 1938
1633 asset.Data = Convert.FromBase64String(b64string); 1939 asset.Data = Convert.FromBase64String(b64string);
@@ -1646,19 +1952,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1646 { 1952 {
1647 ic.Item.AssetID = uuid; 1953 ic.Item.AssetID = uuid;
1648 } 1954 }
1955
1649 } 1956 }
1650 1957
1651 ic.Push(asset); 1958 ic.Push(asset);
1959
1652 } 1960 }
1653 1961
1654 /// <summary> 1962 /// <summary>
1655 /// Store any permissions information provided by the request. 1963 /// Store any permissions information provided by the request.
1656 /// This overrides the default permissions set when the 1964 /// This overrides the default permissions set when the
1657 /// XmlInventoryCollection object was created. 1965 /// XmlInventoryCollection object was created.
1658 /// </summary> 1966 /// </summary>
1659 1967
1660 private void CollectPermissions(XmlInventoryCollection ic) 1968 private void CollectPermissions(XmlInventoryCollection ic)
1661 { 1969 {
1970
1662 if (ic.xml.HasAttributes) 1971 if (ic.xml.HasAttributes)
1663 { 1972 {
1664 for (int i = 0; i < ic.xml.AttributeCount; i++) 1973 for (int i = 0; i < ic.xml.AttributeCount; i++)
@@ -1681,14 +1990,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1681 default : 1990 default :
1682 Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", 1991 Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}",
1683 MsgId,ic.xml.Name, ic.xml.Value); 1992 MsgId,ic.xml.Name, ic.xml.Value);
1684 ic.Fail(Rest.HttpStatusCodeBadRequest, 1993 ic.Fail(Rest.HttpStatusCodeBadRequest,
1685 Rest.HttpStatusDescBadRequest); 1994 String.Format("invalid attribute <{0}>", ic.xml.Name));
1686 break; 1995 break;
1687 } 1996 }
1688 } 1997 }
1689 } 1998 }
1690 1999
1691 ic.xml.MoveToElement(); 2000 ic.xml.MoveToElement();
2001
1692 } 2002 }
1693 2003
1694 /// <summary> 2004 /// <summary>
@@ -1703,35 +2013,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1703 2013
1704 private void Validate(XmlInventoryCollection ic) 2014 private void Validate(XmlInventoryCollection ic)
1705 { 2015 {
2016
1706 // There really should be an item present if we've 2017 // There really should be an item present if we've
1707 // called validate. So fail if there is not. 2018 // called validate. So fail if there is not.
1708 2019
1709 if (ic.Item == null) 2020 if (ic.Item == null)
1710 { 2021 {
1711 Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); 2022 Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId);
1712 ic.Fail(Rest.HttpStatusCodeBadRequest, 2023 ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error");
1713 Rest.HttpStatusDescBadRequest+": request parse error");
1714 } 2024 }
1715 2025
1716 // Every item is required to have a name (via REST anyway) 2026 // Every item is required to have a name (via REST anyway)
1717 2027
1718 if (ic.Item.Name == String.Empty) 2028 if (ic.Item.Name == String.Empty)
1719 { 2029 {
1720 Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); 2030 Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId);
1721 ic.Fail(Rest.HttpStatusCodeBadRequest, 2031 ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required");
1722 Rest.HttpStatusDescBadRequest+": item name required");
1723 } 2032 }
1724 2033
1725 // An item MUST have an asset ID. AssetID should never be zero 2034 // An item MUST have an asset ID. AssetID should never be zero
1726 // here. It should always get set from the information stored 2035 // here. It should always get set from the information stored
1727 // when the Asset element was processed. 2036 // when the Asset element was processed.
1728 2037
1729 if (ic.Item.AssetID == LLUUID.Zero) 2038 if (ic.Item.AssetID == LLUUID.Zero)
1730 { 2039 {
2040
1731 Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); 2041 Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId);
1732 Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); 2042 Rest.Log.InfoFormat("{0} Asset information is missing", MsgId);
1733 ic.Fail(Rest.HttpStatusCodeBadRequest, 2043 ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required");
1734 Rest.HttpStatusDescBadRequest+": asset information required"); 2044
1735 } 2045 }
1736 2046
1737 // If the item is new, then assign it an ID 2047 // If the item is new, then assign it an ID
@@ -1744,18 +2054,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1744 // If the context is being implied, obtain the current 2054 // If the context is being implied, obtain the current
1745 // folder item's ID. If it was specified explicitly, make 2055 // folder item's ID. If it was specified explicitly, make
1746 // sure that theparent folder exists. 2056 // sure that theparent folder exists.
1747 2057
1748 if (ic.Item.Folder == LLUUID.Zero) 2058 if (ic.Item.Folder == LLUUID.Zero)
1749 { 2059 {
1750 ic.Item.Folder = ic.Parent(); 2060 ic.Item.Folder = ic.Parent();
1751 } 2061 }
1752 else 2062 else
1753 { 2063 {
2064
1754 bool found = false; 2065 bool found = false;
1755 2066
1756 foreach (InventoryFolderBase parent in ic.rdata.folders) 2067 foreach (InventoryFolderBase parent in ic.rdata.folders)
1757 { 2068 {
1758 if (parent.ID == ic.Item.Folder) 2069 if ( parent.ID == ic.Item.Folder )
1759 { 2070 {
1760 found = true; 2071 found = true;
1761 break; 2072 break;
@@ -1764,11 +2075,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1764 2075
1765 if (!found) 2076 if (!found)
1766 { 2077 {
1767 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", 2078 Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}",
1768 MsgId, ic.Item.Folder, ic.Item.ID); 2079 MsgId, ic.Item.Folder, ic.Item.ID);
1769 ic.Fail(Rest.HttpStatusCodeBadRequest, 2080 ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required");
1770 Rest.HttpStatusDescBadRequest+": parent information required");
1771 } 2081 }
2082
1772 } 2083 }
1773 2084
1774 // If this is an inline asset being constructed in the context 2085 // If this is an inline asset being constructed in the context
@@ -1790,12 +2101,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1790 ic.Item.NextPermissions = ic.NextPermissions; 2101 ic.Item.NextPermissions = ic.NextPermissions;
1791 2102
1792 // If no type was specified for this item, we can attempt to 2103 // If no type was specified for this item, we can attempt to
1793 // infer something from the file type maybe. This is NOT as 2104 // infer something from the file type maybe. This is NOT as
1794 // good as having type be specified in the XML. 2105 // good as having type be specified in the XML.
1795 2106
1796 if (ic.Item.AssetType == (int) AssetType.Unknown || 2107 if (ic.Item.AssetType == (int) AssetType.Unknown ||
1797 ic.Item.InvType == (int) AssetType.Unknown) 2108 ic.Item.InvType == (int) AssetType.Unknown)
1798 { 2109 {
2110
1799 Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); 2111 Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId);
1800 2112
1801 string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); 2113 string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD);
@@ -1815,7 +2127,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1815 2127
1816 if (parts.Length > 1) 2128 if (parts.Length > 1)
1817 { 2129 {
1818 Rest.Log.DebugFormat("{0} File type is {1}", 2130 Rest.Log.DebugFormat("{0} File type is {1}",
1819 MsgId, parts[parts.Length - 1]); 2131 MsgId, parts[parts.Length - 1]);
1820 switch (parts[parts.Length - 1]) 2132 switch (parts[parts.Length - 1])
1821 { 2133 {
@@ -1823,7 +2135,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1823 case "jpeg-2000" : 2135 case "jpeg-2000" :
1824 case "jpg2000" : 2136 case "jpg2000" :
1825 case "jpg-2000" : 2137 case "jpg-2000" :
1826 Rest.Log.DebugFormat("{0} Type {1} inferred", 2138 Rest.Log.DebugFormat("{0} Type {1} inferred",
1827 MsgId, parts[parts.Length-1]); 2139 MsgId, parts[parts.Length-1]);
1828 if (ic.Item.AssetType == (int) AssetType.Unknown) 2140 if (ic.Item.AssetType == (int) AssetType.Unknown)
1829 ic.Item.AssetType = (int) AssetType.ImageJPEG; 2141 ic.Item.AssetType = (int) AssetType.ImageJPEG;
@@ -1832,7 +2144,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1832 break; 2144 break;
1833 case "jpg" : 2145 case "jpg" :
1834 case "jpeg" : 2146 case "jpeg" :
1835 Rest.Log.DebugFormat("{0} Type {1} inferred", 2147 Rest.Log.DebugFormat("{0} Type {1} inferred",
1836 MsgId, parts[parts.Length - 1]); 2148 MsgId, parts[parts.Length - 1]);
1837 if (ic.Item.AssetType == (int) AssetType.Unknown) 2149 if (ic.Item.AssetType == (int) AssetType.Unknown)
1838 ic.Item.AssetType = (int) AssetType.ImageJPEG; 2150 ic.Item.AssetType = (int) AssetType.ImageJPEG;
@@ -1873,19 +2185,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1873 temp = OpenJPEGNet.LoadTGAClass.LoadTGA(tgadata); 2185 temp = OpenJPEGNet.LoadTGAClass.LoadTGA(tgadata);
1874 ic.Asset.Data = OpenJPEGNet.OpenJPEG.EncodeFromImage(temp, true); 2186 ic.Asset.Data = OpenJPEGNet.OpenJPEG.EncodeFromImage(temp, true);
1875 } 2187 }
1876 2188
1877 ic.reset(); 2189 ic.reset();
2190
1878 } 2191 }
1879 2192
1880 #region Inventory RequestData extension 2193 #region Inventory RequestData extension
1881 2194
1882 internal class InventoryRequestData : RequestData 2195 internal class InventoryRequestData : RequestData
1883 { 2196 {
2197
1884 /// <summary> 2198 /// <summary>
1885 /// These are the inventory specific request/response state 2199 /// These are the inventory specific request/response state
1886 /// extensions. 2200 /// extensions.
1887 /// </summary> 2201 /// </summary>
1888 2202
2203 internal LLUUID uuid = LLUUID.Zero;
1889 internal bool HaveInventory = false; 2204 internal bool HaveInventory = false;
1890 internal ICollection<InventoryFolderImpl> folders = null; 2205 internal ICollection<InventoryFolderImpl> folders = null;
1891 internal ICollection<InventoryItemBase> items = null; 2206 internal ICollection<InventoryItemBase> items = null;
@@ -1898,7 +2213,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1898 } 2213 }
1899 2214
1900 /// <summary> 2215 /// <summary>
1901 /// This is the callback method required by inventory services. The 2216 /// This is the callback method required by inventory services. The
1902 /// requestor issues an inventory request and then blocks until this 2217 /// requestor issues an inventory request and then blocks until this
1903 /// method signals the monitor. 2218 /// method signals the monitor.
1904 /// </summary> 2219 /// </summary>
@@ -1914,6 +2229,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1914 Monitor.Pulse(this); 2229 Monitor.Pulse(this);
1915 } 2230 }
1916 } 2231 }
2232
1917 } 2233 }
1918 2234
1919 #endregion Inventory RequestData extension 2235 #endregion Inventory RequestData extension
@@ -1926,6 +2242,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
1926 2242
1927 internal class XmlInventoryCollection : InventoryCollection 2243 internal class XmlInventoryCollection : InventoryCollection
1928 { 2244 {
2245
1929 internal InventoryRequestData rdata; 2246 internal InventoryRequestData rdata;
1930 private Stack<InventoryFolderBase> stk; 2247 private Stack<InventoryFolderBase> stk;
1931 2248
@@ -2014,10 +2331,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
2014 initPermissions(); 2331 initPermissions();
2015 } 2332 }
2016 2333
2017 internal void Fail(int code, string desc) 2334 internal void Fail(int code, string addendum)
2018 { 2335 {
2019 rdata.Fail(code, desc); 2336 rdata.Fail(code, addendum);
2020 } 2337 }
2338
2021 } 2339 }
2022 } 2340 }
2023} 2341}
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs
index a5165d9..984f6d3 100644
--- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs
+++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs
@@ -140,7 +140,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
140 { 140 {
141 if (!rdata.IsAuthenticated) 141 if (!rdata.IsAuthenticated)
142 { 142 {
143 rdata.Fail(Rest.HttpStatusCodeNotAuthorized, Rest.HttpStatusDescNotAuthorized); 143 rdata.Fail(Rest.HttpStatusCodeNotAuthorized,
144 String.Format("user \"{0}\" could not be authenticated", rdata.userName));
144 } 145 }
145 } 146 }
146 catch (RestException e) 147 catch (RestException e)
@@ -160,10 +161,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
160 161
161 // Check that a test was specified 162 // Check that a test was specified
162 163
163 if (rdata.parameters.Length < 1) 164 if (rdata.Parameters.Length < 1)
164 { 165 {
165 Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId); 166 Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId);
166 rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); 167 rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters");
167 } 168 }
168 169
169 // Select the test 170 // Select the test
@@ -180,8 +181,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
180 private static bool testsLoaded = false; 181 private static bool testsLoaded = false;
181 private static List<Type> classes = new List<Type>(); 182 private static List<Type> classes = new List<Type>();
182 private static List<ITest> tests = new List<ITest>(); 183 private static List<ITest> tests = new List<ITest>();
183 private static Type[] parms = new Type[1]; 184 private static Type[] parms = new Type[0];
184 private static Object[] args = new Object[1]; 185 private static Object[] args = new Object[0];
185 186
186 static RestTestServices() 187 static RestTestServices()
187 { 188 {
@@ -191,9 +192,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
191 Type[] types = m.GetTypes(); 192 Type[] types = m.GetTypes();
192 foreach (Type t in types) 193 foreach (Type t in types)
193 { 194 {
194 if (t.GetInterface("ITest") != null) 195 try
195 { 196 {
196 classes.Add(t); 197 if (t.GetInterface("ITest") != null)
198 {
199 classes.Add(t);
200 }
201 }
202 catch (Exception e)
203 {
204 Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message);
197 } 205 }
198 } 206 }
199 } 207 }
@@ -205,27 +213,38 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
205 /// registering itself with this handler. 213 /// registering itself with this handler.
206 /// I was not able to make this code work in a constructor. 214 /// I was not able to make this code work in a constructor.
207 /// </summary> 215 /// </summary>
216
208 private void loadTests() 217 private void loadTests()
209 { 218 {
210 lock (tests) 219 lock (tests)
211 { 220 {
212 if (!testsLoaded) 221 if (!testsLoaded)
213 { 222 {
214 parms[0] = this.GetType();
215 args[0] = this;
216 223
217 ConstructorInfo ci; 224 ConstructorInfo ci;
218 Object ht; 225 Object ht;
219 226
220 foreach (Type t in classes) 227 foreach (Type t in classes)
221 { 228 {
222 ci = t.GetConstructor(parms); 229 try
223 ht = ci.Invoke(args); 230 {
224 tests.Add((ITest)ht); 231 if (t.GetInterface("ITest") != null)
232 {
233 ci = t.GetConstructor(parms);
234 ht = ci.Invoke(args);
235 tests.Add((ITest)ht);
236 Rest.Log.WarnFormat("{0} Test {1} added", MsgId, t);
237 }
238 }
239 catch (Exception e)
240 {
241 Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message);
242 }
225 } 243 }
226 testsLoaded = true; 244 testsLoaded = true;
227 } 245 }
228 } 246 }
229 } 247 }
248
230 } 249 }
231} 250}