From e977761071a2d614a9a621437fbf86479b414759 Mon Sep 17 00:00:00 2001
From: SignpostMarv
Date: Tue, 23 Oct 2012 15:42:16 +0100
Subject: adding ability for listeners to be filtered by regular expressions
 and a general-purpose function to see if a given string matches a given regex

---
 .../Scripting/WorldComm/WorldCommModule.cs         | 129 ++++++++++++++++++---
 OpenSim/Region/Framework/Interfaces/IWorldComm.cs  |  25 ++++
 .../Shared/Api/Implementation/OSSL_Api.cs          |  63 ++++++++++
 .../ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs |  24 ++++
 .../Shared/Api/Runtime/LSL_Constants.cs            |  10 ++
 .../ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs   |  10 ++
 6 files changed, 242 insertions(+), 19 deletions(-)

diff --git a/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs b/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs
index c68ed6b..cf0eb2a 100644
--- a/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs
+++ b/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs
@@ -28,6 +28,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Text.RegularExpressions;
 using Nini.Config;
 using OpenMetaverse;
 using OpenSim.Framework;
@@ -170,12 +171,42 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
         /// <param name="hostID">UUID of the SceneObjectPart</param>
         /// <param name="channel">channel to listen on</param>
         /// <param name="name">name to filter on</param>
-        /// <param name="id">key to filter on (user given, could be totally faked)</param>
+        /// <param name="id">
+        /// key to filter on (user given, could be totally faked)
+        /// </param>
         /// <param name="msg">msg to filter on</param>
         /// <returns>number of the scripts handle</returns>
-        public int Listen(uint localID, UUID itemID, UUID hostID, int channel, string name, UUID id, string msg)
+        public int Listen(uint localID, UUID itemID, UUID hostID, int channel,
+                string name, UUID id, string msg)
         {
-            return m_listenerManager.AddListener(localID, itemID, hostID, channel, name, id, msg);
+            return m_listenerManager.AddListener(localID, itemID, hostID,
+                channel, name, id, msg);
+        }
+
+        /// <summary>
+        /// Create a listen event callback with the specified filters.
+        /// The parameters localID,itemID are needed to uniquely identify
+        /// the script during 'peek' time. Parameter hostID is needed to
+        /// determine the position of the script.
+        /// </summary>
+        /// <param name="localID">localID of the script engine</param>
+        /// <param name="itemID">UUID of the script engine</param>
+        /// <param name="hostID">UUID of the SceneObjectPart</param>
+        /// <param name="channel">channel to listen on</param>
+        /// <param name="name">name to filter on</param>
+        /// <param name="id">
+        /// key to filter on (user given, could be totally faked)
+        /// </param>
+        /// <param name="msg">msg to filter on</param>
+        /// <param name="regexBitfield">
+        /// Bitfield indicating which strings should be processed as regex.
+        /// </param>
+        /// <returns>number of the scripts handle</returns>
+        public int Listen(uint localID, UUID itemID, UUID hostID, int channel,
+                string name, UUID id, string msg, int regexBitfield)
+        {
+            return m_listenerManager.AddListener(localID, itemID, hostID,
+                    channel, name, id, msg, regexBitfield);
         }
 
         /// <summary>
@@ -465,10 +496,20 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
             m_curlisteners = 0;
         }
 
-        public int AddListener(uint localID, UUID itemID, UUID hostID, int channel, string name, UUID id, string msg)
+        public int AddListener(uint localID, UUID itemID, UUID hostID,
+                int channel, string name, UUID id, string msg)
+        {
+            return AddListener(localID, itemID, hostID, channel, name, id,
+                    msg, 0);
+        }
+
+        public int AddListener(uint localID, UUID itemID, UUID hostID,
+                int channel, string name, UUID id, string msg,
+                int regexBitfield)
         {
             // do we already have a match on this particular filter event?
-            List<ListenerInfo> coll = GetListeners(itemID, channel, name, id, msg);
+            List<ListenerInfo> coll = GetListeners(itemID, channel, name, id,
+                    msg);
 
             if (coll.Count > 0)
             {
@@ -485,7 +526,9 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
 
                     if (newHandle > 0)
                     {
-                        ListenerInfo li = new ListenerInfo(newHandle, localID, itemID, hostID, channel, name, id, msg);
+                        ListenerInfo li = new ListenerInfo(newHandle, localID,
+                                itemID, hostID, channel, name, id, msg,
+                                regexBitfield);
 
                             List<ListenerInfo> listeners;
                             if (!m_listeners.TryGetValue(channel,out listeners))
@@ -626,6 +669,22 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
             return -1;
         }
 
+        /// These are duplicated from ScriptBaseClass
+        /// http://opensimulator.org/mantis/view.php?id=6106#c21945
+        #region Constants for the bitfield parameter of osListenRegex
+
+        /// <summary>
+        /// process name parameter as regex
+        /// </summary>
+        public const int OS_LISTEN_REGEX_NAME = 0x1;
+
+        /// <summary>
+        /// process message parameter as regex
+        /// </summary>
+        public const int OS_LISTEN_REGEX_MESSAGE = 0x2;
+
+        #endregion
+
         // Theres probably a more clever and efficient way to
         // do this, maybe with regex.
         // PM2008: Ha, one could even be smart and define a specialized Enumerator.
@@ -651,7 +710,10 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
                     {
                         continue;
                     }
-                    if (li.GetName().Length > 0 && !li.GetName().Equals(name))
+                    if (li.GetName().Length > 0 && (
+                        ((li.GetRegexBitfield() & OS_LISTEN_REGEX_NAME) != OS_LISTEN_REGEX_NAME && !li.GetName().Equals(name)) ||
+                        ((li.GetRegexBitfield() & OS_LISTEN_REGEX_NAME) == OS_LISTEN_REGEX_NAME && !Regex.IsMatch(name, li.GetName()))
+                    ))
                     {
                         continue;
                     }
@@ -659,7 +721,10 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
                     {
                         continue;
                     }
-                    if (li.GetMessage().Length > 0 && !li.GetMessage().Equals(msg))
+                    if (li.GetMessage().Length > 0 && (
+                        ((li.GetRegexBitfield() & OS_LISTEN_REGEX_MESSAGE) != OS_LISTEN_REGEX_MESSAGE && !li.GetMessage().Equals(msg)) ||
+                        ((li.GetRegexBitfield() & OS_LISTEN_REGEX_MESSAGE) == OS_LISTEN_REGEX_MESSAGE && !Regex.IsMatch(msg, li.GetMessage()))
+                    ))
                     {
                         continue;
                     }
@@ -692,10 +757,13 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
         {
             int idx = 0;
             Object[] item = new Object[6];
+            int dataItemLength = 6;
 
             while (idx < data.Length)
             {
-                Array.Copy(data, idx, item, 0, 6);
+                dataItemLength = (idx + 7 == data.Length || (idx + 7 < data.Length && data[idx + 7] is bool)) ? 7 : 6;
+                item = new Object[dataItemLength];
+                Array.Copy(data, idx, item, 0, dataItemLength);
 
                 ListenerInfo info =
                         ListenerInfo.FromData(localID, itemID, hostID, item);
@@ -707,7 +775,7 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
                     m_listeners[(int)item[2]].Add(info);
                 }
 
-                idx+=6;
+                idx+=dataItemLength;
             }
         }
     }
@@ -723,19 +791,33 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
         private UUID m_id; // ID to filter messages from
         private string m_name; // Object name to filter messages from
         private string m_message; // The message
+        private int m_regexBitfield; // The regex bitfield
 
         public ListenerInfo(int handle, uint localID, UUID ItemID, UUID hostID, int channel, string name, UUID id, string message)
         {
-            Initialise(handle, localID, ItemID, hostID, channel, name, id, message);
+            Initialise(handle, localID, ItemID, hostID, channel, name, id,
+                    message, 0);
+        }
+
+        public ListenerInfo(int handle, uint localID, UUID ItemID,
+                UUID hostID, int channel, string name, UUID id,
+                string message, int regexBitfield)
+        {
+            Initialise(handle, localID, ItemID, hostID, channel, name, id,
+                    message, regexBitfield);
         }
 
         public ListenerInfo(ListenerInfo li, string name, UUID id, string message)
         {
-            Initialise(li.m_handle, li.m_localID, li.m_itemID, li.m_hostID, li.m_channel, name, id, message);
+            Initialise(li.m_handle, li.m_localID, li.m_itemID, li.m_hostID, li.m_channel, name, id, message, 0);
+        }
+
+        public ListenerInfo(ListenerInfo li, string name, UUID id, string message, int regexBitfield)
+        {
+            Initialise(li.m_handle, li.m_localID, li.m_itemID, li.m_hostID, li.m_channel, name, id, message, regexBitfield);
         }
 
-        private void Initialise(int handle, uint localID, UUID ItemID, UUID hostID, int channel, string name,
-                                UUID id, string message)
+        private void Initialise(int handle, uint localID, UUID ItemID, UUID hostID, int channel, string name, UUID id, string message, int regexBitfield)
         {
             m_active = true;
             m_handle = handle;
@@ -746,11 +828,12 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
             m_name = name;
             m_id = id;
             m_message = message;
+            m_regexBitfield = regexBitfield;
         }
 
         public Object[] GetSerializationData()
         {
-            Object[] data = new Object[6];
+            Object[] data = new Object[7];
 
             data[0] = m_active;
             data[1] = m_handle;
@@ -758,16 +841,19 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
             data[3] = m_name;
             data[4] = m_id;
             data[5] = m_message;
+            data[6] = m_regexBitfield;
 
             return data;
         }
 
         public static ListenerInfo FromData(uint localID, UUID ItemID, UUID hostID, Object[] data)
         {
-            ListenerInfo linfo = new ListenerInfo((int)data[1], localID,
-                    ItemID, hostID, (int)data[2], (string)data[3],
-                    (UUID)data[4], (string)data[5]);
-            linfo.m_active=(bool)data[0];
+            ListenerInfo linfo = new ListenerInfo((int)data[1], localID, ItemID, hostID, (int)data[2], (string)data[3], (UUID)data[4], (string)data[5]);
+            linfo.m_active = (bool)data[0];
+            if (data.Length >= 7)
+            {
+                linfo.m_regexBitfield = (int)data[6];
+            }
 
             return linfo;
         }
@@ -826,5 +912,10 @@ namespace OpenSim.Region.CoreModules.Scripting.WorldComm
         {
             return m_id;
         }
+
+        public int GetRegexBitfield()
+        {
+            return m_regexBitfield;
+        }
     }
 }
diff --git a/OpenSim/Region/Framework/Interfaces/IWorldComm.cs b/OpenSim/Region/Framework/Interfaces/IWorldComm.cs
index 8d88065..66b3f3a 100644
--- a/OpenSim/Region/Framework/Interfaces/IWorldComm.cs
+++ b/OpenSim/Region/Framework/Interfaces/IWorldComm.cs
@@ -45,6 +45,14 @@ namespace OpenSim.Region.Framework.Interfaces
         void Deactivate();
         void Activate();
         UUID GetID();
+
+        /// <summary>
+        /// Bitfield indicating which strings should be processed as regex.
+        /// 1 corresponds to IWorldCommListenerInfo::GetName()
+        /// 2 corresponds to IWorldCommListenerInfo::GetMessage()
+        /// </summary>
+        /// <returns></returns>
+        int GetRegexBitfield();
     }
 
     public interface IWorldComm
@@ -70,6 +78,23 @@ namespace OpenSim.Region.Framework.Interfaces
         /// <returns>number of the scripts handle</returns>
         int Listen(uint LocalID, UUID itemID, UUID hostID, int channel, string name, UUID id, string msg);
 
+         /// <summary>
+        /// Create a listen event callback with the specified filters.
+        /// The parameters localID,itemID are needed to uniquely identify
+        /// the script during 'peek' time. Parameter hostID is needed to
+        /// determine the position of the script.
+        /// </summary>
+        /// <param name="LocalID">localID of the script engine</param>
+        /// <param name="itemID">UUID of the script engine</param>
+        /// <param name="hostID">UUID of the SceneObjectPart</param>
+        /// <param name="channel">channel to listen on</param>
+        /// <param name="name">name to filter on</param>
+        /// <param name="id">key to filter on (user given, could be totally faked)</param>
+        /// <param name="msg">msg to filter on</param>
+        /// <param name="regexBitfield">Bitfield indicating which strings should be processed as regex.</param>
+        /// <returns>number of the scripts handle</returns>
+        int Listen(uint LocalID, UUID itemID, UUID hostID, int channel, string name, UUID id, string msg, int regexBitfield);
+
         /// <summary>
         /// This method scans over the objects which registered an interest in listen callbacks.
         /// For everyone it finds, it checks if it fits the given filter. If it does,  then
diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs
index 0650b90..828288d 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs
@@ -3647,5 +3647,68 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
 
             DropAttachmentAt(false, pos, rot);
         }
+
+        public LSL_Integer osListenRegex(int channelID, string name, string ID, string msg, int regexBitfield)
+        {
+            CheckThreatLevel(ThreatLevel.Low, "osListenRegex");
+            m_host.AddScriptLPS(1);
+            UUID keyID;
+            UUID.TryParse(ID, out keyID);
+
+            // if we want the name to be used as a regular expression, ensure it is valid first.
+            if ((regexBitfield & ScriptBaseClass.OS_LISTEN_REGEX_NAME) == ScriptBaseClass.OS_LISTEN_REGEX_NAME)
+            {
+                try
+                {
+                    Regex.IsMatch("", name);
+                }
+                catch (Exception)
+                {
+                    OSSLShoutError("Name regex is invalid.");
+                    return -1;
+                }
+            }
+
+            // if we want the msg to be used as a regular expression, ensure it is valid first.
+            if ((regexBitfield & ScriptBaseClass.OS_LISTEN_REGEX_MESSAGE) == ScriptBaseClass.OS_LISTEN_REGEX_MESSAGE)
+            {
+                try
+                {
+                    Regex.IsMatch("", msg);
+                }
+                catch (Exception)
+                {
+                    OSSLShoutError("Message regex is invalid.");
+                    return -1;
+                }
+            }
+
+            IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>();
+            return (wComm == null) ? -1 : wComm.Listen(
+                m_host.LocalId,
+                m_item.ItemID,
+                m_host.UUID,
+                channelID,
+                name,
+                keyID,
+                msg,
+                regexBitfield
+            );
+        }
+
+        public LSL_Integer osRegexIsMatch(string input, string pattern)
+        {
+            CheckThreatLevel(ThreatLevel.Low, "osRegexIsMatch");
+            m_host.AddScriptLPS(1);
+            try
+            {
+                return Regex.IsMatch(input, pattern) ? 1 : 0;
+            }
+            catch (Exception)
+            {
+                OSSLShoutError("Possible invalid regular expression detected.");
+                return 0;
+            }
+        }
     }
 }
diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs
index 93188c9..cdd9ea8 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs
@@ -418,5 +418,29 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces
         /// <param name="pos"></param>
         /// <param name="rot"></param>
         void osForceDropAttachmentAt(vector pos, rotation rot);
+
+        /// <summary>
+        /// Identical to llListen except for a bitfield which indicates which
+        /// string parameters should be parsed as regex patterns.
+        /// </summary>
+        /// <param name="channelID"></param>
+        /// <param name="name"></param>
+        /// <param name="ID"></param>
+        /// <param name="msg"></param>
+        /// <param name="regexBitfield">
+        /// OS_LISTEN_REGEX_NAME
+        /// OS_LISTEN_REGEX_MESSAGE
+        /// </param>
+        /// <returns></returns>
+        LSL_Integer osListenRegex(int channelID, string name, string ID,
+                string msg, int regexBitfield);
+
+        /// <summary>
+        /// Wraps to bool Regex.IsMatch(string input, string pattern)
+        /// </summary>
+        /// <param name="input">string to test for match</param>
+        /// <param name="regex">string to use as pattern</param>
+        /// <returns>boolean</returns>
+        LSL_Integer osRegexIsMatch(string input, string pattern);
     }
 }
diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs
index 62bd6b8..880841b 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/LSL_Constants.cs
@@ -716,5 +716,15 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase
         public static readonly LSLInteger RCERR_UNKNOWN = -1;        
         public static readonly LSLInteger RCERR_SIM_PERF_LOW = -2;
         public static readonly LSLInteger RCERR_CAST_TIME_EXCEEDED = 3;
+
+        /// <summary>
+        /// process name parameter as regex
+        /// </summary>
+        public const int OS_LISTEN_REGEX_NAME = 0x1;
+
+        /// <summary>
+        /// process message parameter as regex
+        /// </summary>
+        public const int OS_LISTEN_REGEX_MESSAGE = 0x2;
     }
 }
diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs
index dee1b28..afa9ae0 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs
@@ -992,5 +992,15 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase
         {
             m_OSSL_Functions.osForceDropAttachmentAt(pos, rot);
         }
+
+        public LSL_Integer osListenRegex(int channelID, string name, string ID, string msg, int regexBitfield)
+        {
+            return m_OSSL_Functions.osListenRegex(channelID, name, ID, msg, regexBitfield);
+        }
+
+        public LSL_Integer osRegexIsMatch(string input, string pattern)
+        {
+            return m_OSSL_Functions.osRegexIsMatch(input, pattern);
+        }
     }
 }
-- 
cgit v1.1