summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Core/Messaging/Channel.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.Core/Messaging/Channel.cs')
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Channel.cs678
1 files changed, 300 insertions, 378 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
index f8ac6a1..cc61b25 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
@@ -17,20 +17,23 @@ namespace DotNetOpenAuth.Messaging {
using System.Linq;
using System.Net;
using System.Net.Cache;
+ using System.Net.Http;
+ using System.Net.Http.Headers;
using System.Net.Mime;
+ using System.Net.Sockets;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading;
+ using System.Threading.Tasks;
using System.Web;
using System.Xml;
using DotNetOpenAuth.Messaging.Reflection;
+ using Validation;
/// <summary>
/// Manages sending direct messages to a remote party and receiving responses.
/// </summary>
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable.")]
- [ContractVerification(true)]
- [ContractClass(typeof(ChannelContract))]
public abstract class Channel : IDisposable {
/// <summary>
/// The encoding to use when writing out POST entity strings.
@@ -137,31 +140,25 @@ namespace DotNetOpenAuth.Messaging {
private IMessageFactory messageTypeProvider;
/// <summary>
- /// Backing store for the <see cref="CachePolicy"/> property.
- /// </summary>
- private RequestCachePolicy cachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
-
- /// <summary>
/// Backing field for the <see cref="MaximumIndirectMessageUrlLength"/> property.
/// </summary>
private int maximumIndirectMessageUrlLength = Configuration.DotNetOpenAuthSection.Messaging.MaximumIndirectMessageUrlLength;
/// <summary>
- /// Initializes a new instance of the <see cref="Channel"/> class.
+ /// Initializes a new instance of the <see cref="Channel" /> class.
/// </summary>
- /// <param name="messageTypeProvider">
- /// A class prepared to analyze incoming messages and indicate what concrete
- /// message types can deserialize from it.
- /// </param>
- /// <param name="bindingElements">
- /// The binding elements to use in sending and receiving messages.
- /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages.
- /// </param>
- protected Channel(IMessageFactory messageTypeProvider, params IChannelBindingElement[] bindingElements) {
+ /// <param name="messageTypeProvider">A class prepared to analyze incoming messages and indicate what concrete
+ /// message types can deserialize from it.</param>
+ /// <param name="bindingElements">The binding elements to use in sending and receiving messages.
+ /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages.</param>
+ /// <param name="hostFactories">The host factories.</param>
+ protected Channel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements, IHostFactories hostFactories) {
Requires.NotNull(messageTypeProvider, "messageTypeProvider");
+ Requires.NotNull(bindingElements, "bindingElements");
+ Requires.NotNull(hostFactories, "hostFactories");
this.messageTypeProvider = messageTypeProvider;
- this.WebRequestHandler = new StandardWebRequestHandler();
+ this.HostFactories = hostFactories;
this.XmlDictionaryReaderQuotas = DefaultUntrustedXmlDictionaryReaderQuotas;
this.outgoingBindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements));
@@ -179,14 +176,9 @@ namespace DotNetOpenAuth.Messaging {
internal event EventHandler<ChannelEventArgs> Sending;
/// <summary>
- /// Gets or sets an instance to a <see cref="IDirectWebRequestHandler"/> that will be used when
- /// submitting HTTP requests and waiting for responses.
+ /// Gets the host factories instance to use.
/// </summary>
- /// <remarks>
- /// This defaults to a straightforward implementation, but can be set
- /// to a mock object for testing purposes.
- /// </remarks>
- public IDirectWebRequestHandler WebRequestHandler { get; set; }
+ public IHostFactories HostFactories { get; private set; }
/// <summary>
/// Gets or sets the maximum allowable size for a 301 Redirect response before we send
@@ -201,7 +193,7 @@ namespace DotNetOpenAuth.Messaging {
}
set {
- Requires.InRange(value >= 500 && value <= 4096, "value");
+ Requires.Range(value >= 500 && value <= 4096, "value");
this.maximumIndirectMessageUrlLength = value;
}
}
@@ -229,13 +221,28 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets or sets the outgoing message filter.
+ /// </summary>
+ /// <value>
+ /// The outgoing message filter.
+ /// </value>
+ internal Action<IProtocolMessage> OutgoingMessageFilter { get; set; }
+
+ /// <summary>
+ /// Gets or sets the incoming message filter.
+ /// </summary>
+ /// <value>
+ /// The incoming message filter.
+ /// </value>
+ internal Action<IProtocolMessage> IncomingMessageFilter { get; set; }
+
+ /// <summary>
/// Gets the binding elements used by this channel, in no particular guaranteed order.
/// </summary>
protected internal ReadOnlyCollection<IChannelBindingElement> BindingElements {
get {
- Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>() != null);
var result = this.outgoingBindingElements.AsReadOnly();
- Contract.Assume(result != null); // should be an implicit BCL contract
+ Assumes.True(result != null); // should be an implicit BCL contract
return result;
}
}
@@ -252,8 +259,6 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
protected internal ReadOnlyCollection<IChannelBindingElement> IncomingBindingElements {
get {
- Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be.Channel != null));
- Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be != null));
return this.incomingBindingElements.AsReadOnly();
}
}
@@ -276,76 +281,25 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Gets or sets the cache policy to use for direct message requests.
- /// </summary>
- /// <value>Default is <see cref="HttpRequestCacheLevel.NoCacheNoStore"/>.</value>
- protected RequestCachePolicy CachePolicy {
- get {
- return this.cachePolicy;
- }
-
- set {
- Requires.NotNull(value, "value");
- this.cachePolicy = value;
- }
- }
-
- /// <summary>
/// Gets or sets the XML dictionary reader quotas.
/// </summary>
/// <value>The XML dictionary reader quotas.</value>
protected virtual XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas { get; set; }
/// <summary>
- /// Sends an indirect message (either a request or response)
- /// or direct message response for transmission to a remote party
- /// and ends execution on the current page or handler.
- /// </summary>
- /// <param name="message">The one-way message to send</param>
- /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
- /// <remarks>
- /// Requires an HttpContext.Current context.
- /// </remarks>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void Send(IProtocolMessage message) {
- Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired);
- Requires.NotNull(message, "message");
- this.PrepareResponse(message).Respond(HttpContext.Current, true);
- }
-
- /// <summary>
- /// Sends an indirect message (either a request or response)
- /// or direct message response for transmission to a remote party
- /// and skips most of the remaining ASP.NET request handling pipeline.
- /// Not safe to call from ASP.NET web forms.
- /// </summary>
- /// <param name="message">The one-way message to send</param>
- /// <remarks>
- /// Requires an HttpContext.Current context.
- /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because
- /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response.
- /// Use the <see cref="Send"/> method instead for web forms.
- /// </remarks>
- public void Respond(IProtocolMessage message) {
- Requires.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired);
- Requires.NotNull(message, "message");
- this.PrepareResponse(message).Respond();
- }
-
- /// <summary>
/// Prepares an indirect message (either a request or response)
/// or direct message response for transmission to a remote party.
/// </summary>
/// <param name="message">The one-way message to send</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
- public OutgoingWebResponse PrepareResponse(IProtocolMessage message) {
+ public async Task<HttpResponseMessage> PrepareResponseAsync(IProtocolMessage message, CancellationToken cancellationToken = default(CancellationToken)) {
Requires.NotNull(message, "message");
- Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
- this.ProcessOutgoingMessage(message);
+ await this.ProcessOutgoingMessageAsync(message, cancellationToken);
Logger.Channel.DebugFormat("Sending message: {0}", message.GetType().Name);
- OutgoingWebResponse result;
+ HttpResponseMessage result;
switch (message.Transport) {
case MessageTransport.Direct:
// This is a response to a direct message.
@@ -374,8 +328,13 @@ namespace DotNetOpenAuth.Messaging {
// Apply caching policy to any response. We want to disable all caching because in auth* protocols,
// caching can be utilized in identity spoofing attacks.
- result.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, max-age=0, must-revalidate";
- result.Headers[HttpResponseHeader.Pragma] = "no-cache";
+ result.Headers.CacheControl = new CacheControlHeaderValue {
+ NoCache = true,
+ NoStore = true,
+ MaxAge = TimeSpan.Zero,
+ MustRevalidate = true,
+ };
+ result.Headers.Pragma.Add(new NameValueHeaderValue("no-cache"));
return result;
}
@@ -383,71 +342,26 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Gets the protocol message embedded in the given HTTP request, if present.
/// </summary>
- /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
- /// <remarks>
- /// Requires an HttpContext.Current context.
- /// </remarks>
- /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
- public IDirectedProtocolMessage ReadFromRequest() {
- return this.ReadFromRequest(this.GetRequestFromContext());
- }
-
- /// <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="request">The deserialized message, if one is found. Null otherwise.</param>
- /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns>
- /// <remarks>
- /// Requires an HttpContext.Current context.
- /// </remarks>
- /// <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 bool TryReadFromRequest<TRequest>(out TRequest request)
- where TRequest : class, IProtocolMessage {
- return TryReadFromRequest<TRequest>(this.GetRequestFromContext(), out request);
- }
-
- /// <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="httpRequest">The request to search for an embedded message.</param>
- /// <param name="request">The deserialized message, if one is found. Null otherwise.</param>
- /// <returns>True if the expected message was recognized and deserialized. False otherwise.</returns>
- /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// True if the expected message was recognized and deserialized. False otherwise.
+ /// </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 bool TryReadFromRequest<TRequest>(HttpRequestBase httpRequest, out TRequest request)
+ public async Task<TRequest> TryReadFromRequestAsync<TRequest>(HttpRequestMessage httpRequest, CancellationToken cancellationToken)
where TRequest : class, IProtocolMessage {
Requires.NotNull(httpRequest, "httpRequest");
- Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<TRequest>(out request) != null));
- IProtocolMessage untypedRequest = this.ReadFromRequest(httpRequest);
+ IProtocolMessage untypedRequest = await this.ReadFromRequestAsync(httpRequest, cancellationToken);
if (untypedRequest == null) {
- request = null;
- return false;
+ return null;
}
- request = untypedRequest as TRequest;
+ var request = untypedRequest as TRequest;
ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.UnexpectedMessageReceived, typeof(TRequest), untypedRequest.GetType());
-
- return true;
- }
-
- /// <summary>
- /// Gets the protocol message embedded in the current HTTP request.
- /// </summary>
- /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam>
- /// <returns>The deserialized message. Never null.</returns>
- /// <remarks>
- /// Requires an HttpContext.Current context.
- /// </remarks>
- /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
- /// <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 TRequest ReadFromRequest<TRequest>()
- where TRequest : class, IProtocolMessage {
- return this.ReadFromRequest<TRequest>(this.GetRequestFromContext());
+ return request;
}
/// <summary>
@@ -455,43 +369,47 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <typeparam name="TRequest">The expected type of the message to be received.</typeparam>
/// <param name="httpRequest">The request to search for an embedded message.</param>
- /// <returns>The deserialized message. Never null.</returns>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The deserialized message. Never null.
+ /// </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 TRequest ReadFromRequest<TRequest>(HttpRequestBase httpRequest)
+ public async Task<TRequest> ReadFromRequestAsync<TRequest>(HttpRequestMessage httpRequest, CancellationToken cancellationToken)
where TRequest : class, IProtocolMessage {
Requires.NotNull(httpRequest, "httpRequest");
- TRequest request;
- if (this.TryReadFromRequest<TRequest>(httpRequest, out request)) {
- return request;
- } else {
- throw ErrorUtilities.ThrowProtocol(MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest));
- }
+
+ TRequest request = await this.TryReadFromRequestAsync<TRequest>(httpRequest, cancellationToken);
+ ErrorUtilities.VerifyProtocol(request != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TRequest));
+ return request;
}
/// <summary>
/// Gets the protocol message that may be embedded in the given HTTP request.
/// </summary>
/// <param name="httpRequest">The request to search for an embedded message.</param>
- /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
- public IDirectedProtocolMessage ReadFromRequest(HttpRequestBase httpRequest) {
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The deserialized message, if one is found. Null otherwise.
+ /// </returns>
+ 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);
+ 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);
}
}
- this.ProcessIncomingMessage(requestMessage);
+ await this.ProcessIncomingMessageAsync(requestMessage, cancellationToken);
}
return requestMessage;
@@ -502,18 +420,18 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <typeparam name="TResponse">The expected type of the message to be received.</typeparam>
/// <param name="requestMessage">The message to send.</param>
- /// <returns>The remote party's response.</returns>
- /// <exception cref="ProtocolException">
- /// Thrown if no message is recognized in the response
- /// or an unexpected type of message is received.
- /// </exception>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The remote party's response.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown if no message is recognized in the response
+ /// or an unexpected type of message is received.</exception>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")]
- public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage)
+ public async Task<TResponse> RequestAsync<TResponse>(IDirectedProtocolMessage requestMessage, CancellationToken cancellationToken)
where TResponse : class, IProtocolMessage {
Requires.NotNull(requestMessage, "requestMessage");
- Contract.Ensures(Contract.Result<TResponse>() != null);
- IProtocolMessage response = this.Request(requestMessage);
+ IProtocolMessage response = await this.RequestAsync(requestMessage, cancellationToken);
ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse));
var expectedResponse = response as TResponse;
@@ -526,18 +444,21 @@ namespace DotNetOpenAuth.Messaging {
/// Sends a direct message to a remote party and waits for the response.
/// </summary>
/// <param name="requestMessage">The message to send.</param>
- /// <returns>The remote party's response. Guaranteed to never be null.</returns>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The remote party's response. Guaranteed to never be null.
+ /// </returns>
/// <exception cref="ProtocolException">Thrown if the response does not include a protocol message.</exception>
- public IProtocolMessage Request(IDirectedProtocolMessage requestMessage) {
+ public async Task<IProtocolMessage> RequestAsync(IDirectedProtocolMessage requestMessage, CancellationToken cancellationToken) {
Requires.NotNull(requestMessage, "requestMessage");
- this.ProcessOutgoingMessage(requestMessage);
+ await this.ProcessOutgoingMessageAsync(requestMessage, cancellationToken);
Logger.Channel.DebugFormat("Sending {0} request.", requestMessage.GetType().Name);
- var responseMessage = this.RequestCore(requestMessage);
+ var responseMessage = await this.RequestCoreAsync(requestMessage, cancellationToken);
ErrorUtilities.VerifyProtocol(responseMessage != null, MessagingStrings.ExpectedMessageNotReceived, typeof(IProtocolMessage).Name);
Logger.Channel.DebugFormat("Received {0} response.", responseMessage.GetType().Name);
- this.ProcessIncomingMessage(responseMessage);
+ await this.ProcessIncomingMessageAsync(responseMessage, cancellationToken);
return responseMessage;
}
@@ -558,12 +479,14 @@ namespace DotNetOpenAuth.Messaging {
/// Verifies the integrity and applicability of an incoming message.
/// </summary>
/// <param name="message">The message just received.</param>
- /// <exception cref="ProtocolException">
- /// Thrown when the message is somehow invalid.
- /// This can be due to tampering, replay attack or expiration, among other things.
- /// </exception>
- internal void ProcessIncomingMessageTestHook(IProtocolMessage message) {
- this.ProcessIncomingMessage(message);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// A task that completes with the asynchronous operation.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown when the message is somehow invalid.
+ /// This can be due to tampering, replay attack or expiration, among other things.</exception>
+ internal Task ProcessIncomingMessageTestHookAsync(IProtocolMessage message, CancellationToken cancellationToken) {
+ return this.ProcessIncomingMessageAsync(message, cancellationToken);
}
/// <summary>
@@ -572,10 +495,10 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="request">The message to send.</param>
/// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns>
/// <remarks>
- /// This method must be overridden by a derived class, unless the <see cref="RequestCore"/> method
+ /// This method must be overridden by a derived class, unless the <see cref="RequestCoreAsync"/> method
/// is overridden and does not require this method.
/// </remarks>
- internal HttpWebRequest CreateHttpRequestTestHook(IDirectedProtocolMessage request) {
+ internal HttpRequestMessage CreateHttpRequestTestHook(IDirectedProtocolMessage request) {
return this.CreateHttpRequest(request);
}
@@ -588,7 +511,7 @@ namespace DotNetOpenAuth.Messaging {
/// <remarks>
/// This method implements spec OAuth V1.0 section 5.3.
/// </remarks>
- internal OutgoingWebResponse PrepareDirectResponseTestHook(IProtocolMessage response) {
+ internal HttpResponseMessage PrepareDirectResponseTestHook(IProtocolMessage response) {
return this.PrepareDirectResponse(response);
}
@@ -596,22 +519,44 @@ namespace DotNetOpenAuth.Messaging {
/// Gets the protocol message that may be in the given HTTP response.
/// </summary>
/// <param name="response">The response that is anticipated to contain an protocol message.</param>
- /// <returns>The deserialized message parts, if found. Null otherwise.</returns>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The deserialized message parts, if found. Null otherwise.
+ /// </returns>
/// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
- internal IDictionary<string, string> ReadFromResponseCoreTestHook(IncomingWebResponse response) {
- return this.ReadFromResponseCore(response);
+ internal Task<IDictionary<string, string>> ReadFromResponseCoreAsyncTestHook(HttpResponseMessage response, CancellationToken cancellationToken) {
+ return this.ReadFromResponseCoreAsync(response, cancellationToken);
}
- /// <remarks>
- /// This method should NOT be called by derived types
- /// except when sending ONE WAY request messages.
- /// </remarks>
/// <summary>
/// Prepares a message for transmit by applying signatures, nonces, etc.
/// </summary>
/// <param name="message">The message to prepare for sending.</param>
- internal void ProcessOutgoingMessageTestHook(IProtocolMessage message) {
- this.ProcessOutgoingMessage(message);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// A task that completes with the asynchronous operation.
+ /// </returns>
+ /// <remarks>
+ /// This method should NOT be called by derived types
+ /// except when sending ONE WAY request messages.
+ /// </remarks>
+ internal Task ProcessOutgoingMessageTestHookAsync(IProtocolMessage message, CancellationToken cancellationToken = default(CancellationToken)) {
+ return this.ProcessOutgoingMessageAsync(message, cancellationToken);
+ }
+
+ /// <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>
@@ -620,7 +565,7 @@ namespace DotNetOpenAuth.Messaging {
/// <returns>An HttpContextBase instance.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Allocates memory")]
protected internal virtual HttpContextBase GetHttpContext() {
- Requires.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
return new HttpContextWrapper(HttpContext.Current);
}
@@ -634,28 +579,51 @@ namespace DotNetOpenAuth.Messaging {
/// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly call should not be a property.")]
protected internal virtual HttpRequestBase GetRequestFromContext() {
- Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
- Contract.Ensures(Contract.Result<HttpRequestBase>() != null);
+ RequiresEx.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
- Contract.Assume(HttpContext.Current.Request.Url != null);
- Contract.Assume(HttpContext.Current.Request.RawUrl != null);
+ Assumes.True(HttpContext.Current.Request.Url != null);
+ Assumes.True(HttpContext.Current.Request.RawUrl != null);
return new HttpRequestWrapper(HttpContext.Current.Request);
}
/// <summary>
+ /// Adds just the binary data part of a message to a multipart form content object.
+ /// </summary>
+ /// <param name="requestMessageWithBinaryData">The request message with binary data.</param>
+ /// <returns>The initialized HttpContent.</returns>
+ protected static MultipartFormDataContent InitializeMultipartFormDataContent(IMessageWithBinaryData requestMessageWithBinaryData) {
+ Requires.NotNull(requestMessageWithBinaryData, "requestMessageWithBinaryData");
+
+ var content = new MultipartFormDataContent();
+ foreach (var part in requestMessageWithBinaryData.BinaryData) {
+ if (string.IsNullOrEmpty(part.Name)) {
+ content.Add(part.Content);
+ } else if (string.IsNullOrEmpty(part.FileName)) {
+ content.Add(part.Content, part.Name);
+ } else {
+ content.Add(part.Content, part.Name, part.FileName);
+ }
+ }
+
+ return content;
+ }
+
+ /// <summary>
/// Checks whether a given HTTP method is expected to include an entity body in its request.
/// </summary>
/// <param name="httpMethod">The HTTP method.</param>
/// <returns><c>true</c> if the HTTP method is supposed to have an entity; <c>false</c> otherwise.</returns>
- 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, "OPTIONS", StringComparison.Ordinal)) {
+ protected static bool HttpMethodHasEntity(HttpMethod httpMethod) {
+ Requires.NotNull(httpMethod, "httpMethod");
+
+ if (httpMethod == HttpMethod.Get ||
+ httpMethod == HttpMethod.Head ||
+ httpMethod == HttpMethod.Delete ||
+ httpMethod == HttpMethod.Options) {
return false;
- } else if (string.Equals(httpMethod, "POST", StringComparison.Ordinal) ||
- string.Equals(httpMethod, "PUT", StringComparison.Ordinal) ||
- string.Equals(httpMethod, "PATCH", StringComparison.Ordinal)) {
+ } else if (httpMethod == HttpMethod.Post ||
+ httpMethod == HttpMethod.Put ||
+ string.Equals(httpMethod.Method, "PATCH", StringComparison.Ordinal)) {
return true;
} else {
throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod);
@@ -667,11 +635,11 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <param name="message">The message.</param>
/// <param name="response">The HTTP response.</param>
- protected static void ApplyMessageTemplate(IMessage message, OutgoingWebResponse response) {
+ protected static void ApplyMessageTemplate(IMessage message, HttpResponseMessage response) {
Requires.NotNull(message, "message");
var httpMessage = message as IHttpDirectResponse;
if (httpMessage != null) {
- response.Status = httpMessage.HttpStatusCode;
+ response.StatusCode = httpMessage.HttpStatusCode;
foreach (string headerName in httpMessage.Headers) {
response.Headers.Add(headerName, httpMessage.Headers[headerName]);
}
@@ -707,63 +675,63 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Gets the direct response of a direct HTTP request.
- /// </summary>
- /// <param name="webRequest">The web request.</param>
- /// <returns>The response to the web request.</returns>
- /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception>
- protected virtual IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) {
- Requires.NotNull(webRequest, "webRequest");
- return this.WebRequestHandler.GetResponse(webRequest);
- }
-
- /// <summary>
/// Submits a direct request message to some remote party and blocks waiting for an immediately reply.
/// </summary>
/// <param name="request">The request message.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response message, or null if the response did not carry a message.</returns>
/// <remarks>
/// Typically a deriving channel will override <see cref="CreateHttpRequest"/> to customize this method's
/// behavior. However in non-HTTP frameworks, such as unit test mocks, it may be appropriate to override
/// this method to eliminate all use of an HTTP transport.
/// </remarks>
- protected virtual IProtocolMessage RequestCore(IDirectedProtocolMessage request) {
+ protected virtual async Task<IProtocolMessage> RequestCoreAsync(IDirectedProtocolMessage request, CancellationToken cancellationToken) {
Requires.NotNull(request, "request");
- Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
+ Requires.That(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
- HttpWebRequest webRequest = this.CreateHttpRequest(request);
+ if (this.OutgoingMessageFilter != null) {
+ this.OutgoingMessageFilter(request);
+ }
+
+ var webRequest = this.CreateHttpRequest(request);
var directRequest = request as IHttpDirectRequest;
if (directRequest != null) {
- foreach (string header in directRequest.Headers) {
- webRequest.Headers[header] = directRequest.Headers[header];
+ foreach (var header in directRequest.Headers) {
+ webRequest.Headers.Add(header.Key, header.Value);
}
}
- IDictionary<string, string> responseFields;
- IDirectResponseProtocolMessage responseMessage;
-
- using (IncomingWebResponse response = this.GetDirectResponse(webRequest)) {
- if (response.ResponseStream == null) {
- return null;
- }
-
- responseFields = this.ReadFromResponseCore(response);
- if (responseFields == null) {
- return null;
- }
-
- responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields);
- if (responseMessage == null) {
- return null;
+ try {
+ using (var httpClient = this.HostFactories.CreateHttpClient()) {
+ using (var response = await httpClient.SendAsync(webRequest, cancellationToken)) {
+ if (response.Content != null) {
+ var responseFields = await this.ReadFromResponseCoreAsync(response, cancellationToken);
+ if (responseFields != null) {
+ var responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields);
+ if (responseMessage != null) {
+ this.OnReceivingDirectResponse(response, responseMessage);
+
+ var messageAccessor = this.MessageDescriptions.GetAccessor(responseMessage);
+ messageAccessor.Deserialize(responseFields);
+
+ return responseMessage;
+ }
+ }
+ }
+
+ if (!response.IsSuccessStatusCode) {
+ var errorContent = (response.Content != null) ? await response.Content.ReadAsStringAsync() : null;
+ Logger.Http.ErrorFormat(
+ "Error received in HTTP response: {0} {1}\n{2}", (int)response.StatusCode, response.ReasonPhrase, errorContent);
+ response.EnsureSuccessStatusCode(); // throw so we can wrap it in our catch block.
+ }
+
+ return null;
+ }
}
-
- this.OnReceivingDirectResponse(response, responseMessage);
+ } catch (HttpRequestException requestException) {
+ throw ErrorUtilities.Wrap(requestException, "Error sending HTTP request or receiving response.");
}
-
- var messageAccessor = this.MessageDescriptions.GetAccessor(responseMessage);
- messageAccessor.Deserialize(responseFields);
-
- return responseMessage;
}
/// <summary>
@@ -771,24 +739,27 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <param name="response">The HTTP direct response.</param>
/// <param name="message">The newly instantiated message, prior to deserialization.</param>
- protected virtual void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) {
+ protected virtual void OnReceivingDirectResponse(HttpResponseMessage response, IDirectResponseProtocolMessage message) {
}
/// <summary>
/// Gets the protocol message that may be embedded in the given HTTP request.
/// </summary>
/// <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) {
+ 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
- Contract.Assume(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;
@@ -835,18 +806,17 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <param name="message">The message to send.</param>
/// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
- protected virtual OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) {
+ protected virtual HttpResponseMessage PrepareIndirectResponse(IDirectedProtocolMessage message) {
Requires.NotNull(message, "message");
- Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
- Requires.True((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0, "message");
- Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
+ Requires.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
+ Requires.That((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0, "message", "GET or POST expected.");
- Contract.Assert(message != null && message.Recipient != null);
+ Assumes.True(message != null && message.Recipient != null);
var messageAccessor = this.MessageDescriptions.GetAccessor(message);
- Contract.Assert(message != null && message.Recipient != null);
+ Assumes.True(message != null && message.Recipient != null);
var fields = messageAccessor.Serialize();
- OutgoingWebResponse response = null;
+ HttpResponseMessage response = null;
bool tooLargeForGet = false;
if ((message.HttpMethods & HttpDeliveryMethods.GetRequest) == HttpDeliveryMethods.GetRequest) {
bool payloadInFragment = false;
@@ -858,7 +828,7 @@ namespace DotNetOpenAuth.Messaging {
// First try creating a 301 redirect, and fallback to a form POST
// if the message is too big.
response = this.Create301RedirectResponse(message, fields, payloadInFragment);
- tooLargeForGet = response.Headers[HttpResponseHeader.Location].Length > this.MaximumIndirectMessageUrlLength;
+ tooLargeForGet = response.Headers.Location.PathAndQuery.Length > this.MaximumIndirectMessageUrlLength;
}
// Make sure that if the message is too large for GET that POST is allowed.
@@ -885,15 +855,13 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="payloadInFragment">if set to <c>true</c> the redirect will contain the message payload in the #fragment portion of the URL rather than the ?querystring.</param>
/// <returns>The encoded HTTP response.</returns>
[Pure]
- protected virtual OutgoingWebResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) {
+ protected virtual HttpResponseMessage Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) {
Requires.NotNull(message, "message");
- Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
+ Requires.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
Requires.NotNull(fields, "fields");
- Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
// As part of this redirect, we include an HTML body in order to get passed some proxy filters
// such as WebSense.
- WebHeaderCollection headers = new WebHeaderCollection();
UriBuilder builder = new UriBuilder(message.Recipient);
if (payloadInFragment) {
builder.AppendFragmentArgs(fields);
@@ -901,16 +869,14 @@ namespace DotNetOpenAuth.Messaging {
builder.AppendQueryArgs(fields);
}
- headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri);
- headers.Add(HttpResponseHeader.ContentType, "text/html; charset=utf-8");
Logger.Http.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri);
- OutgoingWebResponse response = new OutgoingWebResponse {
- Status = HttpStatusCode.Redirect,
- Headers = headers,
- Body = string.Format(CultureInfo.InvariantCulture, RedirectResponseBodyFormat, builder.Uri.AbsoluteUri),
- OriginalMessage = message
+ HttpResponseMessage response = new HttpResponseMessageWithOriginal(message) {
+ StatusCode = HttpStatusCode.Redirect,
+ Content = new StringContent(string.Format(CultureInfo.InvariantCulture, RedirectResponseBodyFormat, builder.Uri.AbsoluteUri)),
};
+ response.Headers.Location = builder.Uri;
+ response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html") { CharSet = "utf-8" };
return response;
}
@@ -922,14 +888,11 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="fields">The pre-serialized fields from the message.</param>
/// <returns>The encoded HTTP response.</returns>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
- protected virtual OutgoingWebResponse CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
+ protected virtual HttpResponseMessage CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
Requires.NotNull(message, "message");
- Requires.True(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
+ Requires.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
Requires.NotNull(fields, "fields");
- Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
- WebHeaderCollection headers = new WebHeaderCollection();
- headers.Add(HttpResponseHeader.ContentType, "text/html");
using (StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture)) {
StringBuilder hiddenFields = new StringBuilder();
foreach (var field in fields) {
@@ -943,12 +906,11 @@ namespace DotNetOpenAuth.Messaging {
HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri),
hiddenFields);
bodyWriter.Flush();
- OutgoingWebResponse response = new OutgoingWebResponse {
- Status = HttpStatusCode.OK,
- Headers = headers,
- Body = bodyWriter.ToString(),
- OriginalMessage = message
+ HttpResponseMessage response = new HttpResponseMessageWithOriginal(message) {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(bodyWriter.ToString()),
};
+ response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;
}
@@ -958,9 +920,12 @@ namespace DotNetOpenAuth.Messaging {
/// Gets the protocol message that may be in the given HTTP response.
/// </summary>
/// <param name="response">The response that is anticipated to contain an protocol message.</param>
- /// <returns>The deserialized message parts, if found. Null otherwise.</returns>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// The deserialized message parts, if found. Null otherwise.
+ /// </returns>
/// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
- protected abstract IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response);
+ protected abstract Task<IDictionary<string, string>> ReadFromResponseCoreAsync(HttpResponseMessage response, CancellationToken cancellationToken);
/// <summary>
/// Prepares an HTTP request that carries a given message.
@@ -968,13 +933,12 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="request">The message to send.</param>
/// <returns>The <see cref="HttpWebRequest"/> prepared to send the request.</returns>
/// <remarks>
- /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCore"/> method
+ /// This method must be overridden by a derived class, unless the <see cref="Channel.RequestCoreAsync"/> method
/// is overridden and does not require this method.
/// </remarks>
- protected virtual HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) {
+ protected virtual HttpRequestMessage CreateHttpRequest(IDirectedProtocolMessage request) {
Requires.NotNull(request, "request");
- Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
- Contract.Ensures(Contract.Result<HttpWebRequest>() != null);
+ Requires.That(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
throw new NotImplementedException();
}
@@ -987,7 +951,7 @@ namespace DotNetOpenAuth.Messaging {
/// <remarks>
/// This method implements spec OAuth V1.0 section 5.3.
/// </remarks>
- protected abstract OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response);
+ protected abstract HttpResponseMessage PrepareDirectResponse(IProtocolMessage response);
/// <summary>
/// Serializes the given message as a JSON string.
@@ -1021,11 +985,16 @@ namespace DotNetOpenAuth.Messaging {
/// Prepares a message for transmit by applying signatures, nonces, etc.
/// </summary>
/// <param name="message">The message to prepare for sending.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// A task that completes with the asynchronous operation.
+ /// </returns>
+ /// <exception cref="UnprotectedMessageException">Thrown if the message does not have the minimal required protections applied.</exception>
/// <remarks>
/// This method should NOT be called by derived types
/// except when sending ONE WAY request messages.
/// </remarks>
- protected void ProcessOutgoingMessage(IProtocolMessage message) {
+ protected async Task ProcessOutgoingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) {
Requires.NotNull(message, "message");
Logger.Channel.DebugFormat("Preparing to send {0} ({1}) message.", message.GetType().Name, message.Version);
@@ -1039,8 +1008,8 @@ namespace DotNetOpenAuth.Messaging {
MessageProtections appliedProtection = MessageProtections.None;
foreach (IChannelBindingElement bindingElement in this.outgoingBindingElements) {
- Contract.Assume(bindingElement.Channel != null);
- MessageProtections? elementProtection = bindingElement.ProcessOutgoingMessage(message);
+ Assumes.True(bindingElement.Channel != null);
+ MessageProtections? elementProtection = await bindingElement.ProcessOutgoingMessageAsync(message, cancellationToken);
if (elementProtection.HasValue) {
Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName);
@@ -1061,6 +1030,10 @@ namespace DotNetOpenAuth.Messaging {
this.EnsureValidMessageParts(message);
message.EnsureValidMessage();
+ if (this.OutgoingMessageFilter != null) {
+ this.OutgoingMessageFilter(message);
+ }
+
if (Logger.Channel.IsInfoEnabled) {
var directedMessage = message as IDirectedProtocolMessage;
string recipient = (directedMessage != null && directedMessage.Recipient != null) ? directedMessage.Recipient.AbsoluteUri : "<response>";
@@ -1084,16 +1057,16 @@ namespace DotNetOpenAuth.Messaging {
/// This method is simply a standard HTTP Get request with the message parts serialized to the query string.
/// This method satisfies OAuth 1.0 section 5.2, item #3.
/// </remarks>
- protected virtual HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) {
+ protected virtual HttpRequestMessage InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) {
Requires.NotNull(requestMessage, "requestMessage");
- Requires.True(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient);
+ Requires.That(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient);
var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage);
var fields = messageAccessor.Serialize();
UriBuilder builder = new UriBuilder(requestMessage.Recipient);
MessagingUtilities.AppendQueryArgs(builder, fields);
- HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
+ var httpRequest = new HttpRequestMessage(HttpMethod.Get, builder.Uri);
this.PrepareHttpWebRequest(httpRequest);
return httpRequest;
@@ -1108,12 +1081,12 @@ namespace DotNetOpenAuth.Messaging {
/// This method is simply a standard HTTP HEAD request with the message parts serialized to the query string.
/// This method satisfies OAuth 1.0 section 5.2, item #3.
/// </remarks>
- protected virtual HttpWebRequest InitializeRequestAsHead(IDirectedProtocolMessage requestMessage) {
+ protected virtual HttpRequestMessage InitializeRequestAsHead(IDirectedProtocolMessage requestMessage) {
Requires.NotNull(requestMessage, "requestMessage");
- Requires.True(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient);
+ Requires.That(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient);
- HttpWebRequest request = this.InitializeRequestAsGet(requestMessage);
- request.Method = "HEAD";
+ var request = this.InitializeRequestAsGet(requestMessage);
+ request.Method = HttpMethod.Head;
return request;
}
@@ -1127,28 +1100,28 @@ namespace DotNetOpenAuth.Messaging {
/// with the application/x-www-form-urlencoded content type
/// This method satisfies OAuth 1.0 section 5.2, item #2 and OpenID 2.0 section 4.1.2.
/// </remarks>
- protected virtual HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) {
+ protected virtual HttpRequestMessage InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) {
Requires.NotNull(requestMessage, "requestMessage");
- Contract.Ensures(Contract.Result<HttpWebRequest>() != null);
var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage);
var fields = messageAccessor.Serialize();
- var httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
+ var httpRequest = new HttpRequestMessage(HttpMethod.Post, requestMessage.Recipient);
this.PrepareHttpWebRequest(httpRequest);
- httpRequest.CachePolicy = this.CachePolicy;
- httpRequest.Method = "POST";
var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData;
if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) {
- var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData);
+ var content = InitializeMultipartFormDataContent(requestMessageWithBinaryData);
// When sending multi-part, all data gets send as multi-part -- even the non-binary data.
- multiPartFields.AddRange(fields.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value)));
- this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields);
+ foreach (var field in fields) {
+ content.Add(new StringContent(field.Value), field.Key);
+ }
+
+ httpRequest.Content = content;
} else {
ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart);
- this.SendParametersInEntity(httpRequest, fields);
+ httpRequest.Content = new FormUrlEncodedContent(fields);
}
return httpRequest;
@@ -1162,12 +1135,11 @@ namespace DotNetOpenAuth.Messaging {
/// <remarks>
/// This method is simply a standard HTTP PUT request with the message parts serialized to the query string.
/// </remarks>
- protected virtual HttpWebRequest InitializeRequestAsPut(IDirectedProtocolMessage requestMessage) {
+ protected virtual HttpRequestMessage InitializeRequestAsPut(IDirectedProtocolMessage requestMessage) {
Requires.NotNull(requestMessage, "requestMessage");
- Contract.Ensures(Contract.Result<HttpWebRequest>() != null);
- HttpWebRequest request = this.InitializeRequestAsGet(requestMessage);
- request.Method = "PUT";
+ var request = this.InitializeRequestAsGet(requestMessage);
+ request.Method = HttpMethod.Put;
return request;
}
@@ -1179,67 +1151,26 @@ namespace DotNetOpenAuth.Messaging {
/// <remarks>
/// This method is simply a standard HTTP DELETE request with the message parts serialized to the query string.
/// </remarks>
- protected virtual HttpWebRequest InitializeRequestAsDelete(IDirectedProtocolMessage requestMessage) {
+ protected virtual HttpRequestMessage InitializeRequestAsDelete(IDirectedProtocolMessage requestMessage) {
Requires.NotNull(requestMessage, "requestMessage");
- Contract.Ensures(Contract.Result<HttpWebRequest>() != null);
- HttpWebRequest request = this.InitializeRequestAsGet(requestMessage);
- request.Method = "DELETE";
+ var request = this.InitializeRequestAsGet(requestMessage);
+ request.Method = HttpMethod.Delete;
return request;
}
/// <summary>
- /// Sends the given parameters in the entity stream of an HTTP request.
- /// </summary>
- /// <param name="httpRequest">The HTTP request.</param>
- /// <param name="fields">The parameters to send.</param>
- /// <remarks>
- /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes
- /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>.
- /// </remarks>
- protected void SendParametersInEntity(HttpWebRequest httpRequest, IDictionary<string, string> fields) {
- Requires.NotNull(httpRequest, "httpRequest");
- Requires.NotNull(fields, "fields");
-
- string requestBody = MessagingUtilities.CreateQueryString(fields);
- byte[] requestBytes = PostEntityEncoding.GetBytes(requestBody);
- httpRequest.ContentType = HttpFormUrlEncodedContentType.ToString();
- httpRequest.ContentLength = requestBytes.Length;
- Stream requestStream = this.WebRequestHandler.GetRequestStream(httpRequest);
- try {
- requestStream.Write(requestBytes, 0, requestBytes.Length);
- } finally {
- // We need to be sure to close the request stream...
- // unless it is a MemoryStream, which is a clue that we're in
- // a mock stream situation and closing it would preclude reading it later.
- if (!(requestStream is MemoryStream)) {
- requestStream.Dispose();
- }
- }
- }
-
- /// <summary>
- /// Sends the given parameters in the entity stream of an HTTP request in multi-part format.
- /// </summary>
- /// <param name="httpRequest">The HTTP request.</param>
- /// <param name="fields">The parameters to send.</param>
- /// <remarks>
- /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes
- /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>.
- /// </remarks>
- protected void SendParametersInEntityAsMultipart(HttpWebRequest httpRequest, IEnumerable<MultipartPostPart> fields) {
- httpRequest.PostMultipartNoGetResponse(this.WebRequestHandler, fields);
- }
-
- /// <summary>
/// Verifies the integrity and applicability of an incoming message.
/// </summary>
/// <param name="message">The message just received.</param>
- /// <exception cref="ProtocolException">
- /// Thrown when the message is somehow invalid.
- /// This can be due to tampering, replay attack or expiration, among other things.
- /// </exception>
- protected virtual void ProcessIncomingMessage(IProtocolMessage message) {
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>
+ /// A task that completes with the asynchronous operation.
+ /// </returns>
+ /// <exception cref="UnprotectedMessageException">Thrown if the message does not have the minimal required protections applied.</exception>
+ /// <exception cref="ProtocolException">Thrown when the message is somehow invalid.
+ /// This can be due to tampering, replay attack or expiration, among other things.</exception>
+ protected virtual async Task ProcessIncomingMessageAsync(IProtocolMessage message, CancellationToken cancellationToken) {
Requires.NotNull(message, "message");
if (Logger.Channel.IsInfoEnabled) {
@@ -1252,10 +1183,14 @@ namespace DotNetOpenAuth.Messaging {
messageAccessor.ToStringDeferred());
}
+ if (this.IncomingMessageFilter != null) {
+ this.IncomingMessageFilter(message);
+ }
+
MessageProtections appliedProtection = MessageProtections.None;
foreach (IChannelBindingElement bindingElement in this.IncomingBindingElements) {
- Contract.Assume(bindingElement.Channel != null); // CC bug: this.IncomingBindingElements ensures this... why must we assume it here?
- MessageProtections? elementProtection = bindingElement.ProcessIncomingMessage(message);
+ Assumes.True(bindingElement.Channel != null); // CC bug: this.IncomingBindingElements ensures this... why must we assume it here?
+ MessageProtections? elementProtection = await bindingElement.ProcessIncomingMessageAsync(message, cancellationToken);
if (elementProtection.HasValue) {
Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName);
@@ -1316,7 +1251,7 @@ namespace DotNetOpenAuth.Messaging {
/// Performs additional processing on an outgoing web request before it is sent to the remote server.
/// </summary>
/// <param name="request">The request.</param>
- protected virtual void PrepareHttpWebRequest(HttpWebRequest request) {
+ protected virtual void PrepareHttpWebRequest(HttpRequestMessage request) {
Requires.NotNull(request, "request");
}
@@ -1350,8 +1285,7 @@ namespace DotNetOpenAuth.Messaging {
/// <returns>The properly ordered list of elements.</returns>
/// <exception cref="ProtocolException">Thrown when the binding elements are incomplete or inconsistent with each other.</exception>
private static IEnumerable<IChannelBindingElement> ValidateAndPrepareBindingElements(IEnumerable<IChannelBindingElement> elements) {
- Requires.NullOrWithNoNullElements(elements, "elements");
- Contract.Ensures(Contract.Result<IEnumerable<IChannelBindingElement>>() != null);
+ Requires.NullOrNotNullElements(elements, "elements");
if (elements == null) {
return new IChannelBindingElement[0];
}
@@ -1402,18 +1336,6 @@ namespace DotNetOpenAuth.Messaging {
return -((int)protection1).CompareTo((int)protection2); // descending flag ordinal order
}
-#if CONTRACTS_FULL
- /// <summary>
- /// Verifies conditions that should be true for any valid state of this object.
- /// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
- [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
- [ContractInvariantMethod]
- private void ObjectInvariant() {
- Contract.Invariant(this.MessageDescriptions != null);
- }
-#endif
-
/// <summary>
/// Verifies that all required message parts are initialized to values
/// prior to sending the message to a remote party.