using System; using System.Collections.Generic; using System.Text; using System.Globalization; using DotNetOpenId.RelyingParty; using DotNetOpenId.Yadis; using System.IO; using System.Xml; namespace DotNetOpenId { class XriIdentifier : Identifier { internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' }; const string xriScheme = "xri://"; public XriIdentifier(string xri) { if (!IsValidXri(xri)) throw new FormatException(string.Format(CultureInfo.CurrentCulture, Strings.InvalidXri, xri)); OriginalXri = xri; CanonicalXri = canonicalizeXri(xri); } /// /// The original XRI supplied to the constructor. /// public string OriginalXri { get; private set; } /// /// The canonical form of the XRI string. /// public string CanonicalXri { get; private set; } /// /// Tests whether a given string represents a valid XRI format. /// internal static bool IsValidXri(string xri) { if (string.IsNullOrEmpty(xri)) throw new ArgumentNullException("xri"); // TODO: better validation code here return xri.IndexOfAny(GlobalContextSymbols) == 0 || xri.StartsWith("(", StringComparison.Ordinal) || xri.StartsWith(xriScheme, StringComparison.OrdinalIgnoreCase); } /// /// Takes any valid form of XRI string and returns the canonical form of the same XRI. /// static string canonicalizeXri(string xri) { xri = xri.Trim(); if (xri.StartsWith(xriScheme, StringComparison.OrdinalIgnoreCase)) xri = xri.Substring(xriScheme.Length); return xri; } /// /// The magic URL that will provide us an XRDS document for a given XRI identifier. /// /// /// We use application/xrd+xml instead of application/xrds+xml because it gets /// xri.net to automatically give us exactly the right XRD element for community i-names /// automatically, saving us having to choose which one to use out of the result. /// const string xriResolverProxy = "https://xri.net/{0}?_xrd_r=application/xrd%2Bxml;sep=false"; /// /// Resolves the XRI to a URL from which an XRDS document may be downloaded. /// protected virtual Uri XrdsUrl { get { return new Uri(string.Format(CultureInfo.InvariantCulture, xriResolverProxy, this)); } } XrdsDocument downloadXrds() { var xrdsResponse = UntrustedWebRequest.Request(XrdsUrl); return new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); } internal override IEnumerable Discover() { return downloadXrds().CreateServiceEndpoints(this, this); } /// /// Performs discovery on THIS identifier, but generates /// instances that treat another given identifier as the user-supplied identifier. /// internal IEnumerable Discover(XriIdentifier userSuppliedIdentifier) { return downloadXrds().CreateServiceEndpoints(this, userSuppliedIdentifier); } internal override Identifier TrimFragment() { return this; } public override bool Equals(object obj) { XriIdentifier other = obj as XriIdentifier; if (other == null) return false; return this.CanonicalXri == other.CanonicalXri; } public override int GetHashCode() { return CanonicalXri.GetHashCode(); } public override string ToString() { return CanonicalXri; } } }