diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2013-03-04 13:54:33 -0800 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2013-03-04 13:54:33 -0800 |
commit | 36bbbea5002c889558a67c380e46dff668251b25 (patch) | |
tree | 87721c7f13cfd27b75f7132b71549688b55eb14a /src/DotNetOpenAuth.Core/Messaging | |
parent | fd85211bfc50805d740392bfc6770df7c6f4c7d0 (diff) | |
download | DotNetOpenAuth-36bbbea5002c889558a67c380e46dff668251b25.zip DotNetOpenAuth-36bbbea5002c889558a67c380e46dff668251b25.tar.gz DotNetOpenAuth-36bbbea5002c889558a67c380e46dff668251b25.tar.bz2 |
Switched Channel to receiving messages via HttpRequestMessage as well.
Diffstat (limited to 'src/DotNetOpenAuth.Core/Messaging')
-rw-r--r-- | src/DotNetOpenAuth.Core/Messaging/Channel.cs | 71 | ||||
-rw-r--r-- | src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs | 2 | ||||
-rw-r--r-- | src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs | 176 |
3 files changed, 118 insertions, 131 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs index e36bb94..8564e38 100644 --- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs +++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs @@ -325,21 +325,6 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Gets the protocol message embedded in the given HTTP request, if present. /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns> - /// The deserialized message, if one is found. Null otherwise. - /// </returns> - /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current" /> is null.</exception> - /// <remarks> - /// Requires an HttpContext.Current context. - /// </remarks> - public Task<IDirectedProtocolMessage> ReadFromRequestAsync(CancellationToken cancellationToken) { - return this.ReadFromRequestAsync(this.GetRequestFromContext(), cancellationToken); - } - - /// <summary> - /// Gets the protocol message embedded in the given HTTP request, if present. - /// </summary> /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="httpRequest">The request to search for an embedded message.</param> @@ -348,9 +333,9 @@ namespace DotNetOpenAuth.Messaging { /// </returns> /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current" /> is null.</exception> /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> - public async Task<TRequest> TryReadFromRequestAsync<TRequest>(CancellationToken cancellationToken, HttpRequestBase httpRequest = null) + public async Task<TRequest> TryReadFromRequestAsync<TRequest>(HttpRequestMessage httpRequest, CancellationToken cancellationToken) where TRequest : class, IProtocolMessage { - httpRequest = httpRequest ?? this.GetRequestFromContext(); + Requires.NotNull(httpRequest, "httpRequest"); IProtocolMessage untypedRequest = await this.ReadFromRequestAsync(httpRequest, cancellationToken); if (untypedRequest == null) { @@ -373,10 +358,11 @@ namespace DotNetOpenAuth.Messaging { /// </returns> /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] - public async Task<TRequest> ReadFromRequestAsync<TRequest>(CancellationToken cancellationToken, HttpRequestBase httpRequest = null) + public async Task<TRequest> ReadFromRequestAsync<TRequest>(HttpRequestMessage httpRequest, CancellationToken cancellationToken) where TRequest : class, IProtocolMessage { - httpRequest = httpRequest ?? this.GetRequestFromContext(); - TRequest request = await this.TryReadFromRequestAsync<TRequest>(cancellationToken, httpRequest); + Requires.NotNull(httpRequest, "httpRequest"); + + TRequest request = await this.TryReadFromRequestAsync<TRequest>(httpRequest, cancellationToken); ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest)); return request; } @@ -389,20 +375,20 @@ namespace DotNetOpenAuth.Messaging { /// <returns> /// The deserialized message, if one is found. Null otherwise. /// </returns> - public async Task<IDirectedProtocolMessage> ReadFromRequestAsync(HttpRequestBase httpRequest, CancellationToken cancellationToken) { + public async Task<IDirectedProtocolMessage> ReadFromRequestAsync(HttpRequestMessage httpRequest, CancellationToken cancellationToken) { Requires.NotNull(httpRequest, "httpRequest"); - if (Logger.Channel.IsInfoEnabled && httpRequest.GetPublicFacingUrl() != null) { - Logger.Channel.InfoFormat("Scanning incoming request for messages: {0}", httpRequest.GetPublicFacingUrl().AbsoluteUri); + if (Logger.Channel.IsInfoEnabled && httpRequest.RequestUri != null) { + Logger.Channel.InfoFormat("Scanning incoming request for messages: {0}", httpRequest.RequestUri.AbsoluteUri); } - IDirectedProtocolMessage requestMessage = this.ReadFromRequestCore(httpRequest, cancellationToken); + IDirectedProtocolMessage requestMessage = await this.ReadFromRequestCoreAsync(httpRequest, cancellationToken); if (requestMessage != null) { Logger.Channel.DebugFormat("Incoming request received: {0}", requestMessage.GetType().Name); var directRequest = requestMessage as IHttpDirectRequest; if (directRequest != null) { - foreach (string header in httpRequest.Headers) { - directRequest.Headers[header] = httpRequest.Headers[header]; + foreach (var header in httpRequest.Headers) { + directRequest.Headers.Add(header.Key, header.Value); } } @@ -539,6 +525,21 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Parses the URL encoded form content. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>A sequence of key=value pairs found in the request's entity; or an empty sequence if none are found.</returns> + protected internal static async Task<IEnumerable<KeyValuePair<string, string>>> ParseUrlEncodedFormContentAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + if (request.Content != null && request.Content.Headers.ContentType != null + && request.Content.Headers.ContentType.MediaType.Equals(HttpFormUrlEncoded)) { + return HttpUtility.ParseQueryString(await request.Content.ReadAsStringAsync()).AsKeyValuePairs(); + } + + return Enumerable.Empty<KeyValuePair<string, string>>(); + } + + /// <summary> /// Gets the HTTP context for the current HTTP request. /// </summary> /// <returns>An HttpContextBase instance.</returns> @@ -671,8 +672,8 @@ namespace DotNetOpenAuth.Messaging { var webRequest = this.CreateHttpRequest(request); var directRequest = request as IHttpDirectRequest; if (directRequest != null) { - foreach (string header in directRequest.Headers) { - webRequest.Headers.Add(header, directRequest.Headers[header]); + foreach (var header in directRequest.Headers) { + webRequest.Headers.Add(header.Key, header.Value); } } @@ -741,16 +742,18 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to search for an embedded message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The deserialized message, if one is found. Null otherwise.</returns> - protected virtual IDirectedProtocolMessage ReadFromRequestCore(HttpRequestBase request, CancellationToken cancellationToken) { + protected virtual async Task<IDirectedProtocolMessage> ReadFromRequestCoreAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Requires.NotNull(request, "request"); - Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.GetPublicFacingUrl().AbsoluteUri); + Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.Method, request.RequestUri.AbsoluteUri); + + var fields = new Dictionary<string, string>(); // Search Form data first, and if nothing is there search the QueryString - Assumes.True(request.Form != null && request.GetQueryStringBeforeRewriting() != null); - var fields = request.Form.ToDictionary(); - if (fields.Count == 0 && request.HttpMethod != "POST") { // OpenID 2.0 section 4.1.2 - fields = request.GetQueryStringBeforeRewriting().ToDictionary(); + fields.AddRange(await ParseUrlEncodedFormContentAsync(request, cancellationToken)); + + if (fields.Count == 0 && request.Method.Method != "POST") { // OpenID 2.0 section 4.1.2 + fields.AddRange(HttpUtility.ParseQueryString(request.RequestUri.Query).AsKeyValuePairs()); } MessageReceivingEndpoint recipient; diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs index 7b26869..226102d 100644 --- a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs +++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs @@ -15,6 +15,6 @@ namespace DotNetOpenAuth.Messaging { /// Gets the HTTP headers of the request. /// </summary> /// <value>May be an empty collection, but must not be <c>null</c>.</value> - WebHeaderCollection Headers { get; } + System.Net.Http.Headers.HttpRequestHeaders Headers { get; } } } diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs index 38abe4a..25a56f4 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs @@ -360,7 +360,15 @@ namespace DotNetOpenAuth.Messaging { // 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) { + string httpHost; + try { + httpHost = serverVariables["HTTP_HOST"]; + } catch (NullReferenceException) { + // The VS dev web server can throw this. :( + httpHost = null; + } + + if (httpHost != 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"] ?? request.Url.Scheme; Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]); @@ -412,21 +420,45 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Wraps an instance of <see cref="HttpRequestBase"/> as an <see cref="HttpRequestMessage"/> instance. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>An instance of <see cref="HttpRequestMessage"/></returns> + public static HttpRequestMessage AsHttpRequestMessage(this HttpRequestBase request) { + Requires.NotNull(request, "request"); + + Uri publicFacingUrl = request.GetPublicFacingUrl(); + var httpRequest = new HttpRequestMessage(new HttpMethod(request.HttpMethod), publicFacingUrl); + + if (request.Form != null) { + // Avoid a request message that will try to read the request stream twice for already parsed data. + httpRequest.Content = new FormUrlEncodedContent(request.Form.AsKeyValuePairs()); + } else if (request.InputStream != null) { + httpRequest.Content = new StreamContent(request.InputStream); + } + + request.ApplyHeaders(httpRequest); + + return httpRequest; + } + + /// <summary> /// Sends a response message to the HTTP client. /// </summary> /// <param name="response">The response message.</param> - /// <param name="responseContext">The response context to send the response with.</param> + /// <param name="context">The HTTP context to send the response with.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// A task that completes with the asynchronous operation. /// </returns> - public static async Task SendAsync(this HttpResponseMessage response, HttpResponseBase responseContext = null, CancellationToken cancellationToken = default(CancellationToken)) { + public static async Task SendAsync(this HttpResponseMessage response, HttpContextBase context = null, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(response, "response"); - if (responseContext == null) { + if (context == null) { ErrorUtilities.VerifyHttpContext(); - responseContext = new HttpResponseWrapper(HttpContext.Current.Response); + context = new HttpContextWrapper(HttpContext.Current); } + var responseContext = context.Response; responseContext.StatusCode = (int)response.StatusCode; responseContext.StatusDescription = response.ReasonPhrase; foreach (var header in response.Headers) { @@ -438,16 +470,11 @@ namespace DotNetOpenAuth.Messaging { if (response.Content != null) { await response.Content.CopyToAsync(responseContext.OutputStream).ConfigureAwait(false); } - } - /// <summary> - /// Sends a response message to the HTTP client. - /// </summary> - /// <param name="response">The response message.</param> - /// <param name="responseContext">The response context to send the response with.</param> - /// <param name="cancellationToken">The cancellation token.</param> - public static void Send(this HttpResponseMessage response, HttpResponseBase responseContext = null, CancellationToken cancellationToken = default(CancellationToken)) { - SendAsync(response, responseContext, cancellationToken).GetAwaiter().GetResult(); + // This prevents a hosting ASP.NET web forms page from rendering HTML after a control + // has taken control of the response stream. + context.ApplicationInstance.CompleteRequest(); + context.Response.End(); } /// <summary> @@ -587,24 +614,15 @@ namespace DotNetOpenAuth.Messaging { /// <param name="scheme">The scheme. Must not be null or empty.</param> /// <param name="authorizationHeader">The authorization header. May be null or empty.</param> /// <returns>A sequence of key=value pairs discovered in the header. Never null, but may be empty.</returns> - internal static IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string scheme, string authorizationHeader) { + internal static IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string scheme, AuthenticationHeaderValue authorizationHeader) { Requires.NotNullOrEmpty(scheme, "scheme"); - string prefix = scheme + " "; - if (authorizationHeader != null) { - // The authorization header may have multiple sections. Look for the appropriate one. - string[] authorizationSections = new string[] { authorizationHeader }; // what is the right delimiter, if any? - foreach (string authorization in authorizationSections) { - string trimmedAuth = authorization.Trim(); - if (trimmedAuth.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { // RFC 2617 says this is case INsensitive - string data = trimmedAuth.Substring(prefix.Length); - return from element in data.Split(CommaArray) - let parts = element.Trim().Split(EqualsArray, 2) - let key = Uri.UnescapeDataString(parts[0]) - let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray)) - select new KeyValuePair<string, string>(key, value); - } - } + if (authorizationHeader != null && authorizationHeader.Scheme.Equals(scheme, StringComparison.OrdinalIgnoreCase)) { // RFC 2617 says this is case INsensitive + return from element in authorizationHeader.Parameter.Split(CommaArray) + let parts = element.Trim().Split(EqualsArray, 2) + let key = Uri.UnescapeDataString(parts[0]) + let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray)) + select new KeyValuePair<string, string>(key, value); } return Enumerable.Empty<KeyValuePair<string, string>>(); @@ -1184,80 +1202,45 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Clones an <see cref="HttpWebRequest"/> in order to send it again. - /// </summary> - /// <param name="request">The request to clone.</param> - /// <returns>The newly created instance.</returns> - internal static HttpWebRequest Clone(this HttpWebRequest request) { - Requires.NotNull(request, "request"); - Requires.That(request.RequestUri != null, "request", "request.RequestUri cannot be null."); - return Clone(request, request.RequestUri); - } - - /// <summary> - /// Clones an <see cref="HttpWebRequest"/> in order to send it again. + /// Clones an <see cref="HttpWebRequest" /> in order to send it again. /// </summary> - /// <param name="request">The request to clone.</param> - /// <param name="newRequestUri">The new recipient of the request.</param> - /// <returns>The newly created instance.</returns> - internal static HttpWebRequest Clone(this HttpWebRequest request, Uri newRequestUri) { + /// <param name="request">The request with headers to clone.</param> + /// <param name="message">The message to set headers on.</param> + internal static void ApplyHeaders(this HttpRequestBase request, HttpRequestMessage message) { Requires.NotNull(request, "request"); - Requires.NotNull(newRequestUri, "newRequestUri"); - - var newRequest = (HttpWebRequest)WebRequest.Create(newRequestUri); + Requires.NotNull(message, "message"); - // First copy headers. Only set those that are explicitly set on the original request, + // First copy headers. Only set those that are explicitly set on the original requestHeaders, // because some properties (like IfModifiedSince) activate special behavior when set, // even when set to their "original" values. foreach (string headerName in request.Headers) { switch (headerName) { - case "Accept": newRequest.Accept = request.Accept; break; + case "Accept": message.Headers.Accept.AddRange(request.AcceptTypes.Select(at => new MediaTypeWithQualityHeaderValue(at))); break; case "Connection": break; // Keep-Alive controls this - case "Content-Length": newRequest.ContentLength = request.ContentLength; break; - case "Content-Type": newRequest.ContentType = request.ContentType; break; - case "Expect": newRequest.Expect = request.Expect; break; + case "Content-Length": message.Content.Headers.ContentLength = request.ContentLength; break; + case "Content-Type": message.Content.Headers.ContentType = new MediaTypeHeaderValue(request.ContentType); break; + case "Expect": message.Headers.Expect.Add(new NameValueWithParametersHeaderValue(request.Headers[headerName])); break; case "Host": break; // implicitly copied as part of the RequestUri - case "If-Modified-Since": newRequest.IfModifiedSince = request.IfModifiedSince; break; - case "Keep-Alive": newRequest.KeepAlive = request.KeepAlive; break; - case "Proxy-Connection": break; // no property equivalent? - case "Referer": newRequest.Referer = request.Referer; break; - case "Transfer-Encoding": newRequest.TransferEncoding = request.TransferEncoding; break; - case "User-Agent": newRequest.UserAgent = request.UserAgent; break; - default: newRequest.Headers[headerName] = request.Headers[headerName]; break; - } - } - - newRequest.AllowAutoRedirect = request.AllowAutoRedirect; - newRequest.AllowWriteStreamBuffering = request.AllowWriteStreamBuffering; - newRequest.AuthenticationLevel = request.AuthenticationLevel; - newRequest.AutomaticDecompression = request.AutomaticDecompression; - newRequest.CachePolicy = request.CachePolicy; - newRequest.ClientCertificates = request.ClientCertificates; - newRequest.ConnectionGroupName = request.ConnectionGroupName; - newRequest.ContinueDelegate = request.ContinueDelegate; - newRequest.CookieContainer = request.CookieContainer; - newRequest.Credentials = request.Credentials; - newRequest.ImpersonationLevel = request.ImpersonationLevel; - newRequest.MaximumAutomaticRedirections = request.MaximumAutomaticRedirections; - newRequest.MaximumResponseHeadersLength = request.MaximumResponseHeadersLength; - newRequest.MediaType = request.MediaType; - newRequest.Method = request.Method; - newRequest.Pipelined = request.Pipelined; - newRequest.PreAuthenticate = request.PreAuthenticate; - newRequest.ProtocolVersion = request.ProtocolVersion; - newRequest.ReadWriteTimeout = request.ReadWriteTimeout; - newRequest.SendChunked = request.SendChunked; - newRequest.Timeout = request.Timeout; - newRequest.UseDefaultCredentials = request.UseDefaultCredentials; + case "If-Modified-Since": break; // we don't care + case "Keep-Alive": message.Headers.Connection.Add(request.Headers[headerName]); break; + case "Proxy-Connection": break; // we don't care + case "Referer": message.Headers.Referrer = request.UrlReferrer; break; + case "Transfer-Encoding": message.Headers.TransferEncoding.Add(new TransferCodingHeaderValue(request.Headers[headerName])); break; + case "User-Agent": message.Headers.UserAgent.Add(new ProductInfoHeaderValue(request.UserAgent)); break; + default: + HttpHeaders headers = headerName.StartsWith("Content-", StringComparison.Ordinal) && message.Content != null + ? (HttpHeaders)message.Content.Headers + : message.Headers; + var values = request.Headers.GetValues(headerName); + if (values.Length == 1) { + headers.Add(headerName, values[0]); + } else { + headers.Add(headerName, values); + } - try { - newRequest.Proxy = request.Proxy; - newRequest.UnsafeAuthenticatedConnectionSharing = request.UnsafeAuthenticatedConnectionSharing; - } catch (SecurityException) { - Logger.Messaging.Warn("Unable to clone some HttpWebRequest properties due to partial trust."); + break; + } } - - return newRequest; } /// <summary> @@ -1508,8 +1491,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to get recipient information from.</param> /// <returns>The recipient.</returns> /// <exception cref="ArgumentException">Thrown if the HTTP request is something we can't handle.</exception> - internal static MessageReceivingEndpoint GetRecipient(this HttpRequestBase request) { - return new MessageReceivingEndpoint(request.GetPublicFacingUrl(), GetHttpDeliveryMethod(request.HttpMethod)); + internal static MessageReceivingEndpoint GetRecipient(this HttpRequestMessage request) { + return new MessageReceivingEndpoint(request.RequestUri, GetHttpDeliveryMethod(request.Method.Method)); } /// <summary> @@ -2111,7 +2094,8 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="context">The context in which the result is executed. The context information includes the controller, HTTP content, request context, and route data.</param> public override void ExecuteResult(ControllerContext context) { - this.response.Send(context.HttpContext.Response); + // TODO: fix this to be asynchronous. + this.response.SendAsync(context.HttpContext).GetAwaiter().GetResult(); } } } |