diff options
Diffstat (limited to 'src/DotNetOpenAuth.Core/Messaging')
14 files changed, 274 insertions, 100 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs index 5126897..88b8fed 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs @@ -22,7 +22,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { public ExpiredMessageException(DateTime utcExpirationDate, IProtocolMessage faultedMessage) : base(string.Format(CultureInfo.CurrentCulture, MessagingStrings.ExpiredMessage, utcExpirationDate.ToLocalTime(), DateTime.Now), faultedMessage) { Requires.True(utcExpirationDate.Kind == DateTimeKind.Utc, "utcExpirationDate"); - Requires.NotNull(faultedMessage, "faultedMessage"); } /// <summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs index 672a942..f8ac6a1 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs @@ -650,10 +650,12 @@ namespace DotNetOpenAuth.Messaging { protected static bool HttpMethodHasEntity(string httpMethod) { if (string.Equals(httpMethod, "GET", StringComparison.Ordinal) || string.Equals(httpMethod, "HEAD", StringComparison.Ordinal) || - string.Equals(httpMethod, "DELETE", StringComparison.Ordinal)) { + string.Equals(httpMethod, "DELETE", StringComparison.Ordinal) || + string.Equals(httpMethod, "OPTIONS", StringComparison.Ordinal)) { return false; } else if (string.Equals(httpMethod, "POST", StringComparison.Ordinal) || - string.Equals(httpMethod, "PUT", StringComparison.Ordinal)) { + string.Equals(httpMethod, "PUT", StringComparison.Ordinal) || + string.Equals(httpMethod, "PATCH", StringComparison.Ordinal)) { return true; } else { throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs index 2452502..e7ac254 100644 --- a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs +++ b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs @@ -193,20 +193,18 @@ namespace DotNetOpenAuth.Messaging { /// Deserializes a <see cref="DataBag"/>, including decompression, decryption, signature and nonce validation where applicable. /// </summary> /// <param name="message">The instance to initialize with deserialized data.</param> - /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be null.</param> /// <param name="value">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> - /// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation.</param> + /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. May be null if no carrying message is applicable.</param> + /// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation, but may be <c>null</c>.</param> [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")] - public void Deserialize(T message, IProtocolMessage containingMessage, string value, string messagePartName) { + public void Deserialize(T message, string value, IProtocolMessage containingMessage, string messagePartName) { Requires.NotNull(message, "message"); - Requires.NotNull(containingMessage, "containingMessage"); Requires.NotNullOrEmpty(value, "value"); - Requires.NotNullOrEmpty(messagePartName, "messagePartName"); string symmetricSecretHandle = null; if (this.encrypted && this.cryptoKeyStore != null) { string valueWithoutHandle; - MessagingUtilities.ExtractKeyHandleAndPayload(containingMessage, messagePartName, value, out symmetricSecretHandle, out valueWithoutHandle); + MessagingUtilities.ExtractKeyHandleAndPayload(messagePartName, value, out symmetricSecretHandle, out valueWithoutHandle); value = valueWithoutHandle; } diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs b/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs index c92b9de..dbd3855 100644 --- a/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs +++ b/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs @@ -48,11 +48,21 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3). /// </summary> - HeadRequest = 0x20, + HeadRequest = 0x20,
+
+ /// <summary>
+ /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3).
+ /// </summary> + PatchRequest = 0x40,
+
+ /// <summary>
+ /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3).
+ /// </summary> + OptionsRequest = 0x80, /// <summary> /// The flags that control HTTP verbs. - /// </summary> - HttpVerbMask = PostRequest | GetRequest | PutRequest | DeleteRequest | HeadRequest, + /// </summary>
+ HttpVerbMask = PostRequest | GetRequest | PutRequest | DeleteRequest | HeadRequest | PatchRequest | OptionsRequest, } } diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs index 4b4a3fe..75da833 100644 --- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs @@ -61,6 +61,11 @@ namespace DotNetOpenAuth.Messaging { private readonly NameValueCollection serverVariables; /// <summary> + /// The backing field for the <see cref="Cookies"/> property. + /// </summary> + private readonly HttpCookieCollection cookies; + + /// <summary> /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. /// </summary> /// <param name="request">The request.</param> @@ -75,6 +80,7 @@ namespace DotNetOpenAuth.Messaging { this.form = new NameValueCollection(); this.queryString = HttpUtility.ParseQueryString(requestUri.Query); this.serverVariables = new NameValueCollection(); + this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } @@ -86,7 +92,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="requestUri">The request URI.</param> /// <param name="form">The form variables.</param> /// <param name="headers">The HTTP headers.</param> - internal HttpRequestInfo(string httpMethod, Uri requestUri, NameValueCollection form = null, NameValueCollection headers = null) { + /// <param name="cookies">The cookies in the request.</param> + internal HttpRequestInfo(string httpMethod, Uri requestUri, NameValueCollection form = null, NameValueCollection headers = null, HttpCookieCollection cookies = null) { Requires.NotNullOrEmpty(httpMethod, "httpMethod"); Requires.NotNull(requestUri, "requestUri"); @@ -96,6 +103,7 @@ namespace DotNetOpenAuth.Messaging { this.queryString = HttpUtility.ParseQueryString(requestUri.Query); this.headers = headers ?? new WebHeaderCollection(); this.serverVariables = new NameValueCollection(); + this.cookies = cookies ?? new HttpCookieCollection(); } /// <summary> @@ -111,6 +119,7 @@ namespace DotNetOpenAuth.Messaging { this.headers = listenerRequest.Headers; this.form = ParseFormData(listenerRequest.HttpMethod, listenerRequest.Headers, () => listenerRequest.InputStream); this.serverVariables = new NameValueCollection(); + this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } @@ -131,6 +140,7 @@ namespace DotNetOpenAuth.Messaging { AddHeaders(this.headers, request.Content.Headers); this.form = ParseFormData(this.httpMethod, this.headers, () => request.Content.ReadAsStreamAsync().Result); this.serverVariables = new NameValueCollection(); + this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } @@ -153,6 +163,7 @@ namespace DotNetOpenAuth.Messaging { this.queryString = HttpUtility.ParseQueryString(requestUri.Query); this.form = ParseFormData(httpMethod, headers, () => inputStream); this.serverVariables = new NameValueCollection(); + this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } @@ -207,6 +218,14 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets the collection of cookies that were sent by the client. + /// </summary> + /// <returns>The client's cookies.</returns> + public override HttpCookieCollection Cookies { + get { return this.cookies; } + } + + /// <summary> /// Creates an <see cref="HttpRequestBase"/> instance that describes the specified HTTP request. /// </summary> /// <param name="request">The request.</param> diff --git a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs index 923773e..0d1ab03 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs @@ -25,10 +25,10 @@ namespace DotNetOpenAuth.Messaging { /// Deserializes a <see cref="DataBag"/>. /// </summary> /// <param name="message">The instance to deserialize into</param> - /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be null.</param> /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> - /// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation.</param> - void Deserialize(T message, IProtocolMessage containingMessage, string data, string messagePartName); + /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. May be null if no carrying message is applicable.</param> + /// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation, but may be <c>null</c>.</param> + void Deserialize(T message, string data, IProtocolMessage containingMessage = null, string messagePartName = null); } /// <summary> @@ -61,10 +61,10 @@ namespace DotNetOpenAuth.Messaging { /// Deserializes a <see cref="DataBag"/>. /// </summary> /// <param name="message">The instance to deserialize into</param> - /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> + /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> /// <param name="messagePartName">Name of the message part whose value is to be deserialized. Used for exception messages.</param> - void IDataBagFormatter<T>.Deserialize(T message, IProtocolMessage containingMessage, string data, string messagePartName) { + void IDataBagFormatter<T>.Deserialize(T message, string data, IProtocolMessage containingMessage, string messagePartName) { Requires.NotNull(message, "message"); Requires.NotNull(containingMessage, "containingMessage"); Requires.NotNullOrEmpty(data, "data"); diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs index 15df48a..7391867 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs @@ -97,9 +97,10 @@ namespace DotNetOpenAuth.Messaging { Contract.Assume(partDescription != null); if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { include = true; - if (IsNumeric(partDescription.MemberDeclaredType)) { + Type formattingType = partDescription.PreferredFormattingType; + if (IsNumeric(formattingType)) { type = "number"; - } else if (partDescription.MemberDeclaredType.IsAssignableFrom(typeof(bool))) { + } else if (formattingType.IsAssignableFrom(typeof(bool))) { type = "boolean"; } } diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs index 7aa4469..80703c1 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,18 @@ 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> + /// <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) { + byte[] uniq_bytes = GetNonCryptoRandomData(binaryLength); + string uniq = 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 +700,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 +1562,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 +1575,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 +1587,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 +1980,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 +2016,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<T>"/> into an <see cref="IComparer<T>"/>. /// </summary> /// <typeparam name="T">The type of objects being compared.</typeparam> diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs index 9ef89e9..e1e9d53 100644 --- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs +++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs @@ -43,6 +43,7 @@ namespace DotNetOpenAuth.Messaging { internal OutgoingWebResponse() { this.Status = HttpStatusCode.OK; this.Headers = new WebHeaderCollection(); + this.Cookies = new HttpCookieCollection(); } /// <summary> @@ -56,6 +57,7 @@ namespace DotNetOpenAuth.Messaging { this.Status = response.StatusCode; this.Headers = response.Headers; + this.Cookies = new HttpCookieCollection(); this.ResponseStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : (int)response.ContentLength); using (Stream responseStream = response.GetResponseStream()) { // BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here. @@ -86,6 +88,11 @@ namespace DotNetOpenAuth.Messaging { public bool IsResponseTruncated { get; internal set; } /// <summary> + /// Gets the cookies collection to add as headers to the HTTP response. + /// </summary> + public HttpCookieCollection Cookies { get; internal set; } + + /// <summary> /// Gets or sets the body of the response as a string. /// </summary> public string Body { @@ -239,6 +246,17 @@ namespace DotNetOpenAuth.Messaging { response.StatusCode = (int)this.Status; MessagingUtilities.ApplyHeadersToResponse(this.Headers, response); + foreach (HttpCookie httpCookie in this.Cookies) { + var cookie = new Cookie(httpCookie.Name, httpCookie.Value) { + Expires = httpCookie.Expires, + Path = httpCookie.Path, + HttpOnly = httpCookie.HttpOnly, + Secure = httpCookie.Secure, + Domain = httpCookie.Domain, + }; + response.AppendCookie(cookie); + } + if (this.ResponseStream != null) { response.ContentLength64 = this.ResponseStream.Length; this.ResponseStream.CopyTo(response.OutputStream); @@ -346,6 +364,11 @@ namespace DotNetOpenAuth.Messaging { } } + foreach (string cookieName in this.Cookies) { + var cookie = this.Cookies[cookieName]; + context.Response.AppendCookie(cookie); + } + if (endRequest) { // This approach throws an exception in order that // no more code is executed in the calling page. diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartFormattingEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartFormattingEncoder.cs new file mode 100644 index 0000000..66b7f3c --- /dev/null +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartFormattingEncoder.cs @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------- +// <copyright file="IMessagePartFormattingEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// An interface describing how various objects can be serialized and deserialized between their object and string forms. + /// </summary> + /// <remarks> + /// Implementations of this interface must include a default constructor and must be thread-safe. + /// </remarks> + public interface IMessagePartFormattingEncoder : IMessagePartEncoder { + /// <summary> + /// Gets the type of the encoded values produced by this encoder, as they would appear in their preferred form. + /// </summary> + Type FormattingType { get; } + } +} diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs index 0f140d6..a6e8da2 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs @@ -216,6 +216,20 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> + /// Gets the type of the encoded values produced by this encoder, as they would appear in their preferred form. + /// </summary> + internal Type PreferredFormattingType { + get { + var formattingEncoder = this.converter.Encoder as IMessagePartFormattingEncoder; + if (formattingEncoder != null) { + return formattingEncoder.FormattingType; + } + + return this.MemberDeclaredType; + } + } + + /// <summary> /// Sets the member of a given message to some given value. /// Used in deserialization. /// </summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs index bc12f5d..4139f52 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs @@ -35,7 +35,8 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="toString">The mapping function that converts some custom value to a string.</param> /// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param> /// <param name="toValue">The mapping function that converts a string to some custom value.</param> - internal ValueMapping(Func<object, string> toString, Func<object, string> toOriginalString, Func<string, object> toValue) { + internal ValueMapping(Func<object, string> toString, Func<object, string> toOriginalString, Func<string, object> toValue) + : this() { Requires.NotNull(toString, "toString"); Requires.NotNull(toValue, "toValue"); @@ -48,8 +49,11 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// Initializes a new instance of the <see cref="ValueMapping"/> struct. /// </summary> /// <param name="encoder">The encoder.</param> - internal ValueMapping(IMessagePartEncoder encoder) { + internal ValueMapping(IMessagePartEncoder encoder) + : this() { Requires.NotNull(encoder, "encoder"); + + this.Encoder = encoder; var nullEncoder = encoder as IMessagePartNullEncoder; string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null; @@ -63,5 +67,10 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.StringToValue = str => (str != null) ? encoder.Decode(str) : null; this.ValueToOriginalString = obj => (obj != null) ? originalStringEncode(obj) : nullString; } + + /// <summary> + /// Gets the encoder. + /// </summary> + internal IMessagePartEncoder Encoder { get; private set; } } } diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs index 114c191..adca925 100644 --- a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs +++ b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs @@ -151,14 +151,25 @@ namespace DotNetOpenAuth.Messaging { return new NetworkDirectWebResponse(request.RequestUri, response); } - if (Logger.Http.IsErrorEnabled) { - if (response != null) { + if (response != null) { + Logger.Http.ErrorFormat( + "{0} returned {1} {2}: {3}", + response.ResponseUri, + (int)response.StatusCode, + response.StatusCode, + response.StatusDescription); + + if (Logger.Http.IsDebugEnabled) { using (var reader = new StreamReader(ex.Response.GetResponseStream())) { - Logger.Http.ErrorFormat("WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd()); + Logger.Http.DebugFormat( + "WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd()); } - } else { - Logger.Http.ErrorFormat("WebException {1} from {0}, no response available.", request.RequestUri, ex.Status); } + } else { + Logger.Http.ErrorFormat( + "{0} connecting to {0}", + ex.Status, + request.RequestUri); } // Be sure to close the response stream to conserve resources and avoid diff --git a/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs index 842176a..abce20a 100644 --- a/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs +++ b/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs @@ -12,7 +12,7 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Encodes and decodes the <see cref="TimeSpan"/> as an integer of total seconds. /// </summary> - internal class TimespanSecondsEncoder : IMessagePartEncoder { + internal class TimespanSecondsEncoder : IMessagePartEncoder, IMessagePartFormattingEncoder { /// <summary> /// Initializes a new instance of the <see cref="TimespanSecondsEncoder"/> class. /// </summary> @@ -20,6 +20,17 @@ namespace DotNetOpenAuth.Messaging { // Note that this constructor is public so it can be instantiated via Activator. } + #region IMessagePartFormattingEncoder members + + /// <summary> + /// Gets the type of the encoded values produced by this encoder, as they would appear in their preferred form. + /// </summary> + public Type FormattingType { + get { return typeof(int); } + } + + #endregion + #region IMessagePartEncoder Members /// <summary> |