From 134f86e8d5c414409631b25b8c6f0ee45fbd8631 Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Thu, 3 Nov 2016 21:44:39 +1000 Subject: Initial update to OpenSim 0.8.2.1 source code. --- OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs | 43 - .../Rest/Inventory/IRestHandler.cs | 59 - .../Rest/Inventory/RequestData.cs | 1465 ------------ .../Rest/Inventory/Resources/RestHandler.addin.xml | 11 - OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs | 551 ----- .../Rest/Inventory/RestAppearanceServices.cs | 860 ------- .../Rest/Inventory/RestAssetServices.cs | 383 ---- .../Rest/Inventory/RestFileServices.cs | 448 ---- .../Rest/Inventory/RestHandler.cs | 662 ------ .../Rest/Inventory/RestInventoryServices.cs | 2343 -------------------- .../Rest/Inventory/RestTestServices.cs | 246 -- .../Rest/Inventory/tests/ITest.cs | 46 - .../Rest/Inventory/tests/Remote.cs | 204 -- 13 files changed, 7321 deletions(-) delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs (limited to 'OpenSim/ApplicationPlugins/Rest/Inventory') diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs deleted file mode 100644 index 8b43d42..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - /// - /// This interface represents the boundary between the general purpose - /// REST plugin handling, and the functionally specific handlers. The - /// handler knows only to initialize and terminate all such handlers - /// that it finds. Implementing this interface identifies the class as - /// a REST handler implementation. - /// - - internal interface IRest - { - void Initialize(); - void Close(); - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs deleted file mode 100644 index a88fe88..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - - /// - /// 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. - /// - - public delegate void RestMethodHandler(RequestData rdata); - public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path); - - /// - /// This interface exports the generic plugin-handling services - /// available to each loaded REST services module (IRest implementation) - /// - - internal interface IRestHandler - { - - string MsgId { get; } - string RequestId { get; } - - void AddPathHandler(RestMethodHandler mh, string path, RestMethodAllocator ma); - void AddStreamHandler(string httpMethod, string path, RestMethod method); - - } - -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs deleted file mode 100644 index 10f1a6e..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs +++ /dev/null @@ -1,1465 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Services.Interfaces; - -using OpenMetaverse; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - - /// - /// This class represents the current REST request. It - /// encapsulates the request/response state and takes care - /// of response generation without exposing the REST handler - /// to the actual mechanisms involved. - /// - /// 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 - /// 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 - /// of this. - /// - /// If possible, the underlying request/response state is not - /// changed until the handler explicitly issues a Respond call. - /// This ensures that the request/response pair can be safely - /// processed by subsequent, unrelated, handlers even id the - /// agent handler had completed much of its processing. Think - /// of it as a transactional req/resp capability. - /// - - public class RequestData - { - - // HTTP Server interface data (Received values) - - internal OSHttpRequest request = null; - internal OSHttpResponse response = null; - internal string qprefix = null; - - // Request lifetime values - // buffer is global because it is referenced by the handler - // in supported of streamed requests. - // If a service provider wants to construct the message - // body explicitly it can use body to do this. The value - // in body is used if the buffer is still null when a response - // is generated. - // Storing information in body will suppress the return of - // statusBody which is only intended to report status on - // requests which do not themselves ordinarily generate - // an informational response. All of this is handled in - // Respond(). - - internal byte[] buffer = null; - internal string body = null; - internal string bodyType = "text/html"; - - // The encoding in effect is set to a server default. It may - // subsequently be overridden by a Content header. This - // value is established during construction and is used - // wherever encoding services are needed. - - internal Encoding encoding = Rest.Encoding; - - // These values are derived from the supplied URL. They - // are initialized during construction. - - internal string path = null; - internal string method = null; - internal Uri uri = null; - internal string query = null; - internal string hostname = "localhost"; - internal int port = 80; - - // The path part of the URI is decomposed. pathNodes - // is an array of every element in the URI. Parameters - // is an array that contains only those nodes that - // are not a part of the authority prefix - - private string[] pathNodes = null; - private string[] parameters = null; - private static readonly string[] EmptyPath = { String.Empty }; - - // The status code gets set during the course of processing - // and is the HTTP completion code. The status body is - // initialized during construction, is appended to during the - // course of execution, and is finalized during Respond - // processing. - // - // Fail processing marks the request as failed and this is - // then used to inhibit processing during Response processing. - - internal int statusCode = 0; - internal string statusBody = String.Empty; - internal bool fail = false; - - // This carries the URL to which the client should be redirected. - // It is set by the service provider using the Redirect call. - - internal string redirectLocation = null; - - // These values influence response processing. They can be set by - // service providers according to need. The defaults are generally - // good. - - internal bool keepAlive = false; - internal bool chunked = false; - - // XML related state - - internal XmlWriter writer = null; - internal XmlReader reader = null; - - // Internal working state - - private StringBuilder sbuilder = new StringBuilder(1024); - private MemoryStream xmldata = null; - - // This is used to make the response mechanism idempotent. - - internal bool handled = false; - - // Authentication related state - // - // Two supported authentication mechanisms are: - // scheme = Rest.AS_BASIC; - // scheme = Rest.AS_DIGEST; - // Presented in that order (as required by spec) - // A service provider can set the scheme variable to - // force selection of a particular authentication model - // (choosing from amongst those supported of course) - // - - internal bool authenticated = false; - internal string scheme = Rest.Scheme; - internal string realm = Rest.Realm; - internal string domain = null; - internal string nonce = null; - internal string cnonce = null; - internal string qop = Rest.Qop_Auth; - internal string opaque = null; - internal string stale = null; - internal string algorithm = Rest.Digest_MD5; - internal string authParms = null; - internal string authPrefix = null; - internal string userName = String.Empty; - internal string userPass = String.Empty; - - // Session related tables. These are only needed if QOP is set to "auth-sess" - // and for now at least, it is not. Session related authentication is of - // questionable merit in the context of REST anyway, but it is, arguably, more - // secure. - - private static Dictionary cntable = new Dictionary(); - private static Dictionary sktable = new Dictionary(); - - // This dictionary is used to keep track fo all of the parameters discovered - // when the authorisation header is anaylsed. - - private Dictionary authparms = new Dictionary(); - - // These regular expressions are used to decipher the various header entries. - - private static Regex schema = new Regex("^\\s*(?\\w+)\\s*.*", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex basicParms = new Regex("^\\s*(?:\\w+)\\s+(?\\S+)\\s*", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex digestParm1 = new Regex("\\s*(?\\w+)\\s*=\\s*\"(?[^\"]+)\"", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex digestParm2 = new Regex("\\s*(?\\w+)\\s*=\\s*(?[^\\p{P}\\s]+)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static Regex reuserPass = new Regex("(?[^:]+):(?[\\S\\s]*)", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - // For efficiency, we create static instances of these objects - - private static MD5 md5hash = MD5.Create(); - - private static StringComparer sc = StringComparer.OrdinalIgnoreCase; - -#region properties - - // Just for convenience... - - internal string MsgId - { - get { return Rest.MsgId; } - } - - /// - /// Return a boolean indication of whether or no an authenticated user is - /// associated with this request. This could be wholly integrated, but - /// that would make authentication mandatory. - /// - - internal bool IsAuthenticated - { - get - { - if (Rest.Authenticate) - { - if (!authenticated) - { - authenticate(); - } - - return authenticated; - } - else return true; - } - } - - /// - /// Access to all 'nodes' in the supplied URI as an - /// array of strings. - /// - - internal string[] PathNodes - { - get - { - return pathNodes; - } - } - - /// - /// Access to all non-prefix 'nodes' in the supplied URI as an - /// array of strings. These identify a specific resource that - /// is managed by the authority (the prefix). - /// - - internal string[] Parameters - { - get - { - return parameters; - } - } - -#endregion properties - -#region constructors - - // Constructor - - internal RequestData(OSHttpRequest p_request, OSHttpResponse p_response, string p_qprefix) - { - - request = p_request; - response = p_response; - qprefix = p_qprefix; - - sbuilder.Length = 0; - - encoding = request.ContentEncoding; - - if (encoding == null) - { - encoding = Rest.Encoding; - } - - method = request.HttpMethod.ToLower(); - initUrl(); - - initParameters(p_qprefix.Length); - - } - -#endregion constructors - -#region authentication_common - - /// - /// The REST handler has requested authentication. Authentication - /// is considered to be with respect to the current values for - /// Realm, domain, etc. - /// - /// This method checks to see if the current request is already - /// authenticated for this domain. If it is, then it returns - /// true. If it is not, then it issues a challenge to the client - /// and responds negatively to the request. - /// - /// As soon as authentication failure is detected the method calls - /// DoChallenge() which terminates the request with REST exception - /// for unauthroized access. - /// - - private void authenticate() - { - - string authdata = request.Headers.Get("Authorization"); - string reqscheme = String.Empty; - - // If we don't have an authorization header, then this - // user is certainly not authorized. This is the typical - // pivot for the 1st request by a client. - - if (authdata == null) - { - Rest.Log.DebugFormat("{0} Challenge reason: No authorization data", MsgId); - DoChallenge(); - } - - // So, we have authentication data, now we have to check to - // see what we got and whether or not it is valid for the - // current domain. To do this we need to interpret the data - // provided in the Authorization header. First we need to - // identify the scheme being used and route accordingly. - - MatchCollection matches = schema.Matches(authdata); - - foreach (Match m in matches) - { - Rest.Log.DebugFormat("{0} Scheme matched : {1}", MsgId, m.Groups["scheme"].Value); - reqscheme = m.Groups["scheme"].Value.ToLower(); - } - - // If we want a specific authentication mechanism, make sure - // 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: Requested scheme not acceptable", MsgId); - DoChallenge(); - } - - // In the future, these could be made into plug-ins... - // But for now at least we have no reason to use anything other - // then MD5. TLS/SSL are taken care of elsewhere. - - switch (reqscheme) - { - case "digest" : - Rest.Log.DebugFormat("{0} Digest authentication offered", MsgId); - DoDigest(authdata); - break; - - case "basic" : - Rest.Log.DebugFormat("{0} Basic authentication offered", MsgId); - DoBasic(authdata); - break; - } - - // If the current header is invalid, then a challenge is still needed. - - if (!authenticated) - { - Rest.Log.DebugFormat("{0} Challenge reason: Authentication failed", MsgId); - DoChallenge(); - } - - } - - /// - /// Construct the necessary WWW-Authenticate headers and fail the request - /// with a NOT AUTHORIZED response. The parameters are the union of values - /// required by the supported schemes. - /// - - private void DoChallenge() - { - Flush(); - nonce = Rest.NonceGenerator(); // should be unique per 401 (and it is) - Challenge(scheme, realm, domain, nonce, opaque, stale, algorithm, qop, authParms); - Fail(Rest.HttpStatusCodeNotAuthorized); - } - - /// - /// The Flush() call is here to support a problem encountered with the - /// client where an authentication rejection was lost because the rejection - /// may flow before the clienthas finished sending us the inbound data stream, - /// in which case the client responds to the socket error on out put, and - /// never sees the authentication challenge. The client should be fixed, - /// because this solution leaves the server prone to DOS attacks. A message - /// will be issued whenever flushing occurs. It can be enabled/disabled from - /// the configuration file. - /// - - private void Flush() - { - if (Rest.FlushEnabled) - { - byte[] dbuffer = new byte[8192]; - Rest.Log.WarnFormat("{0} REST server is flushing the inbound data stream", MsgId); - while (request.InputStream.Read(dbuffer,0,dbuffer.Length) != 0); - } - return; - } - - // Indicate that authentication is required - - private void Challenge(string scheme, string realm, string domain, string nonce, - string opaque, string stale, string alg, - string qop, string auth) - { - - sbuilder.Length = 0; - - // The service provider can force a particular scheme by - // assigning a value to scheme. - - // Basic authentication is pretty simple. - // Just specify the realm in question. - - if (scheme == null || scheme == Rest.AS_BASIC) - { - - sbuilder.Append(Rest.AS_BASIC); - - if (realm != null) - { - sbuilder.Append(" realm="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(realm); - sbuilder.Append(Rest.CS_DQUOTE); - } - AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); - } - - sbuilder.Length = 0; - - // Digest authentication takes somewhat more - // to express. - - if (scheme == null || scheme == Rest.AS_DIGEST) - { - - sbuilder.Append(Rest.AS_DIGEST); - sbuilder.Append(" "); - - // Specify the effective realm. This should - // never be null if we are uthenticating, as it is required for all - // authentication schemes. It defines, in conjunction with the - // absolute URI information, the domain to which the authentication - // applies. It is an arbitrary string. I *believe* this allows an - // authentication to apply to disjoint resources within the same - // server. - - if (realm != null) - { - sbuilder.Append("realm="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(realm); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // Share our nonce. This is *uniquely* generated each time a 401 is - // returned. We do not generate a very sophisticated nonce at the - // moment (it's simply a base64 encoded UUID). - - if (nonce != null) - { - sbuilder.Append("nonce="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(nonce); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // The opaque string should be returned by the client unchanged in all - // subsequent requests. - - if (opaque != null) - { - sbuilder.Append("opaque="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(opaque); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // This flag indicates that the authentication was rejected because the - // included nonce was stale. The server might use timestamp information - // in the nonce to determine this. We do not. - - if (stale != null) - { - sbuilder.Append("stale="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(stale); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // Identifies the algorithm used to produce the digest and checksum. - // The default is MD5. - - if (alg != null) - { - sbuilder.Append("algorithm="); - sbuilder.Append(alg); - sbuilder.Append(Rest.CS_COMMA); - } - - // Theoretically QOP is optional, but it is required by a compliant - // with current versions of the scheme. In fact IE requires that QOP - // be specified and will refuse to authenticate otherwise. - - if (qop != String.Empty) - { - sbuilder.Append("qop="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(qop); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(Rest.CS_COMMA); - } - - // This parameter allows for arbitrary extensions to the protocol. - // Unrecognized values should be simply ignored. - - if (auth != null) - { - sbuilder.Append(auth); - 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. - - sbuilder.Append("domain="); - sbuilder.Append(Rest.CS_DQUOTE); - sbuilder.Append(qprefix); - sbuilder.Append(Rest.CS_DQUOTE); - - // Generate the authenticate header and we're basically - // done. - - AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); - - } - - } - -#endregion authentication_common - -#region authentication_basic - - /// - /// Interpret a BASIC authorization claim. Some clients can only - /// understand this and also expect it to be the first one - /// offered. So we do. - /// OpenSim also needs this, as it is the only scheme that allows - /// authentication using the hashed passwords stored in the - /// user database. - /// - - private void DoBasic(string authdata) - { - - string response = null; - - MatchCollection matches = basicParms.Matches(authdata); - - // In the case of basic authentication there is - // only expected to be a single argument. - - foreach (Match m in matches) - { - authparms.Add("response",m.Groups["pval"].Value); - Rest.Log.DebugFormat("{0} Parameter matched : {1} = {2}", - MsgId, "response", m.Groups["pval"].Value); - } - - // Did we get a valid response? - - if (authparms.TryGetValue("response", out response)) - { - // Decode - response = Rest.Base64ToString(response); - Rest.Log.DebugFormat("{0} Auth response is: <{1}>", MsgId, response); - - // Extract user & password - Match m = reuserPass.Match(response); - userName = m.Groups["user"].Value; - userPass = m.Groups["pass"].Value; - - // Validate against user database - authenticated = Validate(userName,userPass); - } - - } - - /// - /// 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). - /// - - private bool Validate(string user, string pass) - { - - Rest.Log.DebugFormat("{0} Simple User Validation", MsgId); - - // Both values are required - - if (user == null || pass == null) - return false; - - // Eliminate any leading or trailing spaces - user = user.Trim(); - - return vetPassword(user, pass); - - } - - /// - /// This is used by the BASIC authentication scheme to calculate - /// the double hash used by OpenSim to encode user's passwords. - /// It returns true, if the supplied password is actually correct. - /// If the specified user-id is not recognized, but the password - /// matches the God password, then this is accepted as an admin - /// session. - /// - - private bool vetPassword(string user, string pass) - { - - 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; - } - - UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last); - - // If we don't recognize the user id, perhaps it is god? - if (account == null) - return pass == Rest.GodKey; - - return (Rest.AuthServices.Authenticate(account.PrincipalID, pass, 1) != string.Empty); - - } - -#endregion authentication_basic - -#region authentication_digest - - /// - /// This is an RFC2617 compliant HTTP MD5 Digest authentication - /// implementation. It has been tested with Firefox, Java HTTP client, - /// and Microsoft's Internet Explorer V7. - /// - - private void DoDigest(string authdata) - { - - string response = null; - - // Find all of the values of the for x = "y" - - MatchCollection matches = digestParm1.Matches(authdata); - - foreach (Match m in matches) - { - authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); - Rest.Log.DebugFormat("{0} String Parameter matched : {1} = {2}", - MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); - } - - // Find all of the values of the for x = y - - matches = digestParm2.Matches(authdata); - - foreach (Match m in matches) - { - authparms.Add(m.Groups["parm"].Value,m.Groups["pval"].Value); - Rest.Log.DebugFormat("{0} Tokenized Parameter matched : {1} = {2}", - MsgId, m.Groups["parm"].Value,m.Groups["pval"].Value); - } - - // A response string MUST be returned, otherwise we are - // NOT authenticated. - - Rest.Log.DebugFormat("{0} Validating authorization parameters", MsgId); - - if (authparms.TryGetValue("response", out response)) - { - - string temp = null; - - do - { - - string nck = null; - string ncl = null; - - // The userid is sent in clear text. Needed for the - // verification. - - authparms.TryGetValue("username", out userName); - - // All URI's of which this is a prefix are - // optimistically considered to be authenticated by the - // client. This is also needed to verify the response. - - 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 client returns it - // to us, as it should. - - if (!authparms.TryGetValue("nonce", out nonce) || nonce == null) - { - Rest.Log.WarnFormat("{0} Authentication failed: nonce missing", MsgId); - break; - } - - // If there is an opaque string present, it had better - // match what we sent. - - if (authparms.TryGetValue("opaque", out temp)) - { - if (temp != opaque) - { - Rest.Log.WarnFormat("{0} Authentication failed: bad opaque value", MsgId); - break; - } - } - - // If an algorithm string is present, it had better - // match what we sent. - - if (authparms.TryGetValue("algorithm", out temp)) - { - if (temp != algorithm) - { - Rest.Log.WarnFormat("{0} Authentication failed: bad algorithm value", MsgId); - break; - } - } - - // Quality of protection considerations... - - if (authparms.TryGetValue("qop", out temp)) - { - - qop = temp.ToLower(); // replace with actual value used - - // if QOP was specified then - // these MUST be present. - - if (!authparms.ContainsKey("cnonce")) - { - Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - - cnonce = authparms["cnonce"]; - - if (!authparms.TryGetValue("nc", out nck) || nck == null) - { - Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - - Rest.Log.DebugFormat("{0} Comparing nonce indices", MsgId); - - if (cntable.TryGetValue(nonce, out ncl)) - { - 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); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - cntable[nonce] = nck; - } - else - { - lock (cntable) cntable.Add(nonce, nck); - } - - } - else - { - - qop = String.Empty; - - // if QOP was not specified then - // these MUST NOT be present. - if (authparms.ContainsKey("cnonce")) - { - Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - if (authparms.ContainsKey("nc")) - { - Rest.Log.WarnFormat("{0} Authentication failed: invalid cnonce counter[2]", MsgId); - Fail(Rest.HttpStatusCodeBadRequest); - break; - } - } - - // Validate the supplied userid/password info - - authenticated = ValidateDigest(userName, nonce, cnonce, nck, authPrefix, response); - - } - while (false); - - } - else - Fail(Rest.HttpStatusCodeBadRequest); - - } - - /// - /// This mechanism is used by the digest authentication 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 client has used the same salting mechanism - /// to has the password before using it in the authentication - /// algorithn. This is not inconceivable... - /// - - private string getPassword(string user) - { - - 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; - } - - UserAccount account = Rest.UserServices.GetUserAccount(UUID.Zero, first, last); - // If we don;t recognize the user id, perhaps it is god? - - if (account == null) - { - Rest.Log.DebugFormat("{0} Administrator", MsgId); - return Rest.GodKey; - } - else - { - Rest.Log.DebugFormat("{0} Normal User {1}", MsgId, user); - - // !!! REFACTORING PROBLEM - // This is what it was. It doesn't work in 0.7 - // Nothing retrieves the password from the authentication service, there's only authentication. - //return udata.PasswordHash; - return string.Empty; - } - - } - - // Validate the request-digest - - private bool ValidateDigest(string user, string nonce, string cnonce, string nck, string uri, string response) - { - - string patt = null; - string payl = String.Empty; - string KDS = null; - string HA1 = null; - string HA2 = null; - string pass = getPassword(user); - - // Generate H(A1) - - if (algorithm == Rest.Digest_MD5Sess) - { - if (!sktable.ContainsKey(cnonce)) - { - patt = String.Format("{0}:{1}:{2}:{3}:{4}", user, realm, pass, nonce, cnonce); - HA1 = HashToString(patt); - sktable.Add(cnonce, HA1); - } - else - { - HA1 = sktable[cnonce]; - } - } - else - { - patt = String.Format("{0}:{1}:{2}", user, realm, pass); - HA1 = HashToString(patt); - } - - // Generate H(A2) - - if (qop == "auth-int") - { - patt = String.Format("{0}:{1}:{2}", request.HttpMethod, uri, HashToString(payl)); - } - else - { - patt = String.Format("{0}:{1}", request.HttpMethod, uri); - } - - HA2 = HashToString(patt); - - // Generate Digest - - if (qop != String.Empty) - { - patt = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, nonce, nck, cnonce, qop, HA2); - } - else - { - patt = String.Format("{0}:{1}:{2}", HA1, nonce, HA2); - } - - KDS = HashToString(patt); - - // Compare the generated sequence with the original - - return (0 == sc.Compare(KDS, response)); - - } - - private string HashToString(string pattern) - { - - Rest.Log.DebugFormat("{0} Generate <{1}>", MsgId, pattern); - - byte[] hash = md5hash.ComputeHash(encoding.GetBytes(pattern)); - - sbuilder.Length = 0; - - for (int i = 0; i < hash.Length; i++) - { - sbuilder.Append(hash[i].ToString("x2")); - } - - Rest.Log.DebugFormat("{0} Hash = <{1}>", MsgId, sbuilder.ToString()); - - return sbuilder.ToString(); - - } - -#endregion authentication_digest - -#region service_interface - - /// - /// Conditionally set a normal completion code. This allows a normal - /// execution path to default. - /// - - internal void Complete() - { - if (statusCode == 0) - { - statusCode = Rest.HttpStatusCodeOK; - } - } - - /// - /// Indicate a functionally-dependent conclusion to the - /// request. See Rest.cs for a list of possible values. - /// - - internal void Complete(int code) - { - statusCode = code; - } - - /// - /// Indicate that a request should be redirected, using - /// the HTTP completion codes. Permanent and temporary - /// redirections may be indicated. The supplied URL is - /// the new location of the resource. - /// - - internal void Redirect(string Url, bool temp) - { - - redirectLocation = Url; - - if (temp) - { - statusCode = Rest.HttpStatusCodeTemporaryRedirect; - } - else - { - statusCode = Rest.HttpStatusCodePermanentRedirect; - } - - Fail(statusCode, String.Empty, true); - - } - - /// - /// Fail for an arbitrary reason. Just a failure with - /// headers. The supplied message will be returned in the - /// message body. - /// - - internal void Fail(int code) - { - Fail(code, String.Empty, false); - } - - /// - /// For the more adventurous. This failure also includes a - /// specified entity to be appended to the code-related - /// status string. - /// - - internal void Fail(int code, string addendum) - { - Fail(code, addendum, false); - } - - internal void Fail(int code, string addendum, bool reset) - { - - statusCode = code; - appendStatus(String.Format("({0}) : {1}", code, Rest.HttpStatusDesc[code])); - - // Add any final addendum to the status information - - if (addendum != String.Empty) - { - appendStatus(String.Format(addendum)); - } - - // Help us understand why the request is being rejected - - 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); - Rest.Log.DebugFormat("{0} Nonce = {1}", MsgId, nonce); - Rest.Log.DebugFormat("{0} CNonce = {1}", MsgId, cnonce); - Rest.Log.DebugFormat("{0} Opaque = {1}", MsgId, opaque); - Rest.Log.DebugFormat("{0} Stale = {1}", MsgId, stale); - Rest.Log.DebugFormat("{0} Algorithm = {1}", MsgId, algorithm); - Rest.Log.DebugFormat("{0} QOP = {1}", MsgId, qop); - Rest.Log.DebugFormat("{0} AuthPrefix = {1}", MsgId, authPrefix); - Rest.Log.DebugFormat("{0} UserName = {1}", MsgId, userName); - Rest.Log.DebugFormat("{0} UserPass = {1}", MsgId, userPass); - } - - fail = true; - - // Respond to the client's request, tag the response (for the - // benefit of trace) to indicate the reason. - - Respond(String.Format("Failure response: ({0}) : {1} ", - code, Rest.HttpStatusDesc[code])); - - // Finally initialize and the throw a RestException. All of the - // handler's infrastructure knows that this is a "normal" - // completion from a code point-of-view. - - RestException re = new RestException(Rest.HttpStatusDesc[code]+" <"+code+">"); - - re.statusCode = code; - re.statusDesc = Rest.HttpStatusDesc[code]; - re.httpmethod = method; - re.httppath = path; - - throw re; - - } - - // Reject this request - - internal void Reject() - { - Fail(Rest.HttpStatusCodeNotImplemented, "request rejected (not implemented)"); - } - - // This MUST be called by an agent handler before it returns - // control to Handle, otherwise the request will be ignored. - // This is called implciitly for the REST stream handlers and - // is harmless if it is called twice. - - internal virtual bool Respond(string reason) - { - - - Rest.Log.DebugFormat("{0} Respond ENTRY, handled = {1}, reason = {2}", MsgId, handled, reason); - - // We do this to try and make multiple Respond requests harmless, - // as it is sometimes convenient to isse a response without - // certain knowledge that it has not previously been done. - - if (!handled) - { - - Rest.Log.DebugFormat("{0} Generating Response", MsgId); - Rest.Log.DebugFormat("{0} Method is {1}", MsgId, method); - - // A Head request can NOT have a body! So don't waste time on - // formatting if we're going to reject it anyway! - - if (method != Rest.HEAD) - { - - 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); - Rest.Log.DebugFormat("{0} XML Response exists", MsgId); - writer.Flush(); - writer.Close(); - if (!fail) - { - buffer = xmldata.ToArray(); - AddHeader("Content-Type","application/xml"); - } - xmldata.Close(); - Rest.Log.DebugFormat("{0} XML Response encoded", MsgId); - Rest.Log.DebugFormat("{0} XML Response handler extension EXIT", MsgId); - } - - if (buffer == null && body != null) - { - buffer = encoding.GetBytes(body); - AddHeader("Content-Type",bodyType); - } - - // 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); - } - else - { - if (statusBody != String.Empty) - { - statusBody += Rest.statusTail; - buffer = encoding.GetBytes(statusBody); - AddHeader("Content-Type","text/html"); - } - else - { - statusBody = Rest.statusHead; - appendStatus(String.Format(": ({0}) {1}", - statusCode, Rest.HttpStatusDesc[statusCode])); - statusBody += Rest.statusTail; - buffer = encoding.GetBytes(statusBody); - AddHeader("Content-Type","text/html"); - } - } - - response.ContentLength64 = buffer.Length; - - if (response.ContentEncoding == 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. - - if (statusCode == 0) - { - Complete(); - } - - // Set the response code in the actual carrier - - response.StatusCode = statusCode; - - // 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. - - response.StatusDescription = Rest.HttpStatusDesc[response.StatusCode]; - - // Finally we send back our response. - - // We've left the setting of handled' until the - // last minute because the header settings included - // above are pretty harmless. But everything from - // here on down probably leaves the response - // element unusable by anyone else. - - 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}", MsgId, buffer.Length); - // Rest.Log.DebugFormat("{0} Entity buffer, length = {1} : <{2}>", - // MsgId, buffer.Length, encoding.GetString(buffer)); - response.OutputStream.Write(buffer, 0, buffer.Length); - } - - // Closing the outputstream should complete the transmission process - - Rest.Log.DebugFormat("{0} Sending response", MsgId); - // response.OutputStream.Close(); - response.Send(); - - } - - Rest.Log.DebugFormat("{0} Respond EXIT, handled = {1}, reason = {2}", MsgId, handled, reason); - - return handled; - - } - - /// - /// These methods allow a service provider to manipulate the - /// request/response headers. The DumpHeaders method is intended - /// for problem diagnosis. - /// - - internal void AddHeader(string hdr, string data) - { - if (Rest.DEBUG) Rest.Log.DebugFormat("{0} Adding header: <{1}: {2}>", MsgId, hdr, data); - response.AddHeader(hdr, data); - } - - // internal void RemoveHeader(string hdr) - // { - // if (Rest.DEBUG) - // { - // Rest.Log.DebugFormat("{0} Removing header: <{1}>", MsgId, hdr); - // if (response.Headers.Get(hdr) == null) - // { - // Rest.Log.DebugFormat("{0} No such header existed", - // MsgId, hdr); - // } - // } - // response.Headers.Remove(hdr); - // } - - // internal void DumpHeaders() - // { - // if (Rest.DEBUG) - // { - // for (int i=0;i - /// Helper methods for deconstructing and reconstructing - /// URI path data. - /// - - private void initUrl() - { - - uri = request.Url; - - if (query == null) - { - query = uri.Query; - } - - // If the path has not been previously initialized, - // do so now. - - if (path == null) - { - path = uri.AbsolutePath; - if (path.EndsWith(Rest.UrlPathSeparator)) - path = path.Substring(0,path.Length-1); - } - - // If we succeeded in getting a path, perform any - // additional pre-processing required. - - if (path != null) - { - if (Rest.ExtendedEscape) - { - // Handle "+". Not a standard substitution, but - // common enough... - path = path.Replace(Rest.C_PLUS,Rest.C_SPACE); - } - pathNodes = path.Split(Rest.CA_PATHSEP); - } - else - { - 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; - port = uri.Port; - - } - - private int initParameters(int prfxlen) - { - - if (prfxlen < path.Length-1) - { - parameters = path.Substring(prfxlen+1).Split(Rest.CA_PATHSEP); - } - else - { - parameters = new string[0]; - } - - // Generate a debug list of the decoded parameters - - if (Rest.DEBUG && prfxlen < path.Length-1) - { - Rest.Log.DebugFormat("{0} URI: Parameters: {1}", MsgId, path.Substring(prfxlen)); - for (int i = 0; i < parameters.Length; i++) - { - Rest.Log.DebugFormat("{0} Parameter[{1}]: {2}", MsgId, i, parameters[i]); - } - } - - return parameters.Length; - - } - -#endregion internal_methods - - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml b/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml deleted file mode 100644 index 777a2dc..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Resources/RestHandler.addin.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs deleted file mode 100644 index 9755e73..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; -using log4net; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Framework.Communications; -using OpenSim.Services.Interfaces; -using IAvatarService = OpenSim.Services.Interfaces.IAvatarService; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class Rest - { - internal static readonly ILog Log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - internal static bool DEBUG = Log.IsDebugEnabled; - - /// - /// Supported authentication schemes - /// - - public const string AS_BASIC = "Basic"; // simple user/password verification - public const string AS_DIGEST = "Digest"; // password safe authentication - - /// Supported Digest algorithms - - public const string Digest_MD5 = "MD5"; // assumed default if omitted - public const string Digest_MD5Sess = "MD5-sess"; // session-span - not good for REST? - - public const string Qop_Auth = "auth"; // authentication only - public const string Qop_Int = "auth-int"; // TODO - - /// - /// These values have a single value for the whole - /// domain and lifetime of the plugin handler. We - /// make them static for ease of reference within - /// the assembly. These are initialized by the - /// RestHandler class during start-up. - /// - - internal static IRestHandler Plugin = null; - internal static OpenSimBase main = null; - internal static string Prefix = null; - internal static IConfig Config = null; - internal static string GodKey = null; - internal static bool Authenticate = true; - internal static bool Secure = true; - internal static bool ExtendedEscape = true; - internal static bool DumpAsset = false; - internal static bool Fill = true; - internal static bool FlushEnabled = true; - internal static string Realm = "OpenSim REST"; - internal static string Scheme = AS_BASIC; - internal static int DumpLineSize = 32; // Should be a multiple of 16 or (possibly) 4 - - /// - /// These are all dependent upon the Comms manager - /// being initialized. So they have to be properties - /// because the comms manager is now a module and is - /// not guaranteed to be there when the rest handler - /// initializes. - /// - - internal static IInventoryService InventoryServices - { - get { return main.SceneManager.CurrentOrFirstScene.InventoryService; } - } - - internal static IUserAccountService UserServices - { - get { return main.SceneManager.CurrentOrFirstScene.UserAccountService; } - } - - internal static IAuthenticationService AuthServices - { - get { return main.SceneManager.CurrentOrFirstScene.AuthenticationService; } - } - - internal static IAvatarService AvatarServices - { - get { return main.SceneManager.CurrentOrFirstScene.AvatarService; } - } - - internal static IAssetService AssetServices - { - get { return main.SceneManager.CurrentOrFirstScene.AssetService; } - } - - /// - /// HTTP requires that status information be generated for PUT - /// and POST opertaions. This is in support of that. The - /// operation verb gets substituted into the first string, - /// and the completion code is inserted into the tail. The - /// strings are put here to encourage consistency. - /// - - internal static string statusHead = "{0} status"; - internal static string statusTail = ""; - - internal static Dictionary HttpStatusDesc; - - static Rest() - { - HttpStatusDesc = new Dictionary(); - if (HttpStatusCodeArray.Length != HttpStatusDescArray.Length) - { - Log.ErrorFormat("{0} HTTP Status Code and Description arrays do not match"); - throw new Exception("HTTP Status array discrepancy"); - } - - // Repackage the data into something more tractable. The sparse - // nature of HTTP return codes makes an array a bad choice. - - for (int i=0; i - /// Version control for REST implementation. This - /// refers to the overall infrastructure represented - /// by the following classes - /// RequestData - /// RequestInventoryPlugin - /// Rest - /// It does no describe implementation classes such as - /// RestInventoryServices, which may morph much more - /// often. Such classes ARE dependent upon this however - /// and should check it in their Initialize method. - /// - - public static readonly float Version = 1.0F; - public const string Name = "REST 1.0"; - - /// - /// Currently defined HTTP methods. - /// Only GET and HEAD are required to be - /// supported by all servers. See Respond - /// to see how these are handled. - /// - - // REST AGENT 1.0 interpretations - public const string GET = "get"; // information retrieval - server state unchanged - public const string HEAD = "head"; // same as get except only the headers are returned. - public const string POST = "post"; // Replace the URI designated resource with the entity. - public const string PUT = "put"; // Add the entity to the context represented by the URI - public const string DELETE = "delete"; // Remove the URI designated resource from the server. - - public const string OPTIONS = "options"; // - public const string TRACE = "trace"; // - public const string CONNECT = "connect"; // - - // Define this in one place... - - public const string UrlPathSeparator = "/"; - public const string UrlMethodSeparator = ":"; - - // Redirection qualifications - - public const bool PERMANENT = false; - public const bool TEMPORARY = true; - - // Constant arrays used by String.Split - - public static readonly char C_SPACE = ' '; - public static readonly char C_SLASH = '/'; - public static readonly char C_PATHSEP = '/'; - public static readonly char C_COLON = ':'; - public static readonly char C_PLUS = '+'; - public static readonly char C_PERIOD = '.'; - public static readonly char C_COMMA = ','; - public static readonly char C_DQUOTE = '"'; - - public static readonly string CS_SPACE = " "; - public static readonly string CS_SLASH = "/"; - public static readonly string CS_PATHSEP = "/"; - public static readonly string CS_COLON = ":"; - public static readonly string CS_PLUS = "+"; - public static readonly string CS_PERIOD = "."; - public static readonly string CS_COMMA = ","; - public static readonly string CS_DQUOTE = "\""; - - public static readonly char[] CA_SPACE = { C_SPACE }; - public static readonly char[] CA_SLASH = { C_SLASH }; - public static readonly char[] CA_PATHSEP = { C_PATHSEP }; - public static readonly char[] CA_COLON = { C_COLON }; - public static readonly char[] CA_PERIOD = { C_PERIOD }; - public static readonly char[] CA_PLUS = { C_PLUS }; - public static readonly char[] CA_COMMA = { C_COMMA }; - public static readonly char[] CA_DQUOTE = { C_DQUOTE }; - - // HTTP Code Values (in value order) - - public const int HttpStatusCodeContinue = 100; - public const int HttpStatusCodeSwitchingProtocols = 101; - - public const int HttpStatusCodeOK = 200; - public const int HttpStatusCodeCreated = 201; - public const int HttpStatusCodeAccepted = 202; - public const int HttpStatusCodeNonAuthoritative = 203; - public const int HttpStatusCodeNoContent = 204; - public const int HttpStatusCodeResetContent = 205; - public const int HttpStatusCodePartialContent = 206; - - public const int HttpStatusCodeMultipleChoices = 300; - public const int HttpStatusCodePermanentRedirect = 301; - public const int HttpStatusCodeFound = 302; - public const int HttpStatusCodeSeeOther = 303; - public const int HttpStatusCodeNotModified = 304; - public const int HttpStatusCodeUseProxy = 305; - public const int HttpStatusCodeReserved306 = 306; - public const int HttpStatusCodeTemporaryRedirect = 307; - - public const int HttpStatusCodeBadRequest = 400; - public const int HttpStatusCodeNotAuthorized = 401; - public const int HttpStatusCodePaymentRequired = 402; - public const int HttpStatusCodeForbidden = 403; - public const int HttpStatusCodeNotFound = 404; - public const int HttpStatusCodeMethodNotAllowed = 405; - public const int HttpStatusCodeNotAcceptable = 406; - public const int HttpStatusCodeProxyAuthenticate = 407; - public const int HttpStatusCodeTimeOut = 408; - public const int HttpStatusCodeConflict = 409; - public const int HttpStatusCodeGone = 410; - public const int HttpStatusCodeLengthRequired = 411; - public const int HttpStatusCodePreconditionFailed = 412; - public const int HttpStatusCodeEntityTooLarge = 413; - public const int HttpStatusCodeUriTooLarge = 414; - public const int HttpStatusCodeUnsupportedMedia = 415; - public const int HttpStatusCodeRangeNotSatsified = 416; - public const int HttpStatusCodeExpectationFailed = 417; - - public const int HttpStatusCodeServerError = 500; - public const int HttpStatusCodeNotImplemented = 501; - public const int HttpStatusCodeBadGateway = 502; - public const int HttpStatusCodeServiceUnavailable = 503; - public const int HttpStatusCodeGatewayTimeout = 504; - public const int HttpStatusCodeHttpVersionError = 505; - - public static readonly int[] HttpStatusCodeArray = { - HttpStatusCodeContinue, - HttpStatusCodeSwitchingProtocols, - HttpStatusCodeOK, - HttpStatusCodeCreated, - HttpStatusCodeAccepted, - HttpStatusCodeNonAuthoritative, - HttpStatusCodeNoContent, - HttpStatusCodeResetContent, - HttpStatusCodePartialContent, - HttpStatusCodeMultipleChoices, - HttpStatusCodePermanentRedirect, - HttpStatusCodeFound, - HttpStatusCodeSeeOther, - HttpStatusCodeNotModified, - HttpStatusCodeUseProxy, - HttpStatusCodeReserved306, - HttpStatusCodeTemporaryRedirect, - HttpStatusCodeBadRequest, - HttpStatusCodeNotAuthorized, - HttpStatusCodePaymentRequired, - HttpStatusCodeForbidden, - HttpStatusCodeNotFound, - HttpStatusCodeMethodNotAllowed, - HttpStatusCodeNotAcceptable, - HttpStatusCodeProxyAuthenticate, - HttpStatusCodeTimeOut, - HttpStatusCodeConflict, - HttpStatusCodeGone, - HttpStatusCodeLengthRequired, - HttpStatusCodePreconditionFailed, - HttpStatusCodeEntityTooLarge, - HttpStatusCodeUriTooLarge, - HttpStatusCodeUnsupportedMedia, - HttpStatusCodeRangeNotSatsified, - HttpStatusCodeExpectationFailed, - HttpStatusCodeServerError, - HttpStatusCodeNotImplemented, - HttpStatusCodeBadGateway, - HttpStatusCodeServiceUnavailable, - HttpStatusCodeGatewayTimeout, - HttpStatusCodeHttpVersionError - }; - - // HTTP Status Descriptions (in status code order) - // This array must be kept strictly consistent with respect - // to the status code array above. - - public static readonly string[] HttpStatusDescArray = { - "Continue Request", - "Switching Protocols", - "OK", - "CREATED", - "ACCEPTED", - "NON-AUTHORITATIVE INFORMATION", - "NO CONTENT", - "RESET CONTENT", - "PARTIAL CONTENT", - "MULTIPLE CHOICES", - "PERMANENT REDIRECT", - "FOUND", - "SEE OTHER", - "NOT MODIFIED", - "USE PROXY", - "RESERVED CODE 306", - "TEMPORARY REDIRECT", - "BAD REQUEST", - "NOT AUTHORIZED", - "PAYMENT REQUIRED", - "FORBIDDEN", - "NOT FOUND", - "METHOD NOT ALLOWED", - "NOT ACCEPTABLE", - "PROXY AUTHENTICATION REQUIRED", - "TIMEOUT", - "CONFLICT", - "GONE", - "LENGTH REQUIRED", - "PRECONDITION FAILED", - "ENTITY TOO LARGE", - "URI TOO LARGE", - "UNSUPPORTED MEDIA", - "RANGE NOT SATISFIED", - "EXPECTATION FAILED", - "SERVER ERROR", - "NOT IMPLEMENTED", - "BAD GATEWAY", - "SERVICE UNAVAILABLE", - "GATEWAY TIMEOUT", - "HTTP VERSION NOT SUPPORTED" - }; - - // HTTP Headers - - public const string HttpHeaderAccept = "Accept"; - public const string HttpHeaderAcceptCharset = "Accept-Charset"; - public const string HttpHeaderAcceptEncoding = "Accept-Encoding"; - public const string HttpHeaderAcceptLanguage = "Accept-Language"; - public const string HttpHeaderAcceptRanges = "Accept-Ranges"; - public const string HttpHeaderAge = "Age"; - public const string HttpHeaderAllow = "Allow"; - public const string HttpHeaderAuthorization = "Authorization"; - public const string HttpHeaderCacheControl = "Cache-Control"; - public const string HttpHeaderConnection = "Connection"; - public const string HttpHeaderContentEncoding = "Content-Encoding"; - public const string HttpHeaderContentLanguage = "Content-Language"; - public const string HttpHeaderContentLength = "Content-Length"; - public const string HttpHeaderContentLocation = "Content-Location"; - public const string HttpHeaderContentMD5 = "Content-MD5"; - public const string HttpHeaderContentRange = "Content-Range"; - public const string HttpHeaderContentType = "Content-Type"; - public const string HttpHeaderDate = "Date"; - public const string HttpHeaderETag = "ETag"; - public const string HttpHeaderExpect = "Expect"; - public const string HttpHeaderExpires = "Expires"; - public const string HttpHeaderFrom = "From"; - public const string HttpHeaderHost = "Host"; - public const string HttpHeaderIfMatch = "If-Match"; - public const string HttpHeaderIfModifiedSince = "If-Modified-Since"; - public const string HttpHeaderIfNoneMatch = "If-None-Match"; - public const string HttpHeaderIfRange = "If-Range"; - public const string HttpHeaderIfUnmodifiedSince = "If-Unmodified-Since"; - public const string HttpHeaderLastModified = "Last-Modified"; - public const string HttpHeaderLocation = "Location"; - public const string HttpHeaderMaxForwards = "Max-Forwards"; - public const string HttpHeaderPragma = "Pragma"; - public const string HttpHeaderProxyAuthenticate = "Proxy-Authenticate"; - public const string HttpHeaderProxyAuthorization = "Proxy-Authorization"; - public const string HttpHeaderRange = "Range"; - public const string HttpHeaderReferer = "Referer"; - public const string HttpHeaderRetryAfter = "Retry-After"; - public const string HttpHeaderServer = "Server"; - public const string HttpHeaderTE = "TE"; - public const string HttpHeaderTrailer = "Trailer"; - public const string HttpHeaderTransferEncoding = "Transfer-Encoding"; - public const string HttpHeaderUpgrade = "Upgrade"; - public const string HttpHeaderUserAgent = "User-Agent"; - public const string HttpHeaderVary = "Vary"; - public const string HttpHeaderVia = "Via"; - public const string HttpHeaderWarning = "Warning"; - public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate"; - - /// Utility routines - - public static string StringToBase64(string str) - { - try - { - byte[] encData_byte = new byte[str.Length]; - encData_byte = Util.UTF8.GetBytes(str); - return Convert.ToBase64String(encData_byte); - } - catch - { - return String.Empty; - } - } - - public static string Base64ToString(string str) - { - try - { - return Util.Base64ToString(str); - } - catch - { - return String.Empty; - } - } - - private const string hvals = "0123456789abcdef"; - - public static int Hex2Int(string hex) - { - int val = 0; - int sum = 0; - string tmp = null; - - if (hex != null) - { - tmp = hex.ToLower(); - for (int i = 0; i < tmp.Length; i++) - { - val = hvals.IndexOf(tmp[i]); - if (val == -1) - break; - sum *= 16; - sum += val; - } - } - - return sum; - } - - // Nonce management - - public static string NonceGenerator() - { - return StringToBase64(CreationDate + Guid.NewGuid().ToString()); - } - - // Dump the specified data stream - - public static void Dump(byte[] data) - { - char[] buffer = new char[DumpLineSize]; - int cc = 0; - - for (int i = 0; i < data.Length; i++) - { - if (i % DumpLineSize == 0) Console.Write("\n{0}: ",i.ToString("d8")); - - if (i % 4 == 0) Console.Write(" "); - - Console.Write("{0}",data[i].ToString("x2")); - - if (data[i] < 127 && data[i] > 31) - buffer[i % DumpLineSize] = (char) data[i]; - else - buffer[i % DumpLineSize] = '.'; - - cc++; - - if (i != 0 && (i + 1) % DumpLineSize == 0) - { - Console.Write(" |"+(new String(buffer))+"|"); - cc = 0; - } - } - - // Finish off any incomplete line - - if (cc != 0) - { - for (int i = cc ; i < DumpLineSize; i++) - { - if (i % 4 == 0) Console.Write(" "); - Console.Write(" "); - buffer[i % DumpLineSize] = ' '; - } - Console.WriteLine(" |"+(new String(buffer))+"|"); - } - else - { - Console.Write("\n"); - } - } - } - - // Local exception type - - public class RestException : Exception - { - internal int statusCode; - internal string statusDesc; - internal string httpmethod; - internal string httppath; - - public RestException(string msg) : base(msg) - { - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs deleted file mode 100644 index 3cda984..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs +++ /dev/null @@ -1,860 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Xml; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Services.Interfaces; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - - public class RestAppearanceServices : IRest - { -// private static readonly int PARM_USERID = 0; - - // private static readonly int PARM_PATH = 1; - -// private bool enabled = false; - private string qPrefix = "appearance"; - - /// - /// The constructor makes sure that the service prefix is absolute - /// and the registers the service handler and the allocator. - /// - - public RestAppearanceServices() - { - Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // 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)) - { - Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); - } - - // Register interface using the absolute URI. - - Rest.Plugin.AddPathHandler(DoAppearance,qPrefix,Allocate); - - // Activate if everything went OK - -// enabled = true; - - Rest.Log.InfoFormat("{0} User appearance services initialization complete", MsgId); - } - - /// - /// Post-construction, pre-enabled initialization opportunity - /// Not currently exploited. - /// - - public void Initialize() - { - } - - /// - /// Called by the plug-in to halt service processing. Local processing is - /// disabled. - /// - - public void Close() - { -// enabled = false; - Rest.Log.InfoFormat("{0} User appearance services closing down", MsgId); - } - - /// - /// This property is declared locally because it is used a lot and - /// brevity is nice. - /// - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - /// - /// The plugin (RestHandler) calls this method to allocate the request - /// state carrier for a new request. It is destroyed when the request - /// completes. All request-instance specific state is kept here. This - /// is registered when this service provider is registered. - /// - /// Inbound HTTP request information - /// Outbound HTTP request information - /// REST service domain prefix - /// A RequestData instance suitable for this service - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new AppearanceRequestData(request, response, prefix); - } - - /// - /// 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 for a given request. - /// It handles all aspects of inventory REST processing, i.e. /admin/inventory - /// - /// A consolidated HTTP request work area - - private void DoAppearance(RequestData hdata) - { - // !!! REFACTORIMG PROBLEM. This needs rewriting for 0.7 - - //AppearanceRequestData rdata = (AppearanceRequestData) hdata; - - //Rest.Log.DebugFormat("{0} DoAppearance ENTRY", MsgId); - - //// If we're disabled, do nothing. - - //if (!enabled) - //{ - // return; - //} - - //// Now that we know this is a serious attempt to - //// access inventory data, we should find out who - //// is asking, and make sure they are authorized - //// to do so. We need to validate the caller's - //// identity before revealing anything about the - //// status quo. Authenticate throws an exception - //// via Fail if no identity information is present. - //// - //// With the present HTTP server we can't use the - //// builtin authentication mechanisms because they - //// would be enforced for all in-bound requests. - //// Instead we look at the headers ourselves and - //// handle authentication directly. - - //try - //{ - // if (!rdata.IsAuthenticated) - // { - // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); - // } - //} - //catch (RestException e) - //{ - // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - // { - // Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // else - // { - // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // throw (e); - //} - - //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); - - //// We can only get here if we are authorized - //// - //// The requestor may have specified an UUID or - //// a conjoined FirstName LastName string. We'll - //// try both. If we fail with the first, UUID, - //// attempt, we try the other. As an example, the - //// URI for a valid inventory request might be: - //// - //// http://:/admin/inventory/Arthur Dent - //// - //// Indicating that this is an inventory request for - //// an avatar named Arthur Dent. This is ALL that is - //// required to designate a GET for an entire - //// inventory. - //// - - //// Do we have at least a user agent name? - - //if (rdata.Parameters.Length < 1) - //{ - // Rest.Log.WarnFormat("{0} Appearance: No user agent identifier specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); - //} - - //// The first parameter MUST be the agent identification, either an UUID - //// or a space-separated First-name Last-Name specification. We check for - //// an UUID first, if anyone names their character using a valid UUID - //// that identifies another existing avatar will cause this a problem... - - //try - //{ - // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]); - // Rest.Log.DebugFormat("{0} UUID supplied", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); - //} - //catch - //{ - // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); - // if (names.Length == 2) - // { - // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); - // } - // else - // { - // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); - // } - //} - - //// If the user profile 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} User profile obtained for agent {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - //} - //else - //{ - // Rest.Log.WarnFormat("{0} No user profile for {1}", MsgId, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); - //} - - //// If we get to here, then we have effectively validated the user's - - //switch (rdata.method) - //{ - // case Rest.HEAD : // Do the processing, set the status code, suppress entity - // DoGet(rdata); - // rdata.buffer = null; - // break; - - // case Rest.GET : // Do the processing, set the status code, return entity - // DoGet(rdata); - // break; - - // case Rest.PUT : // Update named element - // DoUpdate(rdata); - // break; - - // case Rest.POST : // Add new information to identified context. - // DoExtend(rdata); - // break; - - // case Rest.DELETE : // Delete information - // DoDelete(rdata); - // break; - - // default : - // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", - // MsgId, rdata.method, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, - // String.Format("{0} not supported", rdata.method)); - // break; - //} - } - - #endregion Interface - - #region method-specific processing - - /// - /// This method implements GET processing for user's appearance. - /// - /// HTTP service request work area - -// private void DoGet(AppearanceRequestData rdata) -// { -// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID); -// -// if (adata == null) -// { -// rdata.Fail(Rest.HttpStatusCodeNoContent, -// String.Format("appearance data not found for user {0} {1}", -// rdata.userProfile.FirstName, rdata.userProfile.SurName)); -// } -// rdata.userAppearance = adata.ToAvatarAppearance(rdata.userProfile.ID); -// -// rdata.initXmlWriter(); -// -// FormatUserAppearance(rdata); -// -// // Indicate a successful request -// -// rdata.Complete(); -// -// // Send the response to the user. The body will be implicitly -// // constructed from the result of the XML writer. -// -// rdata.Respond(String.Format("Appearance {0} Normal completion", rdata.method)); -// } - - /// - /// POST adds NEW information to the user profile database. - /// This effectively resets the appearance before applying those - /// characteristics supplied in the request. - /// - -// private void DoExtend(AppearanceRequestData rdata) -// { -// -// bool created = false; -// bool modified = false; -// string newnode = String.Empty; -// -// Rest.Log.DebugFormat("{0} POST ENTRY", MsgId); -// -// //AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID); -// -// rdata.userAppearance = new AvatarAppearance(); -// -// // Although the following behavior is admitted by HTTP I am becoming -// // increasingly doubtful that it is appropriate for REST. If I attempt to -// // add a new record, and it already exists, then it seems to me that the -// // attempt should fail, rather than update the existing record. -// AvatarData adata = null; -// if (GetUserAppearance(rdata)) -// { -// modified = rdata.userAppearance != null; -// created = !modified; -// adata = new AvatarData(rdata.userAppearance); -// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); -// // Rest.UserServices.UpdateUserProfile(rdata.userProfile); -// } -// else -// { -// created = true; -// adata = new AvatarData(rdata.userAppearance); -// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); -// // Rest.UserServices.UpdateUserProfile(rdata.userProfile); -// } -// -// if (created) -// { -// newnode = String.Format("{0} {1}", rdata.userProfile.FirstName, -// rdata.userProfile.SurName); -// // Must include a location header with a URI that identifies the new resource. -// -// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}{3}{4}", -// rdata.hostname,rdata.port,rdata.path,Rest.UrlPathSeparator, newnode)); -// rdata.Complete(Rest.HttpStatusCodeCreated); -// -// } -// else -// { -// if (modified) -// { -// rdata.Complete(Rest.HttpStatusCodeOK); -// } -// else -// { -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// } -// -// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); -// -// } - - /// - /// This updates the user's appearance. not all aspects need to be provided, - /// only those supplied will be changed. - /// - -// private void DoUpdate(AppearanceRequestData rdata) -// { -// -// // REFACTORING PROBLEM This was commented out. It doesn't work for 0.7 -// -// //bool created = false; -// //bool modified = false; -// -// -// //rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID); -// -// //// If the user exists then this is considered a modification regardless -// //// of what may, or may not be, specified in the payload. -// -// //if (rdata.userAppearance != null) -// //{ -// // modified = true; -// // Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance); -// // Rest.UserServices.UpdateUserProfile(rdata.userProfile); -// //} -// -// //if (created) -// //{ -// // rdata.Complete(Rest.HttpStatusCodeCreated); -// //} -// //else -// //{ -// // if (modified) -// // { -// // rdata.Complete(Rest.HttpStatusCodeOK); -// // } -// // else -// // { -// // rdata.Complete(Rest.HttpStatusCodeNoContent); -// // } -// //} -// -// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); -// -// } - - /// - /// Delete the specified user's appearance. This actually performs a reset - /// to the default avatar appearance, if the info is already there. - /// Existing ownership is preserved. All prior updates are lost and can not - /// be recovered. - /// -// private void DoDelete(AppearanceRequestData rdata) -// { -// AvatarData adata = Rest.AvatarServices.GetAvatar(rdata.userProfile.ID); -// -// if (adata != null) -// { -// AvatarAppearance old = adata.ToAvatarAppearance(rdata.userProfile.ID); -// rdata.userAppearance = new AvatarAppearance(); -// rdata.userAppearance.Owner = old.Owner; -// adata = new AvatarData(rdata.userAppearance); -// -// Rest.AvatarServices.SetAvatar(rdata.userProfile.ID, adata); -// -// rdata.Complete(); -// } -// else -// { -// -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// -// rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method)); -// } - -#endregion method-specific processing - - private bool GetUserAppearance(AppearanceRequestData rdata) - { - - XmlReader xml; - bool indata = false; - - rdata.initXmlReader(); - xml = rdata.reader; - - while (xml.Read()) - { - switch (xml.NodeType) - { - case XmlNodeType.Element : - switch (xml.Name) - { - case "Appearance" : - if (xml.MoveToAttribute("Height")) - { - rdata.userAppearance.AvatarHeight = (float) Convert.ToDouble(xml.Value); - indata = true; - } -// if (xml.MoveToAttribute("Owner")) -// { -// rdata.userAppearance.Owner = (UUID)xml.Value; -// indata = true; -// } - if (xml.MoveToAttribute("Serial")) - { - rdata.userAppearance.Serial = Convert.ToInt32(xml.Value); - indata = true; - } - break; -/* - case "Body" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.BodyItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.BodyAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Skin" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.SkinItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.SkinAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Hair" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.HairItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.HairAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Eyes" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.EyesItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.EyesAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Shirt" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.ShirtItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.ShirtAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Pants" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.PantsItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.PantsAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Shoes" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.ShoesItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.ShoesAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Socks" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.SocksItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.SocksAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Jacket" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.JacketItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.JacketAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Gloves" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.GlovesItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.GlovesAsset = (UUID)xml.Value; - indata = true; - } - break; - case "UnderShirt" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.UnderShirtItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.UnderShirtAsset = (UUID)xml.Value; - indata = true; - } - break; - case "UnderPants" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.UnderPantsItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.UnderPantsAsset = (UUID)xml.Value; - indata = true; - } - break; - case "Skirt" : - if (xml.MoveToAttribute("Item")) - { - rdata.userAppearance.SkirtItem = (UUID)xml.Value; - indata = true; - } - if (xml.MoveToAttribute("Asset")) - { - rdata.userAppearance.SkirtAsset = (UUID)xml.Value; - indata = true; - } - break; -*/ - case "Attachment" : - { - - int ap; - UUID asset; - UUID item; - - if (xml.MoveToAttribute("AtPoint")) - { - ap = Convert.ToInt32(xml.Value); - if (xml.MoveToAttribute("Asset")) - { - asset = new UUID(xml.Value); - if (xml.MoveToAttribute("Asset")) - { - item = new UUID(xml.Value); - rdata.userAppearance.SetAttachment(ap, item, asset); - indata = true; - } - } - } - } - break; - case "Texture" : - if (xml.MoveToAttribute("Default")) - { - rdata.userAppearance.Texture = new Primitive.TextureEntry(new UUID(xml.Value)); - indata = true; - } - break; - case "Face" : - { - uint index; - if (xml.MoveToAttribute("Index")) - { - index = Convert.ToUInt32(xml.Value); - if (xml.MoveToAttribute("Id")) - { - rdata.userAppearance.Texture.CreateFace(index).TextureID = new UUID(xml.Value); - indata = true; - } - } - } - break; - case "VisualParameters" : - { - xml.ReadContentAsBase64(rdata.userAppearance.VisualParams, - 0, rdata.userAppearance.VisualParams.Length); - indata = true; - } - break; - } - break; - } - } - - return indata; - - } - - private void FormatPart(AppearanceRequestData rdata, string part, UUID item, UUID asset) - { - if (item != UUID.Zero || asset != UUID.Zero) - { - rdata.writer.WriteStartElement(part); - if (item != UUID.Zero) - { - rdata.writer.WriteAttributeString("Item",item.ToString()); - } - - if (asset != UUID.Zero) - { - rdata.writer.WriteAttributeString("Asset",asset.ToString()); - } - rdata.writer.WriteEndElement(); - } - } - - private void FormatUserAppearance(AppearanceRequestData rdata) - { - - Rest.Log.DebugFormat("{0} FormatUserAppearance", MsgId); - - if (rdata.userAppearance != null) - { - - Rest.Log.DebugFormat("{0} FormatUserAppearance: appearance object exists", MsgId); - rdata.writer.WriteStartElement("Appearance"); - - rdata.writer.WriteAttributeString("Height", rdata.userAppearance.AvatarHeight.ToString()); -// if (rdata.userAppearance.Owner != UUID.Zero) -// rdata.writer.WriteAttributeString("Owner", rdata.userAppearance.Owner.ToString()); - rdata.writer.WriteAttributeString("Serial", rdata.userAppearance.Serial.ToString()); - -/* - FormatPart(rdata, "Body", rdata.userAppearance.BodyItem, rdata.userAppearance.BodyAsset); - FormatPart(rdata, "Skin", rdata.userAppearance.SkinItem, rdata.userAppearance.SkinAsset); - FormatPart(rdata, "Hair", rdata.userAppearance.HairItem, rdata.userAppearance.HairAsset); - FormatPart(rdata, "Eyes", rdata.userAppearance.EyesItem, rdata.userAppearance.EyesAsset); - - FormatPart(rdata, "Shirt", rdata.userAppearance.ShirtItem, rdata.userAppearance.ShirtAsset); - FormatPart(rdata, "Pants", rdata.userAppearance.PantsItem, rdata.userAppearance.PantsAsset); - FormatPart(rdata, "Skirt", rdata.userAppearance.SkirtItem, rdata.userAppearance.SkirtAsset); - FormatPart(rdata, "Shoes", rdata.userAppearance.ShoesItem, rdata.userAppearance.ShoesAsset); - FormatPart(rdata, "Socks", rdata.userAppearance.SocksItem, rdata.userAppearance.SocksAsset); - - FormatPart(rdata, "Jacket", rdata.userAppearance.JacketItem, rdata.userAppearance.JacketAsset); - FormatPart(rdata, "Gloves", rdata.userAppearance.GlovesItem, rdata.userAppearance.GlovesAsset); - - FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset); - FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset); -*/ - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId); - - rdata.writer.WriteStartElement("Attachments"); - List attachments = rdata.userAppearance.GetAttachments(); - foreach (AvatarAttachment attach in attachments) - { - rdata.writer.WriteStartElement("Attachment"); - rdata.writer.WriteAttributeString("AtPoint", attach.AttachPoint.ToString()); - rdata.writer.WriteAttributeString("Item", attach.ItemID.ToString()); - rdata.writer.WriteAttributeString("Asset", attach.AssetID.ToString()); - rdata.writer.WriteEndElement(); - } - rdata.writer.WriteEndElement(); - - Primitive.TextureEntry texture = rdata.userAppearance.Texture; - - if (texture != null && (texture.DefaultTexture != null || texture.FaceTextures != null)) - { - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting textures", MsgId); - - rdata.writer.WriteStartElement("Texture"); - - if (texture.DefaultTexture != null) - { - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting default texture", MsgId); - rdata.writer.WriteAttributeString("Default", - texture.DefaultTexture.TextureID.ToString()); - } - - if (texture.FaceTextures != null) - { - - Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting face textures", MsgId); - - for (int i=0; i - /// These are the inventory specific request/response state - /// extensions. - /// - - internal UUID uuid = UUID.Zero; - internal UserProfileData userProfile = null; - internal AvatarAppearance userAppearance = null; - - internal AppearanceRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - - } - - #endregion Appearance RequestData extension - - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs deleted file mode 100644 index 4ba3d77..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Xml; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestAssetServices : IRest - { - private bool enabled = false; - private string qPrefix = "assets"; - - // A simple constructor is used to handle any once-only - // initialization of working classes. - - public RestAssetServices() - { - Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // 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)) - { - Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix); - } - - // Register interface using the fully-qualified prefix - - Rest.Plugin.AddPathHandler(DoAsset, qPrefix, Allocate); - - // Activate if all went OK - - enabled = true; - - Rest.Log.InfoFormat("{0} Asset services initialization complete", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - Rest.Log.InfoFormat("{0} Asset services ({1}) closing down", MsgId, qPrefix); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new AssetRequestData(request, response, prefix); - } - - // Asset Handler - - private void DoAsset(RequestData rparm) - { - if (!enabled) return; - - AssetRequestData rdata = (AssetRequestData) rparm; - - Rest.Log.DebugFormat("{0} REST Asset handler ({1}) ENTRY", MsgId, qPrefix); - - // Now that we know this is a serious attempt to - // access inventory data, we should find out who - // is asking, and make sure they are authorized - // to do so. We need to validate the caller's - // identity before revealing anything about the - // status quo. Authenticate throws an exception - // via Fail if no identity information is present. - // - // With the present HTTP server we can't use the - // builtin authentication mechanisms because they - // would be enforced for all in-bound requests. - // Instead we look at the headers ourselves and - // handle authentication directly. - - try - { - if (!rdata.IsAuthenticated) - { - rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); - } - } - catch (RestException e) - { - if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - { - Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - else - { - Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - throw (e); - } - - // Remove the prefix and what's left are the parameters. If we don't have - // the parameters we need, fail the request. Parameters do NOT include - // any supplied query values. - - if (rdata.Parameters.Length > 0) - { - switch (rdata.method) - { - case "get" : - DoGet(rdata); - break; - case "put" : - DoPut(rdata); - break; - case "post" : - DoPost(rdata); - break; - case "delete" : - default : - Rest.Log.WarnFormat("{0} Asset: Method not supported: {1}", - MsgId, rdata.method); - rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method)); - break; - } - } - else - { - Rest.Log.WarnFormat("{0} Asset: No agent information provided", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided"); - } - - Rest.Log.DebugFormat("{0} REST Asset handler EXIT", MsgId); - } - - #endregion Interface - - /// - /// The only parameter we recognize is a UUID.If an asset with this identification is - /// found, it's content, base-64 encoded, is returned to the client. - /// - - private void DoGet(AssetRequestData rdata) - { - Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length == 1) - { - UUID uuid = new UUID(rdata.Parameters[0]); - AssetBase asset = Rest.AssetServices.Get(uuid.ToString()); - - if (asset != null) - { - Rest.Log.DebugFormat("{0} Asset located <{1}>", MsgId, rdata.Parameters[0]); - - rdata.initXmlWriter(); - - rdata.writer.WriteStartElement(String.Empty,"Asset",String.Empty); - - rdata.writer.WriteAttributeString("id", asset.ID); - rdata.writer.WriteAttributeString("name", asset.Name); - rdata.writer.WriteAttributeString("desc", asset.Description); - rdata.writer.WriteAttributeString("type", asset.Type.ToString()); - rdata.writer.WriteAttributeString("local", asset.Local.ToString()); - rdata.writer.WriteAttributeString("temporary", asset.Temporary.ToString()); - - rdata.writer.WriteBase64(asset.Data,0,asset.Data.Length); - - rdata.writer.WriteFullEndElement(); - - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - } - - rdata.Complete(); - rdata.Respond(String.Format("Asset <{0}> : Normal completion", rdata.method)); - - } - - /// - /// UPDATE existing item, if it exists. URI identifies the item in question. - /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded) - /// is decoded and stored in the database, identified by the supplied UUID. - /// - private void DoPut(AssetRequestData rdata) - { - bool modified = false; - bool created = false; - - AssetBase asset = null; - - Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length == 1) - { - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("Asset")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - UUID uuid = new UUID(rdata.Parameters[0]); - asset = Rest.AssetServices.Get(uuid.ToString()); - - modified = (asset != null); - created = !modified; - - asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString()); - asset.Description = xml.GetAttribute("desc"); - asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0; - asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0; - asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", "")); - - if (asset.ID != rdata.Parameters[0]) - { - Rest.Log.WarnFormat("{0} URI and payload disagree on UUID U:{1} vs P:{2}", - MsgId, rdata.Parameters[0], asset.ID); - } - - Rest.AssetServices.Store(asset); - - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method)); - - } - - ///

- /// CREATE new item, replace if it exists. URI identifies the context for the item in question. - /// No parameters are required for POST, just thepayload. - /// - - private void DoPost(AssetRequestData rdata) - { - - bool modified = false; - bool created = false; - - Rest.Log.DebugFormat("{0} REST Asset handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length != 0) - { - Rest.Log.WarnFormat("{0} Parameters ignored <{1}>", MsgId, rdata.path); - Rest.Log.InfoFormat("{0} POST of an asset has no parameters", MsgId, rdata.path); - } - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("Asset")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - UUID uuid = new UUID(xml.GetAttribute("id")); - AssetBase asset = Rest.AssetServices.Get(uuid.ToString()); - - modified = (asset != null); - created = !modified; - - asset = new AssetBase(uuid, xml.GetAttribute("name"), SByte.Parse(xml.GetAttribute("type")), UUID.Zero.ToString()); - asset.Description = xml.GetAttribute("desc"); - asset.Local = Int32.Parse(xml.GetAttribute("local")) != 0; - asset.Temporary = Int32.Parse(xml.GetAttribute("temporary")) != 0; - asset.Data = Convert.FromBase64String(xml.ReadElementContentAsString("Asset", "")); - - Rest.AssetServices.Store(asset); - - if (created) - { - rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified asset {0}, UUID {1}

", asset.Name, asset.FullID)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("Asset {0} : Normal completion", rdata.method)); - - } - - ///

- /// Asset processing has no special data area requirements. - /// - - internal class AssetRequestData : RequestData - { - internal AssetRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs deleted file mode 100644 index e79d2bd..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestFileServices.cs +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Xml; -using System.IO; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestFileServices : IRest - { - private bool enabled = false; - private string qPrefix = "files"; - - // A simple constructor is used to handle any once-only - // initialization of working classes. - - public RestFileServices() - { - Rest.Log.InfoFormat("{0} File services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // 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)) - { - Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix); - } - - // Register interface using the fully-qualified prefix - - Rest.Plugin.AddPathHandler(DoFile, qPrefix, Allocate); - - // Activate if all went OK - - enabled = true; - - Rest.Log.InfoFormat("{0} File services initialization complete", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - Rest.Log.InfoFormat("{0} File services ({1}) closing down", MsgId, qPrefix); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new FileRequestData(request, response, prefix); - } - - // Asset Handler - - private void DoFile(RequestData rparm) - { - if (!enabled) return; - - FileRequestData rdata = (FileRequestData) rparm; - - Rest.Log.DebugFormat("{0} REST File handler ({1}) ENTRY", MsgId, qPrefix); - - // Now that we know this is a serious attempt to - // access file data, we should find out who - // is asking, and make sure they are authorized - // to do so. We need to validate the caller's - // identity before revealing anything about the - // status quo. Authenticate throws an exception - // via Fail if no identity information is present. - // - // With the present HTTP server we can't use the - // builtin authentication mechanisms because they - // would be enforced for all in-bound requests. - // Instead we look at the headers ourselves and - // handle authentication directly. - - try - { - if (!rdata.IsAuthenticated) - { - rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); - } - } - catch (RestException e) - { - if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - { - Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - else - { - Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, - rdata.request.Headers.Get("Authorization")); - } - throw (e); - } - - // Remove the prefix and what's left are the parameters. If we don't have - // the parameters we need, fail the request. Parameters do NOT include - // any supplied query values. - - if (rdata.Parameters.Length > 0) - { - switch (rdata.method) - { - case "get" : - DoGet(rdata); - break; - case "put" : - DoPut(rdata); - break; - case "post" : - DoPost(rdata); - break; - case "delete" : - DoDelete(rdata); - break; - default : - Rest.Log.WarnFormat("{0} File: Method not supported: {1}", - MsgId, rdata.method); - rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method)); - break; - } - } - else - { - Rest.Log.WarnFormat("{0} File: No agent information provided", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided"); - } - - Rest.Log.DebugFormat("{0} REST File handler EXIT", MsgId); - - } - - #endregion Interface - - /// - /// The only parameter we recognize is a UUID.If an asset with this identification is - /// found, it's content, base-64 encoded, is returned to the client. - /// - - private void DoGet(FileRequestData rdata) - { - - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - if (File.Exists(path)) - { - Rest.Log.DebugFormat("{0} File located <{1}>", MsgId, path); - Byte[] data = File.ReadAllBytes(path); - rdata.initXmlWriter(); - rdata.writer.WriteStartElement(String.Empty,"File",String.Empty); - rdata.writer.WriteAttributeString("name", path); - rdata.writer.WriteBase64(data,0,data.Length); - rdata.writer.WriteFullEndElement(); - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, path); - rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0}", path)); - } - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, e.Message); - rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}", - path, e.Message)); - } - } - - rdata.Complete(); - rdata.Respond(String.Format("File <{0}> : Normal completion", rdata.method)); - - } - - /// - /// UPDATE existing item, if it exists. URI identifies the item in question. - /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded) - /// is decoded and stored in the database, identified by the supplied UUID. - /// - private void DoPut(FileRequestData rdata) - { - bool modified = false; - bool created = false; - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - bool maymod = File.Exists(path); - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("File")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", "")); - - File.WriteAllBytes(path,data); - modified = maymod; - created = ! maymod; - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, - e.Message); - } - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); - - } - - ///

- /// CREATE new item, replace if it exists. URI identifies the context for the item in question. - /// No parameters are required for POST, just thepayload. - /// - - private void DoPost(FileRequestData rdata) - { - - bool modified = false; - bool created = false; - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - bool maymod = File.Exists(path); - - rdata.initXmlReader(); - XmlReader xml = rdata.reader; - - if (!xml.ReadToFollowing("File")) - { - Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data"); - } - - Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", "")); - - File.WriteAllBytes(path,data); - modified = maymod; - created = ! maymod; - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, - e.Message); - } - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); - - } - - ///

- /// CREATE new item, replace if it exists. URI identifies the context for the item in question. - /// No parameters are required for POST, just thepayload. - /// - - private void DoDelete(FileRequestData rdata) - { - - bool modified = false; - bool created = false; - string path = String.Empty; - - Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method); - - if (rdata.Parameters.Length > 1) - { - try - { - path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2); - - if (File.Exists(path)) - { - File.Delete(path); - } - } - catch (Exception e) - { - Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId, - e.Message); - rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}", - path, e.Message)); - } - } - else - { - Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters"); - } - - if (created) - { - rdata.appendStatus(String.Format("

Created file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeCreated); - } - else - { - if (modified) - { - rdata.appendStatus(String.Format("

Modified file {0}

", path)); - rdata.Complete(Rest.HttpStatusCodeOK); - } - else - { - rdata.Complete(Rest.HttpStatusCodeNoContent); - } - } - - rdata.Respond(String.Format("File {0} : Normal completion", rdata.method)); - - } - - ///

- /// File processing has no special data area requirements. - /// - - internal class FileRequestData : RequestData - { - internal FileRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs deleted file mode 100644 index 072bd6f..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs +++ /dev/null @@ -1,662 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Reflection; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - /// - /// The class signature reveals the roles that RestHandler plays. - /// - /// [1] It is a sub-class of RestPlugin. It inherits and extends - /// the functionality of this class, constraining it to the - /// specific needs of this REST implementation. This relates - /// to the plug-in mechanism supported by OpenSim, the specifics - /// of which are mostly hidden by RestPlugin. - /// [2] IRestHandler describes the interface that this class - /// exports to service implementations. This is the services - /// management interface. - /// [3] IHttpAgentHandler describes the interface that is exported - /// to the BaseHttpServer in support of this particular HTTP - /// processing model. This is the request interface of the - /// handler. - /// - - public class RestHandler : RestPlugin, IRestHandler, IHttpAgentHandler - { - // Handler tables: both stream and REST are supported. The path handlers and their - // respective allocators are stored in separate tables. - - internal Dictionary pathHandlers = new Dictionary(); - internal Dictionary pathAllocators = new Dictionary(); - internal Dictionary streamHandlers = new Dictionary(); - - #region local static state - - private static bool handlersLoaded = false; - private static List classes = new List(); - private static List handlers = new List(); - private static Type[] parms = new Type[0]; - private static Object[] args = new Object[0]; - - /// - /// This static initializer scans the ASSEMBLY for classes that - /// 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 - /// the IRest interface, and recompile the handler. This gives - /// 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. Examples of services classes are RestInventoryServices - /// and RestSkeleton. - /// - - static RestHandler() - { - Module[] mods = Assembly.GetExecutingAssembly().GetModules(); - - foreach (Module m in mods) - { - Type[] types = m.GetTypes(); - foreach (Type t in types) - { - try - { - 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 - - /// - /// This routine loads all of the handlers discovered during - /// 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 isolated within this method. - /// - - private void LoadHandlers() - { - lock (handlers) - { - if (!handlersLoaded) - { - ConstructorInfo ci; - Object ht; - - foreach (Type t in classes) - { - 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; - } - } - } - - #endregion local instance state - - #region overriding properties - - // 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 .ini configuration space. - - public override string ConfigName - { - get { return "RestHandler"; } - } - - // We have to rename these because we want - // to be able to share the values with other - // classes in our assembly and the base - // names are protected. - - public string MsgId - { - get { return base.MsgID; } - } - - public string RequestId - { - get { return base.RequestID; } - } - - #endregion overriding properties - - #region overriding methods - - /// - /// This method is called by OpenSimMain immediately after loading the - /// plugin and after basic server setup, but before running any server commands. - /// - /// - /// Note that entries MUST be added to the active configuration files before - /// the plugin can be enabled. - /// - - public override void Initialise(OpenSimBase openSim) - { - try - { - // This plugin will only be enabled if the broader - // REST plugin mechanism is enabled. - - //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); - return; - } - - Rest.Log.InfoFormat("{0} Rest <{1}> plugin will be enabled", MsgId, Name); - Rest.Log.InfoFormat("{0} Configuration parameters read from <{1}>", MsgId, ConfigName); - - // These are stored in static variables to make - // them easy to reach from anywhere in the assembly. - - Rest.main = openSim; - if (Rest.main == null) - throw new Exception("OpenSim base pointer is null"); - - Rest.Plugin = this; - Rest.Config = Config; - Rest.Prefix = Prefix; - Rest.GodKey = GodKey; - Rest.Authenticate = Rest.Config.GetBoolean("authenticate", Rest.Authenticate); - Rest.Scheme = Rest.Config.GetString("auth-scheme", Rest.Scheme); - Rest.Secure = Rest.Config.GetBoolean("secured", Rest.Secure); - Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape", Rest.ExtendedEscape); - Rest.Realm = Rest.Config.GetString("realm", Rest.Realm); - Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset", Rest.DumpAsset); - Rest.Fill = Rest.Config.GetBoolean("path-fill", Rest.Fill); - Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size", Rest.DumpLineSize); - Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error", Rest.FlushEnabled); - - // Note: Odd spacing is required in the following strings - - Rest.Log.InfoFormat("{0} Authentication is {1}required", MsgId, - (Rest.Authenticate ? "" : "not ")); - - Rest.Log.InfoFormat("{0} Security is {1}enabled", MsgId, - (Rest.Secure ? "" : "not ")); - - Rest.Log.InfoFormat("{0} Extended URI escape processing is {1}enabled", MsgId, - (Rest.ExtendedEscape ? "" : "not ")); - - Rest.Log.InfoFormat("{0} Dumping of asset data is {1}enabled", MsgId, - (Rest.DumpAsset ? "" : "not ")); - - // The supplied prefix MUST be absolute - - if (Rest.Prefix.Substring(0,1) != Rest.UrlPathSeparator) - { - Rest.Log.WarnFormat("{0} Prefix <{1}> is not absolute and must be", MsgId, Rest.Prefix); - Rest.Log.InfoFormat("{0} Prefix changed to ", MsgId, Rest.Prefix); - Rest.Prefix = String.Format("{0}{1}", Rest.UrlPathSeparator, Rest.Prefix); - } - - // If data dumping is requested, report on the chosen line - // length. - - if (Rest.DumpAsset) - { - Rest.Log.InfoFormat("{0} Dump {1} bytes per line", MsgId, Rest.DumpLineSize); - } - - // Load all of the handlers present in the - // assembly - - // In principle, as we're an application plug-in, - // most of what needs to be done could be done using - // static resources, however the Open Sim plug-in - // model makes this an instance, so that's what we - // need to be. - // There is only one Communications manager per - // server, and by inference, only one each of the - // user, asset, and inventory servers. So we can cache - // those using a static initializer. - // We move all of this processing off to another - // services class to minimize overlap between function - // and infrastructure. - - LoadHandlers(); - - // 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) - { - try - { - handler.Initialize(); - } - catch (Exception e) - { - Rest.Log.ErrorFormat("{0} initialization error: {1}", MsgId, e.Message); - } - } - - // Now that everything is setup we can proceed to - // add THIS agent to the HTTP server's handler list - - // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will - // have to be handled through the AddHttpHandler interface. -// if (!AddAgentHandler(Rest.Name,this)) -// { -// Rest.Log.ErrorFormat("{0} Unable to activate handler interface", MsgId); -// foreach (IRest handler in handlers) -// { -// handler.Close(); -// } -// } - - } - catch (Exception e) - { - Rest.Log.ErrorFormat("{0} Plugin initialization has failed: {1}", MsgId, e.Message); - } - } - - /// - /// In the interests of efficiency, and because we cannot determine whether - /// or not this instance will actually be harvested, we clobber the only - /// anchoring reference to the working state for this plug-in. What the - /// call to close does is irrelevant to this class beyond knowing that it - /// can nullify the reference when it returns. - /// To make sure everything is copacetic we make sure the primary interface - /// is disabled by deleting the handler from the HTTP server tables. - /// - - public override void Close() - { - Rest.Log.InfoFormat("{0} Plugin is terminating", MsgId); - - // FIXME: If this code is ever to be re-enabled (most of it is disabled already) then this will - // have to be handled through the AddHttpHandler interface. -// try -// { -// RemoveAgentHandler(Rest.Name, this); -// } -// catch (KeyNotFoundException){} - - foreach (IRest handler in handlers) - { - handler.Close(); - } - } - - #endregion overriding methods - - #region interface methods - - /// - /// 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. - /// Note: The match is case-insensitive. - /// - - public bool Match(OSHttpRequest request, OSHttpResponse response) - { - - string path = request.RawUrl.ToLower(); - - // Rest.Log.DebugFormat("{0} Match ENTRY", MsgId); - - try - { - foreach (string key in pathHandlers.Keys) - { - // Rest.Log.DebugFormat("{0} Match testing {1} against agent prefix <{2}>", MsgId, path, key); - - // Note that Match will not necessarily find the handler that will - // actually be used - it does no test for the "closest" fit. It - // simply reflects that at least one possible handler exists. - - if (path.StartsWith(key)) - { - // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); - - // This apparently odd evaluation is needed to prevent a match - // on anything other than a URI token boundary. Otherwise we - // may match on URL's that were not intended for this handler. - - 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) - { - // Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key); - - // Note that Match will not necessarily find the handler that will - // actually be used - it does no test for the "closest" fit. It - // simply reflects that at least one possible handler exists. - - if (path.StartsWith(key)) - { - // Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, key); - - // This apparently odd evaluation is needed to prevent a match - // on anything other than a URI token boundary. Otherwise we - // may match on URL's that were not intended for this handler. - - return (path.Length == key.Length || - path.Substring(key.Length, 1) == Rest.UrlPathSeparator); - } - } - } - catch (Exception e) - { - Rest.Log.ErrorFormat("{0} matching exception for path <{1}> : {2}", MsgId, path, e.Message); - } - - return false; - } - - /// - /// This is called by the HTTP server once the handler has indicated - /// that it is able to handle the request. - /// Preconditions: - /// [1] request != null and is a valid request object - /// [2] response != null and is a valid response object - /// Behavior is undefined if preconditions are not satisfied. - /// - - public bool Handle(OSHttpRequest request, OSHttpResponse response) - { - bool handled; - base.MsgID = base.RequestID; - - // Debug only - - if (Rest.DEBUG) - { - Rest.Log.DebugFormat("{0} ENTRY", MsgId); - Rest.Log.DebugFormat("{0} Agent: {1}", MsgId, request.UserAgent); - Rest.Log.DebugFormat("{0} Method: {1}", MsgId, request.HttpMethod); - - for (int i = 0; i < request.Headers.Count; i++) - { - Rest.Log.DebugFormat("{0} Header [{1}] : <{2}> = <{3}>", - MsgId, i, request.Headers.GetKey(i), request.Headers.Get(i)); - } - Rest.Log.DebugFormat("{0} URI: {1}", MsgId, request.RawUrl); - } - - // If a path handler worked we're done, otherwise try any - // available stream handlers too. - - try - { - handled = (FindPathHandler(request, response) || - FindStreamHandler(request, response)); - } - catch (Exception e) - { - // A raw exception indicates that something we weren't expecting has - // happened. This should always reflect a shortcoming in the plugin, - // or a failure to satisfy the preconditions. It should not reflect - // an error in the request itself. Under such circumstances the state - // of the request cannot be determined and we are obliged to mark it - // as 'handled'. - - Rest.Log.ErrorFormat("{0} Plugin error: {1}", MsgId, e.Message); - handled = true; - } - - Rest.Log.DebugFormat("{0} EXIT", MsgId); - - return handled; - } - - #endregion interface methods - - /// - /// If there is a stream handler registered that can handle the - /// request, then fine. If the request is not matched, do - /// nothing. - /// Note: The selection is case-insensitive - /// - - private bool FindStreamHandler(OSHttpRequest request, OSHttpResponse response) - { - RequestData rdata = new RequestData(request, response, String.Empty); - - string bestMatch = String.Empty; - string path = String.Format("{0}:{1}", rdata.method, rdata.path).ToLower(); - - 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)) - { - if (pattern.Length > bestMatch.Length) - { - bestMatch = pattern; - } - } - } - - // Handle using the best match available - - if (bestMatch.Length > 0) - { - Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch); - RestStreamHandler handler = streamHandlers[bestMatch]; - rdata.buffer = handler.Handle(rdata.path, rdata.request.InputStream, rdata.request, rdata.response); - rdata.AddHeader(rdata.response.ContentType,handler.ContentType); - rdata.Respond("FindStreamHandler Completion"); - } - - return rdata.handled; - } - - /// - /// Add a stream handler for the designated HTTP method and path prefix. - /// If the handler is not enabled, the request is ignored. If the path - /// does not start with the REST prefix, it is added. If method-qualified - /// path has not already been registered, the method is added to the active - /// handler table. - /// - public void AddStreamHandler(string httpMethod, string path, RestMethod method) - { - if (!IsEnabled) - { - return; - } - - if (!path.StartsWith(Rest.Prefix)) - { - path = String.Format("{0}{1}", Rest.Prefix, path); - } - - path = String.Format("{0}{1}{2}", httpMethod, Rest.UrlMethodSeparator, path); - - // Conditionally add to the list - - if (!streamHandlers.ContainsKey(path)) - { - streamHandlers.Add(path, new RestStreamHandler(httpMethod, path, method)); - Rest.Log.DebugFormat("{0} Added handler for {1}", MsgId, path); - } - else - { - Rest.Log.WarnFormat("{0} Ignoring duplicate handler for {1}", MsgId, path); - } - } - - /// - /// Given the supplied request/response, if the handler is enabled, the inbound - /// information is used to match an entry in the active path handler tables, using - /// the method-qualified path information. If a match is found, then the handler is - /// invoked. The result is the boolean result of the handler, or false if no - /// handler was located. The boolean indicates whether or not the request has been - /// handled, not whether or not the request was successful - that information is in - /// the response. - /// Note: The selection process is case-insensitive - /// - - internal bool FindPathHandler(OSHttpRequest request, OSHttpResponse response) - { - RequestData rdata = null; - string bestMatch = null; - - if (!IsEnabled) - { - return false; - } - - // Conditionally add to the list - - Rest.Log.DebugFormat("{0} Checking for path handler for <{1}>", MsgId, request.RawUrl); - - foreach (string pattern in pathHandlers.Keys) - { - if (request.RawUrl.ToLower().StartsWith(pattern)) - { - if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) - { - bestMatch = pattern; - } - } - } - - if (!String.IsNullOrEmpty(bestMatch)) - { - rdata = pathAllocators[bestMatch](request, response, bestMatch); - - Rest.Log.DebugFormat("{0} Path based REST handler matched with <{1}>", MsgId, bestMatch); - - try - { - pathHandlers[bestMatch](rdata); - } - - // A plugin generated error indicates a request-related error - // that has been handled by the plugin. - - catch (RestException r) - { - Rest.Log.WarnFormat("{0} Request failed: {1}", MsgId, r.Message); - } - } - - return (rdata == null) ? false : rdata.handled; - } - - /// - /// 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. - /// - - public 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); - pathHandlers.Remove(path); - } - - if (pathAllocators.ContainsKey(path)) - { - Rest.Log.DebugFormat("{0} Replacing allocator for <${1}>", MsgId, path); - pathAllocators.Remove(path); - } - - Rest.Log.DebugFormat("{0} Adding path handler for {1}", MsgId, path); - - pathHandlers.Add(path, mh); - pathAllocators.Add(path, ra); - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs deleted file mode 100644 index 536f167..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs +++ /dev/null @@ -1,2343 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.Threading; -using System.Timers; -using System.Xml; -using OpenMetaverse; -using OpenMetaverse.Imaging; -using OpenSim.Framework; - -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using Timer=System.Timers.Timer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestInventoryServices : IRest - { -// private static readonly int PARM_USERID = 0; -// private static readonly int PARM_PATH = 1; - -// private bool enabled = false; - private string qPrefix = "inventory"; - -// private static readonly string PRIVATE_ROOT_NAME = "My Inventory"; - - /// - /// The constructor makes sure that the service prefix is absolute - /// and the registers the service handler and the allocator. - /// - - public RestInventoryServices() - { - Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // 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)) - { - Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); - } - - // Register interface using the absolute URI. - - Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate); - - // Activate if everything went OK - -// enabled = true; - - Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId); - } - - /// - /// Post-construction, pre-enabled initialization opportunity - /// Not currently exploited. - /// - - public void Initialize() - { - } - - /// - /// Called by the plug-in to halt service processing. Local processing is - /// disabled. - /// - - public void Close() - { -// enabled = false; - Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId); - } - - /// - /// This property is declared locally because it is used a lot and - /// brevity is nice. - /// - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - /// - /// The plugin (RestHandler) calls this method to allocate the request - /// state carrier for a new request. It is destroyed when the request - /// completes. All request-instance specific state is kept here. This - /// is registered when this service provider is registered. - /// - /// Inbound HTTP request information - /// Outbound HTTP request information - /// REST service domain prefix - /// A RequestData instance suitable for this service - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return (RequestData) new InventoryRequestData(request, response, prefix); - } - - /// - /// 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 for a given request. - /// It handles all aspects of inventory REST processing, i.e. /admin/inventory - /// - /// A consolidated HTTP request work area - private void DoInventory(RequestData hdata) - { -// InventoryRequestData rdata = (InventoryRequestData) hdata; - - Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId); - - // !!! REFACTORING PROBLEM - - //// If we're disabled, do nothing. - - //if (!enabled) - //{ - // return; - //} - - //// Now that we know this is a serious attempt to - //// access inventory data, we should find out who - //// is asking, and make sure they are authorized - //// to do so. We need to validate the caller's - //// identity before revealing anything about the - //// status quo. Authenticate throws an exception - //// via Fail if no identity information is present. - //// - //// With the present HTTP server we can't use the - //// builtin authentication mechanisms because they - //// would be enforced for all in-bound requests. - //// Instead we look at the headers ourselves and - //// handle authentication directly. - - //try - //{ - // if (!rdata.IsAuthenticated) - // { - // rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); - // } - //} - //catch (RestException e) - //{ - // if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - // { - // Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // else - // { - // Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - // Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - // } - // throw (e); - //} - - //Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName); - - //// We can only get here if we are authorized - //// - //// The requestor may have specified an UUID or - //// a conjoined FirstName LastName string. We'll - //// try both. If we fail with the first, UUID, - //// attempt, we try the other. As an example, the - //// URI for a valid inventory request might be: - //// - //// http://:/admin/inventory/Arthur Dent - //// - //// Indicating that this is an inventory request for - //// an avatar named Arthur Dent. This is ALL that is - //// required to designate a GET for an entire - //// inventory. - //// - - - //// Do we have at least a user agent name? - - //if (rdata.Parameters.Length < 1) - //{ - // Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified"); - //} - - //// The first parameter MUST be the agent identification, either an UUID - //// or a space-separated First-name Last-Name specification. We check for - //// an UUID first, if anyone names their character using a valid UUID - //// that identifies another existing avatar will cause this a problem... - - //try - //{ - // rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]); - // Rest.Log.DebugFormat("{0} UUID supplied", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid); - //} - //catch - //{ - // string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE); - // if (names.Length == 2) - // { - // Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId); - // rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]); - // } - // else - // { - // Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId); - // rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity"); - // } - //} - - //// If the user profile 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}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - //} - //else - //{ - // Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity"); - //} - - //// 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. - //// - - //rdata.uuid = rdata.userProfile.ID; - - //if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid)) - //{ - // rdata.root = Rest.InventoryServices.GetRootFolder(rdata.uuid); - - // Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - - // Rest.InventoryServices.GetUserInventory(rdata.uuid, rdata.GetUserInventory); - - // Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - - // lock (rdata) - // { - // if (!rdata.HaveInventory) - // { - // rdata.startWD(1000); - // rdata.timeout = false; - // Monitor.Wait(rdata); - // } - // } - - // if (rdata.timeout) - // { - // Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding"); - // } - - // if (rdata.root == null) - // { - // Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - // rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed"); - // } - - //} - //else - //{ - // Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}", - // MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - // rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user"); - //} - - //// If we get here, then we have successfully retrieved the user's information - //// and inventory information is now available locally. - - //switch (rdata.method) - //{ - // case Rest.HEAD : // Do the processing, set the status code, suppress entity - // DoGet(rdata); - // rdata.buffer = null; - // break; - - // case Rest.GET : // Do the processing, set the status code, return entity - // DoGet(rdata); - // break; - - // case Rest.PUT : // Update named element - // DoUpdate(rdata); - // break; - - // case Rest.POST : // Add new information to identified context. - // DoExtend(rdata); - // break; - - // case Rest.DELETE : // Delete information - // DoDelete(rdata); - // break; - - // default : - // Rest.Log.WarnFormat("{0} Method {1} not supported for {2}", - // MsgId, rdata.method, rdata.path); - // rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, - // String.Format("{0} not supported", rdata.method)); - // break; - //} - } - - #endregion Interface - - #region method-specific processing - - /// - /// This method implements GET processing for inventory. - /// Any remaining parameters are used to locate the - /// corresponding subtree based upon node name. - /// - /// HTTP service request work area -// private void DoGet(InventoryRequestData rdata) -// { -// rdata.initXmlWriter(); -// -// rdata.writer.WriteStartElement(String.Empty,"Inventory",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. -// -// traverse(rdata, rdata.root, PARM_PATH); -// -// // Close all open elements -// -// rdata.writer.WriteFullEndElement(); -// -// // Indicate a successful request -// -// rdata.Complete(); -// -// // Send the response to the user. The body will be implicitly -// // constructed from the result of the XML writer. -// -// rdata.Respond(String.Format("Inventory {0} Normal completion", rdata.method)); -// } - - /// - /// In the case of the inventory, and probably in general, - /// the distinction between PUT and POST is not always - /// easy to discern. The standard is badly worded in places, - /// and adding a node to a hierarchy can be viewed as - /// an addition, or as a modification to the inventory as - /// a whole. This is exacerbated by an unjustified lack of - /// consistency across different implementations. - /// - /// For OpenSim PUT is an update and POST is an addition. This - /// is the behavior required by the HTTP specification and - /// therefore as required by REST. - /// - /// The best way to explain the distinction is to - /// consider the relationship between the URI and the - /// enclosed entity. For PUT, the URI identifies the - /// actual entity to be modified or replaced, i.e. the - /// enclosed entity. - /// - /// If the operation is POST,then the URI describes the - /// context into which the new entity will be added. - /// - /// As an example, suppose the URI contains: - /// /admin/inventory/Clothing - /// - /// A PUT request will normally result in some modification of - /// the folder or item named "Clothing". Whereas a POST - /// request will normally add some new information into the - /// content identified by Clothing. It follows from this - /// that for POST, the element identified by the URI MUST - /// be a folder. - /// - - /// - /// POST adds new information to the inventory in the - /// context identified by the URI. - /// - /// HTTP service request work area -// private void DoExtend(InventoryRequestData rdata) -// { -// bool created = false; -// bool modified = false; -// string newnode = String.Empty; -// -// // Resolve the context node specified in the URI. Entity -// // data will be ADDED beneath this node. rdata already contains -// // information about the current content of the user's -// // inventory. -// -// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); -// -// // Processing depends upon the type of inventory node -// // identified in the URI. This is the CONTEXT for the -// // change. We either got a context or we threw an -// // exception. -// -// // It follows that we can only add information if the URI -// // has identified a folder. So only a type of folder is supported -// // in this case. -// -// if (typeof(InventoryFolderBase) == InventoryNode.GetType() || -// typeof(InventoryFolderImpl) == InventoryNode.GetType()) -// { -// // Cast the context node appropriately. -// -// InventoryFolderBase context = (InventoryFolderBase) InventoryNode; -// -// Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}", -// MsgId, rdata.method, rdata.path); -// -// // 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 POST is a harmless no-operation. -// -// XmlInventoryCollection entity = ReconstituteEntity(rdata); -// -// // 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) -// { -// Rest.Log.DebugFormat("{0} Adding {1} assets to server", -// MsgId, entity.Assets.Count); -// -// foreach (AssetBase asset in entity.Assets) -// { -// Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}", -// MsgId, asset.ID, asset.Type, asset.Name); -// Rest.AssetServices.Store(asset); -// -// created = true; -// rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", -// asset.Name, asset.ID)); -// -// if (Rest.DEBUG && Rest.DumpAsset) -// { -// Rest.Dump(asset.Data); -// } -// } -// } -// -// // Modify the context using the collection of folders and items -// // returned in the XmlInventoryCollection. -// -// foreach (InventoryFolderBase folder in entity.Folders) -// { -// InventoryFolderBase found; -// -// // 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 == UUID.Zero || folder.ParentID == context.ID) -// { -// if (newnode != String.Empty) -// { -// Rest.Log.DebugFormat("{0} Too many resources", MsgId); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed"); -// } -// folder.ParentID = context.ID; -// newnode = folder.Name; -// } -// -// // Search the existing inventory for an existing entry. If -// // 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 -// // in which case an update is most certainly necessary. -// -// found = null; -// -// foreach (InventoryFolderBase xf in rdata.folders) -// { -// // Compare identifying attribute -// if (xf.ID == folder.ID) -// { -// found = xf; -// break; -// } -// } -// -// if (found != null && FolderHasChanged(folder,found)) -// { -// Rest.Log.DebugFormat("{0} Updating existing folder", MsgId); -// Rest.InventoryServices.MoveFolder(folder); -// -// modified = true; -// rdata.appendStatus(String.Format("

Created folder {0}, UUID {1}

", -// folder.Name, folder.ID)); -// } -// else -// { -// Rest.Log.DebugFormat("{0} Adding new folder", MsgId); -// Rest.InventoryServices.AddFolder(folder); -// -// created = true; -// rdata.appendStatus(String.Format("

Modified folder {0}, UUID {1}

", -// folder.Name, folder.ID)); -// } -// } -// -// // Now we repeat a similar process for the items included -// // in the entity. -// -// foreach (InventoryItemBase item in entity.Items) -// { -// InventoryItemBase found = null; -// -// // If the parentID is zero, then this is going -// // directly into the root identified by the URI. -// -// if (item.Folder == UUID.Zero) -// { -// item.Folder = context.ID; -// } -// -// // Determine whether this is a new item or a -// // replacement definition. -// -// foreach (InventoryItemBase xi in rdata.items) -// { -// // Compare identifying attribute -// if (xi.ID == item.ID) -// { -// found = xi; -// break; -// } -// } -// -// if (found != null && ItemHasChanged(item, found)) -// { -// Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}", -// MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); -// Rest.InventoryServices.UpdateItem(item); -// modified = true; -// rdata.appendStatus(String.Format("

Modified item {0}, UUID {1}

", item.Name, item.ID)); -// } -// else -// { -// Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}", -// MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name); -// Rest.InventoryServices.AddItem(item); -// created = true; -// rdata.appendStatus(String.Format("

Created item {0}, UUID {1}

", item.Name, item.ID)); -// } -// } -// -// if (created) -// { -// // Must include a location header with a URI that identifies the new resource. -// rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}/{3}", -// rdata.hostname, rdata.port,rdata.path,newnode)); -// rdata.Complete(Rest.HttpStatusCodeCreated); -// } -// else -// { -// if (modified) -// { -// rdata.Complete(Rest.HttpStatusCodeOK); -// } -// else -// { -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// } -// -// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); -// } -// else -// { -// Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}", -// MsgId, rdata.method, rdata.path, InventoryNode.GetType()); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context"); -// } -// } - - ///

- /// PUT updates the URI-identified element in the inventory. This - /// is actually far more flexible than it might at first sound. For - /// PUT the URI serves two purposes: - /// [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 any relative subtree - /// specifications in the entity. If nothing is specified - /// then the whole of the private inventory is implied. - /// Please note that the subtree specified by the URI is only relevant - /// to an entity containing a URI relative specification, i.e. one or - /// more elements do not specify parent folder information. These - /// 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 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. - /// - /// HTTP service request work area -// private void DoUpdate(InventoryRequestData rdata) -// { -// int count = 0; -// bool created = false; -// bool modified = false; -// -// // Resolve the inventory node that is to be modified. -// // rdata already contains information about the current -// // content of the user's inventory. -// -// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill); -// -// // As long as we have a node, then we have something -// // meaningful to do, unlike POST. So we reconstitute the -// // subtree before doing anything else. Note that we -// // etiher got a valid node or we threw an exception. -// -// XmlInventoryCollection entity = ReconstituteEntity(rdata); -// -// // Incorporate any inlined assets first. Any failures -// // will terminate the request. -// -// if (entity.Assets.Count > 0) -// { -// Rest.Log.DebugFormat("{0} Adding {1} assets to server", -// MsgId, entity.Assets.Count); -// -// foreach (AssetBase asset in entity.Assets) -// { -// 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.Store(asset); -// -// created = true; -// rdata.appendStatus(String.Format("

Created asset {0}, UUID {1}

", asset.Name, asset.ID)); -// -// if (Rest.DEBUG && Rest.DumpAsset) -// { -// Rest.Dump(asset.Data); -// } -// } -// } -// -// // The URI specifies either a folder or an item to be updated. -// // -// // The root node in the entity will replace the node identified -// // by the URI. This means the parent will remain the same, but -// // any or all attributes associated with the named element -// // will change. -// // -// // If the inventory collection contains an element with a zero -// // parent ID, then this is taken to be the replacement for the -// // named node. The collection MAY also specify an explicit -// // parent ID, in this case it MAY identify the same parent as -// // the current node, or it MAY specify a different parent, -// // indicating that the folder is being moved in addition to any -// // other modifications being made. -// -// if (typeof(InventoryFolderBase) == InventoryNode.GetType() || -// typeof(InventoryFolderImpl) == InventoryNode.GetType()) -// { -// bool rfound = false; -// InventoryFolderBase uri = (InventoryFolderBase) InventoryNode; -// InventoryFolderBase xml = null; -// -// // If the entity to be replaced resolved to be the root -// // directory itself (My Inventory), then make sure that -// // the supplied data include as appropriately typed and -// // named folder. Note that we can;t rule out the possibility -// // of a sub-directory being called "My Inventory", so that -// // is anticipated. -// -// if (uri == rdata.root) -// { -// foreach (InventoryFolderBase folder in entity.Folders) -// { -// if ((rfound = (folder.Name == PRIVATE_ROOT_NAME))) -// { -// if ((rfound = (folder.ParentID == UUID.Zero))) -// break; -// } -// } -// -// if (!rfound) -// { -// Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure"); -// } -// } -// -// // Scan the set of folders in the entity collection for an -// // 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 -// // ambiguity in this case because this is POST and we are -// // supposed to be modifying a specific node. -// // We assign any element IDs required as an economy; we don't -// // want to iterate over the fodler set again if it can be -// // helped. -// -// foreach (InventoryFolderBase folder in entity.Folders) -// { -// if (folder.ParentID == uri.ParentID || -// folder.ParentID == UUID.Zero) -// { -// folder.ParentID = uri.ParentID; -// xml = folder; -// count++; -// } -// } -// -// // More than one entry is ambiguous. Other folders should be -// // added using the POST verb. -// -// if (count > 1) -// { -// Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous"); -// } -// -// // Exactly one entry means we ARE replacing the node -// // identified by the URI. So we delete the old folder -// // by moving it to the trash and then purging it. -// // We then add all of the folders and items we -// // included in the entity. The subtree has been -// // modified. -// -// if (count == 1) -// { -// InventoryFolderBase TrashCan = GetTrashCan(rdata); -// -// // All went well, so we generate a UUID is one is -// // needed. -// -// if (xml.ID == UUID.Zero) -// { -// xml.ID = UUID.Random(); -// } -// -// uri.ParentID = TrashCan.ID; -// Rest.InventoryServices.MoveFolder(uri); -// Rest.InventoryServices.PurgeFolder(TrashCan); -// modified = true; -// } -// -// // Now, regardelss of what they represent, we -// // integrate all of the elements in the entity. -// -// foreach (InventoryFolderBase f in entity.Folders) -// { -// rdata.appendStatus(String.Format("

Moving folder {0} UUID {1}

", f.Name, f.ID)); -// Rest.InventoryServices.MoveFolder(f); -// } -// -// foreach (InventoryItemBase it in entity.Items) -// { -// rdata.appendStatus(String.Format("

Storing item {0} UUID {1}

", it.Name, it.ID)); -// Rest.InventoryServices.AddItem(it); -// } -// } -// -// ///

-// /// URI specifies an item to be updated -// /// -// /// -// /// The entity must contain a single item node to be -// /// updated. ID and Folder ID must be correct. -// /// -// -// else -// { -// InventoryItemBase uri = (InventoryItemBase) InventoryNode; -// InventoryItemBase xml = null; -// -// if (entity.Folders.Count != 0) -// { -// Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed"); -// } -// -// if (entity.Items.Count > 1) -// { -// Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>", -// MsgId, rdata.method, rdata.path); -// rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items"); -// } -// -// xml = entity.Items[0]; -// -// if (xml.ID == UUID.Zero) -// { -// xml.ID = UUID.Random(); -// } -// -// // If the folder reference has changed, then this item is -// // being moved. Otherwise we'll just delete the old, and -// // add in the new. -// -// // Delete the old item -// -// List uuids = new List(); -// uuids.Add(uri.ID); -// Rest.InventoryServices.DeleteItems(uri.Owner, uuids); -// -// // Add the new item to the inventory -// -// Rest.InventoryServices.AddItem(xml); -// -// rdata.appendStatus(String.Format("

Storing item {0} UUID {1}

", xml.Name, xml.ID)); -// } -// -// if (created) -// { -// rdata.Complete(Rest.HttpStatusCodeCreated); -// } -// else -// { -// if (modified) -// { -// rdata.Complete(Rest.HttpStatusCodeOK); -// } -// else -// { -// rdata.Complete(Rest.HttpStatusCodeNoContent); -// } -// } -// -// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); -// } - - ///

- /// Arguably the most damaging REST interface. It deletes the inventory - /// item or folder identified by the URI. - /// - /// We only process if the URI identified node appears to exist - /// We do not test for success because we either get a context, - /// or an exception is thrown. - /// - /// Folders are deleted by moving them to another folder and then - /// purging that folder. We'll do that by creating a temporary - /// sub-folder in the TrashCan and purging that folder's - /// contents. If we can't can it, we don't delete it... - /// So, if no trashcan is available, the request does nothing. - /// Items are summarily deleted. - /// - /// In the interests of safety, a delete request should normally - /// be performed using UUID, as a name might identify several - /// elements. - /// - /// HTTP service request work area -// private void DoDelete(InventoryRequestData rdata) -// { -// Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false); -// -// if (typeof(InventoryFolderBase) == InventoryNode.GetType() || -// typeof(InventoryFolderImpl) == InventoryNode.GetType()) -// { -// InventoryFolderBase TrashCan = GetTrashCan(rdata); -// -// InventoryFolderBase folder = (InventoryFolderBase) InventoryNode; -// Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted", -// MsgId, rdata.method, rdata.path); -// folder.ParentID = TrashCan.ID; -// Rest.InventoryServices.MoveFolder(folder); -// Rest.InventoryServices.PurgeFolder(TrashCan); -// -// rdata.appendStatus(String.Format("

Deleted folder {0} UUID {1}

", folder.Name, folder.ID)); -// } -// -// // Deleting items is much more straight forward. -// -// else -// { -// InventoryItemBase item = (InventoryItemBase) InventoryNode; -// Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted", -// MsgId, rdata.method, rdata.path); -// List uuids = new List(); -// uuids.Add(item.ID); -// Rest.InventoryServices.DeleteItems(item.Owner, uuids); -// rdata.appendStatus(String.Format("

Deleted item {0} UUID {1}

", item.Name, item.ID)); -// } -// -// rdata.Complete(); -// rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method)); -// } - -#endregion method-specific processing - - ///

- /// This method is called to obtain the OpenSim inventory object identified - /// by the supplied URI. This may be either an Item or a Folder, so a suitably - /// ambiguous return type is employed (Object). This method recurses as - /// necessary to process the designated hierarchy. - /// - /// If we reach the end of the URI then we return the contextual folder to - /// our caller. - /// - /// If we are not yet at the end of the URI we attempt to find a child folder - /// and if we succeed we recurse. - /// - /// If this is the last node, then we look to see if this is an item. If it is, - /// we return that item. - /// - /// If we reach the end of an inventory path and the URI si not yet exhausted, - /// then if 'fill' is specified, we create the intermediate nodes. - /// - /// Otherwise we fail the request on the ground of an invalid URI. - /// - /// An ambiguous request causes the request to fail. - /// - /// - /// HTTP service request work area - /// The folder to be searched (parent) - /// URI parameter index - /// Should missing path members be created? - - private Object getInventoryNode(InventoryRequestData rdata, - InventoryFolderBase folder, - int pi, bool fill) - { - InventoryFolderBase foundf = null; - int fk = 0; - - Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); - - // We have just run off the end of the parameter sequence - - if (pi >= rdata.Parameters.Length) - { - return folder; - } - - // There are more names in the parameter sequence, - // look for the folder named by param[pi] as a - // child of the folder supplied as an argument. - // Note that a UUID may have been supplied as the - // identifier (it is the ONLY guaranteed unambiguous - // option. - - if (rdata.folders != null) - { - foreach (InventoryFolderBase f in rdata.folders) - { - // Look for the present node in the directory list - if (f.ParentID == folder.ID && - (f.Name == rdata.Parameters[pi] || - f.ID.ToString() == rdata.Parameters[pi])) - { - foundf = f; - fk++; - } - } - } - - // If more than one node matched, then the path, as specified - // is ambiguous. - - if (fk > 1) - { - Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", - MsgId, rdata.method, rdata.path); - rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); - } - - // If we find a match, then the method - // increment the parameter index, and calls itself - // passing the found folder as the new context. - - if (foundf != null) - { - return getInventoryNode(rdata, foundf, pi+1, fill); - } - - // No folders that match. Perhaps this parameter identifies an item? If - // it does, then it MUST also be the last name in the sequence. - - if (pi == rdata.Parameters.Length-1) - { - if (rdata.items != null) - { - int k = 0; - InventoryItemBase li = null; - foreach (InventoryItemBase i in rdata.items) - { - if (i.Folder == folder.ID && - (i.Name == rdata.Parameters[pi] || - i.ID.ToString() == rdata.Parameters[pi])) - { - li = i; - k++; - } - } - if (k == 1) - { - return li; - } - else if (k > 1) - { - Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous", - MsgId, rdata.method, rdata.path); - rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous"); - } - } - } - - // If fill is enabled, then we must create the missing intermediate nodes. - // And of course, even this is not straightforward. All intermediate nodes - // are obviously folders, but the last node may be a folder or an item. - - if (fill) - { - } - - // No fill, so abandon the request - - Rest.Log.DebugFormat("{0} {1}: Resource {2} not found", - MsgId, rdata.method, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound, - String.Format("resource {0}:{1} not found", rdata.method, rdata.path)); - - return null; /* Never reached */ - } - - /// - /// This routine traverse the inventory's structure until the end-point identified - /// in the URI is reached, the remainder of the inventory (if any) is then formatted - /// and returned to the requestor. - /// - /// Note that this method is only interested in those folder that match elements of - /// the URI supplied by the requestor, so once a match is fund, the processing does - /// not need to consider any further elements. - /// - /// Only the last element in the URI should identify an item. - /// - /// HTTP service request work area - /// The folder to be searched (parent) - /// URI parameter index - - private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi) - { - Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); - - if (rdata.folders != null) - { - // If there was only one parameter (avatar name), then the entire - // inventory is being requested. - - if (rdata.Parameters.Length == 1) - { - formatInventory(rdata, rdata.root, String.Empty); - } - - // Has the client specified the root directory name explicitly? - // if yes, then we just absorb the reference, because the folder - // we start looking in for a match *is* the root directory. If there - // are more parameters remaining we tarverse, otehrwise it's time - // to format. Otherwise,we consider the "My Inventory" to be implied - // and we just traverse normally. - - else if (folder.ID.ToString() == rdata.Parameters[pi] || - folder.Name == rdata.Parameters[pi]) - { - // Length is -1 because the avatar name is a parameter - if (pi<(rdata.Parameters.Length-1)) - { - traverseInventory(rdata, folder, pi+1); - } - else - { - formatInventory(rdata, folder, String.Empty); - } - } - else - { - traverseInventory(rdata, folder, pi); - } - - return; - } - } - - /// - /// This is the recursive method. I've separated them in this way so that - /// we do not have to waste cycles on any first-case-only processing. - /// - - private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi) - { - int fk = 0; - InventoryFolderBase ffound = null; - InventoryItemBase ifound = null; - - Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi); - - foreach (InventoryFolderBase f in rdata.folders) - { - if (f.ParentID == folder.ID && - (f.Name == rdata.Parameters[pi] || - f.ID.ToString() == rdata.Parameters[pi])) - { - fk++; - ffound = f; - } - } - - // If this is the last element in the parameter sequence, then - // it is reasonable to check for an item. All intermediate nodes - // MUST be folders. - - if (pi == rdata.Parameters.Length-1) - { - // Only if there are any items, and there pretty much always are. - - if (rdata.items != null) - { - foreach (InventoryItemBase i in rdata.items) - { - if (i.Folder == folder.ID && - (i.Name == rdata.Parameters[pi] || - i.ID.ToString() == rdata.Parameters[pi])) - { - fk++; - ifound = i; - } - } - } - } - - if (fk == 1) - { - if (ffound != null) - { - if (pi < rdata.Parameters.Length-1) - { - traverseInventory(rdata, ffound, pi+1); - } - else - { - formatInventory(rdata, ffound, String.Empty); - } - return; - } - else - { - // Fetching an Item has a special significance. In this - // case we also want to fetch the associated asset. - // To make it interesting, we'll do this via redirection. - string asseturl = String.Format("http://{0}:{1}/{2}{3}{4}", rdata.hostname, rdata.port, - "admin/assets",Rest.UrlPathSeparator,ifound.AssetID.ToString()); - rdata.Redirect(asseturl,Rest.PERMANENT); - Rest.Log.DebugFormat("{0} Never Reached", MsgId); - } - } - else if (fk > 1) - { - rdata.Fail(Rest.HttpStatusCodeConflict, - String.Format("ambiguous element ({0}) in path specified: <{1}>", - pi, rdata.path)); - } - - Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>", - MsgId, rdata.path); - rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}", - rdata.Parameters[pi])); - - } - - /// - /// This method generates XML that describes an instance of InventoryFolderBase. - /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem - /// to generate XML for any items encountered along the way. - /// The indentation parameter is solely for the benefit of trace record - /// formatting. - /// - /// HTTP service request work area - /// The folder to be searched (parent) - /// pretty print indentation - private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent) - { - if (Rest.DEBUG) - { - Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}", - MsgId, folder.ID, indent, folder.Name, folder.Type); - indent += "\t"; - } - - // Start folder item - - rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty); - rdata.writer.WriteAttributeString("name",String.Empty,folder.Name); - rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString()); - rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString()); - rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString()); - rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString()); - rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString()); - - if (rdata.folders != null) - { - foreach (InventoryFolderBase f in rdata.folders) - { - if (f.ParentID == folder.ID) - { - formatInventory(rdata, f, indent); - } - } - } - - if (rdata.items != null) - { - foreach (InventoryItemBase i in rdata.items) - { - if (i.Folder == folder.ID) - { - formatItem(rdata, i, indent); - } - } - } - - // End folder item - - rdata.writer.WriteEndElement(); - } - - /// - /// This method generates XML that describes an instance of InventoryItemBase. - /// - /// HTTP service request work area - /// The item to be formatted - /// Pretty print indentation - private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent) - { - Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}", - MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType); - - rdata.writer.WriteStartElement(String.Empty, "Item", String.Empty); - - rdata.writer.WriteAttributeString("name", String.Empty, i.Name); - rdata.writer.WriteAttributeString("desc", String.Empty, i.Description); - rdata.writer.WriteAttributeString("uuid", String.Empty, i.ID.ToString()); - rdata.writer.WriteAttributeString("folder", String.Empty, i.Folder.ToString()); - rdata.writer.WriteAttributeString("owner", String.Empty, i.Owner.ToString()); - rdata.writer.WriteAttributeString("creator", String.Empty, i.CreatorId); - rdata.writer.WriteAttributeString("creatordata", String.Empty, i.CreatorData); - rdata.writer.WriteAttributeString("creationdate", String.Empty, i.CreationDate.ToString()); - rdata.writer.WriteAttributeString("invtype", String.Empty, i.InvType.ToString()); - rdata.writer.WriteAttributeString("assettype", String.Empty, i.AssetType.ToString()); - rdata.writer.WriteAttributeString("groupowned", String.Empty, i.GroupOwned.ToString()); - rdata.writer.WriteAttributeString("groupid", String.Empty, i.GroupID.ToString()); - rdata.writer.WriteAttributeString("saletype", String.Empty, i.SaleType.ToString()); - rdata.writer.WriteAttributeString("saleprice", String.Empty, i.SalePrice.ToString()); - rdata.writer.WriteAttributeString("flags", String.Empty, i.Flags.ToString()); - - rdata.writer.WriteStartElement(String.Empty, "Permissions", String.Empty); - rdata.writer.WriteAttributeString("current", String.Empty, i.CurrentPermissions.ToString("X")); - rdata.writer.WriteAttributeString("next", String.Empty, i.NextPermissions.ToString("X")); - rdata.writer.WriteAttributeString("group", String.Empty, i.GroupPermissions.ToString("X")); - rdata.writer.WriteAttributeString("everyone", String.Empty, i.EveryOnePermissions.ToString("X")); - rdata.writer.WriteAttributeString("base", String.Empty, i.BasePermissions.ToString("X")); - rdata.writer.WriteEndElement(); - - rdata.writer.WriteElementString("Asset", i.AssetID.ToString()); - - rdata.writer.WriteEndElement(); - } - - /// - /// This method creates a "trashcan" folder to support folder and item - /// deletions by this interface. The xisting trash folder is found and - /// this folder is created within it. It is called "tmp" to indicate to - /// the client that it is OK to delete this folder. The REST interface - /// will recreate the folder on an as-required basis. - /// If the trash can cannot be created, then by implication the request - /// that required it cannot be completed, and it fails accordingly. - /// - /// HTTP service request work area - private InventoryFolderBase GetTrashCan(InventoryRequestData rdata) - { - InventoryFolderBase TrashCan = null; - - foreach (InventoryFolderBase f in rdata.folders) - { - if (f.Name == "Trash") - { - foreach (InventoryFolderBase t in rdata.folders) - { - if (t.Name == "tmp") - { - TrashCan = t; - } - } - if (TrashCan == null) - { - TrashCan = new InventoryFolderBase(); - TrashCan.Name = "tmp"; - TrashCan.ID = UUID.Random(); - TrashCan.Version = 1; - TrashCan.Type = (short) AssetType.TrashFolder; - TrashCan.ParentID = f.ID; - TrashCan.Owner = f.Owner; - Rest.InventoryServices.AddFolder(TrashCan); - } - } - } - - if (TrashCan == null) - { - Rest.Log.DebugFormat("{0} No Trash Can available", MsgId); - rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can"); - } - - return TrashCan; - } - - /// - /// Make sure that an unchanged folder is not unnecessarily - /// processed. - /// - /// Folder obtained from enclosed entity - /// Folder obtained from the user's inventory - private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf) - { - return (newf.Name != oldf.Name - || newf.ParentID != oldf.ParentID - || newf.Owner != oldf.Owner - || newf.Type != oldf.Type - || newf.Version != oldf.Version - ); - } - - /// - /// Make sure that an unchanged item is not unnecessarily - /// processed. - /// - /// Item obtained from enclosed entity - /// Item obtained from the user's inventory - private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf) - { - return (newf.Name != oldf.Name - || newf.Folder != oldf.Folder - || newf.Description != oldf.Description - || newf.Owner != oldf.Owner - || newf.CreatorId != oldf.CreatorId - || newf.AssetID != oldf.AssetID - || newf.GroupID != oldf.GroupID - || newf.GroupOwned != oldf.GroupOwned - || newf.InvType != oldf.InvType - || newf.AssetType != oldf.AssetType - ); - } - - /// - /// This method is called by PUT and POST to create an XmlInventoryCollection - /// instance that reflects the content of the entity supplied on the request. - /// Any elements in the completed collection whose UUID is zero, are - /// considered to be located relative to the end-point identified int he - /// URI. In this way, an entire sub-tree can be conveyed in a single REST - /// PUT or POST request. - /// - /// A new instance of XmlInventoryCollection is created and, if the request - /// has an entity, it is more completely initialized. thus, if no entity was - /// provided the collection is valid, but empty. - /// - /// The entity is then scanned and each tag is processed to produce the - /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection - /// will reflect the subtree described by the entity. - /// - /// This is a very flexible mechanism, the entity may contain arbitrary, - /// discontiguous tree fragments, or may contain single element. The caller is - /// responsible for integrating this collection (and ensuring that any - /// missing parent IDs are resolved). - /// - /// HTTP service request work area - internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) - { - Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); - - XmlInventoryCollection ic = new XmlInventoryCollection(); - - if (rdata.request.HasEntityBody) - { - Rest.Log.DebugFormat("{0} Entity present", MsgId); - - ic.init(rdata); - - try - { - while (ic.xml.Read()) - { - switch (ic.xml.NodeType) - { - case XmlNodeType.Element: - Rest.Log.DebugFormat("{0} StartElement: <{1}>", - MsgId, ic.xml.Name); - - switch (ic.xml.Name) - { - case "Folder": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectFolder(ic); - break; - case "Item": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectItem(ic); - break; - case "Asset": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectAsset(ic); - break; - case "Permissions": - Rest.Log.DebugFormat("{0} Processing {1} element", - MsgId, ic.xml.Name); - CollectPermissions(ic); - break; - default: - Rest.Log.DebugFormat("{0} Ignoring {1} element", - MsgId, ic.xml.Name); - break; - } - - // This stinks, but the ReadElement call above not only reads - // the imbedded data, but also consumes the end tag for Asset - // and moves the element pointer on to the containing Item's - // element-end, however, if there was a permissions element - // following, it would get us to the start of that.. - if (ic.xml.NodeType == XmlNodeType.EndElement && - ic.xml.Name == "Item") - { - Validate(ic); - } - break; - - case XmlNodeType.EndElement : - switch (ic.xml.Name) - { - case "Folder": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - ic.Pop(); - break; - case "Item": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - Validate(ic); - break; - case "Asset": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - break; - case "Permissions": - Rest.Log.DebugFormat("{0} Completing {1} element", - MsgId, ic.xml.Name); - break; - default: - Rest.Log.DebugFormat("{0} Ignoring {1} element", - MsgId, ic.xml.Name); - break; - } - break; - - default: - Rest.Log.DebugFormat("{0} Ignoring: <{1}>:<{2}>", - MsgId, ic.xml.NodeType, ic.xml.Value); - break; - } - } - } - catch (XmlException e) - { - Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message); - throw e; - } - catch (Exception e) - { - Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); - throw e; - } - } - else - { - Rest.Log.DebugFormat("{0} Entity absent", MsgId); - } - - if (Rest.DEBUG) - { - Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId); - Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count); - Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count); - Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count); - } - - return ic; - } - - /// - /// This method creates an inventory Folder from the - /// information supplied in the request's entity. - /// A folder instance is created and initialized to reflect - /// default values. These values are then overridden - /// by information supplied in the entity. - /// If context was not explicitly provided, then the - /// appropriate ID values are determined. - /// - - private void CollectFolder(XmlInventoryCollection ic) - { - Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); - - InventoryFolderBase result = new InventoryFolderBase(); - - // Default values - - result.Name = String.Empty; - result.ID = UUID.Zero; - result.Owner = ic.UserID; - result.ParentID = UUID.Zero; // Context - result.Type = (short) AssetType.Folder; - result.Version = 1; - - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - switch (ic.xml.Name) - { - case "name": - result.Name = ic.xml.Value; - break; - case "uuid": - result.ID = new UUID(ic.xml.Value); - break; - case "parent": - result.ParentID = new UUID(ic.xml.Value); - break; - case "owner": - result.Owner = new UUID(ic.xml.Value); - break; - case "type": - result.Type = Int16.Parse(ic.xml.Value); - break; - case "version": - result.Version = UInt16.Parse(ic.xml.Value); - break; - default: - Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", - MsgId, ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>", - ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - - // The client is relying upon the reconstitution process - // to determine the parent's UUID based upon context. This - // is necessary where a new folder may have been - // introduced. - - if (result.ParentID == UUID.Zero) - { - result.ParentID = ic.Parent(); - } - else - { - bool found = false; - - foreach (InventoryFolderBase parent in ic.rdata.folders) - { - if (parent.ID == result.ParentID) - { - found = true; - break; - } - } - - if (!found) - { - Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", - MsgId, ic.Item.Folder, result.ID); - ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent"); - } - } - - // This is a new folder, so no existing UUID is available - // or appropriate - - if (result.ID == UUID.Zero) - { - result.ID = UUID.Random(); - } - - // Treat this as a new context. Any other information is - // obsolete as a consequence. - - ic.Push(result); - } - - /// - /// This method is called to handle the construction of an Item - /// instance from the supplied request entity. It is called - /// whenever an Item start tag is detected. - /// An instance of an Item is created and initialized to default - /// values. These values are then overridden from values supplied - /// as attributes to the Item element. - /// This item is then stored in the XmlInventoryCollection and - /// will be verified by Validate. - /// All context is reset whenever the effective folder changes - /// or an item is successfully validated. - /// - private void CollectItem(XmlInventoryCollection ic) - { - Rest.Log.DebugFormat("{0} Interpret item element", MsgId); - - InventoryItemBase result = new InventoryItemBase(); - - result.Name = String.Empty; - result.Description = String.Empty; - result.ID = UUID.Zero; - result.Folder = UUID.Zero; - result.Owner = ic.UserID; - result.CreatorId = ic.UserID.ToString(); - result.AssetID = UUID.Zero; - result.GroupID = UUID.Zero; - result.GroupOwned = false; - result.InvType = (int) InventoryType.Unknown; - result.AssetType = (int) AssetType.Unknown; - - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - - switch (ic.xml.Name) - { - case "name": - result.Name = ic.xml.Value; - break; - case "desc": - result.Description = ic.xml.Value; - break; - case "uuid": - result.ID = new UUID(ic.xml.Value); - break; - case "folder": - result.Folder = new UUID(ic.xml.Value); - break; - case "owner": - result.Owner = new UUID(ic.xml.Value); - break; - case "invtype": - result.InvType = Int32.Parse(ic.xml.Value); - break; - case "creator": - result.CreatorId = ic.xml.Value; - break; - case "assettype": - result.AssetType = Int32.Parse(ic.xml.Value); - break; - case "groupowned": - result.GroupOwned = Boolean.Parse(ic.xml.Value); - break; - case "groupid": - result.GroupID = new UUID(ic.xml.Value); - break; - case "flags": - result.Flags = UInt32.Parse(ic.xml.Value); - break; - case "creationdate": - result.CreationDate = Int32.Parse(ic.xml.Value); - break; - case "saletype": - result.SaleType = Byte.Parse(ic.xml.Value); - break; - case "saleprice": - result.SalePrice = Int32.Parse(ic.xml.Value); - break; - - default: - Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", - MsgId, ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute", - ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - - ic.Push(result); - } - - /// - /// This method assembles an asset instance from the - /// information supplied in the request's entity. It is - /// called as a result of detecting a start tag for a - /// type of Asset. - /// The information is collected locally, and an asset - /// instance is created only if the basic XML parsing - /// completes successfully. - /// Default values for all parts of the asset are - /// established before overriding them from the supplied - /// XML. - /// If an asset has inline=true as an attribute, then - /// the element contains the data representing the - /// asset. This is saved as the data component. - /// inline=false means that the element's payload is - /// simply the UUID of the asset referenced by the - /// item being constructed. - /// An asset, if created is stored in the - /// XmlInventoryCollection - /// - private void CollectAsset(XmlInventoryCollection ic) - { - Rest.Log.DebugFormat("{0} Interpret asset element", MsgId); - - string name = String.Empty; - string desc = String.Empty; - sbyte type = (sbyte) AssetType.Unknown; - bool temp = false; - bool local = false; - - // This is not a persistent attribute - bool inline = false; - - UUID uuid = UUID.Zero; - - // Attribute is optional - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - switch (ic.xml.Name) - { - case "name" : - name = ic.xml.Value; - break; - - case "type" : - type = SByte.Parse(ic.xml.Value); - break; - - case "description" : - desc = ic.xml.Value; - break; - - case "temporary" : - temp = Boolean.Parse(ic.xml.Value); - break; - - case "uuid" : - uuid = new UUID(ic.xml.Value); - break; - - case "inline" : - inline = Boolean.Parse(ic.xml.Value); - break; - - case "local" : - local = Boolean.Parse(ic.xml.Value); - break; - - default : - Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", - MsgId, ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("unrecognized attribute <{0}>", ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - - // If this is a reference to an existing asset, just store the - // asset ID into the item. - - if (!inline) - { - if (ic.Item != null) - { - ic.Item.AssetID = new UUID(ic.xml.ReadElementContentAsString()); - Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID); - } - else - { - Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset"); - } - } - - // Otherwise, generate an asset ID, store that into the item, and - // create an entry in the asset list for the inlined asset. But - // only if the size is non-zero. - - else - { - AssetBase asset = null; - string b64string = null; - - // Generate a UUID if none were given, and generally none should - // be. Ever. - - if (uuid == UUID.Zero) - { - uuid = UUID.Random(); - } - - // Create AssetBase entity to hold the inlined asset - - asset = new AssetBase(uuid, name, type, UUID.Zero.ToString()); - - asset.Description = desc; - asset.Local = local; - asset.Temporary = temp; - - b64string = ic.xml.ReadElementContentAsString(); - - Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); - Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, - b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); - - asset.Data = Convert.FromBase64String(b64string); - - // Ensure the asset always has some kind of data component - - if (asset.Data == null) - { - asset.Data = new byte[1]; - } - - // If this is in the context of an item, establish - // a link with the item in context. - - if (ic.Item != null && ic.Item.AssetID == UUID.Zero) - { - ic.Item.AssetID = uuid; - } - - ic.Push(asset); - } - } - - /// - /// Store any permissions information provided by the request. - /// This overrides the default permissions set when the - /// XmlInventoryCollection object was created. - /// - private void CollectPermissions(XmlInventoryCollection ic) - { - if (ic.xml.HasAttributes) - { - for (int i = 0; i < ic.xml.AttributeCount; i++) - { - ic.xml.MoveToAttribute(i); - switch (ic.xml.Name) - { - case "current": - ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "next": - ic.NextPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "group": - ic.GroupPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "everyone": - ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - case "base": - ic.BasePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); - break; - default: - Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", - MsgId,ic.xml.Name, ic.xml.Value); - ic.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("invalid attribute <{0}>", ic.xml.Name)); - break; - } - } - } - - ic.xml.MoveToElement(); - } - - /// - /// This method is called whenever an Item has been successfully - /// reconstituted from the request's entity. - /// It uses the information curren tin the XmlInventoryCollection - /// to complete the item's specification, including any implied - /// context and asset associations. - /// It fails the request if any necessary item or asset information - /// is missing. - /// - - private void Validate(XmlInventoryCollection ic) - { - // There really should be an item present if we've - // called validate. So fail if there is not. - - if (ic.Item == null) - { - Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error"); - } - - // Every item is required to have a name (via REST anyway) - - if (ic.Item.Name == String.Empty) - { - Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required"); - } - - // An item MUST have an asset ID. AssetID should never be zero - // here. It should always get set from the information stored - // when the Asset element was processed. - - if (ic.Item.AssetID == UUID.Zero) - { - Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); - Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); - ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required"); - } - - // If the item is new, then assign it an ID - - if (ic.Item.ID == UUID.Zero) - { - ic.Item.ID = UUID.Random(); - } - - // If the context is being implied, obtain the current - // folder item's ID. If it was specified explicitly, make - // sure that theparent folder exists. - - if (ic.Item.Folder == UUID.Zero) - { - ic.Item.Folder = ic.Parent(); - } - else - { - bool found = false; - - foreach (InventoryFolderBase parent in ic.rdata.folders) - { - if (parent.ID == ic.Item.Folder) - { - found = true; - break; - } - } - - if (!found) - { - Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", - MsgId, ic.Item.Folder, ic.Item.ID); - ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required"); - } - } - - // If this is an inline asset being constructed in the context - // of a new Item, then use the itm's name here too. - - if (ic.Asset != null) - { - if (ic.Asset.Name == String.Empty) - ic.Asset.Name = ic.Item.Name; - if (ic.Asset.Description == String.Empty) - ic.Asset.Description = ic.Item.Description; - } - - // Assign permissions - - ic.Item.CurrentPermissions = ic.CurrentPermissions; - ic.Item.EveryOnePermissions = ic.EveryOnePermissions; - ic.Item.BasePermissions = ic.BasePermissions; - ic.Item.GroupPermissions = ic.GroupPermissions; - ic.Item.NextPermissions = ic.NextPermissions; - - // If no type was specified for this item, we can attempt to - // infer something from the file type maybe. This is NOT as - // good as having type be specified in the XML. - - if (ic.Item.AssetType == (int) AssetType.Unknown || - ic.Item.InvType == (int) InventoryType.Unknown) - { - Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); - - string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); - - if (Rest.DEBUG) - { - for (int i = 0; i < parts.Length; i++) - { - Rest.Log.DebugFormat("{0} Name part {1} : {2}", - MsgId, i, parts[i]); - } - } - - // If the associated item name is multi-part, then maybe - // the last part will indicate the item type - if we're - // lucky. - - if (parts.Length > 1) - { - Rest.Log.DebugFormat("{0} File type is {1}", - MsgId, parts[parts.Length - 1]); - switch (parts[parts.Length - 1]) - { - case "jpeg2000" : - case "jpeg-2000" : - case "jpg2000" : - case "jpg-2000" : - Rest.Log.DebugFormat("{0} Type {1} inferred", - MsgId, parts[parts.Length-1]); - if (ic.Item.AssetType == (int) AssetType.Unknown) - ic.Item.AssetType = (int) AssetType.ImageJPEG; - if (ic.Item.InvType == (int) InventoryType.Unknown) - ic.Item.InvType = (int) InventoryType.Texture; - break; - case "jpg" : - case "jpeg" : - Rest.Log.DebugFormat("{0} Type {1} inferred", - MsgId, parts[parts.Length - 1]); - if (ic.Item.AssetType == (int) AssetType.Unknown) - ic.Item.AssetType = (int) AssetType.ImageJPEG; - if (ic.Item.InvType == (int) InventoryType.Unknown) - ic.Item.InvType = (int) InventoryType.Texture; - 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) InventoryType.Texture; - } - else - { - if (ic.Item.AssetType == (int) AssetType.Unknown) - ic.Item.AssetType = (int) AssetType.ImageTGA; - if (ic.Item.InvType == (int) InventoryType.Unknown) - ic.Item.InvType = (int) InventoryType.Snapshot; - } - break; - default : - Rest.Log.DebugFormat("{0} Asset/Inventory type could not be inferred for {1}", - MsgId,ic.Item.Name); - break; - } - } - } - - /// If this is a TGA remember the fact - - if (ic.Item.AssetType == (int) AssetType.TextureTGA || - ic.Item.AssetType == (int) AssetType.ImageTGA) - { - Bitmap temp; - Stream tgadata = new MemoryStream(ic.Asset.Data); - - temp = LoadTGAClass.LoadTGA(tgadata); - try - { - ic.Asset.Data = OpenJPEG.EncodeFromImage(temp, true); - } - catch (DllNotFoundException) - { - Rest.Log.ErrorFormat("OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", ic.Item.Name); - ic.Asset.Data = new Byte[0]; - } - catch (IndexOutOfRangeException) - { - Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); - ic.Asset.Data = new Byte[0]; - } - catch (Exception) - { - Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is empty for {0}", ic.Item.Name); - ic.Asset.Data = new Byte[0]; - } - } - - ic.reset(); - } - - #region Inventory RequestData extension - - internal class InventoryRequestData : RequestData - { - /// - /// These are the inventory specific request/response state - /// extensions. - /// - - internal UUID uuid = UUID.Zero; - internal bool HaveInventory = false; - internal ICollection folders = null; - internal ICollection items = null; - internal UserProfileData userProfile = null; - internal InventoryFolderBase root = null; - internal bool timeout = false; - internal Timer watchDog = new Timer(); - - internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix) - : base(request, response, prefix) - { - } - - internal void startWD(double interval) - { - Rest.Log.DebugFormat("{0} Setting watchdog", MsgId); - watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut); - watchDog.Interval = interval; - watchDog.AutoReset = false; - watchDog.Enabled = true; - lock (watchDog) - watchDog.Start(); - - } - - internal void stopWD() - { - Rest.Log.DebugFormat("{0} Reset watchdog", MsgId); - lock (watchDog) - watchDog.Stop(); - } - - /// - /// This is the callback method required by the inventory watchdog. The - /// requestor issues an inventory request and then blocks until the - /// request completes, or this method signals the monitor. - /// - - private void OnTimeOut(object sender, ElapsedEventArgs args) - { - Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId); - // InventoryRequestData rdata = (InventoryRequestData) sender; - lock (this) - { - this.folders = null; - this.items = null; - this.HaveInventory = false; - this.timeout = true; - Monitor.Pulse(this); - } - } - - /// - /// This is the callback method required by inventory services. The - /// requestor issues an inventory request and then blocks until this - /// method signals the monitor. - /// - - internal void GetUserInventory(ICollection folders, ICollection items) - { - Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId); - lock (this) - { - if (watchDog.Enabled) - { - this.stopWD(); - } - this.folders = folders; - this.items = items; - this.HaveInventory = true; - this.timeout = false; - Monitor.Pulse(this); - } - } - } - - #endregion Inventory RequestData extension - - /// - /// This class is used to record and manage the hierarchy - /// constructed from the entity supplied in the request for - /// PUT and POST. - /// - - internal class XmlInventoryCollection : InventoryCollection - { - internal InventoryRequestData rdata; - private Stack stk; - - internal List Assets; - - internal InventoryItemBase Item; - internal AssetBase Asset; - internal XmlReader xml; - - internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF; - internal /*static*/ const uint DefaultNext = 0x82000; - internal /*static*/ const uint DefaultBase = 0x7FFFFFFF; - internal /*static*/ const uint DefaultEveryOne = 0x0; - internal /*static*/ const uint DefaultGroup = 0x0; - - internal uint CurrentPermissions = 0x00; - internal uint NextPermissions = 0x00; - internal uint BasePermissions = 0x00; - internal uint EveryOnePermissions = 0x00; - internal uint GroupPermissions = 0x00; - - internal XmlInventoryCollection() - { - Folders = new List(); - Items = new List(); - Assets = new List(); - } - - internal void init(InventoryRequestData p_rdata) - { - rdata = p_rdata; - UserID = rdata.uuid; - stk = new Stack(); - rdata.initXmlReader(); - xml = rdata.reader; - initPermissions(); - } - - internal void initPermissions() - { - CurrentPermissions = DefaultCurrent; - NextPermissions = DefaultNext; - BasePermissions = DefaultBase; - GroupPermissions = DefaultGroup; - EveryOnePermissions = DefaultEveryOne; - } - - internal UUID Parent() - { - if (stk.Count != 0) - { - return stk.Peek().ID; - } - else - { - return UUID.Zero; - } - } - - internal void Push(InventoryFolderBase folder) - { - stk.Push(folder); - Folders.Add(folder); - reset(); - } - - internal void Push(InventoryItemBase item) - { - Item = item; - Items.Add(item); - } - - internal void Push(AssetBase asset) - { - Asset = asset; - Assets.Add(asset); - } - - internal void Pop() - { - stk.Pop(); - reset(); - } - - internal void reset() - { - Item = null; - Asset = null; - initPermissions(); - } - - internal void Fail(int code, string addendum) - { - rdata.Fail(code, addendum); - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs deleted file mode 100644 index 81596a3..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestTestServices.cs +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -using System; -using System.Collections.Generic; -using System.Reflection; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - public class RestTestServices : IRest - { - private bool enabled = false; - private string qPrefix = "test"; - - // A simple constructor is used to handle any once-only - // initialization of working classes. - - public RestTestServices() - { - Rest.Log.InfoFormat("{0} Test services initializing", MsgId); - Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); - - // If a relative path was specified, make it absolute by adding - // the standard prefix, e.g. /admin - - if (!qPrefix.StartsWith(Rest.UrlPathSeparator)) - { - Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId); - qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix); - Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix); - } - - // Load test cases - - loadTests(); - foreach (ITest test in tests) - { - test.Initialize(); - } - - // Register interface - - Rest.Plugin.AddPathHandler(DoTests,qPrefix,Allocate); - - // Activate - - enabled = true; - - Rest.Log.InfoFormat("{0} Test services initialization complete", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - foreach (ITest test in tests) - { - test.Close(); - } - Rest.Log.InfoFormat("{0} Test services closing down", MsgId); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - #region Interface - - private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix) - { - return new RequestData(request, response, prefix); - } - - // Inventory Handler - - private void DoTests(RequestData rdata) - { - if (!enabled) - return; - - // Now that we know this is a serious attempt to - // access inventory data, we should find out who - // is asking, and make sure they are authorized - // to do so. We need to validate the caller's - // identity before revealing anything about the - // status quo. Authenticate throws an exception - // via Fail if no identity information is present. - // - // With the present HTTP server we can't use the - // builtin authentication mechanisms because they - // would be enforced for all in-bound requests. - // Instead we look at the headers ourselves and - // handle authentication directly. - - try - { - if (!rdata.IsAuthenticated) - { - rdata.Fail(Rest.HttpStatusCodeNotAuthorized, - String.Format("user \"{0}\" could not be authenticated", rdata.userName)); - } - } - catch (RestException e) - { - if (e.statusCode == Rest.HttpStatusCodeNotAuthorized) - { - Rest.Log.WarnFormat("{0} User not authenticated", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - } - else - { - Rest.Log.ErrorFormat("{0} User authentication failed", MsgId); - Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization")); - } - throw (e); - } - - // Check that a test was specified - - if (rdata.Parameters.Length < 1) - { - Rest.Log.DebugFormat("{0} Insufficient parameters", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "not enough parameters"); - } - - // Select the test - - foreach (ITest test in tests) - { - if (!rdata.handled) - test.Execute(rdata); - } - } - - #endregion Interface - - private static bool testsLoaded = false; - private static List classes = new List(); - private static List tests = new List(); - private static Type[] parms = new Type[0]; - private static Object[] args = new Object[0]; - - static RestTestServices() - { - Module[] mods = Assembly.GetExecutingAssembly().GetModules(); - foreach (Module m in mods) - { - Type[] types = m.GetTypes(); - foreach (Type t in types) - { - try - { - if (t.GetInterface("ITest") != null) - { - classes.Add(t); - } - } - catch (Exception e) - { - Rest.Log.WarnFormat("[STATIC-TEST] Unable to include test {0} : {1}", t, e.Message); - } - } - } - } - - /// - /// 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. - /// - - private void loadTests() - { - lock (tests) - { - if (!testsLoaded) - { - - ConstructorInfo ci; - Object ht; - - foreach (Type t in classes) - { - try - { - if (t.GetInterface("ITest") != null) - { - ci = t.GetConstructor(parms); - ht = ci.Invoke(args); - tests.Add((ITest)ht); - Rest.Log.InfoFormat("{0} Test {1} added", MsgId, t); - } - } - catch (Exception e) - { - Rest.Log.WarnFormat("{0} Unable to load test {1} : {2}", MsgId, t, e.Message); - } - } - testsLoaded = true; - } - } - } - - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs deleted file mode 100644 index eafc154..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/ITest.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* -* Copyright (c) Contributors, http://opensimulator.org/ -* See CONTRIBUTORS.TXT for a full list of copyright holders. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in the -* documentation and/or other materials provided with the distribution. -* * Neither the name of the OpenSimulator Project nor the -* names of its contributors may be used to endorse or promote products -* derived from this software without specific prior written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY -* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -*/ - -namespace OpenSim.ApplicationPlugins.Rest.Inventory -{ - - /// - /// This interface represents the boundary between the general purpose - /// REST plugin handling, and the functionally specific handlers. The - /// handler knows only to initialzie and terminate all such handlers - /// that it finds. - /// - - internal interface ITest - { - void Initialize(); - void Execute(RequestData rdata); - void Close(); - } - -} diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs deleted file mode 100644 index 1c1afd0..0000000 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/tests/Remote.cs +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using OpenMetaverse; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Inventory.Tests -{ - public class Remote : ITest - { - private static readonly int PARM_TESTID = 0; - private static readonly int PARM_COMMAND = 1; - - private static readonly int PARM_MOVE_AVATAR = 2; - private static readonly int PARM_MOVE_X = 3; - private static readonly int PARM_MOVE_Y = 4; - private static readonly int PARM_MOVE_Z = 5; - - private bool enabled = false; - - // No constructor code is required. - - public Remote() - { - Rest.Log.InfoFormat("{0} Remote services constructor", MsgId); - } - - // Post-construction, pre-enabled initialization opportunity - // Not currently exploited. - - public void Initialize() - { - enabled = true; - Rest.Log.InfoFormat("{0} Remote services initialized", MsgId); - } - - // Called by the plug-in to halt REST processing. Local processing is - // disabled, and control blocks until all current processing has - // completed. No new processing will be started - - public void Close() - { - enabled = false; - Rest.Log.InfoFormat("{0} Remote services closing down", MsgId); - } - - // Properties - - internal string MsgId - { - get { return Rest.MsgId; } - } - - // Remote Handler - // Key information of interest here is the Parameters array, each - // entry represents an element of the URI, with element zero being - // the - - public void Execute(RequestData rdata) - { - if (!enabled) return; - - // If we can't relate to what's there, leave it for others. - - if (rdata.Parameters.Length == 0 || rdata.Parameters[PARM_TESTID] != "remote") - return; - - Rest.Log.DebugFormat("{0} REST Remote handler ENTRY", MsgId); - - // Remove the prefix and what's left are the parameters. If we don't have - // the parameters we need, fail the request. Parameters do NOT include - // any supplied query values. - - if (rdata.Parameters.Length > 1) - { - switch (rdata.Parameters[PARM_COMMAND].ToLower()) - { - case "move" : - DoMove(rdata); - break; - default : - DoHelp(rdata); - break; - } - } - else - { - DoHelp(rdata); - } - } - - private void DoHelp(RequestData rdata) - { - rdata.body = Help; - rdata.Complete(); - rdata.Respond("Help"); - } - - private void DoMove(RequestData rdata) - { - if (rdata.Parameters.Length < 6) - { - Rest.Log.WarnFormat("{0} Move: No movement information provided", MsgId); - rdata.Fail(Rest.HttpStatusCodeBadRequest, "no movement information provided"); - } - else - { - string[] names = rdata.Parameters[PARM_MOVE_AVATAR].Split(Rest.CA_SPACE); - ScenePresence presence = null; - Scene scene = null; - - if (names.Length != 2) - { - rdata.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("invalid avatar name: <{0}>",rdata.Parameters[PARM_MOVE_AVATAR])); - } - - Rest.Log.WarnFormat("{0} '{1}' command received for {2} {3}", - MsgId, rdata.Parameters[0], names[0], names[1]); - - // The first parameter should be an avatar name, look for the - // avatar in the known regions first. - - Rest.main.SceneManager.ForEachScene(delegate(Scene s) - { - s.ForEachRootScenePresence(delegate(ScenePresence sp) - { - if (sp.Firstname == names[0] && sp.Lastname == names[1]) - { - scene = s; - presence = sp; - } - }); - }); - - if (presence != null) - { - Rest.Log.DebugFormat("{0} Move : Avatar {1} located in region {2}", - MsgId, rdata.Parameters[PARM_MOVE_AVATAR], scene.RegionInfo.RegionName); - - try - { - float x = Convert.ToSingle(rdata.Parameters[PARM_MOVE_X]); - float y = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Y]); - float z = Convert.ToSingle(rdata.Parameters[PARM_MOVE_Z]); - Vector3 vector = new Vector3(x, y, z); - presence.MoveToTarget(vector, false, false); - } - catch (Exception e) - { - rdata.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("invalid parameters: {0}", e.Message)); - } - } - else - { - rdata.Fail(Rest.HttpStatusCodeBadRequest, - String.Format("avatar {0} not present", rdata.Parameters[PARM_MOVE_AVATAR])); - } - - rdata.Complete(); - rdata.Respond("OK"); - } - } - - private static readonly string Help = - "" - + "Remote Command Usage" - + "" - + "

Supported commands are:

" - + "
" - + "
move/avatar-name/x/y/z
" - + "
moves the specified avatar to another location
" - + "
" - + "" - + "" - ; - } -} -- cgit v1.1