RestInventoryServices.cs 98 KB

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