summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs')
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs199
1 files changed, 127 insertions, 72 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
index 7aa4469..a915559 100644
--- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
@@ -23,6 +23,7 @@ namespace DotNetOpenAuth.Messaging {
using System.Security;
using System.Security.Cryptography;
using System.Text;
+ using System.Threading;
using System.Web;
using System.Web.Mvc;
using System.Xml;
@@ -41,11 +42,6 @@ namespace DotNetOpenAuth.Messaging {
internal static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider();
/// <summary>
- /// A pseudo-random data generator (NOT cryptographically strong random data)
- /// </summary>
- internal static readonly Random NonCryptoRandomDataGenerator = new Random();
-
- /// <summary>
/// The uppercase alphabet.
/// </summary>
internal const string UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -155,6 +151,13 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets a random number generator for use on the current thread only.
+ /// </summary>
+ internal static Random NonCryptoRandomDataGenerator {
+ get { return ThreadSafeRandom.RandomNumberGenerator; }
+ }
+
+ /// <summary>
/// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult.
/// </summary>
/// <param name="response">The response to send to the user agent.</param>
@@ -370,6 +373,64 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets the public facing URL for the given incoming HTTP request.
+ /// </summary>
+ /// <param name="request">The incoming request. Cannot be <c>null</c>.</param>
+ /// <param name="serverVariables">The server variables to consider part of the request. Cannot be <c>null</c>.</param>
+ /// <returns>
+ /// The URI that the outside world used to create this request.
+ /// </returns>
+ /// <remarks>
+ /// Although the <paramref name="serverVariables"/> value can be obtained from
+ /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them
+ /// in so we can simulate injected values from our unit tests since the actual property
+ /// is a read-only kind of <see cref="NameValueCollection"/>.
+ /// </remarks>
+ public static Uri GetPublicFacingUrl(this HttpRequestBase request, NameValueCollection serverVariables) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(serverVariables, "serverVariables");
+
+ // Due to URL rewriting, cloud computing (i.e. Azure)
+ // and web farms, etc., we have to be VERY careful about what
+ // we consider the incoming URL. We want to see the URL as it would
+ // appear on the public-facing side of the hosting web site.
+ // HttpRequest.Url gives us the internal URL in a cloud environment,
+ // So we use a variable that (at least from what I can tell) gives us
+ // the public URL:
+ if (serverVariables["HTTP_HOST"] != null) {
+ ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols.");
+ string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ??
+ (string.Equals(serverVariables["HTTP_FRONT_END_HTTPS"], "on", StringComparison.OrdinalIgnoreCase) ? Uri.UriSchemeHttps : request.Url.Scheme);
+ Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]);
+ UriBuilder publicRequestUri = new UriBuilder(request.Url);
+ publicRequestUri.Scheme = scheme;
+ publicRequestUri.Host = hostAndPort.Host;
+ publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port
+ return publicRequestUri.Uri;
+ } else {
+ // Failover to the method that works for non-web farm enviroments.
+ // We use Request.Url for the full path to the server, and modify it
+ // with Request.RawUrl to capture both the cookieless session "directory" if it exists
+ // and the original path in case URL rewriting is going on. We don't want to be
+ // fooled by URL rewriting because we're comparing the actual URL with what's in
+ // the return_to parameter in some cases.
+ // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
+ // session, but not the URL rewriting problem.
+ return new Uri(request.Url, request.RawUrl);
+ }
+ }
+
+ /// <summary>
+ /// Gets the public facing URL for the given incoming HTTP request.
+ /// </summary>
+ /// <param name="request">The incoming request. Cannot be <c>null</c>. Server variables are read from this request.</param>
+ /// <returns>The URI that the outside world used to create this request.</returns>
+ public static Uri GetPublicFacingUrl(this HttpRequestBase request) {
+ Requires.NotNull(request, "request");
+ return GetPublicFacingUrl(request, request.ServerVariables);
+ }
+
+ /// <summary>
/// Gets the URL to the root of a web site, which may include a virtual directory path.
/// </summary>
/// <returns>An absolute URI.</returns>
@@ -569,18 +630,15 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Extracts the key handle and encrypted blob from a string previously returned from <see cref="CombineKeyHandleAndPayload"/>.
/// </summary>
- /// <param name="containingMessage">The containing message.</param>
- /// <param name="messagePart">The message part.</param>
+ /// <param name="messagePart">The message part. May be null if not applicable.</param>
/// <param name="keyHandleAndBlob">The value previously returned from <see cref="CombineKeyHandleAndPayload"/>.</param>
/// <param name="handle">The crypto key handle.</param>
/// <param name="dataBlob">The encrypted/signed data.</param>
- internal static void ExtractKeyHandleAndPayload(IProtocolMessage containingMessage, string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) {
- Requires.NotNull(containingMessage, "containingMessage");
- Requires.NotNullOrEmpty(messagePart, "messagePart");
+ internal static void ExtractKeyHandleAndPayload(string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) {
Requires.NotNullOrEmpty(keyHandleAndBlob, "keyHandleAndBlob");
int privateHandleIndex = keyHandleAndBlob.IndexOf('!');
- ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart, keyHandleAndBlob);
+ ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart ?? "<unknown>", keyHandleAndBlob);
handle = keyHandleAndBlob.Substring(0, privateHandleIndex);
dataBlob = keyHandleAndBlob.Substring(privateHandleIndex + 1);
}
@@ -608,7 +666,7 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Gets a cryptographically strong random sequence of values.
+ /// Gets a cryptographically strong random string of base64 characters.
/// </summary>
/// <param name="binaryLength">The length of the byte sequence to generate.</param>
/// <returns>A base64 encoding of the generated random data,
@@ -620,6 +678,21 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets a NON-cryptographically strong random string of base64 characters.
+ /// </summary>
+ /// <param name="binaryLength">The length of the byte sequence to generate.</param>
+ /// <param name="useWeb64">A value indicating whether web64 encoding is used to avoid the need to escape characters.</param>
+ /// <returns>
+ /// A base64 encoding of the generated random data,
+ /// whose length in characters will likely be greater than <paramref name="binaryLength" />.
+ /// </returns>
+ internal static string GetNonCryptoRandomDataAsBase64(int binaryLength, bool useWeb64 = false) {
+ byte[] uniq_bytes = GetNonCryptoRandomData(binaryLength);
+ string uniq = useWeb64 ? ConvertToBase64WebSafeString(uniq_bytes) : Convert.ToBase64String(uniq_bytes);
+ return uniq;
+ }
+
+ /// <summary>
/// Gets a random string made up of a given set of allowable characters.
/// </summary>
/// <param name="length">The length of the desired random string.</param>
@@ -630,8 +703,9 @@ namespace DotNetOpenAuth.Messaging {
Requires.True(allowableCharacters != null && allowableCharacters.Length >= 2, "allowableCharacters");
char[] randomString = new char[length];
+ var random = NonCryptoRandomDataGenerator;
for (int i = 0; i < length; i++) {
- randomString[i] = allowableCharacters[NonCryptoRandomDataGenerator.Next(allowableCharacters.Length)];
+ randomString[i] = allowableCharacters[random.Next(allowableCharacters.Length)];
}
return new string(randomString);
@@ -1491,6 +1565,10 @@ namespace DotNetOpenAuth.Messaging {
return HttpDeliveryMethods.DeleteRequest;
} else if (httpVerb == "HEAD") {
return HttpDeliveryMethods.HeadRequest;
+ } else if (httpVerb == "PATCH") {
+ return HttpDeliveryMethods.PatchRequest;
+ } else if (httpVerb == "OPTIONS") {
+ return HttpDeliveryMethods.OptionsRequest;
} else {
throw ErrorUtilities.ThrowArgumentNamed("httpVerb", MessagingStrings.UnsupportedHttpVerb, httpVerb);
}
@@ -1500,7 +1578,7 @@ namespace DotNetOpenAuth.Messaging {
/// Gets the HTTP verb to use for a given <see cref="HttpDeliveryMethods"/> enum value.
/// </summary>
/// <param name="httpMethod">The HTTP method.</param>
- /// <returns>An HTTP verb, such as GET, POST, PUT, or DELETE.</returns>
+ /// <returns>An HTTP verb, such as GET, POST, PUT, DELETE, PATCH, or OPTION.</returns>
internal static string GetHttpVerb(HttpDeliveryMethods httpMethod) {
if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.GetRequest) {
return "GET";
@@ -1512,6 +1590,10 @@ namespace DotNetOpenAuth.Messaging {
return "DELETE";
} else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.HeadRequest) {
return "HEAD";
+ } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PatchRequest) {
+ return "PATCH";
+ } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.OptionsRequest) {
+ return "OPTIONS";
} else if ((httpMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) {
return "GET"; // if AuthorizationHeaderRequest is specified without an explicit HTTP verb, assume GET.
} else {
@@ -1901,64 +1983,6 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Gets the public facing URL for the given incoming HTTP request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="serverVariables">The server variables to consider part of the request.</param>
- /// <returns>
- /// The URI that the outside world used to create this request.
- /// </returns>
- /// <remarks>
- /// Although the <paramref name="serverVariables"/> value can be obtained from
- /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them
- /// in so we can simulate injected values from our unit tests since the actual property
- /// is a read-only kind of <see cref="NameValueCollection"/>.
- /// </remarks>
- internal static Uri GetPublicFacingUrl(this HttpRequestBase request, NameValueCollection serverVariables) {
- Requires.NotNull(request, "request");
- Requires.NotNull(serverVariables, "serverVariables");
-
- // Due to URL rewriting, cloud computing (i.e. Azure)
- // and web farms, etc., we have to be VERY careful about what
- // we consider the incoming URL. We want to see the URL as it would
- // appear on the public-facing side of the hosting web site.
- // HttpRequest.Url gives us the internal URL in a cloud environment,
- // So we use a variable that (at least from what I can tell) gives us
- // the public URL:
- if (serverVariables["HTTP_HOST"] != null) {
- ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols.");
- string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ??
- (string.Equals(serverVariables["HTTP_FRONT_END_HTTPS"], "on", StringComparison.OrdinalIgnoreCase) ? Uri.UriSchemeHttps : request.Url.Scheme);
- Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]);
- UriBuilder publicRequestUri = new UriBuilder(request.Url);
- publicRequestUri.Scheme = scheme;
- publicRequestUri.Host = hostAndPort.Host;
- publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port
- return publicRequestUri.Uri;
- } else {
- // Failover to the method that works for non-web farm enviroments.
- // We use Request.Url for the full path to the server, and modify it
- // with Request.RawUrl to capture both the cookieless session "directory" if it exists
- // and the original path in case URL rewriting is going on. We don't want to be
- // fooled by URL rewriting because we're comparing the actual URL with what's in
- // the return_to parameter in some cases.
- // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
- // session, but not the URL rewriting problem.
- return new Uri(request.Url, request.RawUrl);
- }
- }
-
- /// <summary>
- /// Gets the public facing URL for the given incoming HTTP request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>The URI that the outside world used to create this request.</returns>
- internal static Uri GetPublicFacingUrl(this HttpRequestBase request) {
- Requires.NotNull(request, "request");
- return GetPublicFacingUrl(request, request.ServerVariables);
- }
-
- /// <summary>
/// Gets the query or form data from the original request (before any URL rewriting has occurred.)
/// </summary>
/// <param name="request">The request.</param>
@@ -1995,6 +2019,37 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// A thread-safe, non-crypto random number generator.
+ /// </summary>
+ private static class ThreadSafeRandom {
+ /// <summary>
+ /// The initializer of all new <see cref="Random"/> instances.
+ /// </summary>
+ private static readonly Random threadRandomInitializer = new Random();
+
+ /// <summary>
+ /// A thread-local instance of <see cref="Random"/>
+ /// </summary>
+ [ThreadStatic]
+ private static Random threadRandom;
+
+ /// <summary>
+ /// Gets a random number generator for use on the current thread only.
+ /// </summary>
+ public static Random RandomNumberGenerator {
+ get {
+ if (threadRandom == null) {
+ lock (threadRandomInitializer) {
+ threadRandom = new Random(threadRandomInitializer.Next());
+ }
+ }
+
+ return threadRandom;
+ }
+ }
+ }
+
+ /// <summary>
/// A class to convert a <see cref="Comparison&lt;T&gt;"/> into an <see cref="IComparer&lt;T&gt;"/>.
/// </summary>
/// <typeparam name="T">The type of objects being compared.</typeparam>