From 7025a8040e06250d73c295aa641969d7189474d8 Mon Sep 17 00:00:00 2001
From: Dr Scofield
Date: Fri, 25 Jul 2008 09:56:35 +0000
Subject: From: awebb
Further improvements to the REST handlers.
---
OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs | 5 +-
.../Rest/Inventory/RequestData.cs | 371 ++++++++++++++-------
OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs | 1 -
.../Rest/Inventory/RestAssetServices.cs | 14 +-
.../Rest/Inventory/RestHandler.cs | 242 +++++++++-----
.../Rest/Inventory/RestInventoryServices.cs | 295 ++++++++++------
OpenSim/Framework/Servers/BaseHttpServer.cs | 27 +-
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
///
/// This interface represents the boundary between the general purpose
/// REST plugin handling, and the functionally specific handlers. The
- /// handler knows only to initialzie and terminate all such handlers
- /// that it finds.
+ /// handler knows only to initialize and terminate all such handlers
+ /// that it finds. Implementing this interface identifies the class as
+ /// a REST handler implementation.
///
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;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Collections.Specialized;
+using OpenSim.Framework;
using OpenSim.Framework.Servers;
using libsecondlife;
using System.Xml;
@@ -50,7 +51,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// This structure is created on entry to the Handler
/// method and is disposed of upon return. It is part of
/// the plug-in infrastructure, rather than the functionally
- /// specifici REST handler, and fundamental changes to
+ /// specific REST handler, and fundamental changes to
/// this should be reflected in the Rest HandlerVersion. The
/// object is instantiated, and may be extended by, any
/// given handler. See the inventory handler for an example
@@ -71,11 +72,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal OSHttpRequest request = null;
internal OSHttpResponse response = null;
+ internal string qprefix = null;
// Request lifetime values
- internal NameValueCollection headers = null;
- internal List removed_headers = null;
internal byte[] buffer = null;
internal string body = null;
internal string html = null;
@@ -96,11 +96,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal string hostname = "localhost";
internal int port = 80;
internal string prefix = Rest.UrlPathSeparator;
+ internal bool keepAlive = false;
+ internal bool chunked = false;
// Authentication related state
internal bool authenticated = false;
- internal string scheme = Rest.AS_DIGEST;
+ // internal string scheme = Rest.AS_DIGEST;
+ // internal string scheme = Rest.AS_BASIC;
+ internal string scheme = null;
internal string realm = Rest.Realm;
internal string domain = null;
internal string nonce = null;
@@ -148,13 +152,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?\\S+)\\s*",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
- private static Regex digestParm1 = new Regex("\\s*(?\\w+)\\s*=\\s*\"(?\\S+)\"",
+ private static Regex digestParm1 = new Regex("\\s*(?\\w+)\\s*=\\s*\"(?[^\"]+)\"",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex digestParm2 = new Regex("\\s*(?\\w+)\\s*=\\s*(?[^\\p{P}\\s]+)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
- private static Regex reuserPass = new Regex("\\s*(?\\w+)\\s*:\\s*(?\\S*)",
+ private static Regex reuserPass = new Regex("\\s*(?[^:]+)\\s*:\\s*(?\\S*)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
// For efficiency, we create static instances of these objects
@@ -165,11 +169,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
// Constructor
- internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string qprefix)
+ internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix)
{
request = p_request;
response = p_response;
+ qprefix = p_qprefix;
sbuilder.Length = 0;
@@ -182,7 +187,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
method = request.HttpMethod.ToLower();
initUrl();
- initParameters(qprefix.Length);
+ initParameters(p_qprefix.Length);
}
@@ -254,11 +259,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
// If we want a specific authentication mechanism, make sure
- // we get it.
+ // we get it. null indicates we don't care. non-null indicates
+ // a specific scheme requirement.
if (scheme != null && scheme.ToLower() != reqscheme)
{
- Rest.Log.DebugFormat("{0} Challenge reason: Required scheme not accepted", MsgId);
+ Rest.Log.DebugFormat("{0} Challenge reason: Requested scheme not acceptable", MsgId);
DoChallenge();
}
@@ -268,15 +274,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
switch (reqscheme)
{
- case "digest" :
- Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
- DoDigest(authdata);
- break;
-
- case "basic" :
- Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
- DoBasic(authdata);
- break;
+ case "digest" :
+ Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
+ DoDigest(authdata);
+ break;
+
+ case "basic" :
+ Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
+ DoBasic(authdata);
+ break;
}
// If the current header is invalid, then a challenge is still needed.
@@ -406,10 +412,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
authparms.TryGetValue("uri", out authPrefix);
// There MUST be a nonce string present. We're not preserving any server
- // side state and we can;t validate the MD5 unless the lcient returns it
+ // side state and we can't validate the MD5 unless the client returns it
// to us, as it should.
- if (!authparms.TryGetValue("nonce", out nonce))
+ if (!authparms.TryGetValue("nonce", out nonce) || nonce == null)
{
Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
break;
@@ -457,26 +463,28 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
cnonce = authparms["cnonce"];
- if (!authparms.ContainsKey("nc"))
+ if (!authparms.TryGetValue("nc", out nck) || nck == null)
{
Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
break;
}
- nck = authparms["nc"];
+ Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId);
- if (cntable.TryGetValue(cnonce, out ncl))
+ if (cntable.TryGetValue(nonce, out ncl))
{
- if (Rest.Hex2Int(ncl) <= Rest.Hex2Int(nck))
+ Rest.Log.DebugFormat("{0} nonce values: Verify that request({1}) > Reference({2})", MsgId, nck, ncl);
+
+ if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck))
{
Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
break;
}
- cntable[cnonce] = nck;
+ cntable[nonce] = nck;
}
else
{
- lock (cntable) cntable.Add(cnonce, nck);
+ lock (cntable) cntable.Add(nonce, nck);
}
}
@@ -519,6 +527,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
sbuilder.Length = 0;
+ if (scheme == null || scheme == Rest.AS_BASIC)
+ {
+
+ sbuilder.Append(Rest.AS_BASIC);
+
+ if (realm != null)
+ {
+ sbuilder.Append(" realm=\"");
+ sbuilder.Append(realm);
+ sbuilder.Append("\"");
+ }
+ AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
+ }
+
+ sbuilder.Length = 0;
+
if (scheme == null || scheme == Rest.AS_DIGEST)
{
@@ -583,57 +607,135 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
sbuilder.Append(Rest.CS_COMMA);
}
- if (Rest.Domains.Count != 0)
- {
- sbuilder.Append("domain=");
- sbuilder.Append(Rest.CS_DQUOTE);
- foreach (string dom in Rest.Domains.Values)
- {
- sbuilder.Append(dom);
- sbuilder.Append(Rest.CS_SPACE);
- }
- if (sbuilder[sbuilder.Length-1] == Rest.C_SPACE)
- {
- sbuilder.Length = sbuilder.Length-1;
- }
- sbuilder.Append(Rest.CS_DQUOTE);
- sbuilder.Append(Rest.CS_COMMA);
- }
+ // We don;t know the userid that will be used
+ // so we cannot make any authentication domain
+ // assumptions. So the prefix will determine
+ // this.
- if (sbuilder[sbuilder.Length-1] == Rest.C_COMMA)
- {
- sbuilder.Length = sbuilder.Length-1;
- }
+ sbuilder.Append("domain=");
+ sbuilder.Append(Rest.CS_DQUOTE);
+ sbuilder.Append(qprefix);
+ sbuilder.Append(Rest.CS_DQUOTE);
AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
}
- if (scheme == null || scheme == Rest.AS_BASIC)
- {
+ }
- sbuilder.Append(Rest.AS_BASIC);
+ ///
+ /// This method provides validation in support of the BASIC
+ /// authentication method. This is not normaly expected to be
+ /// used, but is included for completeness (and because I tried
+ /// it first).
+ ///
- if (realm != null)
- {
- sbuilder.Append(" realm=\"");
- sbuilder.Append(realm);
- sbuilder.Append("\"");
- }
- AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
- }
+ private bool Validate(string user, string pass)
+ {
+
+ Rest.Log.DebugFormat("{0} Simple User Validation", MsgId);
+
+ // Both values are required
+
+ if (user == null || pass == null)
+ return false;
+
+ // Eliminate any leading or trailing spaces
+ user = user.Trim();
+
+ return vetPassword(user, pass);
}
- private bool Validate(string user, string pass)
+ ///
+ /// This mechanism is used by the digest authetnication mechanism
+ /// to return the user's password. In fact, because the OpenSim
+ /// user's passwords are already hashed, and the HTTP mechanism
+ /// does not supply an open password, the hashed passwords cannot
+ /// be used unless the cliemt has used the same salting mechanism
+ /// to has the password before using it in the authentication
+ /// algorithn. This is not inconceivable...
+ ///
+
+ private string getPassword(string user)
{
- Rest.Log.DebugFormat("{0} Validating {1}:{2}", MsgId, user, pass);
- return user == "awebb" && pass == getPassword(user);
+
+ int x;
+ string first;
+ string last;
+
+ // Distinguish the parts, if necessary
+
+ if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
+ {
+ first = user.Substring(0,x);
+ last = user.Substring(x+1);
+ }
+ else
+ {
+ first = user;
+ last = String.Empty;
+ }
+
+ UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
+
+ // If we don;t recognize the user id, perhaps it is god?
+
+ if (udata == null)
+ {
+ Rest.Log.DebugFormat("{0} Administrator", MsgId);
+ return Rest.GodKey;
+ }
+ else
+ {
+ Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user);
+ return udata.PasswordHash;
+ }
+
}
- private string getPassword(string user)
+ ///
+ /// This is used by the BASIC authentication scheme to calculate
+ /// the double hash used by OpenSim to encode user's passwords.
+ /// It returns true, if the supplied password is actually correct.
+ /// If the specified user-id is not recognized, but the password
+ /// matches the God password, then this is accepted as an admin
+ /// session.
+ ///
+
+ private bool vetPassword(string user, string pass)
{
- return Rest.GodKey;
+
+ int x;
+ string HA1;
+ string first;
+ string last;
+
+ // Distinguish the parts, if necessary
+
+ if ((x=user.IndexOf(Rest.C_SPACE)) != -1)
+ {
+ first = user.Substring(0,x);
+ last = user.Substring(x+1);
+ }
+ else
+ {
+ first = user;
+ last = String.Empty;
+ }
+
+ UserProfileData udata = Rest.UserServices.GetUserProfile(first, last);
+
+ // If we don;t recognize the user id, perhaps it is god?
+
+ if (udata == null)
+ return pass == Rest.GodKey;
+
+ HA1 = HashToString(pass);
+ HA1 = HashToString(String.Format("{0}:{1}",HA1,udata.PasswordSalt));
+
+ return (0 == sc.Compare(HA1, udata.PasswordHash));
+
}
// Validate the request-digest
@@ -773,11 +875,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (reset)
{
buffer = null;
- body = null;
+ SendHtml(message);
+ body = html;
}
if (Rest.DEBUG)
{
+ Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
@@ -828,10 +932,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.DebugFormat("{0} Generating Response", MsgId);
-
- // Process any arbitrary headers collected
-
- BuildHeaders();
+ Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);
// A Head request can NOT have a body!
if (method != Rest.HEAD)
@@ -839,6 +940,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId);
+ // If the writer is non-null then we know that an XML
+ // data component exists. Flush and close the writer and
+ // then convert the result to the expected buffer format
+ // unless the request has already been failed for some
+ // reason.
+
if (writer != null)
{
Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId);
@@ -869,41 +976,57 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
}
+ // OK, if the buffer contains something, regardless of how
+ // it got there, set various response headers accordingly.
+
if (buffer != null)
{
Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
- if (response.Headers.Get("Content-Encoding") == null)
- response.ContentEncoding = encoding;
response.ContentLength64 = buffer.Length;
- response.SendChunked = false;
- response.KeepAlive = false;
}
+ else
+ {
+ response.ContentLength64 = 0;
+ }
+
+ if (response.Headers.Get("Content-Encoding") == null)
+ response.ContentEncoding = encoding;
+
+ response.SendChunked = chunked;
+ response.KeepAlive = keepAlive;
}
- // Set the status code & description. If nothing
- // has been stored, we consider that a success
+ // Set the status code & description. If nothing has been stored,
+ // we consider that a success.
if (statusCode == 0)
{
Complete();
}
+ // Set the response code in the actual carrier
+
response.StatusCode = statusCode;
- if (response.StatusCode == (int)OSHttpStatusCode.RedirectMovedTemporarily ||
- response.StatusCode == (int)OSHttpStatusCode.RedirectMovedPermanently)
+ // For a redirect we need to set the relocation header accordingly
+
+ if (response.StatusCode == (int) Rest.HttpStatusCodeTemporaryRedirect ||
+ response.StatusCode == (int) Rest.HttpStatusCodePermanentRedirect)
{
+ Rest.Log.DebugFormat("{0} Re-direct location is {1}", MsgId, redirectLocation);
response.RedirectLocation = redirectLocation;
}
+ // And include the status description if provided.
+
if (statusDescription != null)
{
+ Rest.Log.DebugFormat("{0} Status description is {1}", MsgId, statusDescription);
response.StatusDescription = statusDescription;
}
- // Finally we send back our response, consuming
- // any exceptions that doing so might produce.
+ // Finally we send back our response.
// We've left the setting of handled' until the
// last minute because the header settings included
@@ -913,6 +1036,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
handled = true;
+ DumpHeaders();
+
+ // if (request.InputStream != null)
+ // {
+ // Rest.Log.DebugFormat("{0} Closing input stream", MsgId);
+ // request.InputStream.Close();
+ // }
+
if (buffer != null && buffer.Length != 0)
{
Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>",
@@ -920,12 +1051,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
response.OutputStream.Write(buffer, 0, buffer.Length);
}
- response.OutputStream.Close();
+ // Closing the outputstream should complete the transmission process
- if (request.InputStream != null)
- {
- request.InputStream.Close();
- }
+ Rest.Log.DebugFormat("{0} Closing output stream", MsgId);
+ response.OutputStream.Close();
}
@@ -935,19 +1064,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
- // Add a header to the table. If the header
- // already exists, it is replaced.
+ // Add a header to the table. We need to allow
+ // multiple instances of many of the headers.
+ // If the
internal void AddHeader(string hdr, string data)
{
-
- if (headers == null)
+ if (Rest.DEBUG)
{
- headers = new NameValueCollection();
+ Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
+ MsgId, hdr, data);
+ if (response.Headers.Get(hdr) != null)
+ {
+ Rest.Log.DebugFormat("{0} Multipe {1} headers will be generated>",
+ MsgId, hdr);
+ }
}
-
- headers[hdr] = data;
-
+ response.Headers.Add(hdr, data);
}
// Keep explicit track of any headers which
@@ -955,43 +1088,30 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal void RemoveHeader(string hdr)
{
-
- if (removed_headers == null)
- {
- removed_headers = new List();
- }
-
- removed_headers.Add(hdr);
-
- if (headers != null)
+ if (Rest.DEBUG)
{
- headers.Remove(hdr);
+ Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
+ if (response.Headers.Get(hdr) == null)
+ {
+ Rest.Log.DebugFormat("{0} No such header existed",
+ MsgId, hdr);
+ }
}
-
+ response.Headers.Remove(hdr);
}
- // Should it prove necessary, we could always
- // restore the header collection from a cloned
- // copy, but for now we'll assume that that is
- // not necessary.
+ ///
+ /// Dump headers that will be generated in the response
+ ///
- private void BuildHeaders()
+ internal void DumpHeaders()
{
- if (removed_headers != null)
- {
- foreach (string h in removed_headers)
- {
- Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, h);
- response.Headers.Remove(h);
- }
- }
- if (headers!= null)
+ if (Rest.DEBUG)
{
- for (int i = 0; i < headers.Count; i++)
+ for (int i=0;i",
- MsgId, headers.GetKey(i), headers.Get(i));
- response.Headers.Add(headers.GetKey(i), headers.Get(i));
+ Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
+ response.Headers.Get(i));
}
}
}
@@ -1019,7 +1139,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
path = uri.AbsolutePath;
if (path.EndsWith(Rest.UrlPathSeparator))
path = path.Substring(0,path.Length-1);
- path = Uri.UnescapeDataString(path);
}
// If we succeeded in getting a path, perform any
@@ -1040,6 +1159,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
pathNodes = EmptyPath;
}
+ // Elimiate any %-escaped values. This is left until here
+ // so that escaped "+' are not mistakenly replaced.
+
+ path = Uri.UnescapeDataString(path);
+
// Request server context info
hostname = uri.Host;
@@ -1149,14 +1273,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal void initXmlReader()
{
+
XmlReaderSettings settings = new XmlReaderSettings();
+
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
settings.IgnoreProcessingInstructions = true;
settings.ValidationType = ValidationType.None;
- // reader = XmlReader.Create(new StringReader(entity),settings);
+
reader = XmlReader.Create(request.InputStream,settings);
+
}
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
internal static bool ExtendedEscape = true;
internal static bool DumpAsset = false;
internal static string Realm = "REST";
- internal static Dictionary Domains = new Dictionary();
internal static int CreationDate = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4
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
public class RestAssetServices : IRest
{
- private string key = "assets";
private bool enabled = false;
private string qPrefix = "assets";
// A simple constructor is used to handle any once-only
// initialization of working classes.
- public RestAssetServices(RestHandler p_rest)
+ public RestAssetServices()
{
Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
- // Integrate domain
+ // If the handler specifies a relative path for its domain
+ // then we must add the standard absolute prefix, e.g. /admin
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{
qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix;
}
- // Authentication domain
-
- Rest.Domains.Add(key,Rest.Config.GetString("asset-domain",qPrefix));
-
- // Register interface
+ // Register interface using the fully-qualified prefix
Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate);
- // Activate
+ // Activate if all went OK
enabled = true;
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
public class RestHandler : RestPlugin, IHttpAgentHandler
{
+ ///
+ /// The handler delegates are not noteworthy. The allocator allows
+ /// a given handler to optionally subclass the base RequestData
+ /// structure to carry any locally required per-request state
+ /// needed.
+ ///
+
+ internal delegate void RestMethodHandler(RequestData rdata);
+ internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
+
+ // Handler tables: both stream and REST are supported. The path handlers and their
+ // respective allocators are stored in separate tables.
+
+ internal Dictionary pathHandlers = new Dictionary();
+ internal Dictionary pathAllocators = new Dictionary();
+ internal Dictionary streamHandlers = new Dictionary();
+
#region local static state
+ private static bool handlersLoaded = false;
+ private static List classes = new List();
+ private static List handlers = new List();
+ private static Type[] parms = new Type[0];
+ private static Object[] args = new Object[0];
+
///
- /// This static initializer scans the assembly for classes that
+ /// This static initializer scans the ASSEMBLY for classes that
/// export the IRest interface and builds a list of them. These
/// are later activated by the handler. To add a new handler it
/// is only necessary to create a new services class that implements
@@ -49,73 +72,78 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// all of the build-time flexibility of a modular approach
/// while not introducing yet-another module loader. Note that
/// multiple assembles can still be built, each with its own set
- /// of handlers.
+ /// of handlers. Examples of services classes are RestInventoryServices
+ /// and RestSkeleton.
///
- private static bool handlersLoaded = false;
- private static List classes = new List();
- private static List handlers = new List();
- private static Type[] parms = new Type[1];
- private static Object[] args = new Object[1];
-
static RestHandler()
{
+
Module[] mods = Assembly.GetExecutingAssembly().GetModules();
+
foreach (Module m in mods)
{
Type[] types = m.GetTypes();
foreach (Type t in types)
{
- if (t.GetInterface("IRest") != null)
+ try
{
- classes.Add(t);
+ if (t.GetInterface("IRest") != null)
+ {
+ classes.Add(t);
+ }
+ }
+ catch (Exception)
+ {
+ Rest.Log.WarnFormat("[STATIC-HANDLER]: #0 Error scanning {1}", t);
+ Rest.Log.InfoFormat("[STATIC-HANDLER]: #0 {1} is not included", t);
}
}
}
+
}
#endregion local static state
#region local instance state
- ///
- /// The handler delegate is not noteworthy. The allocator allows
- /// a given handler to optionally subclass the base RequestData
- /// structure to carry any locally required per-request state
- /// needed.
- ///
- internal delegate void RestMethodHandler(RequestData rdata);
- internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
-
- // Handler tables: both stream and REST are supported
-
- internal Dictionary pathHandlers = new Dictionary();
- internal Dictionary pathAllocators = new Dictionary();
- internal Dictionary streamHandlers = new Dictionary();
-
///
/// This routine loads all of the handlers discovered during
- /// instance initialization. Each handler is responsible for
- /// registering itself with this handler.
- /// I was not able to make this code work in a constructor.
+ /// instance initialization.
+ /// A table of all loaded and successfully constructed handlers
+ /// is built, and this table is then used by the constructor to
+ /// initialize each of the handlers in turn.
+ /// NOTE: The loading process does not automatically imply that
+ /// the handler has registered any kind of an interface, that
+ /// may be (optionally) done by the handler either during
+ /// construction, or during initialization.
+ ///
+ /// I was not able to make this code work within a constructor
+ /// so it is islated within this method.
///
+
private void LoadHandlers()
{
lock (handlers)
{
if (!handlersLoaded)
{
- parms[0] = this.GetType();
- args[0] = this;
ConstructorInfo ci;
Object ht;
foreach (Type t in classes)
{
- ci = t.GetConstructor(parms);
- ht = ci.Invoke(args);
- handlers.Add((IRest)ht);
+ try
+ {
+ ci = t.GetConstructor(parms);
+ ht = ci.Invoke(args);
+ handlers.Add((IRest)ht);
+ }
+ catch (Exception e)
+ {
+ Rest.Log.WarnFormat("{0} Unable to load {1} : {2}", MsgId, t, e.Message);
+ }
}
handlersLoaded = true;
}
@@ -126,14 +154,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
#region overriding properties
- // Used to differentiate the message header.
+ // These properties override definitions
+ // in the base class.
+
+ // Name is used to differentiate the message header.
public override string Name
{
get { return "HANDLER"; }
}
- // Used to partition the configuration space.
+ // Used to partition the .ini configuration space.
public override string ConfigName
{
@@ -167,32 +198,32 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// Note that entries MUST be added to the active configuration files before
/// the plugin can be enabled.
///
+
public override void Initialise(OpenSimBase openSim)
{
try
{
- ///
- /// This plugin will only be enabled if the broader
- /// REST plugin mechanism is enabled.
- ///
+ // This plugin will only be enabled if the broader
+ // REST plugin mechanism is enabled.
- Rest.Log.InfoFormat("{0} Plugin is initializing", MsgID);
+ Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId);
base.Initialise(openSim);
+ // IsEnabled is implemented by the base class and
+ // reflects an overall RestPlugin status
+
if (!IsEnabled)
{
- Rest.Log.WarnFormat("{0} Plugins are disabled", MsgID);
+ Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
return;
}
- Rest.Log.InfoFormat("{0} Plugin will be enabled", MsgID);
+ Rest.Log.InfoFormat("{0} Plugin will be enabled", MsgId);
- ///
- /// These are stored in static variables to make
- /// them easy to reach from anywhere in the assembly.
- ///
+ // These are stored in static variables to make
+ // them easy to reach from anywhere in the assembly.
Rest.main = openSim;
Rest.Plugin = this;
@@ -223,6 +254,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
(Rest.DumpAsset ? "" : "not "));
+ // If data dumping is requested, report on the chosen line
+ // length.
+
if (Rest.DumpAsset)
{
Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId,
@@ -247,22 +281,24 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
LoadHandlers();
- ///
- /// The intention of a post construction initializer
- /// is to allow for setup that is dependent upon other
- /// activities outside of the agency. We don't currently
- /// have any, but the design allows for it.
- ///
+ // The intention of a post construction initializer
+ // is to allow for setup that is dependent upon other
+ // activities outside of the agency.
foreach (IRest handler in handlers)
{
- handler.Initialize();
+ try
+ {
+ handler.Initialize();
+ }
+ catch (Exception e)
+ {
+ Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message);
+ }
}
- ///
- /// Now that everything is setup we can proceed and
- /// add this agent to the HTTP server's handler list
- ///
+ // Now that everything is setup we can proceed to
+ // add THIS agent to the HTTP server's handler list
if (!AddAgentHandler(Rest.Name,this))
{
@@ -276,7 +312,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
catch (Exception e)
{
- Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgID, e.Message);
+ Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message);
}
}
@@ -290,10 +326,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// To make sure everything is copacetic we make sure the primary interface
/// is disabled by deleting the handler from the HTTP server tables.
///
+
public override void Close()
{
- Rest.Log.InfoFormat("{0} Plugin is terminating", MsgID);
+ Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
try
{
@@ -313,45 +350,62 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
#region interface methods
///
- /// This method is called by the server to match the client, it could
- /// just return true if we only want one such handler. For now we
- /// match any explicitly specified client.
+ /// This method is called by the HTTP server to match an incoming
+ /// request. It scans all of the strings registered by the
+ /// underlying handlers and looks for the best match. It returns
+ /// true if a match is found.
+ /// The matching process could be made arbitrarily complex.
///
+
public bool Match(OSHttpRequest request, OSHttpResponse response)
{
string path = request.RawUrl;
- foreach (string key in pathHandlers.Keys)
+
+ try
{
- if (path.StartsWith(key))
+ foreach (string key in pathHandlers.Keys)
{
- return ( path.Length == key.Length ||
- path.Substring(key.Length,1) == Rest.UrlPathSeparator);
+ if (path.StartsWith(key))
+ {
+ return ( path.Length == key.Length ||
+ path.Substring(key.Length,1) == Rest.UrlPathSeparator);
+ }
}
- }
- path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
- foreach (string key in streamHandlers.Keys)
- {
- if (path.StartsWith(key))
+ path = String.Format("{0}{1}{2}", request.HttpMethod, Rest.UrlMethodSeparator, path);
+ foreach (string key in streamHandlers.Keys)
{
- return true;
+ if (path.StartsWith(key))
+ {
+ return true;
+ }
}
+
+ }
+ catch (Exception e)
+ {
+ Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message);
}
return false;
}
///
+ /// This is called by the HTTP server once the handler has indicated
+ /// that t is able to handle the request.
/// Preconditions:
/// [1] request != null and is a valid request object
/// [2] response != null and is a valid response object
/// Behavior is undefined if preconditions are not satisfied.
///
+
public bool Handle(OSHttpRequest request, OSHttpResponse response)
{
bool handled;
base.MsgID = base.RequestID;
+ // Debug only
+
if (Rest.DEBUG)
{
Rest.Log.DebugFormat("{0} ENTRY", MsgId);
@@ -371,8 +425,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try
{
- handled = FindPathHandler(request, response) ||
- FindStreamHandler(request, response);
+ handled = ( FindPathHandler(request, response) ||
+ FindStreamHandler(request, response) );
}
catch (Exception e)
{
@@ -406,6 +460,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
+ if (!IsEnabled)
+ {
+ return false;
+ }
+
foreach (string pattern in streamHandlers.Keys)
{
if (path.StartsWith(pattern))
@@ -432,7 +491,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
- // Preserves the original handler's semantics
+ ///
+ /// Add a stream handler for the designated HTTP method and path prefix.
+ /// If the handler is not enabled, the request is ignored. If the path
+ /// does not start with the REST prefix, it is added. If method-qualified
+ /// path has not already been registered, the method is added to the active
+ /// handler table.
+ ///
public void AddStreamHandler(string httpMethod, string path, RestMethod method)
{
@@ -454,17 +519,26 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (!streamHandlers.ContainsKey(path))
{
streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method));
- Rest.Log.DebugFormat("{0} Added handler for {1}", MsgID, path);
+ Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path);
}
else
{
- Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgID, path);
+ Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path);
}
}
+ ///
+ /// Given the supplied request/response, if the handler is enabled, the inbound
+ /// information is used to match an entry in the active path handler tables, using
+ /// the method-qualified path information. If a match is found, then the handler is
+ /// invoked. The result is the boolean result of the handler, or false if no
+ /// handler was located. The boolean indicates whether or not the request has been
+ /// handled, not whether or not the request was successful - that information is in
+ /// the response.
+ ///
- internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
+ internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response)
{
RequestData rdata = null;
@@ -516,8 +590,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
+ ///
+ /// A method handler and a request allocator are stored using the designated
+ /// path as a key. If an entry already exists, it is replaced by the new one.
+ ///
+
internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
{
+
+ if (!IsEnabled)
+ {
+ return;
+ }
+
if (pathHandlers.ContainsKey(path))
{
Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, path);
@@ -537,4 +622,5 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
}
+
}
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;
using System.IO;
using System.Threading;
using System.Xml;
+using OpenJPEGNet;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Communications;
@@ -44,35 +45,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public class RestInventoryServices : IRest
{
- private string key = "inventory";
private bool enabled = false;
private string qPrefix = "inventory";
- // A simple constructor is used to handle any once-only
- // initialization of working classes.
+ ///
+ /// A simple constructor is used to handle any once-only
+ /// initialization of working classes.
+ ///
- public RestInventoryServices(RestHandler p_rest)
+ public RestInventoryServices()
{
Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
- // Update to reflect the full prefix if not absolute
+ // If a relative path was specified for the handler's domain,
+ // add the standard prefix to make it absolute, e.g. /admin
if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{
qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix;
}
- // Authentication domain
-
- Rest.Domains.Add(key, Rest.Config.GetString("inventory-domain",qPrefix));
-
- // Register interface
+ // Register interface using the absolute URI.
Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate);
- // Activate
+ // Activate if everything went OK
enabled = true;
@@ -80,16 +79,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
- // Post-construction, pre-enabled initialization opportunity
- // Not currently exploited.
+ ///
+ /// Post-construction, pre-enabled initialization opportunity
+ /// Not currently exploited.
+ ///
public void Initialize()
{
}
- // Called by the plug-in to halt REST processing. Local processing is
- // disabled, and control blocks until all current processing has
- // completed. No new processing will be started
+ ///
+ /// Called by the plug-in to halt REST processing. Local processing is
+ /// disabled, and control blocks until all current processing has
+ /// completed. No new processing will be started
+ ///
public void Close()
{
@@ -97,7 +100,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId);
}
- // Convenient properties
+ ///
+ /// This property is declared locally because it is used a lot and
+ /// brevity is nice.
+ ///
internal string MsgId
{
@@ -106,15 +112,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
#region Interface
+ ///
+ /// The plugin (RestHandler) calls this method to allocate the request
+ /// state carrier for a new request. It is destroyed when the request
+ /// completes. All request-instance specific state is kept here. This
+ /// is registered when this service provider is registered.
+ ///
+
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response)
{
return (RequestData) new InventoryRequestData(request, response, qPrefix);
}
///
- /// This method is registered with the handler when this class is
- /// initialized. It is called whenever the URI includes this handler's
- /// prefix string.
+ /// This method is registered with the handler when this service provider
+ /// is initialized. It is called whenever the plug-in identifies this service
+ /// provider as the best match.
/// It handles all aspects of inventory REST processing.
///
@@ -125,7 +138,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId);
- // We're disabled
+ // If we're disabled, do nothing.
+
if (!enabled)
{
return;
@@ -169,23 +183,32 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
- // We can only get here if we're authorized
- //
- // The requestor may have specified an LLUUID or
- // a conjoined FirstNameLastName string. We'll
- // try both. If we fail with the first, UUID,
- // attempt, then we need two nodes to construct
- // a valid avatar name.
+ ///
+ /// We can only get here if we are authorized
+ ///
+ /// The requestor may have specified an LLUUID or
+ /// a conjoined FirstName LastName string. We'll
+ /// try both. If we fail with the first, UUID,
+ /// attempt, we try the other. As an example, the
+ /// URI for a valid inventory request might be:
+ ///
+ /// http://:/admin/inventory/Arthur Dent
+ ///
+ /// Indicating that this is an inventory request for
+ /// an avatar named Arthur Dent. This is ALl that is
+ /// required to designate a GET for an entire
+ /// inventory.
+ ///
// Do we have at least a user agent name?
if (rdata.parameters.Length < 1)
{
Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId);
- rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest);
+ rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": No user identity specified");
}
- // The next parameter MUST be the agent identification, either an LLUUID
+ // The first parameter MUST be the agent identification, either an LLUUID
// or a space-separated First-name Last-Name specification.
try
@@ -205,10 +228,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
else
{
Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
- rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest);
+ rdata.Fail(Rest.HttpStatusCodeBadRequest, Rest.HttpStatusDescBadRequest+": invalid user identity");
}
}
+ // If the user rpofile is null then either the server is broken, or the
+ // user is not known. We always assume the latter case.
+
if (rdata.userProfile != null)
{
Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}",
@@ -217,11 +243,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
else
{
Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path);
- rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound);
+ rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": unrecognized user identity");
}
- // If we get to here, then we have successfully obtained an inventory
- // for the specified user.
+ // If we get to here, then we have effectively validated the user's
+ // identity. Now we need to get the inventory. If the server does not
+ // have the inventory, we reject the request with an appropriate explanation.
+ //
+ // Note that inventory retrieval is an asynchronous event, we use the rdata
+ // class instance as the basis for our synchronization.
+ //
+ // TODO
+ // If something went wrong in inventory processing the thread could stall here
+ // indefinitely. There should be a watchdog timer to fail the request if the
+ // response is not recieved in a timely fashion.
rdata.uuid = rdata.userProfile.ID;
@@ -250,7 +285,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}",
MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
- rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError);
+ rdata.Fail(Rest.HttpStatusCodeServerError,Rest.HttpStatusDescServerError+": inventory retrieval failed");
}
}
@@ -258,7 +293,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}",
MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
- rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound);
+ rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no inventory for user");
}
// If we get here, then we have successfully retrieved the user's information
@@ -292,7 +327,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Method {1} not supported for {2}",
MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
- Rest.HttpStatusDescMethodNotAllowed);
+ Rest.HttpStatusDescMethodNotAllowed+": "+rdata.method+" not supported");
break;
}
@@ -315,10 +350,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty);
+ // If there was only one parameter, then the entire
+ // inventory is being requested.
+
if (rdata.parameters.Length == 1)
{
formatInventory(rdata, rdata.root, String.Empty);
}
+
+ // If there are additional parameters, then these represent
+ // a path relative to the root of the inventory. This path
+ // must be traversed before we format the sub-tree thus
+ // identified.
+
else
{
traverseInventory(rdata, rdata.root, 1);
@@ -332,33 +376,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
///
- /// In the case of the inventory, and probably much else
+ /// In the case of the inventory, and probably in general,
/// the distinction between PUT and POST is not always
/// easy to discern. Adding a directory can be viewed as
/// an addition, or as a modification to the inventory as
- /// a whole.
+ /// a whole. This is exacerbated by a lack of consistency
+ /// across different implementations.
+ ///
+ /// For OpenSim POST is an update and PUT is an addition.
///
- /// The best distinction may be the relationship between
- /// the entity and the URI. If we view POST as an update,
- /// then the enity represents a replacement for the
- /// element named by the URI. If the operation is PUT,
- /// then the URI describes the context into which the
- /// entity will be added.
+ /// The best way to exaplain the distinction is to
+ /// consider the relationship between the URI and the
+ /// entity in question. For POST, the URI identifies the
+ /// entity to be modified or replaced.
+ /// If the operation is PUT,then the URI describes the
+ /// context into which the new entity will be added.
///
/// As an example, suppose the URI contains:
/// /admin/inventory/Clothing
- /// Suppose the entity represents a Folder, called
- /// "Clothes".
- ///
- /// A POST request will result in the replacement of
- /// "Clothing" by "Clothes". Whereas a PUT request
- /// would add Clothes as a sub-directory of Clothing.
///
- /// This is the model followed by this implementation.
+ /// A POST request will result in some modification of
+ /// the folder or item named "Clothing". Whereas a PUT
+ /// request will add some new information into the
+ /// content identified by Clothing. It follows from this
+ /// that for PUT, the element identified by the URI must
+ /// be a folder.
///
///
- /// PUT adds new information to the inventory at the
+ /// PUT adds new information to the inventory in the
/// context identified by the URI.
///
@@ -376,7 +422,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
// exception.
// It follows that we can only add information if the URI
- // has identified a folder. So only folder is supported
+ // has identified a folder. So only a type of folder is supported
// in this case.
if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
@@ -390,14 +436,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}",
MsgId, rdata.method, rdata.path);
- // Reconstitute inventory sub-tree from the XML supplied in the entity.
- // This is a stand-alone inventory subtree, not yet integrated into the
- // existing tree.
+ // Reconstitute the inventory sub-tree from the XML supplied in the entity.
+ // The result is a stand-alone inventory subtree, not yet integrated into the
+ // existing tree. An inventory collection consists of three components:
+ // [1] A (possibly empty) set of folders.
+ // [2] A (possibly empty) set of items.
+ // [3] A (possibly empty) set of assets.
+ // If all of these are empty, then the PUT is a harmless no-operation.
XmlInventoryCollection entity = ReconstituteEntity(rdata);
- // Inlined assest included in entity. If anything fails,
- // return failure to requestor.
+ // Inlined assets can be included in entity. These must be incorporated into
+ // the asset database before we attempt to update the inventory. If anything
+ // fails, return a failure to requestor.
if (entity.Assets.Count > 0)
{
@@ -410,10 +461,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
MsgId, asset.ID, asset.Type, asset.Name);
Rest.AssetServices.AddAsset(asset);
+
if (Rest.DumpAsset)
{
Rest.Dump(asset.Data);
}
+
}
}
@@ -424,11 +477,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
foreach (InventoryFolderBase folder in entity.Folders)
{
- InventoryFolderBase found = null;
+ InventoryFolderBase found;
- // If the parentID is zero, then this is going
- // into the root identified by the URI. The requestor
- // may have already set the parent ID correctly, in which
+ // If the parentID is zero, then this folder is going
+ // into the root folder identified by the URI. The requestor
+ // may have already set the parent ID explicitly, in which
// case we don't have to do it here.
if (folder.ParentID == LLUUID.Zero)
@@ -437,7 +490,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
// Search the existing inventory for an existing entry. If
- // we have once, we need to decide if it has really changed.
+ // we have one, we need to decide if it has really changed.
// It could just be present as (unnecessary) context, and we
// don't want to waste time updating the database in that
// case, OR, it could be being moved from another location
@@ -451,6 +504,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (xf.ID == folder.ID)
{
found = xf;
+ break;
}
}
@@ -492,6 +546,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (xi.ID == item.ID)
{
found = xi;
+ break;
}
}
@@ -516,7 +571,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}",
MsgId, rdata.method, rdata.path, InventoryNode.GetType());
rdata.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": invalid resource context");
}
rdata.Complete();
@@ -531,7 +586,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// [1] It identifies the user whose inventory is to be
/// processed.
/// [2] It optionally specifies a subtree of the inventory
- /// that is to be used to resolve an relative subtree
+ /// that is to be used to resolve any relative subtree
/// specifications in the entity. If nothing is specified
/// then the whole inventory is implied.
/// Please note that the subtree specified by the URI is only relevant
@@ -540,7 +595,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// elements will be implicitly referenced within the context identified
/// by the URI.
/// If an element in the entity specifies an explicit parent folder, then
- /// that parent is effective, regardless of nay value specified in the
+ /// that parent is effective, regardless of any value specified in the
/// URI. If the parent does not exist, then the element, and any dependent
/// elements, are ignored. This case is actually detected and handled
/// during the reconstitution process.
@@ -555,33 +610,54 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Object InventoryNode = getInventoryNode(rdata, rdata.root, 1);
- // As long as we have a context, then we have something
- // meaningful to do, unlike PUT. So reconstitute the
+ // As long as we have a node, then we have something
+ // meaningful to do, unlike PUT. So we reconstitute the
// subtree before doing anything else. Note that we
- // etiher got a context or we threw an exception.
+ // etiher got a valid node or we threw an exception.
XmlInventoryCollection entity = ReconstituteEntity(rdata);
- // Incorporate any inlined assets first
+ // Incorporate any inlined assets first. Any failures
+ // will terminate the request.
- if (entity.Assets.Count != 0)
+ if (entity.Assets.Count > 0)
{
+ Rest.Log.DebugFormat("{0} Adding {1} assets to server",
+ MsgId, entity.Assets.Count);
+
foreach (AssetBase asset in entity.Assets)
{
- // Asset was validated during the collection
- // process
+ Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
+ MsgId, asset.ID, asset.Type, asset.Name);
+
+ // The asset was validated during the collection process
+
Rest.AssetServices.AddAsset(asset);
+
+ if (Rest.DumpAsset)
+ {
+ Rest.Dump(asset.Data);
+ }
+
}
}
///
- /// URI specifies a folder to be updated.
+ /// The URI specifies either a folder or an item to be updated.
///
///
- /// The root node in the entity must have the same
- /// UUID as the node identified by the URI. The
- /// parentID if different indicates that the updated
- /// folder is actually being moved too.
+ /// The root node in the entity will replace the node identified
+ /// by the URI. This means the parent will remain the same, but
+ /// any or all attributes associated with the named element
+ /// will change.
+ ///
+ /// If the inventory collection contains an element with a zero
+ /// parent ID, then this is taken to be the replacement for the
+ /// named node. The collection MAY also specify an explicit
+ /// parent ID, in this case it MAY identify the same parent as
+ /// the current node, or it MAY specify a different parent,
+ /// indicating that the folder is being moved in addition to any
+ /// other modifications being made.
///
if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
@@ -592,7 +668,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
InventoryFolderBase xml = null;
// Scan the set of folders in the entity collection for an
- // entry that macthes the context folder. It is assumed that
+ // entry that matches the context folder. It is assumed that
// the only reliable indicator of this is a zero UUID ( using
// implicit context), or the parent's UUID matches that of the
// URI designated node (explicit context). We don't allow
@@ -617,14 +693,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
}
- // More than one entry is ambiguous
+ // More than one entry is ambiguous. Other folders should be
+ // added using the PUT verb.
if (count > 1)
{
Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous",
MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": context is ambiguous");
}
// Exactly one entry means we ARE replacing the node
@@ -679,7 +756,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>",
MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": folder is not allowed");
}
if (entity.Items.Count > 1)
@@ -687,7 +764,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>",
MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": too may items");
}
xml = entity.Items[0];
@@ -854,7 +931,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
MsgId, rdata.method, rdata.path);
- rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound);
+ rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": request is ambiguous");
}
}
}
@@ -863,7 +940,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} {1}: Resource {2} not found",
MsgId, rdata.method, rdata.path);
- rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound);
+ rdata.Fail(Rest.HttpStatusCodeNotFound, Rest.HttpStatusDescNotFound+": resource "+rdata.path+" not found");
return null; /* Never reached */
@@ -931,7 +1008,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>",
MsgId, rdata.path);
- rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound);
+ rdata.Fail(Rest.HttpStatusCodeNotFound,Rest.HttpStatusDescNotFound+": no such item/folder");
}
@@ -1061,6 +1138,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
TrashCan.Version = 1;
TrashCan.Type = (short) AssetType.TrashFolder;
TrashCan.ParentID = f.ID;
+ TrashCan.Owner = f.Owner;
Rest.InventoryServices.AddFolder(TrashCan);
}
}
@@ -1070,7 +1148,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.DebugFormat("{0} No Trash Can available", MsgId);
rdata.Fail(Rest.HttpStatusCodeServerError,
- Rest.HttpStatusDescServerError);
+ Rest.HttpStatusDescServerError+": unable to create trash can");
}
return TrashCan;
@@ -1313,7 +1391,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}",
MsgId, ic.xml.Name, ic.xml.Value);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": unrecognized attribute");
break;
}
}
@@ -1349,7 +1427,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}",
MsgId, ic.Item.Folder, result.ID);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": invalid parent");
}
}
@@ -1457,7 +1535,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}",
MsgId, ic.xml.Name, ic.xml.Value);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": unrecognized attribute");
break;
}
}
@@ -1570,7 +1648,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": no context for asset");
}
}
@@ -1691,7 +1769,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": request parse error");
}
// Every item is required to have a name (via REST anyway)
@@ -1700,7 +1778,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{
Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": item name required");
}
// An item MUST have an asset ID. AssetID should never be zero
@@ -1713,7 +1791,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId);
Rest.Log.InfoFormat("{0} Asset information is missing", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": asset information required");
}
@@ -1751,7 +1829,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}",
MsgId, ic.Item.Folder, ic.Item.ID);
ic.Fail(Rest.HttpStatusCodeBadRequest,
- Rest.HttpStatusDescBadRequest);
+ Rest.HttpStatusDescBadRequest+": parent information required");
}
}
@@ -1825,6 +1903,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (ic.Item.InvType == (int) AssetType.Unknown)
ic.Item.InvType = (int) AssetType.ImageJPEG;
break;
+ case "tga" :
+ if (parts[parts.Length - 2].IndexOf("_texture") != -1)
+ {
+ if (ic.Item.AssetType == (int) AssetType.Unknown)
+ ic.Item.AssetType = (int) AssetType.TextureTGA;
+ if (ic.Item.InvType == (int) AssetType.Unknown)
+ ic.Item.InvType = (int) AssetType.TextureTGA;
+ }
+ else
+ {
+ if (ic.Item.AssetType == (int) AssetType.Unknown)
+ ic.Item.AssetType = (int) AssetType.ImageTGA;
+ if (ic.Item.InvType == (int) AssetType.Unknown)
+ ic.Item.InvType = (int) AssetType.ImageTGA;
+ }
+ break;
default :
Rest.Log.DebugFormat("{0} Type was not inferred", MsgId);
break;
@@ -1832,6 +1926,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
}
}
+ /// If this is a TGA remember the fact
+
+ if (ic.Item.AssetType == (int) AssetType.TextureTGA ||
+ ic.Item.AssetType == (int) AssetType.ImageTGA)
+ {
+ // TODO: DO we need to convert it? Or is it enough to flag
+ // it appropriately?
+ }
+
ic.reset();
}
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
OSHttpRequest request = new OSHttpRequest(context.Request);
OSHttpResponse response = new OSHttpResponse(context.Response);
- // user agent based requests? not sure where this actually gets used from
- if (request.UserAgent != null)
- {
- IHttpAgentHandler agentHandler;
+ // This is the REST agent interface. We require an agent to properly identify
+ // itself. If the REST handler recognizes the prefix it will attempt to
+ // satisfy the request. If it is not recognizable, and no damage has occurred
+ // the request can be passed through to the other handlers. This is a low
+ // probability event; if a request is matched it is normally expected to be
+ // handled
+
+ IHttpAgentHandler agentHandler;
- if (TryGetAgentHandler(request, response, out agentHandler))
+ if (TryGetAgentHandler(request, response, out agentHandler))
+ {
+ if (HandleAgentRequest(agentHandler, request, response))
{
- if (HandleAgentRequest(agentHandler, request, response))
- {
- m_log.DebugFormat("[HTTP-AGENT] Handler located for {0}", request.UserAgent);
- return;
- }
+ return;
}
}
@@ -508,7 +510,7 @@ namespace OpenSim.Framework.Servers
catch (Exception e)
{
// If the handler did in fact close the stream, then this will blow
- // chunks, so that that doesn;t disturb anybody we throw away any
+ // chunks. So that that doesn't disturb anybody we throw away any
// and all exceptions raised. We've done our best to release the
// client.
try
@@ -524,7 +526,10 @@ namespace OpenSim.Framework.Servers
}
}
+ // Indicate that the request has been "handled"
+
return true;
+
}
public void HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response)
--
cgit v1.1