PageRenderTime 57ms CodeModel.GetById 26ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

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

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