/* * 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.IO; using System.Reflection; using log4net; using Nini.Config; using OpenMetaverse; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Services.Interfaces; // using OpenSim.Region.Framework.Interfaces; namespace OpenSim.Framework.Capabilities { public delegate void UpLoadedAsset( string assetName, string description, UUID assetID, UUID inventoryItem, UUID parentFolder, byte[] data, string inventoryType, string assetType); public delegate void UploadedBakedTexture(UUID assetID, byte[] data); public delegate UUID UpdateItem(UUID itemID, byte[] data); public delegate void UpdateTaskScript(UUID itemID, UUID primID, bool isScriptRunning, byte[] data, ref ArrayList errors); public delegate void NewInventoryItem(UUID userID, InventoryItemBase item); public delegate void NewAsset(AssetBase asset); public delegate UUID ItemUpdatedCallback(UUID userID, UUID itemID, byte[] data); public delegate ArrayList TaskScriptUpdatedCallback(UUID userID, UUID itemID, UUID primID, bool isScriptRunning, byte[] data); public delegate InventoryCollection FetchInventoryDescendentsCAPS(UUID agentID, UUID folderID, UUID ownerID, bool fetchFolders, bool fetchItems, int sortOrder, out int version); /// /// XXX Probably not a particularly nice way of allow us to get the scene presence from the scene (chiefly so that /// we can popup a message on the user's client if the inventory service has permanently failed). But I didn't want /// to just pass the whole Scene into CAPS. /// public delegate IClientAPI GetClientDelegate(UUID agentID); public class Caps { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private string m_httpListenerHostName; private uint m_httpListenPort; /// /// This is the uuid portion of every CAPS path. It is used to make capability urls private to the requester. /// private string m_capsObjectPath; public string CapsObjectPath { get { return m_capsObjectPath; } } private CapsHandlers m_capsHandlers; private static readonly string m_requestPath = "0000/"; // private static readonly string m_mapLayerPath = "0001/"; private static readonly string m_newInventory = "0002/"; //private static readonly string m_requestTexture = "0003/"; private static readonly string m_notecardUpdatePath = "0004/"; private static readonly string m_notecardTaskUpdatePath = "0005/"; // private static readonly string m_fetchInventoryPath = "0006/"; // The following entries are in a module, however, they are also here so that we don't re-assign // the path to another cap by mistake. // private static readonly string m_parcelVoiceInfoRequestPath = "0007/"; // This is in a module. // private static readonly string m_provisionVoiceAccountRequestPath = "0008/";// This is in a module. // private static readonly string m_remoteParcelRequestPath = "0009/";// This is in the LandManagementModule. private static readonly string m_uploadBakedTexturePath = "0010/";// This is in the LandManagementModule. //private string eventQueue = "0100/"; private IScene m_Scene; private IHttpServer m_httpListener; private UUID m_agentID; private IAssetService m_assetCache; private int m_eventQueueCount = 1; private Queue m_capsEventQueue = new Queue(); private bool m_dumpAssetsToFile; private string m_regionName; private object m_fetchLock = new Object(); private bool m_persistBakedTextures = false; public bool SSLCaps { get { return m_httpListener.UseSSL; } } public string SSLCommonName { get { return m_httpListener.SSLCommonName; } } public CapsHandlers CapsHandlers { get { return m_capsHandlers; } } // These are callbacks which will be setup by the scene so that we can update scene data when we // receive capability calls public NewInventoryItem AddNewInventoryItem = null; public NewAsset AddNewAsset = null; public ItemUpdatedCallback ItemUpdatedCall = null; public TaskScriptUpdatedCallback TaskScriptUpdatedCall = null; public FetchInventoryDescendentsCAPS CAPSFetchInventoryDescendents = null; public GetClientDelegate GetClient = null; public Caps(IScene scene, IAssetService assetCache, IHttpServer httpServer, string httpListen, uint httpPort, string capsPath, UUID agent, bool dumpAssetsToFile, string regionName) { m_Scene = scene; m_assetCache = assetCache; m_capsObjectPath = capsPath; m_httpListener = httpServer; m_httpListenerHostName = httpListen; m_httpListenPort = httpPort; m_persistBakedTextures = false; IConfigSource config = m_Scene.Config; if (config != null) { IConfig sconfig = config.Configs["Startup"]; if (sconfig != null) m_persistBakedTextures = sconfig.GetBoolean("PersistBakedTextures",m_persistBakedTextures); } if (httpServer != null && httpServer.UseSSL) { m_httpListenPort = httpServer.SSLPort; httpListen = httpServer.SSLCommonName; httpPort = httpServer.SSLPort; } m_agentID = agent; m_dumpAssetsToFile = dumpAssetsToFile; m_capsHandlers = new CapsHandlers(httpServer, httpListen, httpPort, (httpServer == null) ? false : httpServer.UseSSL); m_regionName = regionName; } /// /// Register all CAPS http service handlers /// public void RegisterHandlers() { DeregisterHandlers(); string capsBase = "/CAPS/" + m_capsObjectPath; RegisterRegionServiceHandlers(capsBase); RegisterInventoryServiceHandlers(capsBase); } public void RegisterRegionServiceHandlers(string capsBase) { try { // the root of all evil m_capsHandlers["SEED"] = new RestStreamHandler("POST", capsBase + m_requestPath, CapsRequest); m_log.DebugFormat( "[CAPS]: Registered seed capability {0} for {1}", capsBase + m_requestPath, m_agentID); //m_capsHandlers["MapLayer"] = // new LLSDStreamhandler("POST", // capsBase + m_mapLayerPath, // GetMapLayer); m_capsHandlers["UpdateScriptTaskInventory"] = new RestStreamHandler("POST", capsBase + m_notecardTaskUpdatePath, ScriptTaskInventory); m_capsHandlers["UpdateScriptTask"] = m_capsHandlers["UpdateScriptTaskInventory"]; m_capsHandlers["UploadBakedTexture"] = new RestStreamHandler("POST", capsBase + m_uploadBakedTexturePath, UploadBakedTexture); } catch (Exception e) { m_log.Error("[CAPS]: " + e.ToString()); } } public void RegisterInventoryServiceHandlers(string capsBase) { try { // I don't think this one works... m_capsHandlers["NewFileAgentInventory"] = new LLSDStreamhandler("POST", capsBase + m_newInventory, NewAgentInventoryRequest); m_capsHandlers["UpdateNotecardAgentInventory"] = new RestStreamHandler("POST", capsBase + m_notecardUpdatePath, NoteCardAgentInventory); m_capsHandlers["UpdateScriptAgentInventory"] = m_capsHandlers["UpdateNotecardAgentInventory"]; m_capsHandlers["UpdateScriptAgent"] = m_capsHandlers["UpdateScriptAgentInventory"]; // As of RC 1.22.9 of the Linden client this is // supported //m_capsHandlers["WebFetchInventoryDescendents"] =new RestStreamHandler("POST", capsBase + m_fetchInventoryPath, FetchInventoryDescendentsRequest); // justincc: I've disabled the CAPS service for now to fix problems with selecting textures, and // subsequent inventory breakage, in the edit object pane (such as mantis 1085). This requires // enhancements (probably filling out the folder part of the LLSD reply) to our CAPS service, // but when I went on the Linden grid, the // simulators I visited (version 1.21) were, surprisingly, no longer supplying this capability. Instead, // the 1.19.1.4 client appeared to be happily flowing inventory data over UDP // // This is very probably just a temporary measure - once the CAPS service appears again on the Linden grid // we will be // able to get the data we need to implement the necessary part of the protocol to fix the issue above. // m_capsHandlers["FetchInventoryDescendents"] = // new RestStreamHandler("POST", capsBase + m_fetchInventoryPath, FetchInventoryRequest); // m_capsHandlers["FetchInventoryDescendents"] = // new LLSDStreamhandler("POST", // capsBase + m_fetchInventory, // FetchInventory)); // m_capsHandlers["RequestTextureDownload"] = new RestStreamHandler("POST", // capsBase + m_requestTexture, // RequestTexture); } catch (Exception e) { m_log.Error("[CAPS]: " + e.ToString()); } } /// /// Register a handler. This allows modules to register handlers. /// /// /// public void RegisterHandler(string capName, IRequestHandler handler) { m_capsHandlers[capName] = handler; //m_log.DebugFormat("[CAPS]: Registering handler for \"{0}\": path {1}", capName, handler.Path); } /// /// Remove all CAPS service handlers. /// /// /// /// /// public void DeregisterHandlers() { if (m_capsHandlers != null) { foreach (string capsName in m_capsHandlers.Caps) { m_capsHandlers.Remove(capsName); } } } /// /// Construct a client response detailing all the capabilities this server can provide. /// /// /// /// /// HTTP request header object /// HTTP response header object /// public string CapsRequest(string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { m_log.Debug("[CAPS]: Seed Caps Request in region: " + m_regionName); if (!m_Scene.CheckClient(m_agentID, httpRequest.RemoteIPEndPoint)) { m_log.DebugFormat("[CAPS]: Unauthorized CAPS client"); return string.Empty; } string result = LLSDHelpers.SerialiseLLSDReply(m_capsHandlers.CapsDetails); //m_log.DebugFormat("[CAPS] CapsRequest {0}", result); return result; } // FIXME: these all should probably go into the respective region // modules /// /// Processes a fetch inventory request and sends the reply /// /// /// /// /// // Request is like: // // folders // // // fetch-folders1fetch-items1folder-id8e1e3a30-b9bf-11dc-95ff-0800200c9a66owner-id11111111-1111-0000-0000-000100bba000sort-order1 // // // // // // multiple fetch-folder maps are allowed within the larger folders map. public string FetchInventoryRequest(string request, string path, string param) { // string unmodifiedRequest = request.ToString(); //m_log.DebugFormat("[AGENT INVENTORY]: Received CAPS fetch inventory request {0}", unmodifiedRequest); m_log.Debug("[CAPS]: Inventory Request in region: " + m_regionName); Hashtable hash = new Hashtable(); try { hash = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); } catch (LLSD.LLSDParseException pe) { m_log.Error("[AGENT INVENTORY]: Fetch error: " + pe.Message); m_log.Error("Request: " + request.ToString()); } ArrayList foldersrequested = (ArrayList)hash["folders"]; string response = ""; for (int i = 0; i < foldersrequested.Count; i++) { string inventoryitemstr = ""; Hashtable inventoryhash = (Hashtable)foldersrequested[i]; LLSDFetchInventoryDescendents llsdRequest = new LLSDFetchInventoryDescendents(); LLSDHelpers.DeserialiseOSDMap(inventoryhash, llsdRequest); LLSDInventoryDescendents reply = FetchInventoryReply(llsdRequest); inventoryitemstr = LLSDHelpers.SerialiseLLSDReply(reply); inventoryitemstr = inventoryitemstr.Replace("folders", ""); inventoryitemstr = inventoryitemstr.Replace("", ""); response += inventoryitemstr; } if (response.Length == 0) { // Ter-guess: If requests fail a lot, the client seems to stop requesting descendants. // Therefore, I'm concluding that the client only has so many threads available to do requests // and when a thread stalls.. is stays stalled. // Therefore we need to return something valid response = "folders"; } else { response = "folders" + response + ""; } //m_log.DebugFormat("[AGENT INVENTORY]: Replying to CAPS fetch inventory request with following xml"); //m_log.Debug(Util.GetFormattedXml(response)); return response; } public string FetchInventoryDescendentsRequest(string request, string path, string param,OSHttpRequest httpRequest, OSHttpResponse httpResponse) { // nasty temporary hack here, the linden client falsely // identifies the uuid 00000000-0000-0000-0000-000000000000 // as a string which breaks us // // correctly mark it as a uuid // request = request.Replace("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000"); // another hack 1 results in a // System.ArgumentException: Object type System.Int32 cannot // be converted to target type: System.Boolean // request = request.Replace("fetch_folders0", "fetch_folders0"); request = request.Replace("fetch_folders1", "fetch_folders1"); Hashtable hash = new Hashtable(); try { hash = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); } catch (LLSD.LLSDParseException pe) { m_log.Error("[AGENT INVENTORY]: Fetch error: " + pe.Message); m_log.Error("Request: " + request.ToString()); } ArrayList foldersrequested = (ArrayList)hash["folders"]; string response = ""; lock (m_fetchLock) { for (int i = 0; i < foldersrequested.Count; i++) { string inventoryitemstr = ""; Hashtable inventoryhash = (Hashtable)foldersrequested[i]; LLSDFetchInventoryDescendents llsdRequest = new LLSDFetchInventoryDescendents(); try{ LLSDHelpers.DeserialiseOSDMap(inventoryhash, llsdRequest); } catch(Exception e) { m_log.Debug("[CAPS]: caught exception doing OSD deserialize" + e); } LLSDInventoryDescendents reply = FetchInventoryReply(llsdRequest); inventoryitemstr = LLSDHelpers.SerialiseLLSDReply(reply); inventoryitemstr = inventoryitemstr.Replace("folders", ""); inventoryitemstr = inventoryitemstr.Replace("", ""); response += inventoryitemstr; } if (response.Length == 0) { // Ter-guess: If requests fail a lot, the client seems to stop requesting descendants. // Therefore, I'm concluding that the client only has so many threads available to do requests // and when a thread stalls.. is stays stalled. // Therefore we need to return something valid response = "folders"; } else { response = "folders" + response + ""; } //m_log.DebugFormat("[CAPS]: Replying to CAPS fetch inventory request with following xml"); //m_log.Debug("[CAPS] "+response); } return response; } /// /// Construct an LLSD reply packet to a CAPS inventory request /// /// /// private LLSDInventoryDescendents FetchInventoryReply(LLSDFetchInventoryDescendents invFetch) { LLSDInventoryDescendents reply = new LLSDInventoryDescendents(); LLSDInventoryFolderContents contents = new LLSDInventoryFolderContents(); contents.agent_id = m_agentID; contents.owner_id = invFetch.owner_id; contents.folder_id = invFetch.folder_id; reply.folders.Array.Add(contents); InventoryCollection inv = new InventoryCollection(); inv.Folders = new List(); inv.Items = new List(); int version = 0; if (CAPSFetchInventoryDescendents != null) { inv = CAPSFetchInventoryDescendents(m_agentID, invFetch.folder_id, invFetch.owner_id, invFetch.fetch_folders, invFetch.fetch_items, invFetch.sort_order, out version); } if (inv.Folders != null) { foreach (InventoryFolderBase invFolder in inv.Folders) { contents.categories.Array.Add(ConvertInventoryFolder(invFolder)); } } if (inv.Items != null) { foreach (InventoryItemBase invItem in inv.Items) { contents.items.Array.Add(ConvertInventoryItem(invItem)); } } contents.descendents = contents.items.Array.Count + contents.categories.Array.Count; contents.version = version; return reply; } /// /// Convert an internal inventory folder object into an LLSD object. /// /// /// private LLSDInventoryFolder ConvertInventoryFolder(InventoryFolderBase invFolder) { LLSDInventoryFolder llsdFolder = new LLSDInventoryFolder(); llsdFolder.folder_id = invFolder.ID; llsdFolder.parent_id = invFolder.ParentID; llsdFolder.name = invFolder.Name; if (invFolder.Type < 0 || invFolder.Type >= TaskInventoryItem.Types.Length) llsdFolder.type = "-1"; else llsdFolder.type = TaskInventoryItem.Types[invFolder.Type]; llsdFolder.preferred_type = "-1"; return llsdFolder; } /// /// Convert an internal inventory item object into an LLSD object. /// /// /// private LLSDInventoryItem ConvertInventoryItem(InventoryItemBase invItem) { LLSDInventoryItem llsdItem = new LLSDInventoryItem(); llsdItem.asset_id = invItem.AssetID; llsdItem.created_at = invItem.CreationDate; llsdItem.desc = invItem.Description; llsdItem.flags = (int)invItem.Flags; llsdItem.item_id = invItem.ID; llsdItem.name = invItem.Name; llsdItem.parent_id = invItem.Folder; try { // TODO reevaluate after upgrade to libomv >= r2566. Probably should use UtilsConversions. llsdItem.type = TaskInventoryItem.Types[invItem.AssetType]; llsdItem.inv_type = TaskInventoryItem.InvTypes[invItem.InvType]; } catch (Exception e) { m_log.Error("[CAPS]: Problem setting asset/inventory type while converting inventory item " + invItem.Name + " to LLSD:", e); } llsdItem.permissions = new LLSDPermissions(); llsdItem.permissions.creator_id = invItem.CreatorIdAsUuid; llsdItem.permissions.base_mask = (int)invItem.CurrentPermissions; llsdItem.permissions.everyone_mask = (int)invItem.EveryOnePermissions; llsdItem.permissions.group_id = invItem.GroupID; llsdItem.permissions.group_mask = (int)invItem.GroupPermissions; llsdItem.permissions.is_owner_group = invItem.GroupOwned; llsdItem.permissions.next_owner_mask = (int)invItem.NextPermissions; llsdItem.permissions.owner_id = m_agentID; llsdItem.permissions.owner_mask = (int)invItem.CurrentPermissions; llsdItem.sale_info = new LLSDSaleInfo(); llsdItem.sale_info.sale_price = invItem.SalePrice; switch (invItem.SaleType) { default: llsdItem.sale_info.sale_type = "not"; break; case 1: llsdItem.sale_info.sale_type = "original"; break; case 2: llsdItem.sale_info.sale_type = "copy"; break; case 3: llsdItem.sale_info.sale_type = "contents"; break; } return llsdItem; } /// /// /// /// /// public LLSDMapLayerResponse GetMapLayer(LLSDMapRequest mapReq) { m_log.Debug("[CAPS]: MapLayer Request in region: " + m_regionName); LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse(); mapResponse.LayerData.Array.Add(GetOSDMapLayerResponse()); return mapResponse; } /// /// /// /// protected static OSDMapLayer GetOSDMapLayerResponse() { OSDMapLayer mapLayer = new OSDMapLayer(); mapLayer.Right = 5000; mapLayer.Top = 5000; mapLayer.ImageID = new UUID("00000000-0000-1111-9999-000000000006"); return mapLayer; } /// /// /// /// /// /// /// public string RequestTexture(string request, string path, string param) { m_log.Debug("texture request " + request); // Needs implementing (added to remove compiler warning) return String.Empty; } #region EventQueue (Currently not enabled) /// /// /// /// /// /// /// public string ProcessEventQueue(string request, string path, string param) { string res = String.Empty; if (m_capsEventQueue.Count > 0) { lock (m_capsEventQueue) { string item = m_capsEventQueue.Dequeue(); res = item; } } else { res = CreateEmptyEventResponse(); } return res; } /// /// /// /// /// /// public string CreateEstablishAgentComms(string caps, string ipAddressPort) { LLSDCapEvent eventItem = new LLSDCapEvent(); eventItem.id = m_eventQueueCount; //should be creating a EstablishAgentComms item, but there isn't a class for it yet eventItem.events.Array.Add(new LLSDEmpty()); string res = LLSDHelpers.SerialiseLLSDReply(eventItem); m_eventQueueCount++; m_capsEventQueue.Enqueue(res); return res; } /// /// /// /// public string CreateEmptyEventResponse() { LLSDCapEvent eventItem = new LLSDCapEvent(); eventItem.id = m_eventQueueCount; eventItem.events.Array.Add(new LLSDEmpty()); string res = LLSDHelpers.SerialiseLLSDReply(eventItem); m_eventQueueCount++; return res; } #endregion /// /// Called by the script task update handler. Provides a URL to which the client can upload a new asset. /// /// /// /// /// HTTP request header object /// HTTP response header object /// public string ScriptTaskInventory(string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { try { m_log.Debug("[CAPS]: ScriptTaskInventory Request in region: " + m_regionName); //m_log.DebugFormat("[CAPS]: request: {0}, path: {1}, param: {2}", request, path, param); Hashtable hash = (Hashtable) LLSD.LLSDDeserialize(Utils.StringToBytes(request)); LLSDTaskScriptUpdate llsdUpdateRequest = new LLSDTaskScriptUpdate(); LLSDHelpers.DeserialiseOSDMap(hash, llsdUpdateRequest); string capsBase = "/CAPS/" + m_capsObjectPath; string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000"); TaskInventoryScriptUpdater uploader = new TaskInventoryScriptUpdater( llsdUpdateRequest.item_id, llsdUpdateRequest.task_id, llsdUpdateRequest.is_script_running, capsBase + uploaderPath, m_httpListener, m_dumpAssetsToFile); uploader.OnUpLoad += TaskScriptUpdated; m_httpListener.AddStreamHandler( new BinaryStreamHandler("POST", capsBase + uploaderPath, uploader.uploaderCaps)); string protocol = "http://"; if (m_httpListener.UseSSL) protocol = "https://"; string uploaderURL = protocol + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + capsBase + uploaderPath; LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse(); uploadResponse.uploader = uploaderURL; uploadResponse.state = "upload"; // m_log.InfoFormat("[CAPS]: " + // "ScriptTaskInventory response: {0}", // LLSDHelpers.SerialiseLLSDReply(uploadResponse))); return LLSDHelpers.SerialiseLLSDReply(uploadResponse); } catch (Exception e) { m_log.Error("[CAPS]: " + e.ToString()); } return null; } public string UploadBakedTexture(string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { try { m_log.Debug("[CAPS]: UploadBakedTexture Request in region: " + m_regionName); string capsBase = "/CAPS/" + m_capsObjectPath; string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000"); BakedTextureUploader uploader = new BakedTextureUploader(capsBase + uploaderPath, m_httpListener); uploader.OnUpLoad += BakedTextureUploaded; m_httpListener.AddStreamHandler( new BinaryStreamHandler("POST", capsBase + uploaderPath, uploader.uploaderCaps)); string protocol = "http://"; if (m_httpListener.UseSSL) protocol = "https://"; string uploaderURL = protocol + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + capsBase + uploaderPath; LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse(); uploadResponse.uploader = uploaderURL; uploadResponse.state = "upload"; return LLSDHelpers.SerialiseLLSDReply(uploadResponse); } catch (Exception e) { m_log.Error("[CAPS]: " + e.ToString()); } return null; } /// /// Called by the notecard update handler. Provides a URL to which the client can upload a new asset. /// /// /// /// /// public string NoteCardAgentInventory(string request, string path, string param, OSHttpRequest httpRequest, OSHttpResponse httpResponse) { //m_log.Debug("[CAPS]: NoteCardAgentInventory Request in region: " + m_regionName + "\n" + request); //m_log.Debug("[CAPS]: NoteCardAgentInventory Request is: " + request); //OpenMetaverse.StructuredData.OSDMap hash = (OpenMetaverse.StructuredData.OSDMap)OpenMetaverse.StructuredData.LLSDParser.DeserializeBinary(Utils.StringToBytes(request)); Hashtable hash = (Hashtable) LLSD.LLSDDeserialize(Utils.StringToBytes(request)); LLSDItemUpdate llsdRequest = new LLSDItemUpdate(); LLSDHelpers.DeserialiseOSDMap(hash, llsdRequest); string capsBase = "/CAPS/" + m_capsObjectPath; string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000"); ItemUpdater uploader = new ItemUpdater(llsdRequest.item_id, capsBase + uploaderPath, m_httpListener, m_dumpAssetsToFile); uploader.OnUpLoad += ItemUpdated; m_httpListener.AddStreamHandler( new BinaryStreamHandler("POST", capsBase + uploaderPath, uploader.uploaderCaps)); string protocol = "http://"; if (m_httpListener.UseSSL) protocol = "https://"; string uploaderURL = protocol + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + capsBase + uploaderPath; LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse(); uploadResponse.uploader = uploaderURL; uploadResponse.state = "upload"; // m_log.InfoFormat("[CAPS]: " + // "NoteCardAgentInventory response: {0}", // LLSDHelpers.SerialiseLLSDReply(uploadResponse))); return LLSDHelpers.SerialiseLLSDReply(uploadResponse); } /// /// /// /// /// public LLSDAssetUploadResponse NewAgentInventoryRequest(LLSDAssetUploadRequest llsdRequest) { //m_log.Debug("[CAPS]: NewAgentInventoryRequest Request is: " + llsdRequest.ToString()); //m_log.Debug("asset upload request via CAPS" + llsdRequest.inventory_type + " , " + llsdRequest.asset_type); if (llsdRequest.asset_type == "texture" || llsdRequest.asset_type == "animation" || llsdRequest.asset_type == "sound") { IClientAPI client = null; IScene scene = null; if (GetClient != null) { client = GetClient(m_agentID); scene = client.Scene; IMoneyModule mm = scene.RequestModuleInterface(); if (mm != null) { if (!mm.UploadCovered(client, mm.UploadCharge)) { if (client != null) client.SendAgentAlertMessage("Unable to upload asset. Insufficient funds.", false); LLSDAssetUploadResponse errorResponse = new LLSDAssetUploadResponse(); errorResponse.uploader = ""; errorResponse.state = "error"; return errorResponse; } } } } string assetName = llsdRequest.name; string assetDes = llsdRequest.description; string capsBase = "/CAPS/" + m_capsObjectPath; UUID newAsset = UUID.Random(); UUID newInvItem = UUID.Random(); UUID parentFolder = llsdRequest.folder_id; string uploaderPath = Util.RandomClass.Next(5000, 8000).ToString("0000"); AssetUploader uploader = new AssetUploader(assetName, assetDes, newAsset, newInvItem, parentFolder, llsdRequest.inventory_type, llsdRequest.asset_type, capsBase + uploaderPath, m_httpListener, m_dumpAssetsToFile); m_httpListener.AddStreamHandler( new BinaryStreamHandler("POST", capsBase + uploaderPath, uploader.uploaderCaps)); string protocol = "http://"; if (m_httpListener.UseSSL) protocol = "https://"; string uploaderURL = protocol + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + capsBase + uploaderPath; LLSDAssetUploadResponse uploadResponse = new LLSDAssetUploadResponse(); uploadResponse.uploader = uploaderURL; uploadResponse.state = "upload"; uploader.OnUpLoad += UploadCompleteHandler; return uploadResponse; } /// /// /// /// /// /// public void UploadCompleteHandler(string assetName, string assetDescription, UUID assetID, UUID inventoryItem, UUID parentFolder, byte[] data, string inventoryType, string assetType) { sbyte assType = 0; sbyte inType = 0; if (inventoryType == "sound") { inType = 1; assType = 1; } else if (inventoryType == "animation") { inType = 19; assType = 20; } else if (inventoryType == "wearable") { inType = 18; switch (assetType) { case "bodypart": assType = 13; break; case "clothing": assType = 5; break; } } AssetBase asset; asset = new AssetBase(assetID, assetName, assType, m_agentID.ToString()); asset.Data = data; if (AddNewAsset != null) AddNewAsset(asset); else if (m_assetCache != null) m_assetCache.Store(asset); InventoryItemBase item = new InventoryItemBase(); item.Owner = m_agentID; item.CreatorId = m_agentID.ToString(); item.CreatorData = String.Empty; item.ID = inventoryItem; item.AssetID = asset.FullID; item.Description = assetDescription; item.Name = assetName; item.AssetType = assType; item.InvType = inType; item.Folder = parentFolder; item.CurrentPermissions = (uint)PermissionMask.All; item.BasePermissions = (uint)PermissionMask.All; item.EveryOnePermissions = 0; item.NextPermissions = (uint)(PermissionMask.Move | PermissionMask.Modify | PermissionMask.Transfer); item.CreationDate = Util.UnixTimeSinceEpoch(); if (AddNewInventoryItem != null) { AddNewInventoryItem(m_agentID, item); } } public void BakedTextureUploaded(UUID assetID, byte[] data) { // m_log.WarnFormat("[CAPS]: Received baked texture {0}", assetID.ToString()); AssetBase asset; asset = new AssetBase(assetID, "Baked Texture", (sbyte)AssetType.Texture, m_agentID.ToString()); asset.Data = data; asset.Temporary = true; asset.Local = ! m_persistBakedTextures; // Local assets aren't persisted, non-local are m_assetCache.Store(asset); } /// /// Called when new asset data for an agent inventory item update has been uploaded. /// /// Item to update /// New asset data /// public UUID ItemUpdated(UUID itemID, byte[] data) { if (ItemUpdatedCall != null) { return ItemUpdatedCall(m_agentID, itemID, data); } return UUID.Zero; } /// /// Called when new asset data for an agent inventory item update has been uploaded. /// /// Item to update /// Prim containing item to update /// Signals whether the script to update is currently running /// New asset data public void TaskScriptUpdated(UUID itemID, UUID primID, bool isScriptRunning, byte[] data, ref ArrayList errors) { if (TaskScriptUpdatedCall != null) { ArrayList e = TaskScriptUpdatedCall(m_agentID, itemID, primID, isScriptRunning, data); foreach (Object item in e) errors.Add(item); } } public class AssetUploader { public event UpLoadedAsset OnUpLoad; private UpLoadedAsset handlerUpLoad = null; private string uploaderPath = String.Empty; private UUID newAssetID; private UUID inventoryItemID; private UUID parentFolder; private IHttpServer httpListener; private bool m_dumpAssetsToFile; private string m_assetName = String.Empty; private string m_assetDes = String.Empty; private string m_invType = String.Empty; private string m_assetType = String.Empty; public AssetUploader(string assetName, string description, UUID assetID, UUID inventoryItem, UUID parentFolderID, string invType, string assetType, string path, IHttpServer httpServer, bool dumpAssetsToFile) { m_assetName = assetName; m_assetDes = description; newAssetID = assetID; inventoryItemID = inventoryItem; uploaderPath = path; httpListener = httpServer; parentFolder = parentFolderID; m_assetType = assetType; m_invType = invType; m_dumpAssetsToFile = dumpAssetsToFile; } /// /// /// /// /// /// /// public string uploaderCaps(byte[] data, string path, string param) { UUID inv = inventoryItemID; string res = String.Empty; LLSDAssetUploadComplete uploadComplete = new LLSDAssetUploadComplete(); uploadComplete.new_asset = newAssetID.ToString(); uploadComplete.new_inventory_item = inv; uploadComplete.state = "complete"; res = LLSDHelpers.SerialiseLLSDReply(uploadComplete); httpListener.RemoveStreamHandler("POST", uploaderPath); // TODO: probably make this a better set of extensions here string extension = ".jp2"; if (m_invType != "image") { extension = ".dat"; } if (m_dumpAssetsToFile) { SaveAssetToFile(m_assetName + extension, data); } handlerUpLoad = OnUpLoad; if (handlerUpLoad != null) { handlerUpLoad(m_assetName, m_assetDes, newAssetID, inv, parentFolder, data, m_invType, m_assetType); } return res; } ///Left this in and commented in case there are unforseen issues //private void SaveAssetToFile(string filename, byte[] data) //{ // FileStream fs = File.Create(filename); // BinaryWriter bw = new BinaryWriter(fs); // bw.Write(data); // bw.Close(); // fs.Close(); //} private static void SaveAssetToFile(string filename, byte[] data) { string assetPath = "UserAssets"; if (!Directory.Exists(assetPath)) { Directory.CreateDirectory(assetPath); } FileStream fs = File.Create(Path.Combine(assetPath, Util.safeFileName(filename))); BinaryWriter bw = new BinaryWriter(fs); bw.Write(data); bw.Close(); fs.Close(); } } /// /// This class is a callback invoked when a client sends asset data to /// an agent inventory notecard update url /// public class ItemUpdater { public event UpdateItem OnUpLoad; private UpdateItem handlerUpdateItem = null; private string uploaderPath = String.Empty; private UUID inventoryItemID; private IHttpServer httpListener; private bool m_dumpAssetToFile; public ItemUpdater(UUID inventoryItem, string path, IHttpServer httpServer, bool dumpAssetToFile) { m_dumpAssetToFile = dumpAssetToFile; inventoryItemID = inventoryItem; uploaderPath = path; httpListener = httpServer; } /// /// /// /// /// /// /// public string uploaderCaps(byte[] data, string path, string param) { UUID inv = inventoryItemID; string res = String.Empty; LLSDAssetUploadComplete uploadComplete = new LLSDAssetUploadComplete(); UUID assetID = UUID.Zero; handlerUpdateItem = OnUpLoad; if (handlerUpdateItem != null) { assetID = handlerUpdateItem(inv, data); } uploadComplete.new_asset = assetID.ToString(); uploadComplete.new_inventory_item = inv; uploadComplete.state = "complete"; res = LLSDHelpers.SerialiseLLSDReply(uploadComplete); httpListener.RemoveStreamHandler("POST", uploaderPath); if (m_dumpAssetToFile) { SaveAssetToFile("updateditem" + Util.RandomClass.Next(1, 1000) + ".dat", data); } return res; } ///Left this in and commented in case there are unforseen issues //private void SaveAssetToFile(string filename, byte[] data) //{ // FileStream fs = File.Create(filename); // BinaryWriter bw = new BinaryWriter(fs); // bw.Write(data); // bw.Close(); // fs.Close(); //} private static void SaveAssetToFile(string filename, byte[] data) { string assetPath = "UserAssets"; if (!Directory.Exists(assetPath)) { Directory.CreateDirectory(assetPath); } FileStream fs = File.Create(Path.Combine(assetPath, filename)); BinaryWriter bw = new BinaryWriter(fs); bw.Write(data); bw.Close(); fs.Close(); } } /// /// This class is a callback invoked when a client sends asset data to /// a task inventory script update url /// public class TaskInventoryScriptUpdater { public event UpdateTaskScript OnUpLoad; private UpdateTaskScript handlerUpdateTaskScript = null; private string uploaderPath = String.Empty; private UUID inventoryItemID; private UUID primID; private bool isScriptRunning; private IHttpServer httpListener; private bool m_dumpAssetToFile; public TaskInventoryScriptUpdater(UUID inventoryItemID, UUID primID, int isScriptRunning, string path, IHttpServer httpServer, bool dumpAssetToFile) { m_dumpAssetToFile = dumpAssetToFile; this.inventoryItemID = inventoryItemID; this.primID = primID; // This comes in over the packet as an integer, but actually appears to be treated as a bool this.isScriptRunning = (0 == isScriptRunning ? false : true); uploaderPath = path; httpListener = httpServer; } /// /// /// /// /// /// /// public string uploaderCaps(byte[] data, string path, string param) { try { // m_log.InfoFormat("[CAPS]: " + // "TaskInventoryScriptUpdater received data: {0}, path: {1}, param: {2}", // data, path, param)); string res = String.Empty; LLSDTaskScriptUploadComplete uploadComplete = new LLSDTaskScriptUploadComplete(); ArrayList errors = new ArrayList(); handlerUpdateTaskScript = OnUpLoad; if (handlerUpdateTaskScript != null) { handlerUpdateTaskScript(inventoryItemID, primID, isScriptRunning, data, ref errors); } uploadComplete.new_asset = inventoryItemID; uploadComplete.compiled = errors.Count > 0 ? false : true; uploadComplete.state = "complete"; uploadComplete.errors = new OSDArray(); uploadComplete.errors.Array = errors; res = LLSDHelpers.SerialiseLLSDReply(uploadComplete); httpListener.RemoveStreamHandler("POST", uploaderPath); if (m_dumpAssetToFile) { SaveAssetToFile("updatedtaskscript" + Util.RandomClass.Next(1, 1000) + ".dat", data); } // m_log.InfoFormat("[CAPS]: TaskInventoryScriptUpdater.uploaderCaps res: {0}", res); return res; } catch (Exception e) { m_log.Error("[CAPS]: " + e.ToString()); } // XXX Maybe this should be some meaningful error packet return null; } ///Left this in and commented in case there are unforseen issues //private void SaveAssetToFile(string filename, byte[] data) //{ // FileStream fs = File.Create(filename); // BinaryWriter bw = new BinaryWriter(fs); // bw.Write(data); // bw.Close(); // fs.Close(); //} private static void SaveAssetToFile(string filename, byte[] data) { string assetPath = "UserAssets"; if (!Directory.Exists(assetPath)) { Directory.CreateDirectory(assetPath); } FileStream fs = File.Create(Path.Combine(assetPath, filename)); BinaryWriter bw = new BinaryWriter(fs); bw.Write(data); bw.Close(); fs.Close(); } } public class BakedTextureUploader { public event UploadedBakedTexture OnUpLoad; private UploadedBakedTexture handlerUpLoad = null; private string uploaderPath = String.Empty; private UUID newAssetID; private IHttpServer httpListener; public BakedTextureUploader(string path, IHttpServer httpServer) { newAssetID = UUID.Random(); uploaderPath = path; httpListener = httpServer; m_log.InfoFormat("[CAPS] baked texture upload starting for {0}",newAssetID); } /// /// /// /// /// /// /// public string uploaderCaps(byte[] data, string path, string param) { m_log.WarnFormat("[CAPS] baked texture upload completed for {0}",newAssetID); string res = String.Empty; LLSDAssetUploadComplete uploadComplete = new LLSDAssetUploadComplete(); uploadComplete.new_asset = newAssetID.ToString(); uploadComplete.new_inventory_item = UUID.Zero; uploadComplete.state = "complete"; res = LLSDHelpers.SerialiseLLSDReply(uploadComplete); httpListener.RemoveStreamHandler("POST", uploaderPath); handlerUpLoad = OnUpLoad; if (handlerUpLoad != null) { handlerUpLoad(newAssetID, data); } m_log.InfoFormat("[CAPS] baked texture upload completed for {0}",newAssetID); return res; } } } }