//-----------------------------------------------------------------------
//
// 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
}
}