From 12042cdc2b53e581ace6a017d9b17bd763a544b2 Mon Sep 17 00:00:00 2001 From: Dr Scofield Date: Mon, 20 Oct 2008 18:07:06 +0000 Subject: [PATCH] From: Alan Webb cleanups and assorted fixes to REST inventory, asset, and appearance services. --- .../Rest/Inventory/IRestHandler.cs | 7 +- .../Rest/Inventory/RequestData.cs | 19 ++-- .../ApplicationPlugins/Rest/Inventory/Rest.cs | 35 ++++---- .../Rest/Inventory/RestAppearanceServices.cs | 15 +++- .../Rest/Inventory/RestAssetServices.cs | 9 +- .../Rest/Inventory/RestHandler.cs | 43 +++++---- .../Rest/Inventory/RestInventoryServices.cs | 87 ++++++++++++++++--- .../Communications/Cache/LibraryRootFolder.cs | 10 ++- 8 files changed, 160 insertions(+), 65 deletions(-) diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs index 82dc2e4e3b..997c2d352d 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/IRestHandler.cs @@ -42,11 +42,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory public delegate RequestData RestMethodAllocator(OSHttpRequest request, OSHttpResponse response, string path); /// - /// 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. + /// This interface exports the generic plugin-handling services + /// available to each loaded REST services module (IRest implementation) /// internal interface IRestHandler diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs index 081327ea99..20e619a559 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RequestData.cs @@ -167,7 +167,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory // internal bool authenticated = false; - internal string scheme = null; + internal string scheme = Rest.Scheme; internal string realm = Rest.Realm; internal string domain = null; internal string nonce = null; @@ -287,11 +287,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory request = p_request; response = p_response; - qprefix = p_qprefix; + qprefix = p_qprefix; sbuilder.Length = 0; encoding = request.ContentEncoding; + if (encoding == null) { encoding = Rest.Encoding; @@ -448,9 +449,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory if (realm != null) { - sbuilder.Append(" realm=\""); + sbuilder.Append(" realm="); + sbuilder.Append(Rest.CS_DQUOTE); sbuilder.Append(realm); - sbuilder.Append("\""); + sbuilder.Append(Rest.CS_DQUOTE); } AddHeader(Rest.HttpHeaderWWWAuthenticate,sbuilder.ToString()); } @@ -677,7 +679,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory UserProfileData udata = Rest.UserServices.GetUserProfile(first, last); - // If we don;t recognize the user id, perhaps it is god? + // If we don't recognize the user id, perhaps it is god? if (udata == null) return pass == Rest.GodKey; @@ -800,6 +802,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory if (!authparms.ContainsKey("cnonce")) { Rest.Log.WarnFormat("{0} Authentication failed: cnonce missing", MsgId); + Fail(Rest.HttpStatusCodeBadRequest); break; } @@ -808,6 +811,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory if (!authparms.TryGetValue("nc", out nck) || nck == null) { Rest.Log.WarnFormat("{0} Authentication failed: cnonce counter missing", MsgId); + Fail(Rest.HttpStatusCodeBadRequest); break; } @@ -820,6 +824,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory if (Rest.Hex2Int(ncl) >= Rest.Hex2Int(nck)) { Rest.Log.WarnFormat("{0} Authentication failed: bad cnonce counter", MsgId); + Fail(Rest.HttpStatusCodeBadRequest); break; } cntable[nonce] = nck; @@ -840,11 +845,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory 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; } } @@ -857,6 +864,8 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory while (false); } + else + Fail(Rest.HttpStatusCodeBadRequest); } diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs index d7935bccfd..badbb08f95 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/Rest.cs @@ -45,6 +45,21 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory 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 @@ -67,9 +82,10 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory internal static bool Secure = true; internal static bool ExtendedEscape = true; internal static bool DumpAsset = false; - internal static bool Fill = true; + internal static bool Fill = false; internal static bool FlushEnabled = true; - internal static string Realm = "REST"; + 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 /// @@ -383,21 +399,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory public const string HttpHeaderWarning = "Warning"; public const string HttpHeaderWWWAuthenticate = "WWW-Authenticate"; - /// - /// Supported authentication schemes - /// - - public const string AS_BASIC = "Basic"; - public const string AS_DIGEST = "Digest"; - - /// Supported Digest algorithms - - public const string Digest_MD5 = "MD5"; // assumed default if omitted - public const string Digest_MD5Sess = "MD5-sess"; - - public const string Qop_Auth = "auth"; - public const string Qop_Int = "auth-int"; - /// Utility routines public static string StringToBase64(string str) diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs index 6983154300..11cda6e018 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs @@ -63,6 +63,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId); Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); + // This is better than a null reference. + + if (Rest.AvatarServices == null) + throw new Exception(String.Format("{0} OpenSim inventory services are not available", + MsgId)); + + if (Rest.UserServices == null) + throw new Exception(String.Format("{0} OpenSim user profile services are not available", + MsgId)); + // If a relative path was specified for the handler's domain, // add the standard prefix to make it absolute, e.g. /admin @@ -170,9 +180,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory try { - // digest scheme seems borked: disable it for the time - // being - rdata.scheme = Rest.AS_BASIC; if (!rdata.IsAuthenticated) { rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); @@ -731,7 +738,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory if (asset != UUID.Zero) { - rdata.writer.WriteAttributeString("Item",asset.ToString()); + rdata.writer.WriteAttributeString("Asset",asset.ToString()); } rdata.writer.WriteEndElement(); } diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs index 9af5cd07e4..f9abc49bbe 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAssetServices.cs @@ -52,6 +52,12 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory Rest.Log.InfoFormat("{0} Asset services initializing", MsgId); Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); + // This is better than a null reference. + + if (Rest.AssetServices == null) + throw new Exception(String.Format("{0} OpenSim asset services are not available", + MsgId)); + // If the handler specifies a relative path for its domain // then we must add the standard absolute prefix, e.g. /admin @@ -130,9 +136,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory try { - // digest scheme seems borked: disable it for the time - // being - rdata.scheme = Rest.AS_BASIC; if (!rdata.IsAuthenticated) { rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated")); diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs index fd4c2954b6..c022e092e8 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestHandler.cs @@ -239,14 +239,17 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory Rest.Prefix = Prefix; Rest.GodKey = GodKey; - Rest.Authenticate = Rest.Config.GetBoolean("authenticate",true); - Rest.Secure = Rest.Config.GetBoolean("secured",true); - Rest.ExtendedEscape = Rest.Config.GetBoolean("extended-escape",true); - Rest.Realm = Rest.Config.GetString("realm","OpenSim REST"); - Rest.DumpAsset = Rest.Config.GetBoolean("dump-asset",false); - Rest.Fill = Rest.Config.GetBoolean("path-fill",true); - Rest.DumpLineSize = Rest.Config.GetInt("dump-line-size",32); - Rest.FlushEnabled = Rest.Config.GetBoolean("flush-on-error",true); + 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 ")); @@ -374,13 +377,13 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory string path = request.RawUrl.ToLower(); - Rest.Log.DebugFormat("{0} Match ENTRY", MsgId); + // 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); + // 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 @@ -388,7 +391,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory if (path.StartsWith(key)) { - Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, 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 @@ -404,7 +407,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory foreach (string key in streamHandlers.Keys) { - Rest.Log.DebugFormat("{0} Match testing {1} against stream prefix <{2}>", MsgId, path, key); + // 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 @@ -412,7 +415,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory if (path.StartsWith(key)) { - Rest.Log.DebugFormat("{0} Matched prefix <{1}>", MsgId, 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 @@ -434,7 +437,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory /// /// This is called by the HTTP server once the handler has indicated - /// that t is able to handle the request. + /// 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 @@ -474,7 +477,11 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory { // 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. + // 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; } @@ -497,7 +504,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory { RequestData rdata = new RequestData(request, response, String.Empty); - string bestMatch = null; + 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); @@ -511,7 +518,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory { if (path.StartsWith(pattern)) { - if (String.IsNullOrEmpty(bestMatch) || pattern.Length > bestMatch.Length) + if (pattern.Length > bestMatch.Length) { bestMatch = pattern; } @@ -520,7 +527,7 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory // Handle using the best match available - if (!String.IsNullOrEmpty(bestMatch)) + if (bestMatch.Length > 0) { Rest.Log.DebugFormat("{0} Stream-based handler matched with <{1}>", MsgId, bestMatch); RestStreamHandler handler = streamHandlers[bestMatch]; diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs index 9cbbf0c717..003c6eece2 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestInventoryServices.cs @@ -31,6 +31,7 @@ using System.IO; using System.Threading; using System.Xml; using System.Drawing; +using System.Timers; using OpenSim.Framework; using OpenSim.Framework.Servers; using OpenSim.Framework.Communications; @@ -61,6 +62,20 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId); Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version); + // This is better than a null reference. + + if (Rest.InventoryServices == null) + throw new Exception(String.Format("{0} OpenSim inventory services are not available", + MsgId)); + + if (Rest.UserServices == null) + throw new Exception(String.Format("{0} OpenSim user services are not available", + MsgId)); + + if (Rest.AssetServices == null) + throw new Exception(String.Format("{0} OpenSim asset services are not available", + MsgId)); + // If a relative path was specified for the handler's domain, // add the standard prefix to make it absolute, e.g. /admin @@ -167,9 +182,6 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory try { - // digest scheme seems borked: disable it for the time - // being - rdata.scheme = Rest.AS_BASIC; if (!rdata.IsAuthenticated) { rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName)); @@ -283,12 +295,22 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}", MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName); - lock (rdata) + + lock (rdata) + { + if (!rdata.HaveInventory) + { + rdata.startWD(1000); + rdata.timeout = false; + Monitor.Wait(rdata); + } + } + + if (rdata.timeout) { - if (!rdata.HaveInventory) - { - Monitor.Wait(rdata); - } + 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) @@ -2145,12 +2167,50 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory internal ICollection items = null; internal UserProfileData userProfile = null; internal InventoryFolderBase root = null; + internal bool timeout = false; + internal System.Timers.Timer watchDog = new System.Timers.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; + watchDog.Start(); + } + + internal void stopWD() + { + Rest.Log.DebugFormat("{0} Reset watchdog", MsgId); + 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 @@ -2160,11 +2220,16 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory internal void GetUserInventory(ICollection folders, ICollection items) { Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId); - this.folders = folders; - this.items = items; - this.HaveInventory = true; lock (this) { + if (watchDog.Enabled) + { + this.stopWD(); + } + this.folders = folders; + this.items = items; + this.HaveInventory = true; + this.timeout = false; Monitor.Pulse(this); } } diff --git a/OpenSim/Framework/Communications/Cache/LibraryRootFolder.cs b/OpenSim/Framework/Communications/Cache/LibraryRootFolder.cs index a33fa67431..437e5e4f2b 100644 --- a/OpenSim/Framework/Communications/Cache/LibraryRootFolder.cs +++ b/OpenSim/Framework/Communications/Cache/LibraryRootFolder.cs @@ -178,8 +178,14 @@ namespace OpenSim.Framework.Communications.Cache if (libraryFolders.ContainsKey(item.Folder)) { InventoryFolderImpl parentFolder = libraryFolders[item.Folder]; - - parentFolder.Items.Add(item.ID, item); + try + { + parentFolder.Items.Add(item.ID, item); + } + catch (Exception) + { + m_log.WarnFormat("[LIBRARY INVENTORY] Item {1} [{0}] not added, duplicate item", item.ID, item.Name); + } } else {