diff options
Diffstat (limited to '')
7 files changed, 636 insertions, 319 deletions
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs index 6f52582..5fd0219 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs | |||
@@ -34,8 +34,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
34 | /// <summary> | 34 | /// <summary> |
35 | /// This interface represents the boundary between the general purpose | 35 | /// This interface represents the boundary between the general purpose |
36 | /// REST plugin handling, and the functionally specific handlers. The | 36 | /// REST plugin handling, and the functionally specific handlers. The |
37 | /// handler knows only to initialzie and terminate all such handlers | 37 | /// handler knows only to initialize and terminate all such handlers |
38 | /// that it finds. | 38 | /// that it finds. Implementing this interface identifies the class as |
39 | /// a REST handler implementation. | ||
39 | /// </summary> | 40 | /// </summary> |
40 | 41 | ||
41 | internal interface IRest | 42 | internal interface IRest |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs index a885b25..6e42b6c 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs | |||
@@ -34,6 +34,7 @@ using System.Security.Cryptography; | |||
34 | using System.Text.RegularExpressions; | 34 | using System.Text.RegularExpressions; |
35 | using System.Collections.Generic; | 35 | using System.Collections.Generic; |
36 | using System.Collections.Specialized; | 36 | using System.Collections.Specialized; |
37 | using OpenSim.Framework; | ||
37 | using OpenSim.Framework.Servers; | 38 | using OpenSim.Framework.Servers; |
38 | using libsecondlife; | 39 | using libsecondlife; |
39 | using System.Xml; | 40 | using System.Xml; |
@@ -50,7 +51,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
50 | /// This structure is created on entry to the Handler | 51 | /// This structure is created on entry to the Handler |
51 | /// method and is disposed of upon return. It is part of | 52 | /// method and is disposed of upon return. It is part of |
52 | /// the plug-in infrastructure, rather than the functionally | 53 | /// the plug-in infrastructure, rather than the functionally |
53 | /// specifici REST handler, and fundamental changes to | 54 | /// specific REST handler, and fundamental changes to |
54 | /// this should be reflected in the Rest HandlerVersion. The | 55 | /// this should be reflected in the Rest HandlerVersion. The |
55 | /// object is instantiated, and may be extended by, any | 56 | /// object is instantiated, and may be extended by, any |
56 | /// given handler. See the inventory handler for an example | 57 | /// given handler. See the inventory handler for an example |
@@ -71,11 +72,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
71 | 72 | ||
72 | internal OSHttpRequest request = null; | 73 | internal OSHttpRequest request = null; |
73 | internal OSHttpResponse response = null; | 74 | internal OSHttpResponse response = null; |
75 | internal string qprefix = null; | ||
74 | 76 | ||
75 | // Request lifetime values | 77 | // Request lifetime values |
76 | 78 | ||
77 | internal NameValueCollection headers = null; | ||
78 | internal List<string> removed_headers = null; | ||
79 | internal byte[] buffer = null; | 79 | internal byte[] buffer = null; |
80 | internal string body = null; | 80 | internal string body = null; |
81 | internal string html = null; | 81 | internal string html = null; |
@@ -96,11 +96,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
96 | internal string hostname = "localhost"; | 96 | internal string hostname = "localhost"; |
97 | internal int port = 80; | 97 | internal int port = 80; |
98 | internal string prefix = Rest.UrlPathSeparator; | 98 | internal string prefix = Rest.UrlPathSeparator; |
99 | internal bool keepAlive = false; | ||
100 | internal bool chunked = false; | ||
99 | 101 | ||
100 | // Authentication related state | 102 | // Authentication related state |
101 | 103 | ||
102 | internal bool authenticated = false; | 104 | internal bool authenticated = false; |
103 | internal string scheme = Rest.AS_DIGEST; | 105 | // internal string scheme = Rest.AS_DIGEST; |
106 | // internal string scheme = Rest.AS_BASIC; | ||
107 | internal string scheme = null; | ||
104 | internal string realm = Rest.Realm; | 108 | internal string realm = Rest.Realm; |
105 | internal string domain = null; | 109 | internal string domain = null; |
106 | internal string nonce = null; | 110 | internal string nonce = null; |
@@ -148,13 +152,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
148 | private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*", | 152 | private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*", |
149 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 153 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
150 | 154 | ||
151 | private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>\\S+)\"", | 155 | private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"", |
152 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 156 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
153 | 157 | ||
154 | private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", | 158 | private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", |
155 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 159 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
156 | 160 | ||
157 | private static Regex reuserPass = new Regex("\\s*(?<user>\\w+)\\s*:\\s*(?<pass>\\S*)", | 161 | private static Regex reuserPass = new Regex("\\s*(?<user>[^:]+)\\s*:\\s*(?<pass>\\S*)", |
158 | RegexOptions.Compiled | RegexOptions.IgnoreCase); | 162 | RegexOptions.Compiled | RegexOptions.IgnoreCase); |
159 | 163 | ||
160 | // For efficiency, we create static instances of these objects | 164 | // For efficiency, we create static instances of these objects |
@@ -165,11 +169,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
165 | 169 | ||
166 | // Constructor | 170 | // Constructor |
167 | 171 | ||
168 | internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string qprefix) | 172 | internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix) |
169 | { | 173 | { |
170 | 174 | ||
171 | request = p_request; | 175 | request = p_request; |
172 | response = p_response; | 176 | response = p_response; |
177 | qprefix = p_qprefix; | ||
173 | 178 | ||
174 | sbuilder.Length = 0; | 179 | sbuilder.Length = 0; |
175 | 180 | ||
@@ -182,7 +187,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
182 | method = request.HttpMethod.ToLower(); | 187 | method = request.HttpMethod.ToLower(); |
183 | initUrl(); | 188 | initUrl(); |
184 | 189 | ||
185 | initParameters(qprefix.Length); | 190 | initParameters(p_qprefix.Length); |
186 | 191 | ||
187 | } | 192 | } |
188 | 193 | ||
@@ -254,11 +259,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
254 | } | 259 | } |
255 | 260 | ||
256 | // If we want a specific authentication mechanism, make sure | 261 | // If we want a specific authentication mechanism, make sure |
257 | // we get it. | 262 | // we get it. null indicates we don't care. non-null indicates |
263 | // a specific scheme requirement. | ||
258 | 264 | ||
259 | if (scheme != null && scheme.ToLower() != reqscheme) | 265 | if (scheme != null && scheme.ToLower() != reqscheme) |
260 | { | 266 | { |
261 | Rest.Log.DebugFormat("{0} Challenge reason: Required scheme not accepted", MsgId); | 267 | Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId); |
262 | DoChallenge(); | 268 | DoChallenge(); |
263 | } | 269 | } |
264 | 270 | ||
@@ -268,15 +274,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
268 | 274 | ||
269 | switch (reqscheme) | 275 | switch (reqscheme) |
270 | { | 276 | { |
271 | case "digest" : | 277 | case "digest" : |
272 | Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId); | 278 | Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId); |
273 | DoDigest(authdata); | 279 | DoDigest(authdata); |
274 | break; | 280 | break; |
275 | 281 | ||
276 | case "basic" : | 282 | case "basic" : |
277 | Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId); | 283 | Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId); |
278 | DoBasic(authdata); | 284 | DoBasic(authdata); |
279 | break; | 285 | break; |
280 | } | 286 | } |
281 | 287 | ||
282 | // If the current header is invalid, then a challenge is still needed. | 288 | // If the current header is invalid, then a challenge is still needed. |
@@ -406,10 +412,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
406 | authparms.TryGetValue("uri", out authPrefix); | 412 | authparms.TryGetValue("uri", out authPrefix); |
407 | 413 | ||
408 | // There MUST be a nonce string present. We're not preserving any server | 414 | // There MUST be a nonce string present. We're not preserving any server |
409 | // side state and we can;t validate the MD5 unless the lcient returns it | 415 | // side state and we can't validate the MD5 unless the client returns it |
410 | // to us, as it should. | 416 | // to us, as it should. |
411 | 417 | ||
412 | if (!authparms.TryGetValue("nonce", out nonce)) | 418 | if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) |
413 | { | 419 | { |
414 | Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); | 420 | Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); |
415 | break; | 421 | break; |
@@ -457,26 +463,28 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
457 | 463 | ||
458 | cnonce = authparms["cnonce"]; | 464 | cnonce = authparms["cnonce"]; |
459 | 465 | ||
460 | if (!authparms.ContainsKey("nc")) | 466 | if (!authparms.TryGetValue("nc", out nck) || nck == null) |
461 | { | 467 | { |
462 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); | 468 | Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); |
463 | break; | 469 | break; |
464 | } | 470 | } |
465 | 471 | ||
466 | nck = authparms["nc"]; | 472 | Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId); |
467 | 473 | ||
468 | if (cntable.TryGetValue(cnonce, out ncl)) | 474 | if (cntable.TryGetValue(nonce, out ncl)) |
469 | { | 475 | { |
470 | if (Rest.Hex2Int(ncl) <= Rest.Hex2Int(nck)) | 476 | Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl); |
477 | |||
478 | if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) | ||
471 | { | 479 | { |
472 | Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); | 480 | Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); |
473 | break; | 481 | break; |
474 | } | 482 | } |
475 | cntable[cnonce] = nck; | 483 | cntable[nonce] = nck; |
476 | } | 484 | } |
477 | else | 485 | else |
478 | { | 486 | { |
479 | lock (cntable) cntable.Add(cnonce, nck); | 487 | lock (cntable) cntable.Add(nonce, nck); |
480 | } | 488 | } |
481 | 489 | ||
482 | } | 490 | } |
@@ -519,6 +527,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
519 | 527 | ||
520 | sbuilder.Length = 0; | 528 | sbuilder.Length = 0; |
521 | 529 | ||
530 | if (scheme == null || scheme == Rest.AS_BASIC) | ||
531 | { | ||
532 | |||
533 | sbuilder.Append(Rest.AS_BASIC); | ||
534 | |||
535 | if (realm != null) | ||
536 | { | ||
537 | sbuilder.Append(" realm=\""); | ||
538 | sbuilder.Append(realm); | ||
539 | sbuilder.Append("\""); | ||
540 | } | ||
541 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | ||
542 | } | ||
543 | |||
544 | sbuilder.Length = 0; | ||
545 | |||
522 | if (scheme == null || scheme == Rest.AS_DIGEST) | 546 | if (scheme == null || scheme == Rest.AS_DIGEST) |
523 | { | 547 | { |
524 | 548 | ||
@@ -583,57 +607,135 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
583 | sbuilder.Append(Rest.CS_COMMA); | 607 | sbuilder.Append(Rest.CS_COMMA); |
584 | } | 608 | } |
585 | 609 | ||
586 | if (Rest.Domains.Count != 0) | 610 | // We don;t know the userid that will be used |
587 | { | 611 | // so we cannot make any authentication domain |
588 | sbuilder.Append("domain="); | 612 | // assumptions. So the prefix will determine |
589 | sbuilder.Append(Rest.CS_DQUOTE); | 613 | // this. |
590 | foreach (string dom in Rest.Domains.Values) | ||
591 | { | ||
592 | sbuilder.Append(dom); | ||
593 | sbuilder.Append(Rest.CS_SPACE); | ||
594 | } | ||
595 | if (sbuilder[sbuilder.Length-1] == Rest.C_SPACE) | ||
596 | { | ||
597 | sbuilder.Length = sbuilder.Length-1; | ||
598 | } | ||
599 | sbuilder.Append(Rest.CS_DQUOTE); | ||
600 | sbuilder.Append(Rest.CS_COMMA); | ||
601 | } | ||
602 | 614 | ||
603 | if (sbuilder[sbuilder.Length-1] == Rest.C_COMMA) | 615 | sbuilder.Append("domain="); |
604 | { | 616 | sbuilder.Append(Rest.CS_DQUOTE); |
605 | sbuilder.Length = sbuilder.Length-1; | 617 | sbuilder.Append(qprefix); |
606 | } | 618 | sbuilder.Append(Rest.CS_DQUOTE); |
607 | 619 | ||
608 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | 620 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); |
609 | 621 | ||
610 | } | 622 | } |
611 | 623 | ||
612 | if (scheme == null || scheme == Rest.AS_BASIC) | 624 | } |
613 | { | ||
614 | 625 | ||
615 | sbuilder.Append(Rest.AS_BASIC); | 626 | /// <summary> |
627 | /// This method provides validation in support of the BASIC | ||
628 | /// authentication method. This is not normaly expected to be | ||
629 | /// used, but is included for completeness (and because I tried | ||
630 | /// it first). | ||
631 | /// </summary> | ||
616 | 632 | ||
617 | if (realm != null) | 633 | private bool Validate(string user, string pass) |
618 | { | 634 | { |
619 | sbuilder.Append(" realm=\""); | 635 | |
620 | sbuilder.Append(realm); | 636 | Rest.Log.DebugFormat("{0} Simple User Validation", MsgId); |
621 | sbuilder.Append("\""); | 637 | |
622 | } | 638 | // Both values are required |
623 | AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); | 639 | |
624 | } | 640 | if (user == null || pass == null) |
641 | return false; | ||
642 | |||
643 | // Eliminate any leading or trailing spaces | ||
644 | user = user.Trim(); | ||
645 | |||
646 | return vetPassword(user, pass); | ||
625 | 647 | ||
626 | } | 648 | } |
627 | 649 | ||
628 | private bool Validate(string user, string pass) | 650 | /// <summary> |
651 | /// This mechanism is used by the digest authetnication mechanism | ||
652 | /// to return the user's password. In fact, because the OpenSim | ||
653 | /// user's passwords are already hashed, and the HTTP mechanism | ||
654 | /// does not supply an open password, the hashed passwords cannot | ||
655 | /// be used unless the cliemt has used the same salting mechanism | ||
656 | /// to has the password before using it in the authentication | ||
657 | /// algorithn. This is not inconceivable... | ||
658 | /// </summary> | ||
659 | |||
660 | private string getPassword(string user) | ||
629 | { | 661 | { |
630 | Rest.Log.DebugFormat("{0} Validating {1}:{2}", MsgId, user, pass); | 662 | |
631 | return user == "awebb" && pass == getPassword(user); | 663 | int x; |
664 | string first; | ||
665 | string last; | ||
666 | |||
667 | // Distinguish the parts, if necessary | ||
668 | |||
669 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) | ||
670 | { | ||
671 | first = user.Substring(0,x); | ||
672 | last = user.Substring(x+1); | ||
673 | } | ||
674 | else | ||
675 | { | ||
676 | first = user; | ||
677 | last = String.Empty; | ||
678 | } | ||
679 | |||
680 | UserProfileData udata = Rest.UserServices.GetUserProfile(first, last); | ||
681 | |||
682 | // If we don;t recognize the user id, perhaps it is god? | ||
683 | |||
684 | if (udata == null) | ||
685 | { | ||
686 | Rest.Log.DebugFormat("{0} Administrator", MsgId); | ||
687 | return Rest.GodKey; | ||
688 | } | ||
689 | else | ||
690 | { | ||
691 | Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); | ||
692 | return udata.PasswordHash; | ||
693 | } | ||
694 | |||
632 | } | 695 | } |
633 | 696 | ||
634 | private string getPassword(string user) | 697 | /// <summary> |
698 | /// This is used by the BASIC authentication scheme to calculate | ||
699 | /// the double hash used by OpenSim to encode user's passwords. | ||
700 | /// It returns true, if the supplied password is actually correct. | ||
701 | /// If the specified user-id is not recognized, but the password | ||
702 | /// matches the God password, then this is accepted as an admin | ||
703 | /// session. | ||
704 | /// </summary> | ||
705 | |||
706 | private bool vetPassword(string user, string pass) | ||
635 | { | 707 | { |
636 | return Rest.GodKey; | 708 | |
709 | int x; | ||
710 | string HA1; | ||
711 | string first; | ||
712 | string last; | ||
713 | |||
714 | // Distinguish the parts, if necessary | ||
715 | |||
716 | if ((x=user.IndexOf(Rest.C_SPACE)) != -1) | ||
717 | { | ||
718 | first = user.Substring(0,x); | ||
719 | last = user.Substring(x+1); | ||
720 | } | ||
721 | else | ||
722 | { | ||
723 | first = user; | ||
724 | last = String.Empty; | ||
725 | } | ||
726 | |||
727 | UserProfileData udata = Rest.UserServices.GetUserProfile(first, last); | ||
728 | |||
729 | // If we don;t recognize the user id, perhaps it is god? | ||
730 | |||
731 | if (udata == null) | ||
732 | return pass == Rest.GodKey; | ||
733 | |||
734 | HA1 = HashToString(pass); | ||
735 | HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt)); | ||
736 | |||
737 | return (0 == sc.Compare(HA1, udata.PasswordHash)); | ||
738 | |||
637 | } | 739 | } |
638 | 740 | ||
639 | // Validate the request-digest | 741 | // Validate the request-digest |
@@ -773,11 +875,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
773 | if (reset) | 875 | if (reset) |
774 | { | 876 | { |
775 | buffer = null; | 877 | buffer = null; |
776 | body = null; | 878 | SendHtml(message); |
879 | body = html; | ||
777 | } | 880 | } |
778 | 881 | ||
779 | if (Rest.DEBUG) | 882 | if (Rest.DEBUG) |
780 | { | 883 | { |
884 | Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId); | ||
781 | Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme); | 885 | Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme); |
782 | Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm); | 886 | Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm); |
783 | Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain); | 887 | Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain); |
@@ -828,10 +932,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
828 | { | 932 | { |
829 | 933 | ||
830 | Rest.Log.DebugFormat("{0} Generating Response", MsgId); | 934 | Rest.Log.DebugFormat("{0} Generating Response", MsgId); |
831 | 935 | Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); | |
832 | // Process any arbitrary headers collected | ||
833 | |||
834 | BuildHeaders(); | ||
835 | 936 | ||
836 | // A Head request can NOT have a body! | 937 | // A Head request can NOT have a body! |
837 | if (method != Rest.HEAD) | 938 | if (method != Rest.HEAD) |
@@ -839,6 +940,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
839 | 940 | ||
840 | Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); | 941 | Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); |
841 | 942 | ||
943 | // If the writer is non-null then we know that an XML | ||
944 | // data component exists. Flush and close the writer and | ||
945 | // then convert the result to the expected buffer format | ||
946 | // unless the request has already been failed for some | ||
947 | // reason. | ||
948 | |||
842 | if (writer != null) | 949 | if (writer != null) |
843 | { | 950 | { |
844 | Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId); | 951 | Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId); |
@@ -869,41 +976,57 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
869 | } | 976 | } |
870 | } | 977 | } |
871 | 978 | ||
979 | // OK, if the buffer contains something, regardless of how | ||
980 | // it got there, set various response headers accordingly. | ||
981 | |||
872 | if (buffer != null) | 982 | if (buffer != null) |
873 | { | 983 | { |
874 | Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); | 984 | Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); |
875 | if (response.Headers.Get("Content-Encoding") == null) | ||
876 | response.ContentEncoding = encoding; | ||
877 | response.ContentLength64 = buffer.Length; | 985 | response.ContentLength64 = buffer.Length; |
878 | response.SendChunked = false; | ||
879 | response.KeepAlive = false; | ||
880 | } | 986 | } |
987 | else | ||
988 | { | ||
989 | response.ContentLength64 = 0; | ||
990 | } | ||
991 | |||
992 | if (response.Headers.Get("Content-Encoding") == null) | ||
993 | response.ContentEncoding = encoding; | ||
994 | |||
995 | response.SendChunked = chunked; | ||
996 | response.KeepAlive = keepAlive; | ||
881 | 997 | ||
882 | } | 998 | } |
883 | 999 | ||
884 | // Set the status code & description. If nothing | 1000 | // Set the status code & description. If nothing has been stored, |
885 | // has been stored, we consider that a success | 1001 | // we consider that a success. |
886 | 1002 | ||
887 | if (statusCode == 0) | 1003 | if (statusCode == 0) |
888 | { | 1004 | { |
889 | Complete(); | 1005 | Complete(); |
890 | } | 1006 | } |
891 | 1007 | ||
1008 | // Set the response code in the actual carrier | ||
1009 | |||
892 | response.StatusCode = statusCode; | 1010 | response.StatusCode = statusCode; |
893 | 1011 | ||
894 | if (response.StatusCode == (int)OSHttpStatusCode.RedirectMovedTemporarily || | 1012 | // For a redirect we need to set the relocation header accordingly |
895 | response.StatusCode == (int)OSHttpStatusCode.RedirectMovedPermanently) | 1013 | |
1014 | if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect || | ||
1015 | response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect) | ||
896 | { | 1016 | { |
1017 | Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation); | ||
897 | response.RedirectLocation = redirectLocation; | 1018 | response.RedirectLocation = redirectLocation; |
898 | } | 1019 | } |
899 | 1020 | ||
1021 | // And include the status description if provided. | ||
1022 | |||
900 | if (statusDescription != null) | 1023 | if (statusDescription != null) |
901 | { | 1024 | { |
1025 | Rest.Log.DebugFormat("{0} Status description is {1}", MsgId, statusDescription); | ||
902 | response.StatusDescription = statusDescription; | 1026 | response.StatusDescription = statusDescription; |
903 | } | 1027 | } |
904 | 1028 | ||
905 | // Finally we send back our response, consuming | 1029 | // Finally we send back our response. |
906 | // any exceptions that doing so might produce. | ||
907 | 1030 | ||
908 | // We've left the setting of handled' until the | 1031 | // We've left the setting of handled' until the |
909 | // last minute because the header settings included | 1032 | // last minute because the header settings included |
@@ -913,6 +1036,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
913 | 1036 | ||
914 | handled = true; | 1037 | handled = true; |
915 | 1038 | ||
1039 | DumpHeaders(); | ||
1040 | |||
1041 | // if (request.InputStream != null) | ||
1042 | // { | ||
1043 | // Rest.Log.DebugFormat("{0} Closing input stream", MsgId); | ||
1044 | // request.InputStream.Close(); | ||
1045 | // } | ||
1046 | |||
916 | if (buffer != null && buffer.Length != 0) | 1047 | if (buffer != null && buffer.Length != 0) |
917 | { | 1048 | { |
918 | Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", | 1049 | Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", |
@@ -920,12 +1051,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
920 | response.OutputStream.Write(buffer, 0, buffer.Length); | 1051 | response.OutputStream.Write(buffer, 0, buffer.Length); |
921 | } | 1052 | } |
922 | 1053 | ||
923 | response.OutputStream.Close(); | 1054 | // Closing the outputstream should complete the transmission process |
924 | 1055 | ||
925 | if (request.InputStream != null) | 1056 | Rest.Log.DebugFormat("{0} Closing output stream", MsgId); |
926 | { | 1057 | response.OutputStream.Close(); |
927 | request.InputStream.Close(); | ||
928 | } | ||
929 | 1058 | ||
930 | } | 1059 | } |
931 | 1060 | ||
@@ -935,19 +1064,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
935 | 1064 | ||
936 | } | 1065 | } |
937 | 1066 | ||
938 | // Add a header to the table. If the header | 1067 | // Add a header to the table. We need to allow |
939 | // already exists, it is replaced. | 1068 | // multiple instances of many of the headers. |
1069 | // If the | ||
940 | 1070 | ||
941 | internal void AddHeader(string hdr, string data) | 1071 | internal void AddHeader(string hdr, string data) |
942 | { | 1072 | { |
943 | 1073 | if (Rest.DEBUG) | |
944 | if (headers == null) | ||
945 | { | 1074 | { |
946 | headers = new NameValueCollection(); | 1075 | Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", |
1076 | MsgId, hdr, data); | ||
1077 | if (response.Headers.Get(hdr) != null) | ||
1078 | { | ||
1079 | Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>", | ||
1080 | MsgId, hdr); | ||
1081 | } | ||
947 | } | 1082 | } |
948 | 1083 | response.Headers.Add(hdr, data); | |
949 | headers[hdr] = data; | ||
950 | |||
951 | } | 1084 | } |
952 | 1085 | ||
953 | // Keep explicit track of any headers which | 1086 | // Keep explicit track of any headers which |
@@ -955,43 +1088,30 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
955 | 1088 | ||
956 | internal void RemoveHeader(string hdr) | 1089 | internal void RemoveHeader(string hdr) |
957 | { | 1090 | { |
958 | 1091 | if (Rest.DEBUG) | |
959 | if (removed_headers == null) | ||
960 | { | ||
961 | removed_headers = new List<string>(); | ||
962 | } | ||
963 | |||
964 | removed_headers.Add(hdr); | ||
965 | |||
966 | if (headers != null) | ||
967 | { | 1092 | { |
968 | headers.Remove(hdr); | 1093 | Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); |
1094 | if (response.Headers.Get(hdr) == null) | ||
1095 | { | ||
1096 | Rest.Log.DebugFormat("{0} No such header existed", | ||
1097 | MsgId, hdr); | ||
1098 | } | ||
969 | } | 1099 | } |
970 | 1100 | response.Headers.Remove(hdr); | |
971 | } | 1101 | } |
972 | 1102 | ||
973 | // Should it prove necessary, we could always | 1103 | /// <summary> |
974 | // restore the header collection from a cloned | 1104 | /// Dump headers that will be generated in the response |
975 | // copy, but for now we'll assume that that is | 1105 | /// </summary> |
976 | // not necessary. | ||
977 | 1106 | ||
978 | private void BuildHeaders() | 1107 | internal void DumpHeaders() |
979 | { | 1108 | { |
980 | if (removed_headers != null) | 1109 | if (Rest.DEBUG) |
981 | { | ||
982 | foreach (string h in removed_headers) | ||
983 | { | ||
984 | Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, h); | ||
985 | response.Headers.Remove(h); | ||
986 | } | ||
987 | } | ||
988 | if (headers!= null) | ||
989 | { | 1110 | { |
990 | for (int i = 0; i < headers.Count; i++) | 1111 | for (int i=0;i<response.Headers.Count;i++) |
991 | { | 1112 | { |
992 | Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", | 1113 | Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i, |
993 | MsgId, headers.GetKey(i), headers.Get(i)); | 1114 | response.Headers.Get(i)); |
994 | response.Headers.Add(headers.GetKey(i), headers.Get(i)); | ||
995 | } | 1115 | } |
996 | } | 1116 | } |
997 | } | 1117 | } |
@@ -1019,7 +1139,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1019 | path = uri.AbsolutePath; | 1139 | path = uri.AbsolutePath; |
1020 | if (path.EndsWith(Rest.UrlPathSeparator)) | 1140 | if (path.EndsWith(Rest.UrlPathSeparator)) |
1021 | path = path.Substring(0,path.Length-1); | 1141 | path = path.Substring(0,path.Length-1); |
1022 | path = Uri.UnescapeDataString(path); | ||
1023 | } | 1142 | } |
1024 | 1143 | ||
1025 | // If we succeeded in getting a path, perform any | 1144 | // If we succeeded in getting a path, perform any |
@@ -1040,6 +1159,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1040 | pathNodes = EmptyPath; | 1159 | pathNodes = EmptyPath; |
1041 | } | 1160 | } |
1042 | 1161 | ||
1162 | // Elimiate any %-escaped values. This is left until here | ||
1163 | // so that escaped "+' are not mistakenly replaced. | ||
1164 | |||
1165 | path = Uri.UnescapeDataString(path); | ||
1166 | |||
1043 | // Request server context info | 1167 | // Request server context info |
1044 | 1168 | ||
1045 | hostname = uri.Host; | 1169 | hostname = uri.Host; |
@@ -1149,14 +1273,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1149 | 1273 | ||
1150 | internal void initXmlReader() | 1274 | internal void initXmlReader() |
1151 | { | 1275 | { |
1276 | |||
1152 | XmlReaderSettings settings = new XmlReaderSettings(); | 1277 | XmlReaderSettings settings = new XmlReaderSettings(); |
1278 | |||
1153 | settings.ConformanceLevel = ConformanceLevel.Fragment; | 1279 | settings.ConformanceLevel = ConformanceLevel.Fragment; |
1154 | settings.IgnoreComments = true; | 1280 | settings.IgnoreComments = true; |
1155 | settings.IgnoreWhitespace = true; | 1281 | settings.IgnoreWhitespace = true; |
1156 | settings.IgnoreProcessingInstructions = true; | 1282 | settings.IgnoreProcessingInstructions = true; |
1157 | settings.ValidationType = ValidationType.None; | 1283 | settings.ValidationType = ValidationType.None; |
1158 | // reader = XmlReader.Create(new StringReader(entity),settings); | 1284 | |
1159 | reader = XmlReader.Create(request.InputStream,settings); | 1285 | reader = XmlReader.Create(request.InputStream,settings); |
1286 | |||
1160 | } | 1287 | } |
1161 | 1288 | ||
1162 | private void Flush() | 1289 | private void Flush() |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs index e88c54d..439bbb4 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs | |||
@@ -69,7 +69,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
69 | internal static bool ExtendedEscape = true; | 69 | internal static bool ExtendedEscape = true; |
70 | internal static bool DumpAsset = false; | 70 | internal static bool DumpAsset = false; |
71 | internal static string Realm = "REST"; | 71 | internal static string Realm = "REST"; |
72 | internal static Dictionary<string,string> Domains = new Dictionary<string,string>(); | ||
73 | internal static int CreationDate = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; | 72 | internal static int CreationDate = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; |
74 | internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 | 73 | internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 |
75 | 74 | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs index 0fc937f..70957f5 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs | |||
@@ -44,35 +44,31 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
44 | public class RestAssetServices : IRest | 44 | public class RestAssetServices : IRest |
45 | { | 45 | { |
46 | 46 | ||
47 | private string key = "assets"; | ||
48 | private bool enabled = false; | 47 | private bool enabled = false; |
49 | private string qPrefix = "assets"; | 48 | private string qPrefix = "assets"; |
50 | 49 | ||
51 | // A simple constructor is used to handle any once-only | 50 | // A simple constructor is used to handle any once-only |
52 | // initialization of working classes. | 51 | // initialization of working classes. |
53 | 52 | ||
54 | public RestAssetServices(RestHandler p_rest) | 53 | public RestAssetServices() |
55 | { | 54 | { |
56 | 55 | ||
57 | Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); | 56 | Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); |
58 | 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); |
59 | 58 | ||
60 | // Integrate domain | 59 | // If the handler specifies a relative path for its domain |
60 | // then we must add the standard absolute prefix, e.g. /admin | ||
61 | 61 | ||
62 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | 62 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) |
63 | { | 63 | { |
64 | qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix; | 64 | qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix; |
65 | } | 65 | } |
66 | 66 | ||
67 | // Authentication domain | 67 | // Register interface using the fully-qualified prefix |
68 | |||
69 | Rest.Domains.Add(key,Rest.Config.GetString("asset-domain",qPrefix)); | ||
70 | |||
71 | // Register interface | ||
72 | 68 | ||
73 | Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate); | 69 | Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate); |
74 | 70 | ||
75 | // Activate | 71 | // Activate if all went OK |
76 | 72 | ||
77 | enabled = true; | 73 | enabled = true; |
78 | 74 | ||
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs index 50412c9..7bd83c1 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs | |||
@@ -38,10 +38,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
38 | public class RestHandler : RestPlugin, IHttpAgentHandler | 38 | public class RestHandler : RestPlugin, IHttpAgentHandler |
39 | { | 39 | { |
40 | 40 | ||
41 | /// <remarks> | ||
42 | /// The handler delegates are not noteworthy. The allocator allows | ||
43 | /// a given handler to optionally subclass the base RequestData | ||
44 | /// structure to carry any locally required per-request state | ||
45 | /// needed. | ||
46 | /// </remarks> | ||
47 | |||
48 | internal delegate void RestMethodHandler(RequestData rdata); | ||
49 | internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response); | ||
50 | |||
51 | // Handler tables: both stream and REST are supported. The path handlers and their | ||
52 | // respective allocators are stored in separate tables. | ||
53 | |||
54 | internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>(); | ||
55 | internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>(); | ||
56 | internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>(); | ||
57 | |||
41 | #region local static state | 58 | #region local static state |
42 | 59 | ||
60 | private static bool handlersLoaded = false; | ||
61 | private static List<Type> classes = new List<Type>(); | ||
62 | private static List<IRest> handlers = new List<IRest>(); | ||
63 | private static Type[] parms = new Type[0]; | ||
64 | private static Object[] args = new Object[0]; | ||
65 | |||
43 | /// <summary> | 66 | /// <summary> |
44 | /// This static initializer scans the assembly for classes that | 67 | /// This static initializer scans the ASSEMBLY for classes that |
45 | /// export the IRest interface and builds a list of them. These | 68 | /// export the IRest interface and builds a list of them. These |
46 | /// are later activated by the handler. To add a new handler it | 69 | /// are later activated by the handler. To add a new handler it |
47 | /// is only necessary to create a new services class that implements | 70 | /// is only necessary to create a new services class that implements |
@@ -49,73 +72,78 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
49 | /// all of the build-time flexibility of a modular approach | 72 | /// all of the build-time flexibility of a modular approach |
50 | /// while not introducing yet-another module loader. Note that | 73 | /// while not introducing yet-another module loader. Note that |
51 | /// multiple assembles can still be built, each with its own set | 74 | /// multiple assembles can still be built, each with its own set |
52 | /// of handlers. | 75 | /// of handlers. Examples of services classes are RestInventoryServices |
76 | /// and RestSkeleton. | ||
53 | /// </summary> | 77 | /// </summary> |
54 | 78 | ||
55 | private static bool handlersLoaded = false; | ||
56 | private static List<Type> classes = new List<Type>(); | ||
57 | private static List<IRest> handlers = new List<IRest>(); | ||
58 | private static Type[] parms = new Type[1]; | ||
59 | private static Object[] args = new Object[1]; | ||
60 | |||
61 | static RestHandler() | 79 | static RestHandler() |
62 | { | 80 | { |
81 | |||
63 | Module[] mods = Assembly.GetExecutingAssembly().GetModules(); | 82 | Module[] mods = Assembly.GetExecutingAssembly().GetModules(); |
83 | |||
64 | foreach (Module m in mods) | 84 | foreach (Module m in mods) |
65 | { | 85 | { |
66 | Type[] types = m.GetTypes(); | 86 | Type[] types = m.GetTypes(); |
67 | foreach (Type t in types) | 87 | foreach (Type t in types) |
68 | { | 88 | { |
69 | if (t.GetInterface("IRest") != null) | 89 | try |
70 | { | 90 | { |
71 | classes.Add(t); | 91 | if (t.GetInterface("IRest") != null) |
92 | { | ||
93 | classes.Add(t); | ||
94 | } | ||
95 | } | ||
96 | catch (Exception) | ||
97 | { | ||
98 | Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t); | ||
99 | Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t); | ||
72 | } | 100 | } |
73 | } | 101 | } |
74 | } | 102 | } |
103 | |||
75 | } | 104 | } |
76 | 105 | ||
77 | #endregion local static state | 106 | #endregion local static state |
78 | 107 | ||
79 | #region local instance state | 108 | #region local instance state |
80 | 109 | ||
81 | /// <remarks> | ||
82 | /// The handler delegate is not noteworthy. The allocator allows | ||
83 | /// a given handler to optionally subclass the base RequestData | ||
84 | /// structure to carry any locally required per-request state | ||
85 | /// needed. | ||
86 | /// </remarks> | ||
87 | internal delegate void RestMethodHandler(RequestData rdata); | ||
88 | internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response); | ||
89 | |||
90 | // Handler tables: both stream and REST are supported | ||
91 | |||
92 | internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>(); | ||
93 | internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>(); | ||
94 | internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>(); | ||
95 | |||
96 | /// <summary> | 110 | /// <summary> |
97 | /// This routine loads all of the handlers discovered during | 111 | /// This routine loads all of the handlers discovered during |
98 | /// instance initialization. Each handler is responsible for | 112 | /// instance initialization. |
99 | /// registering itself with this handler. | 113 | /// A table of all loaded and successfully constructed handlers |
100 | /// I was not able to make this code work in a constructor. | 114 | /// is built, and this table is then used by the constructor to |
115 | /// initialize each of the handlers in turn. | ||
116 | /// NOTE: The loading process does not automatically imply that | ||
117 | /// the handler has registered any kind of an interface, that | ||
118 | /// may be (optionally) done by the handler either during | ||
119 | /// construction, or during initialization. | ||
120 | /// | ||
121 | /// I was not able to make this code work within a constructor | ||
122 | /// so it is islated within this method. | ||
101 | /// </summary> | 123 | /// </summary> |
124 | |||
102 | private void LoadHandlers() | 125 | private void LoadHandlers() |
103 | { | 126 | { |
104 | lock (handlers) | 127 | lock (handlers) |
105 | { | 128 | { |
106 | if (!handlersLoaded) | 129 | if (!handlersLoaded) |
107 | { | 130 | { |
108 | parms[0] = this.GetType(); | ||
109 | args[0] = this; | ||
110 | 131 | ||
111 | ConstructorInfo ci; | 132 | ConstructorInfo ci; |
112 | Object ht; | 133 | Object ht; |
113 | 134 | ||
114 | foreach (Type t in classes) | 135 | foreach (Type t in classes) |
115 | { | 136 | { |
116 | ci = t.GetConstructor(parms); | 137 | try |
117 | ht = ci.Invoke(args); | 138 | { |
118 | handlers.Add((IRest)ht); | 139 | ci = t.GetConstructor(parms); |
140 | ht = ci.Invoke(args); | ||
141 | handlers.Add((IRest)ht); | ||
142 | } | ||
143 | catch (Exception e) | ||
144 | { | ||
145 | Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message); | ||
146 | } | ||
119 | } | 147 | } |
120 | handlersLoaded = true; | 148 | handlersLoaded = true; |
121 | } | 149 | } |
@@ -126,14 +154,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
126 | 154 | ||
127 | #region overriding properties | 155 | #region overriding properties |
128 | 156 | ||
129 | // Used to differentiate the message header. | 157 | // These properties override definitions |
158 | // in the base class. | ||
159 | |||
160 | // Name is used to differentiate the message header. | ||
130 | 161 | ||
131 | public override string Name | 162 | public override string Name |
132 | { | 163 | { |
133 | get { return "HANDLER"; } | 164 | get { return "HANDLER"; } |
134 | } | 165 | } |
135 | 166 | ||
136 | // Used to partition the configuration space. | 167 | // Used to partition the .ini configuration space. |
137 | 168 | ||
138 | public override string ConfigName | 169 | public override string ConfigName |
139 | { | 170 | { |
@@ -167,32 +198,32 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
167 | /// Note that entries MUST be added to the active configuration files before | 198 | /// Note that entries MUST be added to the active configuration files before |
168 | /// the plugin can be enabled. | 199 | /// the plugin can be enabled. |
169 | /// </remarks> | 200 | /// </remarks> |
201 | |||
170 | public override void Initialise(OpenSimBase openSim) | 202 | public override void Initialise(OpenSimBase openSim) |
171 | { | 203 | { |
172 | try | 204 | try |
173 | { | 205 | { |
174 | 206 | ||
175 | /// <remarks> | 207 | // This plugin will only be enabled if the broader |
176 | /// This plugin will only be enabled if the broader | 208 | // REST plugin mechanism is enabled. |
177 | /// REST plugin mechanism is enabled. | ||
178 | /// </remarks> | ||
179 | 209 | ||
180 | Rest.Log.InfoFormat("{0} Plugin is initializing", MsgID); | 210 | Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId); |
181 | 211 | ||
182 | base.Initialise(openSim); | 212 | base.Initialise(openSim); |
183 | 213 | ||
214 | // IsEnabled is implemented by the base class and | ||
215 | // reflects an overall RestPlugin status | ||
216 | |||
184 | if (!IsEnabled) | 217 | if (!IsEnabled) |
185 | { | 218 | { |
186 | Rest.Log.WarnFormat("{0} Plugins are disabled", MsgID); | 219 | Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId); |
187 | return; | 220 | return; |
188 | } | 221 | } |
189 | 222 | ||
190 | Rest.Log.InfoFormat("{0} Plugin will be enabled", MsgID); | 223 | Rest.Log.InfoFormat("{0} Plugin will be enabled", MsgId); |
191 | 224 | ||
192 | /// <remarks> | 225 | // These are stored in static variables to make |
193 | /// These are stored in static variables to make | 226 | // them easy to reach from anywhere in the assembly. |
194 | /// them easy to reach from anywhere in the assembly. | ||
195 | /// </remarks> | ||
196 | 227 | ||
197 | Rest.main = openSim; | 228 | Rest.main = openSim; |
198 | Rest.Plugin = this; | 229 | Rest.Plugin = this; |
@@ -223,6 +254,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
223 | Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, | 254 | Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, |
224 | (Rest.DumpAsset ? "" : "not ")); | 255 | (Rest.DumpAsset ? "" : "not ")); |
225 | 256 | ||
257 | // If data dumping is requested, report on the chosen line | ||
258 | // length. | ||
259 | |||
226 | if (Rest.DumpAsset) | 260 | if (Rest.DumpAsset) |
227 | { | 261 | { |
228 | Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, | 262 | Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, |
@@ -247,22 +281,24 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
247 | 281 | ||
248 | LoadHandlers(); | 282 | LoadHandlers(); |
249 | 283 | ||
250 | /// <remarks> | 284 | // The intention of a post construction initializer |
251 | /// The intention of a post construction initializer | 285 | // is to allow for setup that is dependent upon other |
252 | /// is to allow for setup that is dependent upon other | 286 | // activities outside of the agency. |
253 | /// activities outside of the agency. We don't currently | ||
254 | /// have any, but the design allows for it. | ||
255 | /// </remarks> | ||
256 | 287 | ||
257 | foreach (IRest handler in handlers) | 288 | foreach (IRest handler in handlers) |
258 | { | 289 | { |
259 | handler.Initialize(); | 290 | try |
291 | { | ||
292 | handler.Initialize(); | ||
293 | } | ||
294 | catch (Exception e) | ||
295 | { | ||
296 | Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message); | ||
297 | } | ||
260 | } | 298 | } |
261 | 299 | ||
262 | /// <remarks> | 300 | // Now that everything is setup we can proceed to |
263 | /// Now that everything is setup we can proceed and | 301 | // add THIS agent to the HTTP server's handler list |
264 | /// add this agent to the HTTP server's handler list | ||
265 | /// </remarks> | ||
266 | 302 | ||
267 | if (!AddAgentHandler(Rest.Name,this)) | 303 | if (!AddAgentHandler(Rest.Name,this)) |
268 | { | 304 | { |
@@ -276,7 +312,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
276 | } | 312 | } |
277 | catch (Exception e) | 313 | catch (Exception e) |
278 | { | 314 | { |
279 | Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgID, e.Message); | 315 | Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); |
280 | } | 316 | } |
281 | 317 | ||
282 | } | 318 | } |
@@ -290,10 +326,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
290 | /// To make sure everything is copacetic we make sure the primary interface | 326 | /// To make sure everything is copacetic we make sure the primary interface |
291 | /// is disabled by deleting the handler from the HTTP server tables. | 327 | /// is disabled by deleting the handler from the HTTP server tables. |
292 | /// </summary> | 328 | /// </summary> |
329 | |||
293 | public override void Close() | 330 | public override void Close() |
294 | { | 331 | { |
295 | 332 | ||
296 | Rest.Log.InfoFormat("{0} Plugin is terminating", MsgID); | 333 | Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); |
297 | 334 | ||
298 | try | 335 | try |
299 | { | 336 | { |
@@ -313,45 +350,62 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
313 | #region interface methods | 350 | #region interface methods |
314 | 351 | ||
315 | /// <summary> | 352 | /// <summary> |
316 | /// This method is called by the server to match the client, it could | 353 | /// This method is called by the HTTP server to match an incoming |
317 | /// just return true if we only want one such handler. For now we | 354 | /// request. It scans all of the strings registered by the |
318 | /// match any explicitly specified client. | 355 | /// underlying handlers and looks for the best match. It returns |
356 | /// true if a match is found. | ||
357 | /// The matching process could be made arbitrarily complex. | ||
319 | /// </summary> | 358 | /// </summary> |
359 | |||
320 | public bool Match(OSHttpRequest request, OSHttpResponse response) | 360 | public bool Match(OSHttpRequest request, OSHttpResponse response) |
321 | { | 361 | { |
322 | string path = request.RawUrl; | 362 | string path = request.RawUrl; |
323 | foreach (string key in pathHandlers.Keys) | 363 | |
364 | try | ||
324 | { | 365 | { |
325 | if (path.StartsWith(key)) | 366 | foreach (string key in pathHandlers.Keys) |
326 | { | 367 | { |
327 | return ( path.Length == key.Length || | 368 | if (path.StartsWith(key)) |
328 | path.Substring(key.Length,1) == Rest.UrlPathSeparator); | 369 | { |
370 | return ( path.Length == key.Length || | ||
371 | path.Substring(key.Length,1) == Rest.UrlPathSeparator); | ||
372 | } | ||
329 | } | 373 | } |
330 | } | ||
331 | 374 | ||
332 | path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); | 375 | path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path); |
333 | foreach (string key in streamHandlers.Keys) | 376 | foreach (string key in streamHandlers.Keys) |
334 | { | ||
335 | if (path.StartsWith(key)) | ||
336 | { | 377 | { |
337 | return true; | 378 | if (path.StartsWith(key)) |
379 | { | ||
380 | return true; | ||
381 | } | ||
338 | } | 382 | } |
383 | |||
384 | } | ||
385 | catch (Exception e) | ||
386 | { | ||
387 | Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message); | ||
339 | } | 388 | } |
340 | 389 | ||
341 | return false; | 390 | return false; |
342 | } | 391 | } |
343 | 392 | ||
344 | /// <summary> | 393 | /// <summary> |
394 | /// This is called by the HTTP server once the handler has indicated | ||
395 | /// that t is able to handle the request. | ||
345 | /// Preconditions: | 396 | /// Preconditions: |
346 | /// [1] request != null and is a valid request object | 397 | /// [1] request != null and is a valid request object |
347 | /// [2] response != null and is a valid response object | 398 | /// [2] response != null and is a valid response object |
348 | /// Behavior is undefined if preconditions are not satisfied. | 399 | /// Behavior is undefined if preconditions are not satisfied. |
349 | /// </summary> | 400 | /// </summary> |
401 | |||
350 | public bool Handle(OSHttpRequest request, OSHttpResponse response) | 402 | public bool Handle(OSHttpRequest request, OSHttpResponse response) |
351 | { | 403 | { |
352 | bool handled; | 404 | bool handled; |
353 | base.MsgID = base.RequestID; | 405 | base.MsgID = base.RequestID; |
354 | 406 | ||
407 | // Debug only | ||
408 | |||
355 | if (Rest.DEBUG) | 409 | if (Rest.DEBUG) |
356 | { | 410 | { |
357 | Rest.Log.DebugFormat("{0} ENTRY", MsgId); | 411 | Rest.Log.DebugFormat("{0} ENTRY", MsgId); |
@@ -371,8 +425,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
371 | 425 | ||
372 | try | 426 | try |
373 | { | 427 | { |
374 | handled = FindPathHandler(request, response) || | 428 | handled = ( FindPathHandler(request, response) || |
375 | FindStreamHandler(request, response); | 429 | FindStreamHandler(request, response) ); |
376 | } | 430 | } |
377 | catch (Exception e) | 431 | catch (Exception e) |
378 | { | 432 | { |
@@ -406,6 +460,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
406 | 460 | ||
407 | Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path); | 461 | Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path); |
408 | 462 | ||
463 | if (!IsEnabled) | ||
464 | { | ||
465 | return false; | ||
466 | } | ||
467 | |||
409 | foreach (string pattern in streamHandlers.Keys) | 468 | foreach (string pattern in streamHandlers.Keys) |
410 | { | 469 | { |
411 | if (path.StartsWith(pattern)) | 470 | if (path.StartsWith(pattern)) |
@@ -432,7 +491,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
432 | 491 | ||
433 | } | 492 | } |
434 | 493 | ||
435 | // Preserves the original handler's semantics | 494 | /// <summary> |
495 | /// Add a stream handler for the designated HTTP method and path prefix. | ||
496 | /// If the handler is not enabled, the request is ignored. If the path | ||
497 | /// does not start with the REST prefix, it is added. If method-qualified | ||
498 | /// path has not already been registered, the method is added to the active | ||
499 | /// handler table. | ||
500 | /// </summary> | ||
436 | 501 | ||
437 | public void AddStreamHandler(string httpMethod, string path, RestMethod method) | 502 | public void AddStreamHandler(string httpMethod, string path, RestMethod method) |
438 | { | 503 | { |
@@ -454,17 +519,26 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
454 | if (!streamHandlers.ContainsKey(path)) | 519 | if (!streamHandlers.ContainsKey(path)) |
455 | { | 520 | { |
456 | streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method)); | 521 | streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method)); |
457 | Rest.Log.DebugFormat("{0} Added handler for {1}", MsgID, path); | 522 | Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path); |
458 | } | 523 | } |
459 | else | 524 | else |
460 | { | 525 | { |
461 | Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgID, path); | 526 | Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); |
462 | } | 527 | } |
463 | 528 | ||
464 | } | 529 | } |
465 | 530 | ||
531 | /// <summary> | ||
532 | /// Given the supplied request/response, if the handler is enabled, the inbound | ||
533 | /// information is used to match an entry in the active path handler tables, using | ||
534 | /// the method-qualified path information. If a match is found, then the handler is | ||
535 | /// invoked. The result is the boolean result of the handler, or false if no | ||
536 | /// handler was located. The boolean indicates whether or not the request has been | ||
537 | /// handled, not whether or not the request was successful - that information is in | ||
538 | /// the response. | ||
539 | /// </summary> | ||
466 | 540 | ||
467 | internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) | 541 | internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) |
468 | { | 542 | { |
469 | 543 | ||
470 | RequestData rdata = null; | 544 | RequestData rdata = null; |
@@ -516,8 +590,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
516 | 590 | ||
517 | } | 591 | } |
518 | 592 | ||
593 | /// <summary> | ||
594 | /// A method handler and a request allocator are stored using the designated | ||
595 | /// path as a key. If an entry already exists, it is replaced by the new one. | ||
596 | /// </summary> | ||
597 | |||
519 | internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) | 598 | internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra) |
520 | { | 599 | { |
600 | |||
601 | if (!IsEnabled) | ||
602 | { | ||
603 | return; | ||
604 | } | ||
605 | |||
521 | if (pathHandlers.ContainsKey(path)) | 606 | if (pathHandlers.ContainsKey(path)) |
522 | { | 607 | { |
523 | Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path); | 608 | Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path); |
@@ -537,4 +622,5 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
537 | 622 | ||
538 | } | 623 | } |
539 | } | 624 | } |
625 | |||
540 | } | 626 | } |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs index 65603c5..5de2cb4 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs | |||
@@ -31,6 +31,7 @@ using System.Collections.Generic; | |||
31 | using System.IO; | 31 | using System.IO; |
32 | using System.Threading; | 32 | using System.Threading; |
33 | using System.Xml; | 33 | using System.Xml; |
34 | using OpenJPEGNet; | ||
34 | using OpenSim.Framework; | 35 | using OpenSim.Framework; |
35 | using OpenSim.Framework.Servers; | 36 | using OpenSim.Framework.Servers; |
36 | using OpenSim.Framework.Communications; | 37 | using OpenSim.Framework.Communications; |
@@ -44,35 +45,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
44 | public class RestInventoryServices : IRest | 45 | public class RestInventoryServices : IRest |
45 | { | 46 | { |
46 | 47 | ||
47 | private string key = "inventory"; | ||
48 | private bool enabled = false; | 48 | private bool enabled = false; |
49 | private string qPrefix = "inventory"; | 49 | private string qPrefix = "inventory"; |
50 | 50 | ||
51 | // A simple constructor is used to handle any once-only | 51 | /// <summary> |
52 | // initialization of working classes. | 52 | /// A simple constructor is used to handle any once-only |
53 | /// initialization of working classes. | ||
54 | /// </summary> | ||
53 | 55 | ||
54 | public RestInventoryServices(RestHandler p_rest) | 56 | public RestInventoryServices() |
55 | { | 57 | { |
56 | 58 | ||
57 | Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); | 59 | Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); |
58 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); | 60 | Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); |
59 | 61 | ||
60 | // Update to reflect the full prefix if not absolute | 62 | // If a relative path was specified for the handler's domain, |
63 | // add the standard prefix to make it absolute, e.g. /admin | ||
61 | 64 | ||
62 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) | 65 | if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) |
63 | { | 66 | { |
64 | qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix; | 67 | qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix; |
65 | } | 68 | } |
66 | 69 | ||
67 | // Authentication domain | 70 | // Register interface using the absolute URI. |
68 | |||
69 | Rest.Domains.Add(key, Rest.Config.GetString("inventory-domain",qPrefix)); | ||
70 | |||
71 | // Register interface | ||
72 | 71 | ||
73 | Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); | 72 | Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); |
74 | 73 | ||
75 | // Activate | 74 | // Activate if everything went OK |
76 | 75 | ||
77 | enabled = true; | 76 | enabled = true; |
78 | 77 | ||
@@ -80,16 +79,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
80 | 79 | ||
81 | } | 80 | } |
82 | 81 | ||
83 | // Post-construction, pre-enabled initialization opportunity | 82 | /// <summary> |
84 | // Not currently exploited. | 83 | /// Post-construction, pre-enabled initialization opportunity |
84 | /// Not currently exploited. | ||
85 | /// </summary> | ||
85 | 86 | ||
86 | public void Initialize() | 87 | public void Initialize() |
87 | { | 88 | { |
88 | } | 89 | } |
89 | 90 | ||
90 | // Called by the plug-in to halt REST processing. Local processing is | 91 | /// <summary> |
91 | // disabled, and control blocks until all current processing has | 92 | /// Called by the plug-in to halt REST processing. Local processing is |
92 | // completed. No new processing will be started | 93 | /// disabled, and control blocks until all current processing has |
94 | /// completed. No new processing will be started | ||
95 | /// </summary> | ||
93 | 96 | ||
94 | public void Close() | 97 | public void Close() |
95 | { | 98 | { |
@@ -97,7 +100,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
97 | Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); | 100 | Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); |
98 | } | 101 | } |
99 | 102 | ||
100 | // Convenient properties | 103 | /// <summary> |
104 | /// This property is declared locally because it is used a lot and | ||
105 | /// brevity is nice. | ||
106 | /// </summary> | ||
101 | 107 | ||
102 | internal string MsgId | 108 | internal string MsgId |
103 | { | 109 | { |
@@ -106,15 +112,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
106 | 112 | ||
107 | #region Interface | 113 | #region Interface |
108 | 114 | ||
115 | /// <summary> | ||
116 | /// The plugin (RestHandler) calls this method to allocate the request | ||
117 | /// state carrier for a new request. It is destroyed when the request | ||
118 | /// completes. All request-instance specific state is kept here. This | ||
119 | /// is registered when this service provider is registered. | ||
120 | /// </summary> | ||
121 | |||
109 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response) | 122 | private RequestData Allocate(OSHttpRequest request, OSHttpResponse response) |
110 | { | 123 | { |
111 | return (RequestData) new InventoryRequestData(request, response, qPrefix); | 124 | return (RequestData) new InventoryRequestData(request, response, qPrefix); |
112 | } | 125 | } |
113 | 126 | ||
114 | /// <summary> | 127 | /// <summary> |
115 | /// This method is registered with the handler when this class is | 128 | /// This method is registered with the handler when this service provider |
116 | /// initialized. It is called whenever the URI includes this handler's | 129 | /// is initialized. It is called whenever the plug-in identifies this service |
117 | /// prefix string. | 130 | /// provider as the best match. |
118 | /// It handles all aspects of inventory REST processing. | 131 | /// It handles all aspects of inventory REST processing. |
119 | /// </summary> | 132 | /// </summary> |
120 | 133 | ||
@@ -125,7 +138,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
125 | 138 | ||
126 | Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); | 139 | Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); |
127 | 140 | ||
128 | // We're disabled | 141 | // If we're disabled, do nothing. |
142 | |||
129 | if (!enabled) | 143 | if (!enabled) |
130 | { | 144 | { |
131 | return; | 145 | return; |
@@ -169,23 +183,32 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
169 | 183 | ||
170 | Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); | 184 | Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); |
171 | 185 | ||
172 | // We can only get here if we're authorized | 186 | /// <remarks> |
173 | // | 187 | /// We can only get here if we are authorized |
174 | // The requestor may have specified an LLUUID or | 188 | /// |
175 | // a conjoined FirstNameLastName string. We'll | 189 | /// The requestor may have specified an LLUUID or |
176 | // try both. If we fail with the first, UUID, | 190 | /// a conjoined FirstName LastName string. We'll |
177 | // attempt, then we need two nodes to construct | 191 | /// try both. If we fail with the first, UUID, |
178 | // a valid avatar name. | 192 | /// attempt, we try the other. As an example, the |
193 | /// URI for a valid inventory request might be: | ||
194 | /// | ||
195 | /// http://<host>:<port>/admin/inventory/Arthur Dent | ||
196 | /// | ||
197 | /// Indicating that this is an inventory request for | ||
198 | /// an avatar named Arthur Dent. This is ALl that is | ||
199 | /// required to designate a GET for an entire | ||
200 | /// inventory. | ||
201 | /// </remarks> | ||
179 | 202 | ||
180 | // Do we have at least a user agent name? | 203 | // Do we have at least a user agent name? |
181 | 204 | ||
182 | if (rdata.parameters.Length < 1) | 205 | if (rdata.parameters.Length < 1) |
183 | { | 206 | { |
184 | Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); | 207 | Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); |
185 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); | 208 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": No user identity specified"); |
186 | } | 209 | } |
187 | 210 | ||
188 | // The next parameter MUST be the agent identification, either an LLUUID | 211 | // The first parameter MUST be the agent identification, either an LLUUID |
189 | // or a space-separated First-name Last-Name specification. | 212 | // or a space-separated First-name Last-Name specification. |
190 | 213 | ||
191 | try | 214 | try |
@@ -205,10 +228,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
205 | else | 228 | else |
206 | { | 229 | { |
207 | Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); | 230 | Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); |
208 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest); | 231 | rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": invalid user identity"); |
209 | } | 232 | } |
210 | } | 233 | } |
211 | 234 | ||
235 | // If the user rpofile is null then either the server is broken, or the | ||
236 | // user is not known. We always assume the latter case. | ||
237 | |||
212 | if (rdata.userProfile != null) | 238 | if (rdata.userProfile != null) |
213 | { | 239 | { |
214 | Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", | 240 | Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", |
@@ -217,11 +243,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
217 | else | 243 | else |
218 | { | 244 | { |
219 | Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path); | 245 | Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path); |
220 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound); | 246 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": unrecognized user identity"); |
221 | } | 247 | } |
222 | 248 | ||
223 | // If we get to here, then we have successfully obtained an inventory | 249 | // If we get to here, then we have effectively validated the user's |
224 | // for the specified user. | 250 | // identity. Now we need to get the inventory. If the server does not |
251 | // have the inventory, we reject the request with an appropriate explanation. | ||
252 | // | ||
253 | // Note that inventory retrieval is an asynchronous event, we use the rdata | ||
254 | // class instance as the basis for our synchronization. | ||
255 | // | ||
256 | // TODO | ||
257 | // If something went wrong in inventory processing the thread could stall here | ||
258 | // indefinitely. There should be a watchdog timer to fail the request if the | ||
259 | // response is not recieved in a timely fashion. | ||
225 | 260 | ||
226 | rdata.uuid = rdata.userProfile.ID; | 261 | rdata.uuid = rdata.userProfile.ID; |
227 | 262 | ||
@@ -250,7 +285,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
250 | { | 285 | { |
251 | Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}", | 286 | Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}", |
252 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | 287 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); |
253 | rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError); | 288 | rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError+": inventory retrieval failed"); |
254 | } | 289 | } |
255 | 290 | ||
256 | } | 291 | } |
@@ -258,7 +293,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
258 | { | 293 | { |
259 | Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}", | 294 | Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}", |
260 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); | 295 | MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); |
261 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound); | 296 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no inventory for user"); |
262 | } | 297 | } |
263 | 298 | ||
264 | // If we get here, then we have successfully retrieved the user's information | 299 | // If we get here, then we have successfully retrieved the user's information |
@@ -292,7 +327,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
292 | Rest.Log.DebugFormat("{0} Method {1} not supported for {2}", | 327 | Rest.Log.DebugFormat("{0} Method {1} not supported for {2}", |
293 | MsgId, rdata.method, rdata.path); | 328 | MsgId, rdata.method, rdata.path); |
294 | rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, | 329 | rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, |
295 | Rest.HttpStatusDescMethodNotAllowed); | 330 | Rest.HttpStatusDescMethodNotAllowed+": "+rdata.method+" not supported"); |
296 | break; | 331 | break; |
297 | } | 332 | } |
298 | 333 | ||
@@ -315,10 +350,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
315 | 350 | ||
316 | rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); | 351 | rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); |
317 | 352 | ||
353 | // If there was only one parameter, then the entire | ||
354 | // inventory is being requested. | ||
355 | |||
318 | if (rdata.parameters.Length == 1) | 356 | if (rdata.parameters.Length == 1) |
319 | { | 357 | { |
320 | formatInventory(rdata, rdata.root, String.Empty); | 358 | formatInventory(rdata, rdata.root, String.Empty); |
321 | } | 359 | } |
360 | |||
361 | // If there are additional parameters, then these represent | ||
362 | // a path relative to the root of the inventory. This path | ||
363 | // must be traversed before we format the sub-tree thus | ||
364 | // identified. | ||
365 | |||
322 | else | 366 | else |
323 | { | 367 | { |
324 | traverseInventory(rdata, rdata.root, 1); | 368 | traverseInventory(rdata, rdata.root, 1); |
@@ -332,33 +376,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
332 | } | 376 | } |
333 | 377 | ||
334 | /// <summary> | 378 | /// <summary> |
335 | /// In the case of the inventory, and probably much else | 379 | /// In the case of the inventory, and probably in general, |
336 | /// the distinction between PUT and POST is not always | 380 | /// the distinction between PUT and POST is not always |
337 | /// easy to discern. Adding a directory can be viewed as | 381 | /// easy to discern. Adding a directory can be viewed as |
338 | /// an addition, or as a modification to the inventory as | 382 | /// an addition, or as a modification to the inventory as |
339 | /// a whole. | 383 | /// a whole. This is exacerbated by a lack of consistency |
384 | /// across different implementations. | ||
385 | /// | ||
386 | /// For OpenSim POST is an update and PUT is an addition. | ||
340 | /// | 387 | /// |
341 | /// The best distinction may be the relationship between | 388 | /// The best way to exaplain the distinction is to |
342 | /// the entity and the URI. If we view POST as an update, | 389 | /// consider the relationship between the URI and the |
343 | /// then the enity represents a replacement for the | 390 | /// entity in question. For POST, the URI identifies the |
344 | /// element named by the URI. If the operation is PUT, | 391 | /// entity to be modified or replaced. |
345 | /// then the URI describes the context into which the | 392 | /// If the operation is PUT,then the URI describes the |
346 | /// entity will be added. | 393 | /// context into which the new entity will be added. |
347 | /// | 394 | /// |
348 | /// As an example, suppose the URI contains: | 395 | /// As an example, suppose the URI contains: |
349 | /// /admin/inventory/Clothing | 396 | /// /admin/inventory/Clothing |
350 | /// Suppose the entity represents a Folder, called | ||
351 | /// "Clothes". | ||
352 | /// | ||
353 | /// A POST request will result in the replacement of | ||
354 | /// "Clothing" by "Clothes". Whereas a PUT request | ||
355 | /// would add Clothes as a sub-directory of Clothing. | ||
356 | /// | 397 | /// |
357 | /// This is the model followed by this implementation. | 398 | /// A POST request will result in some modification of |
399 | /// the folder or item named "Clothing". Whereas a PUT | ||
400 | /// request will add some new information into the | ||
401 | /// content identified by Clothing. It follows from this | ||
402 | /// that for PUT, the element identified by the URI must | ||
403 | /// be a folder. | ||
358 | /// </summary> | 404 | /// </summary> |
359 | 405 | ||
360 | /// <summary> | 406 | /// <summary> |
361 | /// PUT adds new information to the inventory at the | 407 | /// PUT adds new information to the inventory in the |
362 | /// context identified by the URI. | 408 | /// context identified by the URI. |
363 | /// </summary> | 409 | /// </summary> |
364 | 410 | ||
@@ -376,7 +422,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
376 | // exception. | 422 | // exception. |
377 | 423 | ||
378 | // It follows that we can only add information if the URI | 424 | // It follows that we can only add information if the URI |
379 | // has identified a folder. So only folder is supported | 425 | // has identified a folder. So only a type of folder is supported |
380 | // in this case. | 426 | // in this case. |
381 | 427 | ||
382 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | 428 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || |
@@ -390,14 +436,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
390 | Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}", | 436 | Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}", |
391 | MsgId, rdata.method, rdata.path); | 437 | MsgId, rdata.method, rdata.path); |
392 | 438 | ||
393 | // Reconstitute inventory sub-tree from the XML supplied in the entity. | 439 | // Reconstitute the inventory sub-tree from the XML supplied in the entity. |
394 | // This is a stand-alone inventory subtree, not yet integrated into the | 440 | // The result is a stand-alone inventory subtree, not yet integrated into the |
395 | // existing tree. | 441 | // existing tree. An inventory collection consists of three components: |
442 | // [1] A (possibly empty) set of folders. | ||
443 | // [2] A (possibly empty) set of items. | ||
444 | // [3] A (possibly empty) set of assets. | ||
445 | // If all of these are empty, then the PUT is a harmless no-operation. | ||
396 | 446 | ||
397 | XmlInventoryCollection entity = ReconstituteEntity(rdata); | 447 | XmlInventoryCollection entity = ReconstituteEntity(rdata); |
398 | 448 | ||
399 | // Inlined assest included in entity. If anything fails, | 449 | // Inlined assets can be included in entity. These must be incorporated into |
400 | // return failure to requestor. | 450 | // the asset database before we attempt to update the inventory. If anything |
451 | // fails, return a failure to requestor. | ||
401 | 452 | ||
402 | if (entity.Assets.Count > 0) | 453 | if (entity.Assets.Count > 0) |
403 | { | 454 | { |
@@ -410,10 +461,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
410 | Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", | 461 | Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", |
411 | MsgId, asset.ID, asset.Type, asset.Name); | 462 | MsgId, asset.ID, asset.Type, asset.Name); |
412 | Rest.AssetServices.AddAsset(asset); | 463 | Rest.AssetServices.AddAsset(asset); |
464 | |||
413 | if (Rest.DumpAsset) | 465 | if (Rest.DumpAsset) |
414 | { | 466 | { |
415 | Rest.Dump(asset.Data); | 467 | Rest.Dump(asset.Data); |
416 | } | 468 | } |
469 | |||
417 | } | 470 | } |
418 | 471 | ||
419 | } | 472 | } |
@@ -424,11 +477,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
424 | foreach (InventoryFolderBase folder in entity.Folders) | 477 | foreach (InventoryFolderBase folder in entity.Folders) |
425 | { | 478 | { |
426 | 479 | ||
427 | InventoryFolderBase found = null; | 480 | InventoryFolderBase found; |
428 | 481 | ||
429 | // If the parentID is zero, then this is going | 482 | // If the parentID is zero, then this folder is going |
430 | // into the root identified by the URI. The requestor | 483 | // into the root folder identified by the URI. The requestor |
431 | // may have already set the parent ID correctly, in which | 484 | // may have already set the parent ID explicitly, in which |
432 | // case we don't have to do it here. | 485 | // case we don't have to do it here. |
433 | 486 | ||
434 | if (folder.ParentID == LLUUID.Zero) | 487 | if (folder.ParentID == LLUUID.Zero) |
@@ -437,7 +490,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
437 | } | 490 | } |
438 | 491 | ||
439 | // Search the existing inventory for an existing entry. If | 492 | // Search the existing inventory for an existing entry. If |
440 | // we have once, we need to decide if it has really changed. | 493 | // we have one, we need to decide if it has really changed. |
441 | // It could just be present as (unnecessary) context, and we | 494 | // It could just be present as (unnecessary) context, and we |
442 | // don't want to waste time updating the database in that | 495 | // don't want to waste time updating the database in that |
443 | // case, OR, it could be being moved from another location | 496 | // case, OR, it could be being moved from another location |
@@ -451,6 +504,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
451 | if (xf.ID == folder.ID) | 504 | if (xf.ID == folder.ID) |
452 | { | 505 | { |
453 | found = xf; | 506 | found = xf; |
507 | break; | ||
454 | } | 508 | } |
455 | } | 509 | } |
456 | 510 | ||
@@ -492,6 +546,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
492 | if (xi.ID == item.ID) | 546 | if (xi.ID == item.ID) |
493 | { | 547 | { |
494 | found = xi; | 548 | found = xi; |
549 | break; | ||
495 | } | 550 | } |
496 | } | 551 | } |
497 | 552 | ||
@@ -516,7 +571,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
516 | Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", | 571 | Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", |
517 | MsgId, rdata.method, rdata.path, InventoryNode.GetType()); | 572 | MsgId, rdata.method, rdata.path, InventoryNode.GetType()); |
518 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 573 | rdata.Fail(Rest.HttpStatusCodeBadRequest, |
519 | Rest.HttpStatusDescBadRequest); | 574 | Rest.HttpStatusDescBadRequest+": invalid resource context"); |
520 | } | 575 | } |
521 | 576 | ||
522 | rdata.Complete(); | 577 | rdata.Complete(); |
@@ -531,7 +586,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
531 | /// [1] It identifies the user whose inventory is to be | 586 | /// [1] It identifies the user whose inventory is to be |
532 | /// processed. | 587 | /// processed. |
533 | /// [2] It optionally specifies a subtree of the inventory | 588 | /// [2] It optionally specifies a subtree of the inventory |
534 | /// that is to be used to resolve an relative subtree | 589 | /// that is to be used to resolve any relative subtree |
535 | /// specifications in the entity. If nothing is specified | 590 | /// specifications in the entity. If nothing is specified |
536 | /// then the whole inventory is implied. | 591 | /// then the whole inventory is implied. |
537 | /// Please note that the subtree specified by the URI is only relevant | 592 | /// Please note that the subtree specified by the URI is only relevant |
@@ -540,7 +595,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
540 | /// elements will be implicitly referenced within the context identified | 595 | /// elements will be implicitly referenced within the context identified |
541 | /// by the URI. | 596 | /// by the URI. |
542 | /// If an element in the entity specifies an explicit parent folder, then | 597 | /// If an element in the entity specifies an explicit parent folder, then |
543 | /// that parent is effective, regardless of nay value specified in the | 598 | /// that parent is effective, regardless of any value specified in the |
544 | /// URI. If the parent does not exist, then the element, and any dependent | 599 | /// URI. If the parent does not exist, then the element, and any dependent |
545 | /// elements, are ignored. This case is actually detected and handled | 600 | /// elements, are ignored. This case is actually detected and handled |
546 | /// during the reconstitution process. | 601 | /// during the reconstitution process. |
@@ -555,33 +610,54 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
555 | 610 | ||
556 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); | 611 | Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); |
557 | 612 | ||
558 | // As long as we have a context, then we have something | 613 | // As long as we have a node, then we have something |
559 | // meaningful to do, unlike PUT. So reconstitute the | 614 | // meaningful to do, unlike PUT. So we reconstitute the |
560 | // subtree before doing anything else. Note that we | 615 | // subtree before doing anything else. Note that we |
561 | // etiher got a context or we threw an exception. | 616 | // etiher got a valid node or we threw an exception. |
562 | 617 | ||
563 | XmlInventoryCollection entity = ReconstituteEntity(rdata); | 618 | XmlInventoryCollection entity = ReconstituteEntity(rdata); |
564 | 619 | ||
565 | // Incorporate any inlined assets first | 620 | // Incorporate any inlined assets first. Any failures |
621 | // will terminate the request. | ||
566 | 622 | ||
567 | if (entity.Assets.Count != 0) | 623 | if (entity.Assets.Count > 0) |
568 | { | 624 | { |
625 | Rest.Log.DebugFormat("{0} Adding {1} assets to server", | ||
626 | MsgId, entity.Assets.Count); | ||
627 | |||
569 | foreach (AssetBase asset in entity.Assets) | 628 | foreach (AssetBase asset in entity.Assets) |
570 | { | 629 | { |
571 | // Asset was validated during the collection | 630 | Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", |
572 | // process | 631 | MsgId, asset.ID, asset.Type, asset.Name); |
632 | |||
633 | // The asset was validated during the collection process | ||
634 | |||
573 | Rest.AssetServices.AddAsset(asset); | 635 | Rest.AssetServices.AddAsset(asset); |
636 | |||
637 | if (Rest.DumpAsset) | ||
638 | { | ||
639 | Rest.Dump(asset.Data); | ||
640 | } | ||
641 | |||
574 | } | 642 | } |
575 | } | 643 | } |
576 | 644 | ||
577 | /// <summary> | 645 | /// <summary> |
578 | /// URI specifies a folder to be updated. | 646 | /// The URI specifies either a folder or an item to be updated. |
579 | /// </summary> | 647 | /// </summary> |
580 | /// <remarks> | 648 | /// <remarks> |
581 | /// The root node in the entity must have the same | 649 | /// The root node in the entity will replace the node identified |
582 | /// UUID as the node identified by the URI. The | 650 | /// by the URI. This means the parent will remain the same, but |
583 | /// parentID if different indicates that the updated | 651 | /// any or all attributes associated with the named element |
584 | /// folder is actually being moved too. | 652 | /// will change. |
653 | /// | ||
654 | /// If the inventory collection contains an element with a zero | ||
655 | /// parent ID, then this is taken to be the replacement for the | ||
656 | /// named node. The collection MAY also specify an explicit | ||
657 | /// parent ID, in this case it MAY identify the same parent as | ||
658 | /// the current node, or it MAY specify a different parent, | ||
659 | /// indicating that the folder is being moved in addition to any | ||
660 | /// other modifications being made. | ||
585 | /// </remarks> | 661 | /// </remarks> |
586 | 662 | ||
587 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || | 663 | if (typeof(InventoryFolderBase) == InventoryNode.GetType() || |
@@ -592,7 +668,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
592 | InventoryFolderBase xml = null; | 668 | InventoryFolderBase xml = null; |
593 | 669 | ||
594 | // Scan the set of folders in the entity collection for an | 670 | // Scan the set of folders in the entity collection for an |
595 | // entry that macthes the context folder. It is assumed that | 671 | // entry that matches the context folder. It is assumed that |
596 | // the only reliable indicator of this is a zero UUID ( using | 672 | // the only reliable indicator of this is a zero UUID ( using |
597 | // implicit context), or the parent's UUID matches that of the | 673 | // implicit context), or the parent's UUID matches that of the |
598 | // URI designated node (explicit context). We don't allow | 674 | // URI designated node (explicit context). We don't allow |
@@ -617,14 +693,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
617 | } | 693 | } |
618 | } | 694 | } |
619 | 695 | ||
620 | // More than one entry is ambiguous | 696 | // More than one entry is ambiguous. Other folders should be |
697 | // added using the PUT verb. | ||
621 | 698 | ||
622 | if (count > 1) | 699 | if (count > 1) |
623 | { | 700 | { |
624 | Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", | 701 | Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", |
625 | MsgId, rdata.method, rdata.path); | 702 | MsgId, rdata.method, rdata.path); |
626 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 703 | rdata.Fail(Rest.HttpStatusCodeBadRequest, |
627 | Rest.HttpStatusDescBadRequest); | 704 | Rest.HttpStatusDescBadRequest+": context is ambiguous"); |
628 | } | 705 | } |
629 | 706 | ||
630 | // Exactly one entry means we ARE replacing the node | 707 | // Exactly one entry means we ARE replacing the node |
@@ -679,7 +756,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
679 | Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", | 756 | Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", |
680 | MsgId, rdata.method, rdata.path); | 757 | MsgId, rdata.method, rdata.path); |
681 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 758 | rdata.Fail(Rest.HttpStatusCodeBadRequest, |
682 | Rest.HttpStatusDescBadRequest); | 759 | Rest.HttpStatusDescBadRequest+": folder is not allowed"); |
683 | } | 760 | } |
684 | 761 | ||
685 | if (entity.Items.Count > 1) | 762 | if (entity.Items.Count > 1) |
@@ -687,7 +764,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
687 | Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", | 764 | Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", |
688 | MsgId, rdata.method, rdata.path); | 765 | MsgId, rdata.method, rdata.path); |
689 | rdata.Fail(Rest.HttpStatusCodeBadRequest, | 766 | rdata.Fail(Rest.HttpStatusCodeBadRequest, |
690 | Rest.HttpStatusDescBadRequest); | 767 | Rest.HttpStatusDescBadRequest+": too may items"); |
691 | } | 768 | } |
692 | 769 | ||
693 | xml = entity.Items[0]; | 770 | xml = entity.Items[0]; |
@@ -854,7 +931,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
854 | { | 931 | { |
855 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", | 932 | Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", |
856 | MsgId, rdata.method, rdata.path); | 933 | MsgId, rdata.method, rdata.path); |
857 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound); | 934 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": request is ambiguous"); |
858 | } | 935 | } |
859 | } | 936 | } |
860 | } | 937 | } |
@@ -863,7 +940,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
863 | 940 | ||
864 | Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", | 941 | Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", |
865 | MsgId, rdata.method, rdata.path); | 942 | MsgId, rdata.method, rdata.path); |
866 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound); | 943 | rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": resource "+rdata.path+" not found"); |
867 | 944 | ||
868 | return null; /* Never reached */ | 945 | return null; /* Never reached */ |
869 | 946 | ||
@@ -931,7 +1008,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
931 | 1008 | ||
932 | Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", | 1009 | Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", |
933 | MsgId, rdata.path); | 1010 | MsgId, rdata.path); |
934 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound); | 1011 | rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no such item/folder"); |
935 | 1012 | ||
936 | } | 1013 | } |
937 | 1014 | ||
@@ -1061,6 +1138,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1061 | TrashCan.Version = 1; | 1138 | TrashCan.Version = 1; |
1062 | TrashCan.Type = (short) AssetType.TrashFolder; | 1139 | TrashCan.Type = (short) AssetType.TrashFolder; |
1063 | TrashCan.ParentID = f.ID; | 1140 | TrashCan.ParentID = f.ID; |
1141 | TrashCan.Owner = f.Owner; | ||
1064 | Rest.InventoryServices.AddFolder(TrashCan); | 1142 | Rest.InventoryServices.AddFolder(TrashCan); |
1065 | } | 1143 | } |
1066 | } | 1144 | } |
@@ -1070,7 +1148,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1070 | { | 1148 | { |
1071 | Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); | 1149 | Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); |
1072 | rdata.Fail(Rest.HttpStatusCodeServerError, | 1150 | rdata.Fail(Rest.HttpStatusCodeServerError, |
1073 | Rest.HttpStatusDescServerError); | 1151 | Rest.HttpStatusDescServerError+": unable to create trash can"); |
1074 | } | 1152 | } |
1075 | 1153 | ||
1076 | return TrashCan; | 1154 | return TrashCan; |
@@ -1313,7 +1391,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1313 | Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", | 1391 | Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", |
1314 | MsgId, ic.xml.Name, ic.xml.Value); | 1392 | MsgId, ic.xml.Name, ic.xml.Value); |
1315 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1393 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1316 | Rest.HttpStatusDescBadRequest); | 1394 | Rest.HttpStatusDescBadRequest+": unrecognized attribute"); |
1317 | break; | 1395 | break; |
1318 | } | 1396 | } |
1319 | } | 1397 | } |
@@ -1349,7 +1427,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1349 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", | 1427 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", |
1350 | MsgId, ic.Item.Folder, result.ID); | 1428 | MsgId, ic.Item.Folder, result.ID); |
1351 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1429 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1352 | Rest.HttpStatusDescBadRequest); | 1430 | Rest.HttpStatusDescBadRequest+": invalid parent"); |
1353 | } | 1431 | } |
1354 | 1432 | ||
1355 | } | 1433 | } |
@@ -1457,7 +1535,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1457 | Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", | 1535 | Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", |
1458 | MsgId, ic.xml.Name, ic.xml.Value); | 1536 | MsgId, ic.xml.Name, ic.xml.Value); |
1459 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1537 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1460 | Rest.HttpStatusDescBadRequest); | 1538 | Rest.HttpStatusDescBadRequest+": unrecognized attribute"); |
1461 | break; | 1539 | break; |
1462 | } | 1540 | } |
1463 | } | 1541 | } |
@@ -1570,7 +1648,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1570 | { | 1648 | { |
1571 | Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); | 1649 | Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); |
1572 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1650 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1573 | Rest.HttpStatusDescBadRequest); | 1651 | Rest.HttpStatusDescBadRequest+": no context for asset"); |
1574 | } | 1652 | } |
1575 | } | 1653 | } |
1576 | 1654 | ||
@@ -1691,7 +1769,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1691 | { | 1769 | { |
1692 | Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); | 1770 | Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); |
1693 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1771 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1694 | Rest.HttpStatusDescBadRequest); | 1772 | Rest.HttpStatusDescBadRequest+": request parse error"); |
1695 | } | 1773 | } |
1696 | 1774 | ||
1697 | // Every item is required to have a name (via REST anyway) | 1775 | // Every item is required to have a name (via REST anyway) |
@@ -1700,7 +1778,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1700 | { | 1778 | { |
1701 | Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); | 1779 | Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); |
1702 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1780 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1703 | Rest.HttpStatusDescBadRequest); | 1781 | Rest.HttpStatusDescBadRequest+": item name required"); |
1704 | } | 1782 | } |
1705 | 1783 | ||
1706 | // An item MUST have an asset ID. AssetID should never be zero | 1784 | // An item MUST have an asset ID. AssetID should never be zero |
@@ -1713,7 +1791,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1713 | Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); | 1791 | Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); |
1714 | Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); | 1792 | Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); |
1715 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1793 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1716 | Rest.HttpStatusDescBadRequest); | 1794 | Rest.HttpStatusDescBadRequest+": asset information required"); |
1717 | 1795 | ||
1718 | } | 1796 | } |
1719 | 1797 | ||
@@ -1751,7 +1829,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1751 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", | 1829 | Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", |
1752 | MsgId, ic.Item.Folder, ic.Item.ID); | 1830 | MsgId, ic.Item.Folder, ic.Item.ID); |
1753 | ic.Fail(Rest.HttpStatusCodeBadRequest, | 1831 | ic.Fail(Rest.HttpStatusCodeBadRequest, |
1754 | Rest.HttpStatusDescBadRequest); | 1832 | Rest.HttpStatusDescBadRequest+": parent information required"); |
1755 | } | 1833 | } |
1756 | 1834 | ||
1757 | } | 1835 | } |
@@ -1825,6 +1903,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1825 | if (ic.Item.InvType == (int) AssetType.Unknown) | 1903 | if (ic.Item.InvType == (int) AssetType.Unknown) |
1826 | ic.Item.InvType = (int) AssetType.ImageJPEG; | 1904 | ic.Item.InvType = (int) AssetType.ImageJPEG; |
1827 | break; | 1905 | break; |
1906 | case "tga" : | ||
1907 | if (parts[parts.Length - 2].IndexOf("_texture") != -1) | ||
1908 | { | ||
1909 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
1910 | ic.Item.AssetType = (int) AssetType.TextureTGA; | ||
1911 | if (ic.Item.InvType == (int) AssetType.Unknown) | ||
1912 | ic.Item.InvType = (int) AssetType.TextureTGA; | ||
1913 | } | ||
1914 | else | ||
1915 | { | ||
1916 | if (ic.Item.AssetType == (int) AssetType.Unknown) | ||
1917 | ic.Item.AssetType = (int) AssetType.ImageTGA; | ||
1918 | if (ic.Item.InvType == (int) AssetType.Unknown) | ||
1919 | ic.Item.InvType = (int) AssetType.ImageTGA; | ||
1920 | } | ||
1921 | break; | ||
1828 | default : | 1922 | default : |
1829 | Rest.Log.DebugFormat("{0} Type was not inferred", MsgId); | 1923 | Rest.Log.DebugFormat("{0} Type was not inferred", MsgId); |
1830 | break; | 1924 | break; |
@@ -1832,6 +1926,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
1832 | } | 1926 | } |
1833 | } | 1927 | } |
1834 | 1928 | ||
1929 | /// If this is a TGA remember the fact | ||
1930 | |||
1931 | if (ic.Item.AssetType == (int) AssetType.TextureTGA || | ||
1932 | ic.Item.AssetType == (int) AssetType.ImageTGA) | ||
1933 | { | ||
1934 | // TODO: DO we need to convert it? Or is it enough to flag | ||
1935 | // it appropriately? | ||
1936 | } | ||
1937 | |||
1835 | ic.reset(); | 1938 | ic.reset(); |
1836 | 1939 | ||
1837 | } | 1940 | } |
diff --git a/OpenSim/Framework/Servers/BaseHttpServer.cs b/OpenSim/Framework/Servers/BaseHttpServer.cs index 170a653..4dc5994 100644 --- a/OpenSim/Framework/Servers/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/BaseHttpServer.cs | |||
@@ -171,18 +171,20 @@ namespace OpenSim.Framework.Servers | |||
171 | OSHttpRequest request = new OSHttpRequest(context.Request); | 171 | OSHttpRequest request = new OSHttpRequest(context.Request); |
172 | OSHttpResponse response = new OSHttpResponse(context.Response); | 172 | OSHttpResponse response = new OSHttpResponse(context.Response); |
173 | 173 | ||
174 | // user agent based requests? not sure where this actually gets used from | 174 | // This is the REST agent interface. We require an agent to properly identify |
175 | if (request.UserAgent != null) | 175 | // itself. If the REST handler recognizes the prefix it will attempt to |
176 | { | 176 | // satisfy the request. If it is not recognizable, and no damage has occurred |
177 | IHttpAgentHandler agentHandler; | 177 | // the request can be passed through to the other handlers. This is a low |
178 | // probability event; if a request is matched it is normally expected to be | ||
179 | // handled | ||
180 | |||
181 | IHttpAgentHandler agentHandler; | ||
178 | 182 | ||
179 | if (TryGetAgentHandler(request, response, out agentHandler)) | 183 | if (TryGetAgentHandler(request, response, out agentHandler)) |
184 | { | ||
185 | if (HandleAgentRequest(agentHandler, request, response)) | ||
180 | { | 186 | { |
181 | if (HandleAgentRequest(agentHandler, request, response)) | 187 | return; |
182 | { | ||
183 | m_log.DebugFormat("[HTTP-AGENT] Handler located for {0}", request.UserAgent); | ||
184 | return; | ||
185 | } | ||
186 | } | 188 | } |
187 | } | 189 | } |
188 | 190 | ||
@@ -508,7 +510,7 @@ namespace OpenSim.Framework.Servers | |||
508 | catch (Exception e) | 510 | catch (Exception e) |
509 | { | 511 | { |
510 | // If the handler did in fact close the stream, then this will blow | 512 | // If the handler did in fact close the stream, then this will blow |
511 | // chunks, so that that doesn;t disturb anybody we throw away any | 513 | // chunks. So that that doesn't disturb anybody we throw away any |
512 | // and all exceptions raised. We've done our best to release the | 514 | // and all exceptions raised. We've done our best to release the |
513 | // client. | 515 | // client. |
514 | try | 516 | try |
@@ -524,7 +526,10 @@ namespace OpenSim.Framework.Servers | |||
524 | } | 526 | } |
525 | } | 527 | } |
526 | 528 | ||
529 | // Indicate that the request has been "handled" | ||
530 | |||
527 | return true; | 531 | return true; |
532 | |||
528 | } | 533 | } |
529 | 534 | ||
530 | public void HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response) | 535 | public void HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response) |