summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Core/Messaging
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2013-03-04 13:54:33 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2013-03-04 13:54:33 -0800
commit36bbbea5002c889558a67c380e46dff668251b25 (patch)
tree87721c7f13cfd27b75f7132b71549688b55eb14a /src/DotNetOpenAuth.Core/Messaging
parentfd85211bfc50805d740392bfc6770df7c6f4c7d0 (diff)
downloadDotNetOpenAuth-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.cs71
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs2
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs176
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();
}
}
}