//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth { using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Drawing.Design; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using DotNetOpenAuth.Logging; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Yadis; /// /// The locations the YADIS protocol describes can contain a reference /// to an XRDS document. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Justification = "Correct spelling")] [Flags] public enum XrdsUrlLocations { /// /// The XRDS document should not be advertised anywhere. /// /// /// When the XRDS document is not referenced from anywhere, /// the XRDS content is only available when /// is true /// and the discovering client includes an /// "Accept: application/xrds+xml" HTTP header. /// None = 0x0, /// /// Indicates XRDS document referencing from an HTTP protocol header (outside the HTML). /// HttpHeader = 0x1, /// /// Indicates XRDS document referencing from within an HTML page's <HEAD> tag. /// HtmlMeta = 0x2, /// /// Indicates XRDS document referencing in both HTTP headers and HTML HEAD tags. /// Both = 0x3, } /// /// An ASP.NET control that advertises an XRDS document and even responds to specially /// crafted requests to retrieve it. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Justification = "Correct spelling")] [DefaultProperty("XrdsLocation")] [ToolboxData("<{0}:XrdsPublisher runat=server>")] public class XrdsPublisher : Control { /// /// The view state key to ues for storing the value of the property. /// private const string XrdsUrlViewStateKey = "XrdsUrl"; /// /// The default value for the property. /// private const XrdsUrlLocations XrdsAdvertisementDefault = XrdsUrlLocations.HttpHeader; /// /// The view state key to ues for storing the value of the property. /// private const string XrdsAdvertisementViewStateKey = "XrdsAdvertisement"; /// /// The default value for the property. /// private const bool XrdsAutoAnswerDefault = true; /// /// The view state key to ues for storing the value of the property. /// private const string XrdsAutoAnswerViewStateKey = "XrdsAutoAnswer"; /// /// The default value for the property. /// private const bool EnabledDefault = true; /// /// The view state key to ues for storing the value of the property. /// private const string EnabledViewStateKey = "Enabled"; /// /// Initializes a new instance of the class. /// public XrdsPublisher() { Reporting.RecordFeatureUse(this); } #region Properties /// /// Gets or sets the location of the XRDS document. /// [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Justification = "Correct spelling")] [Category("Behavior"), Bindable(true)] [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] public string XrdsUrl { get { return (string)ViewState[XrdsUrlViewStateKey]; } set { UriUtil.ValidateResolvableUrl(Page, DesignMode, value); ViewState[XrdsUrlViewStateKey] = value; } } /// /// Gets or sets where the XRDS document URL is advertised in the web response. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Justification = "Correct spelling")] [Category("Behavior"), DefaultValue(XrdsAdvertisementDefault), Bindable(true)] [Description("Where the XRDS document URL is advertised in the web response.")] public XrdsUrlLocations XrdsAdvertisement { get { return ViewState[XrdsAdvertisementViewStateKey] == null ? XrdsAdvertisementDefault : (XrdsUrlLocations)ViewState[XrdsAdvertisementViewStateKey]; } set { ViewState[XrdsAdvertisementViewStateKey] = value; } } /// /// Gets or sets a value indicating whether a specially crafted YADIS /// search for an XRDS document is immediately answered by this control. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Justification = "Correct spelling")] [Category("Behavior"), DefaultValue(XrdsAutoAnswerDefault), Bindable(true)] [Description("Whether XRDS requests should be immediately answered with the XRDS document if it is served by this web application.")] public bool XrdsAutoAnswer { get { return ViewState[XrdsAutoAnswerViewStateKey] == null ? XrdsAutoAnswerDefault : (bool)ViewState[XrdsAutoAnswerViewStateKey]; } set { ViewState[XrdsAutoAnswerViewStateKey] = value; } } /// /// Gets or sets a value indicating whether the XRDS document is advertised. /// [Category("Behavior"), DefaultValue(EnabledDefault)] public bool Enabled { get { return ViewState[EnabledViewStateKey] == null ? EnabledDefault : (bool)ViewState[EnabledViewStateKey]; } set { ViewState[EnabledViewStateKey] = value; } } #endregion /// /// Detects YADIS requests for the XRDS document and responds immediately /// if is true. /// /// The object that contains the event data. protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (!this.Enabled) { return; } if (!this.Page.IsPostBack) { if (this.XrdsAutoAnswer && !string.IsNullOrEmpty(this.XrdsUrl) && this.XrdsUrl.StartsWith("~/", StringComparison.Ordinal)) { // Check for the presence of an accept types header that is looking // for the XRDS document specifically. if (this.Page.Request.AcceptTypes != null && Array.IndexOf(this.Page.Request.AcceptTypes, ContentTypes.Xrds) >= 0) { // Respond to the caller immediately with an XRDS document // and avoid sending the whole web page's contents to the // client since it isn't interested anyway. // We do NOT simply send a 301 redirect here because that would // alter the Claimed Identifier. Logger.Yadis.InfoFormat("Transferring request from {0} to {1} to respond to XRDS discovery request.", this.Page.Request.Url.AbsoluteUri, this.XrdsUrl); this.Page.Server.Transfer(this.XrdsUrl); } } } } /// /// Renders the HTTP Header and/or HTML HEAD tags. /// /// The object that receives the server control content. [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] protected override void Render(HtmlTextWriter writer) { Assumes.True(writer != null, "Missing contract."); if (this.Enabled && this.Visible && !string.IsNullOrEmpty(this.XrdsUrl)) { Uri xrdsAddress = new Uri(MessagingUtilities.GetRequestUrlFromContext(), Page.Response.ApplyAppPathModifier(this.XrdsUrl)); if ((this.XrdsAdvertisement & XrdsUrlLocations.HttpHeader) != 0) { Page.Response.AddHeader(Yadis.Yadis.HeaderName, xrdsAddress.AbsoluteUri); } if ((this.XrdsAdvertisement & XrdsUrlLocations.HtmlMeta) != 0) { writer.WriteBeginTag("meta"); writer.WriteAttribute("http-equiv", Yadis.Yadis.HeaderName); writer.WriteAttribute("content", xrdsAddress.AbsoluteUri); writer.Write("/>"); writer.WriteLine(); } } } } }