RestInventoryServices.cs 76 KB

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