/* * Copyright (c) 2008 Intel Corporation * All rights reserved. * 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 Intel Corporation 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 COPYRIGHT HOLDERS AND CONTRIBUTORS * ``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 INTEL OR ITS * 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.IO; using System.Xml; using ExtensionLoader; using OpenMetaverse; namespace AssetServer.Extensions { public class OpenSimInventoryFrontend : IExtension { AssetServer server; Utils.InventoryItemSerializer itemSerializer = new Utils.InventoryItemSerializer(); Utils.InventoryFolderSerializer folderSerializer = new Utils.InventoryFolderSerializer(); Utils.InventoryCollectionSerializer collectionSerializer = new Utils.InventoryCollectionSerializer(); public OpenSimInventoryFrontend() { } public void Start(AssetServer server) { this.server = server; server.HttpServer.AddHandler("post", null, @"^/GetInventory/", GetInventoryHandler); server.HttpServer.AddHandler("post", null, @"^/CreateInventory/", CreateInventoryHandler); server.HttpServer.AddHandler("post", null, @"^/NewFolder/", NewFolderHandler); server.HttpServer.AddHandler("post", null, @"^/UpdateFolder/", UpdateFolderHandler); server.HttpServer.AddHandler("post", null, @"^/MoveFolder/", MoveFolderHandler); server.HttpServer.AddHandler("post", null, @"^/PurgeFolder/", PurgeFolderHandler); server.HttpServer.AddHandler("post", null, @"^/NewItem/", NewItemHandler); server.HttpServer.AddHandler("post", null, @"^/DeleteItem/", DeleteItemHandler); server.HttpServer.AddHandler("post", null, @"^/RootFolders/", RootFoldersHandler); server.HttpServer.AddHandler("post", null, @"^/ActiveGestures/", ActiveGesturesHandler); } public void Stop() { } void GetInventoryHandler(ref HttpListenerContext context) { UUID sessionID, agentID; UUID inventoryID = DeserializeUUID(context.Request.InputStream, out agentID, out sessionID); if (inventoryID != UUID.Zero) { Logger.Log.Warn("GetInventory is not scalable on some inventory backends, avoid calling it wherever possible"); InventoryCollection inventory; StorageResponse response = server.InventoryProvider.TryFetchInventory(inventoryID, sessionID, out inventory); if (response == StorageResponse.Success) { context.Response.StatusCode = (int)HttpStatusCode.OK; collectionSerializer.Serialize(context.Response.OutputStream, inventory); context.Response.OutputStream.Flush(); } else if (response == StorageResponse.AuthNeeded) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; } else if (response == StorageResponse.NotFound) { // Return an empty inventory set to mimic OpenSim.Grid.InventoryServer.exe context.Response.StatusCode = (int)HttpStatusCode.OK; inventory = new InventoryCollection(); inventory.UserID = inventoryID; inventory.Folders = new Dictionary(); inventory.Items = new Dictionary(); collectionSerializer.Serialize(context.Response.OutputStream, inventory); context.Response.OutputStream.Flush(); } else { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } void CreateInventoryHandler(ref HttpListenerContext context) { UUID ownerID = DeserializeUUID(context.Request.InputStream); if (ownerID != UUID.Zero) { // HACK: OpenSim doesn't supply us with any credentials when creating a new inventory. // Huge gaping security hole UUID sessionID = UUID.Zero; InventoryFolder rootFolder = new InventoryFolder("My Inventory", ownerID, UUID.Zero, (short)AssetType.Folder); StorageResponse response = server.InventoryProvider.TryCreateInventory(rootFolder, sessionID); if (response == StorageResponse.Success) { CreateFolder("Animations", ownerID, rootFolder.ID, AssetType.Animation, sessionID); CreateFolder("Body Parts", ownerID, rootFolder.ID, AssetType.Bodypart, sessionID); CreateFolder("Calling Cards", ownerID, rootFolder.ID, AssetType.CallingCard, sessionID); CreateFolder("Clothing", ownerID, rootFolder.ID, AssetType.Clothing, sessionID); CreateFolder("Gestures", ownerID, rootFolder.ID, AssetType.Gesture, sessionID); CreateFolder("Landmarks", ownerID, rootFolder.ID, AssetType.Landmark, sessionID); CreateFolder("Lost and Found", ownerID, rootFolder.ID, AssetType.LostAndFoundFolder, sessionID); CreateFolder("Notecards", ownerID, rootFolder.ID, AssetType.Notecard, sessionID); CreateFolder("Objects", ownerID, rootFolder.ID, AssetType.Object, sessionID); CreateFolder("Photo Album", ownerID, rootFolder.ID, AssetType.SnapshotFolder, sessionID); CreateFolder("Scripts", ownerID, rootFolder.ID, AssetType.LSLText, sessionID); CreateFolder("Sounds", ownerID, rootFolder.ID, AssetType.Sound, sessionID); CreateFolder("Textures", ownerID, rootFolder.ID, AssetType.Texture, sessionID); CreateFolder("Trash", ownerID, rootFolder.ID, AssetType.TrashFolder, sessionID); context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, true); return; } } context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, false); } void NewFolderHandler(ref HttpListenerContext context) { UUID agentID, sessionID; InventoryFolder folder = DeserializeFolder(context.Request.InputStream, out agentID, out sessionID); if (folder != null) { // Some calls that are moving or updating a folder instead of creating a new one // will pass in an InventoryFolder without the name set. If this is the case we // need to look up the name first if (String.IsNullOrEmpty(folder.Name)) { InventoryFolder oldFolder; if (server.InventoryProvider.TryFetchFolder(folder.Owner, folder.ID, sessionID, out oldFolder) == StorageResponse.Success) folder.Name = oldFolder.Name; } StorageResponse response = server.InventoryProvider.TryCreateFolder(folder, sessionID); if (response == StorageResponse.Success) { context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, true); return; } } context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, false); } void UpdateFolderHandler(ref HttpListenerContext context) { NewFolderHandler(ref context); } void MoveFolderHandler(ref HttpListenerContext context) { NewFolderHandler(ref context); } void PurgeFolderHandler(ref HttpListenerContext context) { UUID agentID, sessionID; InventoryFolder folder = DeserializeFolder(context.Request.InputStream, out agentID, out sessionID); if (folder != null) { StorageResponse response = server.InventoryProvider.TryPurgeFolder(folder.Owner, folder.ID, sessionID); if (response == StorageResponse.Success) { context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, true); return; } } context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, false); } void NewItemHandler(ref HttpListenerContext context) { UUID agentID, sessionID; InventoryItem item = DeserializeItem(context.Request.InputStream, out agentID, out sessionID); if (item != null) { StorageResponse response = server.InventoryProvider.TryCreateItem(item, sessionID); if (response == StorageResponse.Success) { context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, true); return; } } context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, false); } void DeleteItemHandler(ref HttpListenerContext context) { UUID agentID, sessionID; InventoryItem item = DeserializeItem(context.Request.InputStream, out agentID, out sessionID); if (item != null) { StorageResponse response = server.InventoryProvider.TryDeleteItem(item.Owner, item.ID, sessionID); if (response == StorageResponse.Success) { context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, true); return; } } context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeBool(context.Response.OutputStream, false); } void RootFoldersHandler(ref HttpListenerContext context) { UUID ownerID = DeserializeUUID(context.Request.InputStream); if (ownerID != UUID.Zero) { // HACK: OpenSim doesn't supply us with any credentials when fetching agent folder lists. // Huge gaping security hole UUID sessionID = UUID.Zero; List skeleton; StorageResponse response = server.InventoryProvider.TryFetchFolderList(ownerID, sessionID, out skeleton); if (response == StorageResponse.Success) { context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeFolderList(context.Response.OutputStream, skeleton); } else if (response == StorageResponse.AuthNeeded) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; } else if (response == StorageResponse.NotFound) { // Return an empty set of inventory so the requester knows that // an inventory needs to be created for this agent context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeFolderList(context.Response.OutputStream, new List(0)); } else { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } void ActiveGesturesHandler(ref HttpListenerContext context) { UUID inventoryID = DeserializeUUID(context.Request.InputStream); if (inventoryID != UUID.Zero) { // HACK: OpenSim doesn't supply us with any credentials when fetching agent folder lists. // Huge gaping security hole UUID sessionID = UUID.Zero; List gestures; StorageResponse response = server.InventoryProvider.TryFetchActiveGestures(inventoryID, sessionID, out gestures); if (response == StorageResponse.Success) { context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeItemList(context.Response.OutputStream, gestures); } else if (response == StorageResponse.AuthNeeded) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; } else if (response == StorageResponse.NotFound) { // Return an empty set of gestures to match OpenSim.Grid.InventoryServer.exe behavior context.Response.StatusCode = (int)HttpStatusCode.OK; SerializeItemList(context.Response.OutputStream, new List(0)); } else { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } StorageResponse CreateFolder(string name, UUID ownerID, UUID parentID, AssetType assetType, UUID sessionID) { InventoryFolder folder = new InventoryFolder(name, ownerID, parentID, (short)assetType); return server.InventoryProvider.TryCreateFolder(folder, sessionID); } UUID DeserializeUUID(Stream stream) { UUID id = UUID.Zero; try { using (XmlReader reader = XmlReader.Create(stream)) { reader.MoveToContent(); UUID.TryParse(reader.ReadElementContentAsString("guid", String.Empty), out id); } } catch (Exception ex) { Logger.Log.Warn("Failed to parse POST data (expecting guid): " + ex.Message); } return id; } UUID DeserializeUUID(Stream stream, out UUID agentID, out UUID sessionID) { UUID id; try { using (XmlReader reader = XmlReader.Create(stream)) { reader.MoveToContent(); reader.ReadStartElement("RestSessionObjectOfGuid"); UUID.TryParse(reader.ReadElementContentAsString("SessionID", String.Empty), out sessionID); UUID.TryParse(reader.ReadElementContentAsString("AvatarID", String.Empty), out agentID); UUID.TryParse(reader.ReadElementContentAsString("Body", String.Empty), out id); reader.ReadEndElement(); } } catch (Exception ex) { Logger.Log.Warn("Failed to parse GetInventory POST data: " + ex.Message); agentID = UUID.Zero; sessionID = UUID.Zero; return UUID.Zero; } return id; } InventoryFolder DeserializeFolder(Stream stream, out UUID agentID, out UUID sessionID) { InventoryFolder folder = new InventoryFolder(); try { using (XmlReader reader = XmlReader.Create(stream)) { reader.MoveToContent(); reader.ReadStartElement("RestSessionObjectOfInventoryFolderBase"); UUID.TryParse(reader.ReadElementContentAsString("SessionID", String.Empty), out sessionID); UUID.TryParse(reader.ReadElementContentAsString("AvatarID", String.Empty), out agentID); reader.ReadStartElement("Body"); if (reader.Name == "Name") folder.Name = reader.ReadElementContentAsString("Name", String.Empty); else folder.Name = String.Empty; ReadUUID(reader, "Owner", out folder.Owner); ReadUUID(reader, "ParentID", out folder.ParentID); ReadUUID(reader, "ID", out folder.ID); Int16.TryParse(reader.ReadElementContentAsString("Type", String.Empty), out folder.Type); UInt16.TryParse(reader.ReadElementContentAsString("Version", String.Empty), out folder.Version); reader.ReadEndElement(); reader.ReadEndElement(); } } catch (Exception ex) { Logger.Log.Warn("Failed to parse POST data (expecting InventoryFolderBase): " + ex.Message); agentID = UUID.Zero; sessionID = UUID.Zero; return null; } return folder; } InventoryItem DeserializeItem(Stream stream, out UUID agentID, out UUID sessionID) { InventoryItem item = new InventoryItem(); try { using (XmlReader reader = XmlReader.Create(stream)) { reader.MoveToContent(); reader.ReadStartElement("RestSessionObjectOfInventoryItemBase"); UUID.TryParse(reader.ReadElementContentAsString("SessionID", String.Empty), out sessionID); UUID.TryParse(reader.ReadElementContentAsString("AvatarID", String.Empty), out agentID); reader.ReadStartElement("Body"); ReadUUID(reader, "ID", out item.ID); Int32.TryParse(reader.ReadElementContentAsString("InvType", String.Empty), out item.InvType); ReadUUID(reader, "Folder", out item.Folder); ReadUUID(reader, "Owner", out item.Owner); ReadUUID(reader, "Creator", out item.Creator); item.Name = reader.ReadElementContentAsString("Name", String.Empty); item.Description = reader.ReadElementContentAsString("Description", String.Empty); UInt32.TryParse(reader.ReadElementContentAsString("NextPermissions", String.Empty), out item.NextPermissions); UInt32.TryParse(reader.ReadElementContentAsString("CurrentPermissions", String.Empty), out item.CurrentPermissions); UInt32.TryParse(reader.ReadElementContentAsString("BasePermissions", String.Empty), out item.BasePermissions); UInt32.TryParse(reader.ReadElementContentAsString("EveryOnePermissions", String.Empty), out item.EveryOnePermissions); UInt32.TryParse(reader.ReadElementContentAsString("GroupPermissions", String.Empty), out item.GroupPermissions); Int32.TryParse(reader.ReadElementContentAsString("AssetType", String.Empty), out item.AssetType); ReadUUID(reader, "AssetID", out item.AssetID); ReadUUID(reader, "GroupID", out item.GroupID); Boolean.TryParse(reader.ReadElementContentAsString("GroupOwned", String.Empty), out item.GroupOwned); Int32.TryParse(reader.ReadElementContentAsString("SalePrice", String.Empty), out item.SalePrice); Byte.TryParse(reader.ReadElementContentAsString("SaleType", String.Empty), out item.SaleType); UInt32.TryParse(reader.ReadElementContentAsString("Flags", String.Empty), out item.Flags); Int32.TryParse(reader.ReadElementContentAsString("CreationDate", String.Empty), out item.CreationDate); reader.ReadEndElement(); reader.ReadEndElement(); } } catch (Exception ex) { Logger.Log.Warn("Failed to parse POST data (expecting InventoryItemBase): " + ex.Message); agentID = UUID.Zero; sessionID = UUID.Zero; return null; } return item; } void SerializeBool(Stream stream, bool value) { using (XmlWriter writer = XmlWriter.Create(stream)) { writer.WriteStartDocument(); writer.WriteStartElement("boolean"); writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema"); writer.WriteString(value.ToString().ToLower()); writer.WriteEndElement(); writer.WriteEndDocument(); writer.Flush(); } stream.Flush(); stream.Close(); } void SerializeFolderList(Stream stream, List folders) { using (XmlWriter writer = XmlWriter.Create(stream)) { writer.WriteStartDocument(); writer.WriteStartElement("ArrayOfInventoryFolderBase"); writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema"); foreach (InventoryFolder folder in folders) { writer.WriteStartElement("InventoryFolderBase"); writer.WriteElementString("Name", folder.Name); WriteUUID(writer, "Owner", folder.Owner); WriteUUID(writer, "ParentID", folder.ParentID); WriteUUID(writer, "ID", folder.ID); writer.WriteElementString("Type", XmlConvert.ToString(folder.Type)); writer.WriteElementString("Version", XmlConvert.ToString(folder.Version)); writer.WriteEndElement(); } writer.WriteEndElement(); writer.WriteEndDocument(); writer.Flush(); } stream.Flush(); stream.Close(); } void SerializeItemList(Stream stream, List items) { using (XmlWriter writer = XmlWriter.Create(stream)) { writer.WriteStartDocument(); writer.WriteStartElement("ArrayOfInventoryItemBase"); writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema"); foreach (InventoryItem item in items) { writer.WriteStartElement("InventoryItemBase"); WriteUUID(writer, "ID", item.ID); writer.WriteElementString("InvType", XmlConvert.ToString(item.InvType)); WriteUUID(writer, "Folder", item.Folder); WriteUUID(writer, "Owner", item.Owner); WriteUUID(writer, "Creator", item.Creator); writer.WriteElementString("Name", item.Name); writer.WriteElementString("Description", item.Description); writer.WriteElementString("NextPermissions", XmlConvert.ToString(item.NextPermissions)); writer.WriteElementString("CurrentPermissions", XmlConvert.ToString(item.CurrentPermissions)); writer.WriteElementString("BasePermissions", XmlConvert.ToString(item.BasePermissions)); writer.WriteElementString("EveryOnePermissions", XmlConvert.ToString(item.EveryOnePermissions)); writer.WriteElementString("GroupPermissions", XmlConvert.ToString(item.GroupPermissions)); writer.WriteElementString("AssetType", XmlConvert.ToString(item.AssetType)); WriteUUID(writer, "AssetID", item.AssetID); WriteUUID(writer, "GroupID", item.GroupID); writer.WriteElementString("GroupOwned", XmlConvert.ToString(item.GroupOwned)); writer.WriteElementString("SalePrice", XmlConvert.ToString(item.SalePrice)); writer.WriteElementString("SaleType", XmlConvert.ToString(item.SaleType)); writer.WriteElementString("Flags", XmlConvert.ToString(item.Flags)); writer.WriteElementString("CreationDate", XmlConvert.ToString(item.CreationDate)); writer.WriteEndElement(); } writer.WriteEndElement(); writer.WriteEndDocument(); writer.Flush(); } stream.Flush(); stream.Close(); } void WriteUUID(XmlWriter writer, string name, UUID id) { writer.WriteStartElement(name); writer.WriteElementString("Guid", XmlConvert.ToString(id.Guid)); writer.WriteEndElement(); } void ReadUUID(XmlReader reader, string name, out UUID id) { reader.ReadStartElement(name); UUID.TryParse(reader.ReadElementContentAsString("Guid", String.Empty), out id); reader.ReadEndElement(); } } #region OpenSim AssetType /// /// The different types of grid assets /// public enum AssetType : sbyte { /// Unknown asset type Unknown = -1, /// Texture asset, stores in JPEG2000 J2C stream format Texture = 0, /// Sound asset Sound = 1, /// Calling card for another avatar CallingCard = 2, /// Link to a location in world Landmark = 3, // Legacy script asset, you should never see one of these //[Obsolete] //Script = 4, /// Collection of textures and parameters that can be /// worn by an avatar Clothing = 5, /// Primitive that can contain textures, sounds, /// scripts and more Object = 6, /// Notecard asset Notecard = 7, /// Holds a collection of inventory items Folder = 8, /// Root inventory folder RootFolder = 9, /// Linden scripting language script LSLText = 10, /// LSO bytecode for a script LSLBytecode = 11, /// Uncompressed TGA texture TextureTGA = 12, /// Collection of textures and shape parameters that can /// be worn Bodypart = 13, /// Trash folder TrashFolder = 14, /// Snapshot folder SnapshotFolder = 15, /// Lost and found folder LostAndFoundFolder = 16, /// Uncompressed sound SoundWAV = 17, /// Uncompressed TGA non-square image, not to be used as a /// texture ImageTGA = 18, /// Compressed JPEG non-square image, not to be used as a /// texture ImageJPEG = 19, /// Animation Animation = 20, /// Sequence of animations, sounds, chat, and pauses Gesture = 21, /// Simstate file Simstate = 22, } #endregion OpenSim AssetType }