summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-03-30 21:38:14 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2010-03-30 21:38:14 -0700
commit6cbab280a27cba2e3fe997f9b6d9d8b57cfbb4d5 (patch)
tree4e1bc969cd38f7e8e8f18c4e15c88a5fc05c4822 /src
parentb124481fa620f054dacb76884da1c58764f5fb14 (diff)
downloadDotNetOpenAuth-6cbab280a27cba2e3fe997f9b6d9d8b57cfbb4d5.zip
DotNetOpenAuth-6cbab280a27cba2e3fe997f9b6d9d8b57cfbb4d5.tar.gz
DotNetOpenAuth-6cbab280a27cba2e3fe997f9b6d9d8b57cfbb4d5.tar.bz2
In full trust environments, it seems that identifiers trailing periods in path segments work now.
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs26
-rw-r--r--src/DotNetOpenAuth/OpenId/UriIdentifier.cs151
2 files changed, 172 insertions, 5 deletions
diff --git a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
index bfdc1a1..f1caa43 100644
--- a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
@@ -170,6 +170,23 @@ namespace DotNetOpenAuth.Test.OpenId {
Assert.AreEqual(claimedIdentifier, idUri.ToString());
Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri);
Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString());
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString);
+ Assert.AreEqual(id.ToString(), new UriIdentifier(idUri.Uri).ToString());
+
+ idUri = new UriIdentifier(new Uri(claimedIdentifier));
+ Assert.AreEqual(claimedIdentifier, idUri.OriginalString);
+ Assert.AreEqual(claimedIdentifier, idUri.ToString());
+ Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri);
+ Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString());
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString);
+ Assert.AreEqual(id.ToString(), new UriIdentifier(idUri.Uri).ToString());
+
+ claimedIdentifier = "https://me.yahoo.com:443/a/AsDf.#asdf";
+ id = claimedIdentifier;
+ Assert.AreEqual(claimedIdentifier, id.OriginalString);
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.#asdf", id.ToString());
}
[TestCase]
@@ -215,5 +232,14 @@ namespace DotNetOpenAuth.Test.OpenId {
Assert.AreEqual("http://www.yahoo.com/", secureId.ToString());
Assert.AreEqual(0, Discover(secureId).Count());
}
+
+ /// <summary>
+ /// Verifies that unicode hostnames are handled.
+ /// </summary>
+ [TestCase]
+ public void UnicodeHostSupport() {
+ var id = new UriIdentifier("http://server崎/村");
+ Assert.AreEqual("server崎", id.Uri.Host);
+ }
}
}
diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
index 0b7a0e3..85f270e 100644
--- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
+++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
@@ -10,6 +10,8 @@ namespace DotNetOpenAuth.OpenId {
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
+ using System.Reflection;
+ using System.Security;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;
using System.Xml;
@@ -30,6 +32,54 @@ namespace DotNetOpenAuth.OpenId {
private static readonly string[] allowedSchemes = { "http", "https" };
/// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// A value indicating whether scheme substitution is being used to workaround
+ /// .NET path compression that invalidates some OpenIDs that have trailing periods
+ /// in one of their path segments.
+ /// </summary>
+ private static bool schemeSubstitution;
+
+ /// <summary>
+ /// Initializes static members of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ static UriIdentifier() {
+ try {
+ UriParser.Register(roundTrippingHttpParser, "dnoarthttp", 80);
+ UriParser.Register(roundTrippingHttpsParser, "dnoarthttps", 443);
+ UriParser.Register(publishableHttpParser, "dnoahttp", 80);
+ UriParser.Register(publishableHttpsParser, "dnoahttps", 443);
+ roundTrippingHttpParser.Initialize(false);
+ roundTrippingHttpsParser.Initialize(false);
+ publishableHttpParser.Initialize(true);
+ publishableHttpsParser.Initialize(true);
+ schemeSubstitution = true;
+ } catch (SecurityException) {
+ // We must be running in partial trust. Nothing more we can do.
+ Logger.OpenId.Warn("Unable to coerce .NET to stop compressing URI paths due to partial trust limitations. Some URL identifiers may be unable to complete login.");
+ schemeSubstitution = false;
+ }
+ }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="UriIdentifier"/> class.
/// </summary>
/// <param name="uri">The value this identifier will represent.</param>
@@ -62,7 +112,8 @@ namespace DotNetOpenAuth.OpenId {
/// Initializes a new instance of the <see cref="UriIdentifier"/> class.
/// </summary>
/// <param name="uri">The value this identifier will represent.</param>
- internal UriIdentifier(Uri uri) : this(uri, false) {
+ internal UriIdentifier(Uri uri)
+ : this(uri, false) {
}
/// <summary>
@@ -73,7 +124,13 @@ namespace DotNetOpenAuth.OpenId {
internal UriIdentifier(Uri uri, bool requireSslDiscovery)
: base(uri != null ? uri.OriginalString : null, requireSslDiscovery) {
Contract.Requires<ArgumentNullException>(uri != null);
- if (!TryCanonicalize(new UriBuilder(uri), out uri)) {
+
+ string uriAsString = uri.OriginalString;
+ if (schemeSubstitution) {
+ uriAsString = NormalSchemeToSpecialRoundTrippingScheme(uriAsString);
+ }
+
+ if (!TryCanonicalize(new UriBuilder(uriAsString), out uri)) {
throw new UriFormatException();
}
if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) {
@@ -222,9 +279,7 @@ namespace DotNetOpenAuth.OpenId {
}
// Strip the fragment.
- UriBuilder builder = new UriBuilder(Uri);
- builder.Fragment = null;
- return builder.Uri;
+ return new UriIdentifier(this.OriginalString.Substring(0, this.OriginalString.IndexOf('#')));
}
/// <summary>
@@ -335,6 +390,10 @@ namespace DotNetOpenAuth.OpenId {
schemePrepended = true;
}
+ if (schemeSubstitution) {
+ uri = NormalSchemeToSpecialRoundTrippingScheme(uri);
+ }
+
// Use a UriBuilder because it helps to normalize the URL as well.
return TryCanonicalize(new UriBuilder(uri), out canonicalUri);
} catch (UriFormatException) {
@@ -358,10 +417,41 @@ namespace DotNetOpenAuth.OpenId {
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The user will see the result of this operation and they want to see it in lower case.")]
private static bool TryCanonicalize(UriBuilder uriBuilder, out Uri canonicalUri) {
uriBuilder.Host = uriBuilder.Host.ToLowerInvariant();
+
+ if (schemeSubstitution) {
+ // Swap out our round-trippable scheme for the publishable (hidden) scheme.
+ uriBuilder.Scheme = uriBuilder.Scheme == roundTrippingHttpParser.RegisteredScheme ? publishableHttpParser.RegisteredScheme : publishableHttpsParser.RegisteredScheme;
+ }
+
canonicalUri = uriBuilder.Uri;
+ ////ImprintStandardOriginalString(canonicalUri);
return true;
}
+ /// <summary>
+ /// Gets the special non-compressing scheme or URL for a standard scheme or URL.
+ /// </summary>
+ /// <param name="normal">The ordinary URL or scheme name.</param>
+ /// <returns>The non-compressing equivalent scheme or URL for the given value.</returns>
+ private static string NormalSchemeToSpecialRoundTrippingScheme(string normal) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(normal));
+
+ int delimiterIndex = normal.IndexOf(Uri.SchemeDelimiter);
+ string normalScheme = delimiterIndex < 0 ? normal : normal.Substring(0, delimiterIndex);
+ string nonCompressingScheme;
+ if (string.Equals(normalScheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpParser.RegisteredScheme;
+ } else if (string.Equals(normalScheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpsParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpsParser.RegisteredScheme;
+ } else {
+ throw new NotSupportedException();
+ }
+
+ return delimiterIndex < 0 ? nonCompressingScheme : nonCompressingScheme + normal.Substring(delimiterIndex);
+ }
+
#if CONTRACTS_FULL
/// <summary>
/// Verifies conditions that should be true for any valid state of this object.
@@ -374,5 +464,56 @@ namespace DotNetOpenAuth.OpenId {
Contract.Invariant(this.Uri.AbsoluteUri != null);
}
#endif
+
+ /// <summary>
+ /// A URI parser that does not compress paths, such as trimming trailing periods from path segments.
+ /// </summary>
+ private class NonPathCompressingUriParser : GenericUriParser {
+ /// <summary>
+ /// The field that stores the scheme that this parser is registered under.
+ /// </summary>
+ private static FieldInfo schemeField;
+
+ /// <summary>
+ /// The standard "http" or "https" scheme that this parser is subverting.
+ /// </summary>
+ private string standardScheme;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NonPathCompressingUriParser"/> class.
+ /// </summary>
+ /// <param name="standardScheme">The standard scheme that this parser will be subverting.</param>
+ public NonPathCompressingUriParser(string standardScheme)
+ : base(GenericUriParserOptions.DontCompressPath | GenericUriParserOptions.IriParsing | GenericUriParserOptions.Idn) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(standardScheme));
+ this.standardScheme = standardScheme;
+ }
+
+ /// <summary>
+ /// Gets the scheme this parser is registered under.
+ /// </summary>
+ /// <value>The registered scheme.</value>
+ internal string RegisteredScheme { get; private set; }
+
+ /// <summary>
+ /// Initializes this parser with the actual scheme it should appear to be.
+ /// </summary>
+ /// <param name="hideNonStandardScheme">if set to <c>true</c> Uris using this scheme will look like they're using the original standard scheme.</param>
+ /// <returns>
+ /// A value indicating whether this parser will be able to complete its task.
+ /// It can return <c>false</c> under partial trust conditions.
+ /// </returns>
+ internal void Initialize(bool hideNonStandardScheme) {
+ if (schemeField == null) {
+ schemeField = typeof(UriParser).GetField("m_Scheme", BindingFlags.NonPublic | BindingFlags.Instance);
+ }
+
+ this.RegisteredScheme = (string)schemeField.GetValue(this);
+
+ if (hideNonStandardScheme) {
+ schemeField.SetValue(this, this.standardScheme.ToLowerInvariant());
+ }
+ }
+ }
}
}