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> /// <summary>
/// This interface represents the boundary between the general purpose /// This interface represents the boundary between the general purpose
/// REST plugin handling, and the functionally specific handlers. The /// REST plugin handling, and the functionally specific handlers. The
/// handler knows only to initialzie and terminate all such handlers /// handler knows only to initialize and terminate all such handlers
/// that it finds. /// that it finds. Implementing this interface identifies the class as
/// a REST handler implementation.
/// </summary> /// </summary>
internal interface IRest internal interface IRest

View File

@ -34,6 +34,7 @@ using System.Security.Cryptography;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using OpenSim.Framework;
using OpenSim.Framework.Servers; using OpenSim.Framework.Servers;
using libsecondlife; using libsecondlife;
using System.Xml; using System.Xml;
@ -50,7 +51,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// This structure is created on entry to the Handler /// This structure is created on entry to the Handler
/// method and is disposed of upon return. It is part of /// method and is disposed of upon return. It is part of
/// the plug-in infrastructure, rather than the functionally /// 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 /// this should be reflected in the Rest HandlerVersion. The
/// object is instantiated, and may be extended by, any /// object is instantiated, and may be extended by, any
/// given handler. See the inventory handler for an example /// given handler. See the inventory handler for an example
@ -71,11 +72,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal OSHttpRequest request = null; internal OSHttpRequest request = null;
internal OSHttpResponse response = null; internal OSHttpResponse response = null;
internal string qprefix = null;
// Request lifetime values // Request lifetime values
internal NameValueCollection headers = null;
internal List<string> removed_headers = null;
internal byte[] buffer = null; internal byte[] buffer = null;
internal string body = null; internal string body = null;
internal string html = null; internal string html = null;
@ -96,11 +96,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal string hostname = "localhost"; internal string hostname = "localhost";
internal int port = 80; internal int port = 80;
internal string prefix = Rest.UrlPathSeparator; internal string prefix = Rest.UrlPathSeparator;
internal bool keepAlive = false;
internal bool chunked = false;
// Authentication related state // Authentication related state
internal bool authenticated = false; 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 realm = Rest.Realm;
internal string domain = null; internal string domain = null;
internal string nonce = 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*", private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?<pval>\\S+)\\s*",
RegexOptions.Compiled | RegexOptions.IgnoreCase); 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); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)", private static Regex digestParm2 = new Regex("\\s*(?<parm>\\w+)\\s*=\\s*(?<pval>[^\\p{P}\\s]+)",
RegexOptions.Compiled | RegexOptions.IgnoreCase); 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); RegexOptions.Compiled | RegexOptions.IgnoreCase);
// For efficiency, we create static instances of these objects // For efficiency, we create static instances of these objects
@ -165,11 +169,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
// Constructor // 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; request = p_request;
response = p_response; response = p_response;
qprefix = p_qprefix;
sbuilder.Length = 0; sbuilder.Length = 0;
@ -182,7 +187,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
method = request.HttpMethod.ToLower(); method = request.HttpMethod.ToLower();
initUrl(); 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 // 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) 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(); DoChallenge();
} }
@ -268,15 +274,15 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
switch (reqscheme) switch (reqscheme)
{ {
case "digest" : case "digest" :
Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId); Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId);
DoDigest(authdata); DoDigest(authdata);
break; break;
case "basic" : case "basic" :
Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId); Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId);
DoBasic(authdata); DoBasic(authdata);
break; break;
} }
// If the current header is invalid, then a challenge is still needed. // 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); authparms.TryGetValue("uri", out authPrefix);
// There MUST be a nonce string present. We're not preserving any server // 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. // 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); Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId);
break; break;
@ -457,26 +463,28 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
cnonce = authparms["cnonce"]; 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); Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId);
break; 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); Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId);
break; break;
} }
cntable[cnonce] = nck; cntable[nonce] = nck;
} }
else 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; 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) if (scheme == null || scheme == Rest.AS_DIGEST)
{ {
@ -583,57 +607,135 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
sbuilder.Append(Rest.CS_COMMA); sbuilder.Append(Rest.CS_COMMA);
} }
if (Rest.Domains.Count != 0) // We don;t know the userid that will be used
{ // so we cannot make any authentication domain
sbuilder.Append("domain="); // assumptions. So the prefix will determine
sbuilder.Append(Rest.CS_DQUOTE); // this.
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);
}
if (sbuilder[sbuilder.Length-1] == Rest.C_COMMA) sbuilder.Append("domain=");
{ sbuilder.Append(Rest.CS_DQUOTE);
sbuilder.Length = sbuilder.Length-1; sbuilder.Append(qprefix);
} sbuilder.Append(Rest.CS_DQUOTE);
AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); 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) 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) 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 // Validate the request-digest
@ -773,11 +875,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (reset) if (reset)
{ {
buffer = null; buffer = null;
body = null; SendHtml(message);
body = html;
} }
if (Rest.DEBUG) if (Rest.DEBUG)
{ {
Rest.Log.DebugFormat("{0} Request Failure State Dump", MsgId);
Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme); Rest.Log.DebugFormat("{0} Scheme = {1}", MsgId, scheme);
Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm); Rest.Log.DebugFormat("{0} Realm = {1}", MsgId, realm);
Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain); Rest.Log.DebugFormat("{0} Domain = {1}", MsgId, domain);
@ -828,10 +932,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{ {
Rest.Log.DebugFormat("{0} Generating Response", MsgId); Rest.Log.DebugFormat("{0} Generating Response", MsgId);
Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method);
// Process any arbitrary headers collected
BuildHeaders();
// A Head request can NOT have a body! // A Head request can NOT have a body!
if (method != Rest.HEAD) if (method != Rest.HEAD)
@ -839,6 +940,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Response is not abbreviated", MsgId); 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) if (writer != null)
{ {
Rest.Log.DebugFormat("{0} XML Response handler extension ENTRY", MsgId); 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) if (buffer != null)
{ {
Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId); Rest.Log.DebugFormat("{0} Buffer-based entity", MsgId);
if (response.Headers.Get("Content-Encoding") == null)
response.ContentEncoding = encoding;
response.ContentLength64 = buffer.Length; 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 // Set the status code & description. If nothing has been stored,
// has been stored, we consider that a success // we consider that a success.
if (statusCode == 0) if (statusCode == 0)
{ {
Complete(); Complete();
} }
// Set the response code in the actual carrier
response.StatusCode = statusCode; response.StatusCode = statusCode;
if (response.StatusCode == (int)OSHttpStatusCode.RedirectMovedTemporarily || // For a redirect we need to set the relocation header accordingly
response.StatusCode == (int)OSHttpStatusCode.RedirectMovedPermanently)
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; response.RedirectLocation = redirectLocation;
} }
// And include the status description if provided.
if (statusDescription != null) if (statusDescription != null)
{ {
Rest.Log.DebugFormat("{0} Status description is {1}", MsgId, statusDescription);
response.StatusDescription = statusDescription; response.StatusDescription = statusDescription;
} }
// Finally we send back our response, consuming // Finally we send back our response.
// any exceptions that doing so might produce.
// We've left the setting of handled' until the // We've left the setting of handled' until the
// last minute because the header settings included // last minute because the header settings included
@ -913,6 +1036,14 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
handled = true; handled = true;
DumpHeaders();
// if (request.InputStream != null)
// {
// Rest.Log.DebugFormat("{0} Closing input stream", MsgId);
// request.InputStream.Close();
// }
if (buffer != null && buffer.Length != 0) if (buffer != null && buffer.Length != 0)
{ {
Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", 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.Write(buffer, 0, buffer.Length);
} }
response.OutputStream.Close(); // Closing the outputstream should complete the transmission process
if (request.InputStream != null) Rest.Log.DebugFormat("{0} Closing output stream", MsgId);
{ response.OutputStream.Close();
request.InputStream.Close();
}
} }
@ -935,19 +1064,23 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
} }
// Add a header to the table. If the header // Add a header to the table. We need to allow
// already exists, it is replaced. // multiple instances of many of the headers.
// If the
internal void AddHeader(string hdr, string data) internal void AddHeader(string hdr, string data)
{ {
if (Rest.DEBUG)
if (headers == null)
{ {
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);
}
} }
response.Headers.Add(hdr, data);
headers[hdr] = data;
} }
// Keep explicit track of any headers which // Keep explicit track of any headers which
@ -955,43 +1088,30 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal void RemoveHeader(string hdr) internal void RemoveHeader(string hdr)
{ {
if (Rest.DEBUG)
if (removed_headers == null)
{ {
removed_headers = new List<string>(); Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr);
} if (response.Headers.Get(hdr) == null)
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, h); Rest.Log.DebugFormat("{0} No such header existed",
response.Headers.Remove(h); 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}>", Rest.Log.DebugFormat("{0} Header[{1}] : {2}", MsgId, i,
MsgId, headers.GetKey(i), headers.Get(i)); response.Headers.Get(i));
response.Headers.Add(headers.GetKey(i), headers.Get(i));
} }
} }
} }
@ -1019,7 +1139,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
path = uri.AbsolutePath; path = uri.AbsolutePath;
if (path.EndsWith(Rest.UrlPathSeparator)) if (path.EndsWith(Rest.UrlPathSeparator))
path = path.Substring(0,path.Length-1); path = path.Substring(0,path.Length-1);
path = Uri.UnescapeDataString(path);
} }
// If we succeeded in getting a path, perform any // If we succeeded in getting a path, perform any
@ -1040,6 +1159,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
pathNodes = EmptyPath; 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 // Request server context info
hostname = uri.Host; hostname = uri.Host;
@ -1149,14 +1273,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal void initXmlReader() internal void initXmlReader()
{ {
XmlReaderSettings settings = new XmlReaderSettings(); XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment; settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.IgnoreComments = true; settings.IgnoreComments = true;
settings.IgnoreWhitespace = true; settings.IgnoreWhitespace = true;
settings.IgnoreProcessingInstructions = true; settings.IgnoreProcessingInstructions = true;
settings.ValidationType = ValidationType.None; settings.ValidationType = ValidationType.None;
// reader = XmlReader.Create(new StringReader(entity),settings);
reader = XmlReader.Create(request.InputStream,settings); reader = XmlReader.Create(request.InputStream,settings);
} }
private void Flush() private void Flush()

View File

@ -69,7 +69,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
internal static bool ExtendedEscape = true; internal static bool ExtendedEscape = true;
internal static bool DumpAsset = false; internal static bool DumpAsset = false;
internal static string Realm = "REST"; 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 CreationDate = (int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 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 public class RestAssetServices : IRest
{ {
private string key = "assets";
private bool enabled = false; private bool enabled = false;
private string qPrefix = "assets"; private string qPrefix = "assets";
// A simple constructor is used to handle any once-only // A simple constructor is used to handle any once-only
// initialization of working classes. // initialization of working classes.
public RestAssetServices(RestHandler p_rest) public RestAssetServices()
{ {
Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); Rest.Log.InfoFormat("{0} Asset services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); 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)) if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{ {
qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix; qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix;
} }
// Authentication domain // Register interface using the fully-qualified prefix
Rest.Domains.Add(key,Rest.Config.GetString("asset-domain",qPrefix));
// Register interface
Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate); Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate);
// Activate // Activate if all went OK
enabled = true; enabled = true;

View File

@ -38,10 +38,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public class RestHandler : RestPlugin, IHttpAgentHandler 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 #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> /// <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 /// export the IRest interface and builds a list of them. These
/// are later activated by the handler. To add a new handler it /// are later activated by the handler. To add a new handler it
/// is only necessary to create a new services class that implements /// 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 /// all of the build-time flexibility of a modular approach
/// while not introducing yet-another module loader. Note that /// while not introducing yet-another module loader. Note that
/// multiple assembles can still be built, each with its own set /// multiple assembles can still be built, each with its own set
/// of handlers. /// of handlers. Examples of services classes are RestInventoryServices
/// and RestSkeleton.
/// </summary> /// </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() static RestHandler()
{ {
Module[] mods = Assembly.GetExecutingAssembly().GetModules(); Module[] mods = Assembly.GetExecutingAssembly().GetModules();
foreach (Module m in mods) foreach (Module m in mods)
{ {
Type[] types = m.GetTypes(); Type[] types = m.GetTypes();
foreach (Type t in types) 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 #endregion local static state
#region local instance 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> /// <summary>
/// This routine loads all of the handlers discovered during /// This routine loads all of the handlers discovered during
/// instance initialization. Each handler is responsible for /// instance initialization.
/// registering itself with this handler. /// A table of all loaded and successfully constructed handlers
/// I was not able to make this code work in a constructor. /// 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> /// </summary>
private void LoadHandlers() private void LoadHandlers()
{ {
lock (handlers) lock (handlers)
{ {
if (!handlersLoaded) if (!handlersLoaded)
{ {
parms[0] = this.GetType();
args[0] = this;
ConstructorInfo ci; ConstructorInfo ci;
Object ht; Object ht;
foreach (Type t in classes) foreach (Type t in classes)
{ {
ci = t.GetConstructor(parms); try
ht = ci.Invoke(args); {
handlers.Add((IRest)ht); 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; handlersLoaded = true;
} }
@ -126,14 +154,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
#region overriding properties #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 public override string Name
{ {
get { return "HANDLER"; } get { return "HANDLER"; }
} }
// Used to partition the configuration space. // Used to partition the .ini configuration space.
public override string ConfigName 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 /// Note that entries MUST be added to the active configuration files before
/// the plugin can be enabled. /// the plugin can be enabled.
/// </remarks> /// </remarks>
public override void Initialise(OpenSimBase openSim) public override void Initialise(OpenSimBase openSim)
{ {
try try
{ {
/// <remarks> // This plugin will only be enabled if the broader
/// This plugin will only be enabled if the broader // REST plugin mechanism is enabled.
/// REST plugin mechanism is enabled.
/// </remarks>
Rest.Log.InfoFormat("{0} Plugin is initializing", MsgID); Rest.Log.InfoFormat("{0} Plugin is initializing", MsgId);
base.Initialise(openSim); base.Initialise(openSim);
// IsEnabled is implemented by the base class and
// reflects an overall RestPlugin status
if (!IsEnabled) if (!IsEnabled)
{ {
Rest.Log.WarnFormat("{0} Plugins are disabled", MsgID); Rest.Log.WarnFormat("{0} Plugins are disabled", MsgId);
return; 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
/// These are stored in static variables to make // them easy to reach from anywhere in the assembly.
/// them easy to reach from anywhere in the assembly.
/// </remarks>
Rest.main = openSim; Rest.main = openSim;
Rest.Plugin = this; 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.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId,
(Rest.DumpAsset ? "" : "not ")); (Rest.DumpAsset ? "" : "not "));
// If data dumping is requested, report on the chosen line
// length.
if (Rest.DumpAsset) if (Rest.DumpAsset)
{ {
Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId,
@ -247,22 +281,24 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
LoadHandlers(); LoadHandlers();
/// <remarks> // The intention of a post construction initializer
/// The intention of a post construction initializer // is to allow for setup that is dependent upon other
/// is to allow for setup that is dependent upon other // activities outside of the agency.
/// activities outside of the agency. We don't currently
/// have any, but the design allows for it.
/// </remarks>
foreach (IRest handler in handlers) 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 to
/// Now that everything is setup we can proceed and // add THIS agent to the HTTP server's handler list
/// add this agent to the HTTP server's handler list
/// </remarks>
if (!AddAgentHandler(Rest.Name,this)) if (!AddAgentHandler(Rest.Name,this))
{ {
@ -276,7 +312,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
} }
catch (Exception e) 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 /// To make sure everything is copacetic we make sure the primary interface
/// is disabled by deleting the handler from the HTTP server tables. /// is disabled by deleting the handler from the HTTP server tables.
/// </summary> /// </summary>
public override void Close() public override void Close()
{ {
Rest.Log.InfoFormat("{0} Plugin is terminating", MsgID); Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId);
try try
{ {
@ -313,45 +350,62 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
#region interface methods #region interface methods
/// <summary> /// <summary>
/// This method is called by the server to match the client, it could /// This method is called by the HTTP server to match an incoming
/// just return true if we only want one such handler. For now we /// request. It scans all of the strings registered by the
/// match any explicitly specified client. /// 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> /// </summary>
public bool Match(OSHttpRequest request, OSHttpResponse response) public bool Match(OSHttpRequest request, OSHttpResponse response)
{ {
string path = request.RawUrl; 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); try
foreach (string key in streamHandlers.Keys)
{ {
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; return false;
} }
/// <summary> /// <summary>
/// This is called by the HTTP server once the handler has indicated
/// that t is able to handle the request.
/// Preconditions: /// Preconditions:
/// [1] request != null and is a valid request object /// [1] request != null and is a valid request object
/// [2] response != null and is a valid response object /// [2] response != null and is a valid response object
/// Behavior is undefined if preconditions are not satisfied. /// Behavior is undefined if preconditions are not satisfied.
/// </summary> /// </summary>
public bool Handle(OSHttpRequest request, OSHttpResponse response) public bool Handle(OSHttpRequest request, OSHttpResponse response)
{ {
bool handled; bool handled;
base.MsgID = base.RequestID; base.MsgID = base.RequestID;
// Debug only
if (Rest.DEBUG) if (Rest.DEBUG)
{ {
Rest.Log.DebugFormat("{0} ENTRY", MsgId); Rest.Log.DebugFormat("{0} ENTRY", MsgId);
@ -371,8 +425,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
try try
{ {
handled = FindPathHandler(request, response) || handled = ( FindPathHandler(request, response) ||
FindStreamHandler(request, response); FindStreamHandler(request, response) );
} }
catch (Exception e) catch (Exception e)
{ {
@ -406,6 +460,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path); Rest.Log.DebugFormat("{0} Checking for stream handler for <{1}>", MsgId, path);
if (!IsEnabled)
{
return false;
}
foreach (string pattern in streamHandlers.Keys) foreach (string pattern in streamHandlers.Keys)
{ {
if (path.StartsWith(pattern)) 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) public void AddStreamHandler(string httpMethod, string path, RestMethod method)
{ {
@ -454,17 +519,26 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (!streamHandlers.ContainsKey(path)) if (!streamHandlers.ContainsKey(path))
{ {
streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method)); 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 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; 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) internal void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ra)
{ {
if (!IsEnabled)
{
return;
}
if (pathHandlers.ContainsKey(path)) if (pathHandlers.ContainsKey(path))
{ {
Rest.Log.DebugFormat("{0} Replacing handler for <${1}>", MsgId, 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.IO;
using System.Threading; using System.Threading;
using System.Xml; using System.Xml;
using OpenJPEGNet;
using OpenSim.Framework; using OpenSim.Framework;
using OpenSim.Framework.Servers; using OpenSim.Framework.Servers;
using OpenSim.Framework.Communications; using OpenSim.Framework.Communications;
@ -44,35 +45,33 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
public class RestInventoryServices : IRest public class RestInventoryServices : IRest
{ {
private string key = "inventory";
private bool enabled = false; private bool enabled = false;
private string qPrefix = "inventory"; private string qPrefix = "inventory";
// A simple constructor is used to handle any once-only /// <summary>
// initialization of working classes. /// 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} Inventory services initializing", MsgId);
Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); 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)) if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
{ {
qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix; qPrefix = Rest.Prefix + Rest.UrlPathSeparator + qPrefix;
} }
// Authentication domain // Register interface using the absolute URI.
Rest.Domains.Add(key, Rest.Config.GetString("inventory-domain",qPrefix));
// Register interface
Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate);
// Activate // Activate if everything went OK
enabled = true; enabled = true;
@ -80,16 +79,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
} }
// Post-construction, pre-enabled initialization opportunity /// <summary>
// Not currently exploited. /// Post-construction, pre-enabled initialization opportunity
/// Not currently exploited.
/// </summary>
public void Initialize() public void Initialize()
{ {
} }
// Called by the plug-in to halt REST processing. Local processing is /// <summary>
// disabled, and control blocks until all current processing has /// Called by the plug-in to halt REST processing. Local processing is
// completed. No new processing will be started /// disabled, and control blocks until all current processing has
/// completed. No new processing will be started
/// </summary>
public void Close() public void Close()
{ {
@ -97,7 +100,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); 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 internal string MsgId
{ {
@ -106,15 +112,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
#region Interface #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) private RequestData Allocate(OSHttpRequest request, OSHttpResponse response)
{ {
return (RequestData) new InventoryRequestData(request, response, qPrefix); return (RequestData) new InventoryRequestData(request, response, qPrefix);
} }
/// <summary> /// <summary>
/// This method is registered with the handler when this class is /// This method is registered with the handler when this service provider
/// initialized. It is called whenever the URI includes this handler's /// is initialized. It is called whenever the plug-in identifies this service
/// prefix string. /// provider as the best match.
/// It handles all aspects of inventory REST processing. /// It handles all aspects of inventory REST processing.
/// </summary> /// </summary>
@ -125,7 +138,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId);
// We're disabled // If we're disabled, do nothing.
if (!enabled) if (!enabled)
{ {
return; return;
@ -169,23 +183,32 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
// We can only get here if we're authorized /// <remarks>
// /// We can only get here if we are authorized
// The requestor may have specified an LLUUID or ///
// a conjoined FirstNameLastName string. We'll /// The requestor may have specified an LLUUID or
// try both. If we fail with the first, UUID, /// a conjoined FirstName LastName string. We'll
// attempt, then we need two nodes to construct /// try both. If we fail with the first, UUID,
// a valid avatar name. /// 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? // Do we have at least a user agent name?
if (rdata.parameters.Length < 1) if (rdata.parameters.Length < 1)
{ {
Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); 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. // or a space-separated First-name Last-Name specification.
try try
@ -205,10 +228,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
else else
{ {
Rest.Log.DebugFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); 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) if (rdata.userProfile != null)
{ {
Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}", Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}",
@ -217,11 +243,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
else else
{ {
Rest.Log.DebugFormat("{0} No profile for {1}", MsgId, rdata.path); 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 // If we get to here, then we have effectively validated the user's
// for the specified user. // 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; 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}", Rest.Log.DebugFormat("{0} Inventory is not available [1] for agent {1} {2}",
MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); 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}", Rest.Log.DebugFormat("{0} Inventory is not available for agent [3] {1} {2}",
MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); 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 // 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}", Rest.Log.DebugFormat("{0} Method {1} not supported for {2}",
MsgId, rdata.method, rdata.path); MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
Rest.HttpStatusDescMethodNotAllowed); Rest.HttpStatusDescMethodNotAllowed+": "+rdata.method+" not supported");
break; break;
} }
@ -315,10 +350,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty); 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) if (rdata.parameters.Length == 1)
{ {
formatInventory(rdata, rdata.root, String.Empty); 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 else
{ {
traverseInventory(rdata, rdata.root, 1); traverseInventory(rdata, rdata.root, 1);
@ -332,33 +376,35 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
} }
/// <summary> /// <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 /// the distinction between PUT and POST is not always
/// easy to discern. Adding a directory can be viewed as /// easy to discern. Adding a directory can be viewed as
/// an addition, or as a modification to the inventory 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 /// For OpenSim POST is an update and PUT is an addition.
/// the entity and the URI. If we view POST as an update, ///
/// then the enity represents a replacement for the /// The best way to exaplain the distinction is to
/// element named by the URI. If the operation is PUT, /// consider the relationship between the URI and the
/// then the URI describes the context into which the /// entity in question. For POST, the URI identifies the
/// entity will be added. /// 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: /// As an example, suppose the URI contains:
/// /admin/inventory/Clothing /// /admin/inventory/Clothing
/// Suppose the entity represents a Folder, called
/// "Clothes".
/// ///
/// A POST request will result in the replacement of /// A POST request will result in some modification of
/// "Clothing" by "Clothes". Whereas a PUT request /// the folder or item named "Clothing". Whereas a PUT
/// would add Clothes as a sub-directory of Clothing. /// request will add some new information into the
/// /// content identified by Clothing. It follows from this
/// This is the model followed by this implementation. /// that for PUT, the element identified by the URI must
/// be a folder.
/// </summary> /// </summary>
/// <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. /// context identified by the URI.
/// </summary> /// </summary>
@ -376,7 +422,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
// exception. // exception.
// It follows that we can only add information if the URI // 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. // in this case.
if (typeof(InventoryFolderBase) == InventoryNode.GetType() || 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}", Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}",
MsgId, rdata.method, rdata.path); MsgId, rdata.method, rdata.path);
// Reconstitute inventory sub-tree from the XML supplied in the entity. // Reconstitute the inventory sub-tree from the XML supplied in the entity.
// This is a stand-alone inventory subtree, not yet integrated into the // The result is a stand-alone inventory subtree, not yet integrated into the
// existing tree. // 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); XmlInventoryCollection entity = ReconstituteEntity(rdata);
// Inlined assest included in entity. If anything fails, // Inlined assets can be included in entity. These must be incorporated into
// return failure to requestor. // the asset database before we attempt to update the inventory. If anything
// fails, return a failure to requestor.
if (entity.Assets.Count > 0) if (entity.Assets.Count > 0)
{ {
@ -410,10 +461,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
MsgId, asset.ID, asset.Type, asset.Name); MsgId, asset.ID, asset.Type, asset.Name);
Rest.AssetServices.AddAsset(asset); Rest.AssetServices.AddAsset(asset);
if (Rest.DumpAsset) if (Rest.DumpAsset)
{ {
Rest.Dump(asset.Data); Rest.Dump(asset.Data);
} }
} }
} }
@ -424,11 +477,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
foreach (InventoryFolderBase folder in entity.Folders) foreach (InventoryFolderBase folder in entity.Folders)
{ {
InventoryFolderBase found = null; InventoryFolderBase found;
// If the parentID is zero, then this is going // If the parentID is zero, then this folder is going
// into the root identified by the URI. The requestor // into the root folder identified by the URI. The requestor
// may have already set the parent ID correctly, in which // may have already set the parent ID explicitly, in which
// case we don't have to do it here. // case we don't have to do it here.
if (folder.ParentID == LLUUID.Zero) if (folder.ParentID == LLUUID.Zero)
@ -437,7 +490,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
} }
// Search the existing inventory for an existing entry. If // 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 // It could just be present as (unnecessary) context, and we
// don't want to waste time updating the database in that // don't want to waste time updating the database in that
// case, OR, it could be being moved from another location // case, OR, it could be being moved from another location
@ -451,6 +504,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (xf.ID == folder.ID) if (xf.ID == folder.ID)
{ {
found = xf; found = xf;
break;
} }
} }
@ -492,6 +546,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
if (xi.ID == item.ID) if (xi.ID == item.ID)
{ {
found = xi; 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}", Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}",
MsgId, rdata.method, rdata.path, InventoryNode.GetType()); MsgId, rdata.method, rdata.path, InventoryNode.GetType());
rdata.Fail(Rest.HttpStatusCodeBadRequest, rdata.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": invalid resource context");
} }
rdata.Complete(); rdata.Complete();
@ -531,7 +586,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
/// [1] It identifies the user whose inventory is to be /// [1] It identifies the user whose inventory is to be
/// processed. /// processed.
/// [2] It optionally specifies a subtree of the inventory /// [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 /// specifications in the entity. If nothing is specified
/// then the whole inventory is implied. /// then the whole inventory is implied.
/// Please note that the subtree specified by the URI is only relevant /// 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 /// elements will be implicitly referenced within the context identified
/// by the URI. /// by the URI.
/// If an element in the entity specifies an explicit parent folder, then /// 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 /// URI. If the parent does not exist, then the element, and any dependent
/// elements, are ignored. This case is actually detected and handled /// elements, are ignored. This case is actually detected and handled
/// during the reconstitution process. /// during the reconstitution process.
@ -555,33 +610,54 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Object InventoryNode = getInventoryNode(rdata, rdata.root, 1); Object InventoryNode = getInventoryNode(rdata, rdata.root, 1);
// As long as we have a context, then we have something // As long as we have a node, then we have something
// meaningful to do, unlike PUT. So reconstitute the // meaningful to do, unlike PUT. So we reconstitute the
// subtree before doing anything else. Note that we // 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); 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) foreach (AssetBase asset in entity.Assets)
{ {
// Asset was validated during the collection Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
// process MsgId, asset.ID, asset.Type, asset.Name);
// The asset was validated during the collection process
Rest.AssetServices.AddAsset(asset); Rest.AssetServices.AddAsset(asset);
if (Rest.DumpAsset)
{
Rest.Dump(asset.Data);
}
} }
} }
/// <summary> /// <summary>
/// URI specifies a folder to be updated. /// The URI specifies either a folder or an item to be updated.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The root node in the entity must have the same /// The root node in the entity will replace the node identified
/// UUID as the node identified by the URI. The /// by the URI. This means the parent will remain the same, but
/// parentID if different indicates that the updated /// any or all attributes associated with the named element
/// folder is actually being moved too. /// 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> /// </remarks>
if (typeof(InventoryFolderBase) == InventoryNode.GetType() || if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
@ -592,7 +668,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
InventoryFolderBase xml = null; InventoryFolderBase xml = null;
// Scan the set of folders in the entity collection for an // 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 // the only reliable indicator of this is a zero UUID ( using
// implicit context), or the parent's UUID matches that of the // implicit context), or the parent's UUID matches that of the
// URI designated node (explicit context). We don't allow // 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) if (count > 1)
{ {
Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous",
MsgId, rdata.method, rdata.path); MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest, rdata.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": context is ambiguous");
} }
// Exactly one entry means we ARE replacing the node // 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}>", Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>",
MsgId, rdata.method, rdata.path); MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest, rdata.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": folder is not allowed");
} }
if (entity.Items.Count > 1) 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}>", Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>",
MsgId, rdata.method, rdata.path); MsgId, rdata.method, rdata.path);
rdata.Fail(Rest.HttpStatusCodeBadRequest, rdata.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": too may items");
} }
xml = entity.Items[0]; xml = entity.Items[0];
@ -854,7 +931,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{ {
Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
MsgId, rdata.method, rdata.path); 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", Rest.Log.DebugFormat("{0} {1}: Resource {2} not found",
MsgId, rdata.method, rdata.path); 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 */ return null; /* Never reached */
@ -931,7 +1008,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>",
MsgId, rdata.path); 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.Version = 1;
TrashCan.Type = (short) AssetType.TrashFolder; TrashCan.Type = (short) AssetType.TrashFolder;
TrashCan.ParentID = f.ID; TrashCan.ParentID = f.ID;
TrashCan.Owner = f.Owner;
Rest.InventoryServices.AddFolder(TrashCan); Rest.InventoryServices.AddFolder(TrashCan);
} }
} }
@ -1070,7 +1148,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{ {
Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); Rest.Log.DebugFormat("{0} No Trash Can available", MsgId);
rdata.Fail(Rest.HttpStatusCodeServerError, rdata.Fail(Rest.HttpStatusCodeServerError,
Rest.HttpStatusDescServerError); Rest.HttpStatusDescServerError+": unable to create trash can");
} }
return TrashCan; return TrashCan;
@ -1313,7 +1391,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}",
MsgId, ic.xml.Name, ic.xml.Value); MsgId, ic.xml.Name, ic.xml.Value);
ic.Fail(Rest.HttpStatusCodeBadRequest, ic.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": unrecognized attribute");
break; break;
} }
} }
@ -1349,7 +1427,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}",
MsgId, ic.Item.Folder, result.ID); MsgId, ic.Item.Folder, result.ID);
ic.Fail(Rest.HttpStatusCodeBadRequest, 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}", Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}",
MsgId, ic.xml.Name, ic.xml.Value); MsgId, ic.xml.Name, ic.xml.Value);
ic.Fail(Rest.HttpStatusCodeBadRequest, ic.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": unrecognized attribute");
break; break;
} }
} }
@ -1570,7 +1648,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory
{ {
Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest, 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); Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest, ic.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": request parse error");
} }
// Every item is required to have a name (via REST anyway) // 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); Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest, ic.Fail(Rest.HttpStatusCodeBadRequest,
Rest.HttpStatusDescBadRequest); Rest.HttpStatusDescBadRequest+": item name required");
} }
// An item MUST have an asset ID. AssetID should never be zero // 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.ErrorFormat("{0} Unable to complete request", MsgId);
Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); Rest.Log.InfoFormat("{0} Asset information is missing", MsgId);
ic.Fail(Rest.HttpStatusCodeBadRequest, 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}", Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}",
MsgId, ic.Item.Folder, ic.Item.ID); MsgId, ic.Item.Folder, ic.Item.ID);
ic.Fail(Rest.HttpStatusCodeBadRequest, 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) if (ic.Item.InvType == (int) AssetType.Unknown)
ic.Item.InvType = (int) AssetType.ImageJPEG; ic.Item.InvType = (int) AssetType.ImageJPEG;
break; 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 : default :
Rest.Log.DebugFormat("{0} Type was not inferred", MsgId); Rest.Log.DebugFormat("{0} Type was not inferred", MsgId);
break; 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(); ic.reset();
} }

View File

@ -171,18 +171,20 @@ namespace OpenSim.Framework.Servers
OSHttpRequest request = new OSHttpRequest(context.Request); OSHttpRequest request = new OSHttpRequest(context.Request);
OSHttpResponse response = new OSHttpResponse(context.Response); OSHttpResponse response = new OSHttpResponse(context.Response);
// user agent based requests? not sure where this actually gets used from // This is the REST agent interface. We require an agent to properly identify
if (request.UserAgent != null) // 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
IHttpAgentHandler agentHandler; // 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)) return;
{
m_log.DebugFormat("[HTTP-AGENT] Handler located for {0}", request.UserAgent);
return;
}
} }
} }
@ -508,7 +510,7 @@ namespace OpenSim.Framework.Servers
catch (Exception e) catch (Exception e)
{ {
// If the handler did in fact close the stream, then this will blow // 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 // and all exceptions raised. We've done our best to release the
// client. // client.
try try
@ -524,7 +526,10 @@ namespace OpenSim.Framework.Servers
} }
} }
// Indicate that the request has been "handled"
return true; return true;
} }
public void HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response) public void HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response)