opensim-development – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
32 using System.Reflection;
33 using System.IO;
34 using System.Web;
35 using System.Xml;
36 using log4net;
37 using Mono.Addins;
38 using Nini.Config;
39 using OpenMetaverse;
40 using OpenMetaverse.Messages.Linden;
41 using OpenMetaverse.StructuredData;
42 using OpenSim.Framework;
43 using OpenSim.Framework.Capabilities;
44 using OpenSim.Framework.Servers;
45 using OpenSim.Framework.Servers.HttpServer;
46 using OpenSim.Region.Framework.Interfaces;
47 using OpenSim.Region.Framework.Scenes;
48 using OpenSim.Services.Interfaces;
49 using Caps = OpenSim.Framework.Capabilities.Caps;
50 using OSDArray = OpenMetaverse.StructuredData.OSDArray;
51 using OSDMap = OpenMetaverse.StructuredData.OSDMap;
52  
53 namespace OpenSim.Region.CoreModules.World.Media.Moap
54 {
55 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MoapModule")]
56 public class MoapModule : INonSharedRegionModule, IMoapModule
57 {
58 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
59  
60 public string Name { get { return "MoapModule"; } }
61 public Type ReplaceableInterface { get { return null; } }
62  
63 /// <summary>
64 /// Is this module enabled?
65 /// </summary>
66 protected bool m_isEnabled = true;
67  
68 /// <summary>
69 /// The scene to which this module is attached
70 /// </summary>
71 protected Scene m_scene;
72  
73 /// <summary>
74 /// Track the ObjectMedia capabilities given to users keyed by path
75 /// </summary>
76 protected Dictionary<string, UUID> m_omCapUsers = new Dictionary<string, UUID>();
77  
78 /// <summary>
79 /// Track the ObjectMedia capabilities given to users keyed by agent. Lock m_omCapUsers to manipulate.
80 /// </summary>
81 protected Dictionary<UUID, string> m_omCapUrls = new Dictionary<UUID, string>();
82  
83 /// <summary>
84 /// Track the ObjectMediaUpdate capabilities given to users keyed by path
85 /// </summary>
86 protected Dictionary<string, UUID> m_omuCapUsers = new Dictionary<string, UUID>();
87  
88 /// <summary>
89 /// Track the ObjectMediaUpdate capabilities given to users keyed by agent. Lock m_omuCapUsers to manipulate
90 /// </summary>
91 protected Dictionary<UUID, string> m_omuCapUrls = new Dictionary<UUID, string>();
92  
93 public void Initialise(IConfigSource configSource)
94 {
95 IConfig config = configSource.Configs["MediaOnAPrim"];
96  
97 if (config != null && !config.GetBoolean("Enabled", false))
98 m_isEnabled = false;
99 // else
100 // m_log.Debug("[MOAP]: Initialised module.")l
101 }
102  
103 public void AddRegion(Scene scene)
104 {
105 if (!m_isEnabled)
106 return;
107  
108 m_scene = scene;
109 m_scene.RegisterModuleInterface<IMoapModule>(this);
110 }
111  
112 public void RemoveRegion(Scene scene) {}
113  
114 public void RegionLoaded(Scene scene)
115 {
116 if (!m_isEnabled)
117 return;
118  
119 m_scene.EventManager.OnRegisterCaps += OnRegisterCaps;
120 m_scene.EventManager.OnDeregisterCaps += OnDeregisterCaps;
121 m_scene.EventManager.OnSceneObjectPartCopy += OnSceneObjectPartCopy;
122 }
123  
124 public void Close()
125 {
126 if (!m_isEnabled)
127 return;
128  
129 m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps;
130 m_scene.EventManager.OnDeregisterCaps -= OnDeregisterCaps;
131 m_scene.EventManager.OnSceneObjectPartCopy -= OnSceneObjectPartCopy;
132 }
133  
134 public void OnRegisterCaps(UUID agentID, Caps caps)
135 {
136 // m_log.DebugFormat(
137 // "[MOAP]: Registering ObjectMedia and ObjectMediaNavigate capabilities for agent {0}", agentID);
138  
139 string omCapUrl = "/CAPS/" + UUID.Random();
140  
141 lock (m_omCapUsers)
142 {
143 m_omCapUsers[omCapUrl] = agentID;
144 m_omCapUrls[agentID] = omCapUrl;
145  
146 // Even though we're registering for POST we're going to get GETS and UPDATES too
147 caps.RegisterHandler(
148 "ObjectMedia",
149 new RestStreamHandler(
150 "POST", omCapUrl, HandleObjectMediaMessage, "ObjectMedia", agentID.ToString()));
151 }
152  
153 string omuCapUrl = "/CAPS/" + UUID.Random();
154  
155 lock (m_omuCapUsers)
156 {
157 m_omuCapUsers[omuCapUrl] = agentID;
158 m_omuCapUrls[agentID] = omuCapUrl;
159  
160 // Even though we're registering for POST we're going to get GETS and UPDATES too
161 caps.RegisterHandler(
162 "ObjectMediaNavigate",
163 new RestStreamHandler(
164 "POST", omuCapUrl, HandleObjectMediaNavigateMessage, "ObjectMediaNavigate", agentID.ToString()));
165 }
166 }
167  
168 public void OnDeregisterCaps(UUID agentID, Caps caps)
169 {
170 lock (m_omCapUsers)
171 {
172 string path = m_omCapUrls[agentID];
173 m_omCapUrls.Remove(agentID);
174 m_omCapUsers.Remove(path);
175 }
176  
177 lock (m_omuCapUsers)
178 {
179 string path = m_omuCapUrls[agentID];
180 m_omuCapUrls.Remove(agentID);
181 m_omuCapUsers.Remove(path);
182 }
183 }
184  
185 protected void OnSceneObjectPartCopy(SceneObjectPart copy, SceneObjectPart original, bool userExposed)
186 {
187 if (original.Shape.Media != null)
188 {
189 PrimitiveBaseShape.MediaList dupeMedia = new PrimitiveBaseShape.MediaList();
190 lock (original.Shape.Media)
191 {
192 foreach (MediaEntry me in original.Shape.Media)
193 {
194 if (me != null)
195 dupeMedia.Add(MediaEntry.FromOSD(me.GetOSD()));
196 else
197 dupeMedia.Add(null);
198 }
199 }
200  
201 copy.Shape.Media = dupeMedia;
202 }
203 }
204  
205 public MediaEntry GetMediaEntry(SceneObjectPart part, int face)
206 {
207 MediaEntry me = null;
208  
209 CheckFaceParam(part, face);
210  
211 List<MediaEntry> media = part.Shape.Media;
212  
213 if (null == media)
214 {
215 me = null;
216 }
217 else
218 {
219 lock (media)
220 me = media[face];
221  
222 // TODO: Really need a proper copy constructor down in libopenmetaverse
223 if (me != null)
224 me = MediaEntry.FromOSD(me.GetOSD());
225 }
226  
227 // m_log.DebugFormat("[MOAP]: GetMediaEntry for {0} face {1} found {2}", part.Name, face, me);
228  
229 return me;
230 }
231  
232 /// <summary>
233 /// Set the media entry on the face of the given part.
234 /// </summary>
235 /// <param name="part">/param>
236 /// <param name="face"></param>
237 /// <param name="me">If null, then the media entry is cleared.</param>
238 public void SetMediaEntry(SceneObjectPart part, int face, MediaEntry me)
239 {
240 // m_log.DebugFormat("[MOAP]: SetMediaEntry for {0}, face {1}", part.Name, face);
241  
242 CheckFaceParam(part, face);
243  
244 if (null == part.Shape.Media)
245 {
246 if (me == null)
247 return;
248 else
249 part.Shape.Media = new PrimitiveBaseShape.MediaList(new MediaEntry[part.GetNumberOfSides()]);
250 }
251  
252 lock (part.Shape.Media)
253 part.Shape.Media[face] = me;
254  
255 UpdateMediaUrl(part, UUID.Zero);
256  
257 SetPartMediaFlags(part, face, me != null);
258  
259 part.ScheduleFullUpdate();
260 part.TriggerScriptChangedEvent(Changed.MEDIA);
261 }
262  
263 /// <summary>
264 /// Clear the media entry from the face of the given part.
265 /// </summary>
266 /// <param name="part"></param>
267 /// <param name="face"></param>
268 public void ClearMediaEntry(SceneObjectPart part, int face)
269 {
270 SetMediaEntry(part, face, null);
271 }
272  
273 /// <summary>
274 /// Set the media flags on the texture face of the given part.
275 /// </summary>
276 /// <remarks>
277 /// The fact that we need a separate function to do what should be a simple one line operation is BUTT UGLY.
278 /// </remarks>
279 /// <param name="part"></param>
280 /// <param name="face"></param>
281 /// <param name="flag"></param>
282 protected void SetPartMediaFlags(SceneObjectPart part, int face, bool flag)
283 {
284 Primitive.TextureEntry te = part.Shape.Textures;
285 Primitive.TextureEntryFace teFace = te.CreateFace((uint)face);
286 teFace.MediaFlags = flag;
287 part.Shape.Textures = te;
288 }
289  
290 /// <summary>
291 /// Sets or gets per face media textures.
292 /// </summary>
293 /// <param name="request"></param>
294 /// <param name="path"></param>
295 /// <param name="param"></param>
296 /// <param name="httpRequest"></param>
297 /// <param name="httpResponse"></param>
298 /// <returns></returns>
299 protected string HandleObjectMediaMessage(
300 string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
301 {
302 // m_log.DebugFormat("[MOAP]: Got ObjectMedia path [{0}], raw request [{1}]", path, request);
303  
304 OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request);
305 ObjectMediaMessage omm = new ObjectMediaMessage();
306 omm.Deserialize(osd);
307  
308 if (omm.Request is ObjectMediaRequest)
309 return HandleObjectMediaRequest(omm.Request as ObjectMediaRequest);
310 else if (omm.Request is ObjectMediaUpdate)
311 return HandleObjectMediaUpdate(path, omm.Request as ObjectMediaUpdate);
312  
313 throw new Exception(
314 string.Format(
315 "[MOAP]: ObjectMediaMessage has unrecognized ObjectMediaBlock of {0}",
316 omm.Request.GetType()));
317 }
318  
319 /// <summary>
320 /// Handle a fetch request for media textures
321 /// </summary>
322 /// <param name="omr"></param>
323 /// <returns></returns>
324 protected string HandleObjectMediaRequest(ObjectMediaRequest omr)
325 {
326 UUID primId = omr.PrimID;
327  
328 SceneObjectPart part = m_scene.GetSceneObjectPart(primId);
329  
330 if (null == part)
331 {
332 m_log.WarnFormat(
333 "[MOAP]: Received a GET ObjectMediaRequest for prim {0} but this doesn't exist in region {1}",
334 primId, m_scene.RegionInfo.RegionName);
335 return string.Empty;
336 }
337  
338 if (null == part.Shape.Media)
339 return string.Empty;
340  
341 ObjectMediaResponse resp = new ObjectMediaResponse();
342  
343 resp.PrimID = primId;
344  
345 lock (part.Shape.Media)
346 resp.FaceMedia = part.Shape.Media.ToArray();
347  
348 resp.Version = part.MediaUrl;
349  
350 string rawResp = OSDParser.SerializeLLSDXmlString(resp.Serialize());
351  
352 // m_log.DebugFormat("[MOAP]: Got HandleObjectMediaRequestGet raw response is [{0}]", rawResp);
353  
354 return rawResp;
355 }
356  
357 /// <summary>
358 /// Handle an update of media textures.
359 /// </summary>
360 /// <param name="path">Path on which this request was made</param>
361 /// <param name="omu">/param>
362 /// <returns></returns>
363 protected string HandleObjectMediaUpdate(string path, ObjectMediaUpdate omu)
364 {
365 UUID primId = omu.PrimID;
366  
367 SceneObjectPart part = m_scene.GetSceneObjectPart(primId);
368  
369 if (null == part)
370 {
371 m_log.WarnFormat(
372 "[MOAP]: Received an UPDATE ObjectMediaRequest for prim {0} but this doesn't exist in region {1}",
373 primId, m_scene.RegionInfo.RegionName);
374 return string.Empty;
375 }
376  
377 // m_log.DebugFormat("[MOAP]: Received {0} media entries for prim {1}", omu.FaceMedia.Length, primId);
378 //
379 // for (int i = 0; i < omu.FaceMedia.Length; i++)
380 // {
381 // MediaEntry me = omu.FaceMedia[i];
382 // string v = (null == me ? "null": OSDParser.SerializeLLSDXmlString(me.GetOSD()));
383 // m_log.DebugFormat("[MOAP]: Face {0} [{1}]", i, v);
384 // }
385  
386 if (omu.FaceMedia.Length > part.GetNumberOfSides())
387 {
388 m_log.WarnFormat(
389 "[MOAP]: Received {0} media entries from client for prim {1} {2} but this prim has only {3} faces. Dropping request.",
390 omu.FaceMedia.Length, part.Name, part.UUID, part.GetNumberOfSides());
391 return string.Empty;
392 }
393  
394 UUID agentId = default(UUID);
395  
396 lock (m_omCapUsers)
397 agentId = m_omCapUsers[path];
398  
399 List<MediaEntry> media = part.Shape.Media;
400  
401 if (null == media)
402 {
403 // m_log.DebugFormat("[MOAP]: Setting all new media list for {0}", part.Name);
404 part.Shape.Media = new PrimitiveBaseShape.MediaList(omu.FaceMedia);
405  
406 for (int i = 0; i < omu.FaceMedia.Length; i++)
407 {
408 if (omu.FaceMedia[i] != null)
409 {
410 // FIXME: Race condition here since some other texture entry manipulator may overwrite/get
411 // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry
412 // directly.
413 SetPartMediaFlags(part, i, true);
414 // m_log.DebugFormat(
415 // "[MOAP]: Media flags for face {0} is {1}",
416 // i, part.Shape.Textures.FaceTextures[i].MediaFlags);
417 }
418 }
419 }
420 else
421 {
422 // m_log.DebugFormat("[MOAP]: Setting existing media list for {0}", part.Name);
423  
424 // We need to go through the media textures one at a time to make sure that we have permission
425 // to change them
426  
427 // FIXME: Race condition here since some other texture entry manipulator may overwrite/get
428 // overwritten. Unfortunately, PrimitiveBaseShape does not allow us to change texture entry
429 // directly.
430 Primitive.TextureEntry te = part.Shape.Textures;
431  
432 lock (media)
433 {
434 for (int i = 0; i < media.Count; i++)
435 {
436 if (m_scene.Permissions.CanControlPrimMedia(agentId, part.UUID, i))
437 {
438 media[i] = omu.FaceMedia[i];
439  
440 // When a face is cleared this is done by setting the MediaFlags in the TextureEntry via a normal
441 // texture update, so we don't need to worry about clearing MediaFlags here.
442 if (null == media[i])
443 continue;
444  
445 SetPartMediaFlags(part, i, true);
446  
447 // m_log.DebugFormat(
448 // "[MOAP]: Media flags for face {0} is {1}",
449 // i, face.MediaFlags);
450 // m_log.DebugFormat("[MOAP]: Set media entry for face {0} on {1}", i, part.Name);
451 }
452 }
453 }
454  
455 part.Shape.Textures = te;
456  
457 // for (int i2 = 0; i2 < part.Shape.Textures.FaceTextures.Length; i2++)
458 // m_log.DebugFormat("[MOAP]: FaceTexture[{0}] is {1}", i2, part.Shape.Textures.FaceTextures[i2]);
459 }
460  
461 UpdateMediaUrl(part, agentId);
462  
463 // Arguably, we could avoid sending a full update to the avatar that just changed the texture.
464 part.ScheduleFullUpdate();
465  
466 part.TriggerScriptChangedEvent(Changed.MEDIA);
467  
468 return string.Empty;
469 }
470  
471 /// <summary>
472 /// Received from the viewer if a user has changed the url of a media texture.
473 /// </summary>
474 /// <param name="request"></param>
475 /// <param name="path"></param>
476 /// <param name="param"></param>
477 /// <param name="httpRequest">/param>
478 /// <param name="httpResponse">/param>
479 /// <returns></returns>
480 protected string HandleObjectMediaNavigateMessage(
481 string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
482 {
483 // m_log.DebugFormat("[MOAP]: Got ObjectMediaNavigate request [{0}]", request);
484  
485 OSDMap osd = (OSDMap)OSDParser.DeserializeLLSDXml(request);
486 ObjectMediaNavigateMessage omn = new ObjectMediaNavigateMessage();
487 omn.Deserialize(osd);
488  
489 UUID primId = omn.PrimID;
490  
491 SceneObjectPart part = m_scene.GetSceneObjectPart(primId);
492  
493 if (null == part)
494 {
495 m_log.WarnFormat(
496 "[MOAP]: Received an ObjectMediaNavigateMessage for prim {0} but this doesn't exist in region {1}",
497 primId, m_scene.RegionInfo.RegionName);
498 return string.Empty;
499 }
500  
501 UUID agentId = default(UUID);
502  
503 lock (m_omuCapUsers)
504 agentId = m_omuCapUsers[path];
505  
506 if (!m_scene.Permissions.CanInteractWithPrimMedia(agentId, part.UUID, omn.Face))
507 return string.Empty;
508  
509 // m_log.DebugFormat(
510 // "[MOAP]: Received request to update media entry for face {0} on prim {1} {2} to {3}",
511 // omn.Face, part.Name, part.UUID, omn.URL);
512  
513 // If media has never been set for this prim, then just return.
514 if (null == part.Shape.Media)
515 return string.Empty;
516  
517 MediaEntry me = null;
518  
519 lock (part.Shape.Media)
520 me = part.Shape.Media[omn.Face];
521  
522 // Do the same if media has not been set up for a specific face
523 if (null == me)
524 return string.Empty;
525  
526 if (me.EnableWhiteList)
527 {
528 if (!CheckUrlAgainstWhitelist(omn.URL, me.WhiteList))
529 {
530 // m_log.DebugFormat(
531 // "[MOAP]: Blocking change of face {0} on prim {1} {2} to {3} since it's not on the enabled whitelist",
532 // omn.Face, part.Name, part.UUID, omn.URL);
533  
534 return string.Empty;
535 }
536 }
537  
538 me.CurrentURL = omn.URL;
539  
540 UpdateMediaUrl(part, agentId);
541  
542 part.ScheduleFullUpdate();
543  
544 part.TriggerScriptChangedEvent(Changed.MEDIA);
545  
546 return OSDParser.SerializeLLSDXmlString(new OSD());
547 }
548  
549 /// <summary>
550 /// Check that the face number is valid for the given prim.
551 /// </summary>
552 /// <param name="part"></param>
553 /// <param name="face"></param>
554 protected void CheckFaceParam(SceneObjectPart part, int face)
555 {
556 if (face < 0)
557 throw new ArgumentException("Face cannot be less than zero");
558  
559 int maxFaces = part.GetNumberOfSides() - 1;
560 if (face > maxFaces)
561 throw new ArgumentException(
562 string.Format("Face argument was {0} but max is {1}", face, maxFaces));
563 }
564  
565 /// <summary>
566 /// Update the media url of the given part
567 /// </summary>
568 /// <param name="part"></param>
569 /// <param name="updateId">
570 /// The id to attach to this update. Normally, this is the user that changed the
571 /// texture
572 /// </param>
573 protected void UpdateMediaUrl(SceneObjectPart part, UUID updateId)
574 {
575 if (null == part.MediaUrl)
576 {
577 // TODO: We can't set the last changer until we start tracking which cap we give to which agent id
578 part.MediaUrl = "x-mv:0000000000/" + updateId;
579 }
580 else
581 {
582 string rawVersion = part.MediaUrl.Substring(5, 10);
583 int version = int.Parse(rawVersion);
584 part.MediaUrl = string.Format("x-mv:{0:D10}/{1}", ++version, updateId);
585 }
586  
587 // m_log.DebugFormat("[MOAP]: Storing media url [{0}] in prim {1} {2}", part.MediaUrl, part.Name, part.UUID);
588 }
589  
590 /// <summary>
591 /// Check the given url against the given whitelist.
592 /// </summary>
593 /// <param name="rawUrl"></param>
594 /// <param name="whitelist"></param>
595 /// <returns>true if the url matches an entry on the whitelist, false otherwise</returns>
596 protected bool CheckUrlAgainstWhitelist(string rawUrl, string[] whitelist)
597 {
598 Uri url = new Uri(rawUrl);
599  
600 foreach (string origWlUrl in whitelist)
601 {
602 string wlUrl = origWlUrl;
603  
604 // Deal with a line-ending wildcard
605 if (wlUrl.EndsWith("*"))
606 wlUrl = wlUrl.Remove(wlUrl.Length - 1);
607  
608 // m_log.DebugFormat("[MOAP]: Checking whitelist URL pattern {0}", origWlUrl);
609  
610 // Handle a line starting wildcard slightly differently since this can only match the domain, not the path
611 if (wlUrl.StartsWith("*"))
612 {
613 wlUrl = wlUrl.Substring(1);
614  
615 if (url.Host.Contains(wlUrl))
616 {
617 // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl);
618 return true;
619 }
620 }
621 else
622 {
623 string urlToMatch = url.Authority + url.AbsolutePath;
624  
625 if (urlToMatch.StartsWith(wlUrl))
626 {
627 // m_log.DebugFormat("[MOAP]: Whitelist URL {0} matches {1}", origWlUrl, rawUrl);
628 return true;
629 }
630 }
631 }
632  
633 return false;
634 }
635 }
636 }