1
0

FetchInvDescHandler.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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.Linq;
  30. using System.Reflection;
  31. using System.Text;
  32. using log4net;
  33. using Nini.Config;
  34. using OpenMetaverse;
  35. using OpenMetaverse.StructuredData;
  36. using OpenSim.Framework;
  37. using OpenSim.Framework.Capabilities;
  38. using OpenSim.Framework.Servers.HttpServer;
  39. using OpenSim.Services.Interfaces;
  40. using OSDMap = OpenMetaverse.StructuredData.OSDMap;
  41. using OSDArray = OpenMetaverse.StructuredData.OSDArray;
  42. namespace OpenSim.Capabilities.Handlers
  43. {
  44. public class FetchInvDescHandler
  45. {
  46. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  47. private static readonly byte[] EmptyResponse = Util.UTF8NBGetbytes("<llsd><map><key>folders</key><array /></map></llsd>");
  48. private readonly IInventoryService m_InventoryService;
  49. private readonly ILibraryService m_LibraryService;
  50. private readonly UUID libOwner;
  51. private readonly IScene m_Scene;
  52. public FetchInvDescHandler(IInventoryService invService, ILibraryService libService, IScene s)
  53. {
  54. m_InventoryService = invService;
  55. if(libService != null && libService.LibraryRootFolder != null)
  56. {
  57. m_LibraryService = libService;
  58. libOwner = libService.LibraryRootFolder.Owner;
  59. }
  60. m_Scene = s;
  61. }
  62. public void FetchInventoryDescendentsRequest(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, ExpiringKey<UUID> BadRequests)
  63. {
  64. //m_log.DebugFormat("[XXX]: FetchInventoryDescendentsRequest in {0}, {1}", (m_Scene == null) ? "none" : m_Scene.Name, request);
  65. List<LLSDFetchInventoryDescendents> folders;
  66. List<UUID> bad_folders = new();
  67. try
  68. {
  69. OSDArray foldersrequested = null;
  70. OSD tmp = OSDParser.DeserializeLLSDXml(httpRequest.InputStream);
  71. httpRequest.InputStream.Dispose();
  72. OSDMap map = (OSDMap)tmp;
  73. if(map.TryGetValue("folders", out tmp) && tmp is OSDArray frtmp)
  74. foldersrequested = frtmp;
  75. if (foldersrequested is null || foldersrequested.Count == 0)
  76. {
  77. httpResponse.RawBuffer = EmptyResponse;
  78. return;
  79. }
  80. folders = new List<LLSDFetchInventoryDescendents>(foldersrequested.Count);
  81. for (int i = 0; i < foldersrequested.Count; i++)
  82. {
  83. OSDMap mfolder = foldersrequested[i] as OSDMap;
  84. UUID id = mfolder["folder_id"].AsUUID();
  85. if(BadRequests.ContainsKey(id))
  86. {
  87. bad_folders.Add(id);
  88. }
  89. else
  90. {
  91. LLSDFetchInventoryDescendents llsdRequest = new();
  92. try
  93. {
  94. llsdRequest.folder_id = id;
  95. llsdRequest.owner_id = mfolder["owner_id"].AsUUID();
  96. llsdRequest.sort_order = mfolder["sort_order"].AsInteger();
  97. llsdRequest.fetch_folders = mfolder["fetch_folders"].AsBoolean();
  98. llsdRequest.fetch_items = mfolder["fetch_items"].AsBoolean();
  99. }
  100. catch (Exception e)
  101. {
  102. m_log.Debug("[WEB FETCH INV DESC HANDLER]: caught exception doing OSD deserialize" + e.Message);
  103. continue;
  104. }
  105. folders.Add(llsdRequest);
  106. }
  107. }
  108. foldersrequested = null;
  109. map = null;
  110. }
  111. catch (Exception e)
  112. {
  113. m_log.Error("[FETCH INV DESC]: fail parsing request: " + e.Message);
  114. httpResponse.RawBuffer = EmptyResponse;
  115. return;
  116. }
  117. if (folders is null || folders.Count == 0)
  118. {
  119. if(bad_folders.Count == 0)
  120. {
  121. httpResponse.RawBuffer = EmptyResponse;
  122. return;
  123. }
  124. osUTF8 osu = OSUTF8Cached.Acquire();
  125. osu.AppendASCII("[WEB FETCH INV DESC HANDLER]: Unable to fetch folders owned by Unknown user:");
  126. int limit = 5;
  127. int count = 0;
  128. foreach (UUID bad in bad_folders)
  129. {
  130. if (BadRequests.ContainsKey(bad))
  131. continue;
  132. osu.Append((byte)' ');
  133. osu.AppendASCII(bad.ToString());
  134. ++count;
  135. if (--limit < 0)
  136. break;
  137. }
  138. if(count > 0)
  139. {
  140. if (limit < 0)
  141. osu.AppendASCII(" ...");
  142. m_log.Warn(osu.ToString());
  143. }
  144. osu.Clear();
  145. osu.AppendASCII("<llsd><map><key>folders</key><array /></map><map><key>bad_folders</key><array>");
  146. foreach (UUID bad in bad_folders)
  147. {
  148. osu.AppendASCII("<map><key>folder_id</key><uuid>");
  149. osu.AppendASCII(bad.ToString());
  150. osu.AppendASCII("</uuid><key>error</key><string>Unknown</string></map>");
  151. }
  152. osu.AppendASCII("</array></map></llsd>");
  153. httpResponse.RawBuffer = OSUTF8Cached.GetArrayAndRelease(osu);
  154. return;
  155. }
  156. UUID requester = folders[0].owner_id;
  157. List<InventoryCollection> invcollSet = Fetch(folders, bad_folders);
  158. //m_log.DebugFormat("[XXX]: Got {0} folders from a request of {1}", invcollSet.Count, folders.Count);
  159. int invcollSetCount = 0;
  160. if (invcollSet is not null)
  161. invcollSetCount = invcollSet.Count;
  162. osUTF8 lastresponse = LLSDxmlEncode2.Start();
  163. if (invcollSetCount > 0)
  164. {
  165. lastresponse.AppendASCII("<map><key>folders</key><array>");
  166. int i = 0;
  167. InventoryCollection thiscoll;
  168. for (i = 0; i < invcollSetCount; i++)
  169. {
  170. thiscoll = invcollSet[i];
  171. invcollSet[i] = null;
  172. LLSDxmlEncode2.AddMap(lastresponse);
  173. LLSDxmlEncode2.AddElem_folder_id(thiscoll.FolderID, lastresponse);
  174. LLSDxmlEncode2.AddElem_agent_id(thiscoll.OwnerID, lastresponse);
  175. LLSDxmlEncode2.AddElem_owner_id(thiscoll.OwnerID, lastresponse);
  176. LLSDxmlEncode2.AddElem("descendents", thiscoll.Descendents, lastresponse);
  177. LLSDxmlEncode2.AddElem_version(thiscoll.Version, lastresponse);
  178. if (thiscoll.Folders is null || thiscoll.Folders.Count == 0)
  179. LLSDxmlEncode2.AddEmptyArray("categories", lastresponse);
  180. else
  181. {
  182. LLSDxmlEncode2.AddArray("categories", lastresponse);
  183. foreach (InventoryFolderBase invFolder in thiscoll.Folders)
  184. {
  185. LLSDxmlEncode2.AddMap(lastresponse);
  186. LLSDxmlEncode2.AddElem_category_id(invFolder.ID, lastresponse);
  187. LLSDxmlEncode2.AddElem_parent_id(invFolder.ParentID, lastresponse);
  188. LLSDxmlEncode2.AddElem_name(invFolder.Name, lastresponse);
  189. LLSDxmlEncode2.AddElem("type_default", invFolder.Type, lastresponse);
  190. LLSDxmlEncode2.AddElem_version( invFolder.Version, lastresponse);
  191. LLSDxmlEncode2.AddEndMap(lastresponse);
  192. }
  193. LLSDxmlEncode2.AddEndArray(lastresponse);
  194. }
  195. if (thiscoll.Items is null || thiscoll.Items.Count == 0)
  196. LLSDxmlEncode2.AddEmptyArray("items", lastresponse);
  197. else
  198. {
  199. LLSDxmlEncode2.AddArray("items", lastresponse);
  200. foreach (InventoryItemBase invItem in thiscoll.Items)
  201. {
  202. invItem.ToLLSDxml(lastresponse);
  203. }
  204. LLSDxmlEncode2.AddEndArray(lastresponse);
  205. }
  206. LLSDxmlEncode2.AddEndMap(lastresponse);
  207. invcollSet[i] = null;
  208. }
  209. LLSDxmlEncode2.AddEndArrayAndMap(lastresponse);
  210. }
  211. else
  212. {
  213. lastresponse.AppendASCII("<map><key>folders</key><array /></map>");
  214. }
  215. if (bad_folders.Count > 0)
  216. {
  217. lastresponse.AppendASCII("<map><key>bad_folders</key><array>");
  218. foreach (UUID bad in bad_folders)
  219. {
  220. BadRequests.Add(bad);
  221. lastresponse.AppendASCII("<map><key>folder_id</key><uuid>");
  222. lastresponse.AppendASCII(bad.ToString());
  223. lastresponse.AppendASCII("</uuid><key>error</key><string>Unknown</string></map>");
  224. }
  225. lastresponse.AppendASCII("</array></map>");
  226. StringBuilder sb = osStringBuilderCache.Acquire();
  227. sb.Append("[WEB FETCH INV DESC HANDLER]: Unable to fetch folders owned by ");
  228. sb.Append(requester.ToString());
  229. sb.Append(" :");
  230. int limit = 9;
  231. foreach (UUID bad in bad_folders)
  232. {
  233. sb.Append(' ');
  234. sb.Append(bad.ToString());
  235. if(--limit < 0)
  236. break;
  237. }
  238. if(limit < 0)
  239. sb.Append(" ...");
  240. m_log.Warn(osStringBuilderCache.GetStringAndRelease(sb));
  241. }
  242. httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(lastresponse);
  243. }
  244. private void AddLibraryFolders(List<LLSDFetchInventoryDescendents> libFolders, List<InventoryCollection> result)
  245. {
  246. InventoryFolderImpl fold;
  247. if (m_LibraryService is null || m_LibraryService.LibraryRootFolder is null)
  248. return;
  249. foreach (LLSDFetchInventoryDescendents f in libFolders)
  250. {
  251. if ((fold = m_LibraryService.LibraryRootFolder.FindFolder(f.folder_id)) is not null)
  252. {
  253. InventoryCollection Collection = new();
  254. //ret.Collection.Folders = new List<InventoryFolderBase>();
  255. Collection.Folders = fold.RequestListOfFolders();
  256. Collection.Items = fold.RequestListOfItems();
  257. Collection.OwnerID = m_LibraryService.LibraryRootFolder.Owner;
  258. Collection.FolderID = f.folder_id;
  259. Collection.Version = fold.Version;
  260. Collection.Descendents = Collection.Items.Count + Collection.Folders.Count;
  261. result.Add(Collection);
  262. //m_log.DebugFormat("[XXX]: Added libfolder {0} ({1}) {2}", ret.Collection.FolderID, ret.Collection.OwnerID);
  263. }
  264. }
  265. }
  266. private List<InventoryCollection> Fetch(List<LLSDFetchInventoryDescendents> fetchFolders, List<UUID> bad_folders)
  267. {
  268. //m_log.DebugFormat(
  269. // "[WEB FETCH INV DESC HANDLER]: Fetching {0} folders for owner {1}", fetchFolders.Count, fetchFolders[0].owner_id);
  270. // FIXME MAYBE: We're not handling sortOrder!
  271. List<InventoryCollection> result = new(32);
  272. List<LLSDFetchInventoryDescendents> libFolders = new(32);
  273. List<LLSDFetchInventoryDescendents> otherFolders = new(32);
  274. HashSet<UUID> libIDs = new();
  275. HashSet<UUID> otherIDs = new();
  276. bool dolib = m_LibraryService != null;
  277. // Filter folder Zero right here. Some viewers (Firestorm) send request for folder Zero, which doesn't make sense
  278. // and can kill the sim (all root folders have parent_id Zero)
  279. // send something.
  280. bool doneZeroID = false;
  281. foreach(LLSDFetchInventoryDescendents f in fetchFolders)
  282. {
  283. if (f.folder_id.IsZero())
  284. {
  285. if(doneZeroID)
  286. continue;
  287. doneZeroID = true;
  288. InventoryCollection Collection = new InventoryCollection();
  289. Collection.OwnerID = f.owner_id;
  290. Collection.Version = 0;
  291. Collection.FolderID = f.folder_id;
  292. Collection.Descendents = 0;
  293. result.Add(Collection);
  294. continue;
  295. }
  296. if(dolib && f.owner_id.Equals(libOwner))
  297. {
  298. if(libIDs.Contains(f.folder_id))
  299. continue;
  300. libIDs.Add(f.folder_id);
  301. libFolders.Add(f);
  302. continue;
  303. }
  304. if(otherIDs.Contains(f.folder_id))
  305. continue;
  306. otherIDs.Add(f.folder_id);
  307. otherFolders.Add(f);
  308. }
  309. fetchFolders.Clear();
  310. if(otherFolders.Count > 0)
  311. {
  312. //m_log.DebugFormat("[XXX]: {0}", string.Join(",", fids));
  313. InventoryCollection[] fetchedContents = m_InventoryService.GetMultipleFoldersContent(otherFolders[0].owner_id, otherIDs.ToArray());
  314. if (fetchedContents is null)
  315. return null;
  316. if (fetchedContents.Length == 0)
  317. {
  318. foreach (LLSDFetchInventoryDescendents freq in otherFolders)
  319. BadFolder(freq, null, bad_folders);
  320. }
  321. else
  322. {
  323. int i = 0;
  324. // Do some post-processing. May need to fetch more from inv server for links
  325. foreach (InventoryCollection contents in fetchedContents)
  326. {
  327. // Find the original request
  328. LLSDFetchInventoryDescendents freq = otherFolders[i];
  329. otherFolders[i]=null;
  330. i++;
  331. if (BadFolder(freq, contents, bad_folders))
  332. continue;
  333. if(!freq.fetch_folders)
  334. contents.Folders.Clear();
  335. if(!freq.fetch_items)
  336. contents.Items.Clear();
  337. contents.Descendents = contents.Items.Count + contents.Folders.Count;
  338. // Next: link management
  339. ProcessLinks(freq, contents);
  340. result.Add(contents);
  341. }
  342. }
  343. }
  344. if(libFolders.Count > 0)
  345. AddLibraryFolders(libFolders, result);
  346. return result;
  347. }
  348. private bool BadFolder(LLSDFetchInventoryDescendents freq, InventoryCollection contents, List<UUID> bad_folders)
  349. {
  350. if (contents is null)
  351. {
  352. bad_folders.Add(freq.folder_id);
  353. return true;
  354. }
  355. // The inventory server isn't sending FolderID in the collection...
  356. // Must fetch it individually
  357. if (contents.FolderID.IsZero())
  358. {
  359. InventoryFolderBase containingFolder = m_InventoryService.GetFolder(freq.owner_id, freq.folder_id);
  360. if (containingFolder is null)
  361. {
  362. bad_folders.Add(freq.folder_id);
  363. return true;
  364. }
  365. contents.FolderID = containingFolder.ID;
  366. contents.OwnerID = containingFolder.Owner;
  367. contents.Version = containingFolder.Version;
  368. }
  369. return false;
  370. }
  371. private void ProcessLinks(LLSDFetchInventoryDescendents freq, InventoryCollection contents)
  372. {
  373. if (contents.Items is null || contents.Items.Count == 0)
  374. return;
  375. // viewers are lasy and want a copy of the linked item sent before the link to it
  376. // look for item links
  377. List<UUID> itemIDs = new();
  378. foreach (InventoryItemBase item in contents.Items)
  379. {
  380. //m_log.DebugFormat("[XXX]: {0} {1}", item.Name, item.AssetType);
  381. if (item.AssetType == (int)AssetType.Link)
  382. itemIDs.Add(item.AssetID);
  383. }
  384. // get the linked if any
  385. if (itemIDs.Count > 0)
  386. {
  387. InventoryItemBase[] linked = m_InventoryService.GetMultipleItems(freq.owner_id, itemIDs.ToArray());
  388. if (linked is not null)
  389. {
  390. List<InventoryItemBase> linkedItems = new List<InventoryItemBase>(linked.Length);
  391. // check for broken
  392. foreach (InventoryItemBase linkedItem in linked)
  393. {
  394. // Take care of genuinely broken links where the target doesn't exist
  395. // HACK: Also, don't follow up links that just point to other links. In theory this is legitimate,
  396. // but no viewer has been observed to set these up and this is the lazy way of avoiding cycles
  397. // rather than having to keep track of every folder requested in the recursion.
  398. if (linkedItem is not null && linkedItem.AssetType != (int)AssetType.Link)
  399. {
  400. linkedItems.Add(linkedItem);
  401. //m_log.DebugFormat("[WEB FETCH INV DESC HANDLER]: Added {0} {1} {2}", linkedItem.Name, linkedItem.AssetType, linkedItem.Folder);
  402. }
  403. }
  404. // insert them
  405. if(linkedItems.Count > 0)
  406. contents.Items.InsertRange(0, linkedItems);
  407. }
  408. }
  409. }
  410. }
  411. }