summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs207
1 files changed, 207 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs
new file mode 100644
index 0000000..28460f1
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------
+// <copyright file="XriIdentifier.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+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;
+
+ /// <summary>
+ /// An XRI style of OpenID Identifier.
+ /// </summary>
+ [Serializable]
+ [ContractVerification(true)]
+ [Pure]
+ public sealed class XriIdentifier : Identifier {
+ /// <summary>
+ /// An XRI always starts with one of these symbols.
+ /// </summary>
+ internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' };
+
+ /// <summary>
+ /// The scheme and separator "xri://"
+ /// </summary>
+ private const string XriScheme = "xri://";
+
+ /// <summary>
+ /// Backing store for the <see cref="CanonicalXri"/> property.
+ /// </summary>
+ private readonly string canonicalXri;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XriIdentifier"/> class.
+ /// </summary>
+ /// <param name="xri">The string value of the XRI.</param>
+ internal XriIdentifier(string xri)
+ : this(xri, false) {
+ Requires.NotNullOrEmpty(xri, "xri");
+ Requires.Format(IsValidXri(xri), OpenIdStrings.InvalidXri);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XriIdentifier"/> class.
+ /// </summary>
+ /// <param name="xri">The XRI that this Identifier will represent.</param>
+ /// <param name="requireSsl">
+ /// If set to <c>true</c>, discovery and the initial authentication redirect will
+ /// only succeed if it can be done entirely using SSL.
+ /// </param>
+ 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);
+ }
+
+ /// <summary>
+ /// Gets the original XRI supplied to the constructor.
+ /// </summary>
+ internal string OriginalXri { get; private set; }
+
+ /// <summary>
+ /// Gets the canonical form of the XRI string.
+ /// </summary>
+ internal string CanonicalXri {
+ get {
+ Contract.Ensures(Contract.Result<string>() != null);
+ return this.canonicalXri;
+ }
+ }
+
+ /// <summary>
+ /// Tests equality between this XRI and another XRI.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns the hash code of this XRI.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return this.CanonicalXri.GetHashCode();
+ }
+
+ /// <summary>
+ /// Returns the canonical string form of the XRI.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ return this.CanonicalXri;
+ }
+
+ /// <summary>
+ /// Tests whether a given string represents a valid XRI format.
+ /// </summary>
+ /// <param name="xri">The value to test for XRI validity.</param>
+ /// <returns>
+ /// <c>true</c> if the given string constitutes a valid XRI; otherwise, <c>false</c>.
+ /// </returns>
+ 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);
+ }
+
+ /// <summary>
+ /// Returns an <see cref="Identifier"/> that has no URI fragment.
+ /// Quietly returns the original <see cref="Identifier"/> if it is not
+ /// a <see cref="UriIdentifier"/> or no fragment exists.
+ /// </summary>
+ /// <returns>
+ /// A new <see cref="Identifier"/> instance if there was a
+ /// fragment to remove, otherwise this same instance..
+ /// </returns>
+ /// <remarks>
+ /// XRI Identifiers never have a fragment part, and thus this method
+ /// always returns this same instance.
+ /// </remarks>
+ internal override Identifier TrimFragment() {
+ return this;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="secureIdentifier">The newly created secure identifier.
+ /// If the conversion fails, <paramref name="secureIdentifier"/> retains
+ /// <i>this</i> identifiers identity, but will never discover any endpoints.</param>
+ /// <returns>
+ /// True if the secure conversion was successful.
+ /// False if the Identifier was originally created with an explicit HTTP scheme.
+ /// </returns>
+ [ContractVerification(false)] // bugs/limitations in CC static analysis
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true);
+ return true;
+ }
+
+ /// <summary>
+ /// Takes any valid form of XRI string and returns the canonical form of the same XRI.
+ /// </summary>
+ /// <param name="xri">The xri to canonicalize.</param>
+ /// <returns>The canonicalized form of the XRI.</returns>
+ /// <remarks>The canonical form, per the OpenID spec, is no scheme and no whitespace on either end.</remarks>
+ private static string CanonicalizeXri(string xri) {
+ Requires.NotNull(xri, "xri");
+ Contract.Ensures(Contract.Result<string>() != 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
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [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
+ }
+}