//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.Xml; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Xrds; using DotNetOpenAuth.Yadis; /// /// An XRI style of OpenID Identifier. /// [Serializable] [ContractVerification(true)] [Pure] public sealed class XriIdentifier : Identifier { /// /// An XRI always starts with one of these symbols. /// internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' }; /// /// The scheme and separator "xri://" /// private const string XriScheme = "xri://"; /// /// Backing store for the property. /// private readonly string canonicalXri; /// /// Initializes a new instance of the class. /// /// The string value of the XRI. internal XriIdentifier(string xri) : this(xri, false) { Requires.NotNullOrEmpty(xri, "xri"); Requires.Format(IsValidXri(xri), OpenIdStrings.InvalidXri); } /// /// Initializes a new instance of the class. /// /// The XRI that this Identifier will represent. /// /// If set to true, discovery and the initial authentication redirect will /// only succeed if it can be done entirely using SSL. /// internal XriIdentifier(string xri, bool requireSsl) : base(xri, requireSsl) { Requires.NotNullOrEmpty(xri, "xri"); Requires.Format(IsValidXri(xri), OpenIdStrings.InvalidXri); Contract.Assume(xri != null); // Proven by IsValidXri this.OriginalXri = xri; this.canonicalXri = CanonicalizeXri(xri); } /// /// Gets the original XRI supplied to the constructor. /// internal string OriginalXri { get; private set; } /// /// Gets the canonical form of the XRI string. /// internal string CanonicalXri { get { Contract.Ensures(Contract.Result() != null); return this.canonicalXri; } } /// /// Tests equality between this XRI and another XRI. /// /// The to compare with the current . /// /// true if the specified is equal to the current ; otherwise, false. /// /// /// The parameter is null. /// public override bool Equals(object obj) { XriIdentifier other = obj as XriIdentifier; if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison string objString = obj.ToString(); ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(objString), "Identifier.ToString() returned a null or empty string."); other = Identifier.Parse(objString) as XriIdentifier; } if (other == null) { return false; } return this.CanonicalXri == other.CanonicalXri; } /// /// Returns the hash code of this XRI. /// /// /// A hash code for the current . /// public override int GetHashCode() { return this.CanonicalXri.GetHashCode(); } /// /// Returns the canonical string form of the XRI. /// /// /// A that represents the current . /// public override string ToString() { return this.CanonicalXri; } /// /// Tests whether a given string represents a valid XRI format. /// /// The value to test for XRI validity. /// /// true if the given string constitutes a valid XRI; otherwise, false. /// internal static bool IsValidXri(string xri) { Requires.NotNullOrEmpty(xri, "xri"); xri = xri.Trim(); // TODO: better validation code here return xri.IndexOfAny(GlobalContextSymbols) == 0 || xri.StartsWith("(", StringComparison.Ordinal) || xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase); } /// /// Returns an that has no URI fragment. /// Quietly returns the original if it is not /// a or no fragment exists. /// /// /// A new instance if there was a /// fragment to remove, otherwise this same instance.. /// /// /// XRI Identifiers never have a fragment part, and thus this method /// always returns this same instance. /// internal override Identifier TrimFragment() { return this; } /// /// Converts a given identifier to its secure equivalent. /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS. /// Discovery is made to require SSL for the entire resolution process. /// /// The newly created secure identifier. /// If the conversion fails, retains /// this identifiers identity, but will never discover any endpoints. /// /// True if the secure conversion was successful. /// False if the Identifier was originally created with an explicit HTTP scheme. /// [ContractVerification(false)] // bugs/limitations in CC static analysis internal override bool TryRequireSsl(out Identifier secureIdentifier) { secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true); return true; } /// /// Takes any valid form of XRI string and returns the canonical form of the same XRI. /// /// The xri to canonicalize. /// The canonicalized form of the XRI. /// The canonical form, per the OpenID spec, is no scheme and no whitespace on either end. private static string CanonicalizeXri(string xri) { Requires.NotNull(xri, "xri"); Contract.Ensures(Contract.Result() != null); xri = xri.Trim(); if (xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase)) { Contract.Assume(XriScheme.Length <= xri.Length); // should be implied by StartsWith xri = xri.Substring(XriScheme.Length); } return xri; } #if CONTRACTS_FULL /// /// Verifies conditions that should be true for any valid state of this object. /// [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(this.canonicalXri != null); } #endif } }