RestFileServices.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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.Xml;
  29. using System.IO;
  30. using OpenMetaverse;
  31. using OpenSim.Framework;
  32. using OpenSim.Framework.Servers;
  33. using OpenSim.Framework.Servers.HttpServer;
  34. namespace OpenSim.ApplicationPlugins.Rest.Inventory
  35. {
  36. public class RestFileServices : IRest
  37. {
  38. private bool enabled = false;
  39. private string qPrefix = "files";
  40. // A simple constructor is used to handle any once-only
  41. // initialization of working classes.
  42. public RestFileServices()
  43. {
  44. Rest.Log.InfoFormat("{0} File services initializing", MsgId);
  45. Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);
  46. // If the handler specifies a relative path for its domain
  47. // then we must add the standard absolute prefix, e.g. /admin
  48. if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
  49. {
  50. Rest.Log.InfoFormat("{0} Prefixing domain name ({1})", MsgId, qPrefix);
  51. qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
  52. Rest.Log.InfoFormat("{0} Fully qualified domain name is <{1}>", MsgId, qPrefix);
  53. }
  54. // Register interface using the fully-qualified prefix
  55. Rest.Plugin.AddPathHandler(DoFile, qPrefix, Allocate);
  56. // Activate if all went OK
  57. enabled = true;
  58. Rest.Log.InfoFormat("{0} File services initialization complete", MsgId);
  59. }
  60. // Post-construction, pre-enabled initialization opportunity
  61. // Not currently exploited.
  62. public void Initialize()
  63. {
  64. }
  65. // Called by the plug-in to halt REST processing. Local processing is
  66. // disabled, and control blocks until all current processing has
  67. // completed. No new processing will be started
  68. public void Close()
  69. {
  70. enabled = false;
  71. Rest.Log.InfoFormat("{0} File services ({1}) closing down", MsgId, qPrefix);
  72. }
  73. // Properties
  74. internal string MsgId
  75. {
  76. get { return Rest.MsgId; }
  77. }
  78. #region Interface
  79. private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
  80. {
  81. return (RequestData) new FileRequestData(request, response, prefix);
  82. }
  83. // Asset Handler
  84. private void DoFile(RequestData rparm)
  85. {
  86. if (!enabled) return;
  87. FileRequestData rdata = (FileRequestData) rparm;
  88. Rest.Log.DebugFormat("{0} REST File handler ({1}) ENTRY", MsgId, qPrefix);
  89. // Now that we know this is a serious attempt to
  90. // access file data, we should find out who
  91. // is asking, and make sure they are authorized
  92. // to do so. We need to validate the caller's
  93. // identity before revealing anything about the
  94. // status quo. Authenticate throws an exception
  95. // via Fail if no identity information is present.
  96. //
  97. // With the present HTTP server we can't use the
  98. // builtin authentication mechanisms because they
  99. // would be enforced for all in-bound requests.
  100. // Instead we look at the headers ourselves and
  101. // handle authentication directly.
  102. try
  103. {
  104. if (!rdata.IsAuthenticated)
  105. {
  106. rdata.Fail(Rest.HttpStatusCodeNotAuthorized, String.Format("user \"{0}\" could not be authenticated"));
  107. }
  108. }
  109. catch (RestException e)
  110. {
  111. if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
  112. {
  113. Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
  114. Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
  115. rdata.request.Headers.Get("Authorization"));
  116. }
  117. else
  118. {
  119. Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
  120. Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId,
  121. rdata.request.Headers.Get("Authorization"));
  122. }
  123. throw (e);
  124. }
  125. // Remove the prefix and what's left are the parameters. If we don't have
  126. // the parameters we need, fail the request. Parameters do NOT include
  127. // any supplied query values.
  128. if (rdata.Parameters.Length > 0)
  129. {
  130. switch (rdata.method)
  131. {
  132. case "get" :
  133. DoGet(rdata);
  134. break;
  135. case "put" :
  136. DoPut(rdata);
  137. break;
  138. case "post" :
  139. DoPost(rdata);
  140. break;
  141. case "delete" :
  142. DoDelete(rdata);
  143. break;
  144. default :
  145. Rest.Log.WarnFormat("{0} File: Method not supported: {1}",
  146. MsgId, rdata.method);
  147. rdata.Fail(Rest.HttpStatusCodeBadRequest,String.Format("method <{0}> not supported", rdata.method));
  148. break;
  149. }
  150. }
  151. else
  152. {
  153. Rest.Log.WarnFormat("{0} File: No agent information provided", MsgId);
  154. rdata.Fail(Rest.HttpStatusCodeBadRequest, "no agent information provided");
  155. }
  156. Rest.Log.DebugFormat("{0} REST File handler EXIT", MsgId);
  157. }
  158. #endregion Interface
  159. /// <summary>
  160. /// The only parameter we recognize is a UUID.If an asset with this identification is
  161. /// found, it's content, base-64 encoded, is returned to the client.
  162. /// </summary>
  163. private void DoGet(FileRequestData rdata)
  164. {
  165. string path = String.Empty;
  166. Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
  167. if (rdata.Parameters.Length > 1)
  168. {
  169. try
  170. {
  171. path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
  172. if (File.Exists(path))
  173. {
  174. Rest.Log.DebugFormat("{0} File located <{1}>", MsgId, path);
  175. Byte[] data = File.ReadAllBytes(path);
  176. rdata.initXmlWriter();
  177. rdata.writer.WriteStartElement(String.Empty,"File",String.Empty);
  178. rdata.writer.WriteAttributeString("name", path);
  179. rdata.writer.WriteBase64(data,0,data.Length);
  180. rdata.writer.WriteFullEndElement();
  181. }
  182. else
  183. {
  184. Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, path);
  185. rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0}", path));
  186. }
  187. }
  188. catch (Exception e)
  189. {
  190. Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, e.Message);
  191. rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
  192. path, e.Message));
  193. }
  194. }
  195. rdata.Complete();
  196. rdata.Respond(String.Format("File <{0}> : Normal completion", rdata.method));
  197. }
  198. /// <summary>
  199. /// UPDATE existing item, if it exists. URI identifies the item in question.
  200. /// The only parameter we recognize is a UUID. The enclosed asset data (base-64 encoded)
  201. /// is decoded and stored in the database, identified by the supplied UUID.
  202. /// </summary>
  203. private void DoPut(FileRequestData rdata)
  204. {
  205. bool modified = false;
  206. bool created = false;
  207. string path = String.Empty;
  208. Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
  209. if (rdata.Parameters.Length > 1)
  210. {
  211. try
  212. {
  213. path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
  214. bool maymod = File.Exists(path);
  215. rdata.initXmlReader();
  216. XmlReader xml = rdata.reader;
  217. if (!xml.ReadToFollowing("File"))
  218. {
  219. Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
  220. rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
  221. }
  222. Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
  223. File.WriteAllBytes(path,data);
  224. modified = maymod;
  225. created = ! maymod;
  226. }
  227. catch (Exception e)
  228. {
  229. Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
  230. e.Message);
  231. }
  232. }
  233. else
  234. {
  235. Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
  236. rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
  237. }
  238. if (created)
  239. {
  240. rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
  241. rdata.Complete(Rest.HttpStatusCodeCreated);
  242. }
  243. else
  244. {
  245. if (modified)
  246. {
  247. rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
  248. rdata.Complete(Rest.HttpStatusCodeOK);
  249. }
  250. else
  251. {
  252. rdata.Complete(Rest.HttpStatusCodeNoContent);
  253. }
  254. }
  255. rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
  256. }
  257. /// <summary>
  258. /// CREATE new item, replace if it exists. URI identifies the context for the item in question.
  259. /// No parameters are required for POST, just thepayload.
  260. /// </summary>
  261. private void DoPost(FileRequestData rdata)
  262. {
  263. bool modified = false;
  264. bool created = false;
  265. string path = String.Empty;
  266. Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
  267. if (rdata.Parameters.Length > 1)
  268. {
  269. try
  270. {
  271. path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
  272. bool maymod = File.Exists(path);
  273. rdata.initXmlReader();
  274. XmlReader xml = rdata.reader;
  275. if (!xml.ReadToFollowing("File"))
  276. {
  277. Rest.Log.DebugFormat("{0} Invalid request data: <{1}>", MsgId, rdata.path);
  278. rdata.Fail(Rest.HttpStatusCodeBadRequest,"invalid request data");
  279. }
  280. Byte[] data = Convert.FromBase64String(xml.ReadElementContentAsString("File", ""));
  281. File.WriteAllBytes(path,data);
  282. modified = maymod;
  283. created = ! maymod;
  284. }
  285. catch (Exception e)
  286. {
  287. Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
  288. e.Message);
  289. }
  290. }
  291. else
  292. {
  293. Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
  294. rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
  295. }
  296. if (created)
  297. {
  298. rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
  299. rdata.Complete(Rest.HttpStatusCodeCreated);
  300. }
  301. else
  302. {
  303. if (modified)
  304. {
  305. rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
  306. rdata.Complete(Rest.HttpStatusCodeOK);
  307. }
  308. else
  309. {
  310. rdata.Complete(Rest.HttpStatusCodeNoContent);
  311. }
  312. }
  313. rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
  314. }
  315. /// <summary>
  316. /// CREATE new item, replace if it exists. URI identifies the context for the item in question.
  317. /// No parameters are required for POST, just thepayload.
  318. /// </summary>
  319. private void DoDelete(FileRequestData rdata)
  320. {
  321. bool modified = false;
  322. bool created = false;
  323. string path = String.Empty;
  324. Rest.Log.DebugFormat("{0} REST File handler, Method = <{1}> ENTRY", MsgId, rdata.method);
  325. if (rdata.Parameters.Length > 1)
  326. {
  327. try
  328. {
  329. path = rdata.path.Substring(rdata.Parameters[0].Length+qPrefix.Length+2);
  330. if (File.Exists(path))
  331. {
  332. File.Delete(path);
  333. }
  334. }
  335. catch (Exception e)
  336. {
  337. Rest.Log.DebugFormat("{0} Exception during file processing : {1}", MsgId,
  338. e.Message);
  339. rdata.Fail(Rest.HttpStatusCodeNotFound, String.Format("invalid parameters : {0} {1}",
  340. path, e.Message));
  341. }
  342. }
  343. else
  344. {
  345. Rest.Log.DebugFormat("{0} Invalid parameters: <{1}>", MsgId, rdata.path);
  346. rdata.Fail(Rest.HttpStatusCodeNotFound, "invalid parameters");
  347. }
  348. if (created)
  349. {
  350. rdata.appendStatus(String.Format("<p> Created file {0} <p>", path));
  351. rdata.Complete(Rest.HttpStatusCodeCreated);
  352. }
  353. else
  354. {
  355. if (modified)
  356. {
  357. rdata.appendStatus(String.Format("<p> Modified file {0} <p>", path));
  358. rdata.Complete(Rest.HttpStatusCodeOK);
  359. }
  360. else
  361. {
  362. rdata.Complete(Rest.HttpStatusCodeNoContent);
  363. }
  364. }
  365. rdata.Respond(String.Format("File {0} : Normal completion", rdata.method));
  366. }
  367. /// <summary>
  368. /// File processing has no special data area requirements.
  369. /// </summary>
  370. internal class FileRequestData : RequestData
  371. {
  372. internal FileRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
  373. : base(request, response, prefix)
  374. {
  375. }
  376. }
  377. }
  378. }