RestInventoryServices.cs 97 KB

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