using System; using System.Data; using System.Configuration; using System.Collections; using System.Collections.Generic; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using ScrewTurn.Wiki.PluginFramework; using System.Text; using System.Xml; namespace ScrewTurn.Wiki { public partial class RSS : BasePage { private string currentNamespace = null; private RssFeedsMode rssFeedsMode; protected void Page_Load(object sender, EventArgs e) { rssFeedsMode = Settings.RssFeedsMode; if(rssFeedsMode == RssFeedsMode.Disabled) { Response.Clear(); Response.StatusCode = 404; Response.End(); return; } string currentUsername = SessionFacade.GetCurrentUsername(); string[] currentGroups = SessionFacade.GetCurrentGroupNames(); currentNamespace = DetectNamespace(); if(string.IsNullOrEmpty(currentNamespace)) currentNamespace = null; if(SessionFacade.LoginKey == null) { // Look for username/password in the query string if(Request["Username"] != null && Request["Password"] != null) { // Try to authenticate UserInfo u = Users.FindUser(Request["Username"]); if(u != null) { // Very "dirty" way - pages should not access Providers if(u.Provider.TestAccount(u, Request["Password"])) { // Valid account currentUsername = Request["Username"]; currentGroups = Users.FindUser(currentUsername).Groups; } } else { // Check for built-in admin account if(Request["Username"].Equals("admin") && Request["Password"].Equals(Settings.MasterPassword)) { currentUsername = "admin"; currentGroups = new string[] { Settings.AdministratorsGroup }; } } } } Response.ClearContent(); Response.ContentType = "text/xml;charset=UTF-8"; Response.ContentEncoding = System.Text.UTF8Encoding.UTF8; if(Request["Page"] != null) { PageInfo page = Pages.FindPage(Request["Page"]); if(page == null) return; PageContent content = Content.GetPageContent(page, true); if(Request["Discuss"] == null) { // Check permission for the page bool canReadPage = AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadPage, currentUsername, currentGroups); if(!canReadPage) { Response.StatusCode = 401; return; } // Start an XML writer for the output stream using (XmlWriter rss = XmlWriter.Create(Response.OutputStream)) { // Build an RSS header BuildRssHeader(rss); // Build the channel element BuildChannelHead(rss, Settings.WikiTitle + " - " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)), Settings.MainUrl + page.FullName + Settings.PageExtension, Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx?Page=", page.FullName), Formatter.StripHtml(content.Title) + " - " + Properties.Messages.PageUpdates); // Write the item element rss.WriteStartElement("item"); rss.WriteStartElement("title"); rss.WriteCData(Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page))); rss.WriteEndElement(); rss.WriteElementString("link", Settings.MainUrl + page.FullName + Settings.PageExtension); // Create the description tag rss.WriteStartElement("description"); if (rssFeedsMode == RssFeedsMode.Summary) { rss.WriteCData(Formatter.StripHtml(content.Title) + ": " + Properties.Messages.ThePageHasBeenUpdatedBy + " " + content.User + (content.Comment.Length > 0 ? ".
" + content.Comment : ".")); } else { rss.WriteCData(Content.GetFormattedPageContent(page, false)); } rss.WriteEndElement(); // Write the remaining elements BuildAuthorTag(rss, content.User); rss.WriteElementString("pubDate", content.LastModified.ToUniversalTime().ToString("R")); rss.WriteStartElement("guid"); rss.WriteAttributeString("isPermaLink", "false"); rss.WriteString(GetGuid(page.FullName, content.LastModified)); rss.WriteEndElement(); // Complete the item element CompleteCurrentElement(rss); // Complete the channel element CompleteCurrentElement(rss); // Complete the rss element CompleteCurrentElement(rss); // Finish off rss.Flush(); rss.Close(); } } else { // Check permission for the discussion bool canReadDiscussion = AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadDiscussion, currentUsername, currentGroups); if(!canReadDiscussion) { Response.StatusCode = 401; return; } List messages = new List(Pages.GetPageMessages(page)); // Un-tree Messages messages = UnTreeMessages(messages); // Sort from newer to older messages.Sort(new MessageDateTimeComparer(true)); // Start an XML writer for the output stream using (XmlWriter rss = XmlWriter.Create(Response.OutputStream)) { // Build an RSS header BuildRssHeader(rss); // Build the channel element BuildChannelHead(rss, Settings.WikiTitle + " - " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)) + " - Discussion Updates", Settings.MainUrl + page.FullName + Settings.PageExtension + "?Discuss=1", Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx?Page=", page.FullName, "&Discuss=1"), Settings.WikiTitle + " - " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)) + " - Discussion Updates"); for (int i = 0; i < messages.Count; i++) { // Write the item element rss.WriteStartElement("item"); rss.WriteStartElement("title"); rss.WriteCData(Formatter.StripHtml(FormattingPipeline.PrepareTitle(messages[i].Subject, false, FormattingContext.MessageBody, page))); rss.WriteEndElement(); rss.WriteElementString("link", Settings.MainUrl + page.FullName + Settings.PageExtension + "?Discuss=1"); // Create the description tag rss.WriteStartElement("description"); if (rssFeedsMode == RssFeedsMode.Summary) { rss.WriteCData(Properties.Messages.AMessageHasBeenPostedBy.Replace("##SUBJECT##", messages[i].Subject) + " " + messages[i].Username + "."); } else { rss.WriteCData(FormattingPipeline.FormatWithPhase3(FormattingPipeline.FormatWithPhase1And2(messages[i].Body, false, FormattingContext.MessageBody, page), FormattingContext.MessageBody, page)); } rss.WriteEndElement(); // Write the remaining elements BuildAuthorTag(rss, messages[i].Username); rss.WriteElementString("pubDate", content.LastModified.ToUniversalTime().ToString("R")); rss.WriteStartElement("guid"); rss.WriteAttributeString("isPermaLink", "false"); rss.WriteString(GetGuid(page.FullName + "-" + messages[i].ID.ToString(), messages[i].DateTime)); rss.WriteEndElement(); // Complete the item element CompleteCurrentElement(rss); } // Complete the channel element CompleteCurrentElement(rss); // Complete the rss element CompleteCurrentElement(rss); // Finish off rss.Flush(); rss.Close(); } } } else { if(Request["Discuss"] == null) { // All page updates // Start an XML writer for the output stream using(XmlWriter rss = XmlWriter.Create(Response.OutputStream)) { // Build an RSS header BuildRssHeader(rss); bool useCat = false; string cat = ""; if(Request["Category"] != null) { useCat = true; cat = Request["Category"]; } // Build the channel element BuildChannelHead(rss, Settings.WikiTitle + " - " + Properties.Messages.PageUpdates, Settings.MainUrl, Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx", (useCat ? ("?Category=" + cat) : "")), Properties.Messages.RecentPageUpdates); RecentChange[] ch = RecentChanges.GetAllChanges(); Array.Reverse(ch); for(int i = 0; i < ch.Length; i++) { // Suppress this entry if we've already reported this page (so we don't create duplicate entries in the feed page) bool duplicateFound = false; for(int j = 0; j < i; j++) { if (ch[j].Page == ch[i].Page) { duplicateFound = true; break; } } if(duplicateFound) continue; // Skip message-related entries if(!IsPageChange(ch[i].Change)) continue; PageInfo p = Pages.FindPage(ch[i].Page); if(p != null) { // Check permissions for every page bool canReadThisPage = AuthChecker.CheckActionForPage(p, Actions.ForPages.ReadPage, currentUsername, currentGroups); if(!canReadThisPage) continue; if(useCat) { CategoryInfo[] infos = Pages.GetCategoriesForPage(p); if(infos.Length == 0 && cat != "-") continue; else if(infos.Length != 0) { bool found = false; for(int k = 0; k < infos.Length; k++) { if(infos[k].FullName == cat) { found = true; break; } } if(!found) continue; } } } // Check namespace if(p != null && NameTools.GetNamespace(p.FullName) != currentNamespace) continue; // Write the item element rss.WriteStartElement("item"); rss.WriteStartElement("title"); rss.WriteCData(Formatter.StripHtml(FormattingPipeline.PrepareTitle(ch[i].Title, false, FormattingContext.PageContent, p))); rss.WriteEndElement(); if (ch[i].Change != Change.PageDeleted && p != null) rss.WriteElementString("link", Settings.MainUrl + ch[i].Page + Settings.PageExtension); else rss.WriteElementString("link", Settings.MainUrl); BuildAuthorTag(rss, ch[i].User); // Create the description tag StringBuilder sb = new StringBuilder(); if(rssFeedsMode == RssFeedsMode.Summary || p == null) { switch(ch[i].Change) { case Change.PageUpdated: sb.Append(Properties.Messages.ThePageHasBeenUpdatedBy); break; case Change.PageDeleted: sb.Append(Properties.Messages.ThePageHasBeenDeletedBy); break; case Change.PageRenamed: sb.Append(Properties.Messages.ThePageHasBeenRenamedBy); break; case Change.PageRolledBack: sb.Append(Properties.Messages.ThePageHasBeenRolledBackBy); break; } sb.Append(" " + ch[i].User + (ch[i].Description.Length > 0 ? ".
" + ch[i].Description : ".")); } else { // p != null sb.Append(Content.GetFormattedPageContent(p, false)); } rss.WriteStartElement("description"); rss.WriteCData(sb.ToString()); rss.WriteEndElement(); // Write the remaining elements rss.WriteElementString("pubDate", ch[i].DateTime.ToUniversalTime().ToString("R")); rss.WriteStartElement("guid"); rss.WriteAttributeString("isPermaLink", "false"); rss.WriteString(GetGuid(ch[i].Page, ch[i].DateTime)); rss.WriteEndElement(); // Complete the item element rss.WriteEndElement(); } // Complete the channel element CompleteCurrentElement(rss); // Complete the rss element CompleteCurrentElement(rss); // Finish off rss.Flush(); rss.Close(); } } else { // All discussion updates // Start an XML writer for the output stream using (XmlWriter rss = XmlWriter.Create(Response.OutputStream)) { // Build an RSS header BuildRssHeader(rss); bool useCat = false; string cat = ""; if (Request["Category"] != null) { useCat = true; cat = Request["Category"]; } // Build the channel element BuildChannelHead(rss, Settings.WikiTitle + " - " + Properties.Messages.DiscussionUpdates, Settings.MainUrl, Settings.MainUrl + UrlTools.BuildUrl("RSS.aspx", (useCat ? ("?Category=" + cat) : "")), Properties.Messages.RecentDiscussionUpdates); RecentChange[] ch = RecentChanges.GetAllChanges(); Array.Reverse(ch); for (int i = 0; i < ch.Length; i++) { // Skip page-related entries if (!IsMessageChange(ch[i].Change)) continue; PageInfo p = Pages.FindPage(ch[i].Page); if (p != null) { // Check permissions for every page bool canReadThisPageDiscussion = AuthChecker.CheckActionForPage(p, Actions.ForPages.ReadDiscussion, currentUsername, currentGroups); if (!canReadThisPageDiscussion) continue; if (useCat) { CategoryInfo[] infos = Pages.GetCategoriesForPage(p); if (infos.Length == 0 && cat != "-") continue; else if (infos.Length != 0) { bool found = false; for (int k = 0; k < infos.Length; k++) { if (infos[k].FullName == cat) { found = true; break; } } if (!found) continue; } } // Check namespace if (NameTools.GetNamespace(p.FullName) != currentNamespace) continue; // Write the item element rss.WriteStartElement("item"); rss.WriteStartElement("title"); rss.WriteCData(Properties.Messages.Discussion + ": " + Formatter.StripHtml(FormattingPipeline.PrepareTitle(ch[i].Title, false, FormattingContext.PageContent, p))); rss.WriteEndElement(); string id = Tools.GetMessageIdForAnchor(ch[i].DateTime); if (ch[i].Change != Change.MessageDeleted) { rss.WriteElementString("link", Settings.MainUrl + ch[i].Page + Settings.PageExtension + "?Discuss=1#" + id); } else rss.WriteElementString("link", Settings.MainUrl + ch[i].Page + Settings.PageExtension + "?Discuss=1"); string messageContent = FindMessageContent(ch[i].Page, id); // Create the description tag StringBuilder sb = new StringBuilder(); if (rssFeedsMode == RssFeedsMode.Summary || messageContent == null) { switch (ch[i].Change) { case Change.MessagePosted: sb.Append(Properties.Messages.AMessageHasBeenPostedBy.Replace("##SUBJECT##", ch[i].MessageSubject)); break; case Change.MessageEdited: sb.Append(Properties.Messages.AMessageHasBeenEditedBy.Replace("##SUBJECT##", ch[i].MessageSubject)); break; case Change.MessageDeleted: sb.Append(Properties.Messages.AMessageHasBeenDeletedBy.Replace("##SUBJECT##", ch[i].MessageSubject)); break; } sb.Append(" " + ch[i].User + (ch[i].Description.Length > 0 ? ".
" + ch[i].Description : ".")); } else { sb.Append(FormattingPipeline.FormatWithPhase3(FormattingPipeline.FormatWithPhase1And2(messageContent, false, FormattingContext.MessageBody, null), FormattingContext.MessageBody, null)); } rss.WriteStartElement("description"); rss.WriteCData(sb.ToString()); rss.WriteEndElement(); // Write the remaining elements BuildAuthorTag(rss, ch[i].User); rss.WriteElementString("pubDate", ch[i].DateTime.ToUniversalTime().ToString("R")); rss.WriteStartElement("guid"); rss.WriteAttributeString("isPermaLink", "false"); rss.WriteString(GetGuid(ch[i].Page, ch[i].DateTime)); rss.WriteEndElement(); // Complete the item element rss.WriteEndElement(); } } // Complete the channel element CompleteCurrentElement(rss); // Complete the rss element CompleteCurrentElement(rss); // Finish off rss.Flush(); rss.Close(); } } } } /// /// Tries to find the content of a message. /// /// The name of the page. /// The ID of the message, built using Tools.GetMessageIdForAnchor(...). /// The message content, or null. private string FindMessageContent(string pageName, string messageId) { PageInfo page = Pages.FindPage(pageName); if(page == null) return null; Message[] messages = Pages.GetPageMessages(page); if(messages.Length == 0) return null; List linearMessages = UnTreeMessages(messages); foreach(Message msg in linearMessages) { if(messageId == Tools.GetMessageIdForAnchor(msg.DateTime)) return msg.Body; } return null; } /// /// Determines whether a change refers to page content. /// /// The change. /// true if the change refers to page content. private static bool IsPageChange(Change change) { return change == Change.PageUpdated || change == Change.PageRolledBack || change == Change.PageRenamed || change == Change.PageDeleted; } /// /// Determines whether a change refers to a message. /// /// The change. /// true if the change refers to a message. private static bool IsMessageChange(Change change) { return change == Change.MessagePosted || change == Change.MessageEdited || change == Change.MessageDeleted; } /// /// Deconstructs a tree of messages and converts it into a flat list. /// /// The input tree. /// The resulting flat list. private static List UnTreeMessages(IEnumerable messages) { List output = new List(20); output.AddRange(messages); foreach(Message msg in messages) { output.AddRange(UnTreeMessages(msg.Replies)); } return output; } /// /// Gets a valid and consistent GUID for RSS items. /// /// The item name, for example the page name. /// The last date/time the item was modified. /// The GUID. private string GetGuid(string item, DateTime editDateTime) { return Hash.Compute(item + editDateTime.ToString("yyyyMMddHHmmss")); } // Atom namespace constants private const string atomPrefix = "atom"; private const string atomNs = "http://www.w3.org/2005/Atom"; /// /// Sends the RSS header to the output stream. /// /// The output stream. private void BuildRssHeader(XmlWriter rss) { // Create a version 2.0 heading rss.WriteStartElement("rss"); rss.WriteAttributeString("version", "2.0"); // Define the atom namespace so we can include a self-referencing link rss.WriteAttributeString("xmlns", atomPrefix, null, atomNs); } /// /// Sends the channel head to the output stream. /// /// The output stream. /// The title. /// The link. /// The self link (atom). /// The description. /// The complete channel head. private void BuildChannelHead(XmlWriter rss, string title, string link, string selfLink, string description) { rss.WriteStartElement("channel"); rss.WriteStartElement("title"); rss.WriteCData(title); rss.WriteEndElement(); rss.WriteElementString("link", link); rss.WriteStartElement(atomPrefix, "link", atomNs); rss.WriteAttributeString("href", selfLink); rss.WriteAttributeString("rel", "self"); rss.WriteAttributeString("type", "application/rss+xml"); rss.WriteEndElement(); rss.WriteStartElement("description"); rss.WriteCData(description); rss.WriteEndElement(); rss.WriteElementString("pubDate", DateTime.Now.ToString("R")); rss.WriteElementString("generator", "ScrewTurn Wiki RSS Feed Generator"); } /// /// Completes an element in the output stream. /// /// The output stream. private void CompleteCurrentElement(XmlWriter rss) { rss.WriteEndElement(); } /// /// Sends an author tag to the output stream. /// /// The output stream. /// The author's user name. private void BuildAuthorTag(XmlWriter rss, string userName) { UserInfo author = Users.FindUser(userName); if(author == null) { rss.WriteElementString("author", userName); } else { rss.WriteElementString("author", string.Format("{0} ({1})", Formatter.StripHtml(author.DisplayName), userName)); } } } }