PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/BlogEngine/DotNetSlave.BusinessLogic/Web/HttpHandlers/SyndicationHandler.cs

#
C# | 375 lines | 232 code | 49 blank | 94 comment | 34 complexity | 4347dd124ea782957c961db457643ead MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. namespace BlogEngine.Core.Web.HttpHandlers
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Text;
  8. using System.Web;
  9. using System.Xml;
  10. using BlogEngine.Core.Web.HttpModules;
  11. /// <summary>
  12. /// Implements a custom handler to synchronously process HTTP Web requests for a syndication feed.
  13. /// </summary>
  14. /// <remarks>
  15. /// This handler can generate syndication feeds in a variety of formats and filtering
  16. /// options based on the query string parmaeters provided.
  17. /// </remarks>
  18. /// <seealso cref="IHttpHandler"/>
  19. /// <seealso cref="SyndicationGenerator"/>
  20. public class SyndicationHandler : IHttpHandler
  21. {
  22. #region Properties
  23. /// <summary>
  24. /// Gets a value indicating whether another request can use the <see cref = "T:System.Web.IHttpHandler"></see> instance.
  25. /// </summary>
  26. /// <returns>true if the <see cref = "T:System.Web.IHttpHandler"></see> instance is reusable; otherwise, false.</returns>
  27. public bool IsReusable
  28. {
  29. get
  30. {
  31. return false;
  32. }
  33. }
  34. #endregion
  35. #region Implemented Interfaces
  36. #region IHttpHandler
  37. /// <summary>
  38. /// Enables processing of HTTP Web requests by a custom HttpHandler that implements
  39. /// the <see cref="T:System.Web.IHttpHandler"></see> interface.
  40. /// </summary>
  41. /// <param name="context">
  42. /// An <see cref="T:System.Web.HttpContext"></see> object that provides references
  43. /// to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.
  44. /// </param>
  45. public void ProcessRequest(HttpContext context)
  46. {
  47. if (!Security.IsAuthorizedTo(Rights.ViewPublicPosts))
  48. {
  49. context.Response.StatusCode = 401;
  50. return;
  51. }
  52. var title = RetrieveTitle(context);
  53. var format = RetrieveFormat(context);
  54. var list = GenerateItemList(context);
  55. list = CleanList(list);
  56. if (string.IsNullOrEmpty(context.Request.QueryString["post"]))
  57. {
  58. // Shorten the list to the number of posts stated in the settings, except for the comment feed.
  59. var max = Math.Min(BlogSettings.Instance.PostsPerFeed, list.Count);
  60. list = list.FindAll(item => item.IsVisible);
  61. list = list.GetRange(0, max);
  62. }
  63. SetHeaderInformation(context, list, format);
  64. if (BlogSettings.Instance.EnableHttpCompression)
  65. {
  66. CompressionModule.CompressResponse(context);
  67. }
  68. var generator = new SyndicationGenerator(BlogSettings.Instance, Category.Categories);
  69. generator.WriteFeed(format, context.Response.OutputStream, list, title);
  70. }
  71. #endregion
  72. #endregion
  73. #region Methods
  74. /// <summary>
  75. /// Cleans the list.
  76. /// </summary>
  77. /// <param name="list">The list of IPublishable.</param>
  78. /// <returns>The cleaned list of IPublishable.</returns>
  79. private static List<IPublishable> CleanList(List<IPublishable> list)
  80. {
  81. return list.FindAll(item => item.IsVisible);
  82. }
  83. /// <summary>
  84. /// A converter delegate used for converting Results to Posts.
  85. /// </summary>
  86. /// <param name="item">
  87. /// The publishable item.
  88. /// </param>
  89. /// <returns>
  90. /// Converts to publishable interface.
  91. /// </returns>
  92. private static IPublishable ConvertToIPublishable(IPublishable item)
  93. {
  94. return item;
  95. }
  96. /// <summary>
  97. /// Generates the list of feed items based on the URL.
  98. /// </summary>
  99. /// <param name="context">
  100. /// The context.
  101. /// </param>
  102. /// <returns>
  103. /// A list of IPublishable.
  104. /// </returns>
  105. private static List<IPublishable> GenerateItemList(HttpContext context)
  106. {
  107. if (!string.IsNullOrEmpty(context.Request.QueryString["category"]))
  108. {
  109. // All posts in the specified category
  110. var categoryId = new Guid(context.Request.QueryString["category"]);
  111. return Post.GetPostsByCategory(categoryId).ConvertAll(ConvertToIPublishable);
  112. }
  113. if (!string.IsNullOrEmpty(context.Request.QueryString["tag"]))
  114. {
  115. // All posts with the specified tag
  116. return Post.GetPostsByTag(context.Request.QueryString["tag"].Trim()).ConvertAll(ConvertToIPublishable);
  117. }
  118. if (!string.IsNullOrEmpty(context.Request.QueryString["author"]))
  119. {
  120. // All posts by the specified author
  121. var author = context.Request.QueryString["author"];
  122. return Post.GetPostsByAuthor(author).ConvertAll(ConvertToIPublishable);
  123. }
  124. if (!string.IsNullOrEmpty(context.Request.QueryString["post"]))
  125. {
  126. // All comments of the specified post
  127. var post = Post.GetPost(new Guid(context.Request.QueryString["post"]));
  128. return post.Comments.ConvertAll(ConvertToIPublishable);
  129. }
  130. if (!string.IsNullOrEmpty(context.Request.QueryString["comments"]))
  131. {
  132. // The recent comments added to any post.
  133. return RecentComments();
  134. }
  135. if (!string.IsNullOrEmpty(context.Request.QueryString["q"]))
  136. {
  137. // Searches posts and pages
  138. return Search.Hits(context.Request.QueryString["q"], false);
  139. }
  140. if (!string.IsNullOrEmpty(context.Request.QueryString["apml"]))
  141. {
  142. // Finds matches to an APML file in both posts and pages
  143. try
  144. {
  145. using (var client = new WebClient())
  146. {
  147. client.Credentials = CredentialCache.DefaultNetworkCredentials;
  148. client.Encoding = Encoding.Default;
  149. using (var stream = client.OpenRead(context.Request.QueryString["apml"]))
  150. {
  151. var doc = new XmlDocument();
  152. if (stream != null)
  153. {
  154. doc.Load(stream);
  155. }
  156. var list = Search.ApmlMatches(doc, 30);
  157. list.Sort((i1, i2) => i2.DateCreated.CompareTo(i1.DateCreated));
  158. return list;
  159. }
  160. }
  161. }
  162. catch (Exception ex)
  163. {
  164. context.Response.Clear();
  165. context.Response.Write(ex.Message);
  166. context.Response.ContentType = "text/plain";
  167. context.Response.AppendHeader("Content-Disposition", "inline; filename=\"error.txt\"");
  168. context.Response.End();
  169. }
  170. }
  171. // The latest posts
  172. return Post.Posts.ConvertAll(ConvertToIPublishable);
  173. }
  174. /// <summary>
  175. /// Creates a list of the most recent comments
  176. /// </summary>
  177. /// <returns>
  178. /// A list of IPublishable.
  179. /// </returns>
  180. private static List<IPublishable> RecentComments()
  181. {
  182. var temp = Post.Posts.SelectMany(post => post.ApprovedComments).ToList();
  183. temp.Sort();
  184. temp.Reverse();
  185. var list = temp.ToList();
  186. return list.ConvertAll(ConvertToIPublishable);
  187. }
  188. /// <summary>
  189. /// Retrieves the syndication format from the urL parameters.
  190. /// </summary>
  191. /// <param name="context">
  192. /// The HTTP context.
  193. /// </param>
  194. /// <returns>
  195. /// The syndication format.
  196. /// </returns>
  197. private static SyndicationFormat RetrieveFormat(HttpContext context)
  198. {
  199. var query = context.Request.QueryString["format"];
  200. var format = BlogSettings.Instance.SyndicationFormat;
  201. if (!string.IsNullOrEmpty(query))
  202. {
  203. format = context.Request.QueryString["format"];
  204. }
  205. try
  206. {
  207. return (SyndicationFormat)Enum.Parse(typeof(SyndicationFormat), format, true);
  208. }
  209. catch (ArgumentException)
  210. {
  211. return SyndicationFormat.None;
  212. }
  213. }
  214. /// <summary>
  215. /// Retrieves the title.
  216. /// </summary>
  217. /// <param name="context">The context.</param>
  218. /// <returns>The title string.</returns>
  219. private static string RetrieveTitle(HttpContext context)
  220. {
  221. var title = BlogSettings.Instance.Name;
  222. string subTitle = null;
  223. if (!string.IsNullOrEmpty(context.Request.QueryString["category"]))
  224. {
  225. if (context.Request.QueryString["category"].Length != 36)
  226. {
  227. StopServing(context);
  228. }
  229. var categoryId = new Guid(context.Request.QueryString["category"]);
  230. var currentCategory = Category.GetCategory(categoryId);
  231. if (currentCategory == null)
  232. {
  233. StopServing(context);
  234. }
  235. if (currentCategory != null)
  236. {
  237. subTitle = currentCategory.Title;
  238. }
  239. }
  240. if (!string.IsNullOrEmpty(context.Request.QueryString["author"]))
  241. {
  242. subTitle = context.Request.QueryString["author"];
  243. }
  244. if (!string.IsNullOrEmpty(context.Request.QueryString["post"]))
  245. {
  246. if (context.Request.QueryString["post"].Length != 36)
  247. {
  248. StopServing(context);
  249. }
  250. var post = Post.GetPost(new Guid(context.Request.QueryString["post"]));
  251. if (post == null)
  252. {
  253. StopServing(context);
  254. }
  255. if (post != null)
  256. {
  257. subTitle = post.Title;
  258. }
  259. }
  260. if (!string.IsNullOrEmpty(context.Request.QueryString["comments"]))
  261. {
  262. subTitle = "Comments";
  263. }
  264. if (subTitle != null)
  265. {
  266. return string.Format("{0} - {1}", title, subTitle);
  267. }
  268. return title;
  269. }
  270. /// <summary>
  271. /// Sets the response header information.
  272. /// </summary>
  273. /// <param name="context">
  274. /// An <see cref="HttpContext"/> object that provides references to the intrinsic server objects (for example, <b>Request</b>, <b>Response</b>, <b>Session</b>, and <b>Server</b>) used to service HTTP requests.
  275. /// </param>
  276. /// <param name="items">
  277. /// The collection of <see cref="IPublishable"/> instances used when setting the response header details.
  278. /// </param>
  279. /// <param name="format">
  280. /// The format of the syndication feed being generated.
  281. /// </param>
  282. /// <exception cref="ArgumentNullException">
  283. /// The <paramref name="context"/> is a null reference (Nothing in Visual Basic) -or- the <paramref name="items"/> is a null reference (Nothing in Visual Basic).
  284. /// </exception>
  285. private static void SetHeaderInformation(
  286. HttpContext context, IEnumerable<IPublishable> items, SyndicationFormat format)
  287. {
  288. var lastModified = new DateTime(1900, 1, 3); // don't use DateTime.MinValue here, as Mono doesn't like it
  289. foreach (var item in items)
  290. {
  291. if (item.DateModified.AddHours(-BlogSettings.Instance.Timezone) > lastModified)
  292. {
  293. lastModified = item.DateModified.AddHours(-BlogSettings.Instance.Timezone);
  294. }
  295. }
  296. switch (format)
  297. {
  298. case SyndicationFormat.Atom:
  299. context.Response.ContentType = "application/atom+xml";
  300. context.Response.AppendHeader("Content-Disposition", "inline; filename=atom.xml");
  301. break;
  302. case SyndicationFormat.Rss:
  303. context.Response.ContentType = "application/rss+xml";
  304. context.Response.AppendHeader("Content-Disposition", "inline; filename=rss.xml");
  305. break;
  306. }
  307. if (Utils.SetConditionalGetHeaders(lastModified))
  308. {
  309. context.Response.End();
  310. }
  311. }
  312. /// <summary>
  313. /// The stop serving.
  314. /// </summary>
  315. /// <param name="context">
  316. /// The context.
  317. /// </param>
  318. private static void StopServing(HttpContext context)
  319. {
  320. context.Response.Clear();
  321. context.Response.StatusCode = 404;
  322. context.Response.End();
  323. }
  324. #endregion
  325. }
  326. }