//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; /// /// An Identifier is either a "http" or "https" URI, or an XRI. /// [Serializable] [ContractVerification(true)] [Pure] [ContractClass(typeof(IdentifierContract))] public abstract class Identifier { /// /// Initializes static members of the class. /// static Identifier() { Func safeIdentifier = str => { Contract.Assume(str != null); ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected); return Identifier.Parse(str, true); }; MessagePart.Map(id => id.SerializedString, id => id.OriginalString, safeIdentifier); } /// /// Initializes a new instance of the class. /// /// The original string before any normalization. /// Whether the derived class is prepared to guarantee end-to-end discovery /// and initial redirect for authentication is performed using SSL. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Justification = "Emphasis on string instead of the strong-typed Identifier.")] protected Identifier(string originalString, bool isDiscoverySecureEndToEnd) { this.OriginalString = originalString; this.IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd; } /// /// Gets the original string that was normalized to create this Identifier. /// internal string OriginalString { get; private set; } /// /// Gets the Identifier in the form in which it should be serialized. /// /// /// For Identifiers that were originally deserialized, this is the exact same /// string that was deserialized. For Identifiers instantiated in some other way, this is /// the normalized form of the string used to instantiate the identifier. /// internal virtual string SerializedString { get { return this.IsDeserializedInstance ? this.OriginalString : this.ToString(); } } /// /// Gets or sets a value indicating whether instances are considered equal /// based solely on their string reprsentations. /// /// /// This property serves as a test hook, so that MockIdentifier instances can be considered "equal" /// to UriIdentifier instances. /// protected internal static bool EqualityOnStrings { get; set; } /// /// Gets a value indicating whether this Identifier will ensure SSL is /// used throughout the discovery phase and initial redirect of authentication. /// /// /// If this is false, a value of true may be obtained by calling /// . /// protected internal bool IsDiscoverySecureEndToEnd { get; private set; } /// /// Gets a value indicating whether this instance was initialized from /// deserializing a message. /// /// /// This is interesting because when an Identifier comes from the network, /// we can't normalize it and then expect signatures to still verify. /// But if the Identifier is initialized locally, we can and should normalize it /// before serializing it. /// protected bool IsDeserializedInstance { get; private set; } /// /// Converts the string representation of an Identifier to its strong type. /// /// The identifier. /// The particular Identifier instance to represent the value given. [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all identifiers are URIs.")] [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Our named alternate is Parse.")] [DebuggerStepThrough] public static implicit operator Identifier(string identifier) { Requires.True(identifier == null || identifier.Length > 0, "identifier"); Contract.Ensures((identifier == null) == (Contract.Result() == null)); if (identifier == null) { return null; } return Parse(identifier); } /// /// Converts a given Uri to a strongly-typed Identifier. /// /// The identifier to convert. /// The result of the conversion. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")] [DebuggerStepThrough] public static implicit operator Identifier(Uri identifier) { Contract.Ensures((identifier == null) == (Contract.Result() == null)); if (identifier == null) { return null; } return new UriIdentifier(identifier); } /// /// Converts an Identifier to its string representation. /// /// The identifier to convert to a string. /// The result of the conversion. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")] [DebuggerStepThrough] public static implicit operator string(Identifier identifier) { Contract.Ensures((identifier == null) == (Contract.Result() == null)); if (identifier == null) { return null; } return identifier.ToString(); } /// /// Parses an identifier string and automatically determines /// whether it is an XRI or URI. /// /// Either a URI or XRI identifier. /// An instance for the given value. [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] public static Identifier Parse(string identifier) { Requires.NotNullOrEmpty(identifier, "identifier"); Contract.Ensures(Contract.Result() != null); return Parse(identifier, false); } /// /// Parses an identifier string and automatically determines /// whether it is an XRI or URI. /// /// Either a URI or XRI identifier. /// if set to true this Identifier will serialize exactly as given rather than in its normalized form. /// /// An instance for the given value. /// [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] public static Identifier Parse(string identifier, bool serializeExactValue) { Requires.NotNullOrEmpty(identifier, "identifier"); Contract.Ensures(Contract.Result() != null); Identifier id; if (XriIdentifier.IsValidXri(identifier)) { id = new XriIdentifier(identifier); } else { id = new UriIdentifier(identifier); } id.IsDeserializedInstance = serializeExactValue; return id; } /// /// Attempts to parse a string for an OpenId Identifier. /// /// The string to be parsed. /// The parsed Identifier form. /// /// True if the operation was successful. False if the string was not a valid OpenId Identifier. /// public static bool TryParse(string value, out Identifier result) { if (string.IsNullOrEmpty(value)) { result = null; return false; } if (IsValid(value)) { result = Parse(value); return true; } else { result = null; return false; } } /// /// Checks the validity of a given string representation of some Identifier. /// /// The identifier. /// /// true if the specified identifier is valid; otherwise, false. /// [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] public static bool IsValid(string identifier) { Requires.NotNullOrEmpty(identifier, "identifier"); return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier); } /// /// Tests equality between two s. /// /// The first Identifier. /// The second Identifier. /// /// true if the two instances should be considered equal; false otherwise. /// public static bool operator ==(Identifier id1, Identifier id2) { return id1.EqualsNullSafe(id2); } /// /// Tests inequality between two s. /// /// The first Identifier. /// The second Identifier. /// /// true if the two instances should be considered unequal; false if they are equal. /// public static bool operator !=(Identifier id1, Identifier id2) { return !id1.EqualsNullSafe(id2); } /// /// Tests equality between two s. /// /// 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) { Debug.Fail("This should be overridden in every derived class."); return base.Equals(obj); } /// /// Gets the hash code for an for storage in a hashtable. /// /// /// A hash code for the current . /// public override int GetHashCode() { Debug.Fail("This should be overridden in every derived class."); return base.GetHashCode(); } /// /// Reparses the specified identifier in order to be assured that the concrete type that /// implements the identifier is one of the well-known ones. /// /// The identifier. /// Either or . internal static Identifier Reparse(Identifier identifier) { Requires.NotNull(identifier, "identifier"); Contract.Ensures(Contract.Result() != null); return Parse(identifier, identifier.IsDeserializedInstance); } /// /// 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.. [Pure] internal abstract Identifier TrimFragment(); /// /// 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. /// internal abstract bool TryRequireSsl(out Identifier secureIdentifier); } }