aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorDr Scofield2008-07-25 09:56:35 +0000
committerDr Scofield2008-07-25 09:56:35 +0000
commit7025a8040e06250d73c295aa641969d7189474d8 (patch)
tree87515da33453c530bd18b57c81809d25f59afeac
parentAdd casts from integer to float. Fix issue 1822. (diff)
downloadopensim-SC-7025a8040e06250d73c295aa641969d7189474d8.zip
opensim-SC-7025a8040e06250d73c295aa641969d7189474d8.tar.gz
opensim-SC-7025a8040e06250d73c295aa641969d7189474d8.tar.bz2
opensim-SC-7025a8040e06250d73c295aa641969d7189474d8.tar.xz
From: awebb
Further improvements to the REST handlers.
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs5
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs371
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs1
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs14
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs242
-rw-r--r--OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs295
-rw-r--r--OpenSim/Framework/Servers/BaseHttpServer.cs27
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;
34using System.Text.RegularExpressions; 34using System.Text.RegularExpressions;
35using System.Collections.Generic; 35using System.Collections.Generic;
36using System.Collections.Specialized; 36using System.Collections.Specialized;
37using OpenSim.Framework;
37using OpenSim.Framework.Servers; 38using OpenSim.Framework.Servers;
38using libsecondlife; 39using libsecondlife;
39using System.Xml; 40using 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;
31using System.IO; 31using System.IO;
32using System.Threading; 32using System.Threading;
33using System.Xml; 33using System.Xml;
34using OpenJPEGNet;
34using OpenSim.Framework; 35using OpenSim.Framework;
35using OpenSim.Framework.Servers; 36using OpenSim.Framework.Servers;
36using OpenSim.Framework.Communications; 37using 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)