123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- /*
- * 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.Collections.Specialized;
- using System.Reflection;
- using System.IO;
- using System.Web;
- using System.Xml;
- using log4net;
- using Mono.Addins;
- using Nini.Config;
- using OpenMetaverse;
- using OpenMetaverse.Messages.Linden;
- using OpenMetaverse.StructuredData;
- using OpenSim.Framework;
- using OpenSim.Framework.Capabilities;
- using OpenSim.Framework.Servers;
- using OpenSim.Framework.Servers.HttpServer;
- using OpenSim.Region.Framework.Interfaces;
- using OpenSim.Region.Framework.Scenes;
- using OpenSim.Services.Interfaces;
- using Caps = OpenSim.Framework.Capabilities.Caps;
- using OSDArray = OpenMetaverse.StructuredData.OSDArray;
- using OSDMap = OpenMetaverse.StructuredData.OSDMap;
- namespace OpenSim.Region.CoreModules.World.Media.Moap
- {
- [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")]
- public class MoapModule : INonSharedRegionModule, IMoapModule
- {
- private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
- public string Name { get { return "MoapModule"; } }
- public Type ReplaceableInterface { get { return null; } }
- /// <summary>
- /// Is this module enabled?
- /// </summary>
- protected bool m_isEnabled = true;
- /// <summary>
- /// The scene to which this module is attached
- /// </summary>
- protected Scene m_scene;
- /// <summary>
- /// Track the ObjectMedia capabilities given to users keyed by path
- /// </summary>
- protected Dictionary<string, UUID> m_omCapUsers = new Dictionary<string, UUID>();
- /// <summary>
- /// Track the ObjectMedia capabilities given to users keyed by agent. Lock m_omCapUsers to manipulate.
- /// </summary>
- protected Dictionary<UUID, string> m_omCapUrls = new Dictionary<UUID, string>();
- /// <summary>
- /// Track the ObjectMediaUpdate capabilities given to users keyed by path
- /// </summary>
- protected Dictionary<string, UUID> m_omuCapUsers = new Dictionary<string, UUID>();
- /// <summary>
- /// Track the ObjectMediaUpdate capabilities given to users keyed by agent. Lock m_omuCapUsers to manipulate
- /// </summary>
- protected Dictionary<UUID, string> m_omuCapUrls = new Dictionary<UUID, string>();
- public void Initialise(IConfigSource configSource)
- {
- IConfig config = configSource.Configs["MediaOnAPrim"];
- if (config != null && !config.GetBoolean("Enabled", false))
- m_isEnabled = false;
- // else
- // m_log.Debug("[MOAP]: Initialised module.")l
- }
- public void AddRegion(Scene scene)
- {
- if (!m_isEnabled)
- return;
- m_scene = scene;
- m_scene.RegisterModuleInterface<IMoapModule>(this);
- }
- public void RemoveRegion(Scene scene) {}
- public void RegionLoaded(Scene scene)
- {
- if (!m_isEnabled)
- return;
- m_scene.EventManager.OnRegisterCaps += OnRegisterCaps;
- m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps;
- m_scene.EventManager.OnSceneObjectPartCopy += OnSceneObjectPartCopy;
- }
- public void Close()
- {
- if (!m_isEnabled)
- return;
- m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps;
- m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps;
- m_scene.EventManager.OnSceneObjectPartCopy -= OnSceneObjectPartCopy;
- }
- public void OnRegisterCaps(UUID agentID, Caps caps)
- {
- // m_log.DebugFormat(
- // "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID);
- string omCapUrl = "/CAPS/" + UUID.Random();
- lock (m_omCapUsers)
- {
- m_omCapUsers[omCapUrl] = agentID;
- m_omCapUrls[agentID] = omCapUrl;
- // Even though we're registering for POST we're going to get GETS and UPDATES too
- caps.RegisterHandler(
- "ObjectMedia",
- new RestStreamHandler(
- "POST", omCapUrl, HandleObjectMediaMessage, "ObjectMedia", agentID.ToString()));
- }
- string omuCapUrl = "/CAPS/" + UUID.Random();
- lock (m_omuCapUsers)
- {
- m_omuCapUsers[omuCapUrl] = agentID;
- m_omuCapUrls[agentID] = omuCapUrl;
- // Even though we're registering for POST we're going to get GETS and UPDATES too
- caps.RegisterHandler(
- "ObjectMediaNavigate",
- new RestStreamHandler(
- "POST", omuCapUrl, HandleObjectMediaNavigateMessage, "ObjectMediaNavigate", agentID.ToString()));
- }
- }
- public void OnDeregisterCaps(UUID agentID, Caps caps)
- {
- lock (m_omCapUsers)
- {
- string path = m_omCapUrls[agentID];
- m_omCapUrls.Remove(agentID);
- m_omCapUsers.Remove(path);
- }
- lock (m_omuCapUsers)
- {
- string path = m_omuCapUrls[agentID];
- m_omuCapUrls.Remove(agentID);
- m_omuCapUsers.Remove(path);
- }
- }
- protected void OnSceneObjectPartCopy(SceneObjectPart copy, SceneObjectPart original, bool userExposed)
- {
- if (original.Shape.Media != null)
- {
- PrimitiveBaseShape.MediaList dupeMedia = new PrimitiveBaseShape.MediaList();
- lock (original.Shape.Media)
- {
- foreach (MediaEntry me in original.Shape.Media)
- {
- if (me != null)
- dupeMedia.Add(MediaEntry.FromOSD(me.GetOSD()));
- else
- dupeMedia.Add(null);
- }
- }
- copy.Shape.Media = dupeMedia;
- }
- }
- public MediaEntry GetMediaEntry(SceneObjectPart part, int face)
- {
- MediaEntry me = null;
- CheckFaceParam(part, face);
- List<MediaEntry> media = part.Shape.Media;
- if (null == media)
- {
- me = null;
- }
- else
- {
- lock (media)
- me = media[face];
- if (me != null)
- {
- Primitive.TextureEntry te = part.Shape.Textures;
- Primitive.TextureEntryFace teFace = te.GetFace((uint)face);
- if (teFace != null && teFace.MediaFlags)
- me = MediaEntry.FromOSD(me.GetOSD());
- }
- }
- // m_log.DebugFormat("[MOAP]: GetMediaEntry for {0} face {1} found {2}", part.Name, face, me);
- return me;
- }
- /// <summary>
- /// Set the media entry on the face of the given part.
- /// </summary>
- /// <param name="part">/param>
- /// <param name="face"></param>
- /// <param name="me">If null, then the media entry is cleared.</param>
- public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me)
- {
- // m_log.DebugFormat("[MOAP]: SetMediaEntry for {0}, face {1}", part.Name, face);
- CheckFaceParam(part, face);
- if (null == part.Shape.Media)
- {
- if (me == null)
- return;
- else
- part.Shape.Media = new PrimitiveBaseShape.MediaList(new MediaEntry[part.GetNumberOfSides()]);
- }
- lock (part.Shape.Media)
- part.Shape.Media[face] = me;
- UpdateMediaUrl(part, UUID.Zero);
- SetPartMediaFlags(part, face, me != null);
- part.ParentGroup.HasGroupChanged = true;
- part.ScheduleFullUpdate();
- part.TriggerScriptChangedEvent(Changed.MEDIA);
- }
- /// <summary>
- /// Clear the media entry from the face of the given part.
- /// </summary>
- /// <param name="part"></param>
- /// <param name="face"></param>
- public void ClearMediaEntry(SceneObjectPart part, int face)
- {
- SetMediaEntry(part, face, null);
- }
- /// <summary>
- /// Set the media flags on the texture face of the given part.
- /// </summary>
- /// <remarks>
- /// The fact that we need a separate function to do what should be a simple one line operation is BUTT UGLY.
- /// </remarks>
- /// <param name="part"></param>
- /// <param name="face"></param>
- /// <param name="flag"></param>
- protected void SetPartMediaFlags(SceneObjectPart part, int face, bool flag)
- {
- Primitive.TextureEntry te = part.Shape.Textures;
- Primitive.TextureEntryFace teFace = te.GetFace((uint)face);
- teFace.MediaFlags = flag;
- part.Shape.Textures = te;
- }
- /// <summary>
- /// Sets or gets per face media textures.
- /// </summary>
- /// <param name="request"></param>
- /// <param name="path"></param>
- /// <param name="param"></param>
- /// <param name="httpRequest"></param>
- /// <param name="httpResponse"></param>
- /// <returns></returns>
- protected string HandleObjectMediaMessage(
- string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
- {
- // m_log.DebugFormat("[MOAP]: Got ObjectMedia path [{0}], raw request [{1}]", path, request);
- OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request);
- ObjectMediaMessage omm = new ObjectMediaMessage();
- omm.Deserialize(osd);
- if (omm.Request is ObjectMediaRequest)
- return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest);
- else if (omm.Request is ObjectMediaUpdate)
- return HandleObjectMediaUpdate(path, omm.Request as ObjectMediaUpdate);
- throw new Exception(
- string.Format(
- "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}",
- omm.Request.GetType()));
- }
- /// <summary>
- /// Handle a fetch request for media textures
- /// </summary>
- /// <param name="omr"></param>
- /// <returns></returns>
- protected string HandleObjectMediaRequest(ObjectMediaRequest omr)
- {
- UUID primId = omr.PrimID;
- SceneObjectPart part = m_scene.GetSceneObjectPart(primId);
- if (null == part)
- {
- m_log.WarnFormat(
- "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in region {1}",
- primId, m_scene.RegionInfo.RegionName);
- return string.Empty;
- }
- if (part.Shape.Media == null)
- return string.Empty;
- MediaEntry[] currentML = part.Shape.Media.ToArray();
- int nentries = currentML.Length;
- int nsides = part.GetNumberOfSides();
- if(nentries > nsides)
- nentries = nsides;
- Primitive.TextureEntry te = part.Shape.Textures;
- bool isnull = true;
- for(int face = 0; face < nentries; ++face)
- {
- Primitive.TextureEntryFace teFace = te.GetFace((uint)face);
- if(!teFace.MediaFlags)
- currentML[face] = null;
- else
- isnull = false;
- }
- if(isnull)
- {
- //remove the damm thing
- part.Shape.Media = null;
- part.MediaUrl = null;
- return string.Empty;
- }
- ObjectMediaResponse resp = new ObjectMediaResponse();
- resp.PrimID = primId;
- resp.FaceMedia = currentML;
- resp.Version = part.MediaUrl;
- string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize());
- // m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp);
- return rawResp;
- }
- /// <summary>
- /// Handle an update of media textures.
- /// </summary>
- /// <param name="path">Path on which this request was made</param>
- /// <param name="omu">/param>
- /// <returns></returns>
- protected string HandleObjectMediaUpdate(string path, ObjectMediaUpdate omu)
- {
- UUID primId = omu.PrimID;
- SceneObjectPart part = m_scene.GetSceneObjectPart(primId);
- if (null == part)
- {
- m_log.WarnFormat(
- "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}",
- primId, m_scene.RegionInfo.RegionName);
- return string.Empty;
- }
- // m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId);
- //
- // for (int i = 0; i < omu.FaceMedia.Length; i++)
- // {
- // MediaEntry me = omu.FaceMedia[i];
- // string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD()));
- // m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v);
- // }
- if (omu.FaceMedia.Length > part.GetNumberOfSides())
- {
- m_log.WarnFormat(
- "[MOAP]: Received {0} media entries from client for prim {1} {2} but this prim has only {3} faces. Dropping request.",
- omu.FaceMedia.Length, part.Name, part.UUID, part.GetNumberOfSides());
- return string.Empty;
- }
- UUID agentId = default(UUID);
- lock (m_omCapUsers)
- agentId = m_omCapUsers[path];
- List<MediaEntry> media = part.Shape.Media;
- if (null == media)
- {
- // m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name);
- part.Shape.Media = new PrimitiveBaseShape.MediaList(omu.FaceMedia);
- for (int i = 0; i < omu.FaceMedia.Length; i++)
- {
- if (omu.FaceMedia[i] != null)
- {
- // FIXME: Race condition here since some other texture entry manipulator may overwrite/get
- // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry
- // directly.
- SetPartMediaFlags(part, i, true);
- // m_log.DebugFormat(
- // "[MOAP]: Media flags for face {0} is {1}",
- // i, part.Shape.Textures.FaceTextures[i].MediaFlags);
- }
- }
- }
- else
- {
- // m_log.DebugFormat("[MOAP]: Setting existing media list for {0}", part.Name);
- // We need to go through the media textures one at a time to make sure that we have permission
- // to change them
- // FIXME: Race condition here since some other texture entry manipulator may overwrite/get
- // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry
- // directly.
- Primitive.TextureEntry te = part.Shape.Textures;
- lock (media)
- {
- for (int i = 0; i < media.Count; i++)
- {
- if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i))
- {
- media[i] = omu.FaceMedia[i];
- // When a face is cleared this is done by setting the MediaFlags in the TextureEntry via a normal
- // texture update, so we don't need to worry about clearing MediaFlags here.
- if (null == media[i])
- continue;
- SetPartMediaFlags(part, i, true);
- // m_log.DebugFormat(
- // "[MOAP]: Media flags for face {0} is {1}",
- // i, face.MediaFlags);
- // m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name);
- }
- }
- }
- part.Shape.Textures = te;
- // for (int i2 = 0; i2 < part.Shape.Textures.FaceTextures.Length; i2++)
- // m_log.DebugFormat("[MOAP]: FaceTexture[{0}] is {1}", i2, part.Shape.Textures.FaceTextures[i2]);
- }
- UpdateMediaUrl(part, agentId);
- // Arguably, we could avoid sending a full update to the avatar that just changed the texture.
- part.ParentGroup.HasGroupChanged = true;
- part.ScheduleFullUpdate();
- part.TriggerScriptChangedEvent(Changed.MEDIA);
- return string.Empty;
- }
- /// <summary>
- /// Received from the viewer if a user has changed the url of a media texture.
- /// </summary>
- /// <param name="request"></param>
- /// <param name="path"></param>
- /// <param name="param"></param>
- /// <param name="httpRequest">/param>
- /// <param name="httpResponse">/param>
- /// <returns></returns>
- protected string HandleObjectMediaNavigateMessage(
- string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
- {
- // m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request [{0}]", request);
- OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request);
- ObjectMediaNavigateMessage omn = new ObjectMediaNavigateMessage();
- omn.Deserialize(osd);
- UUID primId = omn.PrimID;
- SceneObjectPart part = m_scene.GetSceneObjectPart(primId);
- if (null == part)
- {
- m_log.WarnFormat(
- "[MOAP]: Received an ObjectMediaNavigateMessage for prim {0} but this doesn't exist in region {1}",
- primId, m_scene.RegionInfo.RegionName);
- return string.Empty;
- }
- UUID agentId = default(UUID);
- lock (m_omuCapUsers)
- agentId = m_omuCapUsers[path];
- if (!m_scene.Permissions.CanInteractWithPrimMedia(agentId, part.UUID, omn.Face))
- return string.Empty;
- // m_log.DebugFormat(
- // "[MOAP]: Received request to update media entry for face {0} on prim {1} {2} to {3}",
- // omn.Face, part.Name, part.UUID, omn.URL);
- // If media has never been set for this prim, then just return.
- if (null == part.Shape.Media)
- return string.Empty;
- MediaEntry me = null;
- lock (part.Shape.Media)
- me = part.Shape.Media[omn.Face];
- // Do the same if media has not been set up for a specific face
- if (null == me)
- return string.Empty;
- if (me.EnableWhiteList)
- {
- if (!CheckUrlAgainstWhitelist(omn.URL, me.WhiteList))
- {
- // m_log.DebugFormat(
- // "[MOAP]: Blocking change of face {0} on prim {1} {2} to {3} since it's not on the enabled whitelist",
- // omn.Face, part.Name, part.UUID, omn.URL);
- return string.Empty;
- }
- }
- me.CurrentURL = omn.URL;
- UpdateMediaUrl(part, agentId);
- part.ParentGroup.HasGroupChanged = true;
- part.ScheduleFullUpdate();
- part.TriggerScriptChangedEvent(Changed.MEDIA);
- return OSDParser.SerializeLLSDXmlString(new OSD());
- }
- /// <summary>
- /// Check that the face number is valid for the given prim.
- /// </summary>
- /// <param name="part"></param>
- /// <param name="face"></param>
- protected void CheckFaceParam(SceneObjectPart part, int face)
- {
- if (face < 0)
- throw new ArgumentException("Face cannot be less than zero");
- int maxFaces = part.GetNumberOfSides() - 1;
- if (face > maxFaces)
- throw new ArgumentException(
- string.Format("Face argument was {0} but max is {1}", face, maxFaces));
- }
- /// <summary>
- /// Update the media url of the given part
- /// </summary>
- /// <param name="part"></param>
- /// <param name="updateId">
- /// The id to attach to this update. Normally, this is the user that changed the
- /// texture
- /// </param>
- protected void UpdateMediaUrl(SceneObjectPart part, UUID updateId)
- {
- if (null == part.MediaUrl)
- {
- // TODO: We can't set the last changer until we start tracking which cap we give to which agent id
- part.MediaUrl = "x-mv:0000000000/" + updateId;
- }
- else
- {
- string rawVersion = part.MediaUrl.Substring(5, 10);
- int version = int.Parse(rawVersion);
- part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, updateId);
- }
- // m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID);
- }
- /// <summary>
- /// Check the given url against the given whitelist.
- /// </summary>
- /// <param name="rawUrl"></param>
- /// <param name="whitelist"></param>
- /// <returns>true if the url matches an entry on the whitelist, false otherwise</returns>
- protected bool CheckUrlAgainstWhitelist(string rawUrl, string[] whitelist)
- {
- if (whitelist == null)
- return false;
- Uri url = new Uri(rawUrl);
- foreach (string origWlUrl in whitelist)
- {
- string wlUrl = origWlUrl;
- // Deal with a line-ending wildcard
- if (wlUrl.EndsWith("*"))
- wlUrl = wlUrl.Remove(wlUrl.Length - 1);
- // m_log.DebugFormat("[MOAP]: Checking whitelist URL pattern {0}", origWlUrl);
- // Handle a line starting wildcard slightly differently since this can only match the domain, not the path
- if (wlUrl.StartsWith("*"))
- {
- wlUrl = wlUrl.Substring(1);
- if (url.Host.Contains(wlUrl))
- {
- // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl);
- return true;
- }
- }
- else
- {
- string urlToMatch = url.Authority + url.AbsolutePath;
- if (urlToMatch.StartsWith(wlUrl))
- {
- // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl);
- return true;
- }
- }
- }
- return false;
- }
- }
- }
|