/// <summary> /// This method is called whenever an Item has been successfully /// reconstituted from the request's entity. /// It uses the information curren tin the XmlInventoryCollection /// to complete the item's specification, including any implied /// context and asset associations. /// It fails the request if any necessary item or asset information /// is missing. /// </summary> private void Validate(XmlInventoryCollection ic) { // There really should be an item present if we've // called validate. So fail if there is not. if (ic.Item == null) { Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId); ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error"); } // Every item is required to have a name (via REST anyway) if (ic.Item.Name == String.Empty) { Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId); ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required"); } // An item MUST have an asset ID. AssetID should never be zero // here. It should always get set from the information stored // when the Asset element was processed. if (ic.Item.AssetID == UUID.Zero) { Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId); Rest.Log.InfoFormat("{0} Asset information is missing", MsgId); ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required"); } // If the item is new, then assign it an ID if (ic.Item.ID == UUID.Zero) { ic.Item.ID = UUID.Random(); } // If the context is being implied, obtain the current // folder item's ID. If it was specified explicitly, make // sure that theparent folder exists. if (ic.Item.Folder == UUID.Zero) { ic.Item.Folder = ic.Parent(); } else { bool found = false; foreach (InventoryFolderBase parent in ic.rdata.folders) { if (parent.ID == ic.Item.Folder) { found = true; break; } } if (!found) { Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}", MsgId, ic.Item.Folder, ic.Item.ID); ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required"); } } // If this is an inline asset being constructed in the context // of a new Item, then use the itm's name here too. if (ic.Asset != null) { if (ic.Asset.Name == String.Empty) ic.Asset.Name = ic.Item.Name; if (ic.Asset.Description == String.Empty) ic.Asset.Description = ic.Item.Description; } // Assign permissions ic.Item.CurrentPermissions = ic.CurrentPermissions; ic.Item.EveryOnePermissions = ic.EveryOnePermissions; ic.Item.BasePermissions = ic.BasePermissions; ic.Item.GroupPermissions = ic.GroupPermissions; ic.Item.NextPermissions = ic.NextPermissions; // If no type was specified for this item, we can attempt to // infer something from the file type maybe. This is NOT as // good as having type be specified in the XML. if (ic.Item.AssetType == (int) AssetType.Unknown || ic.Item.InvType == (int) InventoryType.Unknown) { Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId); string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD); if (Rest.DEBUG) { for (int i = 0; i < parts.Length; i++) { Rest.Log.DebugFormat("{0} Name part {1} : {2}", MsgId, i, parts[i]); } } // If the associated item name is multi-part, then maybe // the last part will indicate the item type - if we're // lucky. if (parts.Length > 1) { Rest.Log.DebugFormat("{0} File type is {1}", MsgId, parts[parts.Length - 1]); switch (parts[parts.Length - 1]) { case "jpeg2000" : case "jpeg-2000" : case "jpg2000" : case "jpg-2000" : Rest.Log.DebugFormat("{0} Type {1} inferred", MsgId, parts[parts.Length-1]); if (ic.Item.AssetType == (int) AssetType.Unknown) ic.Item.AssetType = (int) AssetType.ImageJPEG; if (ic.Item.InvType == (int) InventoryType.Unknown) ic.Item.InvType = (int) InventoryType.Texture; break; case "jpg" : case "jpeg" : Rest.Log.DebugFormat("{0} Type {1} inferred", MsgId, parts[parts.Length - 1]); if (ic.Item.AssetType == (int) AssetType.Unknown) ic.Item.AssetType = (int) AssetType.ImageJPEG; if (ic.Item.InvType == (int) InventoryType.Unknown) ic.Item.InvType = (int) InventoryType.Texture; break; case "tga" : if (parts[parts.Length - 2].IndexOf("_texture") != -1) { if (ic.Item.AssetType == (int) AssetType.Unknown) ic.Item.AssetType = (int) AssetType.TextureTGA; if (ic.Item.InvType == (int) AssetType.Unknown) ic.Item.InvType = (int) InventoryType.Texture; } else { if (ic.Item.AssetType == (int) AssetType.Unknown) ic.Item.AssetType = (int) AssetType.ImageTGA; if (ic.Item.InvType == (int) InventoryType.Unknown) ic.Item.InvType = (int) InventoryType.Snapshot; } break; default : Rest.Log.DebugFormat("{0} Asset/Inventory type could not be inferred for {1}", MsgId,ic.Item.Name); break; } } } /// If this is a TGA remember the fact if (ic.Item.AssetType == (int) AssetType.TextureTGA || ic.Item.AssetType == (int) AssetType.ImageTGA) { Bitmap temp; Stream tgadata = new MemoryStream(ic.Asset.Data); temp = LoadTGAClass.LoadTGA(tgadata); try { ic.Asset.Data = OpenJPEG.EncodeFromImage(temp, true); } catch (DllNotFoundException) { Rest.Log.ErrorFormat("OpenJpeg is not installed correctly on this system. Asset Data is emtpy for {0}", ic.Item.Name); ic.Asset.Data = new Byte[0]; } catch (IndexOutOfRangeException) { Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is emtpy for {0}", ic.Item.Name); ic.Asset.Data = new Byte[0]; } catch (Exception) { Rest.Log.ErrorFormat("OpenJpeg was unable to encode this. Asset Data is emtpy for {0}", ic.Item.Name); ic.Asset.Data = new Byte[0]; } } ic.reset(); }
/// <summary> /// Store any permissions information provided by the request. /// This overrides the default permissions set when the /// XmlInventoryCollection object was created. /// </summary> private void CollectPermissions(XmlInventoryCollection ic) { if (ic.xml.HasAttributes) { for (int i = 0; i < ic.xml.AttributeCount; i++) { ic.xml.MoveToAttribute(i); switch (ic.xml.Name) { case "current": ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); break; case "next": ic.NextPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); break; case "group": ic.GroupPermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); break; case "everyone": ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); break; case "base": ic.BasePermissions = UInt32.Parse(ic.xml.Value, NumberStyles.HexNumber); break; default: Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}", MsgId,ic.xml.Name, ic.xml.Value); ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("invalid attribute <{0}>", ic.xml.Name)); break; } } } ic.xml.MoveToElement(); }
/// <summary> /// This method assembles an asset instance from the /// information supplied in the request's entity. It is /// called as a result of detecting a start tag for a /// type of Asset. /// The information is collected locally, and an asset /// instance is created only if the basic XML parsing /// completes successfully. /// Default values for all parts of the asset are /// established before overriding them from the supplied /// XML. /// If an asset has inline=true as an attribute, then /// the element contains the data representing the /// asset. This is saved as the data component. /// inline=false means that the element's payload is /// simply the UUID of the asset referenced by the /// item being constructed. /// An asset, if created is stored in the /// XmlInventoryCollection /// </summary> private void CollectAsset(XmlInventoryCollection ic) { Rest.Log.DebugFormat("{0} Interpret asset element", MsgId); string name = String.Empty; string desc = String.Empty; sbyte type = (sbyte) AssetType.Unknown; bool temp = false; bool local = false; // This is not a persistent attribute bool inline = false; UUID uuid = UUID.Zero; // Attribute is optional if (ic.xml.HasAttributes) { for (int i = 0; i < ic.xml.AttributeCount; i++) { ic.xml.MoveToAttribute(i); switch (ic.xml.Name) { case "name" : name = ic.xml.Value; break; case "type" : type = SByte.Parse(ic.xml.Value); break; case "description" : desc = ic.xml.Value; break; case "temporary" : temp = Boolean.Parse(ic.xml.Value); break; case "uuid" : uuid = new UUID(ic.xml.Value); break; case "inline" : inline = Boolean.Parse(ic.xml.Value); break; case "local" : local = Boolean.Parse(ic.xml.Value); break; default : Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}", MsgId, ic.xml.Name, ic.xml.Value); ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>", ic.xml.Name)); break; } } } ic.xml.MoveToElement(); // If this is a reference to an existing asset, just store the // asset ID into the item. if (!inline) { if (ic.Item != null) { ic.Item.AssetID = new UUID(ic.xml.ReadElementContentAsString()); Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID); } else { Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId); ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset"); } } // Otherwise, generate an asset ID, store that into the item, and // create an entry in the asset list for the inlined asset. But // only if the size is non-zero. else { AssetBase asset = null; string b64string = null; // Generate a UUID if none were given, and generally none should // be. Ever. if (uuid == UUID.Zero) { uuid = UUID.Random(); } // Create AssetBase entity to hold the inlined asset asset = new AssetBase(uuid, name, type, UUID.Zero.ToString()); asset.Description = desc; asset.Local = local; asset.Temporary = temp; b64string = ic.xml.ReadElementContentAsString(); Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length); Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId, b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length)); asset.Data = Convert.FromBase64String(b64string); // Ensure the asset always has some kind of data component if (asset.Data == null) { asset.Data = new byte[1]; } // If this is in the context of an item, establish // a link with the item in context. if (ic.Item != null && ic.Item.AssetID == UUID.Zero) { ic.Item.AssetID = uuid; } ic.Push(asset); } }
/// <summary> /// This method is called to handle the construction of an Item /// instance from the supplied request entity. It is called /// whenever an Item start tag is detected. /// An instance of an Item is created and initialized to default /// values. These values are then overridden from values supplied /// as attributes to the Item element. /// This item is then stored in the XmlInventoryCollection and /// will be verified by Validate. /// All context is reset whenever the effective folder changes /// or an item is successfully validated. /// </summary> private void CollectItem(XmlInventoryCollection ic) { Rest.Log.DebugFormat("{0} Interpret item element", MsgId); InventoryItemBase result = new InventoryItemBase(); result.Name = String.Empty; result.Description = String.Empty; result.ID = UUID.Zero; result.Folder = UUID.Zero; result.Owner = ic.UserID; result.CreatorId = ic.UserID.ToString(); result.AssetID = UUID.Zero; result.GroupID = UUID.Zero; result.GroupOwned = false; result.InvType = (int) InventoryType.Unknown; result.AssetType = (int) AssetType.Unknown; if (ic.xml.HasAttributes) { for (int i = 0; i < ic.xml.AttributeCount; i++) { ic.xml.MoveToAttribute(i); switch (ic.xml.Name) { case "name": result.Name = ic.xml.Value; break; case "desc": result.Description = ic.xml.Value; break; case "uuid": result.ID = new UUID(ic.xml.Value); break; case "folder": result.Folder = new UUID(ic.xml.Value); break; case "owner": result.Owner = new UUID(ic.xml.Value); break; case "invtype": result.InvType = Int32.Parse(ic.xml.Value); break; case "creator": result.CreatorId = ic.xml.Value; break; case "assettype": result.AssetType = Int32.Parse(ic.xml.Value); break; case "groupowned": result.GroupOwned = Boolean.Parse(ic.xml.Value); break; case "groupid": result.GroupID = new UUID(ic.xml.Value); break; case "flags": result.Flags = UInt32.Parse(ic.xml.Value); break; case "creationdate": result.CreationDate = Int32.Parse(ic.xml.Value); break; case "saletype": result.SaleType = Byte.Parse(ic.xml.Value); break; case "saleprice": result.SalePrice = Int32.Parse(ic.xml.Value); break; default: Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}", MsgId, ic.xml.Name, ic.xml.Value); ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute", ic.xml.Name)); break; } } } ic.xml.MoveToElement(); ic.Push(result); }
/// <summary> /// This method creates an inventory Folder from the /// information supplied in the request's entity. /// A folder instance is created and initialized to reflect /// default values. These values are then overridden /// by information supplied in the entity. /// If context was not explicitly provided, then the /// appropriate ID values are determined. /// </summary> private void CollectFolder(XmlInventoryCollection ic) { Rest.Log.DebugFormat("{0} Interpret folder element", MsgId); InventoryFolderBase result = new InventoryFolderBase(); // Default values result.Name = String.Empty; result.ID = UUID.Zero; result.Owner = ic.UserID; result.ParentID = UUID.Zero; // Context result.Type = (short) AssetType.Folder; result.Version = 1; if (ic.xml.HasAttributes) { for (int i = 0; i < ic.xml.AttributeCount; i++) { ic.xml.MoveToAttribute(i); switch (ic.xml.Name) { case "name": result.Name = ic.xml.Value; break; case "uuid": result.ID = new UUID(ic.xml.Value); break; case "parent": result.ParentID = new UUID(ic.xml.Value); break; case "owner": result.Owner = new UUID(ic.xml.Value); break; case "type": result.Type = Int16.Parse(ic.xml.Value); break; case "version": result.Version = UInt16.Parse(ic.xml.Value); break; default: Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}", MsgId, ic.xml.Name, ic.xml.Value); ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>", ic.xml.Name)); break; } } } ic.xml.MoveToElement(); // The client is relying upon the reconstitution process // to determine the parent's UUID based upon context. This // is necessary where a new folder may have been // introduced. if (result.ParentID == UUID.Zero) { result.ParentID = ic.Parent(); } else { bool found = false; foreach (InventoryFolderBase parent in ic.rdata.folders) { if (parent.ID == result.ParentID) { found = true; break; } } if (!found) { Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}", MsgId, ic.Item.Folder, result.ID); ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent"); } } // This is a new folder, so no existing UUID is available // or appropriate if (result.ID == UUID.Zero) { result.ID = UUID.Random(); } // Treat this as a new context. Any other information is // obsolete as a consequence. ic.Push(result); }
/// <summary> /// This method is called by PUT and POST to create an XmlInventoryCollection /// instance that reflects the content of the entity supplied on the request. /// Any elements in the completed collection whose UUID is zero, are /// considered to be located relative to the end-point identified int he /// URI. In this way, an entire sub-tree can be conveyed in a single REST /// PUT or POST request. /// /// A new instance of XmlInventoryCollection is created and, if the request /// has an entity, it is more completely initialized. thus, if no entity was /// provided the collection is valid, but empty. /// /// The entity is then scanned and each tag is processed to produce the /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection /// will reflect the subtree described by the entity. /// /// This is a very flexible mechanism, the entity may contain arbitrary, /// discontiguous tree fragments, or may contain single element. The caller is /// responsible for integrating this collection (and ensuring that any /// missing parent IDs are resolved). /// </summary> /// <param name=rdata>HTTP service request work area</param> internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata) { Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId); XmlInventoryCollection ic = new XmlInventoryCollection(); if (rdata.request.HasEntityBody) { Rest.Log.DebugFormat("{0} Entity present", MsgId); ic.init(rdata); try { while (ic.xml.Read()) { switch (ic.xml.NodeType) { case XmlNodeType.Element: Rest.Log.DebugFormat("{0} StartElement: <{1}>", MsgId, ic.xml.Name); switch (ic.xml.Name) { case "Folder": Rest.Log.DebugFormat("{0} Processing {1} element", MsgId, ic.xml.Name); CollectFolder(ic); break; case "Item": Rest.Log.DebugFormat("{0} Processing {1} element", MsgId, ic.xml.Name); CollectItem(ic); break; case "Asset": Rest.Log.DebugFormat("{0} Processing {1} element", MsgId, ic.xml.Name); CollectAsset(ic); break; case "Permissions": Rest.Log.DebugFormat("{0} Processing {1} element", MsgId, ic.xml.Name); CollectPermissions(ic); break; default: Rest.Log.DebugFormat("{0} Ignoring {1} element", MsgId, ic.xml.Name); break; } // This stinks, but the ReadElement call above not only reads // the imbedded data, but also consumes the end tag for Asset // and moves the element pointer on to the containing Item's // element-end, however, if there was a permissions element // following, it would get us to the start of that.. if (ic.xml.NodeType == XmlNodeType.EndElement && ic.xml.Name == "Item") { Validate(ic); } break; case XmlNodeType.EndElement : switch (ic.xml.Name) { case "Folder": Rest.Log.DebugFormat("{0} Completing {1} element", MsgId, ic.xml.Name); ic.Pop(); break; case "Item": Rest.Log.DebugFormat("{0} Completing {1} element", MsgId, ic.xml.Name); Validate(ic); break; case "Asset": Rest.Log.DebugFormat("{0} Completing {1} element", MsgId, ic.xml.Name); break; case "Permissions": Rest.Log.DebugFormat("{0} Completing {1} element", MsgId, ic.xml.Name); break; default: Rest.Log.DebugFormat("{0} Ignoring {1} element", MsgId, ic.xml.Name); break; } break; default: Rest.Log.DebugFormat("{0} Ignoring: <{1}>:<{2}>", MsgId, ic.xml.NodeType, ic.xml.Value); break; } } } catch (XmlException e) { Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message); throw e; } catch (Exception e) { Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message); throw e; } } else { Rest.Log.DebugFormat("{0} Entity absent", MsgId); } if (Rest.DEBUG) { Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId); Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count); Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count); Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count); } return ic; }