diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs index 9e0f1384f7..0b9e0e7bea 100644 --- a/OpenSim/Framework/Util.cs +++ b/OpenSim/Framework/Util.cs @@ -1,383 +1,383 @@ -/* - * 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.Data; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Net.Sockets; +/* + * 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.Data; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Net.Sockets; using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using System.Threading; -using log4net; -using Nini.Config; -using Nwc.XmlRpc; -using OpenMetaverse; -using OpenMetaverse.StructuredData; -using Amib.Threading; - -namespace OpenSim.Framework -{ - /// - /// The method used by Util.FireAndForget for asynchronously firing events - /// - /// - /// None is used to execute the method in the same thread that made the call. It should only be used by regression - /// test code that relies on predictable event ordering. - /// RegressionTest is used by regression tests. It fires the call synchronously and does not catch any exceptions. - /// - public enum FireAndForgetMethod - { - None, - RegressionTest, - UnsafeQueueUserWorkItem, - QueueUserWorkItem, - BeginInvoke, - SmartThreadPool, - Thread, - } - - /// - /// Miscellaneous utility functions - /// - public class Util - { - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - private static uint nextXferID = 5000; - private static Random randomClass = new Random(); - - // Get a list of invalid file characters (OS dependent) - private static string regexInvalidFileChars = "[" + new String(Path.GetInvalidFileNameChars()) + "]"; - private static string regexInvalidPathChars = "[" + new String(Path.GetInvalidPathChars()) + "]"; - private static object XferLock = new object(); - - /// - /// Thread pool used for Util.FireAndForget if FireAndForgetMethod.SmartThreadPool is used - /// - private static SmartThreadPool m_ThreadPool; - - // Unix-epoch starts at January 1st 1970, 00:00:00 UTC. And all our times in the server are (or at least should be) in UTC. - private static readonly DateTime unixEpoch = - DateTime.ParseExact("1970-01-01 00:00:00 +0", "yyyy-MM-dd hh:mm:ss z", DateTimeFormatInfo.InvariantInfo).ToUniversalTime(); - - private static readonly string rawUUIDPattern - = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; - public static readonly Regex PermissiveUUIDPattern = new Regex(rawUUIDPattern); - public static readonly Regex UUIDPattern = new Regex(string.Format("^{0}$", rawUUIDPattern)); - - public static FireAndForgetMethod DefaultFireAndForgetMethod = FireAndForgetMethod.SmartThreadPool; - public static FireAndForgetMethod FireAndForgetMethod = DefaultFireAndForgetMethod; - - /// - /// Gets the name of the directory where the current running executable - /// is located - /// - /// Filesystem path to the directory containing the current - /// executable - public static string ExecutingDirectory() - { - return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - } - - /// - /// Linear interpolates B<->C using percent A - /// - /// - /// - /// - /// - public static double lerp(double a, double b, double c) - { - return (b*a) + (c*(1 - a)); - } - - /// - /// Bilinear Interpolate, see Lerp but for 2D using 'percents' X & Y. - /// Layout: - /// A B - /// C D - /// A<->C = Y - /// C<->D = X - /// - /// - /// - /// - /// - /// - /// - /// - public static double lerp2D(double x, double y, double a, double b, double c, double d) - { - return lerp(y, lerp(x, a, b), lerp(x, c, d)); - } - - public static Encoding UTF8 = Encoding.UTF8; - - /// - /// Well known UUID for the blank texture used in the Linden SL viewer version 1.20 (and hopefully onwards) - /// - public static UUID BLANK_TEXTURE_UUID = new UUID("5748decc-f629-461c-9a36-a35a221fe21f"); - - #region Vector Equations - - /// - /// Get the distance between two 3d vectors - /// - /// A 3d vector - /// A 3d vector - /// The distance between the two vectors - public static double GetDistanceTo(Vector3 a, Vector3 b) - { - float dx = a.X - b.X; - float dy = a.Y - b.Y; - float dz = a.Z - b.Z; - return Math.Sqrt(dx * dx + dy * dy + dz * dz); - } - - /// - /// Returns true if the distance beween A and B is less than amount. Significantly faster than GetDistanceTo since it eliminates the Sqrt. - /// - /// - /// - /// - /// - public static bool DistanceLessThan(Vector3 a, Vector3 b, double amount) - { - float dx = a.X - b.X; - float dy = a.Y - b.Y; - float dz = a.Z - b.Z; - return (dx*dx + dy*dy + dz*dz) < (amount*amount); - } - - /// - /// Get the magnitude of a 3d vector - /// - /// A 3d vector - /// The magnitude of the vector - public static double GetMagnitude(Vector3 a) - { - return Math.Sqrt((a.X * a.X) + (a.Y * a.Y) + (a.Z * a.Z)); - } - - /// - /// Get a normalized form of a 3d vector - /// - /// A 3d vector - /// A new vector which is normalized form of the vector - /// The vector paramater cannot be <0,0,0> - public static Vector3 GetNormalizedVector(Vector3 a) - { - if (IsZeroVector(a)) - throw new ArgumentException("Vector paramater cannot be a zero vector."); - - float Mag = (float) GetMagnitude(a); - return new Vector3(a.X / Mag, a.Y / Mag, a.Z / Mag); - } - - /// - /// Returns if a vector is a zero vector (has all zero components) - /// - /// - public static bool IsZeroVector(Vector3 v) - { - if (v.X == 0 && v.Y == 0 && v.Z == 0) - { - return true; - } - - return false; - } - - # endregion - - public static Quaternion Axes2Rot(Vector3 fwd, Vector3 left, Vector3 up) - { - float s; - float tr = (float) (fwd.X + left.Y + up.Z + 1.0); - - if (tr >= 1.0) - { - s = (float) (0.5 / Math.Sqrt(tr)); - return new Quaternion( - (left.Z - up.Y) * s, - (up.X - fwd.Z) * s, - (fwd.Y - left.X) * s, - (float) 0.25 / s); - } - else - { - float max = (left.Y > up.Z) ? left.Y : up.Z; - - if (max < fwd.X) - { - s = (float) (Math.Sqrt(fwd.X - (left.Y + up.Z) + 1.0)); - float x = (float) (s * 0.5); - s = (float) (0.5 / s); - return new Quaternion( - x, - (fwd.Y + left.X) * s, - (up.X + fwd.Z) * s, - (left.Z - up.Y) * s); - } - else if (max == left.Y) - { - s = (float) (Math.Sqrt(left.Y - (up.Z + fwd.X) + 1.0)); - float y = (float) (s * 0.5); - s = (float) (0.5 / s); - return new Quaternion( - (fwd.Y + left.X) * s, - y, - (left.Z + up.Y) * s, - (up.X - fwd.Z) * s); - } - else - { - s = (float) (Math.Sqrt(up.Z - (fwd.X + left.Y) + 1.0)); - float z = (float) (s * 0.5); - s = (float) (0.5 / s); - return new Quaternion( - (up.X + fwd.Z) * s, - (left.Z + up.Y) * s, - z, - (fwd.Y - left.X) * s); - } - } - } - - public static Random RandomClass - { - get { return randomClass; } - } - - public static ulong UIntsToLong(uint X, uint Y) - { - return Utils.UIntsToLong(X, Y); - } - - public static T Clamp(T x, T min, T max) - where T : IComparable - { - return x.CompareTo(max) > 0 ? max : - x.CompareTo(min) < 0 ? min : - x; - } - - public static uint GetNextXferID() - { - uint id = 0; - lock (XferLock) - { - id = nextXferID; - nextXferID++; - } - return id; - } - - public static string GetFileName(string file) - { - // Return just the filename on UNIX platforms - // TODO: this should be customisable with a prefix, but that's something to do later. - if (Environment.OSVersion.Platform == PlatformID.Unix) - { - return file; - } - - // Return %APPDATA%/OpenSim/file for 2K/XP/NT/2K3/VISTA - // TODO: Switch this to System.Enviroment.SpecialFolders.ApplicationData - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - if (!Directory.Exists("%APPDATA%\\OpenSim\\")) - { - Directory.CreateDirectory("%APPDATA%\\OpenSim"); - } - - return "%APPDATA%\\OpenSim\\" + file; - } - - // Catch all - covers older windows versions - // (but those probably wont work anyway) - return file; - } - - /// - /// Debug utility function to convert OSD into formatted XML for debugging purposes. - /// - /// - /// A - /// - /// - /// A - /// - public static string GetFormattedXml(OSD osd) - { - return GetFormattedXml(OSDParser.SerializeLLSDXmlString(osd)); - } - - /// - /// Debug utility function to convert unbroken strings of XML into something human readable for occasional debugging purposes. - /// - /// - /// Please don't delete me even if I appear currently unused! - /// - /// - /// - public static string GetFormattedXml(string rawXml) - { - XmlDocument xd = new XmlDocument(); - xd.LoadXml(rawXml); - - StringBuilder sb = new StringBuilder(); - StringWriter sw = new StringWriter(sb); - - XmlTextWriter xtw = new XmlTextWriter(sw); - xtw.Formatting = Formatting.Indented; - - try - { - xd.WriteTo(xtw); - } - finally - { - xtw.Close(); - } - - return sb.ToString(); +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Threading; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using Amib.Threading; + +namespace OpenSim.Framework +{ + /// + /// The method used by Util.FireAndForget for asynchronously firing events + /// + /// + /// None is used to execute the method in the same thread that made the call. It should only be used by regression + /// test code that relies on predictable event ordering. + /// RegressionTest is used by regression tests. It fires the call synchronously and does not catch any exceptions. + /// + public enum FireAndForgetMethod + { + None, + RegressionTest, + UnsafeQueueUserWorkItem, + QueueUserWorkItem, + BeginInvoke, + SmartThreadPool, + Thread, + } + + /// + /// Miscellaneous utility functions + /// + public class Util + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private static uint nextXferID = 5000; + private static Random randomClass = new Random(); + + // Get a list of invalid file characters (OS dependent) + private static string regexInvalidFileChars = "[" + new String(Path.GetInvalidFileNameChars()) + "]"; + private static string regexInvalidPathChars = "[" + new String(Path.GetInvalidPathChars()) + "]"; + private static object XferLock = new object(); + + /// + /// Thread pool used for Util.FireAndForget if FireAndForgetMethod.SmartThreadPool is used + /// + private static SmartThreadPool m_ThreadPool; + + // Unix-epoch starts at January 1st 1970, 00:00:00 UTC. And all our times in the server are (or at least should be) in UTC. + private static readonly DateTime unixEpoch = + DateTime.ParseExact("1970-01-01 00:00:00 +0", "yyyy-MM-dd hh:mm:ss z", DateTimeFormatInfo.InvariantInfo).ToUniversalTime(); + + private static readonly string rawUUIDPattern + = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; + public static readonly Regex PermissiveUUIDPattern = new Regex(rawUUIDPattern); + public static readonly Regex UUIDPattern = new Regex(string.Format("^{0}$", rawUUIDPattern)); + + public static FireAndForgetMethod DefaultFireAndForgetMethod = FireAndForgetMethod.SmartThreadPool; + public static FireAndForgetMethod FireAndForgetMethod = DefaultFireAndForgetMethod; + + /// + /// Gets the name of the directory where the current running executable + /// is located + /// + /// Filesystem path to the directory containing the current + /// executable + public static string ExecutingDirectory() + { + return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } + + /// + /// Linear interpolates B<->C using percent A + /// + /// + /// + /// + /// + public static double lerp(double a, double b, double c) + { + return (b*a) + (c*(1 - a)); + } + + /// + /// Bilinear Interpolate, see Lerp but for 2D using 'percents' X & Y. + /// Layout: + /// A B + /// C D + /// A<->C = Y + /// C<->D = X + /// + /// + /// + /// + /// + /// + /// + /// + public static double lerp2D(double x, double y, double a, double b, double c, double d) + { + return lerp(y, lerp(x, a, b), lerp(x, c, d)); + } + + public static Encoding UTF8 = Encoding.UTF8; + + /// + /// Well known UUID for the blank texture used in the Linden SL viewer version 1.20 (and hopefully onwards) + /// + public static UUID BLANK_TEXTURE_UUID = new UUID("5748decc-f629-461c-9a36-a35a221fe21f"); + + #region Vector Equations + + /// + /// Get the distance between two 3d vectors + /// + /// A 3d vector + /// A 3d vector + /// The distance between the two vectors + public static double GetDistanceTo(Vector3 a, Vector3 b) + { + float dx = a.X - b.X; + float dy = a.Y - b.Y; + float dz = a.Z - b.Z; + return Math.Sqrt(dx * dx + dy * dy + dz * dz); + } + + /// + /// Returns true if the distance beween A and B is less than amount. Significantly faster than GetDistanceTo since it eliminates the Sqrt. + /// + /// + /// + /// + /// + public static bool DistanceLessThan(Vector3 a, Vector3 b, double amount) + { + float dx = a.X - b.X; + float dy = a.Y - b.Y; + float dz = a.Z - b.Z; + return (dx*dx + dy*dy + dz*dz) < (amount*amount); + } + + /// + /// Get the magnitude of a 3d vector + /// + /// A 3d vector + /// The magnitude of the vector + public static double GetMagnitude(Vector3 a) + { + return Math.Sqrt((a.X * a.X) + (a.Y * a.Y) + (a.Z * a.Z)); + } + + /// + /// Get a normalized form of a 3d vector + /// + /// A 3d vector + /// A new vector which is normalized form of the vector + /// The vector paramater cannot be <0,0,0> + public static Vector3 GetNormalizedVector(Vector3 a) + { + if (IsZeroVector(a)) + throw new ArgumentException("Vector paramater cannot be a zero vector."); + + float Mag = (float) GetMagnitude(a); + return new Vector3(a.X / Mag, a.Y / Mag, a.Z / Mag); + } + + /// + /// Returns if a vector is a zero vector (has all zero components) + /// + /// + public static bool IsZeroVector(Vector3 v) + { + if (v.X == 0 && v.Y == 0 && v.Z == 0) + { + return true; + } + + return false; + } + + # endregion + + public static Quaternion Axes2Rot(Vector3 fwd, Vector3 left, Vector3 up) + { + float s; + float tr = (float) (fwd.X + left.Y + up.Z + 1.0); + + if (tr >= 1.0) + { + s = (float) (0.5 / Math.Sqrt(tr)); + return new Quaternion( + (left.Z - up.Y) * s, + (up.X - fwd.Z) * s, + (fwd.Y - left.X) * s, + (float) 0.25 / s); + } + else + { + float max = (left.Y > up.Z) ? left.Y : up.Z; + + if (max < fwd.X) + { + s = (float) (Math.Sqrt(fwd.X - (left.Y + up.Z) + 1.0)); + float x = (float) (s * 0.5); + s = (float) (0.5 / s); + return new Quaternion( + x, + (fwd.Y + left.X) * s, + (up.X + fwd.Z) * s, + (left.Z - up.Y) * s); + } + else if (max == left.Y) + { + s = (float) (Math.Sqrt(left.Y - (up.Z + fwd.X) + 1.0)); + float y = (float) (s * 0.5); + s = (float) (0.5 / s); + return new Quaternion( + (fwd.Y + left.X) * s, + y, + (left.Z + up.Y) * s, + (up.X - fwd.Z) * s); + } + else + { + s = (float) (Math.Sqrt(up.Z - (fwd.X + left.Y) + 1.0)); + float z = (float) (s * 0.5); + s = (float) (0.5 / s); + return new Quaternion( + (up.X + fwd.Z) * s, + (left.Z + up.Y) * s, + z, + (fwd.Y - left.X) * s); + } + } + } + + public static Random RandomClass + { + get { return randomClass; } + } + + public static ulong UIntsToLong(uint X, uint Y) + { + return Utils.UIntsToLong(X, Y); + } + + public static T Clamp(T x, T min, T max) + where T : IComparable + { + return x.CompareTo(max) > 0 ? max : + x.CompareTo(min) < 0 ? min : + x; + } + + public static uint GetNextXferID() + { + uint id = 0; + lock (XferLock) + { + id = nextXferID; + nextXferID++; + } + return id; + } + + public static string GetFileName(string file) + { + // Return just the filename on UNIX platforms + // TODO: this should be customisable with a prefix, but that's something to do later. + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + return file; + } + + // Return %APPDATA%/OpenSim/file for 2K/XP/NT/2K3/VISTA + // TODO: Switch this to System.Enviroment.SpecialFolders.ApplicationData + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + if (!Directory.Exists("%APPDATA%\\OpenSim\\")) + { + Directory.CreateDirectory("%APPDATA%\\OpenSim"); + } + + return "%APPDATA%\\OpenSim\\" + file; + } + + // Catch all - covers older windows versions + // (but those probably wont work anyway) + return file; + } + + /// + /// Debug utility function to convert OSD into formatted XML for debugging purposes. + /// + /// + /// A + /// + /// + /// A + /// + public static string GetFormattedXml(OSD osd) + { + return GetFormattedXml(OSDParser.SerializeLLSDXmlString(osd)); + } + + /// + /// Debug utility function to convert unbroken strings of XML into something human readable for occasional debugging purposes. + /// + /// + /// Please don't delete me even if I appear currently unused! + /// + /// + /// + public static string GetFormattedXml(string rawXml) + { + XmlDocument xd = new XmlDocument(); + xd.LoadXml(rawXml); + + StringBuilder sb = new StringBuilder(); + StringWriter sw = new StringWriter(sb); + + XmlTextWriter xtw = new XmlTextWriter(sw); + xtw.Formatting = Formatting.Indented; + + try + { + xd.WriteTo(xtw); + } + finally + { + xtw.Close(); + } + + return sb.ToString(); } /// @@ -392,1118 +392,1118 @@ namespace OpenSim.Framework || platformId == PlatformID.Win32S || platformId == PlatformID.Win32Windows || platformId == PlatformID.WinCE); - } - - public static bool LoadArchSpecificWindowsDll(string libraryName) - { - // We do this so that OpenSimulator on Windows loads the correct native library depending on whether - // it's running as a 32-bit process or a 64-bit one. By invoking LoadLibary here, later DLLImports - // will find it already loaded later on. - // - // This isn't necessary for other platforms (e.g. Mac OSX and Linux) since the DLL used can be - // controlled in config files. - string nativeLibraryPath; - - if (Util.Is64BitProcess()) - nativeLibraryPath = "lib64/" + libraryName; - else - nativeLibraryPath = "lib32/" + libraryName; - - m_log.DebugFormat("[UTIL]: Loading native Windows library at {0}", nativeLibraryPath); - - if (Util.LoadLibrary(nativeLibraryPath) == IntPtr.Zero) - { - m_log.ErrorFormat( - "[UTIL]: Couldn't find native Windows library at {0}", nativeLibraryPath); - - return false; - } - else - { - return true; - } - } - - public static bool IsEnvironmentSupported(ref string reason) - { - // Must have .NET 2.0 (Generics / libsl) - if (Environment.Version.Major < 2) - { - reason = ".NET 1.0/1.1 lacks components that is used by OpenSim"; - return false; - } - - // Windows 95/98/ME are unsupported - if (Environment.OSVersion.Platform == PlatformID.Win32Windows && - Environment.OSVersion.Platform != PlatformID.Win32NT) - { - reason = "Windows 95/98/ME will not run OpenSim"; - return false; - } - - // Windows 2000 / Pre-SP2 XP - if (Environment.OSVersion.Version.Major == 5 && - Environment.OSVersion.Version.Minor == 0) - { - reason = "Please update to Windows XP Service Pack 2 or Server2003"; - return false; - } - - return true; - } - - public static int UnixTimeSinceEpoch() - { - return ToUnixTime(DateTime.UtcNow); - } - - public static int ToUnixTime(DateTime stamp) - { - TimeSpan t = stamp.ToUniversalTime() - unixEpoch; - return (int) t.TotalSeconds; - } - - public static DateTime ToDateTime(ulong seconds) - { - DateTime epoch = unixEpoch; - return epoch.AddSeconds(seconds); - } - - public static DateTime ToDateTime(int seconds) - { - DateTime epoch = unixEpoch; - return epoch.AddSeconds(seconds); - } - - /// - /// Return an md5 hash of the given string - /// - /// - /// - public static string Md5Hash(string data) - { - byte[] dataMd5 = ComputeMD5Hash(data); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < dataMd5.Length; i++) - sb.AppendFormat("{0:x2}", dataMd5[i]); - return sb.ToString(); - } - - private static byte[] ComputeMD5Hash(string data) - { - MD5 md5 = MD5.Create(); - return md5.ComputeHash(Encoding.Default.GetBytes(data)); - } - - /// - /// Return an SHA1 hash - /// - /// - /// - public static string SHA1Hash(string data) - { - return SHA1Hash(Encoding.Default.GetBytes(data)); - } - - /// - /// Return an SHA1 hash - /// - /// - /// - public static string SHA1Hash(byte[] data) - { - byte[] hash = ComputeSHA1Hash(data); - return BitConverter.ToString(hash).Replace("-", String.Empty); - } - - private static byte[] ComputeSHA1Hash(byte[] src) - { - SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider(); - return SHA1.ComputeHash(src); - } - - public static int fast_distance2d(int x, int y) - { - x = Math.Abs(x); - y = Math.Abs(y); - - int min = Math.Min(x, y); - - return (x + y - (min >> 1) - (min >> 2) + (min >> 4)); - } - - /// - /// Are the co-ordinates of the new region visible from the old region? - /// - /// Old region x-coord - /// New region x-coord - /// Old region y-coord - /// New region y-coord - /// - public static bool IsOutsideView(float drawdist, uint oldx, uint newx, uint oldy, uint newy) - { - int dd = (int)((drawdist + Constants.RegionSize - 1) / Constants.RegionSize); - - int startX = (int)oldx - dd; - int startY = (int)oldy - dd; - - int endX = (int)oldx + dd; - int endY = (int)oldy + dd; - - return (newx < startX || endX < newx || newy < startY || endY < newy); - } - - public static string FieldToString(byte[] bytes) - { - return FieldToString(bytes, String.Empty); - } - - /// - /// Convert a variable length field (byte array) to a string, with a - /// field name prepended to each line of the output - /// - /// If the byte array has unprintable characters in it, a - /// hex dump will be put in the string instead - /// The byte array to convert to a string - /// A field name to prepend to each line of output - /// An ASCII string or a string containing a hex dump, minus - /// the null terminator - public static string FieldToString(byte[] bytes, string fieldName) - { - // Check for a common case - if (bytes.Length == 0) return String.Empty; - - StringBuilder output = new StringBuilder(); - bool printable = true; - - for (int i = 0; i < bytes.Length; ++i) - { - // Check if there are any unprintable characters in the array - if ((bytes[i] < 0x20 || bytes[i] > 0x7E) && bytes[i] != 0x09 - && bytes[i] != 0x0D && bytes[i] != 0x0A && bytes[i] != 0x00) - { - printable = false; - break; - } - } - - if (printable) - { - if (fieldName.Length > 0) - { - output.Append(fieldName); - output.Append(": "); - } - - output.Append(CleanString(Util.UTF8.GetString(bytes, 0, bytes.Length - 1))); - } - else - { - for (int i = 0; i < bytes.Length; i += 16) - { - if (i != 0) - output.Append(Environment.NewLine); - if (fieldName.Length > 0) - { - output.Append(fieldName); - output.Append(": "); - } - - for (int j = 0; j < 16; j++) - { - if ((i + j) < bytes.Length) - output.Append(String.Format("{0:X2} ", bytes[i + j])); - else - output.Append(" "); - } - - for (int j = 0; j < 16 && (i + j) < bytes.Length; j++) - { - if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E) - output.Append((char) bytes[i + j]); - else - output.Append("."); - } - } - } - - return output.ToString(); - } - - /// - /// Converts a URL to a IPAddress - /// - /// URL Standard Format - /// A resolved IP Address - public static IPAddress GetHostFromURL(string url) - { - return GetHostFromDNS(url.Split(new char[] {'/', ':'})[3]); - } - - /// - /// Returns a IP address from a specified DNS, favouring IPv4 addresses. - /// - /// DNS Hostname - /// An IP address, or null - public static IPAddress GetHostFromDNS(string dnsAddress) - { - // Is it already a valid IP? No need to look it up. - IPAddress ipa; - if (IPAddress.TryParse(dnsAddress, out ipa)) - return ipa; - - IPAddress[] hosts = null; - - // Not an IP, lookup required - try - { - hosts = Dns.GetHostEntry(dnsAddress).AddressList; - } - catch (Exception e) - { - m_log.WarnFormat("[UTIL]: An error occurred while resolving host name {0}, {1}", dnsAddress, e); - - // Still going to throw the exception on for now, since this was what was happening in the first place - throw e; - } - - foreach (IPAddress host in hosts) - { - if (host.AddressFamily == AddressFamily.InterNetwork) - { - return host; - } - } - - if (hosts.Length > 0) - return hosts[0]; - - return null; - } - - public static Uri GetURI(string protocol, string hostname, int port, string path) - { - return new UriBuilder(protocol, hostname, port, path).Uri; - } - - /// - /// Gets a list of all local system IP addresses - /// - /// - public static IPAddress[] GetLocalHosts() - { - return Dns.GetHostAddresses(Dns.GetHostName()); - } - - public static IPAddress GetLocalHost() - { - IPAddress[] iplist = GetLocalHosts(); - - if (iplist.Length == 0) // No accessible external interfaces - { - IPAddress[] loopback = Dns.GetHostAddresses("localhost"); - IPAddress localhost = loopback[0]; - - return localhost; - } - - foreach (IPAddress host in iplist) - { - if (!IPAddress.IsLoopback(host) && host.AddressFamily == AddressFamily.InterNetwork) - { - return host; - } - } - - if (iplist.Length > 0) - { - foreach (IPAddress host in iplist) - { - if (host.AddressFamily == AddressFamily.InterNetwork) - return host; - } - // Well all else failed... - return iplist[0]; - } - - return null; - } - - /// - /// Removes all invalid path chars (OS dependent) - /// - /// path - /// safe path - public static string safePath(string path) - { - return Regex.Replace(path, regexInvalidPathChars, String.Empty); - } - - /// - /// Removes all invalid filename chars (OS dependent) - /// - /// filename - /// safe filename - public static string safeFileName(string filename) - { - return Regex.Replace(filename, regexInvalidFileChars, String.Empty); - ; - } - - // - // directory locations - // - - public static string homeDir() - { - string temp; - // string personal=(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); - // temp = Path.Combine(personal,".OpenSim"); - temp = "."; - return temp; - } - - public static string assetsDir() - { - return Path.Combine(configDir(), "assets"); - } - - public static string inventoryDir() - { - return Path.Combine(configDir(), "inventory"); - } - - public static string configDir() - { - return "."; - } - - public static string dataDir() - { - return "."; - } - - public static string logDir() - { - return "."; - } - - // From: http://coercedcode.blogspot.com/2008/03/c-generate-unique-filenames-within.html - public static string GetUniqueFilename(string FileName) - { - int count = 0; - string Name; - - if (File.Exists(FileName)) - { - FileInfo f = new FileInfo(FileName); - - if (!String.IsNullOrEmpty(f.Extension)) - { - Name = f.FullName.Substring(0, f.FullName.LastIndexOf('.')); - } - else - { - Name = f.FullName; - } - - while (File.Exists(FileName)) - { - count++; - FileName = Name + count + f.Extension; - } - } - return FileName; - } - - // Nini (config) related Methods - public static IConfigSource ConvertDataRowToXMLConfig(DataRow row, string fileName) - { - if (!File.Exists(fileName)) - { - //create new file - } - XmlConfigSource config = new XmlConfigSource(fileName); - AddDataRowToConfig(config, row); - config.Save(); - - return config; - } - - public static void AddDataRowToConfig(IConfigSource config, DataRow row) - { - config.Configs.Add((string) row[0]); - for (int i = 0; i < row.Table.Columns.Count; i++) - { - config.Configs[(string) row[0]].Set(row.Table.Columns[i].ColumnName, row[i]); - } - } - - public static float Clip(float x, float min, float max) - { - return Math.Min(Math.Max(x, min), max); - } - - public static int Clip(int x, int min, int max) - { - return Math.Min(Math.Max(x, min), max); - } - - /// - /// Convert an UUID to a raw uuid string. Right now this is a string without hyphens. - /// - /// - /// - public static String ToRawUuidString(UUID UUID) - { - return UUID.Guid.ToString("n"); - } - - public static string CleanString(string input) - { - if (input.Length == 0) - return input; - - int clip = input.Length; - - // Test for ++ string terminator - int pos = input.IndexOf("\0"); - if (pos != -1 && pos < clip) - clip = pos; - - // Test for CR - pos = input.IndexOf("\r"); - if (pos != -1 && pos < clip) - clip = pos; - - // Test for LF - pos = input.IndexOf("\n"); - if (pos != -1 && pos < clip) - clip = pos; - - // Truncate string before first end-of-line character found - return input.Substring(0, clip); - } - - /// - /// returns the contents of /etc/issue on Unix Systems - /// Use this for where it's absolutely necessary to implement platform specific stuff - /// - /// - public static string ReadEtcIssue() - { - try - { - StreamReader sr = new StreamReader("/etc/issue.net"); - string issue = sr.ReadToEnd(); - sr.Close(); - return issue; - } - catch (Exception) - { - return ""; - } - } - - public static void SerializeToFile(string filename, Object obj) - { - IFormatter formatter = new BinaryFormatter(); - Stream stream = null; - - try - { - stream = new FileStream( - filename, FileMode.Create, - FileAccess.Write, FileShare.None); - - formatter.Serialize(stream, obj); - } - catch (Exception e) - { - m_log.Error(e.ToString()); - } - finally - { - if (stream != null) - { - stream.Close(); - } - } - } - - public static Object DeserializeFromFile(string filename) - { - IFormatter formatter = new BinaryFormatter(); - Stream stream = null; - Object ret = null; - - try - { - stream = new FileStream( - filename, FileMode.Open, - FileAccess.Read, FileShare.None); - - ret = formatter.Deserialize(stream); - } - catch (Exception e) - { - m_log.Error(e.ToString()); - } - finally - { - if (stream != null) - { - stream.Close(); - } - } - - return ret; - } - - public static string Compress(string text) - { - byte[] buffer = Util.UTF8.GetBytes(text); - MemoryStream memory = new MemoryStream(); - using (GZipStream compressor = new GZipStream(memory, CompressionMode.Compress, true)) - { - compressor.Write(buffer, 0, buffer.Length); - } - - memory.Position = 0; - - byte[] compressed = new byte[memory.Length]; - memory.Read(compressed, 0, compressed.Length); - - byte[] compressedBuffer = new byte[compressed.Length + 4]; - Buffer.BlockCopy(compressed, 0, compressedBuffer, 4, compressed.Length); - Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, compressedBuffer, 0, 4); - return Convert.ToBase64String(compressedBuffer); - } - - public static string Decompress(string compressedText) - { - byte[] compressedBuffer = Convert.FromBase64String(compressedText); - using (MemoryStream memory = new MemoryStream()) - { - int msgLength = BitConverter.ToInt32(compressedBuffer, 0); - memory.Write(compressedBuffer, 4, compressedBuffer.Length - 4); - - byte[] buffer = new byte[msgLength]; - - memory.Position = 0; - using (GZipStream decompressor = new GZipStream(memory, CompressionMode.Decompress)) - { - decompressor.Read(buffer, 0, buffer.Length); - } - - return Util.UTF8.GetString(buffer); - } - } - - public static XmlRpcResponse XmlRpcCommand(string url, string methodName, params object[] args) - { - return SendXmlRpcCommand(url, methodName, args); - } - - public static XmlRpcResponse SendXmlRpcCommand(string url, string methodName, object[] args) - { - XmlRpcRequest client = new XmlRpcRequest(methodName, args); - return client.Send(url, 6000); - } - - /// - /// Returns an error message that the user could not be found in the database - /// - /// XML string consisting of a error element containing individual error(s) - public static XmlRpcResponse CreateUnknownUserErrorResponse() - { - XmlRpcResponse response = new XmlRpcResponse(); - Hashtable responseData = new Hashtable(); - responseData["error_type"] = "unknown_user"; - responseData["error_desc"] = "The user requested is not in the database"; - - response.Value = responseData; - return response; - } - - /// - /// Converts a byte array in big endian order into an ulong. - /// - /// - /// The array of bytes - /// - /// - /// The extracted ulong - /// - public static ulong BytesToUInt64Big(byte[] bytes) - { - if (bytes.Length < 8) return 0; - return ((ulong)bytes[0] << 56) | ((ulong)bytes[1] << 48) | ((ulong)bytes[2] << 40) | ((ulong)bytes[3] << 32) | - ((ulong)bytes[4] << 24) | ((ulong)bytes[5] << 16) | ((ulong)bytes[6] << 8) | (ulong)bytes[7]; - } - - // used for RemoteParcelRequest (for "About Landmark") - public static UUID BuildFakeParcelID(ulong regionHandle, uint x, uint y) - { - byte[] bytes = - { - (byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24), - (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56), - (byte)x, (byte)(x >> 8), 0, 0, - (byte)y, (byte)(y >> 8), 0, 0 }; - return new UUID(bytes, 0); - } - - public static UUID BuildFakeParcelID(ulong regionHandle, uint x, uint y, uint z) - { - byte[] bytes = - { - (byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24), - (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56), - (byte)x, (byte)(x >> 8), (byte)z, (byte)(z >> 8), - (byte)y, (byte)(y >> 8), 0, 0 }; - return new UUID(bytes, 0); - } - - public static void ParseFakeParcelID(UUID parcelID, out ulong regionHandle, out uint x, out uint y) - { - byte[] bytes = parcelID.GetBytes(); - regionHandle = Utils.BytesToUInt64(bytes); - x = Utils.BytesToUInt(bytes, 8) & 0xffff; - y = Utils.BytesToUInt(bytes, 12) & 0xffff; - } - - public static void ParseFakeParcelID(UUID parcelID, out ulong regionHandle, out uint x, out uint y, out uint z) - { - byte[] bytes = parcelID.GetBytes(); - regionHandle = Utils.BytesToUInt64(bytes); - x = Utils.BytesToUInt(bytes, 8) & 0xffff; - z = (Utils.BytesToUInt(bytes, 8) & 0xffff0000) >> 16; - y = Utils.BytesToUInt(bytes, 12) & 0xffff; - } - - public static void FakeParcelIDToGlobalPosition(UUID parcelID, out uint x, out uint y) - { - ulong regionHandle; - uint rx, ry; - - ParseFakeParcelID(parcelID, out regionHandle, out x, out y); - Utils.LongToUInts(regionHandle, out rx, out ry); - - x += rx; - y += ry; - } - - /// - /// Get operating system information if available. Returns only the first 45 characters of information - /// - /// - /// Operating system information. Returns an empty string if none was available. - /// - public static string GetOperatingSystemInformation() - { - string os = String.Empty; - - if (Environment.OSVersion.Platform != PlatformID.Unix) - { - os = Environment.OSVersion.ToString(); - } - else - { - os = ReadEtcIssue(); - } - - if (os.Length > 45) - { - os = os.Substring(0, 45); - } - - return os; - } - - public static string GetRuntimeInformation() - { - string ru = String.Empty; - - if (Environment.OSVersion.Platform == PlatformID.Unix) - ru = "Unix/Mono"; - else - if (Environment.OSVersion.Platform == PlatformID.MacOSX) - ru = "OSX/Mono"; - else - { - if (Type.GetType("Mono.Runtime") != null) - ru = "Win/Mono"; - else - ru = "Win/.NET"; - } - - return ru; - } - - /// - /// Is the given string a UUID? - /// - /// - /// - public static bool isUUID(string s) - { - return UUIDPattern.IsMatch(s); - } - - public static string GetDisplayConnectionString(string connectionString) - { - int passPosition = 0; - int passEndPosition = 0; - string displayConnectionString = null; - - // hide the password in the connection string - passPosition = connectionString.IndexOf("password", StringComparison.OrdinalIgnoreCase); - passPosition = connectionString.IndexOf("=", passPosition); - if (passPosition < connectionString.Length) - passPosition += 1; - passEndPosition = connectionString.IndexOf(";", passPosition); - - displayConnectionString = connectionString.Substring(0, passPosition); - displayConnectionString += "***"; - displayConnectionString += connectionString.Substring(passEndPosition, connectionString.Length - passEndPosition); - - return displayConnectionString; - } - - public static T ReadSettingsFromIniFile(IConfig config, T settingsClass) - { - Type settingsType = settingsClass.GetType(); - - FieldInfo[] fieldInfos = settingsType.GetFields(); - foreach (FieldInfo fieldInfo in fieldInfos) - { - if (!fieldInfo.IsStatic) - { - if (fieldInfo.FieldType == typeof(System.String)) - { - fieldInfo.SetValue(settingsClass, config.Get(fieldInfo.Name, (string)fieldInfo.GetValue(settingsClass))); - } - else if (fieldInfo.FieldType == typeof(System.Boolean)) - { - fieldInfo.SetValue(settingsClass, config.GetBoolean(fieldInfo.Name, (bool)fieldInfo.GetValue(settingsClass))); - } - else if (fieldInfo.FieldType == typeof(System.Int32)) - { - fieldInfo.SetValue(settingsClass, config.GetInt(fieldInfo.Name, (int)fieldInfo.GetValue(settingsClass))); - } - else if (fieldInfo.FieldType == typeof(System.Single)) - { - fieldInfo.SetValue(settingsClass, config.GetFloat(fieldInfo.Name, (float)fieldInfo.GetValue(settingsClass))); - } - else if (fieldInfo.FieldType == typeof(System.UInt32)) - { - fieldInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(fieldInfo.Name, ((uint)fieldInfo.GetValue(settingsClass)).ToString()))); - } - } - } - - PropertyInfo[] propertyInfos = settingsType.GetProperties(); - foreach (PropertyInfo propInfo in propertyInfos) - { - if ((propInfo.CanRead) && (propInfo.CanWrite)) - { - if (propInfo.PropertyType == typeof(System.String)) - { - propInfo.SetValue(settingsClass, config.Get(propInfo.Name, (string)propInfo.GetValue(settingsClass, null)), null); - } - else if (propInfo.PropertyType == typeof(System.Boolean)) - { - propInfo.SetValue(settingsClass, config.GetBoolean(propInfo.Name, (bool)propInfo.GetValue(settingsClass, null)), null); - } - else if (propInfo.PropertyType == typeof(System.Int32)) - { - propInfo.SetValue(settingsClass, config.GetInt(propInfo.Name, (int)propInfo.GetValue(settingsClass, null)), null); - } - else if (propInfo.PropertyType == typeof(System.Single)) - { - propInfo.SetValue(settingsClass, config.GetFloat(propInfo.Name, (float)propInfo.GetValue(settingsClass, null)), null); - } - if (propInfo.PropertyType == typeof(System.UInt32)) - { - propInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(propInfo.Name, ((uint)propInfo.GetValue(settingsClass, null)).ToString())), null); - } - } - } - - return settingsClass; - } - - public static string Base64ToString(string str) - { - UTF8Encoding encoder = new UTF8Encoding(); - Decoder utf8Decode = encoder.GetDecoder(); - - byte[] todecode_byte = Convert.FromBase64String(str); - int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length); - char[] decoded_char = new char[charCount]; - utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0); - string result = new String(decoded_char); - return result; - } - - public static Guid GetHashGuid(string data, string salt) - { - byte[] hash = ComputeMD5Hash(data + salt); - - //string s = BitConverter.ToString(hash); - - Guid guid = new Guid(hash); - - return guid; - } - - public static byte ConvertMaturityToAccessLevel(uint maturity) - { - byte retVal = 0; - switch (maturity) - { - case 0: //PG - retVal = 13; - break; - case 1: //Mature - retVal = 21; - break; - case 2: // Adult - retVal = 42; - break; - } - - return retVal; - - } - - public static uint ConvertAccessLevelToMaturity(byte maturity) - { - if (maturity <= 13) - return 0; - else if (maturity <= 21) - return 1; - else - return 2; - } - - /// - /// Produces an OSDMap from its string representation on a stream - /// - /// The stream - /// The size of the data on the stream - /// The OSDMap or an exception - public static OSDMap GetOSDMap(Stream stream, int length) - { - byte[] data = new byte[length]; - stream.Read(data, 0, length); - string strdata = Util.UTF8.GetString(data); - OSDMap args = null; - OSD buffer; - buffer = OSDParser.DeserializeJson(strdata); - if (buffer.Type == OSDType.Map) - { - args = (OSDMap)buffer; - return args; - } - return null; - } - - public static OSDMap GetOSDMap(string data) - { - OSDMap args = null; - try - { - OSD buffer; - // We should pay attention to the content-type, but let's assume we know it's Json - buffer = OSDParser.DeserializeJson(data); - if (buffer.Type == OSDType.Map) - { - args = (OSDMap)buffer; - return args; - } - else - { - // uh? - m_log.Debug(("[UTILS]: Got OSD of unexpected type " + buffer.Type.ToString())); - return null; - } - } - catch (Exception ex) - { - m_log.Debug("[UTILS]: exception on GetOSDMap " + ex.Message); - return null; - } - } - - public static string[] Glob(string path) - { - string vol=String.Empty; - - if (Path.VolumeSeparatorChar != Path.DirectorySeparatorChar) - { - string[] vcomps = path.Split(new char[] {Path.VolumeSeparatorChar}, 2, StringSplitOptions.RemoveEmptyEntries); - - if (vcomps.Length > 1) - { - path = vcomps[1]; - vol = vcomps[0]; - } - } - - string[] comps = path.Split(new char[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}, StringSplitOptions.RemoveEmptyEntries); - - // Glob - - path = vol; - if (vol != String.Empty) - path += new String(new char[] {Path.VolumeSeparatorChar, Path.DirectorySeparatorChar}); - else - path = new String(new char[] {Path.DirectorySeparatorChar}); - - List paths = new List(); - List found = new List(); - paths.Add(path); - - int compIndex = -1; - foreach (string c in comps) - { - compIndex++; - - List addpaths = new List(); - foreach (string p in paths) - { - string[] dirs = Directory.GetDirectories(p, c); - - if (dirs.Length != 0) - { - foreach (string dir in dirs) - addpaths.Add(Path.Combine(path, dir)); - } - - // Only add files if that is the last path component - if (compIndex == comps.Length - 1) - { - string[] files = Directory.GetFiles(p, c); - foreach (string f in files) - found.Add(f); - } - } - paths = addpaths; - } - - return found.ToArray(); - } - - public static string ServerURI(string uri) - { - if (uri == string.Empty) - return string.Empty; - - // Get rid of eventual slashes at the end - uri = uri.TrimEnd('/'); - - IPAddress ipaddr1 = null; - string port1 = ""; - try - { - ipaddr1 = Util.GetHostFromURL(uri); - } - catch { } - - try - { - port1 = uri.Split(new char[] { ':' })[2]; - } - catch { } - - // We tried our best to convert the domain names to IP addresses - return (ipaddr1 != null) ? "http://" + ipaddr1.ToString() + ":" + port1 : uri; - } - - /// - /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 256 bytes if necessary. - /// - /// - /// If null or empty, then an bytes[0] is returned. - /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] - /// - /// - /// Arguments to substitute into the string via the {} mechanism. - /// - /// - public static byte[] StringToBytes256(string str, params object[] args) - { - return StringToBytes256(string.Format(str, args)); - } - - /// - /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 256 bytes if necessary. - /// - /// - /// If null or empty, then an bytes[0] is returned. - /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] - /// - /// - public static byte[] StringToBytes256(string str) - { - if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; } - if (str.Length > 254) str = str.Remove(254); - if (!str.EndsWith("\0")) { str += "\0"; } - - // Because this is UTF-8 encoding and not ASCII, it's possible we - // might have gotten an oversized array even after the string trim - byte[] data = UTF8.GetBytes(str); - if (data.Length > 256) - { - Array.Resize(ref data, 256); - data[255] = 0; - } - - return data; - } - - /// - /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 1024 bytes if necessary. - /// - /// - /// If null or empty, then an bytes[0] is returned. - /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] - /// - /// - /// Arguments to substitute into the string via the {} mechanism. - /// - /// - public static byte[] StringToBytes1024(string str, params object[] args) - { - return StringToBytes1024(string.Format(str, args)); - } - - /// - /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 1024 bytes if necessary. - /// - /// - /// If null or empty, then an bytes[0] is returned. - /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] - /// - /// - public static byte[] StringToBytes1024(string str) - { - if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; } - if (str.Length > 1023) str = str.Remove(1023); - if (!str.EndsWith("\0")) { str += "\0"; } - - // Because this is UTF-8 encoding and not ASCII, it's possible we - // might have gotten an oversized array even after the string trim - byte[] data = UTF8.GetBytes(str); - if (data.Length > 1024) - { - Array.Resize(ref data, 1024); - data[1023] = 0; - } - - return data; + } + + public static bool LoadArchSpecificWindowsDll(string libraryName) + { + // We do this so that OpenSimulator on Windows loads the correct native library depending on whether + // it's running as a 32-bit process or a 64-bit one. By invoking LoadLibary here, later DLLImports + // will find it already loaded later on. + // + // This isn't necessary for other platforms (e.g. Mac OSX and Linux) since the DLL used can be + // controlled in config files. + string nativeLibraryPath; + + if (Util.Is64BitProcess()) + nativeLibraryPath = "lib64/" + libraryName; + else + nativeLibraryPath = "lib32/" + libraryName; + + m_log.DebugFormat("[UTIL]: Loading native Windows library at {0}", nativeLibraryPath); + + if (Util.LoadLibrary(nativeLibraryPath) == IntPtr.Zero) + { + m_log.ErrorFormat( + "[UTIL]: Couldn't find native Windows library at {0}", nativeLibraryPath); + + return false; + } + else + { + return true; + } + } + + public static bool IsEnvironmentSupported(ref string reason) + { + // Must have .NET 2.0 (Generics / libsl) + if (Environment.Version.Major < 2) + { + reason = ".NET 1.0/1.1 lacks components that is used by OpenSim"; + return false; + } + + // Windows 95/98/ME are unsupported + if (Environment.OSVersion.Platform == PlatformID.Win32Windows && + Environment.OSVersion.Platform != PlatformID.Win32NT) + { + reason = "Windows 95/98/ME will not run OpenSim"; + return false; + } + + // Windows 2000 / Pre-SP2 XP + if (Environment.OSVersion.Version.Major == 5 && + Environment.OSVersion.Version.Minor == 0) + { + reason = "Please update to Windows XP Service Pack 2 or Server2003"; + return false; + } + + return true; + } + + public static int UnixTimeSinceEpoch() + { + return ToUnixTime(DateTime.UtcNow); + } + + public static int ToUnixTime(DateTime stamp) + { + TimeSpan t = stamp.ToUniversalTime() - unixEpoch; + return (int) t.TotalSeconds; + } + + public static DateTime ToDateTime(ulong seconds) + { + DateTime epoch = unixEpoch; + return epoch.AddSeconds(seconds); + } + + public static DateTime ToDateTime(int seconds) + { + DateTime epoch = unixEpoch; + return epoch.AddSeconds(seconds); + } + + /// + /// Return an md5 hash of the given string + /// + /// + /// + public static string Md5Hash(string data) + { + byte[] dataMd5 = ComputeMD5Hash(data); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < dataMd5.Length; i++) + sb.AppendFormat("{0:x2}", dataMd5[i]); + return sb.ToString(); + } + + private static byte[] ComputeMD5Hash(string data) + { + MD5 md5 = MD5.Create(); + return md5.ComputeHash(Encoding.Default.GetBytes(data)); + } + + /// + /// Return an SHA1 hash + /// + /// + /// + public static string SHA1Hash(string data) + { + return SHA1Hash(Encoding.Default.GetBytes(data)); + } + + /// + /// Return an SHA1 hash + /// + /// + /// + public static string SHA1Hash(byte[] data) + { + byte[] hash = ComputeSHA1Hash(data); + return BitConverter.ToString(hash).Replace("-", String.Empty); + } + + private static byte[] ComputeSHA1Hash(byte[] src) + { + SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider(); + return SHA1.ComputeHash(src); + } + + public static int fast_distance2d(int x, int y) + { + x = Math.Abs(x); + y = Math.Abs(y); + + int min = Math.Min(x, y); + + return (x + y - (min >> 1) - (min >> 2) + (min >> 4)); + } + + /// + /// Are the co-ordinates of the new region visible from the old region? + /// + /// Old region x-coord + /// New region x-coord + /// Old region y-coord + /// New region y-coord + /// + public static bool IsOutsideView(float drawdist, uint oldx, uint newx, uint oldy, uint newy) + { + int dd = (int)((drawdist + Constants.RegionSize - 1) / Constants.RegionSize); + + int startX = (int)oldx - dd; + int startY = (int)oldy - dd; + + int endX = (int)oldx + dd; + int endY = (int)oldy + dd; + + return (newx < startX || endX < newx || newy < startY || endY < newy); + } + + public static string FieldToString(byte[] bytes) + { + return FieldToString(bytes, String.Empty); + } + + /// + /// Convert a variable length field (byte array) to a string, with a + /// field name prepended to each line of the output + /// + /// If the byte array has unprintable characters in it, a + /// hex dump will be put in the string instead + /// The byte array to convert to a string + /// A field name to prepend to each line of output + /// An ASCII string or a string containing a hex dump, minus + /// the null terminator + public static string FieldToString(byte[] bytes, string fieldName) + { + // Check for a common case + if (bytes.Length == 0) return String.Empty; + + StringBuilder output = new StringBuilder(); + bool printable = true; + + for (int i = 0; i < bytes.Length; ++i) + { + // Check if there are any unprintable characters in the array + if ((bytes[i] < 0x20 || bytes[i] > 0x7E) && bytes[i] != 0x09 + && bytes[i] != 0x0D && bytes[i] != 0x0A && bytes[i] != 0x00) + { + printable = false; + break; + } + } + + if (printable) + { + if (fieldName.Length > 0) + { + output.Append(fieldName); + output.Append(": "); + } + + output.Append(CleanString(Util.UTF8.GetString(bytes, 0, bytes.Length - 1))); + } + else + { + for (int i = 0; i < bytes.Length; i += 16) + { + if (i != 0) + output.Append(Environment.NewLine); + if (fieldName.Length > 0) + { + output.Append(fieldName); + output.Append(": "); + } + + for (int j = 0; j < 16; j++) + { + if ((i + j) < bytes.Length) + output.Append(String.Format("{0:X2} ", bytes[i + j])); + else + output.Append(" "); + } + + for (int j = 0; j < 16 && (i + j) < bytes.Length; j++) + { + if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E) + output.Append((char) bytes[i + j]); + else + output.Append("."); + } + } + } + + return output.ToString(); + } + + /// + /// Converts a URL to a IPAddress + /// + /// URL Standard Format + /// A resolved IP Address + public static IPAddress GetHostFromURL(string url) + { + return GetHostFromDNS(url.Split(new char[] {'/', ':'})[3]); + } + + /// + /// Returns a IP address from a specified DNS, favouring IPv4 addresses. + /// + /// DNS Hostname + /// An IP address, or null + public static IPAddress GetHostFromDNS(string dnsAddress) + { + // Is it already a valid IP? No need to look it up. + IPAddress ipa; + if (IPAddress.TryParse(dnsAddress, out ipa)) + return ipa; + + IPAddress[] hosts = null; + + // Not an IP, lookup required + try + { + hosts = Dns.GetHostEntry(dnsAddress).AddressList; + } + catch (Exception e) + { + m_log.WarnFormat("[UTIL]: An error occurred while resolving host name {0}, {1}", dnsAddress, e); + + // Still going to throw the exception on for now, since this was what was happening in the first place + throw e; + } + + foreach (IPAddress host in hosts) + { + if (host.AddressFamily == AddressFamily.InterNetwork) + { + return host; + } + } + + if (hosts.Length > 0) + return hosts[0]; + + return null; + } + + public static Uri GetURI(string protocol, string hostname, int port, string path) + { + return new UriBuilder(protocol, hostname, port, path).Uri; + } + + /// + /// Gets a list of all local system IP addresses + /// + /// + public static IPAddress[] GetLocalHosts() + { + return Dns.GetHostAddresses(Dns.GetHostName()); + } + + public static IPAddress GetLocalHost() + { + IPAddress[] iplist = GetLocalHosts(); + + if (iplist.Length == 0) // No accessible external interfaces + { + IPAddress[] loopback = Dns.GetHostAddresses("localhost"); + IPAddress localhost = loopback[0]; + + return localhost; + } + + foreach (IPAddress host in iplist) + { + if (!IPAddress.IsLoopback(host) && host.AddressFamily == AddressFamily.InterNetwork) + { + return host; + } + } + + if (iplist.Length > 0) + { + foreach (IPAddress host in iplist) + { + if (host.AddressFamily == AddressFamily.InterNetwork) + return host; + } + // Well all else failed... + return iplist[0]; + } + + return null; + } + + /// + /// Removes all invalid path chars (OS dependent) + /// + /// path + /// safe path + public static string safePath(string path) + { + return Regex.Replace(path, regexInvalidPathChars, String.Empty); + } + + /// + /// Removes all invalid filename chars (OS dependent) + /// + /// filename + /// safe filename + public static string safeFileName(string filename) + { + return Regex.Replace(filename, regexInvalidFileChars, String.Empty); + ; + } + + // + // directory locations + // + + public static string homeDir() + { + string temp; + // string personal=(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + // temp = Path.Combine(personal,".OpenSim"); + temp = "."; + return temp; + } + + public static string assetsDir() + { + return Path.Combine(configDir(), "assets"); + } + + public static string inventoryDir() + { + return Path.Combine(configDir(), "inventory"); + } + + public static string configDir() + { + return "."; + } + + public static string dataDir() + { + return "."; + } + + public static string logDir() + { + return "."; + } + + // From: http://coercedcode.blogspot.com/2008/03/c-generate-unique-filenames-within.html + public static string GetUniqueFilename(string FileName) + { + int count = 0; + string Name; + + if (File.Exists(FileName)) + { + FileInfo f = new FileInfo(FileName); + + if (!String.IsNullOrEmpty(f.Extension)) + { + Name = f.FullName.Substring(0, f.FullName.LastIndexOf('.')); + } + else + { + Name = f.FullName; + } + + while (File.Exists(FileName)) + { + count++; + FileName = Name + count + f.Extension; + } + } + return FileName; + } + + // Nini (config) related Methods + public static IConfigSource ConvertDataRowToXMLConfig(DataRow row, string fileName) + { + if (!File.Exists(fileName)) + { + //create new file + } + XmlConfigSource config = new XmlConfigSource(fileName); + AddDataRowToConfig(config, row); + config.Save(); + + return config; + } + + public static void AddDataRowToConfig(IConfigSource config, DataRow row) + { + config.Configs.Add((string) row[0]); + for (int i = 0; i < row.Table.Columns.Count; i++) + { + config.Configs[(string) row[0]].Set(row.Table.Columns[i].ColumnName, row[i]); + } + } + + public static float Clip(float x, float min, float max) + { + return Math.Min(Math.Max(x, min), max); + } + + public static int Clip(int x, int min, int max) + { + return Math.Min(Math.Max(x, min), max); + } + + /// + /// Convert an UUID to a raw uuid string. Right now this is a string without hyphens. + /// + /// + /// + public static String ToRawUuidString(UUID UUID) + { + return UUID.Guid.ToString("n"); + } + + public static string CleanString(string input) + { + if (input.Length == 0) + return input; + + int clip = input.Length; + + // Test for ++ string terminator + int pos = input.IndexOf("\0"); + if (pos != -1 && pos < clip) + clip = pos; + + // Test for CR + pos = input.IndexOf("\r"); + if (pos != -1 && pos < clip) + clip = pos; + + // Test for LF + pos = input.IndexOf("\n"); + if (pos != -1 && pos < clip) + clip = pos; + + // Truncate string before first end-of-line character found + return input.Substring(0, clip); + } + + /// + /// returns the contents of /etc/issue on Unix Systems + /// Use this for where it's absolutely necessary to implement platform specific stuff + /// + /// + public static string ReadEtcIssue() + { + try + { + StreamReader sr = new StreamReader("/etc/issue.net"); + string issue = sr.ReadToEnd(); + sr.Close(); + return issue; + } + catch (Exception) + { + return ""; + } + } + + public static void SerializeToFile(string filename, Object obj) + { + IFormatter formatter = new BinaryFormatter(); + Stream stream = null; + + try + { + stream = new FileStream( + filename, FileMode.Create, + FileAccess.Write, FileShare.None); + + formatter.Serialize(stream, obj); + } + catch (Exception e) + { + m_log.Error(e.ToString()); + } + finally + { + if (stream != null) + { + stream.Close(); + } + } + } + + public static Object DeserializeFromFile(string filename) + { + IFormatter formatter = new BinaryFormatter(); + Stream stream = null; + Object ret = null; + + try + { + stream = new FileStream( + filename, FileMode.Open, + FileAccess.Read, FileShare.None); + + ret = formatter.Deserialize(stream); + } + catch (Exception e) + { + m_log.Error(e.ToString()); + } + finally + { + if (stream != null) + { + stream.Close(); + } + } + + return ret; + } + + public static string Compress(string text) + { + byte[] buffer = Util.UTF8.GetBytes(text); + MemoryStream memory = new MemoryStream(); + using (GZipStream compressor = new GZipStream(memory, CompressionMode.Compress, true)) + { + compressor.Write(buffer, 0, buffer.Length); + } + + memory.Position = 0; + + byte[] compressed = new byte[memory.Length]; + memory.Read(compressed, 0, compressed.Length); + + byte[] compressedBuffer = new byte[compressed.Length + 4]; + Buffer.BlockCopy(compressed, 0, compressedBuffer, 4, compressed.Length); + Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, compressedBuffer, 0, 4); + return Convert.ToBase64String(compressedBuffer); + } + + public static string Decompress(string compressedText) + { + byte[] compressedBuffer = Convert.FromBase64String(compressedText); + using (MemoryStream memory = new MemoryStream()) + { + int msgLength = BitConverter.ToInt32(compressedBuffer, 0); + memory.Write(compressedBuffer, 4, compressedBuffer.Length - 4); + + byte[] buffer = new byte[msgLength]; + + memory.Position = 0; + using (GZipStream decompressor = new GZipStream(memory, CompressionMode.Decompress)) + { + decompressor.Read(buffer, 0, buffer.Length); + } + + return Util.UTF8.GetString(buffer); + } + } + + public static XmlRpcResponse XmlRpcCommand(string url, string methodName, params object[] args) + { + return SendXmlRpcCommand(url, methodName, args); + } + + public static XmlRpcResponse SendXmlRpcCommand(string url, string methodName, object[] args) + { + XmlRpcRequest client = new XmlRpcRequest(methodName, args); + return client.Send(url, 6000); + } + + /// + /// Returns an error message that the user could not be found in the database + /// + /// XML string consisting of a error element containing individual error(s) + public static XmlRpcResponse CreateUnknownUserErrorResponse() + { + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + responseData["error_type"] = "unknown_user"; + responseData["error_desc"] = "The user requested is not in the database"; + + response.Value = responseData; + return response; + } + + /// + /// Converts a byte array in big endian order into an ulong. + /// + /// + /// The array of bytes + /// + /// + /// The extracted ulong + /// + public static ulong BytesToUInt64Big(byte[] bytes) + { + if (bytes.Length < 8) return 0; + return ((ulong)bytes[0] << 56) | ((ulong)bytes[1] << 48) | ((ulong)bytes[2] << 40) | ((ulong)bytes[3] << 32) | + ((ulong)bytes[4] << 24) | ((ulong)bytes[5] << 16) | ((ulong)bytes[6] << 8) | (ulong)bytes[7]; + } + + // used for RemoteParcelRequest (for "About Landmark") + public static UUID BuildFakeParcelID(ulong regionHandle, uint x, uint y) + { + byte[] bytes = + { + (byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24), + (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56), + (byte)x, (byte)(x >> 8), 0, 0, + (byte)y, (byte)(y >> 8), 0, 0 }; + return new UUID(bytes, 0); + } + + public static UUID BuildFakeParcelID(ulong regionHandle, uint x, uint y, uint z) + { + byte[] bytes = + { + (byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24), + (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56), + (byte)x, (byte)(x >> 8), (byte)z, (byte)(z >> 8), + (byte)y, (byte)(y >> 8), 0, 0 }; + return new UUID(bytes, 0); + } + + public static void ParseFakeParcelID(UUID parcelID, out ulong regionHandle, out uint x, out uint y) + { + byte[] bytes = parcelID.GetBytes(); + regionHandle = Utils.BytesToUInt64(bytes); + x = Utils.BytesToUInt(bytes, 8) & 0xffff; + y = Utils.BytesToUInt(bytes, 12) & 0xffff; + } + + public static void ParseFakeParcelID(UUID parcelID, out ulong regionHandle, out uint x, out uint y, out uint z) + { + byte[] bytes = parcelID.GetBytes(); + regionHandle = Utils.BytesToUInt64(bytes); + x = Utils.BytesToUInt(bytes, 8) & 0xffff; + z = (Utils.BytesToUInt(bytes, 8) & 0xffff0000) >> 16; + y = Utils.BytesToUInt(bytes, 12) & 0xffff; + } + + public static void FakeParcelIDToGlobalPosition(UUID parcelID, out uint x, out uint y) + { + ulong regionHandle; + uint rx, ry; + + ParseFakeParcelID(parcelID, out regionHandle, out x, out y); + Utils.LongToUInts(regionHandle, out rx, out ry); + + x += rx; + y += ry; + } + + /// + /// Get operating system information if available. Returns only the first 45 characters of information + /// + /// + /// Operating system information. Returns an empty string if none was available. + /// + public static string GetOperatingSystemInformation() + { + string os = String.Empty; + + if (Environment.OSVersion.Platform != PlatformID.Unix) + { + os = Environment.OSVersion.ToString(); + } + else + { + os = ReadEtcIssue(); + } + + if (os.Length > 45) + { + os = os.Substring(0, 45); + } + + return os; + } + + public static string GetRuntimeInformation() + { + string ru = String.Empty; + + if (Environment.OSVersion.Platform == PlatformID.Unix) + ru = "Unix/Mono"; + else + if (Environment.OSVersion.Platform == PlatformID.MacOSX) + ru = "OSX/Mono"; + else + { + if (Type.GetType("Mono.Runtime") != null) + ru = "Win/Mono"; + else + ru = "Win/.NET"; + } + + return ru; + } + + /// + /// Is the given string a UUID? + /// + /// + /// + public static bool isUUID(string s) + { + return UUIDPattern.IsMatch(s); + } + + public static string GetDisplayConnectionString(string connectionString) + { + int passPosition = 0; + int passEndPosition = 0; + string displayConnectionString = null; + + // hide the password in the connection string + passPosition = connectionString.IndexOf("password", StringComparison.OrdinalIgnoreCase); + passPosition = connectionString.IndexOf("=", passPosition); + if (passPosition < connectionString.Length) + passPosition += 1; + passEndPosition = connectionString.IndexOf(";", passPosition); + + displayConnectionString = connectionString.Substring(0, passPosition); + displayConnectionString += "***"; + displayConnectionString += connectionString.Substring(passEndPosition, connectionString.Length - passEndPosition); + + return displayConnectionString; + } + + public static T ReadSettingsFromIniFile(IConfig config, T settingsClass) + { + Type settingsType = settingsClass.GetType(); + + FieldInfo[] fieldInfos = settingsType.GetFields(); + foreach (FieldInfo fieldInfo in fieldInfos) + { + if (!fieldInfo.IsStatic) + { + if (fieldInfo.FieldType == typeof(System.String)) + { + fieldInfo.SetValue(settingsClass, config.Get(fieldInfo.Name, (string)fieldInfo.GetValue(settingsClass))); + } + else if (fieldInfo.FieldType == typeof(System.Boolean)) + { + fieldInfo.SetValue(settingsClass, config.GetBoolean(fieldInfo.Name, (bool)fieldInfo.GetValue(settingsClass))); + } + else if (fieldInfo.FieldType == typeof(System.Int32)) + { + fieldInfo.SetValue(settingsClass, config.GetInt(fieldInfo.Name, (int)fieldInfo.GetValue(settingsClass))); + } + else if (fieldInfo.FieldType == typeof(System.Single)) + { + fieldInfo.SetValue(settingsClass, config.GetFloat(fieldInfo.Name, (float)fieldInfo.GetValue(settingsClass))); + } + else if (fieldInfo.FieldType == typeof(System.UInt32)) + { + fieldInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(fieldInfo.Name, ((uint)fieldInfo.GetValue(settingsClass)).ToString()))); + } + } + } + + PropertyInfo[] propertyInfos = settingsType.GetProperties(); + foreach (PropertyInfo propInfo in propertyInfos) + { + if ((propInfo.CanRead) && (propInfo.CanWrite)) + { + if (propInfo.PropertyType == typeof(System.String)) + { + propInfo.SetValue(settingsClass, config.Get(propInfo.Name, (string)propInfo.GetValue(settingsClass, null)), null); + } + else if (propInfo.PropertyType == typeof(System.Boolean)) + { + propInfo.SetValue(settingsClass, config.GetBoolean(propInfo.Name, (bool)propInfo.GetValue(settingsClass, null)), null); + } + else if (propInfo.PropertyType == typeof(System.Int32)) + { + propInfo.SetValue(settingsClass, config.GetInt(propInfo.Name, (int)propInfo.GetValue(settingsClass, null)), null); + } + else if (propInfo.PropertyType == typeof(System.Single)) + { + propInfo.SetValue(settingsClass, config.GetFloat(propInfo.Name, (float)propInfo.GetValue(settingsClass, null)), null); + } + if (propInfo.PropertyType == typeof(System.UInt32)) + { + propInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(propInfo.Name, ((uint)propInfo.GetValue(settingsClass, null)).ToString())), null); + } + } + } + + return settingsClass; + } + + public static string Base64ToString(string str) + { + UTF8Encoding encoder = new UTF8Encoding(); + Decoder utf8Decode = encoder.GetDecoder(); + + byte[] todecode_byte = Convert.FromBase64String(str); + int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length); + char[] decoded_char = new char[charCount]; + utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0); + string result = new String(decoded_char); + return result; + } + + public static Guid GetHashGuid(string data, string salt) + { + byte[] hash = ComputeMD5Hash(data + salt); + + //string s = BitConverter.ToString(hash); + + Guid guid = new Guid(hash); + + return guid; + } + + public static byte ConvertMaturityToAccessLevel(uint maturity) + { + byte retVal = 0; + switch (maturity) + { + case 0: //PG + retVal = 13; + break; + case 1: //Mature + retVal = 21; + break; + case 2: // Adult + retVal = 42; + break; + } + + return retVal; + + } + + public static uint ConvertAccessLevelToMaturity(byte maturity) + { + if (maturity <= 13) + return 0; + else if (maturity <= 21) + return 1; + else + return 2; + } + + /// + /// Produces an OSDMap from its string representation on a stream + /// + /// The stream + /// The size of the data on the stream + /// The OSDMap or an exception + public static OSDMap GetOSDMap(Stream stream, int length) + { + byte[] data = new byte[length]; + stream.Read(data, 0, length); + string strdata = Util.UTF8.GetString(data); + OSDMap args = null; + OSD buffer; + buffer = OSDParser.DeserializeJson(strdata); + if (buffer.Type == OSDType.Map) + { + args = (OSDMap)buffer; + return args; + } + return null; + } + + public static OSDMap GetOSDMap(string data) + { + OSDMap args = null; + try + { + OSD buffer; + // We should pay attention to the content-type, but let's assume we know it's Json + buffer = OSDParser.DeserializeJson(data); + if (buffer.Type == OSDType.Map) + { + args = (OSDMap)buffer; + return args; + } + else + { + // uh? + m_log.Debug(("[UTILS]: Got OSD of unexpected type " + buffer.Type.ToString())); + return null; + } + } + catch (Exception ex) + { + m_log.Debug("[UTILS]: exception on GetOSDMap " + ex.Message); + return null; + } + } + + public static string[] Glob(string path) + { + string vol=String.Empty; + + if (Path.VolumeSeparatorChar != Path.DirectorySeparatorChar) + { + string[] vcomps = path.Split(new char[] {Path.VolumeSeparatorChar}, 2, StringSplitOptions.RemoveEmptyEntries); + + if (vcomps.Length > 1) + { + path = vcomps[1]; + vol = vcomps[0]; + } + } + + string[] comps = path.Split(new char[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}, StringSplitOptions.RemoveEmptyEntries); + + // Glob + + path = vol; + if (vol != String.Empty) + path += new String(new char[] {Path.VolumeSeparatorChar, Path.DirectorySeparatorChar}); + else + path = new String(new char[] {Path.DirectorySeparatorChar}); + + List paths = new List(); + List found = new List(); + paths.Add(path); + + int compIndex = -1; + foreach (string c in comps) + { + compIndex++; + + List addpaths = new List(); + foreach (string p in paths) + { + string[] dirs = Directory.GetDirectories(p, c); + + if (dirs.Length != 0) + { + foreach (string dir in dirs) + addpaths.Add(Path.Combine(path, dir)); + } + + // Only add files if that is the last path component + if (compIndex == comps.Length - 1) + { + string[] files = Directory.GetFiles(p, c); + foreach (string f in files) + found.Add(f); + } + } + paths = addpaths; + } + + return found.ToArray(); + } + + public static string ServerURI(string uri) + { + if (uri == string.Empty) + return string.Empty; + + // Get rid of eventual slashes at the end + uri = uri.TrimEnd('/'); + + IPAddress ipaddr1 = null; + string port1 = ""; + try + { + ipaddr1 = Util.GetHostFromURL(uri); + } + catch { } + + try + { + port1 = uri.Split(new char[] { ':' })[2]; + } + catch { } + + // We tried our best to convert the domain names to IP addresses + return (ipaddr1 != null) ? "http://" + ipaddr1.ToString() + ":" + port1 : uri; + } + + /// + /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 256 bytes if necessary. + /// + /// + /// If null or empty, then an bytes[0] is returned. + /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] + /// + /// + /// Arguments to substitute into the string via the {} mechanism. + /// + /// + public static byte[] StringToBytes256(string str, params object[] args) + { + return StringToBytes256(string.Format(str, args)); + } + + /// + /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 256 bytes if necessary. + /// + /// + /// If null or empty, then an bytes[0] is returned. + /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] + /// + /// + public static byte[] StringToBytes256(string str) + { + if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; } + if (str.Length > 254) str = str.Remove(254); + if (!str.EndsWith("\0")) { str += "\0"; } + + // Because this is UTF-8 encoding and not ASCII, it's possible we + // might have gotten an oversized array even after the string trim + byte[] data = UTF8.GetBytes(str); + if (data.Length > 256) + { + Array.Resize(ref data, 256); + data[255] = 0; + } + + return data; + } + + /// + /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 1024 bytes if necessary. + /// + /// + /// If null or empty, then an bytes[0] is returned. + /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] + /// + /// + /// Arguments to substitute into the string via the {} mechanism. + /// + /// + public static byte[] StringToBytes1024(string str, params object[] args) + { + return StringToBytes1024(string.Format(str, args)); + } + + /// + /// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 1024 bytes if necessary. + /// + /// + /// If null or empty, then an bytes[0] is returned. + /// Using "\0" will return a conversion of the null character to a byte. This is not the same as bytes[0] + /// + /// + public static byte[] StringToBytes1024(string str) + { + if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; } + if (str.Length > 1023) str = str.Remove(1023); + if (!str.EndsWith("\0")) { str += "\0"; } + + // Because this is UTF-8 encoding and not ASCII, it's possible we + // might have gotten an oversized array even after the string trim + byte[] data = UTF8.GetBytes(str); + if (data.Length > 1024) + { + Array.Resize(ref data, 1024); + data[1023] = 0; + } + + return data; } /// @@ -1525,466 +1525,500 @@ namespace OpenSim.Framework public static bool Is64BitProcess() { return IntPtr.Size == 8; - } - - #region FireAndForget Threading Pattern - - /// - /// Created to work around a limitation in Mono with nested delegates - /// - private sealed class FireAndForgetWrapper - { - private static volatile FireAndForgetWrapper instance; - private static object syncRoot = new Object(); - - public static FireAndForgetWrapper Instance { - get { - - if (instance == null) - { - lock (syncRoot) - { - if (instance == null) - { - instance = new FireAndForgetWrapper(); - } - } - } - - return instance; - } - } - - public void FireAndForget(System.Threading.WaitCallback callback) - { - callback.BeginInvoke(null, EndFireAndForget, callback); - } - - public void FireAndForget(System.Threading.WaitCallback callback, object obj) - { - callback.BeginInvoke(obj, EndFireAndForget, callback); - } - - private static void EndFireAndForget(IAsyncResult ar) - { - System.Threading.WaitCallback callback = (System.Threading.WaitCallback)ar.AsyncState; - - try { callback.EndInvoke(ar); } - catch (Exception ex) { m_log.Error("[UTIL]: Asynchronous method threw an exception: " + ex.Message, ex); } - - ar.AsyncWaitHandle.Close(); - } - } - - public static void FireAndForget(System.Threading.WaitCallback callback) - { - FireAndForget(callback, null); - } - - public static void InitThreadPool(int maxThreads) - { - if (maxThreads < 2) - throw new ArgumentOutOfRangeException("maxThreads", "maxThreads must be greater than 2"); - if (m_ThreadPool != null) - throw new InvalidOperationException("SmartThreadPool is already initialized"); - - m_ThreadPool = new SmartThreadPool(2000, maxThreads, 2); - } - - public static int FireAndForgetCount() - { - const int MAX_SYSTEM_THREADS = 200; - - switch (FireAndForgetMethod) - { - case FireAndForgetMethod.UnsafeQueueUserWorkItem: - case FireAndForgetMethod.QueueUserWorkItem: - case FireAndForgetMethod.BeginInvoke: - int workerThreads, iocpThreads; - ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads); - return workerThreads; - case FireAndForgetMethod.SmartThreadPool: - return m_ThreadPool.MaxThreads - m_ThreadPool.InUseThreads; - case FireAndForgetMethod.Thread: - return MAX_SYSTEM_THREADS - System.Diagnostics.Process.GetCurrentProcess().Threads.Count; - default: - throw new NotImplementedException(); - } - } - - public static void FireAndForget(System.Threading.WaitCallback callback, object obj) - { - WaitCallback realCallback; - - if (FireAndForgetMethod == FireAndForgetMethod.RegressionTest) - { - // If we're running regression tests, then we want any exceptions to rise up to the test code. - realCallback = o => { Culture.SetCurrentCulture(); callback(o); }; - } - else - { - // When OpenSim interacts with a database or sends data over the wire, it must send this in en_US culture - // so that we don't encounter problems where, for instance, data is saved with a culture that uses commas - // for decimals places but is read by a culture that treats commas as number seperators. - realCallback = o => - { - Culture.SetCurrentCulture(); - - try - { - callback(o); - } - catch (Exception e) - { - m_log.ErrorFormat( - "[UTIL]: Continuing after async_call_method thread terminated with exception {0}{1}", - e.Message, e.StackTrace); - } - }; - } - - switch (FireAndForgetMethod) - { - case FireAndForgetMethod.RegressionTest: - case FireAndForgetMethod.None: - realCallback.Invoke(obj); - break; - case FireAndForgetMethod.UnsafeQueueUserWorkItem: - ThreadPool.UnsafeQueueUserWorkItem(realCallback, obj); - break; - case FireAndForgetMethod.QueueUserWorkItem: - ThreadPool.QueueUserWorkItem(realCallback, obj); - break; - case FireAndForgetMethod.BeginInvoke: - FireAndForgetWrapper wrapper = FireAndForgetWrapper.Instance; - wrapper.FireAndForget(realCallback, obj); - break; - case FireAndForgetMethod.SmartThreadPool: - if (m_ThreadPool == null) - m_ThreadPool = new SmartThreadPool(2000, 15, 2); - m_ThreadPool.QueueWorkItem(SmartThreadPoolCallback, new object[] { realCallback, obj }); - break; - case FireAndForgetMethod.Thread: - Thread thread = new Thread(delegate(object o) { realCallback(o); }); - thread.Start(obj); - break; - default: - throw new NotImplementedException(); - } - } - - /// - /// Get a thread pool report. - /// - /// - public static string GetThreadPoolReport() - { - string threadPoolUsed = null; - int maxThreads = 0; - int minThreads = 0; - int allocatedThreads = 0; - int inUseThreads = 0; - int waitingCallbacks = 0; - int completionPortThreads = 0; - - StringBuilder sb = new StringBuilder(); - if (FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool) - { - threadPoolUsed = "SmartThreadPool"; - maxThreads = m_ThreadPool.MaxThreads; - minThreads = m_ThreadPool.MinThreads; - inUseThreads = m_ThreadPool.InUseThreads; - allocatedThreads = m_ThreadPool.ActiveThreads; - waitingCallbacks = m_ThreadPool.WaitingCallbacks; - } - else if ( - FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem - || FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem) - { - threadPoolUsed = "BuiltInThreadPool"; - ThreadPool.GetMaxThreads(out maxThreads, out completionPortThreads); - ThreadPool.GetMinThreads(out minThreads, out completionPortThreads); - int availableThreads; - ThreadPool.GetAvailableThreads(out availableThreads, out completionPortThreads); - inUseThreads = maxThreads - availableThreads; - allocatedThreads = -1; - waitingCallbacks = -1; - } - - if (threadPoolUsed != null) - { - sb.AppendFormat("Thread pool used : {0}\n", threadPoolUsed); - sb.AppendFormat("Max threads : {0}\n", maxThreads); - sb.AppendFormat("Min threads : {0}\n", minThreads); - sb.AppendFormat("Allocated threads : {0}\n", allocatedThreads < 0 ? "not applicable" : allocatedThreads.ToString()); - sb.AppendFormat("In use threads : {0}\n", inUseThreads); - sb.AppendFormat("Work items waiting : {0}\n", waitingCallbacks < 0 ? "not available" : waitingCallbacks.ToString()); - } - else - { - sb.AppendFormat("Thread pool not used\n"); - } - - return sb.ToString(); - } - - private static object SmartThreadPoolCallback(object o) - { - object[] array = (object[])o; - WaitCallback callback = (WaitCallback)array[0]; - object obj = array[1]; - - callback(obj); - return null; - } - - #endregion FireAndForget Threading Pattern - - /// - /// 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. - /// - /// - public static Int32 EnvironmentTickCount() - { - return Environment.TickCount & EnvironmentTickCountMask; - } - const Int32 EnvironmentTickCountMask = 0x3fffffff; - - /// - /// 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 - public static Int32 EnvironmentTickCountSubtract(Int32 prevValue) - { - Int32 diff = EnvironmentTickCount() - prevValue; - return (diff >= 0) ? diff : (diff + EnvironmentTickCountMask + 1); - } - - // Returns value of Tick Count A - TickCount B accounting for wrapping of TickCount - // Assumes both tcA and tcB came from previous calls to Util.EnvironmentTickCount(). - // A positive return value indicates A occured later than B - public static Int32 EnvironmentTickCountCompare(Int32 tcA, Int32 tcB) - { - // A, B and TC are all between 0 and 0x3fffffff - int tc = EnvironmentTickCount(); - - if (tc - tcA >= 0) - tcA += EnvironmentTickCountMask + 1; - - if (tc - tcB >= 0) - tcB += EnvironmentTickCountMask + 1; - - return tcA - tcB; - } - - /// - /// Prints the call stack at any given point. Useful for debugging. - /// - public static void PrintCallStack() - { - StackTrace stackTrace = new StackTrace(true); // get call stack - StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames) - - // write call stack method names - foreach (StackFrame stackFrame in stackFrames) - { - MethodBase mb = stackFrame.GetMethod(); - m_log.DebugFormat("{0}.{1}:{2}", mb.DeclaringType, mb.Name, stackFrame.GetFileLineNumber()); // write method name - } - } - - /// - /// Gets the client IP address - /// - /// - /// - public static IPEndPoint GetClientIPFromXFF(string xff) - { - if (xff == string.Empty) - return null; - - string[] parts = xff.Split(new char[] { ',' }); - if (parts.Length > 0) - { - try - { - return new IPEndPoint(IPAddress.Parse(parts[0]), 0); - } - catch (Exception e) - { - m_log.WarnFormat("[UTIL]: Exception parsing XFF header {0}: {1}", xff, e.Message); - } - } - - return null; - } - - public static string GetCallerIP(Hashtable req) - { - if (req.ContainsKey("headers")) - { - try - { - Hashtable headers = (Hashtable)req["headers"]; - if (headers.ContainsKey("remote_addr") && headers["remote_addr"] != null) - return headers["remote_addr"].ToString(); - } - catch (Exception e) - { - m_log.WarnFormat("[UTIL]: exception in GetCallerIP: {0}", e.Message); - } - } - return string.Empty; - } - - #region Xml Serialization Utilities - public static bool ReadBoolean(XmlTextReader reader) - { - reader.ReadStartElement(); - bool result = Boolean.Parse(reader.ReadContentAsString().ToLower()); - reader.ReadEndElement(); - - return result; - } - - public static UUID ReadUUID(XmlTextReader reader, string name) - { - UUID id; - string idStr; - - reader.ReadStartElement(name); - - if (reader.Name == "Guid") - idStr = reader.ReadElementString("Guid"); - else if (reader.Name == "UUID") - idStr = reader.ReadElementString("UUID"); - else // no leading tag - idStr = reader.ReadContentAsString(); - UUID.TryParse(idStr, out id); - reader.ReadEndElement(); - - return id; - } - - public static Vector3 ReadVector(XmlTextReader reader, string name) - { - Vector3 vec; - - reader.ReadStartElement(name); - vec.X = reader.ReadElementContentAsFloat(reader.Name, String.Empty); // X or x - vec.Y = reader.ReadElementContentAsFloat(reader.Name, String.Empty); // Y or y - vec.Z = reader.ReadElementContentAsFloat(reader.Name, String.Empty); // Z or z - reader.ReadEndElement(); - - return vec; - } - - public static Quaternion ReadQuaternion(XmlTextReader reader, string name) - { - Quaternion quat = new Quaternion(); - - reader.ReadStartElement(name); - while (reader.NodeType != XmlNodeType.EndElement) - { - switch (reader.Name.ToLower()) - { - case "x": - quat.X = reader.ReadElementContentAsFloat(reader.Name, String.Empty); - break; - case "y": - quat.Y = reader.ReadElementContentAsFloat(reader.Name, String.Empty); - break; - case "z": - quat.Z = reader.ReadElementContentAsFloat(reader.Name, String.Empty); - break; - case "w": - quat.W = reader.ReadElementContentAsFloat(reader.Name, String.Empty); - break; - } - } - - reader.ReadEndElement(); - - return quat; - } - - public static T ReadEnum(XmlTextReader reader, string name) - { - string value = reader.ReadElementContentAsString(name, String.Empty); - // !!!!! to deal with flags without commas - if (value.Contains(" ") && !value.Contains(",")) - value = value.Replace(" ", ", "); - - return (T)Enum.Parse(typeof(T), value); ; - } - #endregion - - #region Universal User Identifiers - /// - /// - /// uuid[;endpoint[;name]] - /// - /// - /// - /// - public static bool ParseUniversalUserIdentifier(string value, out UUID uuid, out string url, out string firstname, out string lastname, out string secret) - { - uuid = UUID.Zero; url = string.Empty; firstname = "Unknown"; lastname = "User"; secret = string.Empty; - - string[] parts = value.Split(';'); - if (parts.Length >= 1) - if (!UUID.TryParse(parts[0], out uuid)) - return false; - - if (parts.Length >= 2) - url = parts[1]; - - if (parts.Length >= 3) - { - string[] name = parts[2].Split(); - if (name.Length == 2) - { - firstname = name[0]; - lastname = name[1]; - } - } - if (parts.Length >= 4) - secret = parts[3]; - - return true; - } - - /// - /// - /// - /// - /// uuid[;endpoint[;name]] - public static string ProduceUserUniversalIdentifier(AgentCircuitData acircuit) - { - if (acircuit.ServiceURLs.ContainsKey("HomeURI")) - { - string agentsURI = acircuit.ServiceURLs["HomeURI"].ToString(); - if (!agentsURI.EndsWith("/")) - agentsURI += "/"; - - // This is ugly, but there's no other way, given that the name is changed - // in the agent circuit data for foreigners - if (acircuit.lastname.Contains("@")) - { - string[] parts = acircuit.firstname.Split(new char[] { '.' }); - if (parts.Length == 2) - return acircuit.AgentID.ToString() + ";" + agentsURI + ";" + parts[0] + " " + parts[1]; - } - return acircuit.AgentID.ToString() + ";" + agentsURI + ";" + acircuit.firstname + " " + acircuit.lastname; - } - else - return acircuit.AgentID.ToString(); - } - #endregion - } -} + } + + #region FireAndForget Threading Pattern + + /// + /// Created to work around a limitation in Mono with nested delegates + /// + private sealed class FireAndForgetWrapper + { + private static volatile FireAndForgetWrapper instance; + private static object syncRoot = new Object(); + + public static FireAndForgetWrapper Instance { + get { + + if (instance == null) + { + lock (syncRoot) + { + if (instance == null) + { + instance = new FireAndForgetWrapper(); + } + } + } + + return instance; + } + } + + public void FireAndForget(System.Threading.WaitCallback callback) + { + callback.BeginInvoke(null, EndFireAndForget, callback); + } + + public void FireAndForget(System.Threading.WaitCallback callback, object obj) + { + callback.BeginInvoke(obj, EndFireAndForget, callback); + } + + private static void EndFireAndForget(IAsyncResult ar) + { + System.Threading.WaitCallback callback = (System.Threading.WaitCallback)ar.AsyncState; + + try { callback.EndInvoke(ar); } + catch (Exception ex) { m_log.Error("[UTIL]: Asynchronous method threw an exception: " + ex.Message, ex); } + + ar.AsyncWaitHandle.Close(); + } + } + + public static void FireAndForget(System.Threading.WaitCallback callback) + { + FireAndForget(callback, null); + } + + public static void InitThreadPool(int maxThreads) + { + if (maxThreads < 2) + throw new ArgumentOutOfRangeException("maxThreads", "maxThreads must be greater than 2"); + if (m_ThreadPool != null) + throw new InvalidOperationException("SmartThreadPool is already initialized"); + + m_ThreadPool = new SmartThreadPool(2000, maxThreads, 2); + } + + public static int FireAndForgetCount() + { + const int MAX_SYSTEM_THREADS = 200; + + switch (FireAndForgetMethod) + { + case FireAndForgetMethod.UnsafeQueueUserWorkItem: + case FireAndForgetMethod.QueueUserWorkItem: + case FireAndForgetMethod.BeginInvoke: + int workerThreads, iocpThreads; + ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads); + return workerThreads; + case FireAndForgetMethod.SmartThreadPool: + return m_ThreadPool.MaxThreads - m_ThreadPool.InUseThreads; + case FireAndForgetMethod.Thread: + return MAX_SYSTEM_THREADS - System.Diagnostics.Process.GetCurrentProcess().Threads.Count; + default: + throw new NotImplementedException(); + } + } + + public static void FireAndForget(System.Threading.WaitCallback callback, object obj) + { + WaitCallback realCallback; + + if (FireAndForgetMethod == FireAndForgetMethod.RegressionTest) + { + // If we're running regression tests, then we want any exceptions to rise up to the test code. + realCallback = o => { Culture.SetCurrentCulture(); callback(o); }; + } + else + { + // When OpenSim interacts with a database or sends data over the wire, it must send this in en_US culture + // so that we don't encounter problems where, for instance, data is saved with a culture that uses commas + // for decimals places but is read by a culture that treats commas as number seperators. + realCallback = o => + { + Culture.SetCurrentCulture(); + + try + { + callback(o); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[UTIL]: Continuing after async_call_method thread terminated with exception {0}{1}", + e.Message, e.StackTrace); + } + }; + } + + switch (FireAndForgetMethod) + { + case FireAndForgetMethod.RegressionTest: + case FireAndForgetMethod.None: + realCallback.Invoke(obj); + break; + case FireAndForgetMethod.UnsafeQueueUserWorkItem: + ThreadPool.UnsafeQueueUserWorkItem(realCallback, obj); + break; + case FireAndForgetMethod.QueueUserWorkItem: + ThreadPool.QueueUserWorkItem(realCallback, obj); + break; + case FireAndForgetMethod.BeginInvoke: + FireAndForgetWrapper wrapper = FireAndForgetWrapper.Instance; + wrapper.FireAndForget(realCallback, obj); + break; + case FireAndForgetMethod.SmartThreadPool: + if (m_ThreadPool == null) + m_ThreadPool = new SmartThreadPool(2000, 15, 2); + m_ThreadPool.QueueWorkItem(SmartThreadPoolCallback, new object[] { realCallback, obj }); + break; + case FireAndForgetMethod.Thread: + Thread thread = new Thread(delegate(object o) { realCallback(o); }); + thread.Start(obj); + break; + default: + throw new NotImplementedException(); + } + } + + /// + /// Get a thread pool report. + /// + /// + public static string GetThreadPoolReport() + { + string threadPoolUsed = null; + int maxThreads = 0; + int minThreads = 0; + int allocatedThreads = 0; + int inUseThreads = 0; + int waitingCallbacks = 0; + int completionPortThreads = 0; + + StringBuilder sb = new StringBuilder(); + if (FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool) + { + threadPoolUsed = "SmartThreadPool"; + maxThreads = m_ThreadPool.MaxThreads; + minThreads = m_ThreadPool.MinThreads; + inUseThreads = m_ThreadPool.InUseThreads; + allocatedThreads = m_ThreadPool.ActiveThreads; + waitingCallbacks = m_ThreadPool.WaitingCallbacks; + } + else if ( + FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem + || FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem) + { + threadPoolUsed = "BuiltInThreadPool"; + ThreadPool.GetMaxThreads(out maxThreads, out completionPortThreads); + ThreadPool.GetMinThreads(out minThreads, out completionPortThreads); + int availableThreads; + ThreadPool.GetAvailableThreads(out availableThreads, out completionPortThreads); + inUseThreads = maxThreads - availableThreads; + allocatedThreads = -1; + waitingCallbacks = -1; + } + + if (threadPoolUsed != null) + { + sb.AppendFormat("Thread pool used : {0}\n", threadPoolUsed); + sb.AppendFormat("Max threads : {0}\n", maxThreads); + sb.AppendFormat("Min threads : {0}\n", minThreads); + sb.AppendFormat("Allocated threads : {0}\n", allocatedThreads < 0 ? "not applicable" : allocatedThreads.ToString()); + sb.AppendFormat("In use threads : {0}\n", inUseThreads); + sb.AppendFormat("Work items waiting : {0}\n", waitingCallbacks < 0 ? "not available" : waitingCallbacks.ToString()); + } + else + { + sb.AppendFormat("Thread pool not used\n"); + } + + return sb.ToString(); + } + + private static object SmartThreadPoolCallback(object o) + { + object[] array = (object[])o; + WaitCallback callback = (WaitCallback)array[0]; + object obj = array[1]; + + callback(obj); + return null; + } + + #endregion FireAndForget Threading Pattern + + /// + /// 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. + /// + /// + public static Int32 EnvironmentTickCount() + { + return Environment.TickCount & EnvironmentTickCountMask; + } + const Int32 EnvironmentTickCountMask = 0x3fffffff; + + /// + /// 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 + public static Int32 EnvironmentTickCountSubtract(Int32 prevValue) + { + Int32 diff = EnvironmentTickCount() - prevValue; + return (diff >= 0) ? diff : (diff + EnvironmentTickCountMask + 1); + } + + // Returns value of Tick Count A - TickCount B accounting for wrapping of TickCount + // Assumes both tcA and tcB came from previous calls to Util.EnvironmentTickCount(). + // A positive return value indicates A occured later than B + public static Int32 EnvironmentTickCountCompare(Int32 tcA, Int32 tcB) + { + // A, B and TC are all between 0 and 0x3fffffff + int tc = EnvironmentTickCount(); + + if (tc - tcA >= 0) + tcA += EnvironmentTickCountMask + 1; + + if (tc - tcB >= 0) + tcB += EnvironmentTickCountMask + 1; + + return tcA - tcB; + } + + /// + /// Prints the call stack at any given point. Useful for debugging. + /// + public static void PrintCallStack() + { + StackTrace stackTrace = new StackTrace(true); // get call stack + StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames) + + // write call stack method names + foreach (StackFrame stackFrame in stackFrames) + { + MethodBase mb = stackFrame.GetMethod(); + m_log.DebugFormat("{0}.{1}:{2}", mb.DeclaringType, mb.Name, stackFrame.GetFileLineNumber()); // write method name + } + } + + /// + /// Gets the client IP address + /// + /// + /// + public static IPEndPoint GetClientIPFromXFF(string xff) + { + if (xff == string.Empty) + return null; + + string[] parts = xff.Split(new char[] { ',' }); + if (parts.Length > 0) + { + try + { + return new IPEndPoint(IPAddress.Parse(parts[0]), 0); + } + catch (Exception e) + { + m_log.WarnFormat("[UTIL]: Exception parsing XFF header {0}: {1}", xff, e.Message); + } + } + + return null; + } + + public static string GetCallerIP(Hashtable req) + { + if (req.ContainsKey("headers")) + { + try + { + Hashtable headers = (Hashtable)req["headers"]; + if (headers.ContainsKey("remote_addr") && headers["remote_addr"] != null) + return headers["remote_addr"].ToString(); + } + catch (Exception e) + { + m_log.WarnFormat("[UTIL]: exception in GetCallerIP: {0}", e.Message); + } + } + return string.Empty; + } + + #region Xml Serialization Utilities + public static bool ReadBoolean(XmlTextReader reader) + { + reader.ReadStartElement(); + bool result = Boolean.Parse(reader.ReadContentAsString().ToLower()); + reader.ReadEndElement(); + + return result; + } + + public static UUID ReadUUID(XmlTextReader reader, string name) + { + UUID id; + string idStr; + + reader.ReadStartElement(name); + + if (reader.Name == "Guid") + idStr = reader.ReadElementString("Guid"); + else if (reader.Name == "UUID") + idStr = reader.ReadElementString("UUID"); + else // no leading tag + idStr = reader.ReadContentAsString(); + UUID.TryParse(idStr, out id); + reader.ReadEndElement(); + + return id; + } + + public static Vector3 ReadVector(XmlTextReader reader, string name) + { + Vector3 vec; + + reader.ReadStartElement(name); + vec.X = reader.ReadElementContentAsFloat(reader.Name, String.Empty); // X or x + vec.Y = reader.ReadElementContentAsFloat(reader.Name, String.Empty); // Y or y + vec.Z = reader.ReadElementContentAsFloat(reader.Name, String.Empty); // Z or z + reader.ReadEndElement(); + + return vec; + } + + public static Quaternion ReadQuaternion(XmlTextReader reader, string name) + { + Quaternion quat = new Quaternion(); + + reader.ReadStartElement(name); + while (reader.NodeType != XmlNodeType.EndElement) + { + switch (reader.Name.ToLower()) + { + case "x": + quat.X = reader.ReadElementContentAsFloat(reader.Name, String.Empty); + break; + case "y": + quat.Y = reader.ReadElementContentAsFloat(reader.Name, String.Empty); + break; + case "z": + quat.Z = reader.ReadElementContentAsFloat(reader.Name, String.Empty); + break; + case "w": + quat.W = reader.ReadElementContentAsFloat(reader.Name, String.Empty); + break; + } + } + + reader.ReadEndElement(); + + return quat; + } + + public static T ReadEnum(XmlTextReader reader, string name) + { + string value = reader.ReadElementContentAsString(name, String.Empty); + // !!!!! to deal with flags without commas + if (value.Contains(" ") && !value.Contains(",")) + value = value.Replace(" ", ", "); + + return (T)Enum.Parse(typeof(T), value); ; + } + #endregion + + #region Universal User Identifiers + /// + /// + /// uuid[;endpoint[;first last[;secret]]] + /// the uuid part + /// the endpoint part (e.g. http://foo.com) + /// the first name part (e.g. Test) + /// the last name part (e.g User) + /// the secret part + public static bool ParseUniversalUserIdentifier(string value, out UUID uuid, out string url, out string firstname, out string lastname, out string secret) + { + uuid = UUID.Zero; url = string.Empty; firstname = "Unknown"; lastname = "User"; secret = string.Empty; + + string[] parts = value.Split(';'); + if (parts.Length >= 1) + if (!UUID.TryParse(parts[0], out uuid)) + return false; + + if (parts.Length >= 2) + url = parts[1]; + + if (parts.Length >= 3) + { + string[] name = parts[2].Split(); + if (name.Length == 2) + { + firstname = name[0]; + lastname = name[1]; + } + } + if (parts.Length >= 4) + secret = parts[3]; + + return true; + } + + /// + /// Produces a universal (HG) system-facing identifier given the information + /// + /// + /// uuid[;homeURI[;first last]] + public static string ProduceUserUniversalIdentifier(AgentCircuitData acircuit) + { + if (acircuit.ServiceURLs.ContainsKey("HomeURI")) + return UniversalIdentifier(acircuit.AgentID, acircuit.firstname, acircuit.lastname, acircuit.ServiceURLs["HomeURI"].ToString()); + else + return acircuit.AgentID.ToString(); + } + + /// + /// Produces a universal (HG) system-facing identifier given the information + /// + /// UUID of the user + /// first name (e.g Test) + /// last name (e.g. User) + /// homeURI (e.g. http://foo.com) + /// a string of the form uuid[;homeURI[;first last]] + public static string UniversalIdentifier(UUID id, String firstName, String lastName, String homeURI) + { + string agentsURI = homeURI; + if (!agentsURI.EndsWith("/")) + agentsURI += "/"; + + // This is ugly, but there's no other way, given that the name is changed + // in the agent circuit data for foreigners + if (lastName.Contains("@")) + { + string[] parts = firstName.Split(new char[] { '.' }); + if (parts.Length == 2) + return id.ToString() + ";" + agentsURI + ";" + parts[0] + " " + parts[1]; + } + return id.ToString() + ";" + agentsURI + ";" + firstName + " " + lastName; + + } + + /// + /// Produces a universal (HG) user-facing name given the information + /// + /// + /// + /// + /// string of the form first.last @foo.com or first last + public static string UniversalName(String firstName, String lastName, String homeURI) + { + Uri uri = null; + try + { + uri = new Uri(homeURI); + } + catch (UriFormatException) + { + return firstName + " " + lastName; + } + return firstName + "." + lastName + " " + "@" + uri.Authority; + } + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs index c266fe58ca..f6a31b5871 100644 --- a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs @@ -550,7 +550,19 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends UUID principalID = new UUID(im.fromAgentID); UUID friendID = new UUID(im.toAgentID); - m_log.DebugFormat("[FRIENDS]: {0} ({1}) offered friendship to {2}", principalID, im.fromAgentName, friendID); + m_log.DebugFormat("[FRIENDS]: {0} ({1}) offered friendship to {2} ({3})", principalID, client.FirstName + client.LastName, friendID, im.fromAgentName); + + // Check that the friendship doesn't exist yet + FriendInfo[] finfos = GetFriends(principalID); + if (finfos != null) + { + FriendInfo f = GetFriend(finfos, friendID); + if (f != null) + { + client.SendAgentAlertMessage("This person is already your friend. Please delete it first if you want to reestablish the friendship.", false); + return; + } + } // This user wants to be friends with the other user. // Let's add the relation backwards, in case the other is not online @@ -561,7 +573,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends } } - private void ForwardFriendshipOffer(UUID agentID, UUID friendID, GridInstantMessage im) + protected virtual bool ForwardFriendshipOffer(UUID agentID, UUID friendID, GridInstantMessage im) { // !!!!!!!! This is a hack so that we don't have to keep state (transactionID/imSessionID) // We stick this agent's ID as imSession, so that it's directly available on the receiving end @@ -570,7 +582,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends // Try the local sim if (LocalFriendshipOffered(friendID, im)) - return; + { + m_log.DebugFormat("[XXX]: LocalFriendshipOffered successes"); + return true; + } // The prospective friend is not here [as root]. Let's forward. PresenceInfo[] friendSessions = PresenceService.GetAgents(new string[] { friendID.ToString() }); @@ -581,9 +596,11 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends { GridRegion region = GridService.GetRegionByUUID(m_Scenes[0].RegionInfo.ScopeID, friendSession.RegionID); m_FriendsSimConnector.FriendshipOffered(region, agentID, friendID, im.message); + return true; } } // If the prospective friend is not online, he'll get the message upon login. + return false; } protected virtual string GetFriendshipRequesterName(UUID agentID) @@ -592,7 +609,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends return (account == null) ? "Unknown" : account.FirstName + " " + account.LastName; } - private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List callingCardFolders) + protected virtual void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List callingCardFolders) { m_log.DebugFormat("[FRIENDS]: {0} accepted friendship from {1}", client.AgentId, friendID); @@ -603,7 +620,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends { StoreFriendships(client.AgentId, friendID); - // Update the local cache + // Update the local cache. RecacheFriends(client); // @@ -756,7 +773,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends #region Local - public bool LocalFriendshipOffered(UUID toID, GridInstantMessage im) + public virtual bool LocalFriendshipOffered(UUID toID, GridInstantMessage im) { IClientAPI friendClient = LocateClientObject(toID); if (friendClient != null) @@ -912,7 +929,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends return FriendsService.GetFriends(client.AgentId); } - private void RecacheFriends(IClientAPI client) + protected void RecacheFriends(IClientAPI client) { // FIXME: Ideally, we want to avoid doing this here since it sits the EventManager.OnMakeRootAgent event // is on the critical path for transferring an avatar from one region to another. diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs b/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs index 9c53fc4ac8..0fe1134d57 100644 --- a/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Friends/HGFriendsModule.cs @@ -61,6 +61,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends } } + protected HGFriendsServicesConnector m_HGFriendsConnector = new HGFriendsServicesConnector(); + #region ISharedRegionModule public override string Name { @@ -94,6 +96,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends #endregion + protected override void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List callingCardFolders) + { + // Update the local cache. Yes, we need to do it right here + // because the HGFriendsService placed something on the DB + // from under the sim + base.OnApproveFriendRequest(client, agentID, friendID, callingCardFolders); + } + protected override bool CacheFriends(IClientAPI client) { // m_log.DebugFormat("[HGFRIENDS MODULE]: Entered CacheFriends for {0}", client.Name); @@ -183,91 +193,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends // m_log.DebugFormat("[HGFRIENDS MODULE]: Exiting GetOnlineFriends for {0}", userID); } - //protected override void GetOnlineFriends(UUID userID, List friendList, /*collector*/ List online) - //{ - // // Let's single out the UUIs - // List localFriends = new List(); - // List foreignFriends = new List(); - // string tmp = string.Empty; - - // foreach (string s in friendList) - // { - // UUID id; - // if (UUID.TryParse(s, out id)) - // localFriends.Add(s); - // else if (Util.ParseUniversalUserIdentifier(s, out id, out tmp, out tmp, out tmp, out tmp)) - // { - // foreignFriends.Add(s); - // // add it here too, who knows maybe the foreign friends happens to be on this grid - // localFriends.Add(id.ToString()); - // } - // } - - // // OK, see who's present on this grid - // List toBeRemoved = new List(); - // PresenceInfo[] presence = PresenceService.GetAgents(localFriends.ToArray()); - // foreach (PresenceInfo pi in presence) - // { - // UUID presenceID; - // if (UUID.TryParse(pi.UserID, out presenceID)) - // { - // online.Add(presenceID); - // foreach (string s in foreignFriends) - // if (s.StartsWith(pi.UserID)) - // toBeRemoved.Add(s); - // } - // } - - // foreach (string s in toBeRemoved) - // foreignFriends.Remove(s); - - // // OK, let's send this up the stack, and leave a closure here - // // collecting online friends in other grids - // Util.FireAndForget(delegate { CollectOnlineFriendsElsewhere(userID, foreignFriends); }); - - //} - - //private void CollectOnlineFriendsElsewhere(UUID userID, List foreignFriends) - //{ - // // let's divide the friends on a per-domain basis - // Dictionary> friendsPerDomain = new Dictionary>(); - // foreach (string friend in foreignFriends) - // { - // UUID friendID; - // if (!UUID.TryParse(friend, out friendID)) - // { - // // it's a foreign friend - // string url = string.Empty, tmp = string.Empty; - // if (Util.ParseUniversalUserIdentifier(friend, out friendID, out url, out tmp, out tmp, out tmp)) - // { - // if (!friendsPerDomain.ContainsKey(url)) - // friendsPerDomain[url] = new List(); - // friendsPerDomain[url].Add(friend); - // } - // } - // } - - // // Now, call those worlds - - // foreach (KeyValuePair> kvp in friendsPerDomain) - // { - // List ids = new List(); - // foreach (string f in kvp.Value) - // ids.Add(f); - // UserAgentServiceConnector uConn = new UserAgentServiceConnector(kvp.Key); - // List online = uConn.GetOnlineFriends(userID, ids); - // // Finally send the notifications to the user - // // this whole process may take a while, so let's check at every - // // iteration that the user is still here - // IClientAPI client = LocateClientObject(userID); - // if (client != null) - // client.SendAgentOnline(online.ToArray()); - // else - // break; - // } - - //} - protected override void StatusNotify(List friendList, UUID userID, bool online) { // m_log.DebugFormat("[HGFRIENDS MODULE]: Entering StatusNotify for {0}", userID); @@ -335,12 +260,25 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends return true; // fid is not a UUID... - string url = string.Empty, tmp = string.Empty; - if (Util.ParseUniversalUserIdentifier(fid, out agentID, out url, out first, out last, out tmp)) + string url = string.Empty, tmp = string.Empty, f = string.Empty, l = string.Empty; + m_log.DebugFormat("[YYY]: FID {0}", fid); + if (Util.ParseUniversalUserIdentifier(fid, out agentID, out url, out f, out l, out tmp)) { - IUserManagement userMan = m_Scenes[0].RequestModuleInterface(); - userMan.AddUser(agentID, first, last, url); + m_log.DebugFormat("[YYY]: Adding user {0} {1} {2}", f, l, url); + m_uMan.AddUser(agentID, f, l, url); + string name = m_uMan.GetUserName(agentID); + string[] parts = name.Trim().Split(new char[] {' '}); + if (parts.Length == 2) + { + first = parts[0]; + last = parts[1]; + } + else + { + first = f; + last = l; + } return true; } return false; @@ -348,13 +286,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends protected override string GetFriendshipRequesterName(UUID agentID) { - // For the time being we assume that HG friendship requests can only happen - // when avies are on the same region. - IClientAPI client = LocateClientObject(agentID); - if (client != null) - return client.FirstName + " " + client.LastName; - else - return base.GetFriendshipRequesterName(agentID); + return m_uMan.GetUserName(agentID); } protected override string FriendshipMessage(string friendID) @@ -392,10 +324,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends AgentCircuitData agentClientCircuit = ((Scene)(client.Scene)).AuthenticateHandler.GetAgentCircuitData(client.CircuitCode); if (agentClientCircuit != null) { - string agentUUI = Util.ProduceUserUniversalIdentifier(agentClientCircuit); + //[XXX] string agentUUI = Util.ProduceUserUniversalIdentifier(agentClientCircuit); - finfos = FriendsService.GetFriends(agentUUI); - m_log.DebugFormat("[HGFRIENDS MODULE]: Fetched {0} local friends for visitor {1}", finfos.Length, agentUUI); + finfos = FriendsService.GetFriends(client.AgentId.ToString()); + m_log.DebugFormat("[HGFRIENDS MODULE]: Fetched {0} local friends for visitor {1}", finfos.Length, client.AgentId.ToString()); } // m_log.DebugFormat("[HGFRIENDS MODULE]: Exiting GetFriendsFromService for {0}", client.Name); @@ -454,16 +386,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends friendIsLocal = UserManagementModule.IsLocalGridUser(friendID); } - // Are they both local users? - if (agentIsLocal && friendIsLocal) + // Is the requester a local user? + if (agentIsLocal) { // local grid users - m_log.DebugFormat("[HGFRIENDS MODULE]: Users are both local"); + m_log.DebugFormat("[HGFRIENDS MODULE]: Friendship requester is local. Storing backwards."); + base.StoreBackwards(friendID, agentID); return; } - // no provision for this temporary friendship state + // no provision for this temporary friendship state when user is not local //FriendsService.StoreFriend(friendID.ToString(), agentID.ToString(), 0); } @@ -501,12 +434,14 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends agentClientCircuit = ((Scene)(agentClient.Scene)).AuthenticateHandler.GetAgentCircuitData(agentClient.CircuitCode); agentUUI = Util.ProduceUserUniversalIdentifier(agentClientCircuit); agentFriendService = agentClientCircuit.ServiceURLs["FriendsServerURI"].ToString(); + RecacheFriends(agentClient); } if (friendClient != null) { friendClientCircuit = ((Scene)(friendClient.Scene)).AuthenticateHandler.GetAgentCircuitData(friendClient.CircuitCode); friendUUI = Util.ProduceUserUniversalIdentifier(friendClientCircuit); friendFriendService = friendClientCircuit.ServiceURLs["FriendsServerURI"].ToString(); + RecacheFriends(friendClient); } m_log.DebugFormat("[HGFRIENDS MODULE] HG Friendship! thisUUI={0}; friendUUI={1}; foreignThisFriendService={2}; foreignFriendFriendService={3}", @@ -515,14 +450,18 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends // Generate a random 8-character hex number that will sign this friendship string secret = UUID.Random().ToString().Substring(0, 8); + string theFriendUUID = friendUUI + ";" + secret; + string agentUUID = agentUUI + ";" + secret; + if (agentIsLocal) // agent is local, 'friend' is foreigner { // This may happen when the agent returned home, in which case the friend is not there // We need to look for its information in the friends list itself + FriendInfo[] finfos = null; bool confirming = false; if (friendUUI == string.Empty) { - FriendInfo[] finfos = GetFriends(agentID); + finfos = GetFriends(agentID); foreach (FriendInfo finfo in finfos) { if (finfo.TheirFlags == -1) @@ -530,29 +469,57 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends if (finfo.Friend.StartsWith(friendID.ToString())) { friendUUI = finfo.Friend; + theFriendUUID = friendUUI; + UUID utmp = UUID.Zero; String url = String.Empty; String first = String.Empty, last = String.Empty, tmp = String.Empty; + // If it's confirming the friendship, we already have the full UUI with the secret + if (Util.ParseUniversalUserIdentifier(theFriendUUID, out utmp, out url, out first, out last, out secret)) + { + agentUUID = agentUUI + ";" + secret; + m_uMan.AddUser(utmp, first, last, url); + } confirming = true; + break; } } } - } + if (!confirming) + { + friendUUI = m_uMan.GetUserUUI(friendID); + theFriendUUID = friendUUI + ";" + secret; + } - // If it's confirming the friendship, we already have the full friendUUI with the secret - string theFriendUUID = confirming ? friendUUI : friendUUI + ";" + secret; + friendFriendService = m_uMan.GetUserServerURL(friendID, "FriendsServerURI"); + + // m_log.DebugFormat("[HGFRIENDS MODULE] HG Friendship! thisUUI={0}; friendUUI={1}; foreignThisFriendService={2}; foreignFriendFriendService={3}", + // agentUUI, friendUUI, agentFriendService, friendFriendService); + + } + + // Delete any previous friendship relations + DeletePreviousRelations(agentID, friendID); // store in the local friends service a reference to the foreign friend FriendsService.StoreFriend(agentID.ToString(), theFriendUUID, 1); // and also the converse FriendsService.StoreFriend(theFriendUUID, agentID.ToString(), 1); - if (!confirming && friendClientCircuit != null) - { + //if (!confirming) + //{ // store in the foreign friends service a reference to the local agent - HGFriendsServicesConnector friendsConn = new HGFriendsServicesConnector(friendFriendService, friendClientCircuit.SessionID, friendClientCircuit.ServiceSessionID); - friendsConn.NewFriendship(friendID, agentUUI + ";" + secret); - } + HGFriendsServicesConnector friendsConn = null; + if (friendClientCircuit != null) // the friend is here, validate session + friendsConn = new HGFriendsServicesConnector(friendFriendService, friendClientCircuit.SessionID, friendClientCircuit.ServiceSessionID); + else // the friend is not here, he initiated the request in his home world + friendsConn = new HGFriendsServicesConnector(friendFriendService); + + friendsConn.NewFriendship(friendID, agentUUID); + //} } else if (friendIsLocal) // 'friend' is local, agent is foreigner { + // Delete any previous friendship relations + DeletePreviousRelations(agentID, friendID); + // store in the local friends service a reference to the foreign agent FriendsService.StoreFriend(friendID.ToString(), agentUUI + ";" + secret, 1); // and also the converse @@ -582,6 +549,36 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends // my brain hurts now } + private void DeletePreviousRelations(UUID a1, UUID a2) + { + // Delete any previous friendship relations + FriendInfo[] finfos = null; + FriendInfo f = null; + finfos = GetFriends(a1); + if (finfos != null) + { + f = GetFriend(finfos, a2); + if (f != null) + { + FriendsService.Delete(a1, f.Friend); + // and also the converse + FriendsService.Delete(f.Friend, a1.ToString()); + } + } + + finfos = GetFriends(a2); + if (finfos != null) + { + f = GetFriend(finfos, a1); + if (f != null) + { + FriendsService.Delete(a2, f.Friend); + // and also the converse + FriendsService.Delete(f.Friend, a2.ToString()); + } + } + } + protected override bool DeleteFriendship(UUID agentID, UUID exfriendID) { Boolean agentIsLocal = true; @@ -684,5 +681,74 @@ namespace OpenSim.Region.CoreModules.Avatar.Friends friendConn.DeleteFriendship(foreignUser, localUser, secret); } } + + protected override bool ForwardFriendshipOffer(UUID agentID, UUID friendID, GridInstantMessage im) + { + if (base.ForwardFriendshipOffer(agentID, friendID, im)) + return true; + + // OK, that didn't work, so let's try to find this user somewhere + if (!m_uMan.IsLocalGridUser(friendID)) + { + string friendsURL = m_uMan.GetUserServerURL(friendID, "FriendsServerURI"); + if (friendsURL != string.Empty) + { + m_log.DebugFormat("[HGFRIENDS MODULE]: Forwading friendship from {0} to {1} @ {2}", agentID, friendID, friendsURL); + GridRegion region = new GridRegion(); + region.ServerURI = friendsURL; + + string name = im.fromAgentName; + if (m_uMan.IsLocalGridUser(agentID)) + { + IClientAPI agentClient = LocateClientObject(agentID); + AgentCircuitData agentClientCircuit = ((Scene)(agentClient.Scene)).AuthenticateHandler.GetAgentCircuitData(agentClient.CircuitCode); + string agentHomeService = string.Empty; + try + { + agentHomeService = agentClientCircuit.ServiceURLs["HomeURI"].ToString(); + string lastname = "@" + new Uri(agentHomeService).Authority; + string firstname = im.fromAgentName.Replace(" ", "."); + name = firstname + lastname; + } + catch (KeyNotFoundException) + { + m_log.DebugFormat("[HGFRIENDS MODULE]: Key HomeURI not found for user {0}", agentID); + return false; + } + catch (NullReferenceException) + { + m_log.DebugFormat("[HGFRIENDS MODULE]: Null HomeUri for local user {0}", agentID); + return false; + } + catch (UriFormatException) + { + m_log.DebugFormat("[HGFRIENDS MODULE]: Malformed HomeUri {0} for local user {1}", agentHomeService, agentID); + return false; + } + } + + m_HGFriendsConnector.FriendshipOffered(region, agentID, friendID, im.message, name); + + return true; + } + } + + return false; + } + + public override bool LocalFriendshipOffered(UUID toID, GridInstantMessage im) + { + if (base.LocalFriendshipOffered(toID, im)) + { + if (im.fromAgentName.Contains("@")) + { + string[] parts = im.fromAgentName.Split(new char[] { '@' }); + if (parts.Length == 2) + m_uMan.AddUser(new UUID(im.fromAgentID), parts[0], "http://" + parts[1]); + } + return true; + } + return false; + } } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs b/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs index 66de8e4f5a..4eecaa2080 100644 --- a/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs +++ b/OpenSim/Region/CoreModules/Framework/UserManagement/HGUserManagementModule.cs @@ -71,43 +71,52 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement protected override void AddAdditionalUsers(UUID avatarID, string query, List users) { - string[] words = query.Split(new char[] { ' ' }); - - for (int i = 0; i < words.Length; i++) + if (query.Contains("@")) // First.Last@foo.com, maybe? { - if (words[i].Length < 3) + string[] words = query.Split(new char[] { '@' }); + if (words.Length != 2) { - if (i != words.Length - 1) - Array.Copy(words, i + 1, words, i, words.Length - i - 1); - Array.Resize(ref words, words.Length - 1); + m_log.DebugFormat("[USER MANAGEMENT MODULE]: Malformed address {0}", query); + return; } - } - if (words.Length == 0 || words.Length > 2) - return; + words[0] = words[0].Trim(); // it has at least 1 + words[1] = words[1].Trim(); - if (words.Length == 2) // First.Last @foo.com, maybe? - { - bool found = false; + if (words[0] == String.Empty) // query was @foo.com? + { + foreach (UserData d in m_UserCache.Values) + { + if (d.LastName.ToLower().StartsWith("@" + words[1].ToLower())) + users.Add(d); + } + + // We're done + return; + } + + // words.Length == 2 and words[0] != string.empty + // first.last@foo.com ? foreach (UserData d in m_UserCache.Values) { - if (d.LastName.StartsWith("@") && + if (d.LastName.StartsWith("@") && d.FirstName.ToLower().Equals(words[0].ToLower()) && - d.LastName.ToLower().Equals(words[1].ToLower())) + d.LastName.ToLower().Equals("@" + words[1].ToLower())) { users.Add(d); - found = true; - break; + // It's cached. We're done + return; } } - if (!found && words[1].StartsWith("@") && words[0].Contains(".")) // This is it! Let's ask the other world + // This is it! Let's ask the other world + if (words[0].Contains(".")) { string[] names = words[0].Split(new char[] { '.' }); if (names.Length >= 2) { - string uriStr = "http://" + words[1].Substring(1); // remove the @ + string uriStr = "http://" + words[1]; // Let's check that the last name is a valid address try { @@ -115,6 +124,7 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement } catch (UriFormatException) { + m_log.DebugFormat("[USER MANAGEMENT MODULE]: Malformed address {0}", uriStr); return; } @@ -125,26 +135,26 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement UserData ud = new UserData(); ud.Id = userID; ud.FirstName = words[0]; - ud.LastName = words[1]; + ud.LastName = "@" + words[1]; users.Add(ud); - AddUser(userID, ud.FirstName, ud.LastName, uriStr); - m_log.DebugFormat("[USER MANAGEMENT MODULE]: User {0} {1} found", words[0], words[1]); + AddUser(userID, names[0], names[1], uriStr); + m_log.DebugFormat("[USER MANAGEMENT MODULE]: User {0}@{1} found", words[0], words[1]); } else - m_log.DebugFormat("[USER MANAGEMENT MODULE]: User {0} {1} not found", words[0], words[1]); + m_log.DebugFormat("[USER MANAGEMENT MODULE]: User {0}@{1} not found", words[0], words[1]); } } } - else - { - foreach (UserData d in m_UserCache.Values) - { - if (d.LastName.StartsWith("@") && - (d.FirstName.ToLower().StartsWith(query.ToLower()) || - d.LastName.ToLower().StartsWith(query.ToLower()))) - users.Add(d); - } - } + //else + //{ + // foreach (UserData d in m_UserCache.Values) + // { + // if (d.LastName.StartsWith("@") && + // (d.FirstName.ToLower().StartsWith(query.ToLower()) || + // d.LastName.ToLower().StartsWith(query.ToLower()))) + // users.Add(d); + // } + //} } } diff --git a/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs b/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs index cb562a2703..039747812d 100644 --- a/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs +++ b/OpenSim/Region/CoreModules/Framework/UserManagement/UserManagementModule.cs @@ -299,7 +299,6 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement public string GetUserName(UUID uuid) { - //m_log.DebugFormat("[XXX] GetUserName {0}", uuid); string[] names = GetUserNames(uuid); if (names.Length == 2) { @@ -340,9 +339,9 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement if (userdata.HomeURL != null && userdata.HomeURL != string.Empty) { - m_log.DebugFormat( - "[USER MANAGEMENT MODULE]: Did not find url type {0} so requesting urls from '{1}' for {2}", - serverType, userdata.HomeURL, userID); + //m_log.DebugFormat( + // "[USER MANAGEMENT MODULE]: Did not find url type {0} so requesting urls from '{1}' for {2}", + // serverType, userdata.HomeURL, userID); UserAgentServiceConnector uConn = new UserAgentServiceConnector(userdata.HomeURL); userdata.ServerURLs = uConn.GetServerURLs(userID); @@ -401,11 +400,15 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement public void AddUser(UUID uuid, string first, string last, string homeURL) { + // m_log.DebugFormat("[USER MANAGEMENT MODULE]: Adding user with id {0}, first {1}, last {2}, url {3}", uuid, first, last, homeURL); + AddUser(uuid, homeURL + ";" + first + " " + last); } public void AddUser (UUID id, string creatorData) { + //m_log.DebugFormat("[USER MANAGEMENT MODULE]: Adding user with id {0}, creatorData {1}", id, creatorData); + UserData oldUser; //lock the whole block - prevent concurrent update lock (m_UserCache) @@ -431,9 +434,8 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement return; } } -// m_log.DebugFormat("[USER MANAGEMENT MODULE]: Adding user with id {0}, creatorData {1}", id, creatorData); - UserAccount account = m_Scenes [0].UserAccountService.GetUserAccount (m_Scenes [0].RegionInfo.ScopeID, id); + UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount (m_Scenes [0].RegionInfo.ScopeID, id); if (account != null) { @@ -482,9 +484,9 @@ namespace OpenSim.Region.CoreModules.Framework.UserManagement lock (m_UserCache) m_UserCache[user.Id] = user; -// m_log.DebugFormat( -// "[USER MANAGEMENT MODULE]: Added user {0} {1} {2} {3}", -// user.Id, user.FirstName, user.LastName, user.HomeURL); + //m_log.DebugFormat( + // "[USER MANAGEMENT MODULE]: Added user {0} {1} {2} {3}", + // user.Id, user.FirstName, user.LastName, user.HomeURL); } public bool IsLocalGridUser(UUID uuid) diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsIn/Hypergrid/HypergridServiceInConnectorModule.cs b/OpenSim/Region/CoreModules/ServiceConnectorsIn/Hypergrid/HypergridServiceInConnectorModule.cs index 89abbb225b..8df1c7b49c 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsIn/Hypergrid/HypergridServiceInConnectorModule.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsIn/Hypergrid/HypergridServiceInConnectorModule.cs @@ -48,8 +48,10 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsIn.Hypergrid private static bool m_Enabled = false; private IConfigSource m_Config; - bool m_Registered = false; - GatekeeperServiceInConnector m_HypergridHandler; + private bool m_Registered = false; + private string m_LocalServiceDll = String.Empty; + private GatekeeperServiceInConnector m_HypergridHandler; + private UserAgentServerConnector m_UASHandler; #region IRegionModule interface @@ -63,6 +65,13 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsIn.Hypergrid if (m_Enabled) { m_log.Info("[HGGRID IN CONNECTOR]: Hypergrid Service In Connector enabled"); + IConfig fconfig = config.Configs["FriendsService"]; + if (fconfig != null) + { + m_LocalServiceDll = fconfig.GetString("LocalServiceModule", m_LocalServiceDll); + if (m_LocalServiceDll == String.Empty) + m_log.WarnFormat("[HGGRID IN CONNECTOR]: Friends LocalServiceModule config missing"); + } } } @@ -91,7 +100,6 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsIn.Hypergrid { if (!m_Enabled) return; - } public void RemoveRegion(Scene scene) @@ -112,14 +120,20 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsIn.Hypergrid m_log.Info("[HypergridService]: Starting..."); ISimulationService simService = scene.RequestModuleInterface(); + IFriendsSimConnector friendsConn = scene.RequestModuleInterface(); + Object[] args = new Object[] { m_Config }; + IFriendsService friendsService = ServerUtils.LoadPlugin(m_LocalServiceDll, args); + m_HypergridHandler = new GatekeeperServiceInConnector(m_Config, MainServer.Instance, simService); - IFriendsSimConnector friendsConn = scene.RequestModuleInterface(); - new UserAgentServerConnector(m_Config, MainServer.Instance, friendsConn); + m_UASHandler = new UserAgentServerConnector(m_Config, MainServer.Instance, friendsConn); + new HeloServiceInConnector(m_Config, MainServer.Instance, "HeloService"); - new HGFriendsServerConnector(m_Config, MainServer.Instance, "HGFriendsService"); + + new HGFriendsServerConnector(m_Config, MainServer.Instance, "HGFriendsService", friendsConn); } scene.RegisterModuleInterface(m_HypergridHandler.GateKeeper); + scene.RegisterModuleInterface(m_UASHandler.HomeUsersService); } #endregion diff --git a/OpenSim/Server/Handlers/Hypergrid/HGFriendServerConnector.cs b/OpenSim/Server/Handlers/Hypergrid/HGFriendServerConnector.cs index 82a72206d3..6c79c607af 100644 --- a/OpenSim/Server/Handlers/Hypergrid/HGFriendServerConnector.cs +++ b/OpenSim/Server/Handlers/Hypergrid/HGFriendServerConnector.cs @@ -36,36 +36,42 @@ namespace OpenSim.Server.Handlers.Hypergrid { public class HGFriendsServerConnector : ServiceConnector { - private IFriendsService m_FriendsService; private IUserAgentService m_UserAgentService; + private IHGFriendsService m_TheService; private string m_ConfigName = "HGFriendsService"; + // Called from Robust public HGFriendsServerConnector(IConfigSource config, IHttpServer server, string configName) : - base(config, server, configName) + this(config, server, configName, null) { - if (configName != string.Empty) + + } + + // Called from standalone configurations + public HGFriendsServerConnector(IConfigSource config, IHttpServer server, string configName, IFriendsSimConnector localConn) + : base(config, server, configName) + { + if (configName != string.Empty) m_ConfigName = configName; + Object[] args = new Object[] { config, m_ConfigName, localConn }; + IConfig serverConfig = config.Configs[m_ConfigName]; if (serverConfig == null) throw new Exception(String.Format("No section {0} in config file", m_ConfigName)); string theService = serverConfig.GetString("LocalServiceModule", String.Empty); - if (theService == String.Empty) throw new Exception("No LocalServiceModule in config file"); - - Object[] args = new Object[] { config }; - m_FriendsService = ServerUtils.LoadPlugin(theService, args); + m_TheService = ServerUtils.LoadPlugin(theService, args); theService = serverConfig.GetString("UserAgentService", string.Empty); if (theService == String.Empty) throw new Exception("No UserAgentService in " + m_ConfigName); + m_UserAgentService = ServerUtils.LoadPlugin(theService, new Object[] { config, localConn }); - m_UserAgentService = ServerUtils.LoadPlugin(theService, args); - - server.AddStreamHandler(new HGFriendsServerPostHandler(m_FriendsService, m_UserAgentService)); + server.AddStreamHandler(new HGFriendsServerPostHandler(m_TheService, m_UserAgentService, localConn)); } } } diff --git a/OpenSim/Server/Handlers/Hypergrid/HGFriendsServerPostHandler.cs b/OpenSim/Server/Handlers/Hypergrid/HGFriendsServerPostHandler.cs index 661507e7e4..ca566f2857 100644 --- a/OpenSim/Server/Handlers/Hypergrid/HGFriendsServerPostHandler.cs +++ b/OpenSim/Server/Handlers/Hypergrid/HGFriendsServerPostHandler.cs @@ -39,6 +39,7 @@ using System.Collections.Generic; using OpenSim.Server.Base; using OpenSim.Services.Interfaces; using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; using OpenSim.Framework; using OpenSim.Framework.Servers.HttpServer; using OpenMetaverse; @@ -49,15 +50,22 @@ namespace OpenSim.Server.Handlers.Hypergrid { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private IFriendsService m_FriendsService; private IUserAgentService m_UserAgentService; + private IFriendsSimConnector m_FriendsLocalSimConnector; + private IHGFriendsService m_TheService; - public HGFriendsServerPostHandler(IFriendsService service, IUserAgentService uservice) : + public HGFriendsServerPostHandler(IHGFriendsService service, IUserAgentService uas, IFriendsSimConnector friendsConn) : base("POST", "/hgfriends") { - m_FriendsService = service; - m_UserAgentService = uservice; - m_log.DebugFormat("[HGFRIENDS HANDLER]: HGFriendsServerPostHandler is On"); + m_TheService = service; + m_UserAgentService = uas; + m_FriendsLocalSimConnector = friendsConn; + + m_log.DebugFormat("[HGFRIENDS HANDLER]: HGFriendsServerPostHandler is On ({0})", + (m_FriendsLocalSimConnector == null ? "robust" : "standalone")); + + if (m_TheService == null) + m_log.ErrorFormat("[HGFRIENDS HANDLER]: TheService is null!"); } public override byte[] Handle(string path, Stream requestData, @@ -90,6 +98,26 @@ namespace OpenSim.Server.Handlers.Hypergrid case "deletefriendship": return DeleteFriendship(request); + + /* Same as inter-sim */ + case "friendship_offered": + return FriendshipOffered(request); + + case "validate_friendship_offered": + return ValidateFriendshipOffered(request); + /* + case "friendship_approved": + return FriendshipApproved(request); + + case "friendship_denied": + return FriendshipDenied(request); + + case "friendship_terminated": + return FriendshipTerminated(request); + + case "grant_rights": + return GrantRights(request); + */ } m_log.DebugFormat("[HGFRIENDS HANDLER]: unknown method {0} request {1}", method.Length, method); } @@ -126,39 +154,20 @@ namespace OpenSim.Server.Handlers.Hypergrid return FailureResult(); } - FriendInfo[] friendsInfo = m_FriendsService.GetFriends(principalID); - foreach (FriendInfo finfo in friendsInfo) - { - if (finfo.Friend.StartsWith(friendID.ToString())) - return SuccessResult(finfo.TheirFlags.ToString()); - } + int perms = m_TheService.GetFriendPerms(principalID, friendID); + if (perms < 0) + return FailureResult("Friend not found"); - return FailureResult("Friend not found"); + return SuccessResult(perms.ToString()); } byte[] NewFriendship(Dictionary request) { - if (!VerifyServiceKey(request)) - return FailureResult(); + bool verified = VerifyServiceKey(request); - // OK, can proceed FriendInfo friend = new FriendInfo(request); - UUID friendID; - string tmp = string.Empty; - if (!Util.ParseUniversalUserIdentifier(friend.Friend, out friendID, out tmp, out tmp, out tmp, out tmp)) - return FailureResult(); - - m_log.DebugFormat("[HGFRIENDS HANDLER]: New friendship {0} {1}", friend.PrincipalID, friend.Friend); - - // If the friendship already exists, return fail - FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); - foreach (FriendInfo finfo in finfos) - if (finfo.Friend.StartsWith(friendID.ToString())) - return FailureResult(); - - // the user needs to confirm when he gets home - bool success = m_FriendsService.StoreFriend(friend.PrincipalID.ToString(), friend.Friend, 0); + bool success = m_TheService.NewFriendship(friend, verified); if (success) return SuccessResult(); @@ -174,25 +183,53 @@ namespace OpenSim.Server.Handlers.Hypergrid secret = request["SECRET"].ToString(); if (secret == string.Empty) - return FailureResult(); + return BoolResult(false); - FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); - foreach (FriendInfo finfo in finfos) - { - // We check the secret here - if (finfo.Friend.StartsWith(friend.Friend) && finfo.Friend.EndsWith(secret)) - { - m_log.DebugFormat("[HGFRIENDS HANDLER]: Delete friendship {0} {1}", friend.PrincipalID, friend.Friend); - m_FriendsService.Delete(friend.PrincipalID, finfo.Friend); - m_FriendsService.Delete(finfo.Friend, friend.PrincipalID.ToString()); + bool success = m_TheService.DeleteFriendship(friend, secret); - return SuccessResult(); - } - } - - return FailureResult(); + return BoolResult(success); } + byte[] FriendshipOffered(Dictionary request) + { + UUID fromID = UUID.Zero; + UUID toID = UUID.Zero; + string message = string.Empty; + string name = string.Empty; + + m_log.DebugFormat("[HGFRIENDS HANDLER]: Friendship offered"); + if (!request.ContainsKey("FromID") || !request.ContainsKey("ToID")) + return BoolResult(false); + + if (!UUID.TryParse(request["ToID"].ToString(), out toID)) + return BoolResult(false); + + message = request["Message"].ToString(); + + if (!UUID.TryParse(request["FromID"].ToString(), out fromID)) + return BoolResult(false); + + if (request.ContainsKey("FromName")) + name = request["FromName"].ToString(); + + bool success = m_TheService.FriendshipOffered(fromID, name, toID, message); + + return BoolResult(success); + } + + byte[] ValidateFriendshipOffered(Dictionary request) + { + FriendInfo friend = new FriendInfo(request); + UUID friendID = UUID.Zero; + if (!UUID.TryParse(friend.Friend, out friendID)) + return BoolResult(false); + + bool success = m_TheService.ValidateFriendshipOffered(friend.PrincipalID, friendID); + + return BoolResult(success); + } + + #endregion #region Misc @@ -205,10 +242,15 @@ namespace OpenSim.Server.Handlers.Hypergrid return false; } + if (request["KEY"] == null || request["SESSIONID"] == null) + return false; + string serviceKey = request["KEY"].ToString(); string sessionStr = request["SESSIONID"].ToString(); + UUID sessionID; - UUID.TryParse(sessionStr, out sessionID); + if (!UUID.TryParse(sessionStr, out sessionID) || serviceKey == string.Empty) + return false; if (!m_UserAgentService.VerifyAgent(sessionID, serviceKey)) { @@ -256,7 +298,7 @@ namespace OpenSim.Server.Handlers.Hypergrid doc.AppendChild(rootElement); - XmlElement result = doc.CreateElement("", "Result", ""); + XmlElement result = doc.CreateElement("", "RESULT", ""); result.AppendChild(doc.CreateTextNode("Success")); rootElement.AppendChild(result); @@ -289,7 +331,7 @@ namespace OpenSim.Server.Handlers.Hypergrid doc.AppendChild(rootElement); - XmlElement result = doc.CreateElement("", "Result", ""); + XmlElement result = doc.CreateElement("", "RESULT", ""); result.AppendChild(doc.CreateTextNode("Failure")); rootElement.AppendChild(result); @@ -302,6 +344,28 @@ namespace OpenSim.Server.Handlers.Hypergrid return DocToBytes(doc); } + private byte[] BoolResult(bool value) + { + XmlDocument doc = new XmlDocument(); + + XmlNode xmlnode = doc.CreateNode(XmlNodeType.XmlDeclaration, + "", ""); + + doc.AppendChild(xmlnode); + + XmlElement rootElement = doc.CreateElement("", "ServerResponse", + ""); + + doc.AppendChild(rootElement); + + XmlElement result = doc.CreateElement("", "RESULT", ""); + result.AppendChild(doc.CreateTextNode(value.ToString())); + + rootElement.AppendChild(result); + + return DocToBytes(doc); + } + private byte[] DocToBytes(XmlDocument doc) { MemoryStream ms = new MemoryStream(); @@ -313,6 +377,7 @@ namespace OpenSim.Server.Handlers.Hypergrid return ms.ToArray(); } + #endregion } } diff --git a/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs b/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs index 734836855f..9a0e27e2f1 100644 --- a/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs +++ b/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs @@ -52,6 +52,11 @@ namespace OpenSim.Server.Handlers.Hypergrid // MethodBase.GetCurrentMethod().DeclaringType); private IUserAgentService m_HomeUsersService; + public IUserAgentService HomeUsersService + { + get { return m_HomeUsersService; } + } + private string[] m_AuthorizedCallers; private bool m_VerifyCallers = false; diff --git a/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs b/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs index eea9853463..3fd0c53633 100644 --- a/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs +++ b/OpenSim/Services/Connectors/Friends/FriendsSimConnector.cs @@ -43,7 +43,17 @@ namespace OpenSim.Services.Connectors.Friends { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + protected virtual string ServicePath() + { + return "friends"; + } + public bool FriendshipOffered(GridRegion region, UUID userID, UUID friendID, string message) + { + return FriendshipOffered(region, userID, friendID, message, String.Empty); + } + + public virtual bool FriendshipOffered(GridRegion region, UUID userID, UUID friendID, string message, string userName) { Dictionary sendData = new Dictionary(); //sendData["VERSIONMIN"] = ProtocolVersions.ClientProtocolVersionMin.ToString(); @@ -53,9 +63,10 @@ namespace OpenSim.Services.Connectors.Friends sendData["FromID"] = userID.ToString(); sendData["ToID"] = friendID.ToString(); sendData["Message"] = message; + if (userName != String.Empty) + sendData["FromName"] = userName; return Call(region, sendData); - } public bool FriendshipApproved(GridRegion region, UUID userID, string userName, UUID friendID) @@ -138,8 +149,11 @@ namespace OpenSim.Services.Connectors.Friends if (region == null) return false; - m_log.DebugFormat("[FRIENDS SIM CONNECTOR]: region: {0}", region.ExternalHostName + ":" + region.HttpPort); - string uri = "http://" + region.ExternalHostName + ":" + region.HttpPort + "/friends"; + string path = ServicePath(); + if (!region.ServerURI.EndsWith("/")) + path = "/" + path; + string uri = region.ServerURI + path; + m_log.DebugFormat("[FRIENDS SIM CONNECTOR]: calling {0}", uri); try { diff --git a/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs b/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs index af4b0daf9b..e3f326037c 100644 --- a/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs +++ b/OpenSim/Services/Connectors/Hypergrid/HGFriendsServiceConnector.cs @@ -40,7 +40,7 @@ using OpenMetaverse; namespace OpenSim.Services.Connectors.Hypergrid { - public class HGFriendsServicesConnector + public class HGFriendsServicesConnector : FriendsSimConnector { private static readonly ILog m_log = LogManager.GetLogger( @@ -66,6 +66,11 @@ namespace OpenSim.Services.Connectors.Hypergrid m_SessionID = sessionID; } + protected override string ServicePath() + { + return "hgfriends"; + } + #region IFriendsService public uint GetFriendPerms(UUID PrincipalID, UUID friendID) @@ -187,23 +192,69 @@ namespace OpenSim.Services.Connectors.Hypergrid { Dictionary replyData = ServerUtils.ParseXmlResponse(reply); - if ((replyData != null) && replyData.ContainsKey("Result") && (replyData["Result"] != null)) + if (replyData.ContainsKey("RESULT")) { - bool success = false; - Boolean.TryParse(replyData["Result"].ToString(), out success); - return success; + if (replyData["RESULT"].ToString().ToLower() == "true") + return true; + else + return false; } else - m_log.DebugFormat("[HGFRIENDS CONNECTOR]: Delete {0} {1} received null response", - PrincipalID, Friend); + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: reply data does not contain result field"); + } else - m_log.DebugFormat("[HGFRIENDS CONNECTOR]: DeleteFriend received null reply"); + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: received empty reply"); return false; } + public bool ValidateFriendshipOffered(UUID fromID, UUID toID) + { + FriendInfo finfo = new FriendInfo(); + finfo.PrincipalID = fromID; + finfo.Friend = toID.ToString(); + + Dictionary sendData = finfo.ToKeyValuePairs(); + + sendData["METHOD"] = "validate_friendship_offered"; + + string reply = string.Empty; + string uri = m_ServerURI + "/hgfriends"; + try + { + reply = SynchronousRestFormsRequester.MakeRequest("POST", + uri, + ServerUtils.BuildQueryString(sendData)); + } + catch (Exception e) + { + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: Exception when contacting friends server at {0}: {1}", uri, e.Message); + return false; + } + + if (reply != string.Empty) + { + Dictionary replyData = ServerUtils.ParseXmlResponse(reply); + + if (replyData.ContainsKey("RESULT")) + { + if (replyData["RESULT"].ToString().ToLower() == "true") + return true; + else + return false; + } + else + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: reply data does not contain result field"); + + } + else + m_log.DebugFormat("[HGFRIENDS CONNECTOR]: received empty reply"); + + return false; + + } #endregion } } \ No newline at end of file diff --git a/OpenSim/Services/HypergridService/HGFriendsService.cs b/OpenSim/Services/HypergridService/HGFriendsService.cs new file mode 100644 index 0000000000..19ee3e27f9 --- /dev/null +++ b/OpenSim/Services/HypergridService/HGFriendsService.cs @@ -0,0 +1,301 @@ +/* + * 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.Net; +using System.Reflection; + +using OpenSim.Framework; +using OpenSim.Services.Connectors.Friends; +using OpenSim.Services.Connectors.Hypergrid; +using OpenSim.Services.Interfaces; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenSim.Server.Base; +using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; + +using OpenMetaverse; +using log4net; +using Nini.Config; + +namespace OpenSim.Services.HypergridService +{ + /// + /// W2W social networking + /// + public class HGFriendsService : IHGFriendsService + { + private static readonly ILog m_log = + LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + + static bool m_Initialized = false; + + protected static IGridUserService m_GridUserService; + protected static IGridService m_GridService; + protected static IGatekeeperService m_GatekeeperService; + protected static IFriendsService m_FriendsService; + protected static IPresenceService m_PresenceService; + protected static IUserAccountService m_UserAccountService; + protected static IFriendsSimConnector m_FriendsLocalSimConnector; // standalone, points to HGFriendsModule + protected static FriendsSimConnector m_FriendsSimConnector; // grid + + private static string m_ConfigName = "HGFriendsService"; + + public HGFriendsService(IConfigSource config, String configName, IFriendsSimConnector localSimConn) + { + if (m_FriendsLocalSimConnector == null) + m_FriendsLocalSimConnector = localSimConn; + + if (!m_Initialized) + { + m_Initialized = true; + + if (configName != String.Empty) + m_ConfigName = configName; + + Object[] args = new Object[] { config }; + + IConfig serverConfig = config.Configs[m_ConfigName]; + if (serverConfig == null) + throw new Exception(String.Format("No section {0} in config file", m_ConfigName)); + + string theService = serverConfig.GetString("FriendsService", string.Empty); + if (theService == String.Empty) + throw new Exception("No FriendsService in config file " + m_ConfigName); + m_FriendsService = ServerUtils.LoadPlugin(theService, args); + + theService = serverConfig.GetString("UserAccountService", string.Empty); + if (theService == String.Empty) + throw new Exception("No UserAccountService in " + m_ConfigName); + m_UserAccountService = ServerUtils.LoadPlugin(theService, args); + + theService = serverConfig.GetString("GridService", string.Empty); + if (theService == String.Empty) + throw new Exception("No GridService in " + m_ConfigName); + m_GridService = ServerUtils.LoadPlugin(theService, args); + + theService = serverConfig.GetString("PresenceService", string.Empty); + if (theService == String.Empty) + throw new Exception("No PresenceService in " + m_ConfigName); + m_PresenceService = ServerUtils.LoadPlugin(theService, args); + + m_FriendsSimConnector = new FriendsSimConnector(); + + m_log.DebugFormat("[HGFRIENDS SERVICE]: Starting..."); + + } + } + + #region IHGFriendsService + + public int GetFriendPerms(UUID userID, UUID friendID) + { + FriendInfo[] friendsInfo = m_FriendsService.GetFriends(userID); + foreach (FriendInfo finfo in friendsInfo) + { + if (finfo.Friend.StartsWith(friendID.ToString())) + return finfo.TheirFlags; + } + return -1; + } + + public bool NewFriendship(FriendInfo friend, bool verified) + { + UUID friendID; + string tmp = string.Empty, url = String.Empty, first = String.Empty, last = String.Empty; + if (!Util.ParseUniversalUserIdentifier(friend.Friend, out friendID, out url, out first, out last, out tmp)) + return false; + + m_log.DebugFormat("[HGFRIENDS SERVICE]: New friendship {0} {1} ({2})", friend.PrincipalID, friend.Friend, verified); + + // Does the friendship already exist? + FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); + foreach (FriendInfo finfo in finfos) + { + if (finfo.Friend.StartsWith(friendID.ToString())) + return false; + } + // Verified user session. But the user needs to confirm friendship when he gets home + if (verified) + return m_FriendsService.StoreFriend(friend.PrincipalID.ToString(), friend.Friend, 0); + + // Does the reverted friendship exist? meaning that this user initiated the request + finfos = m_FriendsService.GetFriends(friendID); + bool userInitiatedOffer = false; + foreach (FriendInfo finfo in finfos) + { + if (friend.Friend.StartsWith(finfo.PrincipalID.ToString()) && finfo.Friend.StartsWith(friend.PrincipalID.ToString()) && finfo.TheirFlags == -1) + { + userInitiatedOffer = true; + // Let's delete the existing friendship relations that was stored + m_FriendsService.Delete(friendID, finfo.Friend); + break; + } + } + + if (userInitiatedOffer) + { + m_FriendsService.StoreFriend(friend.PrincipalID.ToString(), friend.Friend, 1); + m_FriendsService.StoreFriend(friend.Friend, friend.PrincipalID.ToString(), 1); + // notify the user + ForwardToSim("ApproveFriendshipRequest", friendID, Util.UniversalName(first, last, url), "", friend.PrincipalID, ""); + return true; + } + return false; + } + + public bool DeleteFriendship(FriendInfo friend, string secret) + { + FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); + foreach (FriendInfo finfo in finfos) + { + // We check the secret here. Or if the friendship request was initiated here, and was declined + if (finfo.Friend.StartsWith(friend.Friend) && finfo.Friend.EndsWith(secret)) + { + m_log.DebugFormat("[HGFRIENDS SERVICE]: Delete friendship {0} {1}", friend.PrincipalID, friend.Friend); + m_FriendsService.Delete(friend.PrincipalID, finfo.Friend); + m_FriendsService.Delete(finfo.Friend, friend.PrincipalID.ToString()); + + return true; + } + } + + return false; + } + + public bool FriendshipOffered(UUID fromID, string fromName, UUID toID, string message) + { + UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero, toID); + if (account == null) + return false; + + // OK, we have that user here. + // So let's send back the call, but start a thread to continue + // with the verification and the actual action. + + Util.FireAndForget(delegate { ProcessFriendshipOffered(fromID, fromName, toID, message); }); + + return true; + } + + public bool ValidateFriendshipOffered(UUID fromID, UUID toID) + { + FriendInfo[] finfos = m_FriendsService.GetFriends(toID.ToString()); + foreach (FriendInfo fi in finfos) + { + if (fi.Friend.StartsWith(fromID.ToString()) && fi.TheirFlags == -1) + return true; + } + return false; + } + + #endregion IHGFriendsService + + #region Aux + + private void ProcessFriendshipOffered(UUID fromID, String fromName, UUID toID, String message) + { + // Great, it's a genuine request. Let's proceed. + // But now we need to confirm that the requester is who he says he is + // before we act on the friendship request. + + if (!fromName.Contains("@")) + return; + + string[] parts = fromName.Split(new char[] {'@'}); + if (parts.Length != 2) + return; + + string uriStr = "http://" + parts[1]; + try + { + new Uri(uriStr); + } + catch (UriFormatException) + { + return; + } + + UserAgentServiceConnector uasConn = new UserAgentServiceConnector(uriStr); + Dictionary servers = uasConn.GetServerURLs(fromID); + if (!servers.ContainsKey("FriendsServerURI")) + return; + + HGFriendsServicesConnector friendsConn = new HGFriendsServicesConnector(servers["FriendsServerURI"].ToString()); + if (!friendsConn.ValidateFriendshipOffered(fromID, toID)) + { + m_log.WarnFormat("[HGFRIENDS SERVICE]: Friendship request from {0} to {1} is invalid. Impersonations?", fromID, toID); + return; + } + + string fromUUI = Util.UniversalIdentifier(fromID, parts[0], "@" + parts[1], uriStr); + // OK, we're good! + ForwardToSim("FriendshipOffered", fromID, fromName, fromUUI, toID, message); + } + + private bool ForwardToSim(string op, UUID fromID, string name, String fromUUI, UUID toID, string message) + { + PresenceInfo session = null; + GridRegion region = null; + PresenceInfo[] sessions = m_PresenceService.GetAgents(new string[] { toID.ToString() }); + if (sessions != null && sessions.Length > 0) + session = sessions[0]; + if (session != null) + region = m_GridService.GetRegionByUUID(UUID.Zero, session.RegionID); + + switch (op) + { + case "FriendshipOffered": + // Let's store backwards + string secret = UUID.Random().ToString().Substring(0, 8); + m_FriendsService.StoreFriend(toID.ToString(), fromUUI + ";" + secret, 0); + if (m_FriendsLocalSimConnector != null) // standalone + { + GridInstantMessage im = new GridInstantMessage(null, fromID, name, toID, + (byte)InstantMessageDialog.FriendshipOffered, message, false, Vector3.Zero); + // !! HACK + im.imSessionID = im.fromAgentID; + return m_FriendsLocalSimConnector.LocalFriendshipOffered(toID, im); + } + else if (region != null) // grid + return m_FriendsSimConnector.FriendshipOffered(region, fromID, toID, message, name); + break; + case "ApproveFriendshipRequest": + if (m_FriendsLocalSimConnector != null) // standalone + return m_FriendsLocalSimConnector.LocalFriendshipApproved(fromID, name, toID); + else if (region != null) //grid + return m_FriendsSimConnector.FriendshipApproved(region, fromID, name, toID); + break; + } + + return false; + } + + #endregion Aux + } +} diff --git a/OpenSim/Services/Interfaces/IHypergridServices.cs b/OpenSim/Services/Interfaces/IHypergridServices.cs index 0cd44f721f..f48b8a9a76 100644 --- a/OpenSim/Services/Interfaces/IHypergridServices.cs +++ b/OpenSim/Services/Interfaces/IHypergridServices.cs @@ -81,6 +81,17 @@ namespace OpenSim.Services.Interfaces public interface IFriendsSimConnector { bool StatusNotify(UUID userID, UUID friendID, bool online); + bool LocalFriendshipOffered(UUID toID, GridInstantMessage im); + bool LocalFriendshipApproved(UUID userID, string userName, UUID friendID); + } + + public interface IHGFriendsService + { + int GetFriendPerms(UUID userID, UUID friendID); + bool NewFriendship(FriendInfo finfo, bool verified); + bool DeleteFriendship(FriendInfo finfo, string secret); + bool FriendshipOffered(UUID from, string fromName, UUID to, string message); + bool ValidateFriendshipOffered(UUID fromID, UUID toID); } public interface IInstantMessageSimConnector diff --git a/bin/config-include/StandaloneHypergrid.ini b/bin/config-include/StandaloneHypergrid.ini index ee51067fed..75c478803b 100644 --- a/bin/config-include/StandaloneHypergrid.ini +++ b/bin/config-include/StandaloneHypergrid.ini @@ -159,8 +159,12 @@ UserAccountsService = "OpenSim.Services.UserAccountService.dll:UserAccountService" [HGFriendsService] - LocalServiceModule = "OpenSim.Services.FriendsService.dll:FriendsService" + LocalServiceModule = "OpenSim.Services.HypergridService.dll:HGFriendsService" UserAgentService = "OpenSim.Services.HypergridService.dll:UserAgentService" + FriendsService = "OpenSim.Services.FriendsService.dll:FriendsService" + UserAccountService = "OpenSim.Services.UserAccountService.dll:UserAccountService" + GridService = "OpenSim.Services.GridService.dll:GridService" + PresenceService = "OpenSim.Services.PresenceService.dll:PresenceService" [HGInstantMessageService] LocalServiceModule = "OpenSim.Services.HypergridService.dll:HGInstantMessageService"