From: awebb

Further improvements to the REST handlers.
0.6.0-stable
Dr Scofield 2008-07-25 09:56:35 +00:00
parent 19ad7db5e1
commit 7025a8040e
7 changed files with 640 additions and 323 deletions

View File

@ -34,8 +34,9 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// <summary>
/// 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.
/// </summary>
internal interface IRest

View File

@ -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<string> 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+(?<pval>\\S+)\\s*",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>\\S+)\"",
private static Regex digestParm1 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*\"(?<pval>[^\"]+)\"",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex reuserPass = new Regex("\\s*(?<user>\\w+)\\s*:\\s*(?<pass>\\S*)",
private static Regex reuserPass = new Regex("\\s*(?<user>[^:]+)\\s*:\\s*(?<pass>\\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 "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 "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);
if (realm != null)
{
sbuilder.Append(" realm=\"");
sbuilder.Append(realm);
sbuilder.Append("\"");
}
AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString());
}
}
/// <summary>
/// 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).
/// </summary>
private bool Validate(string user, string pass)
{
Rest.Log.DebugFormat("{0} Validating {1}:{2}", MsgId, user, pass);
return user == "awebb" && pass == getPassword(user);
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);
}
/// <summary>
/// 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...
/// </summary>
private string getPassword(string user)
{
return Rest.GodKey;
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;
}
}
/// <summary>
/// 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.
/// </summary>
private bool vetPassword(string user, string pass)
{
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)
if (Rest.DEBUG)
{
removed_headers = new List<string>();
}
removed_headers.Add(hdr);
if (headers != null)
{
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.
private void BuildHeaders()
{
if (removed_headers != null)
{
foreach (string h in removed_headers)
Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
if (response.Headers.Get(hdr) == null)
{
Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, h);
response.Headers.Remove(h);
Rest.Log.DebugFormat("{0} No such header existed",
MsgId, hdr);
}
}
if (headers!= null)
response.Headers.Remove(hdr);
}
/// <summary>
/// Dump headers that will be generated in the response
/// </summary>
internal void DumpHeaders()
{
if (Rest.DEBUG)
{
for (int i = 0; i < headers.Count; i++)
for (int i=0;i<response.Headers.Count;i++)
{
Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>",
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()

View File

@ -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<string,string> Domains = new Dictionary<string,string>();
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

View File

@ -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;

View File

@ -38,10 +38,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public class RestHandler : RestPlugin, IHttpAgentHandler
{
/// <remarks>
/// 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.
/// </remarks>
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<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
#region local static state
private static bool handlersLoaded = false;
private static List<Type> classes = new List<Type>();
private static List<IRest> handlers = new List<IRest>();
private static Type[] parms = new Type[0];
private static Object[] args = new Object[0];
/// <summary>
/// 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.
/// </summary>
private static bool handlersLoaded = false;
private static List<Type> classes = new List<Type>();
private static List<IRest> handlers = new List<IRest>();
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
/// <remarks>
/// 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.
/// </remarks>
internal delegate void RestMethodHandler(RequestData rdata);
internal delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response);
// Handler tables: both stream and REST are supported
internal Dictionary<string,RestMethodHandler> pathHandlers = new Dictionary<string,RestMethodHandler>();
internal Dictionary<string,RestMethodAllocator> pathAllocators = new Dictionary<string,RestMethodAllocator>();
internal Dictionary<string,RestStreamHandler> streamHandlers = new Dictionary<string,RestStreamHandler>();
/// <summary>
/// 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.
/// </summary>
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.
/// </remarks>
public override void Initialise(OpenSimBase openSim)
{
try
{
/// <remarks>
/// This plugin will only be enabled if the broader
/// REST plugin mechanism is enabled.
/// </remarks>
// 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);
/// <remarks>
/// These are stored in static variables to make
/// them easy to reach from anywhere in the assembly.
/// </remarks>
// 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();
/// <remarks>
/// 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.
/// </remarks>
// 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);
}
}
/// <remarks>
/// Now that everything is setup we can proceed and
/// add this agent to the HTTP server's handler list
/// </remarks>
// 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.
/// </summary>
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
/// <summary>
/// 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.
/// </summary>
public bool Match(OSHttpRequest request, OSHttpResponse response)
{
string path = request.RawUrl;
foreach (string key in pathHandlers.Keys)
{
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)
try
{
if (path.StartsWith(key))
foreach (string key in pathHandlers.Keys)
{
return true;
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))
{
return true;
}
}
}
catch (Exception e)
{
Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message);
}
return false;
}
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// 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.
/// </summary>
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);
}
}
/// <summary>
/// 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.
/// </summary>
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
}
/// <summary>
/// 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.
/// </summary>
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
}
}
}

View File

@ -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.
/// <summary>
/// A simple constructor is used to handle any once-only
/// initialization of working classes.
/// </summary>
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.
/// <summary>
/// Post-construction, pre-enabled initialization opportunity
/// Not currently exploited.
/// </summary>
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
/// <summary>
/// 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
/// </summary>
public void Close()
{
@ -97,7 +100,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId);
}
// Convenient properties
/// <summary>
/// This property is declared locally because it is used a lot and
/// brevity is nice.
/// </summary>
internal string MsgId
{
@ -106,15 +112,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
#region Interface
/// <summary>
/// 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.
/// </summary>
private RequestData Allocate(OSHttpRequest request, OSHttpResponse response)
{
return (RequestData) new InventoryRequestData(request, response, qPrefix);
}
/// <summary>
/// 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.
/// </summary>
@ -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.
/// <remarks>
/// 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://<host>:<port>/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.
/// </remarks>
// 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
}
/// <summary>
/// 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.
///
/// 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.
/// For OpenSim POST is an update and PUT is an addition.
///
/// 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.
/// </summary>
/// <summary>
/// PUT adds new information to the inventory at the
/// PUT adds new information to the inventory in the
/// context identified by the URI.
/// </summary>
@ -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);
}
}
}
/// <summary>
/// URI specifies a folder to be updated.
/// The URI specifies either a folder or an item to be updated.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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();
}

View File

@ -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
if (TryGetAgentHandler(request, response, out agentHandler))
IHttpAgentHandler 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)