RestInventoryServices.cs 97 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335
  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 OpenSim 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. using System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. using System.Threading;
  31. using System.Xml;
  32. using System.Drawing;
  33. using System.Timers;
  34. using OpenSim.Framework;
  35. using OpenSim.Framework.Servers;
  36. using OpenSim.Framework.Communications;
  37. using OpenSim.Framework.Communications.Cache;
  38. using OpenMetaverse;
  39. using OpenMetaverse.Imaging;
  40. using Nini.Config;
  41. namespace OpenSim.ApplicationPlugins.Rest.Inventory
  42. {
  43. public class RestInventoryServices : IRest
  44. {
  45. private static readonly int PARM_USERID = 0;
  46. private static readonly int PARM_PATH = 1;
  47. private bool enabled = false;
  48. private string qPrefix = "inventory";
  49. private static readonly string PRIVATE_ROOT_NAME = "My Inventory";
  50. /// <summary>
  51. /// The constructor makes sure that the service prefix is absolute
  52. /// and the registers the service handler and the allocator.
  53. /// </summary>
  54. public RestInventoryServices()
  55. {
  56. Rest.Log.InfoFormat("{0} Inventory services initializing", MsgId);
  57. Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
  58. // This is better than a null reference.
  59. if (Rest.InventoryServices == null)
  60. throw new Exception(String.Format("{0} OpenSim inventory services are not available",
  61. MsgId));
  62. if (Rest.UserServices == null)
  63. throw new Exception(String.Format("{0} OpenSim user services are not available",
  64. MsgId));
  65. if (Rest.AssetServices == null)
  66. throw new Exception(String.Format("{0} OpenSim asset services are not available",
  67. MsgId));
  68. // If a relative path was specified for the handler's domain,
  69. // add the standard prefix to make it absolute, e.g. /admin
  70. if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
  71. {
  72. Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
  73. qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
  74. Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
  75. }
  76. // Register interface using the absolute URI.
  77. Rest.Plugin.AddPathHandler(DoInventory,qPrefix,Allocate);
  78. // Activate if everything went OK
  79. enabled = true;
  80. Rest.Log.InfoFormat("{0} Inventory services initialization complete", MsgId);
  81. }
  82. /// <summary>
  83. /// Post-construction, pre-enabled initialization opportunity
  84. /// Not currently exploited.
  85. /// </summary>
  86. public void Initialize()
  87. {
  88. }
  89. /// <summary>
  90. /// Called by the plug-in to halt service processing. Local processing is
  91. /// disabled.
  92. /// </summary>
  93. public void Close()
  94. {
  95. enabled = false;
  96. Rest.Log.InfoFormat("{0} Inventory services closing down", MsgId);
  97. }
  98. /// <summary>
  99. /// This property is declared locally because it is used a lot and
  100. /// brevity is nice.
  101. /// </summary>
  102. internal string MsgId
  103. {
  104. get { return Rest.MsgId; }
  105. }
  106. #region Interface
  107. /// <summary>
  108. /// The plugin (RestHandler) calls this method to allocate the request
  109. /// state carrier for a new request. It is destroyed when the request
  110. /// completes. All request-instance specific state is kept here. This
  111. /// is registered when this service provider is registered.
  112. /// </summary>
  113. /// <param name=request>Inbound HTTP request information</param>
  114. /// <param name=response>Outbound HTTP request information</param>
  115. /// <param name=qPrefix>REST service domain prefix</param>
  116. /// <returns>A RequestData instance suitable for this service</returns>
  117. private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
  118. {
  119. return (RequestData) new InventoryRequestData(request, response, prefix);
  120. }
  121. /// <summary>
  122. /// This method is registered with the handler when this service provider
  123. /// is initialized. It is called whenever the plug-in identifies this service
  124. /// provider as the best match for a given request.
  125. /// It handles all aspects of inventory REST processing, i.e. /admin/inventory
  126. /// </summary>
  127. /// <param name=hdata>A consolidated HTTP request work area</param>
  128. private void DoInventory(RequestData hdata)
  129. {
  130. InventoryRequestData rdata = (InventoryRequestData) hdata;
  131. Rest.Log.DebugFormat("{0} DoInventory ENTRY", MsgId);
  132. // If we're disabled, do nothing.
  133. if (!enabled)
  134. {
  135. return;
  136. }
  137. // Now that we know this is a serious attempt to
  138. // access inventory data, we should find out who
  139. // is asking, and make sure they are authorized
  140. // to do so. We need to validate the caller's
  141. // identity before revealing anything about the
  142. // status quo. Authenticate throws an exception
  143. // via Fail if no identity information is present.
  144. //
  145. // With the present HTTP server we can't use the
  146. // builtin authentication mechanisms because they
  147. // would be enforced for all in-bound requests.
  148. // Instead we look at the headers ourselves and
  149. // handle authentication directly.
  150. try
  151. {
  152. if (!rdata.IsAuthenticated)
  153. {
  154. rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
  155. }
  156. }
  157. catch (RestException e)
  158. {
  159. if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
  160. {
  161. Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
  162. Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
  163. }
  164. else
  165. {
  166. Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
  167. Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
  168. }
  169. throw (e);
  170. }
  171. Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);
  172. // We can only get here if we are authorized
  173. //
  174. // The requestor may have specified an UUID or
  175. // a conjoined FirstName LastName string. We'll
  176. // try both. If we fail with the first, UUID,
  177. // attempt, we try the other. As an example, the
  178. // URI for a valid inventory request might be:
  179. //
  180. // http://<host>:<port>/admin/inventory/Arthur Dent
  181. //
  182. // Indicating that this is an inventory request for
  183. // an avatar named Arthur Dent. This is ALL that is
  184. // required to designate a GET for an entire
  185. // inventory.
  186. //
  187. // Do we have at least a user agent name?
  188. if (rdata.Parameters.Length < 1)
  189. {
  190. Rest.Log.WarnFormat("{0} Inventory: No user agent identifier specified", MsgId);
  191. rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified");
  192. }
  193. // The first parameter MUST be the agent identification, either an UUID
  194. // or a space-separated First-name Last-Name specification. We check for
  195. // an UUID first, if anyone names their character using a valid UUID
  196. // that identifies another existing avatar will cause this a problem...
  197. try
  198. {
  199. rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]);
  200. Rest.Log.DebugFormat("{0} UUID supplied", MsgId);
  201. rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
  202. }
  203. catch
  204. {
  205. string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE);
  206. if (names.Length == 2)
  207. {
  208. Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
  209. rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]);
  210. }
  211. else
  212. {
  213. Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
  214. rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity");
  215. }
  216. }
  217. // If the user profile is null then either the server is broken, or the
  218. // user is not known. We always assume the latter case.
  219. if (rdata.userProfile != null)
  220. {
  221. Rest.Log.DebugFormat("{0} Profile obtained for agent {1} {2}",
  222. MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
  223. }
  224. else
  225. {
  226. Rest.Log.WarnFormat("{0} No profile for {1}", MsgId, rdata.path);
  227. rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity");
  228. }
  229. // If we get to here, then we have effectively validated the user's
  230. // identity. Now we need to get the inventory. If the server does not
  231. // have the inventory, we reject the request with an appropriate explanation.
  232. //
  233. // Note that inventory retrieval is an asynchronous event, we use the rdata
  234. // class instance as the basis for our synchronization.
  235. //
  236. // TODO
  237. // If something went wrong in inventory processing the thread could stall here
  238. // indefinitely. There should be a watchdog timer to fail the request if the
  239. // response is not received in a timely fashion.
  240. rdata.uuid = rdata.userProfile.ID;
  241. if (Rest.InventoryServices.HasInventoryForUser(rdata.uuid))
  242. {
  243. rdata.root = Rest.InventoryServices.RequestRootFolder(rdata.uuid);
  244. Rest.Log.DebugFormat("{0} Inventory Root retrieved for {1} {2}",
  245. MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
  246. Rest.InventoryServices.RequestInventoryForUser(rdata.uuid, rdata.GetUserInventory);
  247. Rest.Log.DebugFormat("{0} Inventory catalog requested for {1} {2}",
  248. MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
  249. lock (rdata)
  250. {
  251. if (!rdata.HaveInventory)
  252. {
  253. rdata.startWD(1000);
  254. rdata.timeout = false;
  255. Monitor.Wait(rdata);
  256. }
  257. }
  258. if (rdata.timeout)
  259. {
  260. Rest.Log.WarnFormat("{0} Inventory not available for {1} {2}. No response from service.",
  261. MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
  262. rdata.Fail(Rest.HttpStatusCodeServerError, "inventory server not responding");
  263. }
  264. if (rdata.root == null)
  265. {
  266. Rest.Log.WarnFormat("{0} Inventory is not available [1] for agent {1} {2}",
  267. MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
  268. rdata.Fail(Rest.HttpStatusCodeServerError, "inventory retrieval failed");
  269. }
  270. }
  271. else
  272. {
  273. Rest.Log.WarnFormat("{0} Inventory is not locally available for agent {1} {2}",
  274. MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
  275. rdata.Fail(Rest.HttpStatusCodeNotFound, "no local inventory for user");
  276. }
  277. // If we get here, then we have successfully retrieved the user's information
  278. // and inventory information is now available locally.
  279. switch (rdata.method)
  280. {
  281. case Rest.HEAD : // Do the processing, set the status code, suppress entity
  282. DoGet(rdata);
  283. rdata.buffer = null;
  284. break;
  285. case Rest.GET : // Do the processing, set the status code, return entity
  286. DoGet(rdata);
  287. break;
  288. case Rest.PUT : // Update named element
  289. DoUpdate(rdata);
  290. break;
  291. case Rest.POST : // Add new information to identified context.
  292. DoExtend(rdata);
  293. break;
  294. case Rest.DELETE : // Delete information
  295. DoDelete(rdata);
  296. break;
  297. default :
  298. Rest.Log.WarnFormat("{0} Method {1} not supported for {2}",
  299. MsgId, rdata.method, rdata.path);
  300. rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed,
  301. String.Format("{0} not supported", rdata.method));
  302. break;
  303. }
  304. }
  305. #endregion Interface
  306. #region method-specific processing
  307. /// <summary>
  308. /// This method implements GET processing for inventory.
  309. /// Any remaining parameters are used to locate the
  310. /// corresponding subtree based upon node name.
  311. /// </summary>
  312. /// <param name=rdata>HTTP service request work area</param>
  313. private void DoGet(InventoryRequestData rdata)
  314. {
  315. rdata.initXmlWriter();
  316. rdata.writer.WriteStartElement(String.Empty,"Inventory",String.Empty);
  317. // If there are additional parameters, then these represent
  318. // a path relative to the root of the inventory. This path
  319. // must be traversed before we format the sub-tree thus
  320. // identified.
  321. traverse(rdata, rdata.root, PARM_PATH);
  322. // Close all open elements
  323. rdata.writer.WriteFullEndElement();
  324. // Indicate a successful request
  325. rdata.Complete();
  326. // Send the response to the user. The body will be implicitly
  327. // constructed from the result of the XML writer.
  328. rdata.Respond(String.Format("Inventory {0} Normal completion", rdata.method));
  329. }
  330. /// <summary>
  331. /// In the case of the inventory, and probably in general,
  332. /// the distinction between PUT and POST is not always
  333. /// easy to discern. The standard is badly worded in places,
  334. /// and adding a node to a hierarchy can be viewed as
  335. /// an addition, or as a modification to the inventory as
  336. /// a whole. This is exacerbated by an unjustified lack of
  337. /// consistency across different implementations.
  338. ///
  339. /// For OpenSim PUT is an update and POST is an addition. This
  340. /// is the behavior required by the HTTP specification and
  341. /// therefore as required by REST.
  342. ///
  343. /// The best way to explain the distinction is to
  344. /// consider the relationship between the URI and the
  345. /// enclosed entity. For PUT, the URI identifies the
  346. /// actual entity to be modified or replaced, i.e. the
  347. /// enclosed entity.
  348. ///
  349. /// If the operation is POST,then the URI describes the
  350. /// context into which the new entity will be added.
  351. ///
  352. /// As an example, suppose the URI contains:
  353. /// /admin/inventory/Clothing
  354. ///
  355. /// A PUT request will normally result in some modification of
  356. /// the folder or item named "Clothing". Whereas a POST
  357. /// request will normally add some new information into the
  358. /// content identified by Clothing. It follows from this
  359. /// that for POST, the element identified by the URI MUST
  360. /// be a folder.
  361. /// </summary>
  362. /// <summary>
  363. /// POST adds new information to the inventory in the
  364. /// context identified by the URI.
  365. /// </summary>
  366. /// <param name=rdata>HTTP service request work area</param>
  367. private void DoExtend(InventoryRequestData rdata)
  368. {
  369. bool created = false;
  370. bool modified = false;
  371. string newnode = String.Empty;
  372. // Resolve the context node specified in the URI. Entity
  373. // data will be ADDED beneath this node. rdata already contains
  374. // information about the current content of the user's
  375. // inventory.
  376. Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill);
  377. // Processing depends upon the type of inventory node
  378. // identified in the URI. This is the CONTEXT for the
  379. // change. We either got a context or we threw an
  380. // exception.
  381. // It follows that we can only add information if the URI
  382. // has identified a folder. So only a type of folder is supported
  383. // in this case.
  384. if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
  385. typeof(InventoryFolderImpl) == InventoryNode.GetType())
  386. {
  387. // Cast the context node appropriately.
  388. InventoryFolderBase context = (InventoryFolderBase) InventoryNode;
  389. Rest.Log.DebugFormat("{0} {1}: Resource(s) will be added to folder {2}",
  390. MsgId, rdata.method, rdata.path);
  391. // Reconstitute the inventory sub-tree from the XML supplied in the entity.
  392. // The result is a stand-alone inventory subtree, not yet integrated into the
  393. // existing tree. An inventory collection consists of three components:
  394. // [1] A (possibly empty) set of folders.
  395. // [2] A (possibly empty) set of items.
  396. // [3] A (possibly empty) set of assets.
  397. // If all of these are empty, then the POST is a harmless no-operation.
  398. XmlInventoryCollection entity = ReconstituteEntity(rdata);
  399. // Inlined assets can be included in entity. These must be incorporated into
  400. // the asset database before we attempt to update the inventory. If anything
  401. // fails, return a failure to requestor.
  402. if (entity.Assets.Count > 0)
  403. {
  404. Rest.Log.DebugFormat("{0} Adding {1} assets to server",
  405. MsgId, entity.Assets.Count);
  406. foreach (AssetBase asset in entity.Assets)
  407. {
  408. Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
  409. MsgId, asset.ID, asset.Type, asset.Name);
  410. Rest.AssetServices.AddAsset(asset);
  411. created = true;
  412. rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>",
  413. asset.Name, asset.ID));
  414. if (Rest.DEBUG && Rest.DumpAsset)
  415. {
  416. Rest.Dump(asset.Data);
  417. }
  418. }
  419. }
  420. // Modify the context using the collection of folders and items
  421. // returned in the XmlInventoryCollection.
  422. foreach (InventoryFolderBase folder in entity.Folders)
  423. {
  424. InventoryFolderBase found;
  425. // If the parentID is zero, then this folder is going
  426. // into the root folder identified by the URI. The requestor
  427. // may have already set the parent ID explicitly, in which
  428. // case we don't have to do it here.
  429. if (folder.ParentID == UUID.Zero || folder.ParentID == context.ID)
  430. {
  431. if (newnode != String.Empty)
  432. {
  433. Rest.Log.DebugFormat("{0} Too many resources", MsgId);
  434. rdata.Fail(Rest.HttpStatusCodeBadRequest, "only one root entity is allowed");
  435. }
  436. folder.ParentID = context.ID;
  437. newnode = folder.Name;
  438. }
  439. // Search the existing inventory for an existing entry. If
  440. // we have one, we need to decide if it has really changed.
  441. // It could just be present as (unnecessary) context, and we
  442. // don't want to waste time updating the database in that
  443. // case, OR, it could be being moved from another location
  444. // in which case an update is most certainly necessary.
  445. found = null;
  446. foreach (InventoryFolderBase xf in rdata.folders)
  447. {
  448. // Compare identifying attribute
  449. if (xf.ID == folder.ID)
  450. {
  451. found = xf;
  452. break;
  453. }
  454. }
  455. if (found != null && FolderHasChanged(folder,found))
  456. {
  457. Rest.Log.DebugFormat("{0} Updating existing folder", MsgId);
  458. Rest.InventoryServices.MoveFolder(folder);
  459. modified = true;
  460. rdata.appendStatus(String.Format("<p> Created folder {0}, UUID {1} <p>",
  461. folder.Name, folder.ID));
  462. }
  463. else
  464. {
  465. Rest.Log.DebugFormat("{0} Adding new folder", MsgId);
  466. Rest.InventoryServices.AddFolder(folder);
  467. created = true;
  468. rdata.appendStatus(String.Format("<p> Modified folder {0}, UUID {1} <p>",
  469. folder.Name, folder.ID));
  470. }
  471. }
  472. // Now we repeat a similar process for the items included
  473. // in the entity.
  474. foreach (InventoryItemBase item in entity.Items)
  475. {
  476. InventoryItemBase found = null;
  477. // If the parentID is zero, then this is going
  478. // directly into the root identified by the URI.
  479. if (item.Folder == UUID.Zero)
  480. {
  481. item.Folder = context.ID;
  482. }
  483. // Determine whether this is a new item or a
  484. // replacement definition.
  485. foreach (InventoryItemBase xi in rdata.items)
  486. {
  487. // Compare identifying attribute
  488. if (xi.ID == item.ID)
  489. {
  490. found = xi;
  491. break;
  492. }
  493. }
  494. if (found != null && ItemHasChanged(item, found))
  495. {
  496. Rest.Log.DebugFormat("{0} Updating item {1} {2} {3} {4} {5}",
  497. MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
  498. Rest.InventoryServices.UpdateItem(item);
  499. modified = true;
  500. rdata.appendStatus(String.Format("<p> Modified item {0}, UUID {1} <p>", item.Name, item.ID));
  501. }
  502. else
  503. {
  504. Rest.Log.DebugFormat("{0} Adding item {1} {2} {3} {4} {5}",
  505. MsgId, item.ID, item.AssetID, item.InvType, item.AssetType, item.Name);
  506. Rest.InventoryServices.AddItem(item);
  507. created = true;
  508. rdata.appendStatus(String.Format("<p> Created item {0}, UUID {1} <p>", item.Name, item.ID));
  509. }
  510. }
  511. if (created)
  512. {
  513. // Must include a location header with a URI that identifies the new resource.
  514. rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}/{3}",
  515. rdata.hostname, rdata.port,rdata.path,newnode));
  516. rdata.Complete(Rest.HttpStatusCodeCreated);
  517. }
  518. else
  519. {
  520. if (modified)
  521. {
  522. rdata.Complete(Rest.HttpStatusCodeOK);
  523. }
  524. else
  525. {
  526. rdata.Complete(Rest.HttpStatusCodeNoContent);
  527. }
  528. }
  529. rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method));
  530. }
  531. else
  532. {
  533. Rest.Log.DebugFormat("{0} {1}: Resource {2} is not a valid context: {3}",
  534. MsgId, rdata.method, rdata.path, InventoryNode.GetType());
  535. rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid resource context");
  536. }
  537. }
  538. /// <summary>
  539. /// PUT updates the URI-identified element in the inventory. This
  540. /// is actually far more flexible than it might at first sound. For
  541. /// PUT the URI serves two purposes:
  542. /// [1] It identifies the user whose inventory is to be
  543. /// processed.
  544. /// [2] It optionally specifies a subtree of the inventory
  545. /// that is to be used to resolve any relative subtree
  546. /// specifications in the entity. If nothing is specified
  547. /// then the whole of the private inventory is implied.
  548. /// Please note that the subtree specified by the URI is only relevant
  549. /// to an entity containing a URI relative specification, i.e. one or
  550. /// more elements do not specify parent folder information. These
  551. /// elements will be implicitly referenced within the context identified
  552. /// by the URI.
  553. /// If an element in the entity specifies an explicit parent folder, then
  554. /// that parent is effective, regardless of any value specified in the
  555. /// URI. If the parent does not exist, then the element, and any dependent
  556. /// elements, are ignored. This case is actually detected and handled
  557. /// during the reconstitution process.
  558. /// </summary>
  559. /// <param name=rdata>HTTP service request work area</param>
  560. private void DoUpdate(InventoryRequestData rdata)
  561. {
  562. int count = 0;
  563. bool created = false;
  564. bool modified = false;
  565. // Resolve the inventory node that is to be modified.
  566. // rdata already contains information about the current
  567. // content of the user's inventory.
  568. Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, Rest.Fill);
  569. // As long as we have a node, then we have something
  570. // meaningful to do, unlike POST. So we reconstitute the
  571. // subtree before doing anything else. Note that we
  572. // etiher got a valid node or we threw an exception.
  573. XmlInventoryCollection entity = ReconstituteEntity(rdata);
  574. // Incorporate any inlined assets first. Any failures
  575. // will terminate the request.
  576. if (entity.Assets.Count > 0)
  577. {
  578. Rest.Log.DebugFormat("{0} Adding {1} assets to server",
  579. MsgId, entity.Assets.Count);
  580. foreach (AssetBase asset in entity.Assets)
  581. {
  582. Rest.Log.DebugFormat("{0} Rest asset: {1} {2} {3}",
  583. MsgId, asset.ID, asset.Type, asset.Name);
  584. // The asset was validated during the collection process
  585. Rest.AssetServices.AddAsset(asset);
  586. created = true;
  587. rdata.appendStatus(String.Format("<p> Created asset {0}, UUID {1} <p>", asset.Name, asset.ID));
  588. if (Rest.DEBUG && Rest.DumpAsset)
  589. {
  590. Rest.Dump(asset.Data);
  591. }
  592. }
  593. }
  594. // The URI specifies either a folder or an item to be updated.
  595. //
  596. // The root node in the entity will replace the node identified
  597. // by the URI. This means the parent will remain the same, but
  598. // any or all attributes associated with the named element
  599. // will change.
  600. //
  601. // If the inventory collection contains an element with a zero
  602. // parent ID, then this is taken to be the replacement for the
  603. // named node. The collection MAY also specify an explicit
  604. // parent ID, in this case it MAY identify the same parent as
  605. // the current node, or it MAY specify a different parent,
  606. // indicating that the folder is being moved in addition to any
  607. // other modifications being made.
  608. if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
  609. typeof(InventoryFolderImpl) == InventoryNode.GetType())
  610. {
  611. bool rfound = false;
  612. InventoryFolderBase uri = (InventoryFolderBase) InventoryNode;
  613. InventoryFolderBase xml = null;
  614. // If the entity to be replaced resolved to be the root
  615. // directory itself (My Inventory), then make sure that
  616. // the supplied data include as appropriately typed and
  617. // named folder. Note that we can;t rule out the possibility
  618. // of a sub-directory being called "My Inventory", so that
  619. // is anticipated.
  620. if (uri == rdata.root)
  621. {
  622. foreach (InventoryFolderBase folder in entity.Folders)
  623. {
  624. if ((rfound = (folder.Name == PRIVATE_ROOT_NAME)))
  625. {
  626. if ((rfound = (folder.ParentID == UUID.Zero)))
  627. break;
  628. }
  629. }
  630. if (!rfound)
  631. {
  632. Rest.Log.DebugFormat("{0} {1}: Path <{2}> will result in loss of inventory",
  633. MsgId, rdata.method, rdata.path);
  634. rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid inventory structure");
  635. }
  636. }
  637. // Scan the set of folders in the entity collection for an
  638. // entry that matches the context folder. It is assumed that
  639. // the only reliable indicator of this is a zero UUID ( using
  640. // implicit context), or the parent's UUID matches that of the
  641. // URI designated node (explicit context). We don't allow
  642. // ambiguity in this case because this is POST and we are
  643. // supposed to be modifying a specific node.
  644. // We assign any element IDs required as an economy; we don't
  645. // want to iterate over the fodler set again if it can be
  646. // helped.
  647. foreach (InventoryFolderBase folder in entity.Folders)
  648. {
  649. if (folder.ParentID == uri.ParentID ||
  650. folder.ParentID == UUID.Zero)
  651. {
  652. folder.ParentID = uri.ParentID;
  653. xml = folder;
  654. count++;
  655. }
  656. }
  657. // More than one entry is ambiguous. Other folders should be
  658. // added using the POST verb.
  659. if (count > 1)
  660. {
  661. Rest.Log.DebugFormat("{0} {1}: Request for <{2}> is ambiguous",
  662. MsgId, rdata.method, rdata.path);
  663. rdata.Fail(Rest.HttpStatusCodeConflict, "context is ambiguous");
  664. }
  665. // Exactly one entry means we ARE replacing the node
  666. // identified by the URI. So we delete the old folder
  667. // by moving it to the trash and then purging it.
  668. // We then add all of the folders and items we
  669. // included in the entity. The subtree has been
  670. // modified.
  671. if (count == 1)
  672. {
  673. InventoryFolderBase TrashCan = GetTrashCan(rdata);
  674. // All went well, so we generate a UUID is one is
  675. // needed.
  676. if (xml.ID == UUID.Zero)
  677. {
  678. xml.ID = UUID.Random();
  679. }
  680. uri.ParentID = TrashCan.ID;
  681. Rest.InventoryServices.MoveFolder(uri);
  682. Rest.InventoryServices.PurgeFolder(TrashCan);
  683. modified = true;
  684. }
  685. // Now, regardelss of what they represent, we
  686. // integrate all of the elements in the entity.
  687. foreach (InventoryFolderBase f in entity.Folders)
  688. {
  689. rdata.appendStatus(String.Format("<p>Moving folder {0} UUID {1} <p>", f.Name, f.ID));
  690. Rest.InventoryServices.MoveFolder(f);
  691. }
  692. foreach (InventoryItemBase it in entity.Items)
  693. {
  694. rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", it.Name, it.ID));
  695. Rest.InventoryServices.AddItem(it);
  696. }
  697. }
  698. /// <summary>
  699. /// URI specifies an item to be updated
  700. /// </summary>
  701. /// <remarks>
  702. /// The entity must contain a single item node to be
  703. /// updated. ID and Folder ID must be correct.
  704. /// </remarks>
  705. else
  706. {
  707. InventoryItemBase uri = (InventoryItemBase) InventoryNode;
  708. InventoryItemBase xml = null;
  709. if (entity.Folders.Count != 0)
  710. {
  711. Rest.Log.DebugFormat("{0} {1}: Request should not contain any folders <{2}>",
  712. MsgId, rdata.method, rdata.path);
  713. rdata.Fail(Rest.HttpStatusCodeBadRequest, "folder is not allowed");
  714. }
  715. if (entity.Items.Count > 1)
  716. {
  717. Rest.Log.DebugFormat("{0} {1}: Entity contains too many items <{2}>",
  718. MsgId, rdata.method, rdata.path);
  719. rdata.Fail(Rest.HttpStatusCodeBadRequest, "too may items");
  720. }
  721. xml = entity.Items[0];
  722. if (xml.ID == UUID.Zero)
  723. {
  724. xml.ID = UUID.Random();
  725. }
  726. // If the folder reference has changed, then this item is
  727. // being moved. Otherwise we'll just delete the old, and
  728. // add in the new.
  729. // Delete the old item
  730. Rest.InventoryServices.DeleteItem(uri);
  731. // Add the new item to the inventory
  732. Rest.InventoryServices.AddItem(xml);
  733. rdata.appendStatus(String.Format("<p>Storing item {0} UUID {1} <p>", xml.Name, xml.ID));
  734. }
  735. if (created)
  736. {
  737. rdata.Complete(Rest.HttpStatusCodeCreated);
  738. }
  739. else
  740. {
  741. if (modified)
  742. {
  743. rdata.Complete(Rest.HttpStatusCodeOK);
  744. }
  745. else
  746. {
  747. rdata.Complete(Rest.HttpStatusCodeNoContent);
  748. }
  749. }
  750. rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method));
  751. }
  752. /// <summary>
  753. /// Arguably the most damaging REST interface. It deletes the inventory
  754. /// item or folder identified by the URI.
  755. ///
  756. /// We only process if the URI identified node appears to exist
  757. /// We do not test for success because we either get a context,
  758. /// or an exception is thrown.
  759. ///
  760. /// Folders are deleted by moving them to another folder and then
  761. /// purging that folder. We'll do that by creating a temporary
  762. /// sub-folder in the TrashCan and purging that folder's
  763. /// contents. If we can't can it, we don't delete it...
  764. /// So, if no trashcan is available, the request does nothing.
  765. /// Items are summarily deleted.
  766. ///
  767. /// In the interests of safety, a delete request should normally
  768. /// be performed using UUID, as a name might identify several
  769. /// elements.
  770. /// </summary>
  771. /// <param name=rdata>HTTP service request work area</param>
  772. private void DoDelete(InventoryRequestData rdata)
  773. {
  774. Object InventoryNode = getInventoryNode(rdata, rdata.root, PARM_PATH, false);
  775. if (typeof(InventoryFolderBase) == InventoryNode.GetType() ||
  776. typeof(InventoryFolderImpl) == InventoryNode.GetType())
  777. {
  778. InventoryFolderBase TrashCan = GetTrashCan(rdata);
  779. InventoryFolderBase folder = (InventoryFolderBase) InventoryNode;
  780. Rest.Log.DebugFormat("{0} {1}: Folder {2} will be deleted",
  781. MsgId, rdata.method, rdata.path);
  782. folder.ParentID = TrashCan.ID;
  783. Rest.InventoryServices.MoveFolder(folder);
  784. Rest.InventoryServices.PurgeFolder(TrashCan);
  785. rdata.appendStatus(String.Format("<p>Deleted folder {0} UUID {1} <p>", folder.Name, folder.ID));
  786. }
  787. // Deleting items is much more straight forward.
  788. else
  789. {
  790. InventoryItemBase item = (InventoryItemBase) InventoryNode;
  791. Rest.Log.DebugFormat("{0} {1}: Item {2} will be deleted",
  792. MsgId, rdata.method, rdata.path);
  793. Rest.InventoryServices.DeleteItem(item);
  794. rdata.appendStatus(String.Format("<p>Deleted item {0} UUID {1} <p>", item.Name, item.ID));
  795. }
  796. rdata.Complete();
  797. rdata.Respond(String.Format("Profile {0} : Normal completion", rdata.method));
  798. }
  799. #endregion method-specific processing
  800. /// <summary>
  801. /// This method is called to obtain the OpenSim inventory object identified
  802. /// by the supplied URI. This may be either an Item or a Folder, so a suitably
  803. /// ambiguous return type is employed (Object). This method recurses as
  804. /// necessary to process the designated hierarchy.
  805. ///
  806. /// If we reach the end of the URI then we return the contextual folder to
  807. /// our caller.
  808. ///
  809. /// If we are not yet at the end of the URI we attempt to find a child folder
  810. /// and if we succeed we recurse.
  811. ///
  812. /// If this is the last node, then we look to see if this is an item. If it is,
  813. /// we return that item.
  814. ///
  815. /// If we reach the end of an inventory path and the URI si not yet exhausted,
  816. /// then if 'fill' is specified, we create the intermediate nodes.
  817. ///
  818. /// Otherwise we fail the request on the ground of an invalid URI.
  819. ///
  820. /// An ambiguous request causes the request to fail.
  821. ///
  822. /// </summary>
  823. /// <param name=rdata>HTTP service request work area</param>
  824. /// <param name=folder>The folder to be searched (parent)</param>
  825. /// <param name=pi>URI parameter index</param>
  826. /// <param name=fill>Should missing path members be created?</param>
  827. private Object getInventoryNode(InventoryRequestData rdata,
  828. InventoryFolderBase folder,
  829. int pi, bool fill)
  830. {
  831. InventoryFolderBase foundf = null;
  832. int fk = 0;
  833. Rest.Log.DebugFormat("{0} Searching folder {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
  834. // We have just run off the end of the parameter sequence
  835. if (pi >= rdata.Parameters.Length)
  836. {
  837. return folder;
  838. }
  839. // There are more names in the parameter sequence,
  840. // look for the folder named by param[pi] as a
  841. // child of the folder supplied as an argument.
  842. // Note that a UUID may have been supplied as the
  843. // identifier (it is the ONLY guaranteed unambiguous
  844. // option.
  845. if (rdata.folders != null)
  846. {
  847. foreach (InventoryFolderBase f in rdata.folders)
  848. {
  849. // Look for the present node in the directory list
  850. if (f.ParentID == folder.ID &&
  851. (f.Name == rdata.Parameters[pi] ||
  852. f.ID.ToString() == rdata.Parameters[pi]))
  853. {
  854. foundf = f;
  855. fk++;
  856. }
  857. }
  858. }
  859. // If more than one node matched, then the path, as specified
  860. // is ambiguous.
  861. if (fk > 1)
  862. {
  863. Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
  864. MsgId, rdata.method, rdata.path);
  865. rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous");
  866. }
  867. // If we find a match, then the method
  868. // increment the parameter index, and calls itself
  869. // passing the found folder as the new context.
  870. if (foundf != null)
  871. {
  872. return getInventoryNode(rdata, foundf, pi+1, fill);
  873. }
  874. // No folders that match. Perhaps this parameter identifies an item? If
  875. // it does, then it MUST also be the last name in the sequence.
  876. if (pi == rdata.Parameters.Length-1)
  877. {
  878. if (rdata.items != null)
  879. {
  880. int k = 0;
  881. InventoryItemBase li = null;
  882. foreach (InventoryItemBase i in rdata.items)
  883. {
  884. if (i.Folder == folder.ID &&
  885. (i.Name == rdata.Parameters[pi] ||
  886. i.ID.ToString() == rdata.Parameters[pi]))
  887. {
  888. li = i;
  889. k++;
  890. }
  891. }
  892. if (k == 1)
  893. {
  894. return li;
  895. }
  896. else if (k > 1)
  897. {
  898. Rest.Log.DebugFormat("{0} {1}: Request for {2} is ambiguous",
  899. MsgId, rdata.method, rdata.path);
  900. rdata.Fail(Rest.HttpStatusCodeConflict, "request is ambiguous");
  901. }
  902. }
  903. }
  904. // If fill is enabled, then we must create the missing intermediate nodes.
  905. // And of course, even this is not straightforward. All intermediate nodes
  906. // are obviously folders, but the last node may be a folder or an item.
  907. if (fill)
  908. {
  909. }
  910. // No fill, so abandon the request
  911. Rest.Log.DebugFormat("{0} {1}: Resource {2} not found",
  912. MsgId, rdata.method, rdata.path);
  913. rdata.Fail(Rest.HttpStatusCodeNotFound,
  914. String.Format("resource {0}:{1} not found", rdata.method, rdata.path));
  915. return null; /* Never reached */
  916. }
  917. /// <summary>
  918. /// This routine traverse the inventory's structure until the end-point identified
  919. /// in the URI is reached, the remainder of the inventory (if any) is then formatted
  920. /// and returned to the requestor.
  921. ///
  922. /// Note that this method is only interested in those folder that match elements of
  923. /// the URI supplied by the requestor, so once a match is fund, the processing does
  924. /// not need to consider any further elements.
  925. ///
  926. /// Only the last element in the URI should identify an item.
  927. /// </summary>
  928. /// <param name=rdata>HTTP service request work area</param>
  929. /// <param name=folder>The folder to be searched (parent)</param>
  930. /// <param name=pi>URI parameter index</param>
  931. private void traverse(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
  932. {
  933. Rest.Log.DebugFormat("{0} Traverse[initial] : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
  934. if (rdata.folders != null)
  935. {
  936. // If there was only one parameter (avatar name), then the entire
  937. // inventory is being requested.
  938. if (rdata.Parameters.Length == 1)
  939. {
  940. formatInventory(rdata, rdata.root, String.Empty);
  941. }
  942. // Has the client specified the root directory name explicitly?
  943. // if yes, then we just absorb the reference, because the folder
  944. // we start looking in for a match *is* the root directory. If there
  945. // are more parameters remaining we tarverse, otehrwise it's time
  946. // to format. Otherwise,we consider the "My Inventory" to be implied
  947. // and we just traverse normally.
  948. else if (folder.ID.ToString() == rdata.Parameters[pi] ||
  949. folder.Name == rdata.Parameters[pi])
  950. {
  951. // Length is -1 because the avatar name is a parameter
  952. if (pi<(rdata.Parameters.Length-1))
  953. {
  954. traverseInventory(rdata, folder, pi+1);
  955. }
  956. else
  957. {
  958. formatInventory(rdata, folder, String.Empty);
  959. }
  960. }
  961. else
  962. {
  963. traverseInventory(rdata, folder, pi);
  964. }
  965. return;
  966. }
  967. }
  968. /// <summary>
  969. /// This is the recursive method. I've separated them in this way so that
  970. /// we do not have to waste cycles on any first-case-only processing.
  971. /// </summary>
  972. private void traverseInventory(InventoryRequestData rdata, InventoryFolderBase folder, int pi)
  973. {
  974. int fk = 0;
  975. InventoryFolderBase ffound = null;
  976. InventoryItemBase ifound = null;
  977. Rest.Log.DebugFormat("{0} Traverse Folder : {1} {2} [{3}]", MsgId, folder.ID, folder.Name, pi);
  978. foreach (InventoryFolderBase f in rdata.folders)
  979. {
  980. if (f.ParentID == folder.ID &&
  981. (f.Name == rdata.Parameters[pi] ||
  982. f.ID.ToString() == rdata.Parameters[pi]))
  983. {
  984. fk++;
  985. ffound = f;
  986. }
  987. }
  988. // If this is the last element in the parameter sequence, then
  989. // it is reasonable to check for an item. All intermediate nodes
  990. // MUST be folders.
  991. if (pi == rdata.Parameters.Length-1)
  992. {
  993. // Only if there are any items, and there pretty much always are.
  994. if (rdata.items != null)
  995. {
  996. foreach (InventoryItemBase i in rdata.items)
  997. {
  998. if (i.Folder == folder.ID &&
  999. (i.Name == rdata.Parameters[pi] ||
  1000. i.ID.ToString() == rdata.Parameters[pi]))
  1001. {
  1002. fk++;
  1003. ifound = i;
  1004. }
  1005. }
  1006. }
  1007. }
  1008. if (fk == 1)
  1009. {
  1010. if (ffound != null)
  1011. {
  1012. if (pi < rdata.Parameters.Length-1)
  1013. {
  1014. traverseInventory(rdata, ffound, pi+1);
  1015. }
  1016. else
  1017. {
  1018. formatInventory(rdata, ffound, String.Empty);
  1019. }
  1020. return;
  1021. }
  1022. else
  1023. {
  1024. // Fetching an Item has a special significance. In this
  1025. // case we also want to fetch the associated asset.
  1026. // To make it interesting, we'll do this via redirection.
  1027. string asseturl = String.Format("http://{0}:{1}/{2}{3}{4}", rdata.hostname, rdata.port,
  1028. "admin/assets",Rest.UrlPathSeparator,ifound.AssetID.ToString());
  1029. rdata.Redirect(asseturl,Rest.PERMANENT);
  1030. Rest.Log.DebugFormat("{0} Never Reached", MsgId);
  1031. }
  1032. }
  1033. else if (fk > 1)
  1034. {
  1035. rdata.Fail(Rest.HttpStatusCodeConflict,
  1036. String.Format("ambiguous element ({0}) in path specified: <{1}>",
  1037. pi, rdata.path));
  1038. }
  1039. Rest.Log.DebugFormat("{0} Inventory does not contain item/folder: <{1}>",
  1040. MsgId, rdata.path);
  1041. rdata.Fail(Rest.HttpStatusCodeNotFound,String.Format("no such item/folder : {0}",
  1042. rdata.Parameters[pi]));
  1043. }
  1044. /// <summary>
  1045. /// This method generates XML that describes an instance of InventoryFolderBase.
  1046. /// It recurses as necessary to reflect a folder hierarchy, and calls formatItem
  1047. /// to generate XML for any items encountered along the way.
  1048. /// The indentation parameter is solely for the benefit of trace record
  1049. /// formatting.
  1050. /// </summary>
  1051. /// <param name=rdata>HTTP service request work area</param>
  1052. /// <param name=folder>The folder to be searched (parent)</param>
  1053. /// <param name=indent>pretty print indentation</param>
  1054. private void formatInventory(InventoryRequestData rdata, InventoryFolderBase folder, string indent)
  1055. {
  1056. if (Rest.DEBUG)
  1057. {
  1058. Rest.Log.DebugFormat("{0} Folder : {1} {2} {3} type = {4}",
  1059. MsgId, folder.ID, indent, folder.Name, folder.Type);
  1060. indent += "\t";
  1061. }
  1062. // Start folder item
  1063. rdata.writer.WriteStartElement(String.Empty,"Folder",String.Empty);
  1064. rdata.writer.WriteAttributeString("name",String.Empty,folder.Name);
  1065. rdata.writer.WriteAttributeString("uuid",String.Empty,folder.ID.ToString());
  1066. rdata.writer.WriteAttributeString("parent",String.Empty,folder.ParentID.ToString());
  1067. rdata.writer.WriteAttributeString("owner",String.Empty,folder.Owner.ToString());
  1068. rdata.writer.WriteAttributeString("type",String.Empty,folder.Type.ToString());
  1069. rdata.writer.WriteAttributeString("version",String.Empty,folder.Version.ToString());
  1070. if (rdata.folders != null)
  1071. {
  1072. foreach (InventoryFolderBase f in rdata.folders)
  1073. {
  1074. if (f.ParentID == folder.ID)
  1075. {
  1076. formatInventory(rdata, f, indent);
  1077. }
  1078. }
  1079. }
  1080. if (rdata.items != null)
  1081. {
  1082. foreach (InventoryItemBase i in rdata.items)
  1083. {
  1084. if (i.Folder == folder.ID)
  1085. {
  1086. formatItem(rdata, i, indent);
  1087. }
  1088. }
  1089. }
  1090. // End folder item
  1091. rdata.writer.WriteEndElement();
  1092. }
  1093. /// <summary>
  1094. /// This method generates XML that describes an instance of InventoryItemBase.
  1095. /// </summary>
  1096. /// <param name="rdata">HTTP service request work area</param>
  1097. /// <param name="i">The item to be formatted</param>
  1098. /// <param name="indent">Pretty print indentation</param>
  1099. private void formatItem(InventoryRequestData rdata, InventoryItemBase i, string indent)
  1100. {
  1101. Rest.Log.DebugFormat("{0} Item : {1} {2} {3} Type = {4}, AssetType = {5}",
  1102. MsgId, i.ID, indent, i.Name, i.InvType, i.AssetType);
  1103. rdata.writer.WriteStartElement(String.Empty, "Item", String.Empty);
  1104. rdata.writer.WriteAttributeString("name", String.Empty, i.Name);
  1105. rdata.writer.WriteAttributeString("desc", String.Empty, i.Description);
  1106. rdata.writer.WriteAttributeString("uuid", String.Empty, i.ID.ToString());
  1107. rdata.writer.WriteAttributeString("folder", String.Empty, i.Folder.ToString());
  1108. rdata.writer.WriteAttributeString("owner", String.Empty, i.Owner.ToString());
  1109. rdata.writer.WriteAttributeString("creator", String.Empty, i.Creator.ToString());
  1110. rdata.writer.WriteAttributeString("creationdate", String.Empty, i.CreationDate.ToString());
  1111. rdata.writer.WriteAttributeString("invtype", String.Empty, i.InvType.ToString());
  1112. rdata.writer.WriteAttributeString("assettype", String.Empty, i.AssetType.ToString());
  1113. rdata.writer.WriteAttributeString("groupowned", String.Empty, i.GroupOwned.ToString());
  1114. rdata.writer.WriteAttributeString("groupid", String.Empty, i.GroupID.ToString());
  1115. rdata.writer.WriteAttributeString("saletype", String.Empty, i.SaleType.ToString());
  1116. rdata.writer.WriteAttributeString("saleprice", String.Empty, i.SalePrice.ToString());
  1117. rdata.writer.WriteAttributeString("flags", String.Empty, i.Flags.ToString());
  1118. rdata.writer.WriteStartElement(String.Empty, "Permissions", String.Empty);
  1119. rdata.writer.WriteAttributeString("current", String.Empty, i.CurrentPermissions.ToString("X"));
  1120. rdata.writer.WriteAttributeString("next", String.Empty, i.NextPermissions.ToString("X"));
  1121. rdata.writer.WriteAttributeString("group", String.Empty, i.GroupPermissions.ToString("X"));
  1122. rdata.writer.WriteAttributeString("everyone", String.Empty, i.EveryOnePermissions.ToString("X"));
  1123. rdata.writer.WriteAttributeString("base", String.Empty, i.BasePermissions.ToString("X"));
  1124. rdata.writer.WriteEndElement();
  1125. rdata.writer.WriteElementString("Asset", i.AssetID.ToString());
  1126. rdata.writer.WriteEndElement();
  1127. }
  1128. /// <summary>
  1129. /// This method creates a "trashcan" folder to support folder and item
  1130. /// deletions by this interface. The xisting trash folder is found and
  1131. /// this folder is created within it. It is called "tmp" to indicate to
  1132. /// the client that it is OK to delete this folder. The REST interface
  1133. /// will recreate the folder on an as-required basis.
  1134. /// If the trash can cannot be created, then by implication the request
  1135. /// that required it cannot be completed, and it fails accordingly.
  1136. /// </summary>
  1137. /// <param name=rdata>HTTP service request work area</param>
  1138. private InventoryFolderBase GetTrashCan(InventoryRequestData rdata)
  1139. {
  1140. InventoryFolderBase TrashCan = null;
  1141. foreach (InventoryFolderBase f in rdata.folders)
  1142. {
  1143. if (f.Name == "Trash")
  1144. {
  1145. foreach (InventoryFolderBase t in rdata.folders)
  1146. {
  1147. if (t.Name == "tmp")
  1148. {
  1149. TrashCan = t;
  1150. }
  1151. }
  1152. if (TrashCan == null)
  1153. {
  1154. TrashCan = new InventoryFolderBase();
  1155. TrashCan.Name = "tmp";
  1156. TrashCan.ID = UUID.Random();
  1157. TrashCan.Version = 1;
  1158. TrashCan.Type = (short) AssetType.TrashFolder;
  1159. TrashCan.ParentID = f.ID;
  1160. TrashCan.Owner = f.Owner;
  1161. Rest.InventoryServices.AddFolder(TrashCan);
  1162. }
  1163. }
  1164. }
  1165. if (TrashCan == null)
  1166. {
  1167. Rest.Log.DebugFormat("{0} No Trash Can available", MsgId);
  1168. rdata.Fail(Rest.HttpStatusCodeServerError, "unable to create trash can");
  1169. }
  1170. return TrashCan;
  1171. }
  1172. /// <summary>
  1173. /// Make sure that an unchanged folder is not unnecessarily
  1174. /// processed.
  1175. /// </summary>
  1176. /// <param name=newf>Folder obtained from enclosed entity</param>
  1177. /// <param name=oldf>Folder obtained from the user's inventory</param>
  1178. private bool FolderHasChanged(InventoryFolderBase newf, InventoryFolderBase oldf)
  1179. {
  1180. return (newf.Name != oldf.Name
  1181. || newf.ParentID != oldf.ParentID
  1182. || newf.Owner != oldf.Owner
  1183. || newf.Type != oldf.Type
  1184. || newf.Version != oldf.Version
  1185. );
  1186. }
  1187. /// <summary>
  1188. /// Make sure that an unchanged item is not unnecessarily
  1189. /// processed.
  1190. /// </summary>
  1191. /// <param name=newf>Item obtained from enclosed entity</param>
  1192. /// <param name=oldf>Item obtained from the user's inventory</param>
  1193. private bool ItemHasChanged(InventoryItemBase newf, InventoryItemBase oldf)
  1194. {
  1195. return (newf.Name != oldf.Name
  1196. || newf.Folder != oldf.Folder
  1197. || newf.Description != oldf.Description
  1198. || newf.Owner != oldf.Owner
  1199. || newf.Creator != oldf.Creator
  1200. || newf.AssetID != oldf.AssetID
  1201. || newf.GroupID != oldf.GroupID
  1202. || newf.GroupOwned != oldf.GroupOwned
  1203. || newf.InvType != oldf.InvType
  1204. || newf.AssetType != oldf.AssetType
  1205. );
  1206. }
  1207. /// <summary>
  1208. /// This method is called by PUT and POST to create an XmlInventoryCollection
  1209. /// instance that reflects the content of the entity supplied on the request.
  1210. /// Any elements in the completed collection whose UUID is zero, are
  1211. /// considered to be located relative to the end-point identified int he
  1212. /// URI. In this way, an entire sub-tree can be conveyed in a single REST
  1213. /// PUT or POST request.
  1214. ///
  1215. /// A new instance of XmlInventoryCollection is created and, if the request
  1216. /// has an entity, it is more completely initialized. thus, if no entity was
  1217. /// provided the collection is valid, but empty.
  1218. ///
  1219. /// The entity is then scanned and each tag is processed to produce the
  1220. /// appropriate inventory elements. At the end f the scan, teh XmlInventoryCollection
  1221. /// will reflect the subtree described by the entity.
  1222. ///
  1223. /// This is a very flexible mechanism, the entity may contain arbitrary,
  1224. /// discontiguous tree fragments, or may contain single element. The caller is
  1225. /// responsible for integrating this collection (and ensuring that any
  1226. /// missing parent IDs are resolved).
  1227. /// </summary>
  1228. /// <param name=rdata>HTTP service request work area</param>
  1229. internal XmlInventoryCollection ReconstituteEntity(InventoryRequestData rdata)
  1230. {
  1231. Rest.Log.DebugFormat("{0} Reconstituting entity", MsgId);
  1232. XmlInventoryCollection ic = new XmlInventoryCollection();
  1233. if (rdata.request.HasEntityBody)
  1234. {
  1235. Rest.Log.DebugFormat("{0} Entity present", MsgId);
  1236. ic.init(rdata);
  1237. try
  1238. {
  1239. while (ic.xml.Read())
  1240. {
  1241. switch (ic.xml.NodeType)
  1242. {
  1243. case XmlNodeType.Element:
  1244. Rest.Log.DebugFormat("{0} StartElement: <{1}>",
  1245. MsgId, ic.xml.Name);
  1246. switch (ic.xml.Name)
  1247. {
  1248. case "Folder":
  1249. Rest.Log.DebugFormat("{0} Processing {1} element",
  1250. MsgId, ic.xml.Name);
  1251. CollectFolder(ic);
  1252. break;
  1253. case "Item":
  1254. Rest.Log.DebugFormat("{0} Processing {1} element",
  1255. MsgId, ic.xml.Name);
  1256. CollectItem(ic);
  1257. break;
  1258. case "Asset":
  1259. Rest.Log.DebugFormat("{0} Processing {1} element",
  1260. MsgId, ic.xml.Name);
  1261. CollectAsset(ic);
  1262. break;
  1263. case "Permissions":
  1264. Rest.Log.DebugFormat("{0} Processing {1} element",
  1265. MsgId, ic.xml.Name);
  1266. CollectPermissions(ic);
  1267. break;
  1268. default:
  1269. Rest.Log.DebugFormat("{0} Ignoring {1} element",
  1270. MsgId, ic.xml.Name);
  1271. break;
  1272. }
  1273. // This stinks, but the ReadElement call above not only reads
  1274. // the imbedded data, but also consumes the end tag for Asset
  1275. // and moves the element pointer on to the containing Item's
  1276. // element-end, however, if there was a permissions element
  1277. // following, it would get us to the start of that..
  1278. if (ic.xml.NodeType == XmlNodeType.EndElement &&
  1279. ic.xml.Name == "Item")
  1280. {
  1281. Validate(ic);
  1282. }
  1283. break;
  1284. case XmlNodeType.EndElement :
  1285. switch (ic.xml.Name)
  1286. {
  1287. case "Folder":
  1288. Rest.Log.DebugFormat("{0} Completing {1} element",
  1289. MsgId, ic.xml.Name);
  1290. ic.Pop();
  1291. break;
  1292. case "Item":
  1293. Rest.Log.DebugFormat("{0} Completing {1} element",
  1294. MsgId, ic.xml.Name);
  1295. Validate(ic);
  1296. break;
  1297. case "Asset":
  1298. Rest.Log.DebugFormat("{0} Completing {1} element",
  1299. MsgId, ic.xml.Name);
  1300. break;
  1301. case "Permissions":
  1302. Rest.Log.DebugFormat("{0} Completing {1} element",
  1303. MsgId, ic.xml.Name);
  1304. break;
  1305. default:
  1306. Rest.Log.DebugFormat("{0} Ignoring {1} element",
  1307. MsgId, ic.xml.Name);
  1308. break;
  1309. }
  1310. break;
  1311. default:
  1312. Rest.Log.DebugFormat("{0} Ignoring: <{1}>:<{2}>",
  1313. MsgId, ic.xml.NodeType, ic.xml.Value);
  1314. break;
  1315. }
  1316. }
  1317. }
  1318. catch (XmlException e)
  1319. {
  1320. Rest.Log.WarnFormat("{0} XML parsing error: {1}", MsgId, e.Message);
  1321. throw e;
  1322. }
  1323. catch (Exception e)
  1324. {
  1325. Rest.Log.WarnFormat("{0} Unexpected XML parsing error: {1}", MsgId, e.Message);
  1326. throw e;
  1327. }
  1328. }
  1329. else
  1330. {
  1331. Rest.Log.DebugFormat("{0} Entity absent", MsgId);
  1332. }
  1333. if (Rest.DEBUG)
  1334. {
  1335. Rest.Log.DebugFormat("{0} Reconstituted entity", MsgId);
  1336. Rest.Log.DebugFormat("{0} {1} assets", MsgId, ic.Assets.Count);
  1337. Rest.Log.DebugFormat("{0} {1} folder", MsgId, ic.Folders.Count);
  1338. Rest.Log.DebugFormat("{0} {1} items", MsgId, ic.Items.Count);
  1339. }
  1340. return ic;
  1341. }
  1342. /// <summary>
  1343. /// This method creates an inventory Folder from the
  1344. /// information supplied in the request's entity.
  1345. /// A folder instance is created and initialized to reflect
  1346. /// default values. These values are then overridden
  1347. /// by information supplied in the entity.
  1348. /// If context was not explicitly provided, then the
  1349. /// appropriate ID values are determined.
  1350. /// </summary>
  1351. private void CollectFolder(XmlInventoryCollection ic)
  1352. {
  1353. Rest.Log.DebugFormat("{0} Interpret folder element", MsgId);
  1354. InventoryFolderBase result = new InventoryFolderBase();
  1355. // Default values
  1356. result.Name = String.Empty;
  1357. result.ID = UUID.Zero;
  1358. result.Owner = ic.UserID;
  1359. result.ParentID = UUID.Zero; // Context
  1360. result.Type = (short) AssetType.Folder;
  1361. result.Version = 1;
  1362. if (ic.xml.HasAttributes)
  1363. {
  1364. for (int i = 0; i < ic.xml.AttributeCount; i++)
  1365. {
  1366. ic.xml.MoveToAttribute(i);
  1367. switch (ic.xml.Name)
  1368. {
  1369. case "name":
  1370. result.Name = ic.xml.Value;
  1371. break;
  1372. case "uuid":
  1373. result.ID = new UUID(ic.xml.Value);
  1374. break;
  1375. case "parent":
  1376. result.ParentID = new UUID(ic.xml.Value);
  1377. break;
  1378. case "owner":
  1379. result.Owner = new UUID(ic.xml.Value);
  1380. break;
  1381. case "type":
  1382. result.Type = Int16.Parse(ic.xml.Value);
  1383. break;
  1384. case "version":
  1385. result.Version = UInt16.Parse(ic.xml.Value);
  1386. break;
  1387. default:
  1388. Rest.Log.DebugFormat("{0} Folder: unrecognized attribute: {1}:{2}",
  1389. MsgId, ic.xml.Name, ic.xml.Value);
  1390. ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute <{0}>",
  1391. ic.xml.Name));
  1392. break;
  1393. }
  1394. }
  1395. }
  1396. ic.xml.MoveToElement();
  1397. // The client is relying upon the reconstitution process
  1398. // to determine the parent's UUID based upon context. This
  1399. // is necessary where a new folder may have been
  1400. // introduced.
  1401. if (result.ParentID == UUID.Zero)
  1402. {
  1403. result.ParentID = ic.Parent();
  1404. }
  1405. else
  1406. {
  1407. bool found = false;
  1408. foreach (InventoryFolderBase parent in ic.rdata.folders)
  1409. {
  1410. if ( parent.ID == result.ParentID )
  1411. {
  1412. found = true;
  1413. break;
  1414. }
  1415. }
  1416. if (!found)
  1417. {
  1418. Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in folder {2}",
  1419. MsgId, ic.Item.Folder, result.ID);
  1420. ic.Fail(Rest.HttpStatusCodeBadRequest, "invalid parent");
  1421. }
  1422. }
  1423. // This is a new folder, so no existing UUID is available
  1424. // or appropriate
  1425. if (result.ID == UUID.Zero)
  1426. {
  1427. result.ID = UUID.Random();
  1428. }
  1429. // Treat this as a new context. Any other information is
  1430. // obsolete as a consequence.
  1431. ic.Push(result);
  1432. }
  1433. /// <summary>
  1434. /// This method is called to handle the construction of an Item
  1435. /// instance from the supplied request entity. It is called
  1436. /// whenever an Item start tag is detected.
  1437. /// An instance of an Item is created and initialized to default
  1438. /// values. These values are then overridden from values supplied
  1439. /// as attributes to the Item element.
  1440. /// This item is then stored in the XmlInventoryCollection and
  1441. /// will be verified by Validate.
  1442. /// All context is reset whenever the effective folder changes
  1443. /// or an item is successfully validated.
  1444. /// </summary>
  1445. private void CollectItem(XmlInventoryCollection ic)
  1446. {
  1447. Rest.Log.DebugFormat("{0} Interpret item element", MsgId);
  1448. InventoryItemBase result = new InventoryItemBase();
  1449. result.Name = String.Empty;
  1450. result.Description = String.Empty;
  1451. result.ID = UUID.Zero;
  1452. result.Folder = UUID.Zero;
  1453. result.Owner = ic.UserID;
  1454. result.Creator = ic.UserID;
  1455. result.AssetID = UUID.Zero;
  1456. result.GroupID = UUID.Zero;
  1457. result.GroupOwned = false;
  1458. result.InvType = (int) InventoryType.Unknown;
  1459. result.AssetType = (int) AssetType.Unknown;
  1460. if (ic.xml.HasAttributes)
  1461. {
  1462. for (int i = 0; i < ic.xml.AttributeCount; i++)
  1463. {
  1464. ic.xml.MoveToAttribute(i);
  1465. switch (ic.xml.Name)
  1466. {
  1467. case "name":
  1468. result.Name = ic.xml.Value;
  1469. break;
  1470. case "desc":
  1471. result.Description = ic.xml.Value;
  1472. break;
  1473. case "uuid":
  1474. result.ID = new UUID(ic.xml.Value);
  1475. break;
  1476. case "folder":
  1477. result.Folder = new UUID(ic.xml.Value);
  1478. break;
  1479. case "owner":
  1480. result.Owner = new UUID(ic.xml.Value);
  1481. break;
  1482. case "invtype":
  1483. result.InvType = Int32.Parse(ic.xml.Value);
  1484. break;
  1485. case "creator":
  1486. result.Creator = new UUID(ic.xml.Value);
  1487. break;
  1488. case "assettype":
  1489. result.AssetType = Int32.Parse(ic.xml.Value);
  1490. break;
  1491. case "groupowned":
  1492. result.GroupOwned = Boolean.Parse(ic.xml.Value);
  1493. break;
  1494. case "groupid":
  1495. result.GroupID = new UUID(ic.xml.Value);
  1496. break;
  1497. case "flags":
  1498. result.Flags = UInt32.Parse(ic.xml.Value);
  1499. break;
  1500. case "creationdate":
  1501. result.CreationDate = Int32.Parse(ic.xml.Value);
  1502. break;
  1503. case "saletype":
  1504. result.SaleType = Byte.Parse(ic.xml.Value);
  1505. break;
  1506. case "saleprice":
  1507. result.SalePrice = Int32.Parse(ic.xml.Value);
  1508. break;
  1509. default:
  1510. Rest.Log.DebugFormat("{0} Item: Unrecognized attribute: {1}:{2}",
  1511. MsgId, ic.xml.Name, ic.xml.Value);
  1512. ic.Fail(Rest.HttpStatusCodeBadRequest, String.Format("unrecognized attribute",
  1513. ic.xml.Name));
  1514. break;
  1515. }
  1516. }
  1517. }
  1518. ic.xml.MoveToElement();
  1519. ic.Push(result);
  1520. }
  1521. /// <summary>
  1522. /// This method assembles an asset instance from the
  1523. /// information supplied in the request's entity. It is
  1524. /// called as a result of detecting a start tag for a
  1525. /// type of Asset.
  1526. /// The information is collected locally, and an asset
  1527. /// instance is created only if the basic XML parsing
  1528. /// completes successfully.
  1529. /// Default values for all parts of the asset are
  1530. /// established before overriding them from the supplied
  1531. /// XML.
  1532. /// If an asset has inline=true as an attribute, then
  1533. /// the element contains the data representing the
  1534. /// asset. This is saved as the data component.
  1535. /// inline=false means that the element's payload is
  1536. /// simply the UUID of the asset referenced by the
  1537. /// item being constructed.
  1538. /// An asset, if created is stored in the
  1539. /// XmlInventoryCollection
  1540. /// </summary>
  1541. private void CollectAsset(XmlInventoryCollection ic)
  1542. {
  1543. Rest.Log.DebugFormat("{0} Interpret asset element", MsgId);
  1544. string name = String.Empty;
  1545. string desc = String.Empty;
  1546. sbyte type = (sbyte) AssetType.Unknown;
  1547. bool temp = false;
  1548. bool local = false;
  1549. // This is not a persistent attribute
  1550. bool inline = false;
  1551. UUID uuid = UUID.Zero;
  1552. // Attribute is optional
  1553. if (ic.xml.HasAttributes)
  1554. {
  1555. for (int i = 0; i < ic.xml.AttributeCount; i++)
  1556. {
  1557. ic.xml.MoveToAttribute(i);
  1558. switch (ic.xml.Name)
  1559. {
  1560. case "name" :
  1561. name = ic.xml.Value;
  1562. break;
  1563. case "type" :
  1564. type = SByte.Parse(ic.xml.Value);
  1565. break;
  1566. case "description" :
  1567. desc = ic.xml.Value;
  1568. break;
  1569. case "temporary" :
  1570. temp = Boolean.Parse(ic.xml.Value);
  1571. break;
  1572. case "uuid" :
  1573. uuid = new UUID(ic.xml.Value);
  1574. break;
  1575. case "inline" :
  1576. inline = Boolean.Parse(ic.xml.Value);
  1577. break;
  1578. case "local" :
  1579. local = Boolean.Parse(ic.xml.Value);
  1580. break;
  1581. default :
  1582. Rest.Log.DebugFormat("{0} Asset: Unrecognized attribute: {1}:{2}",
  1583. MsgId, ic.xml.Name, ic.xml.Value);
  1584. ic.Fail(Rest.HttpStatusCodeBadRequest,
  1585. String.Format("unrecognized attribute <{0}>", ic.xml.Name));
  1586. break;
  1587. }
  1588. }
  1589. }
  1590. ic.xml.MoveToElement();
  1591. // If this is a reference to an existing asset, just store the
  1592. // asset ID into the item.
  1593. if (!inline)
  1594. {
  1595. if (ic.Item != null)
  1596. {
  1597. ic.Item.AssetID = new UUID(ic.xml.ReadElementContentAsString());
  1598. Rest.Log.DebugFormat("{0} Asset ID supplied: {1}", MsgId, ic.Item.AssetID);
  1599. }
  1600. else
  1601. {
  1602. Rest.Log.DebugFormat("{0} LLUID unimbedded asset must be inline", MsgId);
  1603. ic.Fail(Rest.HttpStatusCodeBadRequest, "no context for asset");
  1604. }
  1605. }
  1606. // Otherwise, generate an asset ID, store that into the item, and
  1607. // create an entry in the asset list for the inlined asset. But
  1608. // only if the size is non-zero.
  1609. else
  1610. {
  1611. AssetBase asset = null;
  1612. string b64string = null;
  1613. // Generate a UUID if none were given, and generally none should
  1614. // be. Ever.
  1615. if (uuid == UUID.Zero)
  1616. {
  1617. uuid = UUID.Random();
  1618. }
  1619. // Create AssetBase entity to hold the inlined asset
  1620. asset = new AssetBase(uuid, name);
  1621. asset.Description = desc;
  1622. asset.Type = type; // type == 0 == texture
  1623. asset.Local = local;
  1624. asset.Temporary = temp;
  1625. b64string = ic.xml.ReadElementContentAsString();
  1626. Rest.Log.DebugFormat("{0} Data length is {1}", MsgId, b64string.Length);
  1627. Rest.Log.DebugFormat("{0} Data content starts with: \n\t<{1}>", MsgId,
  1628. b64string.Substring(0, b64string.Length > 132 ? 132 : b64string.Length));
  1629. asset.Data = Convert.FromBase64String(b64string);
  1630. // Ensure the asset always has some kind of data component
  1631. if (asset.Data == null)
  1632. {
  1633. asset.Data = new byte[1];
  1634. }
  1635. // If this is in the context of an item, establish
  1636. // a link with the item in context.
  1637. if (ic.Item != null && ic.Item.AssetID == UUID.Zero)
  1638. {
  1639. ic.Item.AssetID = uuid;
  1640. }
  1641. ic.Push(asset);
  1642. }
  1643. }
  1644. /// <summary>
  1645. /// Store any permissions information provided by the request.
  1646. /// This overrides the default permissions set when the
  1647. /// XmlInventoryCollection object was created.
  1648. /// </summary>
  1649. private void CollectPermissions(XmlInventoryCollection ic)
  1650. {
  1651. if (ic.xml.HasAttributes)
  1652. {
  1653. for (int i = 0; i < ic.xml.AttributeCount; i++)
  1654. {
  1655. ic.xml.MoveToAttribute(i);
  1656. switch (ic.xml.Name)
  1657. {
  1658. case "current":
  1659. ic.CurrentPermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
  1660. break;
  1661. case "next":
  1662. ic.NextPermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
  1663. break;
  1664. case "group":
  1665. ic.GroupPermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
  1666. break;
  1667. case "everyone":
  1668. ic.EveryOnePermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
  1669. break;
  1670. case "base":
  1671. ic.BasePermissions = UInt32.Parse(ic.xml.Value, System.Globalization.NumberStyles.HexNumber);
  1672. break;
  1673. default:
  1674. Rest.Log.DebugFormat("{0} Permissions: invalid attribute {1}:{2}",
  1675. MsgId,ic.xml.Name, ic.xml.Value);
  1676. ic.Fail(Rest.HttpStatusCodeBadRequest,
  1677. String.Format("invalid attribute <{0}>", ic.xml.Name));
  1678. break;
  1679. }
  1680. }
  1681. }
  1682. ic.xml.MoveToElement();
  1683. }
  1684. /// <summary>
  1685. /// This method is called whenever an Item has been successfully
  1686. /// reconstituted from the request's entity.
  1687. /// It uses the information curren tin the XmlInventoryCollection
  1688. /// to complete the item's specification, including any implied
  1689. /// context and asset associations.
  1690. /// It fails the request if any necessary item or asset information
  1691. /// is missing.
  1692. /// </summary>
  1693. private void Validate(XmlInventoryCollection ic)
  1694. {
  1695. // There really should be an item present if we've
  1696. // called validate. So fail if there is not.
  1697. if (ic.Item == null)
  1698. {
  1699. Rest.Log.ErrorFormat("{0} Unable to parse request", MsgId);
  1700. ic.Fail(Rest.HttpStatusCodeBadRequest, "request parse error");
  1701. }
  1702. // Every item is required to have a name (via REST anyway)
  1703. if (ic.Item.Name == String.Empty)
  1704. {
  1705. Rest.Log.ErrorFormat("{0} An item name MUST be specified", MsgId);
  1706. ic.Fail(Rest.HttpStatusCodeBadRequest, "item name required");
  1707. }
  1708. // An item MUST have an asset ID. AssetID should never be zero
  1709. // here. It should always get set from the information stored
  1710. // when the Asset element was processed.
  1711. if (ic.Item.AssetID == UUID.Zero)
  1712. {
  1713. Rest.Log.ErrorFormat("{0} Unable to complete request", MsgId);
  1714. Rest.Log.InfoFormat("{0} Asset information is missing", MsgId);
  1715. ic.Fail(Rest.HttpStatusCodeBadRequest, "asset information required");
  1716. }
  1717. // If the item is new, then assign it an ID
  1718. if (ic.Item.ID == UUID.Zero)
  1719. {
  1720. ic.Item.ID = UUID.Random();
  1721. }
  1722. // If the context is being implied, obtain the current
  1723. // folder item's ID. If it was specified explicitly, make
  1724. // sure that theparent folder exists.
  1725. if (ic.Item.Folder == UUID.Zero)
  1726. {
  1727. ic.Item.Folder = ic.Parent();
  1728. }
  1729. else
  1730. {
  1731. bool found = false;
  1732. foreach (InventoryFolderBase parent in ic.rdata.folders)
  1733. {
  1734. if ( parent.ID == ic.Item.Folder )
  1735. {
  1736. found = true;
  1737. break;
  1738. }
  1739. }
  1740. if (!found)
  1741. {
  1742. Rest.Log.ErrorFormat("{0} Invalid parent ID ({1}) in item {2}",
  1743. MsgId, ic.Item.Folder, ic.Item.ID);
  1744. ic.Fail(Rest.HttpStatusCodeBadRequest, "parent information required");
  1745. }
  1746. }
  1747. // If this is an inline asset being constructed in the context
  1748. // of a new Item, then use the itm's name here too.
  1749. if (ic.Asset != null)
  1750. {
  1751. if (ic.Asset.Name == String.Empty)
  1752. ic.Asset.Name = ic.Item.Name;
  1753. if (ic.Asset.Description == String.Empty)
  1754. ic.Asset.Description = ic.Item.Description;
  1755. }
  1756. // Assign permissions
  1757. ic.Item.CurrentPermissions = ic.CurrentPermissions;
  1758. ic.Item.EveryOnePermissions = ic.EveryOnePermissions;
  1759. ic.Item.BasePermissions = ic.BasePermissions;
  1760. ic.Item.GroupPermissions = ic.GroupPermissions;
  1761. ic.Item.NextPermissions = ic.NextPermissions;
  1762. // If no type was specified for this item, we can attempt to
  1763. // infer something from the file type maybe. This is NOT as
  1764. // good as having type be specified in the XML.
  1765. if (ic.Item.AssetType == (int) AssetType.Unknown ||
  1766. ic.Item.InvType == (int) AssetType.Unknown)
  1767. {
  1768. Rest.Log.DebugFormat("{0} Attempting to infer item type", MsgId);
  1769. string[] parts = ic.Item.Name.Split(Rest.CA_PERIOD);
  1770. if (Rest.DEBUG)
  1771. {
  1772. for (int i = 0; i < parts.Length; i++)
  1773. {
  1774. Rest.Log.DebugFormat("{0} Name part {1} : {2}",
  1775. MsgId, i, parts[i]);
  1776. }
  1777. }
  1778. // If the associated item name is multi-part, then maybe
  1779. // the last part will indicate the item type - if we're
  1780. // lucky.
  1781. if (parts.Length > 1)
  1782. {
  1783. Rest.Log.DebugFormat("{0} File type is {1}",
  1784. MsgId, parts[parts.Length - 1]);
  1785. switch (parts[parts.Length - 1])
  1786. {
  1787. case "jpeg2000" :
  1788. case "jpeg-2000" :
  1789. case "jpg2000" :
  1790. case "jpg-2000" :
  1791. Rest.Log.DebugFormat("{0} Type {1} inferred",
  1792. MsgId, parts[parts.Length-1]);
  1793. if (ic.Item.AssetType == (int) AssetType.Unknown)
  1794. ic.Item.AssetType = (int) AssetType.ImageJPEG;
  1795. if (ic.Item.InvType == (int) AssetType.Unknown)
  1796. ic.Item.InvType = (int) AssetType.ImageJPEG;
  1797. break;
  1798. case "jpg" :
  1799. case "jpeg" :
  1800. Rest.Log.DebugFormat("{0} Type {1} inferred",
  1801. MsgId, parts[parts.Length - 1]);
  1802. if (ic.Item.AssetType == (int) AssetType.Unknown)
  1803. ic.Item.AssetType = (int) AssetType.ImageJPEG;
  1804. if (ic.Item.InvType == (int) AssetType.Unknown)
  1805. ic.Item.InvType = (int) AssetType.ImageJPEG;
  1806. break;
  1807. case "tga" :
  1808. if (parts[parts.Length - 2].IndexOf("_texture") != -1)
  1809. {
  1810. if (ic.Item.AssetType == (int) AssetType.Unknown)
  1811. ic.Item.AssetType = (int) AssetType.TextureTGA;
  1812. if (ic.Item.InvType == (int) AssetType.Unknown)
  1813. ic.Item.InvType = (int) AssetType.TextureTGA;
  1814. }
  1815. else
  1816. {
  1817. if (ic.Item.AssetType == (int) AssetType.Unknown)
  1818. ic.Item.AssetType = (int) AssetType.ImageTGA;
  1819. if (ic.Item.InvType == (int) AssetType.Unknown)
  1820. ic.Item.InvType = (int) AssetType.ImageTGA;
  1821. }
  1822. break;
  1823. default :
  1824. Rest.Log.DebugFormat("{0} Asset/Inventory type could not be inferred for {1}",
  1825. MsgId,ic.Item.Name);
  1826. break;
  1827. }
  1828. }
  1829. }
  1830. /// If this is a TGA remember the fact
  1831. if (ic.Item.AssetType == (int) AssetType.TextureTGA ||
  1832. ic.Item.AssetType == (int) AssetType.ImageTGA)
  1833. {
  1834. Bitmap temp;
  1835. Stream tgadata = new MemoryStream(ic.Asset.Data);
  1836. temp = LoadTGAClass.LoadTGA(tgadata);
  1837. ic.Asset.Data = OpenJPEG.EncodeFromImage(temp, true);
  1838. }
  1839. ic.reset();
  1840. }
  1841. #region Inventory RequestData extension
  1842. internal class InventoryRequestData : RequestData
  1843. {
  1844. /// <summary>
  1845. /// These are the inventory specific request/response state
  1846. /// extensions.
  1847. /// </summary>
  1848. internal UUID uuid = UUID.Zero;
  1849. internal bool HaveInventory = false;
  1850. internal ICollection<InventoryFolderImpl> folders = null;
  1851. internal ICollection<InventoryItemBase> items = null;
  1852. internal UserProfileData userProfile = null;
  1853. internal InventoryFolderBase root = null;
  1854. internal bool timeout = false;
  1855. internal System.Timers.Timer watchDog = new System.Timers.Timer();
  1856. internal InventoryRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
  1857. : base(request, response, prefix)
  1858. {
  1859. }
  1860. internal void startWD(double interval)
  1861. {
  1862. Rest.Log.DebugFormat("{0} Setting watchdog", MsgId);
  1863. watchDog.Elapsed += new ElapsedEventHandler(OnTimeOut);
  1864. watchDog.Interval = interval;
  1865. watchDog.AutoReset = false;
  1866. watchDog.Enabled = true;
  1867. watchDog.Start();
  1868. }
  1869. internal void stopWD()
  1870. {
  1871. Rest.Log.DebugFormat("{0} Reset watchdog", MsgId);
  1872. watchDog.Stop();
  1873. }
  1874. /// <summary>
  1875. /// This is the callback method required by the inventory watchdog. The
  1876. /// requestor issues an inventory request and then blocks until the
  1877. /// request completes, or this method signals the monitor.
  1878. /// </summary>
  1879. private void OnTimeOut(object sender, ElapsedEventArgs args)
  1880. {
  1881. Rest.Log.DebugFormat("{0} Asynchronous inventory update timed-out", MsgId);
  1882. // InventoryRequestData rdata = (InventoryRequestData) sender;
  1883. lock (this)
  1884. {
  1885. this.folders = null;
  1886. this.items = null;
  1887. this.HaveInventory = false;
  1888. this.timeout = true;
  1889. Monitor.Pulse(this);
  1890. }
  1891. }
  1892. /// <summary>
  1893. /// This is the callback method required by inventory services. The
  1894. /// requestor issues an inventory request and then blocks until this
  1895. /// method signals the monitor.
  1896. /// </summary>
  1897. internal void GetUserInventory(ICollection<InventoryFolderImpl> folders, ICollection<InventoryItemBase> items)
  1898. {
  1899. Rest.Log.DebugFormat("{0} Asynchronously updating inventory data", MsgId);
  1900. lock (this)
  1901. {
  1902. if (watchDog.Enabled)
  1903. {
  1904. this.stopWD();
  1905. }
  1906. this.folders = folders;
  1907. this.items = items;
  1908. this.HaveInventory = true;
  1909. this.timeout = false;
  1910. Monitor.Pulse(this);
  1911. }
  1912. }
  1913. }
  1914. #endregion Inventory RequestData extension
  1915. /// <summary>
  1916. /// This class is used to record and manage the hierarchy
  1917. /// constructed from the entity supplied in the request for
  1918. /// PUT and POST.
  1919. /// </summary>
  1920. internal class XmlInventoryCollection : InventoryCollection
  1921. {
  1922. internal InventoryRequestData rdata;
  1923. private Stack<InventoryFolderBase> stk;
  1924. internal List<AssetBase> Assets;
  1925. internal InventoryItemBase Item;
  1926. internal AssetBase Asset;
  1927. internal XmlReader xml;
  1928. internal /*static*/ const uint DefaultCurrent = 0x7FFFFFFF;
  1929. internal /*static*/ const uint DefaultNext = 0x82000;
  1930. internal /*static*/ const uint DefaultBase = 0x7FFFFFFF;
  1931. internal /*static*/ const uint DefaultEveryOne = 0x0;
  1932. internal /*static*/ const uint DefaultGroup = 0x0;
  1933. internal uint CurrentPermissions = 0x00;
  1934. internal uint NextPermissions = 0x00;
  1935. internal uint BasePermissions = 0x00;
  1936. internal uint EveryOnePermissions = 0x00;
  1937. internal uint GroupPermissions = 0x00;
  1938. internal XmlInventoryCollection()
  1939. {
  1940. Folders = new List<InventoryFolderBase>();
  1941. Items = new List<InventoryItemBase>();
  1942. Assets = new List<AssetBase>();
  1943. }
  1944. internal void init(InventoryRequestData p_rdata)
  1945. {
  1946. rdata = p_rdata;
  1947. UserID = rdata.uuid;
  1948. stk = new Stack<InventoryFolderBase>();
  1949. rdata.initXmlReader();
  1950. xml = rdata.reader;
  1951. initPermissions();
  1952. }
  1953. internal void initPermissions()
  1954. {
  1955. CurrentPermissions = DefaultCurrent;
  1956. NextPermissions = DefaultNext;
  1957. BasePermissions = DefaultBase;
  1958. GroupPermissions = DefaultGroup;
  1959. EveryOnePermissions = DefaultEveryOne;
  1960. }
  1961. internal UUID Parent()
  1962. {
  1963. if (stk.Count != 0)
  1964. {
  1965. return stk.Peek().ID;
  1966. }
  1967. else
  1968. {
  1969. return UUID.Zero;
  1970. }
  1971. }
  1972. internal void Push(InventoryFolderBase folder)
  1973. {
  1974. stk.Push(folder);
  1975. Folders.Add(folder);
  1976. reset();
  1977. }
  1978. internal void Push(InventoryItemBase item)
  1979. {
  1980. Item = item;
  1981. Items.Add(item);
  1982. }
  1983. internal void Push(AssetBase asset)
  1984. {
  1985. Asset = asset;
  1986. Assets.Add(asset);
  1987. }
  1988. internal void Pop()
  1989. {
  1990. stk.Pop();
  1991. reset();
  1992. }
  1993. internal void reset()
  1994. {
  1995. Item = null;
  1996. Asset = null;
  1997. initPermissions();
  1998. }
  1999. internal void Fail(int code, string addendum)
  2000. {
  2001. rdata.Fail(code, addendum);
  2002. }
  2003. }
  2004. }
  2005. }