From 03075359b5f1a4470ed5fa54afd76f08bf57a437 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 15 Mar 2013 23:48:40 +0000 Subject: [PATCH 1/4] Finally remove the 'REST' ApplicationPlugins code which has been non-functional and largely commented out for many years. --- .../Rest/Inventory/IRest.cs | 43 - .../Rest/Inventory/IRestHandler.cs | 59 - .../Rest/Inventory/RequestData.cs | 1465 ----------- .../Inventory/Resources/RestHandler.addin.xml | 11 - .../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 -- .../Rest/Regions/GETHandler.cs | 228 -- .../Rest/Regions/GETRegionInfoHandler.cs | 136 - .../Rest/Regions/POSTHandler.cs | 122 - .../Rest/Regions/RegionDetails.cs | 98 - .../Resources/RestRegionPlugin.addin.xml | 11 - .../Rest/Regions/RestRegionPlugin.cs | 94 - OpenSim/ApplicationPlugins/Rest/RestPlugin.cs | 417 --- .../ApplicationPlugins/Rest/RestXmlWriter.cs | 72 - OpenSim/ApplicationPlugins/Rest/rest.xsd | 276 -- prebuild.xml | 115 - 23 files changed, 8890 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 delete mode 100644 OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/Regions/Resources/RestRegionPlugin.addin.xml delete mode 100644 OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/RestPlugin.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs delete mode 100644 OpenSim/ApplicationPlugins/Rest/rest.xsd diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/IRest.cs deleted file mode 100644 index 8b43d42662..0000000000 --- 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 a88fe88449..0000000000 --- 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 10f1a6ecf3..0000000000 --- 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 777a2dc27f..0000000000 --- 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 9755e73ac3..0000000000 --- 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 3cda98442e..0000000000 --- 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 4ba3d77a5b..0000000000 --- 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 e79d2bd644..0000000000 --- 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 072bd6f010..0000000000 --- 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 536f167793..0000000000 --- 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 81596a367a..0000000000 --- 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 eafc1548ee..0000000000 --- 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 1c1afd033f..0000000000 --- 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
" - + "
" - + "" - + "" - ; - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs b/OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs deleted file mode 100644 index d99ba57eb8..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/GETHandler.cs +++ /dev/null @@ -1,228 +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.IO; -using System.Xml.Serialization; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - public partial class RestRegionPlugin : RestPlugin - { - #region GET methods - public string GetHandler(string request, string path, string param, - IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) - { - // foreach (string h in httpRequest.Headers.AllKeys) - // foreach (string v in httpRequest.Headers.GetValues(h)) - // m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v); - - MsgID = RequestID; - m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param); - - try - { - // param empty: regions list - if (String.IsNullOrEmpty(param)) return GetHandlerRegions(httpResponse); - - // param not empty: specific region - return GetHandlerRegion(httpResponse, param); - } - catch (Exception e) - { - return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e); - } - } - - public string GetHandlerRegions(IOSHttpResponse httpResponse) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - rxw.WriteStartElement(String.Empty, "regions", String.Empty); - foreach (Scene s in App.SceneManager.Scenes) - { - rxw.WriteStartElement(String.Empty, "uuid", String.Empty); - rxw.WriteString(s.RegionInfo.RegionID.ToString()); - rxw.WriteEndElement(); - } - rxw.WriteEndElement(); - - return rxw.ToString(); - } - - protected string ShortRegionInfo(string key, string value) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - if (String.IsNullOrEmpty(value) || - String.IsNullOrEmpty(key)) return null; - - rxw.WriteStartElement(String.Empty, "region", String.Empty); - rxw.WriteStartElement(String.Empty, key, String.Empty); - rxw.WriteString(value); - rxw.WriteEndDocument(); - - return rxw.ToString(); - } - - public string GetHandlerRegion(IOSHttpResponse httpResponse, string param) - { - // be resilient and don't get confused by a terminating '/' - param = param.TrimEnd(new char[]{'/'}); - string[] comps = param.Split('/'); - UUID regionID = (UUID)comps[0]; - - m_log.DebugFormat("{0} GET region UUID {1}", MsgID, regionID.ToString()); - - if (UUID.Zero == regionID) throw new Exception("missing region ID"); - - Scene scene = null; - App.SceneManager.TryGetScene(regionID, out scene); - if (null == scene) return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound, - "GET", "cannot find region {0}", regionID.ToString()); - - RegionDetails details = new RegionDetails(scene.RegionInfo); - - // m_log.DebugFormat("{0} GET comps {1}", MsgID, comps.Length); - // for (int i = 0; i < comps.Length; i++) m_log.DebugFormat("{0} GET comps[{1}] >{2}<", MsgID, i, comps[i]); - - if (1 == comps.Length) - { - // complete region details requested - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - XmlSerializer xs = new XmlSerializer(typeof(RegionDetails)); - xs.Serialize(rxw, details, _xmlNs); - return rxw.ToString(); - } - - if (2 == comps.Length) - { - string resp = ShortRegionInfo(comps[1], details[comps[1]]); - if (null != resp) return resp; - - // m_log.DebugFormat("{0} GET comps advanced: >{1}<", MsgID, comps[1]); - - // check for {terrain,stats,prims} - switch (comps[1].ToLower()) - { - case "terrain": - return RegionTerrain(httpResponse, scene); - - case "stats": - return RegionStats(httpResponse, scene); - - case "prims": - return RegionPrims(httpResponse, scene, Vector3.Zero, Vector3.Zero); - } - } - - if (3 == comps.Length) - { - switch (comps[1].ToLower()) - { - case "prims": - string[] subregion = comps[2].Split(','); - if (subregion.Length == 6) - { - Vector3 min, max; - try - { - min = new Vector3((float)Double.Parse(subregion[0], Culture.NumberFormatInfo), (float)Double.Parse(subregion[1], Culture.NumberFormatInfo), (float)Double.Parse(subregion[2], Culture.NumberFormatInfo)); - max = new Vector3((float)Double.Parse(subregion[3], Culture.NumberFormatInfo), (float)Double.Parse(subregion[4], Culture.NumberFormatInfo), (float)Double.Parse(subregion[5], Culture.NumberFormatInfo)); - } - catch (Exception) - { - return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest, - "GET", "invalid subregion parameter"); - } - return RegionPrims(httpResponse, scene, min, max); - } - else - { - return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest, - "GET", "invalid subregion parameter"); - } - } - } - - return Failure(httpResponse, OSHttpStatusCode.ClientErrorBadRequest, - "GET", "too many parameters {0}", param); - } - #endregion GET methods - - protected string RegionTerrain(IOSHttpResponse httpResponse, Scene scene) - { - httpResponse.SendChunked = true; - httpResponse.ContentType = "text/xml"; - - return scene.Heightmap.SaveToXmlString(); - //return Failure(httpResponse, OSHttpStatusCode.ServerErrorNotImplemented, - // "GET", "terrain not implemented"); - } - - protected string RegionStats(IOSHttpResponse httpResponse, Scene scene) - { - int users = scene.GetRootAgentCount(); - int objects = scene.Entities.Count - users; - - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - rxw.WriteStartElement(String.Empty, "region", String.Empty); - rxw.WriteStartElement(String.Empty, "stats", String.Empty); - - rxw.WriteStartElement(String.Empty, "users", String.Empty); - rxw.WriteString(users.ToString()); - rxw.WriteEndElement(); - - rxw.WriteStartElement(String.Empty, "objects", String.Empty); - rxw.WriteString(objects.ToString()); - rxw.WriteEndElement(); - - rxw.WriteEndDocument(); - - return rxw.ToString(); - } - - protected string RegionPrims(IOSHttpResponse httpResponse, Scene scene, Vector3 min, Vector3 max) - { - httpResponse.SendChunked = true; - httpResponse.ContentType = "text/xml"; - - IRegionSerialiserModule serialiser = scene.RequestModuleInterface(); - if (serialiser != null) - serialiser.SavePrimsToXml2(scene, new StreamWriter(httpResponse.OutputStream), min, max); - - return ""; - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs b/OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs deleted file mode 100644 index 468faeaa50..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/GETRegionInfoHandler.cs +++ /dev/null @@ -1,136 +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.IO; -using System.Xml.Serialization; -using OpenMetaverse; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - public partial class RestRegionPlugin : RestPlugin - { - #region GET methods - public string GetRegionInfoHandler(string request, string path, string param, - IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) - { - // foreach (string h in httpRequest.Headers.AllKeys) - // foreach (string v in httpRequest.Headers.GetValues(h)) - // m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v); - - MsgID = RequestID; - m_log.DebugFormat("{0} GET path {1} param {2}", MsgID, path, param); - - try - { - // param empty: regions list - // if (String.IsNullOrEmpty(param)) - return GetRegionInfoHandlerRegions(httpResponse); - - // // param not empty: specific region - // return GetRegionInfoHandlerRegion(httpResponse, param); - } - catch (Exception e) - { - return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "GET", e); - } - } - - public string GetRegionInfoHandlerRegions(IOSHttpResponse httpResponse) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - // regions info - rxw.WriteStartElement(String.Empty, "regions", String.Empty); - { - // regions info: number of regions - rxw.WriteStartAttribute(String.Empty, "number", String.Empty); - rxw.WriteValue(App.SceneManager.Scenes.Count); - rxw.WriteEndAttribute(); - - // regions info: max number of regions - rxw.WriteStartAttribute(String.Empty, "max", String.Empty); - if (App.ConfigSource.Source.Configs["RemoteAdmin"] != null) - { - rxw.WriteValue(App.ConfigSource.Source.Configs["RemoteAdmin"].GetInt("region_limit", -1)); - } - else - { - rxw.WriteValue(-1); - } - rxw.WriteEndAttribute(); - - // regions info: region - foreach (Scene s in App.SceneManager.Scenes) - { - rxw.WriteStartElement(String.Empty, "region", String.Empty); - - rxw.WriteStartAttribute(String.Empty, "uuid", String.Empty); - rxw.WriteString(s.RegionInfo.RegionID.ToString()); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "name", String.Empty); - rxw.WriteString(s.RegionInfo.RegionName); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "x", String.Empty); - rxw.WriteValue(s.RegionInfo.RegionLocX); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "y", String.Empty); - rxw.WriteValue(s.RegionInfo.RegionLocY); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "external_hostname", String.Empty); - rxw.WriteString(s.RegionInfo.ExternalHostName); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "ip", String.Empty); - rxw.WriteString(s.RegionInfo.InternalEndPoint.ToString()); - rxw.WriteEndAttribute(); - - int users = s.GetRootAgentCount(); - rxw.WriteStartAttribute(String.Empty, "avatars", String.Empty); - rxw.WriteValue(users); - rxw.WriteEndAttribute(); - - rxw.WriteStartAttribute(String.Empty, "objects", String.Empty); - rxw.WriteValue(s.Entities.Count - users); - rxw.WriteEndAttribute(); - - rxw.WriteEndElement(); - } - } - return rxw.ToString(); - } - #endregion GET methods - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs b/OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs deleted file mode 100644 index f666f45e9a..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/POSTHandler.cs +++ /dev/null @@ -1,122 +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.IO; -using OpenMetaverse; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - public partial class RestRegionPlugin : RestPlugin - { - #region POST methods - - public string PostHandler(string request, string path, string param, - IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) - { - // foreach (string h in httpRequest.Headers.AllKeys) - // foreach (string v in httpRequest.Headers.GetValues(h)) - // m_log.DebugFormat("{0} IsGod: {1} -> {2}", MsgID, h, v); - - MsgID = RequestID; - m_log.DebugFormat("{0} POST path {1} param {2}", MsgID, path, param); - - try - { - // param empty: new region post - if (!IsGod(httpRequest)) - // XXX: this needs to be turned into a FailureUnauthorized(...) - return Failure(httpResponse, OSHttpStatusCode.ClientErrorUnauthorized, - "GET", "you are not god"); - - if (String.IsNullOrEmpty(param)) return CreateRegion(httpRequest, httpResponse); - - // Parse region ID and other parameters - param = param.TrimEnd(new char[] {'/'}); - string[] comps = param.Split('/'); - UUID regionID = (UUID) comps[0]; - - m_log.DebugFormat("{0} POST region UUID {1}", MsgID, regionID.ToString()); - if (UUID.Zero == regionID) throw new Exception("missing region ID"); - - Scene scene = null; - App.SceneManager.TryGetScene(regionID, out scene); - if (null == scene) - return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound, - "POST", "cannot find region {0}", regionID.ToString()); - - if (2 == comps.Length) - { - // check for {prims} - switch (comps[1].ToLower()) - { - case "prims": - return LoadPrims(request, httpRequest, httpResponse, scene); - } - } - - return Failure(httpResponse, OSHttpStatusCode.ClientErrorNotFound, - "POST", "url {0} not supported", param); - } - catch (Exception e) - { - return Failure(httpResponse, OSHttpStatusCode.ServerErrorInternalError, "POST", e); - } - } - - public string CreateRegion(IOSHttpRequest request, IOSHttpResponse response) - { - RestXmlWriter rxw = new RestXmlWriter(new StringWriter()); - - rxw.WriteStartElement(String.Empty, "regions", String.Empty); - foreach (Scene s in App.SceneManager.Scenes) - { - rxw.WriteStartElement(String.Empty, "uuid", String.Empty); - rxw.WriteString(s.RegionInfo.RegionID.ToString()); - rxw.WriteEndElement(); - } - rxw.WriteEndElement(); - - return rxw.ToString(); - } - - public string LoadPrims(string requestBody, IOSHttpRequest request, IOSHttpResponse response, Scene scene) - { - IRegionSerialiserModule serialiser = scene.RequestModuleInterface(); - if (serialiser != null) - serialiser.LoadPrimsFromXml2(scene, new StringReader(requestBody), true); - - return ""; - } - - #endregion POST methods - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs b/OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs deleted file mode 100644 index 5e760091b9..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/RegionDetails.cs +++ /dev/null @@ -1,98 +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.Serialization; -using OpenMetaverse; -using OpenSim.Framework; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - [XmlRoot(ElementName="region", IsNullable = false)] - public class RegionDetails - { - public string region_name; - public string region_id; - public uint region_x; - public uint region_y; - public string region_owner; - public string region_owner_id; - public uint region_http_port; - public uint region_port; - public string region_server_uri; - public string region_external_hostname; - - public RegionDetails() - { - } - - public RegionDetails(RegionInfo regInfo) - { - region_name = regInfo.RegionName; - region_id = regInfo.RegionID.ToString(); - region_x = regInfo.RegionLocX; - region_y = regInfo.RegionLocY; - region_owner_id = regInfo.EstateSettings.EstateOwner.ToString(); - region_http_port = regInfo.HttpPort; - region_server_uri = regInfo.ServerURI; - region_external_hostname = regInfo.ExternalHostName; - - Uri uri = new Uri(region_server_uri); - region_port = (uint)uri.Port; - } - - public string this[string idx] - { - get - { - switch (idx.ToLower()) - { - case "name": - return region_name; - case "id": - return region_id; - case "location": - return String.Format("{0}{1}", region_x, region_y); - case "owner": - return region_owner; - case "owner_id": - return region_owner_id; - case "http_port": - return region_http_port.ToString(); - case "server_uri": - return region_server_uri; - case "external_hostname": - case "hostname": - return region_external_hostname; - - default: - return null; - } - } - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/Resources/RestRegionPlugin.addin.xml b/OpenSim/ApplicationPlugins/Rest/Regions/Resources/RestRegionPlugin.addin.xml deleted file mode 100644 index 94eca48371..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/Resources/RestRegionPlugin.addin.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs b/OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs deleted file mode 100644 index 02ef588806..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/Regions/RestRegionPlugin.cs +++ /dev/null @@ -1,94 +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.Serialization; - -namespace OpenSim.ApplicationPlugins.Rest.Regions -{ - public partial class RestRegionPlugin : RestPlugin - { - private static XmlSerializerNamespaces _xmlNs; - - static RestRegionPlugin() - { - _xmlNs = new XmlSerializerNamespaces(); - _xmlNs.Add(String.Empty, String.Empty); - } - - #region overriding properties - public override string Name - { - get { return "REGION"; } - } - - public override string ConfigName - { - get { return "RestRegionPlugin"; } - } - #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 - { - base.Initialise(openSim); - if (!IsEnabled) - { - //m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID); - return; - } - - m_log.InfoFormat("{0} REST region plugin enabled", MsgID); - - // add REST method handlers - AddRestStreamHandler("GET", "/regions/", GetHandler); - AddRestStreamHandler("POST", "/regions/", PostHandler); - AddRestStreamHandler("GET", "/regioninfo/", GetRegionInfoHandler); - } - catch (Exception e) - { - m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message); - m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString()); - } - } - - public override void Close() - { - } - #endregion overriding methods - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/RestPlugin.cs b/OpenSim/ApplicationPlugins/Rest/RestPlugin.cs deleted file mode 100644 index a2425b5cf3..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/RestPlugin.cs +++ /dev/null @@ -1,417 +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.Reflection; -using System.Xml; -using log4net; -using Nini.Config; -using OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Framework.Servers; -using OpenSim.Framework.Servers.HttpServer; - -namespace OpenSim.ApplicationPlugins.Rest -{ - public abstract class RestPlugin : IApplicationPlugin - { - #region properties - - protected static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - private IConfig _config; // Configuration source: Rest Plugins - private IConfig _pluginConfig; // Configuration source: Plugin specific - private OpenSimBase _app; // The 'server' - private BaseHttpServer _httpd; // The server's RPC interface - private string _prefix; // URL prefix below - // which all REST URLs - // are living - // private StringWriter _sw = null; - // private RestXmlWriter _xw = null; - - private string _godkey; - private int _reqk; - - [ThreadStatic] - private static string _threadRequestID = String.Empty; - - /// - /// Return an ever increasing request ID for logging - /// - protected string RequestID - { - get { return _reqk++.ToString(); } - set { _reqk = Convert.ToInt32(value); } - } - - /// - /// Thread-constant message IDs for logging. - /// - protected string MsgID - { - get { return String.Format("[REST-{0}] #{1}", Name, _threadRequestID); } - set { _threadRequestID = value; } - } - - /// - /// Returns true if Rest Plugins are enabled. - /// - public bool PluginsAreEnabled - { - get { return null != _config; } - } - - /// - /// Returns true if specific Rest Plugin is enabled. - /// - public bool IsEnabled - { - get - { - return (null != _pluginConfig) && _pluginConfig.GetBoolean("enabled", false); - } - } - - /// - /// OpenSimMain application - /// - public OpenSimBase App - { - get { return _app; } - } - - /// - /// RPC server - /// - public BaseHttpServer HttpServer - { - get { return _httpd; } - } - - /// - /// URL prefix to use for all REST handlers - /// - public string Prefix - { - get { return _prefix; } - } - - /// - /// Access to GOD password string - /// - protected string GodKey - { - get { return _godkey; } - } - - /// - /// Configuration of the plugin - /// - public IConfig Config - { - get { return _pluginConfig; } - } - - /// - /// Name of the plugin - /// - public abstract string Name { get; } - - /// - /// Return the config section name - /// - public abstract string ConfigName { get; } - - // public XmlTextWriter XmlWriter - // { - // get - // { - // if (null == _xw) - // { - // _sw = new StringWriter(); - // _xw = new RestXmlWriter(_sw); - // _xw.Formatting = Formatting.Indented; - // } - // return _xw; - // } - // } - - // public string XmlWriterResult - // { - // get - // { - // _xw.Flush(); - // _xw.Close(); - // _xw = null; - - // return _sw.ToString(); - // } - // } - - #endregion properties - - #region methods - - // TODO: required by IPlugin, but likely not at all right - private string m_version = "0.0"; - - public string Version - { - get { return m_version; } - } - - public void Initialise() - { - m_log.Info("[RESTPLUGIN]: " + Name + " cannot be default-initialized!"); - throw new PluginNotInitialisedException(Name); - } - - /// - /// 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 virtual void Initialise(OpenSimBase openSim) - { - RequestID = "0"; - MsgID = RequestID; - - try - { - if ((_config = openSim.ConfigSource.Source.Configs["RestPlugins"]) == null) - { - m_log.WarnFormat("{0} Rest Plugins not configured", MsgID); - return; - } - - if (!_config.GetBoolean("enabled", false)) - { - //m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID); - return; - } - - _app = openSim; - _httpd = openSim.HttpServer; - - // Retrieve GOD key value, if any. - _godkey = _config.GetString("god_key", String.Empty); - - // Retrive prefix if any. - _prefix = _config.GetString("prefix", "/admin"); - - // Get plugin specific config - _pluginConfig = openSim.ConfigSource.Source.Configs[ConfigName]; - - m_log.InfoFormat("{0} Rest Plugins Enabled", MsgID); - } - catch (Exception e) - { - // we can safely ignore this, as it just means that - // the key lookup in Configs failed, which signals to - // us that noone is interested in our services...they - // don't know what they are missing out on... - // NOTE: Under the present OpenSimulator implementation it is - // not possible for the openSimulator pointer to be null. However - // were the implementation to be changed, this could - // result in a silent initialization failure. Harmless - // except for lack of function and lack of any - // diagnostic indication as to why. The same is true if - // the HTTP server reference is bad. - // We should at least issue a message... - m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message); - m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString()); - } - } - - public virtual void PostInitialise() - { - } - - private List _handlers = new List(); - private Dictionary _agents = new Dictionary(); - - /// - /// Add a REST stream handler to the underlying HTTP server. - /// - /// GET/PUT/POST/DELETE or - /// similar - /// URL prefix - /// RestMethod handler doing the actual work - public virtual void AddRestStreamHandler(string httpMethod, string path, RestMethod method) - { - if (!IsEnabled) return; - - if (!path.StartsWith(_prefix)) - { - path = String.Format("{0}{1}", _prefix, path); - } - - RestStreamHandler h = new RestStreamHandler(httpMethod, path, method); - _httpd.AddStreamHandler(h); - _handlers.Add(h); - - m_log.DebugFormat("{0} Added REST handler {1} {2}", MsgID, httpMethod, path); - } - - /// - /// Add a powerful Agent handler to the underlying HTTP - /// server. - /// - /// name of agent handler - /// agent handler method - /// false when the plugin is disabled or the agent - /// handler could not be added. Any generated exceptions are - /// allowed to drop through to the caller, i.e. ArgumentException. - /// - public bool AddAgentHandler(string agentName, IHttpAgentHandler handler) - { - if (!IsEnabled) return false; - _agents.Add(agentName, handler); -// return _httpd.AddAgentHandler(agentName, handler); - - return false; - } - - /// - /// Remove a powerful Agent handler from the underlying HTTP - /// server. - /// - /// name of agent handler - /// agent handler method - /// false when the plugin is disabled or the agent - /// handler could not be removed. Any generated exceptions are - /// allowed to drop through to the caller, i.e. KeyNotFound. - /// - public bool RemoveAgentHandler(string agentName, IHttpAgentHandler handler) - { - if (!IsEnabled) return false; - if (_agents[agentName] == handler) - { - _agents.Remove(agentName); -// return _httpd.RemoveAgentHandler(agentName, handler); - } - return false; - } - - /// - /// Check whether the HTTP request came from god; that is, is - /// the god_key as configured in the config section supplied - /// via X-OpenSim-Godkey? - /// - /// HTTP request header - /// true when the HTTP request came from god. - protected bool IsGod(IOSHttpRequest request) - { - string[] keys = request.Headers.GetValues("X-OpenSim-Godkey"); - if (null == keys) return false; - - // we take the last key supplied - return keys[keys.Length - 1] == _godkey; - } - - /// - /// Checks wether the X-OpenSim-Password value provided in the - /// HTTP header is indeed the password on file for the avatar - /// specified by the UUID - /// - protected bool IsVerifiedUser(IOSHttpRequest request, UUID uuid) - { - // XXX under construction - return false; - } - - /// - /// Clean up and remove all handlers that were added earlier. - /// - public virtual void Close() - { - foreach (RestStreamHandler h in _handlers) - { - _httpd.RemoveStreamHandler(h.HttpMethod, h.Path); - } - _handlers = null; -// foreach (KeyValuePair h in _agents) -// { -// _httpd.RemoveAgentHandler(h.Key, h.Value); -// } - _agents = null; - } - - public virtual void Dispose() - { - Close(); - } - - /// - /// Return a failure message. - /// - /// origin of the failure message - /// failure message - /// This should probably set a return code as - /// well. (?) - protected string Failure(IOSHttpResponse response, OSHttpStatusCode status, - string method, string format, params string[] msg) - { - string m = String.Format(format, msg); - - response.StatusCode = (int) status; - response.StatusDescription = m; - - m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, m); - return String.Format("{0}", m); - } - - /// - /// Return a failure message. - /// - /// origin of the failure message - /// exception causing the failure message - /// This should probably set a return code as - /// well. (?) - public string Failure(IOSHttpResponse response, OSHttpStatusCode status, - string method, Exception e) - { - string m = String.Format("exception occurred: {0}", e.Message); - - response.StatusCode = (int) status; - response.StatusDescription = m; - - m_log.DebugFormat("{0} {1} failed: {2}", MsgID, method, e.ToString()); - m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, e.Message); - - return String.Format("{0}", e.Message); - } - - #endregion methods - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs b/OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs deleted file mode 100644 index 283fa2e134..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/RestXmlWriter.cs +++ /dev/null @@ -1,72 +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.IO; -using System.Text; -using System.Xml; - -namespace OpenSim.ApplicationPlugins.Rest -{ - public class RestXmlWriter: XmlTextWriter - { - private StringWriter m_sw = null; - - public RestXmlWriter(StringWriter sw) : base(sw) - { - m_sw = sw; - Formatting = Formatting.Indented; - } - - public RestXmlWriter(TextWriter textWriter) : base(textWriter) - { - } - - public RestXmlWriter(Stream stream) - : this(stream, Encoding.UTF8) - { - } - - public RestXmlWriter(Stream stream, Encoding enc) : base(stream, enc) - { - } - - public override void WriteStartDocument() - { - } - - public override void WriteStartDocument(bool standalone) - { - } - - public override string ToString() - { - Flush(); - Close(); - return m_sw.ToString(); - } - } -} diff --git a/OpenSim/ApplicationPlugins/Rest/rest.xsd b/OpenSim/ApplicationPlugins/Rest/rest.xsd deleted file mode 100644 index 4dc0ae4d4e..0000000000 --- a/OpenSim/ApplicationPlugins/Rest/rest.xsd +++ /dev/null @@ -1,276 +0,0 @@ - - - - - Open Simulator Export/Import XML schema - August 2008 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/prebuild.xml b/prebuild.xml index 4d27a0bb4c..7a4455d0a6 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -1914,121 +1914,6 @@ - - - - - ../../../bin/ - - - - - ../../../bin/ - - - - ../../../bin/ - - - - - - - - - - - - - - - - - - - - - - - - - - ../../../../bin/ - - - - - ../../../../bin/ - - - - ../../../../bin/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - ../../../../bin/ - - - - - ../../../../bin/ - - - - ../../../../bin/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 6e1b3f9951b5ae9fbc0dc65e8404cb878206c68d Mon Sep 17 00:00:00 2001 From: teravus Date: Sat, 16 Mar 2013 03:14:11 -0400 Subject: [PATCH 2/4] *Yet another HTTPServer update code changes in OpenSim Libs. * This fixes a connection close issue by getting rid of the socket references * This adds a connection timeout checker to shutdown poor or evil connections and combats DOS attempts that just connect and make no complete requests and just wait. It also actually implements KeepAlive... instead of just understanding the connection header in the request... you can test by connecting and requesting a keepalive header and sending another request on the same connection. The new timeout checker closes expired keepalive sessions, just make sure you send the request within 70 seconds of connecting or the timeout checker will timeout the connection. --- .../Servers/HttpServer/BaseHttpServer.cs | 17 ++- bin/HttpServer_OpenSim.dll | Bin 116224 -> 119808 bytes bin/HttpServer_OpenSim.pdb | Bin 343552 -> 355840 bytes bin/HttpServer_OpenSim.xml | 121 ++++++++++++++++++ 4 files changed, 135 insertions(+), 3 deletions(-) diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index 58312abf01..dfdd566258 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -486,7 +486,9 @@ namespace OpenSim.Framework.Servers.HttpServer { try { - SendHTML500(response); + byte[] buffer500 = SendHTML500(response); + response.Body.Write(buffer500,0,buffer500.Length); + response.Body.Close(); } catch { @@ -719,7 +721,15 @@ namespace OpenSim.Framework.Servers.HttpServer catch (Exception e) { m_log.Error(String.Format("[BASE HTTP SERVER]: HandleRequest() threw {0} ", e.StackTrace), e); - SendHTML500(response); + try + { + byte[] buffer500 = SendHTML500(response); + response.Body.Write(buffer500, 0, buffer500.Length); + response.Body.Close(); + } + catch + { + } } finally { @@ -1746,7 +1756,8 @@ namespace OpenSim.Framework.Servers.HttpServer response.SendChunked = false; response.ContentLength64 = buffer.Length; response.ContentEncoding = Encoding.UTF8; - + + return buffer; } diff --git a/bin/HttpServer_OpenSim.dll b/bin/HttpServer_OpenSim.dll index 36c089252d06d9a4200cd03a1e23f8da4c8d4de9..e15493d338184e902a38476f1020f6f4cdfc3144 100755 GIT binary patch literal 119808 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 h+gU delta 54364 zcmbTf349dA);?TaJ=3#fA(KgFvTr0|m@JU6?@?9}WtBx)1ymMooFE7xnM4po5XDAR zR74DlhzR0>UUxAUL=<;ah>FPNiYV?I;`f}>J(Gdg_xu09FF&cS=hUfFr%o+hb*g%- z-|VPg>A3Bt5f2PIZ@94jJ{K}SPH_tHq9TN55-&c4cpl=P<0-1lxD>($M1PT|$O94o zeY9=$C&h`UKUKs~N=B~D>gxQII4R-*XM>(~=R0pIrN%g=jeFGdmhcyio0ZlsDuU#_ zY&@;xx=0%S6T6LOrNGyo^uXUB{FA~ihV2(3E79=D?%`X;Dz{e_8lyrR4!ybfNUwHx zm7M%#-a~6#yI;R-;3d6Rp1k?q)WIhv#Ceu4T=347wUdJVN1fbz;uS}iy*-vTW(?n! z*k+~orri4u^!odyUtY*~Wohz~ndzI)ybxIQ^TBW9YJYy^kribh{&&gUmpfMVn>Xix zck<1BzPnT$4qY{FLGzAR>_6^M@{9xWBIQjZM=jPKl|rk&%NVQsS*rUQ%+b!W#dNN72g=%67iB+VdHr!MshBsCr<0-9cMCl*366Gl)*D(Y+ zW;?D9-VMpY&ax(vRb5A@(nR__XC>WhsLpm3@ruy&JR#h^nHBM>tf3yE7lGKE;SSvj z{j};`KnvqS{|0bW?=q%3JErdeNww)s^a!<6#2o!iit5iA+np6TR_Z9&{-0R>c6L-! zjgGF)$`E6g>lNj7qs*Na&W32+*2-F3btA*Rt8{BP2WKodP$iU@=J6Nh=&ejvFdF|(YGV8@Pou2gIBj;>O%)6 z8n?#fD`m!xxKd?-(HwWJve6h8Uy_yz5upqf8ldO8yY0c?^Sf0&)iC0xz+6Aa_fJm; zi|-cnK}Ek9(Kvf(4)`7VYGY(V2W6PCEMZsSMM!YlLrajL=?4Jpp@&E)wTwM)HB{D{ zjH!vG;rmg;(nPa=Xbyb?U{Gp~0F4`~*E~S97fn#4-v{I$tFOmHs?-+TlTBAoCnxB!GYSY;S2O}n^ z9*q*>ZBD(|SkS5;Oz>i>v>rE7>D(XmFro=1WhV02^akLfvc%#jp`IDyBkYDJsiQK` z7?3nn8M1S2(q1M1K`;b+%Yc`vf=)do2CFtZxD##7$5$`8h8-W=t}ooBpHN!L%XLGV>o&o-X-WyMo}*Ds|0{<~rv z_2svX65LWtRsy}L-p(@V3)&n!y0|wdP+utb`(UpA{^Ij#0NO%hU1CnM8k&no051VI zw>V5j!&-lXSoHT1sycO@(DlUD14*S9+Vt0r$NcS-ca6jT!OAm6Agzb;i!m+j>J+3) zv{x!Brs+qJO{@OQ_$ux7sfQ8Q$0E0x=+MU_?zHJAK@XZVRlkg+dn1TAZ2IvS2Gv#_ zl@nYV;iFt-THWnNLPnW#)aa4X%}ZQv&84#V5~ozF4;#xe`Y5N2e`j2+oY>hZ^FOLG z*vQTeA{>-ENx6OJ=G^I0u^EX4?IEL2!HqtyidLEoE37~X`b-Lr@BF0TBPBjx5jNi~ zgJ~=UcfL^gt{U!z){n2YQK#t&)1mX$ybL1*+9OwKa_H$~F~?)$d8&F7NDGnvN+iAe zC^N%L5khHJD9=k7SVm|f;sFD>qFG-9=li#eLHS<03bt3#WHukoR_~$oPW?K9%|n5?V82T*CjEvO_qWgR z(eRcveZW`>26{@DKG1l(eGdqWt0+gU)eF|pU8K1 z%uQWEgF)8xBb7CMAF8D3w;206PF1pvcAfHavymgW)G93e`s9ODKG6B z&}uR3*ktu~7@~FvNB>lehdMnC^G@honLdZa)DVwNpBs&9ddS$^xf`r=r1J&fEbUTC z#_!S&;qoqn5Wd@GG{UxBMxrUc4vlOfPr^dwdrxtz+FjMQG+DjAFBArm$ zIQydfv;DSs}D3>XCX$SgR#YyoDbKNoc7XMMyzcgM8?Q0daNun)KVRXANH%u>r zdeS|3gV{c6XlTAwKWVF-h!m;TP9TP2sip)5ll;N5hA1cx3(hM*@dqc$8sep?g`TQD zlNf!J$!2CMkZRyUB4&hUp#<19v%Pb;z=}MjHa)yIJAiKW@;aR*9(4ymC95b6*7#|R7wWL$<^ancc51{yCY^@*UAx$34E zGl%529!fx_1ZCh7ie-d0fzN4k1@3?-ujbJL6O3&`ij)b)`$M`YV~zNWx|Cmz;)19{ zEk|Hm8fo=-LMsmoP^FCpxGDR!7o{Nk(u*!Zc<7>0;Rz^+x>6?EZx)_CqE(JFYfRPG zkE8tfl6J{5@L$B-$s+JIo`GEyoIp^P!e#>f8zB88g5u@^uzyR5dzurI~Hj$KS^isGSbm0rdRL$k8KM>anXa<9|l z^yTK==W*&kfS4qzf86=Y&|!-5lQD33>ztoKE0tO>C7202paY5CkRCV%a$K@vln%{k z)#{S`L((wlJRR0DHd;N}w)c>HwKZTJnB2S(cM!OM3J*)o7 zOsf7DI7>@dzN()AFirF~k&`U)N#nK=+gFHM;{-cUFuVqGa13+7_TtNA9sY)!c#j2$3gN)hV>24Gk zk3knl#TJ`1xD(v`qR|V9pr=z9RFcStlptKNnN15~um96R9Fjo6qjkTOlg_4yPJe1V*pDxhg>h79vX98E#cyWHpKM zoAbPAb52f)BxA8-t~M!Mqy#Xe^mxL2ASRJRt7idKpN;voy_o(&pJ+8fO6?fDHJFPsCxu z?M1f7pRB2aoiMijwG^>Lq?=E_DI-3DcEeIoCe3hOS{x38oqBTQSx>kCayX&|e~{JK zW4db>li0eY2zk70MwP19z##QRVtVb3#1a`V4FEhcID!KBxlz`TAXU$xTA_;JT~Ma> z2FhurWK!i)g1&wnOT@(_JIo5`4H-w!#je7ox2BjKL#_~!+<06MXiP(%k6c0+#VMcGv-W4%^>C3j;y1ORdh7@ z{t2n!e@HG#wq#;|Z(lV-Z;LF=b>6tpBv{GCz6DR5>2>wS#}eZ$VnQr2!6GKc5)&`)^%B%Gz9h@3ml_)< zl`Hod?@S6@Kq;zThNM!(VpR2VK=N91wsr(CL{qy4er}WGkaX9gFJlYcOwlzIwI;H_mkl09Pd`X^DpNI%FCL-3umBx2diY_LV^bV+JLP<)p8t8~voU9?LL;oE7pmhSR z>`Sba)ib{G=t^0tHKY#fgt2}E)u_G0xcu^>FpKPrOysYl!b~q8`Rf4Cawcs&q*Ehd z2Vpi6M?z-dI_0OW5ef6@T}WC=5VL$#c$t-`vYL>}oT}ax>l^o3oTcezVoYW~7MZ3< zbR8Dijo|HS8TBlxS7}`64|qSTFNKy1O&+TxDA7_WylP^qqaTG8!YOAI-x&ovxk_1A zh3tV-)Wp1o`vH^V5E?zElh7ptWH)z-Uk$xqgej0===n@cf!(2y+tv*$e7uBPYV9-2 z)3Hg$g3rkX#Fa?w-6BlWDPe*mx7SMOuJ9ZYNu+l6I!gS>4sPgphcma-Qy9h#woD^>?E+E|OUacQgtP3wX6WM6tUQ3-}I zYI;7Dnm2tyYD*Kv^gHAD^mfWxBkhWIVM=$f_qhzqHAaq<7FV-HI>%+*B9zv6CZcW} zby+j%ESL2FlTV)pHXW_+4l*SSleLhr<@W_nP%jz44eGCM2B_!ZvQbv|M6=c21%+jG zFN)qxQF9t$M`7)qAOxTOe`QpQ%CzDZ?#v*q5**@ssB{r^NWCj`%WaoiuJ<-3UzvjA zi0iJb3iqK7hnoYcp;D# z?i@E>>izI2ae3&4Pn5Hi0Iff0IGh5VY==|2!Nsi587g=(;w~D1DtUsRzA7h-wbofC zLhju0WGh6>&A(go<(A*4@y?$dcB;sQVTK}~Sv1zgKch{Pg+2gz$d#h$0e7RMKp}1?^PQjtE}%*r#u(Mk z_%Qq|?}=0Y!ZY zQMsE0L+0)r+5SR)yek#RmG;2jBrRs^z7ARVHO~>eQm}3_2^`6hm)6`Hp>SlPtcG9B zU5nYnCPS;pv{Mw~^n1^N<^-@(_T(S{opvx?J6PLb({lQjfXy#Z|K zJac9hB(xAz{UYQ3Sw+eS`)& zNJQgstUC%%z)SEbJC63Xo$+;!wy&m-20gyn;WC|MPRt^-VQq7D{-u|Y4gm}ltkPMN z*qZ5MC?8fBGB6e~a?_2&lTO(*&@M%sR~Yccb%~Z{X?mHl@#@MF4NYH9D@LwGfb^)R zdO?HeHmQWou*I0vCso;ETy#yoe+xLwEfdzP?z#C;7h5JyQyyi$N15-@f8=}Ac;TAd z@Hdzu10>mBi>(Frs!t%~y=oWeRJ|X9>xsnlS}IZ2?ToN=jj|Al>9tgtta~!T(v4Wi zA5%{xrq@2kEKdf#`GSfCW;Hah@@(a3#^F}?X``h zLQIhX^5lR(?jCC6I6MR7?!h59sl1W+6Y)l%h@OP$ul7MA)o^%5YYSF3D7kgfkBA(G zgUUzrw^6G8O_VN+&Nxzuet_uTo<&a~`ddW*n&{P^0@rS$l6+2S5 zL;SS!`D*P309u)Dre~PEa2%_XH6+8P<2?>TS95qRLfTtu#ncnYA&5Dj|7`jWlo4{b zB`5oPP^PTVrtc(D8c6IjElrl89GjSvJ1tZRTA+lKvI?Qh#2<$I0>}@(8%a4$MZ$X# zWj#X#e+&U0G>vxJG)uBLo*hYfQZf_oeY|YMXdW(Xftn3S!_tx)^o$lj{_0nta*95I z+!Mvk*y#jvNjtxJIK)>Unz1r{)F=wI4r8Use=?8{CSF880*SOwOEGQB33R65mMH;p z4w{P5Zd^mFJOFN4V-B7Vso_3CV0Hlg6xxtZOCl24t7nZydJSZgji{!NQw6EHI06E7 zv*67#X6?g0(CltzC;WkggO$v{c{u{Y_6UI^Anb?`ID+buw}b5H6Jfl$%d*YNU)K%` z_R=I1;GP8z|0YqbYj{kn)z_jx9lK}pzf14yf8RAh$Mqcq7J z>?Aj@D4^*zY;?@=NUZ1%lu!Q;65uA}hP5At&saF(O9HW+j7lo1eDZ0%&$KBP-~!HF9suc~ zykNxlVlsttCSeTc-3BJp;mhN=Ih!25oFy|9LKf>TNMz*X{fV(?if=|cd+m_{7TQJ3 zc6JS$Ypx+i`Mlh`snC-7OX6fqdoQERILyyAuAb))v(|Z?BfX*dQm-Q_H=_h{9kFt0 z7`EuqT;_E~nOO;3rNS_@lsJc31jTvSmeSt})I1H(2Nk~%*@#ewr3CuJIg1Dx74yB> z6gz-_=)$7@9F8`|l=bW$2U*Oq^ zCZsZSXqH418O^SAZjH_^#u>LJ*Oz2-Djd)prflQmhi!cPu#Ju%!ej&F zjX&GZY!g^e(_tnpkLWFl2bsm19%5hV|ApbA5W3S?_o{c!Wr*TW?YQ&xTWRP)|doql-_Lu>+mw^ zKx?`K-O!|g@`w<3paD_yK)4z=cX%z2b&7jaH>0Aq>-1gJCbrsV>0!5VSFC-GfUKd% zsu^ix&9%?Jn4=aQ9Nq>}x%Ndw&5o@KC?O_75psMI-N(0JWDSYe=NNC*`g;=D@s5sl z5$4qQ_%Mt}=Cv94-42nSoSExtO16pNkUYuAubZSyHX7<~YC{TOpN@`+#-n$wd1O9? ztPto81Gq~tv-LLGhs)!sR=&V$u;S*?%y1R%gFG7UKk;%>bS5t1_;97u)DTJ~xKcZ) zsWXGlvQk&ByF(i*39C7`f95#1qtvd^(|=Z;k~f@XDX!dvGYW>gRoRQ{{dCBLddEn0 z<(8FezIa^zS!y!M8nP(frSyOY9}5QdRhiKAf0dIyW@g73l5%X9bp@Aq7a$7}c}SQk^my4>b7Grc#U6kTzv_`<#c0m%=$xJ;*rF zke}TLu=Zt=gR3d*PpASVFo4Lf7>PHu>+}*5W#D`eisH&vm_@L0;x%XOtDM*miD-2` zr_fx^akZ}*p&Lq+>x}g`_=ZeF@wslkBMlc$$X6!jY}EX+hU9M4(n9T-fVBZ>u~Ewi zRWSi8l~W7yFdg4FBRj%3)*7;+_Mryn3dEeu7*p9=P5ZvZS!+VygQBu$XIbI!$(GGl4u# zis%UWR~UCJ>6SGVob~&lAgzznX$)z>^6C*pebe}QNq+ckq{-?Zky3krqcxCK`z}R! zCaJ|D+$M8VYOxNtcA0AEG&C}InV~)8+o267c6&a&`D))Gz8VTm*LGYezC5~B%-aLa z0@I0It$hnTVPtECY=KFj1+i_C;tH_PpRC54Myvh*p`a~v1&ZX^QT~q-G5N8Ib}lkZ zOa2d_2rv0L0s>W^2^<08V1&RC5J&`ba0G;pBLt3sKz3sej(|W0WCBM(_ymM7Qz%A? zd>Ucl2ng6Mnt3<^!uv$%4dYP+gEyivCR3Aw%M%J<%Wwy~{+3YZN3S4ksgkE7@Kl~C+b0tr{v(aQ_c(2Z<<_uhe5N9Pf zwTqsW1jd4-=b>xiXfRaYCHbZt&F9dkLlRhwVK@2C<1*SsYKH_|_b|)Aw%8J6Za!nwkp6&<0)h|8z-;54 zo4kHPrVLt6k$k!|!D-=`G;-YYQZEx1B;Ufa^s1;`V2OV1qn7Xou4aj3%+L*>^ToQd z*0Lr_;d6hlmaN5G$tR;*EGr|K!eu>TNM;Z5%`8uli>QkPYLTTR#i}xy-s`|&4pF%Y zt*l!j(#r4}VHJoX12dszEZ^=ZU#i8oI~}CZyF_R0xKaHbu$?+!{JA_QOnsa8k{dnF zv`|_FCQ*+QlP1w~Q!b4p1+cq3OQT|NhE7GqmPCqUa!;gLrN#89@`@PMO?&3bkukO5 zu|kI(p+OM=ypD0>a|RI1F)RbqP-j+42ChJqCoOsb5h%g#eVTFN=JZrDl3oWhI@X~F z6LRQbBYj0)pdQ3xkJ~@iJmSZ#-9xy@8;7kc=lK~Gt!$KmImVO~t(6Aj#ub%b8UQjd z83kBAls|;=#)^VYJTICxcpn<7{4(I02j|H=9B^AxXDrJiBY$PVilT^XoPfR* zs`@H&%_Myp&~>Dl8H&d_YC%*rwx@f0RpKiHO-wIB%t^0Jngk-wxdZs}RF&)BqI497H{5cIN_% z!>_N6wh>6TQKr=hic6d>`m)Yl)=2vn!(nurtheY8IR`MSOO-E^%@;2kJ|u7e(qsK+_Fl^P1xDaTbQXvaQTd!?h6^s@Fi_ zY8b;^O9`@uLTU_n?R&1u{-Q>l0e0Rm>xElb@SrTO)XWa#r zuMI=zt~VeiYe+__pN0ZSA-SdVWq>~FlYzStR^VGeF6SQNC>dr|D$?9kDg9n5@hrY( zcn&$~J|v-gTURep(@j*X#N7BiE)B(Kgv;?%FAeW5g%uVGBHo2TgSIU?5}P(&F%G$l;dd2%7p& zBGC5_J7J0h>dR^^(ae0<-`;yISq<%=$ft6xSEfT7?;X znt5>zejpnv89Ig;AMVq$WEc=+7N=V5b>BgXU*CWnf&HYtRV>S}SpNqPdBT4|KK=rt z02OgiMd;3^c`CtgPfU36#`Zhm!i8+ySqaLTACZR^hp@w6l%3>o1cV!|% zEYs-8Db|O5S~cy|^p}uN(_cjB@HwoDfhLYFrA^4*ERC(NZ$wVBA@m0k_1a7N!p}Sg z#m*g!PL88d=5y+e#>;nSDhG{2cXv=eF_P95hq+Hw#D%^_ceUeCFl!j6^(WZyoXu@3shMR*RRHRNEb>W=F&hn_mUZ* z@E{bTvLl+8c34HsscwXTh1Pb{7rKEsLTeD3w?{cu)i&v@$!(8Og| z=Ttw0grd+M@cRM}bNna7{Z`!9LCi_B;+1J)@SJoD^jE~33@c7P=S(YJhzwa)oR0jm zIex}lPvu#~x^S?**{HjxbGSRW%|pTNm!qR$C-M%}UD{$VXOzE<;qK?a_}x=t(ehHgTlHKDP^ zO6;M#h~JJ9|BnOh@K($x@u4l}$Z*oV!=!>No#D4V@-c%^uKlo?2`bY?X61LI+QR(xO2b65g+* z-wRB~wOCB?A35KvIznh)sJPH(9}DL@Iy`+*QvE12T4<*mpVojJ4+8jy5dWpbk8c!c zh*UVuXA%Ynja+uigBOQalx!cz)1f^~%LHgO0>_ zTxLn_hEhGa4HJy6>>c_ykXMSUPxRbG3_%Q+&lnhY-k%?sk22_I8{D|^{58llyTQ}H zYDQV6Mc58P=z8Px`wJ%SKvaJMfm7d!(3|Y@db>8ElAdIbcU?mDe;~k%>*X%}7-AlW z*M={lp9Cko-B00R)xeu#UF4xExb&xyf?IHp8Iw2o!lY+WnX6cO?5w7K6olrBy;>&w zF}{RbTyIU}1&z?~v8!bw?6dy1yhR*>6h6E&p%9;c*x7cDp=|%i4%!F6CpC z;*TxGY!ow7;CW-?14Uu7Mr>Qn?PR?t^as$tigBVx)ky1lI~poEE$||t1W$Z&Lf|Dt zL%WH+lzJjo$?js1Uk2GULwZU35Sa4~d46|3!^}^5*7y?%e{ThO_P`gtm zOpg(6_A?vzrO2&L63rua$Ablzy$)?_iDG%Cu>g}mykz4D2x^4Dk%$i)30O0moE!lG zZ!42P5u2@|b<^R{gmxWO(j-N99e)y+N%NTx_Pxje zUEJj8IJ)wVWu`mu7f3ifuiIO37GbRoo||AoE7=63Q} zXJgvt9`p-I7+#&x9o10xE>_>Ng$75WxcQbvENX2AJ#cPLj~WW=@(`HUj_CuiD2Zn; zldmoZECKxVkcRCd23x>KQFH++mY$Wl6rnvBM^sEq4`B68e=?8;=Fm^LF%z=EVESO# z5kzCyBPn4{>J6&-p_P?H{Nw+LIE{$2V}uZA{XY?JCgSbqa6UxDZ85|!<$sl^Z^Tf! zco;4;4M9X*A~qA}v2!?oBcjyK<_rLb;7vFy`!FJojU^hFZ}FC}4H_wJYit_Idy|Oo z#}JJTTP&Lmb|7g4f>`}-BjVa~h`WjS#yP|z|3v(nh^NlsOm>2pfgn;4+pPV$R5DLj zB38w)5^*#UC!Rx`N5r~wh|dx6)pLj+67f(h(Rkuf?|E#qL>JP02w1gXbL8_x>VknB z6kf_-A<}FvYpC1v_Y1gxt_HFVZEHbM3u9bpE!x`JIq?vP2+J@o+UhTT6XTKZ{_|T` z(yv^IN}8?oBan`J&iDZiMDXiO7{#}3?K!fRhOGHo2xLJO-bz?F8yGIHJUrqY*i_tv zD$o)P+bA9@4t*qKm8M`oQ$2A-8&H4K$bBpm`xq35-+2Oes3TRKc3eCemR0-@@wcWnP6-iaGNci8Sw!A{hz#Q{NIqMZdNpyd0CdzI@ zHOh{v8Q+!Flp7ssp-rgPMn^j08KLuyPan_kPj9Qp4^Tl!XeU*?JB$?6LSrIE;FoNm zEme|U7w9SiZ+%bX1Z=Ev-uArA3M89(tosFO8>iYSTLg2JXHK#Rj7LA z;@r#_UsCGi%yaQtz_<9JRV|qu5&iEn?t3CvHg+{$d?FZz;{kj9^&8U1+3pFwoZA%-A$yCuw8G4>>Oq6r)BRkE6ofbnu zcp1_r_&X$px?+bA+K!ynIJ$=e;w<_bQF`^e(2agvs6GFL(0gRfM2hnFJ2V>UCxzDO zA1E%YPt4yIGQQjC8)I?WoUxoh;KT=P&d84tIn&1C_YF{wH?U$wir3zCEjm%MJ@64w zf(#W#e?>^*+t7o?_$TwiA0vss*z}LhUQ+$9{2=;x=%FsKK3)dA<_LLMlzf_#-C3v(Yj(x)aM&FK2_5DF8C8#*TM4u z=Rnf=h>x5bnsLpQ)u@L^*(Jm`T|v79vnu=<=!epY z|I$oSYfdJq@?a(v+^#iCE83Z?iQK@g@osTv+w0r9#lr|SQC#DVs=NOuRz=_!kaphrq6NsZw|liNX00fNb8hNq*e)sLlJgRF2fQ)6E}9ETx&UO zD?XB(0v~8#|NyXFCVMvnR}?{S4e)C|gz+(zgr8$8tEQ z3l(-9$8YEO{Rk7qlU;sFN)#V)D7sQSlf&~k9M0iHgqm2@bwO7Y)|KQR;PA7qq(oM? zFo_UM26O>9D36P>$LyUy!QEqXiRxCGNq4uGA59DxopLcs}Vp1Qf%&mPW*ZMwG%jY=$ zS0AcCaTSFfI2?u$+D)$_*6$Fy@e4>4_nt@DCUg8A4)>i$LOAsG4Cj0vwH&{jI#o>Od|B!dX~#{v>53ADm`V%I;Eco6zA_z&S98XR zoNKAon)DJLCHi%o1 z7ETiX1#|)4&8)ww{g8c`c!ZJL))}@~F19jCwb0{?+S*o&Jh4JN%|g$!kuod9vs}ZW zKy88WGJ>>;Hj=pth!mX-l#btw`;-u75U{$$?cy*e-f1J_-zk1z^stR=u}=KKMLf?% z+$SX5Pf6kcP?oq4`(Z*Kvy=_^{DYe51WS2Z^uTU5N&L)eKP~Vxzd)*;e!})8F_1aK zads+xx41x}MEnp2>AhPFkq@9Y=|Dp415zNgkU95>ix_oc^rjfj#rI{jA74d~GQ;d- znP#z&rCe?&b2p1coHiH81+pnC3Dv?XDSdCzl;k^RosjWM(IkN zN*3J!Bt(Xi#A#1!J2VooMK!oMap?H<|aE?WI0f%Fq;}M_6VR*iC62OhlX$Ws~ z&Oms#b2hR)>bw^5XPnmwk9gg=5OG`F97Pc)lKJA}g!2)`B}|5!VPE`*2(NAR32eEt)t3lgY4w9d-f+TS07qKEtl~I_ zaS3+BNf1^_Nfah=n8#sR5{XXnbXD_3mS;RvA2A4{GA`UlC~5l`~=wke{m z_k4t=wK{moT7A7F?r_{}#HX~HkJQIN@rZZ3&4%Ud-HCG08@74GrtTElIXsFmU)WQ| zhfzRc3KgH4LN!~Lnqx=tsjcl1Q*TcVApS}!6?Pz%YVa9{qf&c-K0UPx;r!GK5Uxla zfv~UdGP_5__2_}{X!k0Fl|3#%IK9URyHA`*OVLKxr>9eiCFz^BaK7lC{+L!F)~A1{ zdBoQA&o!UenND?Tli`x1>o3kA!(M{0vlyRI>QKaU8TAO?%^-#Ti*R&(t4!jV2`i4S z_i_Am8pX3YUcg~7hyQ~!T>sCk`QqYCGXCY6M;z1~-yk&29&6-HY~FR5WVvmb)VZEU zToF}SWT$>vr&P$v8Uknds4Sv)%o^pSh8X3{7mLA@FTw^t)@Mxy)8?$JoSK%MLv^}2 zvB^2Q{-^9FuomSIt2wAjIDTQy2Ttnw|3xbG{CqJj=UYfHJEPhE59H8DIhd2|$`@be zWV;m6KbO>+!eJf4&f=+XZXYDSl-ti$Ax`E_Mck1`=53Wn<~0XfM&4Y|$sUR*$om2H zsLcBW!>MZ?)#-c=hjKVMkNU)74pZ|tpvY0J9z|%{ESyjL>+(s7XAx2_37g%7!pa_x zx~QAXw+*lN7QEydUhhMgFVYJ(Aj~Zw8?-H`M?;t@b}b;Y_bO;Y>cE0GuoPORjmPTp zeGEuVJcHq?i6?kz|9t`0V3u8rGo^cUeL`yry{%;!k2D4kwjxp<;+;it>r4*Yx`)^I zZ%qY{Z9T!AFQ&Aff^cT*>25_-gRY4B)>O--t-nEN&JcIBrdpC(ig>&=*{oe5Q3e$@ zIWg-Mo{#vVLdv@abgVyi;E^waMYlq9$D+F-x?d4x8&>qWmMq&oj@yq9#y2F4_kP^NJ@Rq&ZFzcNSA&k0IaSw;-9G>zMrKp5Pe~`m=9Ck;TFM5|Gp{yAk4{^9CTtddD7fFRMkgSMD z5LSq1k!;RZUh!$w-#Ep_8r+ zu64fUtP@UgkP%5)3^XdA-D(7v;|2OYpx`2I0h(P9L2IxVwzZC+d$5#Q?L>W?U?Fo_4+lRBoYE*IuB`gu+NHbnOT1VaJAUimMnkhz0IGN~w6pLW|u4lyY(0LU#kT7vIoY3zc}nJyfX__3a{Pw9-+ewKq}t z7Zl$`Ok=oP`O`f~=_2M@iGRAURJw^JCUPdl%~g7e=Pi^6)Jyzip$>5imENL?+$(6% zfk1sIioYiN4{=3Gl~`$}i9h1*5q-saM!OY9IDV4SPi$k@93Xa^ z87*`?itZ5ugaZy`D)8#~XO)41eq5XCTNA$=*X=jMPe5o({HsXY#i&6%6aR)XSbW2% zN&FBOE>bQOztO>fXb|tlzoT4CQT#R854(;i!%QHW626T;u8fEv`>*leDI>*IIJrX+ zPz@`8aRVb#J6>KQ8lxy#ju9Q1%C7eXhA-xw_g%9E4CP2DKpS}`P{NKO{V7&R$l z5|)c8;tZoNlvxQu8J;4Nx|>Bjm(WX25e1A^r+wP_*;Z3TM@Ce`0Z1DaMHk7b;yM#K zKT8-buMoA-wEft;u8cufihFxd35zkBX2`2V-=2h)I)6wwC}xDkFbjSguge+Y8b-$y zQf8*8H`4^^JyYDm=nLg^!gWa7V5MD{xIcP7t0`e@Vxxi&4fwAqVOlJ*hZ2{_Xqvq? z@n$)jjuq&y32=?P)&z|9)6GWyYqH;%c&~)7GKw}zcq}b+bK-KbAc7KZLxC-5L*f?M zf?i5|TrP}q9st6DvRQsp!qLQM<)UcfZ$LPU;=d+)t5&bcI&sRZFOP#d@tcX9*SBg$ zn)y4h4bB@{Edg@%qKwpNOYoB$0TVeNY4we)7ws7}IG<{DQq+rXy|{d0J&FE5GMX__ zy+O=nWOmCXqQ*kh5toX)O~jpanfN!O#p1hG$?_&~z(Qw$mWxA-%+7YRh=+rSx-&KJ z&7x;-uD{vYZWg0Bk(BYNH;Z?oC`Vl-jxyRUx+E=!>mY?^7jrnR76lf^+;SJrda4Uq^%J%88wIvDE8HG*JMAMRHU|`6G`7GcZ)+7C#9_uja6X8`%gGL?bZ9l%jZ$X zV+yg}FAg&zTPznF#8*+&L){=e=MyKT^;H{1*C-mSJ}hoCktp@-6_1E}qbN^4B1-#m zK2hlzrfw038HJaMPJmm*DJ!un&^F=jXR`M3j8V6XJPY;rOi-T?_gZL>XS({7I2c9O zsLzU({mpzsJ;mZhakqs=Lz$Pv21X6yQlz~k;s+4tv9LnMcv&pKwWmn!UlS`CnYG_7 z)SlNuk&FUUiR{X(FCz-VpOG^lkh*>Koz?3x(@E zAF2Dqa}3RXxnI0#Ci1kiUwq8y3uT$7S=}#Am}%TF`^A|kI-=|sod#3!=7jvV7{kcy zq;HE!7HWnq-WFF{=y=$(A8?KdA@nEpfY=a4r`7kwzoKZbdQco-WESy>SdLS2>cB<# zU*UWrR$HhdIS%Mi3-wCgD?SxFEi@o`Kaly?WFMa#_Sp_a;mqVL+u|jJ`!);%R15q@=_8;*Tqoqz-zm@uf~$rhG5PTZon^;UB~r3(^$)qj=LoGzI@84sd-}i>7Yx zd43k}TIly~p8(m0bAiq;yf@i?7CkNWEzmEb+C*%pU&Vt_G~D)w_#lcV*v^E7lUEgM zap&EzsY=Hvy2oZ$rbW>vn^RfCXt$vH#w*vd7*cI|a)R=Jh2HE==sgP^WW;8|l=MT~ zv$h1~MV3Ni>xj}y3BMnOuh~4x_a<@{rR>EK@$VL@V1xw^8Q9q)rBO*y;u$q5B$N)5 z>8~kaR>~VTUj*3~r5tEM3Ad&kwBeeR#n}jy0spfFpHKPBhC56adKU^KAr`6t%8MX7ts3$pD1o|O3!=S4zA}3xmrusH2M)whqexDJ z_%8K;t+nz6qhq{iDpnkqP}(sUi78fES%}0GD}{`fh~HE9i#E!x(Zsn#*nLNofZ`iN zs6iz9mgCo=+A=!kB0B|@iZNXO24}vn%pO#Fnu!XTtwh;9-pp9(>u4`mdQKp8%tc0Q zugtX&8L>jyY9X>=rE=6lWWx?h)=AEay3l?SF$+FCKlQ=rXIwntI8T)RdYHZ79a zLz}ClO*hf$w82`9GM3S5aXRf1{4Y5sUty-%(x+-ml-W@MZ zroW+VSLT|Ci`b#mMbY!xPGuRRCf?ybsoZ8FK{B6IPS2o9Ep@)0-l*(SY%@*te)<-< zE370jTr0jz|4MsSxtGyy<;(ORv{#j%7_Am3(obu9l$&Nz#wI~}?^Wi^CbU)@O;Q~D zl=jyU+O7PSzE|v5HZxi)oEb%Ozv9%*w2X}Jl(&@Y8JX68TL~{=xK>QhsBpZk{4|GH zSBrTWT^t9L$=8~w4(L6lQ^-VzT~&?`l?zP7`{a+Url78uCnAU%ZII*BXfvH~jBtDw zMZY>GJH9gCf05Qc9!=cqctiO*icUCYI!;8%wq zW*%_-97WwTKXUvMLA;6mJ&I@r^@noGlmbP+cl@ags3selOT9D7#nt@&Tk52>-Wg>q zCoU1CnWr6pE5msIy<4dPQsq^Q%%!G7UT>k5nQo^`zR&2GLdwL+xx7<9=Az|XqTFgB zQl^zWY9fUyktDx?YN$WG<390tWGmj(lS%Od+A=MQ{LW-q5Jfr86j>TYZJa*Yg;A3r zPe8hy%xE|74b$cGQBIFDL+&$?^To^xXQq6Q5cxm%W;QCB@>4TWe2}?ZWJ)p!Jt>Hvw*u5!jSq&#H3f zMOYJ3f%4_CXf5`O0@->#6}VVT$l5Pj%Th)#Hxwpm zA{SLDD61?)RVtB3xk{@=xT#w~YN@=Q6RGWwD5df+BXSmwbC%0fjL2D7q_mU$;IyG4 zXxmyTw_Aud?VaR)MkHmq=qwMK2ouz7XJ=Wzi1N|abiT8jeAGnFAHt|_ce%@gyRvGX z-Q~*`dOd3?&>jmN%$g7Mj)}O49`fi}=$9zMf(9Q8aDm|5srHOEC@sW#N6|uGcxDV# zv-gTV(L{gt{uoq}-KbPW)4F7Dku8XpZ0AMOXvx-s`extl#Ls^5UsJ-c?Da8dLiWM1 zz;CEoiLXA8O&iuRADt%IU1=tZF+o~8Cnq`G zCP8}jc2AbY z^^|7%I;O~ujL5%jb4`&~-9TwyxNgiju1u9H8JVuiX>#CVPGkS}6>=OS)3tMjoW+Qw z6pJh5b&I(`b1`tGTxKTn;JQ+7kD`0TmGW~d<4-xSDp$#AH&PJ|Lgp5WS@K>+OL2Zs z>Y6S0SSTU4(si}myp%YXiiF&*u50Aa7V_nOr_7PX%gpy53d~Wjm3@~nD;GakUT7j` zo7}x(t{iEh&be?*%kdVXoy>J|nuTa1bDg}}LbR`#C$F;*?JMR*``SkQpJnm8VE9A* zo9t6s4Nw-yI+K-)uaRq`2tRitw=gn0%_8}-nI@WYFLu?*Q8%&L;w{uEET6Z~r@5nC z^|I@7PIG>ld#S5I9%R(SezgWU?Pf0DG=77;i6FYrRe1+(4f16R%>}wa{$`;SK#OJj z6(;AMKuhEt6G83kT{p_BZlSbeE^@@LkaH|Vj`$UF0i)f@LwPs3R?2%Bg?B5@<=y65 zC4XhuAok_m<+@Flt|Bo@oFC_XBX5@-E%Z&^NnF&bV$`HO0oRftw_51$JVMF0a`6hS z1q>N9k&6x`3|VO*I+QSEcSeiDPDlP0X~@0|O$XT;d7*{Ky|6}JW3oE4^QYVIkn=1Q z$e#%mwor%sy<)9gW}*K1`+;t=&={oMDetn-6-c{NHZnS+)yW z?v|4c6D9f@UF&4w8naGxU~;eY-4Q`+4L-KtSx#$$+ z5gEUhrMM1+0k_D07Nj-XqjHgjXw9}&K4~I_PC*`%0 z2)4=T+%HTA#x{ASg?@)SW1E~|p*OpSm-)BJSr#1E;}g#|d6dPh6@TULbZwK1?xF(M z3P-^%*W>bYMl@T#?bVh%qQ?k)Q>kAr{ zXC(dZ1Lb?PU~gDFBZn|tE1oH6c0D8KT8X<0wm6@aJ?|sdrQ)N4W3FeVx}MN#@t=Ys z`K;{9$ebUalRFuii;w5zzZk6*zZHDvdQN_8A*J=tuIFXe{Ul|raJBx^6@D=~DLCS< zR9=zSbK)_D&~BNw!DQ{(I?w%YIgOE7#2z`8kvV+#$k~j{e0$`=7|wn2ni$S~a!E98 zfU++v3m*{pTMU!VbnlmIP2_y8b)#}XuD8%ft+&Vn@?l2DT(pLHM?P*LawfeacUg#* zDeuacEkw(dcjaCS(ema!`HqEXdGnqOe_}ye!@Mt#Scujz@5>VwqBYD1@<$8N8s-Ce z+Co^vh!3T0BUJ)^JuG29lnEB1CCo=M)k3s{`AFtih?X!1WpUVow1zn-+gXU#Fdxe< z7NRxG$Fj;ow1)Xa4zdufVLp*#EksM0PvvAr4bGonwNK>?D~%R3hvcB2)T$bQCKW&lGd!{QBezTa(}FXL4*}V>nVQm(xm%655FMGj)l?I~YRg5ON_-LbO$3n*2Pg?v z8rkqa?p9GmHuS355h-D8I8)RMA`n7->Ts(-T4?*!aTcP5wokp>LbTBKt1~S`3vItj zzsO4l{u@?HQ|Z_6F|Rt^ZMMiXbqS+mE?O0)t1B(^q(8hL@WRKLRiw80&7Gl+Vq}QC zHh;S_)f*TcbCHuiOTEQH)t%|xP@rlo2PznA#&O0tG`=_T=w~@ z+)fod<{~G3f$FvpIqAa%YO)2%OJAT?S%|#!t<^z{8k`rPhOO1%RvLN!3)OKJqN9dF zb&7@P{Gd>sVdWbQ;}@zSD~*mQiqwTx+LSh%>_uU9iJ7Qe-6lS+Sp8-PYl_Qdw}@hO z#1keWe|{VFG7FJEKcG&t5KYK!)zeR!oODJ|qF(=$iRg@=T>XDq`xf}9itGP*+|6#t zKC+wF=7os@1_&512}E9r1m$G~MGyr^Bq*o|2q-FWcM+@nqy;LnRk2M9K2WM6Xh1*{ zNejrMqFBMkHXzm7M%179YptmN?>V!ZEHCTt|NrwL-+RuPGiT1soS8d!@4a)AUm?z% zwtMQ&InA85dusbZPW(tt2&7v*^@%7)cY-g{bLuH$7`G-nC0(SOUL}q$&d#@r^uZ`c z_l0}um0Kyz6wymRZ8(>sz@5}fzYygD1#3F=(wn1PkAm(=y>){fW&0KM0=wIAJh(2_ z2cl^Y7F?cmu|6E-9xE6L?r4--kF;X__b9g=X~p`>C`ZMY=x?GN6^FIxlj|;|uO*4sHd>@_L#vR401zp>?IZ0)@q>WpU)L+kGjxNQwSmpX(ZD|iA zU8;Yz-ITDY>$0RF`YGnZuEwq_!M(7Z&woF4J%pIOOxH&fA4bexrW>Le_jko_7J64S zt-0$ya0jAke@EI-eK?x-J<^8iqtUd~Zo9=W{r6~E=WhGJeQDA}X}2kk;X2_pKL1g# zy_7IqCxbP8xGiC%&ScK~*nWjBk8<>T{+0S;!|`wZSL@@2&5yc~!b2bA0|=BC2#zTjEAt zr3bZf`^1fUR2#R$nymM>aYyA%`aQ$(BZyP=U!vU3?p2Pd`r|0~e)oi=srqxn@#Bfp z^bYk%7+iCLcMA|%kO_Z~#XYSQCQI>jUp`ID#(h3K; z7wV6q+a8aTDFk_2;`y@s||d z>RzSSz8U50!ny8W>e4-i9aOl${g|#~?qP9R;bQmWdhcFJBW{`dSGr;!ai+et(UU6m zXr731bo%|J`!~@vI{ntUpKRm)>3rV(EI2797GjqZVb|aw#jS0q#2ujV2@RIq%eaW~ zA;!lUpJ|gQ5l>U9j!Ir)ogc={HY;ssY+~FCRN`Hr7JrJ9v@mUSjLu z%PFrEl&UyYM^nFHNqa6SzAv;%DWL~tXg7K)orF{+Qdp;*)*{G~PMq46>F>D!lHhyj zEIC*0+nlqZYJBg-8RE^EhV2Wv5(C-j2-cy@XRF`%9txT_u+A#ZWr`$7hSlUx?G+4q8R)+D>cn2J7r%JivIEv4!zNM(!qC90#Mszkyod#Xa;T z^|~WkkJD3SG$!!X2GofVT?5o2izVl3@OZh24o88E&8W5VEv zoVk@Teh&CIOVmZ=L?Td%(^IYJJaKlp!xl-LExxc+mSiw?JR>z;GoC)ZfFI|IO*Jq+ zI-;($8#>j5neWX zWB#zU_6VTC$?<>GK$m>?+ow;itu9 zPBlZ(;2mwLQW*M9NMgg)NNgkFyd=T>x2T9CIo<>rjXC#Y&7Eq^xMc<*O+lI`pV&OX z9bt==?9s1*mUtHE5SxLqDV8#8*0BWXI9Mm)N9#Dn{cMd&QEWl81uJU7Hck4&PEZGF zu>&UHJDW2>WSpZ^i&HVjosoKGE@N$XB>n_#9ywFK0pglweaU>$7N0a}V% z7~|_?2CX@Z&g4|eCB?mr4*-=|jw)%fs!f9Uj~CHeNbyohM~7v6~aTp z3O2CemLPq_ZK=h*EMd!b^qfu8VQfuz2k285;O*0q7U!BD%>+!S_>`m1a%Q15rV>x0 z_F9-zmu>p#MQ)r|IbkG5D&m=&=v?ynGhRIX!^O9|h*$5~(OotdW0-eNqqW$@60=k? znQ7p~#*1;?pji{dOK5OPVenkJqVxZmna$98pNpqhjdcxOY{VAq)J|txy3qno(b@)@ zCKx|r3&uHfT|mf78ylwMz5d_QW+F1V&WsY0NFHqFj3C8vxMBj_$g%#X&^z@)$*g4J zO&j`4t_LkdO&6Tab<@=U=3->8cFlNelqv;Z@Yv$a>l<2-NnV!4qgifCUUXw(foUZ& zfts%%&gM$|)u5rxC^Hv4#vz+7f~3el&C|1)GmYGZ3+~AUQy*Jm0B8pfs)XpMF`Dt? z;#~AgX8rTSy>6~oQh5$HfoU#SPro+$zqxYa`TvJ6oknom7#FW$dOQ%34~#|lO)N2e zA5TwZ`E+2cE6I)$vsnU~)<1@Mh*c?BM*4nXfDAFww>irNfY21 z&{X0)pcb^dBKE&%7sUplIpG>KBYPkh(B2-gQ?Su7j-GiLV6=|%U^ZLTZkW;)$Irml0m2gXmaqL_?UCk160W%e} z7W8d6DcY|RW54p&@JN~7la`<@_>~a%y}26Gdv-fjWbvBSfiEf1^ zcAmhNQeuDI2Maa(GNRA^uwCIAGWe67lruC6_<;k9E!hTqFIa-g?NE3 zB(6sOG1#+mBJ?JSdD1Oj^%SdA#vI15qcNOCeEj(ql zMA#GX1oL{&7{+xXRQ#-~7YyCvC{u42?-OOJxp)b720L1Oc~XU<8`xdNSH;-vL(;9A zVJflYWI~Ogr%P+Zad)FHv7CE7b$-Br@TJ4-DC zlFp&TyKNM9!k5=1Cx*)l!q&skb3Em50-{-LJqtzcvF7eLc8cbmM ze3s8=$!O*9U}r29Dn0~9Pn7({^(fn~V=P2V>zj3UM_eqTRRBL_7bsIjPOd|5%%sA;1Y}l!e+)VSVDApa3BCp1y?iOvGW)yYKSk3QZdf92U3qU6nkV>GArpzAq(@RxkA349qTR z1ispL7vmq2^D#Q;UD3wAAAoM`dyM5LlKmXW)AS$vev|wO_M`3Kw`b|={CM_bok?=oiIhhr|*+ zr0g31EH$j`I=|H8%c}hc#LZ<>{jJ;`t=t{0+#M&lJ6dtS0D;rW{c(bO`Xo2|3jJVN zXLUsUvE(`b5%G(%jljnkpD)`CdVAR${x6{UtSS_{ok#o|RN*C``Rnv$m#mc=)Ns(% zNX^4OfnzTD$}i54f+XWF6L~-Y%~)} z^8<;%Jkg)!m5d`9`?JnywX`1v?8<&m2P#=IlCc+d6MF@DcRSZ69#E_M?eS4dyc(dk zd?S!AX^7^_^>)8ze4~I~)3CVDMyvXMlTs|~owD9z$=CZ$NpWLy2~4PxW-h6kA z7KBnBHt^ix+?6t3H}`ufIR`o?Q#wJ%pIRts932s#^?S}=C>{MD_vA&{k&<*0ZlX75pi+<>eL)a-uE8n=sh6j^uIazfLPdn zUg{D3NdJYYTNKR-Ig;jp9L$MM6#QiW=lnU*%53OADP^>xTiIX00=Bczd-``))UNwP zG3nSiP+`ULU3l7w8@57z&>vf=XqpKsT>HZD2rr3$%$8g6@ z;Et=oQzp_^!`)iLrQF7@WZLo_6tGE9KUv7I#50F8%;Da$Sk;23jYwVS|31x<$paJ8 zDTtEOxA72~&xRH=(twq;fp?}a<-^{3zNcnF?OX1+V0S z7jx#-oNaZKrZ`y5jkcQIS{qHhr=u@pJzVdUu?ZGt_||eUo7m1;c6T+0=QgDJQ=2$E zw{hxfInKrtJ?q4%fotVDF>&C}GuH80d7YrsZYLf{>%`3iYcqyfa|b?^G2U7+5RrlI zcrRlXcgie_b{V>x>AS78LD-;U6px~$M-QF-xeHQg56_jq-=m& zd15>YTc0^zCYG<2QaN_AFw#e%FHMUzP{boCbsM{N8@pAb?y^w>uUIec)?V=K2JtCyrhFv9 zE0#*yTY9<72CkJ$fX~QP!1eM8;EVDp;3oMpaI<_3xJ~{J*ua`ithtLd_p;_&ta*?% z4?)uXJglEX#p)RC`y6ZZ`qT>&C}A+;WGB)2E>fystY)lZY-BvbI609t z?_+H2K>9}*WfIZ(j1`P)l1Q_{P4fE~8ySz-e2^YWSmGfcTbLGJl9V%6F*bPD=-lM6 zkK}cXjmbn;q!3m!h8b(px})A}8IPn<>M^EeI??%z6^zyCHi^Q_EKa9vbxd0sY=W^N zNIETyd09l4Gs$5_Ew%@|?~GuATJF*Yz(=W>aRb-B;kFxSXT)p?W^M3Tk5 z2(8{sGj_U|=wro%qJ%VSN?1}#SXo9G8btD1#slrc1(X-07!+hHtctTH%($#Ta6&Wo%$< zG4u_j-@+&+abCu9#wx}TV=ZF?V~fF>HTvc3swsq_DQj#Tnx=8j-9osS@fc&}tt6?r zo$wfAy<9PeW9uf1=0k9)noBHtyxa^D=^ zoBCer zN2!jq%(Rhd)6-U_J)gEG?JsG;^as+{rmsuioPIR@)AX;?%Q9}ucr0UM#;%Ng86Vpj zCo_V<;^2_r@ZhN6b-_u&sli#n`+^SzR|KCAei%Fv{CCisnVC5@bAIOInZM6GnAw_n zURG(=(5$UlmuBCbJtO~+~MWbe=ZB>SuE3-F;UJm`Yo#IkT3CmZ*pJK}Ei zdDz&s2lij@iJz%@VFy8Z6GC-GdBP<&mBH2(fFeDKc_vp1j)bjNjY3;99er7zkG@ zM3J}@?pDIxq4?U@71-Hylqkb*ZI_4}pg9TqB;F(jiD{_HEn=9s1E6!Dej(#`AN%x zE0R_LS2I4JMEkaEPO1eqFuuju&HW4{$J~d3!b4kX9Q05wL;o}JdL_hFUdkmr3qhCq zNPi&XP{uXBk0F2GhmTHjF7$>mQJ+kP8WO!L>ou< zq);(GNhQfx#tEq;xjYT=5Y;i7Msh4;BjlfFY<3_TzRDm~E4UpZUyyvy4ekKlIk*eh zEw~@pJ4mJn21)Y~IS(P%iJX-{u9;}gC2Os@q+_c0 zr{v9~lS{>amrI@Husf2HFSs2ErrF&cDMOGkH`oaIrL1#@^BCw@r?mII>6Eps^L)n+ zi9)>Gky0b)k!90zrd4F#tw=R-@NPauK&j8B!c2ff`_heG5y$c=^M5n$>_j@IPNp`d zX6LNakEl`$+Z3J$?GE_fOJ@_Y54O>FbfzNXBY$8Q(z&V&$(MCG3QZHk@qzi}g_LUQ zYN9GG&`yc4_NYSqn}9kqluU@3Ff^fO0<+5n1CoSTSWp3+9hd?PGaC8iuD3u^=l&J& zfedQJH@guYWc;h4dC-pS4tX-dE$EJo>4d_K^;dS5{iJ=D%tP8dpu+T#k69}$&xd3Y zP+>B;0Q4_qC&(i}+)5UmK|dwCK>jpPVXC?i5|1i?#0ym7X8evT#Z;h6_*6H@lYvUi z5Zxi433Q2z@atZ(C<@|$t%$4}4hAlecfV*IFHdOQFT+n%pBjsJ7uf`6#N?arFMe10_nQ{RnGk~}k zh5Sm)Vw^3*kjw!pagV$Y^u0iZ%fS1AKa&ptm&ql-Ns6M?n9Iu~tSv*I@%!DSiV~xcl-r@Fn>KB%82Xs}dW5_%wv91HA=^ zP4VR#&|860ye6Lny`AxOTRsg@15k-Z{EQ>T8;ni3{8C~Ec8{U2N0dF{sKB5DDjiV5zrR^@#L)f5Oimt6ywxiL62p; zR(%Y~c*g71Cy-wU#7#c+56}}BZ&05?G6|?emHG^HHBjNJPG5kY3`G3bs4u}x0pck; z^%dxw8KI3g z76O%cP&t9iRU#xSfXc?s5J?a{syq-q21EcVAL!LUC7w|D{aySDhzB-R3h*hF2FcSv zyltW~K(Awb3BL>@7#TOJY{)kO6+Zlv3;GqH!uA5^f!+*6Fybx+wjA58E&#KQ@io;M zqSqN4)rF8Z0HxTWx`KX#u}O6Y{X5kI^4~M=QWrt8lX17|1^FIzF(hvSrP!xRK<{PT zulj)ggDL~QrTQWDfQ^F^Z>s@d-eEkZ20`*6P>PRK1?ayr{!I;r;70jNhm$A^$fJL9P0X26Ivoey6U6=zE~TTgTS` z-Fh6*qsIe-`g&ldo(P>RJu%|%Av5iM;*btuE!nI!zKamR7XWstKd22>g2U@bcT{_( zdS-YY_Vn`3_wMn&R+d(q_H^2|w5GH}Y1gLTm~N-PkbXGbm64rMlyP0gg6{`A zW%kZ2&-_KEE313f4Ow%u7H2(@^<>td><_X(%>F(*DJLVx3elZ#^N)_Ny9XE3UG(;U z7{L*LPnjY+@WDQTli8^nzwys1BiUdqsz+gYd=2gvkHsJTyIW4h%HP6-OGkQ+Xm)_g zv1OR~yDcN%>M(lS`G_4S9Y$}5BOI6~@AAx(3-EWJ=bj2M_kdX_kK_1H2*1VgThB7F z-MdVr1eS@;_#2GBX8aw;UvkPa(Hno4r7aVG&Jd#hyPnwIsD4f_yn9{0rdSrK`gcp^ zA3Mk~_0Lwwf9d-DL*%Dcq-nT3zy90d@-J5Mq}g-sm{L7`RQ2rHQ);ff>8ARoQS#?j zWa(J>Uc#8ENUDk4I8I)xhul7^{^fD9W1?g7jJdbZsXtyNYgE$gDYIu!n{oTF8Zo2( zsmZd$8@b{R+0`08W7_SJxp&CbeTptEuIN=(adD5{eS7ulQ9QV^vPa*+y~}!BHe_&R z#gMX!%04AS7Kl!rrcaqObw*9mywakgqTZ8>>W9vj9WJil^ps3+p1&q@c#^;1gON+S zez))W$KJa^xa#kjFgCNeWr;Hl8~p}Ct&umt_0=4Ub{n*u_KY^d2kz>7`iV4*pXhZJuh z6!PX-GEfMDgrO!UB^UZDP_k+h8aGygx1p5)m7G9dHbfbT4-9d+sE8r1z%Vl552vCe zEpv!Ky^T*U zh9XbAAagEnrP?cRLIEn&N~Po^8bz=wK{?=u7l;CLz9_x&x=l#DTPOGzP@XV1E$SWGltfL* zRA87V&(TKv7qAmOfGRMBDpM`de&!fz=l~B{6l#b}dP#N;HXs0^jh}!v^im~+KRh+E z`X!k;;+b~+6K!O8C|rjMf)%zy&qR+W%rQKaJbeaWs1wu=&|bAsrUsvhIdUROkPekr zwNa*}w6JQZ#X^V`aM+!UJh)L7bZib3S_LB43kY08g+m8q0XN~v$o`FT_^`z;VR_+x zt~W_IyxxF65MG+2+#&(ODB~5*b|%-0FW|&dJOV8kTETje>P>P)MlIE1tr;D)piDH? zMs{qH&m$)8cv;?^A*hn`I#4WzgD#MsJSz|u5$7wiGNrFoAalIYXv!hYzL6VVks0XP z$X&05hC+FSBH7#JyPtlXF}``a|n5Jjji_o{^WIlszLiZkFja z)Z`e6JgP%Y#)l>j@+NOvfHx5pdV&;b%;!YQEJh3ldgKJc#erTt8b~EvP7U{4DyWjp zg)1ZZTV!T>2ueC3SDP|U;&ZMPc9BtAWUlL42w~^i$lNXRYZ;8pcvY78(0d-!d!g;< z%leJ4N{>ug>F5AI4ibh78H15`w#o|2nWa+gl9ryyJ*L#uEqjJ11%vG3x=)a1ZFa|>_Ph+)HdYw+i zbDGoW%*=8o()2bnP=NM|yt_?4Vz;_@PQmz3bZTH=Vj$Py)Go|2P76E;56%3rFh!y8 zR&cmY8RHV$D2$90nd#KtHpwZ92|{~wa&j;kVWSkwsa!4uP+5p0XL%CU zV!8PrscDcGj%&zp>gc?3>H(7>x;E6{LMw&m2SS@2PL$U`AruJx%$vZ;nNh80qQ?{F z7#7OVamTir8p7s+ah&4WQu!RAXFE1zBtmb zL*{$>Qe%btn*WjGJLF}-&`FFZ#AhWAI4e|opizkij*M)Q#lBYbSzPBwmNm&9Hclt) zT~5J$suI#p7bThrBoMAbFvU$J2oA5-IH9T(thOX3Z3IBLDhGEhX>tr#MFr7AuFmzk ztYW9k)zow0Di@apBG2hORhu&BdUJJ5p?=2i6Ag#bjl!VGK0v*=)$@KX7~AL z;k@tZ>~zE$7WVhWjK+6ClYds)DW1igh}IxnPL7Am@!zC`%XyMpG@h0WSdA#&n#~P{boD zFc-6>AEnZu%sgeYgDVF@EK;&T+^jv67ukI&_#2jJ#X01hFHIjfu}ivxw$z$ zw966rY{&AEU3?WLAUp;R2EsTQkHHukgOlVKoM%rn1Yr*4 zapfWlHEPuL+#D!+`XTDZ5D`6+Pcau_V%1TmN9%q{gGBP)lw}$B`$JFq7cKES5#fPF zVgIaL2b%L|^)ug;Q)Pyz8&xryE_W3EXt(MIvAy?$+at&K$aJYAj=geHpi^_~x(h$e z5}mgqKV26!%lgP(d6$f|?3FVk-u<$lYLWFL_RCjv;#+u=AI3)YulK(#Ka(qpoc{-R CCeKm; diff --git a/bin/HttpServer_OpenSim.pdb b/bin/HttpServer_OpenSim.pdb index a69e420d64b5d6a6826a4b7d59b69292d2bc91ea..cfff9a791b8a052806072c2b1341a583ee167f74 100644 GIT binary patch delta 71221 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 diff --git a/bin/HttpServer_OpenSim.xml b/bin/HttpServer_OpenSim.xml index fa88fc7899..61c3ad8b4e 100644 --- a/bin/HttpServer_OpenSim.xml +++ b/bin/HttpServer_OpenSim.xml @@ -1669,6 +1669,65 @@ A header have been received. + + + A thread-safe lockless queue that supports multiple readers and + multiple writers + + + + Queue head + + + Queue tail + + + Queue item count + + + + Constructor + + + + + Enqueue an item + + Item to enqeue + + + + Try to dequeue an item + + Dequeued item if the dequeue was successful + True if an item was successfully deqeued, otherwise false + + + Gets the current number of items in the queue. Since this + is a lockless collection this value should be treated as a close + estimate + + + + Provides a node container for data in a singly linked list + + + + Pointer to the next node in list + + + The data contained by the node + + + + Constructor + + + + + Constructor + + Contains server side HTTP request information. @@ -2825,6 +2884,11 @@ Kind of HTTPS protocol. Usually TLS or SSL. A created . + + + Server is shutting down so shut down the factory + + A request have been received from one of the contexts. @@ -2876,6 +2940,11 @@ A creates . + + + Server is shutting down so shut down the factory + + True if detailed trace logs should be written. @@ -4315,6 +4384,58 @@ message describing the error + + + Timeout Manager. Checks for dead clients. Clients with open connections that are not doing anything. Closes sessions opened with keepalive. + + + + + Causes the watcher to immediately check the connections. + + + + + Environment.TickCount is an int but it counts all 32 bits so it goes positive + and negative every 24.9 days. This trims down TickCount so it doesn't wrap + for the callers. + This trims it to a 12 day interval so don't let your frame time get too long. + + + + + + Environment.TickCount is an int but it counts all 32 bits so it goes positive + and negative every 24.9 days. Subtracts the passed value (previously fetched by + 'EnvironmentTickCount()') and accounts for any wrapping. + + + + subtraction of passed prevValue from current Environment.TickCount + + + + Environment.TickCount is an int but it counts all 32 bits so it goes positive + and negative every 24.9 days. Subtracts the passed value (previously fetched by + 'EnvironmentTickCount()') and accounts for any wrapping. + + + + subtraction of passed prevValue from current Environment.TickCount + + + + Environment.TickCount is an int but it counts all 32 bits so it goes positive + and negative every 24.9 days. Subtracts the passed value (previously fetched by + 'EnvironmentTickCount()') and accounts for any wrapping. + + subtraction of passed prevValue from current Environment.TickCount + + + + Use a Thread or a Timer to monitor the ugly + + Session store using memory for each session. From fc84ebb819b590099bbfa5bd357e886ce7460063 Mon Sep 17 00:00:00 2001 From: Vegaslon Date: Sat, 16 Mar 2013 17:16:01 -0400 Subject: [PATCH 3/4] BulletSim: Working Implementation of Angular Banking for Vehicles (Not SL Grade, Other features when implemented should slow it down for now be Strong with Vertical Angular attraction setting and conservative with Angular Velocity on X axis) Signed-off-by: Robert Adams --- .../Physics/BulletSPlugin/BSDynamics.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs index d34715969e..96eaa6b76d 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs @@ -143,7 +143,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin { enableAngularVerticalAttraction = true; enableAngularDeflection = false; - enableAngularBanking = false; + enableAngularBanking = true; if (BSParam.VehicleDebuggingEnabled) { enableAngularVerticalAttraction = true; @@ -1280,11 +1280,11 @@ namespace OpenSim.Region.Physics.BulletSPlugin // That is, NO_DEFLECTION_UP says angular motion should not add any pitch or roll movement // TODO: This is here because this is where ODE put it but documentation says it // is a linear effect. Where should this check go? - if ((m_flags & (VehicleFlag.NO_DEFLECTION_UP)) != 0) - { - angularMotorContributionV.X = 0f; - angularMotorContributionV.Y = 0f; - } + //if ((m_flags & (VehicleFlag.NO_DEFLECTION_UP)) != 0) + // { + // angularMotorContributionV.X = 0f; + // angularMotorContributionV.Y = 0f; + // } VehicleRotationalVelocity += angularMotorContributionV * VehicleOrientation; VDetailLog("{0}, MoveAngular,angularTurning,angularMotorContrib={1}", Prim.LocalID, angularMotorContributionV); @@ -1437,24 +1437,25 @@ namespace OpenSim.Region.Physics.BulletSPlugin // As the vehicle rolls to the right or left, the Y value will increase from // zero (straight up) to 1 or -1 (full tilt right or left) Vector3 rollComponents = Vector3.UnitZ * VehicleOrientation; - // Figure out the yaw value for this much roll. // Squared because that seems to give a good value - float yawAngle = (float)Math.Asin(rollComponents.Y * rollComponents.Y) * m_bankingEfficiency; - + // float yawAngle = (float)Math.Asin(rollComponents.X * rollComponents.X) * m_bankingEfficiency; + float yawAngle = m_angularMotorDirection.X * m_bankingEfficiency; // actual error = static turn error + dynamic turn error - float mixedYawAngle = yawAngle * (1f - m_bankingMix) + yawAngle * m_bankingMix * VehicleForwardSpeed; - - // TODO: the banking effect should not go to infinity but what to limit it to? - mixedYawAngle = ClampInRange(-20f, mixedYawAngle, 20f); + float mixedYawAngle =(yawAngle * (1f - m_bankingMix)) + ((yawAngle * m_bankingMix) * VehicleForwardSpeed); + // TODO: the banking effect should not go to infinity but what to limit it to? and what should happen when this is + // being added to a user defined yaw that is already PI*4? + mixedYawAngle = ClampInRange(-12, mixedYawAngle, 12); // Build the force vector to change rotation from what it is to what it should be bankingContributionV.Z = -mixedYawAngle; - // Don't do it all at once. - bankingContributionV /= m_bankingTimescale; + // Don't do it all at once. 60 becouse 1 second is too fast with most user defined roll as PI*4 + bankingContributionV /= m_bankingTimescale*60; - VehicleRotationalVelocity += bankingContributionV * VehicleOrientation; + //VehicleRotationalVelocity += bankingContributionV * VehicleOrientation; + VehicleRotationalVelocity += bankingContributionV; + VDetailLog("{0}, MoveAngular,Banking,rollComp={1},speed={2},rollComp={3},yAng={4},mYAng={5},ret={6}", Prim.LocalID, rollComponents, VehicleForwardSpeed, rollComponents, yawAngle, mixedYawAngle, bankingContributionV); From 464201b41d5f5fdd7c88ab5e95dd7b6fbae6d766 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sat, 16 Mar 2013 15:34:07 -0700 Subject: [PATCH 4/4] BulletSim: add INI parameter for angular banking timescale fudge parameter. --- OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs | 12 ++++++------ OpenSim/Region/Physics/BulletSPlugin/BSParam.cs | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs index 96eaa6b76d..38596fa012 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs @@ -1437,21 +1437,21 @@ namespace OpenSim.Region.Physics.BulletSPlugin // As the vehicle rolls to the right or left, the Y value will increase from // zero (straight up) to 1 or -1 (full tilt right or left) Vector3 rollComponents = Vector3.UnitZ * VehicleOrientation; + // Figure out the yaw value for this much roll. - // Squared because that seems to give a good value - // float yawAngle = (float)Math.Asin(rollComponents.X * rollComponents.X) * m_bankingEfficiency; float yawAngle = m_angularMotorDirection.X * m_bankingEfficiency; // actual error = static turn error + dynamic turn error float mixedYawAngle =(yawAngle * (1f - m_bankingMix)) + ((yawAngle * m_bankingMix) * VehicleForwardSpeed); - // TODO: the banking effect should not go to infinity but what to limit it to? and what should happen when this is - // being added to a user defined yaw that is already PI*4? + + // TODO: the banking effect should not go to infinity but what to limit it to? + // And what should happen when this is being added to a user defined yaw that is already PI*4? mixedYawAngle = ClampInRange(-12, mixedYawAngle, 12); // Build the force vector to change rotation from what it is to what it should be bankingContributionV.Z = -mixedYawAngle; - // Don't do it all at once. 60 becouse 1 second is too fast with most user defined roll as PI*4 - bankingContributionV /= m_bankingTimescale*60; + // Don't do it all at once. Fudge because 1 second is too fast with most user defined roll as PI*4. + bankingContributionV /= m_bankingTimescale * BSParam.VehicleAngularBankingTimescaleFudge; //VehicleRotationalVelocity += bankingContributionV * VehicleOrientation; VehicleRotationalVelocity += bankingContributionV; diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs index 2af84684fc..77bdacb458 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs @@ -123,6 +123,7 @@ public static class BSParam public static Vector3 VehicleLinearFactor { get; private set; } public static Vector3 VehicleAngularFactor { get; private set; } public static float VehicleGroundGravityFudge { get; private set; } + public static float VehicleAngularBankingTimescaleFudge { get; private set; } public static bool VehicleDebuggingEnabled { get; private set; } // Linkset implementation parameters @@ -543,10 +544,14 @@ public static class BSParam 0.0f, (s) => { return VehicleRestitution; }, (s,v) => { VehicleRestitution = v; } ), - new ParameterDefn("VehicleGroundGravityFudge", "Factor to multiple gravity if a ground vehicle is probably on the ground (0.0 - 1.0)", + new ParameterDefn("VehicleGroundGravityFudge", "Factor to multiply gravity if a ground vehicle is probably on the ground (0.0 - 1.0)", 0.2f, (s) => { return VehicleGroundGravityFudge; }, (s,v) => { VehicleGroundGravityFudge = v; } ), + new ParameterDefn("VehicleAngularBankingTimescaleFudge", "Factor to multiple angular banking timescale. Tune to increase realism.", + 60.0f, + (s) => { return VehicleAngularBankingTimescaleFudge; }, + (s,v) => { VehicleAngularBankingTimescaleFudge = v; } ), new ParameterDefn("VehicleDebuggingEnable", "Turn on/off vehicle debugging", false, (s) => { return VehicleDebuggingEnabled; },