summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Core/Messaging
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-12-29 21:20:40 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2012-12-29 21:20:40 -0800
commit187d3c24b6a76ec0898399f738b3a4f82031ceb0 (patch)
tree5ee920acbfbf1bca76e3a7b4edfcb04e930e2367 /src/DotNetOpenAuth.Core/Messaging
parent5e9014f36b2d53b8e419918675df636540ea24e2 (diff)
downloadDotNetOpenAuth-187d3c24b6a76ec0898399f738b3a4f82031ceb0.zip
DotNetOpenAuth-187d3c24b6a76ec0898399f738b3a4f82031ceb0.tar.gz
DotNetOpenAuth-187d3c24b6a76ec0898399f738b3a4f82031ceb0.tar.bz2
Replaces IDirectWebRequestHandler with HttpClient in DNOA.Core.
Build breaks are everywhere outside of just this one project as a result.
Diffstat (limited to 'src/DotNetOpenAuth.Core/Messaging')
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs182
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Channel.cs256
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs105
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs3
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs189
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs100
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs223
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs115
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs385
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs45
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs3
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs261
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs477
13 files changed, 87 insertions, 2257 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs
deleted file mode 100644
index 20b1831..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/CachedDirectWebResponse.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="CachedDirectWebResponse.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.IO;
- using System.Net;
- using System.Text;
- using Validation;
-
- /// <summary>
- /// Cached details on the response from a direct web request to a remote party.
- /// </summary>
- [DebuggerDisplay("{Status} {ContentType.MediaType}, length: {ResponseStream.Length}")]
- internal class CachedDirectWebResponse : IncomingWebResponse {
- /// <summary>
- /// A seekable, repeatable response stream.
- /// </summary>
- private MemoryStream responseStream;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class.
- /// </summary>
- internal CachedDirectWebResponse() {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class.
- /// </summary>
- /// <param name="requestUri">The request URI.</param>
- /// <param name="response">The response.</param>
- /// <param name="maximumBytesToRead">The maximum bytes to read.</param>
- internal CachedDirectWebResponse(Uri requestUri, HttpWebResponse response, int maximumBytesToRead)
- : base(requestUri, response) {
- Requires.NotNull(requestUri, "requestUri");
- Requires.NotNull(response, "response");
- this.responseStream = CacheNetworkStreamAndClose(response, maximumBytesToRead);
-
- // BUGBUG: if the response was exactly maximumBytesToRead, we'll incorrectly believe it was truncated.
- this.ResponseTruncated = this.responseStream.Length == maximumBytesToRead;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CachedDirectWebResponse"/> class.
- /// </summary>
- /// <param name="requestUri">The request URI.</param>
- /// <param name="responseUri">The final URI to respond to the request.</param>
- /// <param name="headers">The headers.</param>
- /// <param name="statusCode">The status code.</param>
- /// <param name="contentType">Type of the content.</param>
- /// <param name="contentEncoding">The content encoding.</param>
- /// <param name="responseStream">The response stream.</param>
- internal CachedDirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, MemoryStream responseStream)
- : base(requestUri, responseUri, headers, statusCode, contentType, contentEncoding) {
- Requires.NotNull(requestUri, "requestUri");
- Requires.NotNull(responseStream, "responseStream");
- this.responseStream = responseStream;
- }
-
- /// <summary>
- /// Gets a value indicating whether the cached response stream was
- /// truncated to a maximum allowable length.
- /// </summary>
- public bool ResponseTruncated { get; private set; }
-
- /// <summary>
- /// Gets the body of the HTTP response.
- /// </summary>
- public override Stream ResponseStream {
- get { return this.responseStream; }
- }
-
- /// <summary>
- /// Gets or sets the cached response stream.
- /// </summary>
- internal MemoryStream CachedResponseStream {
- get { return this.responseStream; }
- set { this.responseStream = value; }
- }
-
- /// <summary>
- /// Creates a text reader for the response stream.
- /// </summary>
- /// <returns>The text reader, initialized for the proper encoding.</returns>
- public override StreamReader GetResponseReader() {
- this.ResponseStream.Seek(0, SeekOrigin.Begin);
- string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
- Encoding encoding = null;
- if (!string.IsNullOrEmpty(contentEncoding)) {
- try {
- encoding = Encoding.GetEncoding(contentEncoding);
- } catch (ArgumentException ex) {
- Logger.Messaging.ErrorFormat("Encoding.GetEncoding(\"{0}\") threw ArgumentException: {1}", contentEncoding, ex);
- }
- }
-
- return encoding != null ? new StreamReader(this.ResponseStream, encoding) : new StreamReader(this.ResponseStream);
- }
-
- /// <summary>
- /// Gets the body of the response as a string.
- /// </summary>
- /// <returns>The entire body of the response.</returns>
- internal string GetResponseString() {
- if (this.ResponseStream != null) {
- string value = this.GetResponseReader().ReadToEnd();
- this.ResponseStream.Seek(0, SeekOrigin.Begin);
- return value;
- } else {
- return null;
- }
- }
-
- /// <summary>
- /// Gets an offline snapshot version of this instance.
- /// </summary>
- /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param>
- /// <returns>A snapshot version of this instance.</returns>
- /// <remarks>
- /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot
- /// will automatically close and dispose of the underlying response stream.
- /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will
- /// be the self same instance.
- /// </remarks>
- internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) {
- return this;
- }
-
- /// <summary>
- /// Sets the response to some string, encoded as UTF-8.
- /// </summary>
- /// <param name="body">The string to set the response to.</param>
- internal void SetResponse(string body) {
- if (body == null) {
- this.responseStream = null;
- return;
- }
-
- Encoding encoding = Encoding.UTF8;
- this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName;
- this.responseStream = new MemoryStream();
- StreamWriter writer = new StreamWriter(this.ResponseStream, encoding);
- writer.Write(body);
- writer.Flush();
- this.ResponseStream.Seek(0, SeekOrigin.Begin);
- }
-
- /// <summary>
- /// Caches the network stream and closes it if it is open.
- /// </summary>
- /// <param name="response">The response whose stream is to be cloned.</param>
- /// <param name="maximumBytesToRead">The maximum bytes to cache.</param>
- /// <returns>The seekable Stream instance that contains a copy of what was returned in the HTTP response.</returns>
- [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")]
- private static MemoryStream CacheNetworkStreamAndClose(HttpWebResponse response, int maximumBytesToRead) {
- Requires.NotNull(response, "response");
-
- // Now read and cache the network stream
- Stream networkStream = response.GetResponseStream();
- MemoryStream cachedStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : Math.Min((int)response.ContentLength, maximumBytesToRead));
- try {
- Assumes.True(networkStream.CanRead, "HttpWebResponse.GetResponseStream() always returns a readable stream."); // CC missing
- Assumes.True(cachedStream.CanWrite, "This is a MemoryStream -- it's always writable."); // CC missing
- networkStream.CopyTo(cachedStream);
- cachedStream.Seek(0, SeekOrigin.Begin);
-
- networkStream.Dispose();
- response.Close();
-
- return cachedStream;
- } catch {
- cachedStream.Dispose();
- throw;
- }
- }
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
index 9c2ba8c..ad094e0 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
@@ -17,10 +17,13 @@ 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.Runtime.Serialization.Json;
using System.Text;
using System.Threading;
+ using System.Threading.Tasks;
using System.Web;
using System.Xml;
using DotNetOpenAuth.Messaging.Reflection;
@@ -156,11 +159,20 @@ namespace DotNetOpenAuth.Messaging {
/// 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="messageHandler">The HTTP handler to use for outgoing HTTP requests.</param>
+ protected Channel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements, HttpMessageHandler messageHandler = null) {
Requires.NotNull(messageTypeProvider, "messageTypeProvider");
+ messageHandler = messageHandler ?? new WebRequestHandler();
+ var httpHandler = messageHandler as WebRequestHandler;
+ if (httpHandler != null) {
+ // TODO: provide this as a recommendation to derived Channel types, but without tampering with
+ // the setting once it has been provided to this constructor.
+ httpHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
+ }
+
this.messageTypeProvider = messageTypeProvider;
- this.WebRequestHandler = new StandardWebRequestHandler();
+ this.WebRequestHandler = new HttpClient(messageHandler);
this.XmlDictionaryReaderQuotas = DefaultUntrustedXmlDictionaryReaderQuotas;
this.outgoingBindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements));
@@ -178,14 +190,14 @@ namespace DotNetOpenAuth.Messaging {
internal event EventHandler<ChannelEventArgs> Sending;
/// <summary>
- /// Gets or sets an instance to a <see cref="IDirectWebRequestHandler"/> that will be used when
+ /// Gets or sets an instance to a <see cref="HttpClient"/> that will be used when
/// submitting HTTP requests and waiting for responses.
/// </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 HttpClient WebRequestHandler { get; set; }
/// <summary>
/// Gets or sets the maximum allowable size for a 301 Redirect response before we send
@@ -272,75 +284,24 @@ 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) {
- RequiresEx.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) {
- RequiresEx.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>
/// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
- public OutgoingWebResponse PrepareResponse(IProtocolMessage message) {
+ public HttpResponseMessage PrepareResponse(IProtocolMessage message) {
Requires.NotNull(message, "message");
this.ProcessOutgoingMessage(message);
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.
@@ -369,8 +330,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;
}
@@ -502,11 +468,11 @@ namespace DotNetOpenAuth.Messaging {
/// 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)
where TResponse : class, IProtocolMessage {
Requires.NotNull(requestMessage, "requestMessage");
- IProtocolMessage response = this.Request(requestMessage);
+ IProtocolMessage response = await this.RequestAsync(requestMessage);
ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse));
var expectedResponse = response as TResponse;
@@ -521,12 +487,12 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="requestMessage">The message to send.</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) {
Requires.NotNull(requestMessage, "requestMessage");
this.ProcessOutgoingMessage(requestMessage);
Logger.Channel.DebugFormat("Sending {0} request.", requestMessage.GetType().Name);
- var responseMessage = this.RequestCore(requestMessage);
+ var responseMessage = await this.RequestCoreAsync(requestMessage);
ErrorUtilities.VerifyProtocol(responseMessage != null, MessagingStrings.ExpectedMessageNotReceived, typeof(IProtocolMessage).Name);
Logger.Channel.DebugFormat("Received {0} response.", responseMessage.GetType().Name);
@@ -565,10 +531,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);
}
@@ -581,7 +547,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);
}
@@ -591,7 +557,7 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="response">The response that is anticipated to contain an protocol message.</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) {
+ internal IDictionary<string, string> ReadFromResponseCoreTestHook(HttpResponseMessage response) {
return this.ReadFromResponseCore(response);
}
@@ -659,11 +625,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]);
}
@@ -699,17 +665,6 @@ 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>
@@ -719,23 +674,23 @@ namespace DotNetOpenAuth.Messaging {
/// 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) {
Requires.NotNull(request, "request");
Requires.That(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
- HttpWebRequest webRequest = this.CreateHttpRequest(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];
+ webRequest.Headers.Add(header, directRequest.Headers[header]);
}
}
IDictionary<string, string> responseFields;
IDirectResponseProtocolMessage responseMessage;
- using (IncomingWebResponse response = this.GetDirectResponse(webRequest)) {
- if (response.ResponseStream == null) {
+ using (HttpResponseMessage response = await this.WebRequestHandler.SendAsync(webRequest)) {
+ if (response.Content == null) {
return null;
}
@@ -763,7 +718,7 @@ 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>
@@ -827,7 +782,7 @@ 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.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
Requires.That((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0, "message", "GET or POST expected.");
@@ -837,7 +792,7 @@ namespace DotNetOpenAuth.Messaging {
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;
@@ -849,7 +804,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.
@@ -876,14 +831,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.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
Requires.NotNull(fields, "fields");
// 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);
@@ -891,16 +845,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 HttpResponseMessage {
+ 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;
}
@@ -912,13 +864,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.That(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient);
Requires.NotNull(fields, "fields");
- 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) {
@@ -932,12 +882,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 HttpResponseMessage {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(bodyWriter.ToString()),
};
+ response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return response;
}
@@ -949,7 +898,7 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="response">The response that is anticipated to contain an protocol message.</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 IDictionary<string, string> ReadFromResponseCore(HttpResponseMessage response);
/// <summary>
/// Prepares an HTTP request that carries a given message.
@@ -957,10 +906,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="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.That(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
throw new NotImplementedException();
@@ -975,7 +924,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.
@@ -1072,7 +1021,7 @@ 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.That(requestMessage.Recipient != null, "requestMessage", MessagingStrings.DirectedMessageMissingRecipient);
@@ -1081,7 +1030,7 @@ namespace DotNetOpenAuth.Messaging {
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;
@@ -1096,12 +1045,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.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;
}
@@ -1115,27 +1064,31 @@ 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");
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 = new MultipartFormDataContent();
+ foreach (var part in requestMessageWithBinaryData.BinaryData) {
+ content.Add(part);
+ }
// 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;
@@ -1149,11 +1102,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");
- HttpWebRequest request = this.InitializeRequestAsGet(requestMessage);
- request.Method = "PUT";
+ var request = this.InitializeRequestAsGet(requestMessage);
+ request.Method = HttpMethod.Put;
return request;
}
@@ -1165,58 +1118,15 @@ 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");
- 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>
@@ -1301,7 +1211,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");
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs
deleted file mode 100644
index f3975b3..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/IDirectWebRequestHandler.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="IDirectWebRequestHandler.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.Contracts;
- using System.IO;
- using System.Net;
- using DotNetOpenAuth.Messaging;
- using Validation;
-
- /// <summary>
- /// A contract for <see cref="HttpWebRequest"/> handling.
- /// </summary>
- /// <remarks>
- /// Implementations of this interface must be thread safe.
- /// </remarks>
- public interface IDirectWebRequestHandler {
- /// <summary>
- /// Determines whether this instance can support the specified options.
- /// </summary>
- /// <param name="options">The set of options that might be given in a subsequent web request.</param>
- /// <returns>
- /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>.
- /// </returns>
- [Pure]
- bool CanSupport(DirectWebRequestOptions options);
-
- /// <summary>
- /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
- /// <returns>
- /// The stream the caller should write out the entity data to.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
- /// and any other appropriate properties <i>before</i> calling this method.
- /// Callers <i>must</i> close and dispose of the request stream when they are done
- /// writing to it to avoid taking up the connection too long and causing long waits on
- /// subsequent requests.</para>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch.</para>
- /// </remarks>
- Stream GetRequestStream(HttpWebRequest request);
-
- /// <summary>
- /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
- /// <param name="options">The options to apply to this web request.</param>
- /// <returns>
- /// The stream the caller should write out the entity data to.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
- /// and any other appropriate properties <i>before</i> calling this method.
- /// Callers <i>must</i> close and dispose of the request stream when they are done
- /// writing to it to avoid taking up the connection too long and causing long waits on
- /// subsequent requests.</para>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch.</para>
- /// </remarks>
- Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options);
-
- /// <summary>
- /// Processes an <see cref="HttpWebRequest"/> and converts the
- /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
- /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
- /// value, if set, should be Closed before throwing.</para>
- /// </remarks>
- IncomingWebResponse GetResponse(HttpWebRequest request);
-
- /// <summary>
- /// Processes an <see cref="HttpWebRequest"/> and converts the
- /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
- /// <param name="options">The options to apply to this web request.</param>
- /// <returns>An instance of <see cref="IncomingWebResponse"/> describing the response.</returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
- /// value, if set, should be Closed before throwing.</para>
- /// </remarks>
- IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options);
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs b/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs
index 2992678..074894c 100644
--- a/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/IMessageWithBinaryData.cs
@@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Messaging {
using System;
using System.Collections.Generic;
using System.Linq;
+ using System.Net.Http;
using System.Text;
/// <summary>
@@ -19,7 +20,7 @@ namespace DotNetOpenAuth.Messaging {
/// Gets the parts of the message that carry binary data.
/// </summary>
/// <value>A list of parts. Never null.</value>
- IList<MultipartPostPart> BinaryData { get; }
+ IList<HttpContent> BinaryData { get; }
/// <summary>
/// Gets a value indicating whether this message should be sent as multi-part POST.
diff --git a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs
deleted file mode 100644
index abb01a1..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/IncomingWebResponse.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="IncomingWebResponse.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Diagnostics.CodeAnalysis;
- using System.Globalization;
- using System.IO;
- using System.Net;
- using System.Net.Mime;
- using System.Text;
- using Validation;
-
- /// <summary>
- /// Details on the incoming response from a direct web request to a remote party.
- /// </summary>
- public abstract class IncomingWebResponse : IDisposable {
- /// <summary>
- /// The encoding to use in reading a response that does not declare its own content encoding.
- /// </summary>
- private const string DefaultContentEncoding = "ISO-8859-1";
-
- /// <summary>
- /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class.
- /// </summary>
- protected internal IncomingWebResponse() {
- this.Status = HttpStatusCode.OK;
- this.Headers = new WebHeaderCollection();
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class.
- /// </summary>
- /// <param name="requestUri">The original request URI.</param>
- /// <param name="response">The response to initialize from. The network stream is used by this class directly.</param>
- protected IncomingWebResponse(Uri requestUri, HttpWebResponse response) {
- Requires.NotNull(requestUri, "requestUri");
- Requires.NotNull(response, "response");
-
- this.RequestUri = requestUri;
- if (!string.IsNullOrEmpty(response.ContentType)) {
- try {
- this.ContentType = new ContentType(response.ContentType);
- } catch (FormatException) {
- Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", response.ResponseUri.AbsoluteUri, response.ContentType);
- }
- }
- this.ContentEncoding = string.IsNullOrEmpty(response.ContentEncoding) ? DefaultContentEncoding : response.ContentEncoding;
- this.FinalUri = response.ResponseUri;
- this.Status = response.StatusCode;
- this.Headers = response.Headers;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="IncomingWebResponse"/> class.
- /// </summary>
- /// <param name="requestUri">The request URI.</param>
- /// <param name="responseUri">The final URI to respond to the request.</param>
- /// <param name="headers">The headers.</param>
- /// <param name="statusCode">The status code.</param>
- /// <param name="contentType">Type of the content.</param>
- /// <param name="contentEncoding">The content encoding.</param>
- protected IncomingWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding) {
- Requires.NotNull(requestUri, "requestUri");
-
- this.RequestUri = requestUri;
- this.Status = statusCode;
- if (!string.IsNullOrEmpty(contentType)) {
- try {
- this.ContentType = new ContentType(contentType);
- } catch (FormatException) {
- Logger.Messaging.ErrorFormat("HTTP response to {0} included an invalid Content-Type header value: {1}", responseUri.AbsoluteUri, contentType);
- }
- }
- this.ContentEncoding = string.IsNullOrEmpty(contentEncoding) ? DefaultContentEncoding : contentEncoding;
- this.Headers = headers;
- this.FinalUri = responseUri;
- }
-
- /// <summary>
- /// Gets the type of the content.
- /// </summary>
- public ContentType ContentType { get; private set; }
-
- /// <summary>
- /// Gets the content encoding.
- /// </summary>
- public string ContentEncoding { get; private set; }
-
- /// <summary>
- /// Gets the URI of the initial request.
- /// </summary>
- public Uri RequestUri { get; private set; }
-
- /// <summary>
- /// Gets the URI that finally responded to the request.
- /// </summary>
- /// <remarks>
- /// This can be different from the <see cref="RequestUri"/> in cases of
- /// redirection during the request.
- /// </remarks>
- public Uri FinalUri { get; internal set; }
-
- /// <summary>
- /// Gets the headers that must be included in the response to the user agent.
- /// </summary>
- /// <remarks>
- /// The headers in this collection are not meant to be a comprehensive list
- /// of exactly what should be sent, but are meant to augment whatever headers
- /// are generally included in a typical response.
- /// </remarks>
- public WebHeaderCollection Headers { get; internal set; }
-
- /// <summary>
- /// Gets the HTTP status code to use in the HTTP response.
- /// </summary>
- public HttpStatusCode Status { get; internal set; }
-
- /// <summary>
- /// Gets the body of the HTTP response.
- /// </summary>
- public abstract Stream ResponseStream { get; }
-
- /// <summary>
- /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
- /// </summary>
- /// <returns>
- /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
- /// </returns>
- public override string ToString() {
- StringBuilder sb = new StringBuilder();
- sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "RequestUri = {0}", this.RequestUri));
- sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ResponseUri = {0}", this.FinalUri));
- sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "StatusCode = {0}", this.Status));
- sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentType = {0}", this.ContentType));
- sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "ContentEncoding = {0}", this.ContentEncoding));
- sb.AppendLine("Headers:");
- foreach (string header in this.Headers) {
- sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "\t{0}: {1}", header, this.Headers[header]));
- }
-
- return sb.ToString();
- }
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose() {
- this.Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Creates a text reader for the response stream.
- /// </summary>
- /// <returns>The text reader, initialized for the proper encoding.</returns>
- [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")]
- public abstract StreamReader GetResponseReader();
-
- /// <summary>
- /// Gets an offline snapshot version of this instance.
- /// </summary>
- /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param>
- /// <returns>A snapshot version of this instance.</returns>
- /// <remarks>
- /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot
- /// will automatically close and dispose of the underlying response stream.
- /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will
- /// be the self same instance.
- /// </remarks>
- internal abstract CachedDirectWebResponse GetSnapshot(int maximumBytesToCache);
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources
- /// </summary>
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool disposing) {
- if (disposing) {
- Stream responseStream = this.ResponseStream;
- if (responseStream != null) {
- responseStream.Dispose();
- }
- }
- }
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
index 221a29c..3da62e9 100644
--- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
@@ -156,37 +156,6 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult.
- /// </summary>
- /// <param name="response">The response to send to the user agent.</param>
- /// <returns>The <see cref="ActionResult"/> instance to be returned by the Controller's action method.</returns>
- public static ActionResult AsActionResult(this OutgoingWebResponse response) {
- Requires.NotNull(response, "response");
- return new OutgoingWebResponseActionResult(response);
- }
-
- /// <summary>
- /// Transforms an OutgoingWebResponse to a Web API-friendly HttpResponseMessage.
- /// </summary>
- /// <param name="outgoingResponse">The response to send to the user agent.</param>
- /// <returns>The <see cref="HttpResponseMessage"/> instance to be returned by the Web API method.</returns>
- public static HttpResponseMessage AsHttpResponseMessage(this OutgoingWebResponse outgoingResponse) {
- HttpResponseMessage response = new HttpResponseMessage(outgoingResponse.Status);
- if (outgoingResponse.ResponseStream != null) {
- response.Content = new StreamContent(outgoingResponse.ResponseStream);
- }
-
- var responseHeaders = outgoingResponse.Headers;
- foreach (var header in responseHeaders.AllKeys) {
- if (!response.Headers.TryAddWithoutValidation(header, responseHeaders[header])) {
- response.Content.Headers.TryAddWithoutValidation(header, responseHeaders[header]);
- }
- }
-
- return response;
- }
-
- /// <summary>
/// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any.
/// Cookieless session directory (if applicable) is also included.
/// </summary>
@@ -223,22 +192,6 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Sends a multipart HTTP POST request (useful for posting files).
- /// </summary>
- /// <param name="request">The HTTP request.</param>
- /// <param name="requestHandler">The request handler.</param>
- /// <param name="parts">The parts to include in the POST entity.</param>
- /// <returns>The HTTP response.</returns>
- public static IncomingWebResponse PostMultipart(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) {
- Requires.NotNull(request, "request");
- Requires.NotNull(requestHandler, "requestHandler");
- Requires.NotNull(parts, "parts");
-
- PostMultipartNoGetResponse(request, requestHandler, parts);
- return requestHandler.GetResponse(request);
- }
-
- /// <summary>
/// Assembles a message comprised of the message on a given exception and all inner exceptions.
/// </summary>
/// <param name="exception">The exception.</param>
@@ -496,59 +449,6 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Sends a multipart HTTP POST request (useful for posting files) but doesn't call GetResponse on it.
- /// </summary>
- /// <param name="request">The HTTP request.</param>
- /// <param name="requestHandler">The request handler.</param>
- /// <param name="parts">The parts to include in the POST entity.</param>
- internal static void PostMultipartNoGetResponse(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) {
- Requires.NotNull(request, "request");
- Requires.NotNull(requestHandler, "requestHandler");
- Requires.NotNull(parts, "parts");
-
- Reporting.RecordFeatureUse("MessagingUtilities.PostMultipart");
- parts = parts.CacheGeneratedResults();
- string boundary = Guid.NewGuid().ToString();
- string initialPartLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "--{0}\r\n", boundary);
- string partLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}\r\n", boundary);
- string finalTrailingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}--\r\n", boundary);
- var contentType = new ContentType("multipart/form-data") {
- Boundary = boundary,
- CharSet = Channel.PostEntityEncoding.WebName,
- };
-
- request.Method = "POST";
- request.ContentType = contentType.ToString();
- long contentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length;
- if (parts.Any()) {
- contentLength -= 2; // the initial part leading boundary has no leading \r\n
- }
- request.ContentLength = contentLength;
-
- var requestStream = requestHandler.GetRequestStream(request);
- try {
- StreamWriter writer = new StreamWriter(requestStream, Channel.PostEntityEncoding);
- bool firstPart = true;
- foreach (var part in parts) {
- writer.Write(firstPart ? initialPartLeadingBoundary : partLeadingBoundary);
- firstPart = false;
- part.Serialize(writer);
- part.Dispose();
- }
-
- writer.Write(finalTrailingBoundary);
- writer.Flush();
- } 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>
/// Assembles the content of the HTTP Authorization or WWW-Authenticate header.
/// </summary>
/// <param name="scheme">The scheme.</param>
diff --git a/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs b/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs
deleted file mode 100644
index b4a0968..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/MultipartPostPart.cs
+++ /dev/null
@@ -1,223 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="MultipartPostPart.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.IO;
- using System.Net;
- using System.Text;
- using Validation;
-
- /// <summary>
- /// Represents a single part in a HTTP multipart POST request.
- /// </summary>
- public class MultipartPostPart : IDisposable {
- /// <summary>
- /// The "Content-Disposition" string.
- /// </summary>
- private const string ContentDispositionHeader = "Content-Disposition";
-
- /// <summary>
- /// The two-character \r\n newline character sequence to use.
- /// </summary>
- private const string NewLine = "\r\n";
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MultipartPostPart"/> class.
- /// </summary>
- /// <param name="contentDisposition">The content disposition of the part.</param>
- public MultipartPostPart(string contentDisposition) {
- Requires.NotNullOrEmpty(contentDisposition, "contentDisposition");
-
- this.ContentDisposition = contentDisposition;
- this.ContentAttributes = new Dictionary<string, string>();
- this.PartHeaders = new WebHeaderCollection();
- }
-
- /// <summary>
- /// Gets or sets the content disposition.
- /// </summary>
- /// <value>The content disposition.</value>
- public string ContentDisposition { get; set; }
-
- /// <summary>
- /// Gets the key=value attributes that appear on the same line as the Content-Disposition.
- /// </summary>
- /// <value>The content attributes.</value>
- public IDictionary<string, string> ContentAttributes { get; private set; }
-
- /// <summary>
- /// Gets the headers that appear on subsequent lines after the Content-Disposition.
- /// </summary>
- public WebHeaderCollection PartHeaders { get; private set; }
-
- /// <summary>
- /// Gets or sets the content of the part.
- /// </summary>
- public Stream Content { get; set; }
-
- /// <summary>
- /// Gets the length of this entire part.
- /// </summary>
- /// <remarks>Useful for calculating the ContentLength HTTP header to send before actually serializing the content.</remarks>
- public long Length {
- get {
- ErrorUtilities.VerifyOperation(this.Content != null && this.Content.Length >= 0, MessagingStrings.StreamMustHaveKnownLength);
-
- long length = 0;
- length += ContentDispositionHeader.Length;
- length += ": ".Length;
- length += this.ContentDisposition.Length;
- foreach (var pair in this.ContentAttributes) {
- length += "; ".Length + pair.Key.Length + "=\"".Length + pair.Value.Length + "\"".Length;
- }
-
- length += NewLine.Length;
- foreach (string headerName in this.PartHeaders) {
- length += headerName.Length;
- length += ": ".Length;
- length += this.PartHeaders[headerName].Length;
- length += NewLine.Length;
- }
-
- length += NewLine.Length;
- length += this.Content.Length;
-
- return length;
- }
- }
-
- /// <summary>
- /// Creates a part that represents a simple form field.
- /// </summary>
- /// <param name="name">The name of the form field.</param>
- /// <param name="value">The value.</param>
- /// <returns>The constructed part.</returns>
- public static MultipartPostPart CreateFormPart(string name, string value) {
- Requires.NotNullOrEmpty(name, "name");
- Requires.NotNull(value, "value");
-
- var part = new MultipartPostPart("form-data");
- try {
- part.ContentAttributes["name"] = name;
- part.Content = new MemoryStream(Encoding.UTF8.GetBytes(value));
- return part;
- } catch {
- part.Dispose();
- throw;
- }
- }
-
- /// <summary>
- /// Creates a part that represents a file attachment.
- /// </summary>
- /// <param name="name">The name of the form field.</param>
- /// <param name="filePath">The path to the file to send.</param>
- /// <param name="contentType">Type of the content in HTTP Content-Type format.</param>
- /// <returns>The constructed part.</returns>
- public static MultipartPostPart CreateFormFilePart(string name, string filePath, string contentType) {
- Requires.NotNullOrEmpty(name, "name");
- Requires.NotNullOrEmpty(filePath, "filePath");
- Requires.NotNullOrEmpty(contentType, "contentType");
-
- string fileName = Path.GetFileName(filePath);
- var fileStream = File.OpenRead(filePath);
- try {
- return CreateFormFilePart(name, fileName, contentType, fileStream);
- } catch {
- fileStream.Dispose();
- throw;
- }
- }
-
- /// <summary>
- /// Creates a part that represents a file attachment.
- /// </summary>
- /// <param name="name">The name of the form field.</param>
- /// <param name="fileName">Name of the file as the server should see it.</param>
- /// <param name="contentType">Type of the content in HTTP Content-Type format.</param>
- /// <param name="content">The content of the file.</param>
- /// <returns>The constructed part.</returns>
- public static MultipartPostPart CreateFormFilePart(string name, string fileName, string contentType, Stream content) {
- Requires.NotNullOrEmpty(name, "name");
- Requires.NotNullOrEmpty(fileName, "fileName");
- Requires.NotNullOrEmpty(contentType, "contentType");
- Requires.NotNull(content, "content");
-
- var part = new MultipartPostPart("file");
- try {
- part.ContentAttributes["name"] = name;
- part.ContentAttributes["filename"] = fileName;
- part.PartHeaders[HttpRequestHeader.ContentType] = contentType;
- if (!contentType.StartsWith("text/", StringComparison.Ordinal)) {
- part.PartHeaders["Content-Transfer-Encoding"] = "binary";
- }
-
- part.Content = content;
- return part;
- } catch {
- part.Dispose();
- throw;
- }
- }
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose() {
- this.Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Serializes the part to a stream.
- /// </summary>
- /// <param name="streamWriter">The stream writer.</param>
- internal void Serialize(StreamWriter streamWriter) {
- // VERY IMPORTANT: any changes at all made to this must be kept in sync with the
- // Length property which calculates exactly how many bytes this method will write.
- streamWriter.NewLine = NewLine;
- streamWriter.Write("{0}: {1}", ContentDispositionHeader, this.ContentDisposition);
- foreach (var pair in this.ContentAttributes) {
- streamWriter.Write("; {0}=\"{1}\"", pair.Key, pair.Value);
- }
-
- streamWriter.WriteLine();
- foreach (string headerName in this.PartHeaders) {
- streamWriter.WriteLine("{0}: {1}", headerName, this.PartHeaders[headerName]);
- }
-
- streamWriter.WriteLine();
- streamWriter.Flush();
- this.Content.CopyTo(streamWriter.BaseStream);
- }
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources
- /// </summary>
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool disposing) {
- if (disposing) {
- this.Content.Dispose();
- }
- }
-
-#if CONTRACTS_FULL
- /// <summary>
- /// Verifies conditions that should be true for any valid state of this object.
- /// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
- [ContractInvariantMethod]
- private void Invariant() {
- Contract.Invariant(!string.IsNullOrEmpty(this.ContentDisposition));
- Contract.Invariant(this.PartHeaders != null);
- Contract.Invariant(this.ContentAttributes != null);
- }
-#endif
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs
deleted file mode 100644
index 754d71d..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/NetworkDirectWebResponse.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="NetworkDirectWebResponse.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Diagnostics;
- using System.IO;
- using System.Net;
- using System.Text;
- using Validation;
-
- /// <summary>
- /// A live network HTTP response
- /// </summary>
- [DebuggerDisplay("{Status} {ContentType.MediaType}")]
- internal class NetworkDirectWebResponse : IncomingWebResponse, IDisposable {
- /// <summary>
- /// The network response object, used to initialize this instance, that still needs
- /// to be closed if applicable.
- /// </summary>
- private HttpWebResponse httpWebResponse;
-
- /// <summary>
- /// The incoming network response stream.
- /// </summary>
- private Stream responseStream;
-
- /// <summary>
- /// A value indicating whether a stream reader has already been
- /// created on this instance.
- /// </summary>
- private bool streamReadBegun;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="NetworkDirectWebResponse"/> class.
- /// </summary>
- /// <param name="requestUri">The request URI.</param>
- /// <param name="response">The response.</param>
- internal NetworkDirectWebResponse(Uri requestUri, HttpWebResponse response)
- : base(requestUri, response) {
- Requires.NotNull(requestUri, "requestUri");
- Requires.NotNull(response, "response");
- this.httpWebResponse = response;
- this.responseStream = response.GetResponseStream();
- }
-
- /// <summary>
- /// Gets the body of the HTTP response.
- /// </summary>
- public override Stream ResponseStream {
- get { return this.responseStream; }
- }
-
- /// <summary>
- /// Creates a text reader for the response stream.
- /// </summary>
- /// <returns>The text reader, initialized for the proper encoding.</returns>
- public override StreamReader GetResponseReader() {
- this.streamReadBegun = true;
- if (this.responseStream == null) {
- throw new ObjectDisposedException(GetType().Name);
- }
-
- string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
- if (string.IsNullOrEmpty(contentEncoding)) {
- return new StreamReader(this.ResponseStream);
- } else {
- return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding));
- }
- }
-
- /// <summary>
- /// Gets an offline snapshot version of this instance.
- /// </summary>
- /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param>
- /// <returns>A snapshot version of this instance.</returns>
- /// <remarks>
- /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot
- /// will automatically close and dispose of the underlying response stream.
- /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will
- /// be the self same instance.
- /// </remarks>
- internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) {
- ErrorUtilities.VerifyOperation(!this.streamReadBegun, "Network stream reading has already begun.");
- ErrorUtilities.VerifyOperation(this.httpWebResponse != null, "httpWebResponse != null");
-
- this.streamReadBegun = true;
- var result = new CachedDirectWebResponse(this.RequestUri, this.httpWebResponse, maximumBytesToCache);
- this.Dispose();
- return result;
- }
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources
- /// </summary>
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected override void Dispose(bool disposing) {
- if (disposing) {
- if (this.responseStream != null) {
- this.responseStream.Dispose();
- this.responseStream = null;
- }
- if (this.httpWebResponse != null) {
- this.httpWebResponse.Close();
- this.httpWebResponse = null;
- }
- }
-
- base.Dispose(disposing);
- }
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
deleted file mode 100644
index be7774f..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
+++ /dev/null
@@ -1,385 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="OutgoingWebResponse.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.ComponentModel;
- using System.Diagnostics.CodeAnalysis;
- using System.IO;
- using System.Net;
- using System.Net.Mime;
- using System.ServiceModel.Web;
- using System.Text;
- using System.Threading;
- using System.Web;
- using Validation;
-
- /// <summary>
- /// A protocol message (request or response) that passes from this
- /// to a remote party via the user agent using a redirect or form
- /// POST submission, OR a direct message response.
- /// </summary>
- /// <remarks>
- /// <para>An instance of this type describes the HTTP response that must be sent
- /// in response to the current HTTP request.</para>
- /// <para>It is important that this response make up the entire HTTP response.
- /// A hosting ASPX page should not be allowed to render its normal HTML output
- /// after this response is sent. The normal rendered output of an ASPX page
- /// can be canceled by calling <see cref="HttpResponse.End"/> after this message
- /// is sent on the response stream.</para>
- /// </remarks>
- public class OutgoingWebResponse {
- /// <summary>
- /// The encoder to use for serializing the response body.
- /// </summary>
- private static Encoding bodyStringEncoder = new UTF8Encoding(false);
-
- /// <summary>
- /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class.
- /// </summary>
- internal OutgoingWebResponse() {
- this.Status = HttpStatusCode.OK;
- this.Headers = new WebHeaderCollection();
- this.Cookies = new HttpCookieCollection();
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class
- /// based on the contents of an <see cref="HttpWebResponse"/>.
- /// </summary>
- /// <param name="response">The <see cref="HttpWebResponse"/> to clone.</param>
- /// <param name="maximumBytesToRead">The maximum bytes to read from the response stream.</param>
- protected internal OutgoingWebResponse(HttpWebResponse response, int maximumBytesToRead) {
- Requires.NotNull(response, "response");
-
- this.Status = response.StatusCode;
- this.Headers = response.Headers;
- this.Cookies = new HttpCookieCollection();
- this.ResponseStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : (int)response.ContentLength);
- using (Stream responseStream = response.GetResponseStream()) {
- // BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here.
- this.IsResponseTruncated = responseStream.CopyUpTo(this.ResponseStream, maximumBytesToRead) == maximumBytesToRead;
- this.ResponseStream.Seek(0, SeekOrigin.Begin);
- }
- }
-
- /// <summary>
- /// Gets the headers that must be included in the response to the user agent.
- /// </summary>
- /// <remarks>
- /// The headers in this collection are not meant to be a comprehensive list
- /// of exactly what should be sent, but are meant to augment whatever headers
- /// are generally included in a typical response.
- /// </remarks>
- public WebHeaderCollection Headers { get; internal set; }
-
- /// <summary>
- /// Gets the body of the HTTP response.
- /// </summary>
- public Stream ResponseStream { get; internal set; }
-
- /// <summary>
- /// Gets a value indicating whether the response stream is incomplete due
- /// to a length limitation imposed by the HttpWebRequest or calling method.
- /// </summary>
- public bool IsResponseTruncated { get; internal set; }
-
- /// <summary>
- /// Gets the cookies collection to add as headers to the HTTP response.
- /// </summary>
- public HttpCookieCollection Cookies { get; internal set; }
-
- /// <summary>
- /// Gets or sets the body of the response as a string.
- /// </summary>
- public string Body {
- get { return this.ResponseStream != null ? this.GetResponseReader().ReadToEnd() : null; }
- set { this.SetResponse(value, null); }
- }
-
- /// <summary>
- /// Gets the HTTP status code to use in the HTTP response.
- /// </summary>
- public HttpStatusCode Status { get; internal set; }
-
- /// <summary>
- /// Gets or sets a reference to the actual protocol message that
- /// is being sent via the user agent.
- /// </summary>
- internal IProtocolMessage OriginalMessage { get; set; }
-
- /// <summary>
- /// Creates a text reader for the response stream.
- /// </summary>
- /// <returns>The text reader, initialized for the proper encoding.</returns>
- [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")]
- public StreamReader GetResponseReader() {
- this.ResponseStream.Seek(0, SeekOrigin.Begin);
- string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
- if (string.IsNullOrEmpty(contentEncoding)) {
- return new StreamReader(this.ResponseStream);
- } else {
- return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding));
- }
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and ends execution on the current page or handler.
- /// </summary>
- /// <exception cref="ThreadAbortException">Typically 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 a current HttpContext.
- /// </remarks>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual void Send() {
- RequiresEx.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
-
- this.Send(HttpContext.Current);
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and ends execution on the current page or handler.
- /// </summary>
- /// <param name="context">The context of the HTTP request whose response should be set.
- /// Typically this is <see cref="HttpContext.Current"/>.</param>
- /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual void Send(HttpContext context) {
- this.Respond(new HttpContextWrapper(context), true);
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and ends execution on the current page or handler.
- /// </summary>
- /// <param name="context">The context of the HTTP request whose response should be set.
- /// Typically this is <see cref="HttpContext.Current"/>.</param>
- /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual void Send(HttpContextBase context) {
- this.Respond(context, true);
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and signals ASP.NET to short-circuit the page execution pipeline
- /// now that the response has been completed.
- /// Not safe to call from ASP.NET web forms.
- /// </summary>
- /// <remarks>
- /// Requires a current HttpContext.
- /// 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 virtual void Respond() {
- RequiresEx.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
-
- this.Respond(HttpContext.Current);
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and signals ASP.NET to short-circuit the page execution pipeline
- /// now that the response has been completed.
- /// Not safe to call from ASP.NET web forms.
- /// </summary>
- /// <param name="context">The context of the HTTP request whose response should be set.
- /// Typically this is <see cref="HttpContext.Current"/>.</param>
- /// <remarks>
- /// 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(HttpContext context) {
- Requires.NotNull(context, "context");
- this.Respond(new HttpContextWrapper(context));
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and signals ASP.NET to short-circuit the page execution pipeline
- /// now that the response has been completed.
- /// Not safe to call from ASP.NET web forms.
- /// </summary>
- /// <param name="context">The context of the HTTP request whose response should be set.
- /// Typically this is <see cref="HttpContext.Current"/>.</param>
- /// <remarks>
- /// 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 virtual void Respond(HttpContextBase context) {
- Requires.NotNull(context, "context");
-
- this.Respond(context, false);
- }
-
- /// <summary>
- /// Submits this response to a WCF response context. Only available when no response body is included.
- /// </summary>
- /// <param name="responseContext">The response context to apply the response to.</param>
- public virtual void Respond(OutgoingWebResponseContext responseContext) {
- Requires.NotNull(responseContext, "responseContext");
- if (this.ResponseStream != null) {
- throw new NotSupportedException(Strings.ResponseBodyNotSupported);
- }
-
- responseContext.StatusCode = this.Status;
- responseContext.SuppressEntityBody = true;
- foreach (string header in this.Headers) {
- responseContext.Headers[header] = this.Headers[header];
- }
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent.
- /// </summary>
- /// <param name="response">The response to set to this message.</param>
- public virtual void Send(HttpListenerResponse response) {
- Requires.NotNull(response, "response");
-
- response.StatusCode = (int)this.Status;
- MessagingUtilities.ApplyHeadersToResponse(this.Headers, response);
- foreach (HttpCookie httpCookie in this.Cookies) {
- var cookie = new Cookie(httpCookie.Name, httpCookie.Value) {
- Expires = httpCookie.Expires,
- Path = httpCookie.Path,
- HttpOnly = httpCookie.HttpOnly,
- Secure = httpCookie.Secure,
- Domain = httpCookie.Domain,
- };
- response.AppendCookie(cookie);
- }
-
- if (this.ResponseStream != null) {
- response.ContentLength64 = this.ResponseStream.Length;
- this.ResponseStream.CopyTo(response.OutputStream);
- }
-
- response.OutputStream.Close();
- }
-
- /// <summary>
- /// Gets the URI that, when requested with an HTTP GET request,
- /// would transmit the message that normally would be transmitted via a user agent redirect.
- /// </summary>
- /// <param name="channel">The channel to use for encoding.</param>
- /// <returns>
- /// The URL that would transmit the original message. This URL may exceed the normal 2K limit,
- /// and should therefore be broken up manually and POSTed as form fields when it exceeds this length.
- /// </returns>
- /// <remarks>
- /// This is useful for desktop applications that will spawn a user agent to transmit the message
- /// rather than cause a redirect.
- /// </remarks>
- internal Uri GetDirectUriRequest(Channel channel) {
- Requires.NotNull(channel, "channel");
-
- var message = this.OriginalMessage as IDirectedProtocolMessage;
- if (message == null) {
- throw new InvalidOperationException(); // this only makes sense for directed messages (indirect responses)
- }
-
- var fields = channel.MessageDescriptions.GetAccessor(message).Serialize();
- UriBuilder builder = new UriBuilder(message.Recipient);
- MessagingUtilities.AppendQueryArgs(builder, fields);
- return builder.Uri;
- }
-
- /// <summary>
- /// Sets the response to some string, encoded as UTF-8.
- /// </summary>
- /// <param name="body">The string to set the response to.</param>
- /// <param name="contentType">Type of the content. May be null.</param>
- internal void SetResponse(string body, ContentType contentType) {
- if (body == null) {
- this.ResponseStream = null;
- return;
- }
-
- if (contentType == null) {
- contentType = new ContentType("text/html");
- contentType.CharSet = bodyStringEncoder.WebName;
- } else if (contentType.CharSet != bodyStringEncoder.WebName) {
- // clone the original so we're not tampering with our inputs if it came as a parameter.
- contentType = new ContentType(contentType.ToString());
- contentType.CharSet = bodyStringEncoder.WebName;
- }
-
- this.Headers[HttpResponseHeader.ContentType] = contentType.ToString();
- this.ResponseStream = new MemoryStream();
- StreamWriter writer = new StreamWriter(this.ResponseStream, bodyStringEncoder);
- writer.Write(body);
- writer.Flush();
- this.ResponseStream.Seek(0, SeekOrigin.Begin);
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and signals ASP.NET to short-circuit the page execution pipeline
- /// now that the response has been completed.
- /// </summary>
- /// <param name="context">The context of the HTTP request whose response should be set.
- /// Typically this is <see cref="HttpContext.Current"/>.</param>
- /// <param name="endRequest">If set to <c>false</c>, this method calls
- /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/>
- /// to avoid a <see cref="ThreadAbortException"/>.</param>
- protected internal void Respond(HttpContext context, bool endRequest) {
- this.Respond(new HttpContextWrapper(context), endRequest);
- }
-
- /// <summary>
- /// Automatically sends the appropriate response to the user agent
- /// and signals ASP.NET to short-circuit the page execution pipeline
- /// now that the response has been completed.
- /// </summary>
- /// <param name="context">The context of the HTTP request whose response should be set.
- /// Typically this is <see cref="HttpContext.Current"/>.</param>
- /// <param name="endRequest">If set to <c>false</c>, this method calls
- /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/>
- /// to avoid a <see cref="ThreadAbortException"/>.</param>
- protected internal virtual void Respond(HttpContextBase context, bool endRequest) {
- Requires.NotNull(context, "context");
-
- context.Response.Clear();
- context.Response.StatusCode = (int)this.Status;
- MessagingUtilities.ApplyHeadersToResponse(this.Headers, context.Response);
- if (this.ResponseStream != null) {
- try {
- this.ResponseStream.CopyTo(context.Response.OutputStream);
- } catch (HttpException ex) {
- if (ex.ErrorCode == -2147467259 && context.Response.Output != null) {
- // Test scenarios can generate this, since the stream is being spoofed:
- // System.Web.HttpException: OutputStream is not available when a custom TextWriter is used.
- context.Response.Output.Write(this.Body);
- } else {
- throw;
- }
- }
- }
-
- foreach (string cookieName in this.Cookies) {
- var cookie = this.Cookies[cookieName];
- context.Response.AppendCookie(cookie);
- }
-
- if (endRequest) {
- // This approach throws an exception in order that
- // no more code is executed in the calling page.
- // Microsoft no longer recommends this approach.
- context.Response.End();
- } else if (context.ApplicationInstance != null) {
- // This approach doesn't throw an exception, but
- // still tells ASP.NET to short-circuit most of the
- // request handling pipeline to speed things up.
- context.ApplicationInstance.CompleteRequest();
- }
- }
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
deleted file mode 100644
index bc2f985..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="OutgoingWebResponseActionResult.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Web.Mvc;
- using DotNetOpenAuth.Messaging;
- using Validation;
-
- /// <summary>
- /// An ASP.NET MVC structure to represent the response to send
- /// to the user agent when the controller has finished its work.
- /// </summary>
- internal class OutgoingWebResponseActionResult : ActionResult {
- /// <summary>
- /// The outgoing web response to send when the ActionResult is executed.
- /// </summary>
- private readonly OutgoingWebResponse response;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="OutgoingWebResponseActionResult"/> class.
- /// </summary>
- /// <param name="response">The response.</param>
- internal OutgoingWebResponseActionResult(OutgoingWebResponse response) {
- Requires.NotNull(response, "response");
- this.response = response;
- }
-
- /// <summary>
- /// Enables processing of the result of an action method by a custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>.
- /// </summary>
- /// <param name="context">The context in which to set the response.</param>
- public override void ExecuteResult(ControllerContext context) {
- this.response.Respond(context.HttpContext);
-
- // MVC likes to muck with our response. For example, when returning contrived 401 Unauthorized responses
- // MVC will rewrite our response and turn it into a redirect, which breaks OAuth 2 authorization server token endpoints.
- // It turns out we can prevent this unwanted behavior by flushing the response before returning from this method.
- context.HttpContext.Response.Flush();
- }
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs b/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs
index 4d5c418..3c8839e 100644
--- a/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs
@@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Messaging {
using System;
using System.Collections.Generic;
using System.Linq;
+ using System.Net.Http;
using System.Text;
using Validation;
@@ -62,7 +63,7 @@ namespace DotNetOpenAuth.Messaging {
/// Creates the HTTP response to forward to the client to report the error.
/// </summary>
/// <returns>The HTTP response.</returns>
- public OutgoingWebResponse CreateErrorResponse() {
+ public HttpResponseMessage CreateErrorResponse() {
var response = this.channel.PrepareResponse(this.ErrorResponseMessage);
return response;
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs
deleted file mode 100644
index 2383a5b..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs
+++ /dev/null
@@ -1,261 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="StandardWebRequestHandler.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Diagnostics;
- using System.Diagnostics.Contracts;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using System.Reflection;
- using DotNetOpenAuth.Messaging;
- using Validation;
-
- /// <summary>
- /// The default handler for transmitting <see cref="HttpWebRequest"/> instances
- /// and returning the responses.
- /// </summary>
- public class StandardWebRequestHandler : IDirectWebRequestHandler {
- /// <summary>
- /// The set of options this web request handler supports.
- /// </summary>
- private const DirectWebRequestOptions SupportedOptions = DirectWebRequestOptions.AcceptAllHttpResponses;
-
- /// <summary>
- /// The value to use for the User-Agent HTTP header.
- /// </summary>
- private static string userAgentValue = Assembly.GetExecutingAssembly().GetName().Name + "/" + Util.AssemblyFileVersion;
-
- #region IWebRequestHandler Members
-
- /// <summary>
- /// Determines whether this instance can support the specified options.
- /// </summary>
- /// <param name="options">The set of options that might be given in a subsequent web request.</param>
- /// <returns>
- /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>.
- /// </returns>
- [Pure]
- public bool CanSupport(DirectWebRequestOptions options) {
- return (options & ~SupportedOptions) == 0;
- }
-
- /// <summary>
- /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
- /// <returns>
- /// The writer the caller should write out the entity data to.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
- /// and any other appropriate properties <i>before</i> calling this method.</para>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch.</para>
- /// </remarks>
- public Stream GetRequestStream(HttpWebRequest request) {
- return this.GetRequestStream(request, DirectWebRequestOptions.None);
- }
-
- /// <summary>
- /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
- /// <param name="options">The options to apply to this web request.</param>
- /// <returns>
- /// The writer the caller should write out the entity data to.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
- /// and any other appropriate properties <i>before</i> calling this method.</para>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch.</para>
- /// </remarks>
- public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) {
- return GetRequestStreamCore(request);
- }
-
- /// <summary>
- /// Processes an <see cref="HttpWebRequest"/> and converts the
- /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
- /// <returns>
- /// An instance of <see cref="IncomingWebResponse"/> describing the response.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
- /// value, if set, should be Closed before throwing.</para>
- /// </remarks>
- public IncomingWebResponse GetResponse(HttpWebRequest request) {
- return this.GetResponse(request, DirectWebRequestOptions.None);
- }
-
- /// <summary>
- /// Processes an <see cref="HttpWebRequest"/> and converts the
- /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
- /// <param name="options">The options to apply to this web request.</param>
- /// <returns>
- /// An instance of <see cref="IncomingWebResponse"/> describing the response.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
- /// value, if set, should be Closed before throwing.</para>
- /// </remarks>
- public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) {
- // This request MAY have already been prepared by GetRequestStream, but
- // we have no guarantee, so do it just to be safe.
- PrepareRequest(request, false);
-
- try {
- Logger.Http.DebugFormat("HTTP {0} {1}", request.Method, request.RequestUri);
- HttpWebResponse response = (HttpWebResponse)request.GetResponse();
- return new NetworkDirectWebResponse(request.RequestUri, response);
- } catch (WebException ex) {
- HttpWebResponse response = (HttpWebResponse)ex.Response;
- if (response != null && response.StatusCode == HttpStatusCode.ExpectationFailed &&
- request.ServicePoint.Expect100Continue) {
- // Some OpenID servers doesn't understand the Expect header and send 417 error back.
- // If this server just failed from that, alter the ServicePoint for this server
- // so that we don't send that header again next time (whenever that is).
- // "Expect: 100-Continue" HTTP header. (see Google Code Issue 72)
- // We don't want to blindly set all ServicePoints to not use the Expect header
- // as that would be a security hole allowing any visitor to a web site change
- // the web site's global behavior when calling that host.
- Logger.Http.InfoFormat("HTTP POST to {0} resulted in 417 Expectation Failed. Changing ServicePoint to not use Expect: Continue next time.", request.RequestUri);
- request.ServicePoint.Expect100Continue = false; // TODO: investigate that CAS may throw here
-
- // An alternative to ServicePoint if we don't have permission to set that,
- // but we'd have to set it BEFORE each request.
- ////request.Expect = "";
- }
-
- if ((options & DirectWebRequestOptions.AcceptAllHttpResponses) != 0 && response != null &&
- response.StatusCode != HttpStatusCode.ExpectationFailed) {
- Logger.Http.InfoFormat("The HTTP error code {0} {1} is being accepted because the {2} flag is set.", (int)response.StatusCode, response.StatusCode, DirectWebRequestOptions.AcceptAllHttpResponses);
- return new NetworkDirectWebResponse(request.RequestUri, response);
- }
-
- if (response != null) {
- Logger.Http.ErrorFormat(
- "{0} returned {1} {2}: {3}",
- response.ResponseUri,
- (int)response.StatusCode,
- response.StatusCode,
- response.StatusDescription);
-
- if (Logger.Http.IsDebugEnabled) {
- using (var reader = new StreamReader(ex.Response.GetResponseStream())) {
- Logger.Http.DebugFormat(
- "WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd());
- }
- }
- } else {
- Logger.Http.ErrorFormat(
- "{0} connecting to {0}",
- ex.Status,
- request.RequestUri);
- }
-
- // Be sure to close the response stream to conserve resources and avoid
- // filling up all our incoming pipes and denying future requests.
- // If in the future, some callers actually want to read this response
- // we'll need to figure out how to reliably call Close on exception
- // responses at all callers.
- if (response != null) {
- response.Close();
- }
-
- throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorInRequestReplyMessage);
- }
- }
-
- #endregion
-
- /// <summary>
- /// Determines whether an exception was thrown because of the remote HTTP server returning HTTP 417 Expectation Failed.
- /// </summary>
- /// <param name="ex">The caught exception.</param>
- /// <returns>
- /// <c>true</c> if the failure was originally caused by a 417 Exceptation Failed error; otherwise, <c>false</c>.
- /// </returns>
- internal static bool IsExceptionFrom417ExpectationFailed(Exception ex) {
- while (ex != null) {
- WebException webEx = ex as WebException;
- if (webEx != null) {
- HttpWebResponse response = webEx.Response as HttpWebResponse;
- if (response != null) {
- if (response.StatusCode == HttpStatusCode.ExpectationFailed) {
- return true;
- }
- }
- }
-
- ex = ex.InnerException;
- }
-
- return false;
- }
-
- /// <summary>
- /// Initiates a POST request and prepares for sending data.
- /// </summary>
- /// <param name="request">The HTTP request with information about the remote party to contact.</param>
- /// <returns>
- /// The stream where the POST entity can be written.
- /// </returns>
- private static Stream GetRequestStreamCore(HttpWebRequest request) {
- PrepareRequest(request, true);
-
- try {
- return request.GetRequestStream();
- } catch (SocketException ex) {
- throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri);
- } catch (WebException ex) {
- throw ErrorUtilities.Wrap(ex, MessagingStrings.WebRequestFailed, request.RequestUri);
- }
- }
-
- /// <summary>
- /// Prepares an HTTP request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param>
- private static void PrepareRequest(HttpWebRequest request, bool preparingPost) {
- Requires.NotNull(request, "request");
-
- // Be careful to not try to change the HTTP headers that have already gone out.
- if (preparingPost || request.Method == "GET") {
- // Set/override a few properties of the request to apply our policies for requests.
- if (Debugger.IsAttached) {
- // Since a debugger is attached, requests may be MUCH slower,
- // so give ourselves huge timeouts.
- request.ReadWriteTimeout = (int)TimeSpan.FromHours(1).TotalMilliseconds;
- request.Timeout = (int)TimeSpan.FromHours(1).TotalMilliseconds;
- }
-
- // Some sites, such as Technorati, return 403 Forbidden on identity
- // pages unless a User-Agent header is included.
- if (string.IsNullOrEmpty(request.UserAgent)) {
- request.UserAgent = userAgentValue;
- }
- }
- }
- }
-}
diff --git a/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs
deleted file mode 100644
index be0182d..0000000
--- a/src/DotNetOpenAuth.Core/Messaging/UntrustedWebRequestHandler.cs
+++ /dev/null
@@ -1,477 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="UntrustedWebRequestHandler.cs" company="Outercurve Foundation">
-// Copyright (c) Outercurve Foundation. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.Messaging {
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.Diagnostics.Contracts;
- using System.Globalization;
- using System.IO;
- using System.Net;
- using System.Net.Cache;
- using System.Text.RegularExpressions;
- using DotNetOpenAuth.Configuration;
- using DotNetOpenAuth.Messaging;
- using Validation;
-
- /// <summary>
- /// A paranoid HTTP get/post request engine. It helps to protect against attacks from remote
- /// server leaving dangling connections, sending too much data, causing requests against
- /// internal servers, etc.
- /// </summary>
- /// <remarks>
- /// Protections include:
- /// * Conservative maximum time to receive the complete response.
- /// * Only HTTP and HTTPS schemes are permitted.
- /// * Internal IP address ranges are not permitted: 127.*.*.*, 1::*
- /// * Internal host names are not permitted (periods must be found in the host name)
- /// If a particular host would be permitted but is in the blacklist, it is not allowed.
- /// If a particular host would not be permitted but is in the whitelist, it is allowed.
- /// </remarks>
- public class UntrustedWebRequestHandler : IDirectWebRequestHandler {
- /// <summary>
- /// The set of URI schemes allowed in untrusted web requests.
- /// </summary>
- private ICollection<string> allowableSchemes = new List<string> { "http", "https" };
-
- /// <summary>
- /// The collection of blacklisted hosts.
- /// </summary>
- private ICollection<string> blacklistHosts = new List<string>(Configuration.BlacklistHosts.KeysAsStrings);
-
- /// <summary>
- /// The collection of regular expressions used to identify additional blacklisted hosts.
- /// </summary>
- private ICollection<Regex> blacklistHostsRegex = new List<Regex>(Configuration.BlacklistHostsRegex.KeysAsRegexs);
-
- /// <summary>
- /// The collection of whitelisted hosts.
- /// </summary>
- private ICollection<string> whitelistHosts = new List<string>(Configuration.WhitelistHosts.KeysAsStrings);
-
- /// <summary>
- /// The collection of regular expressions used to identify additional whitelisted hosts.
- /// </summary>
- private ICollection<Regex> whitelistHostsRegex = new List<Regex>(Configuration.WhitelistHostsRegex.KeysAsRegexs);
-
- /// <summary>
- /// The maximum redirections to follow in the course of a single request.
- /// </summary>
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private int maximumRedirections = Configuration.MaximumRedirections;
-
- /// <summary>
- /// The maximum number of bytes to read from the response of an untrusted server.
- /// </summary>
- [DebuggerBrowsable(DebuggerBrowsableState.Never)]
- private int maximumBytesToRead = Configuration.MaximumBytesToRead;
-
- /// <summary>
- /// The handler that will actually send the HTTP request and collect
- /// the response once the untrusted server gates have been satisfied.
- /// </summary>
- private IDirectWebRequestHandler chainedWebRequestHandler;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class.
- /// </summary>
- public UntrustedWebRequestHandler()
- : this(new StandardWebRequestHandler()) {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class.
- /// </summary>
- /// <param name="chainedWebRequestHandler">The chained web request handler.</param>
- public UntrustedWebRequestHandler(IDirectWebRequestHandler chainedWebRequestHandler) {
- Requires.NotNull(chainedWebRequestHandler, "chainedWebRequestHandler");
-
- this.chainedWebRequestHandler = chainedWebRequestHandler;
- if (Debugger.IsAttached) {
- // Since a debugger is attached, requests may be MUCH slower,
- // so give ourselves huge timeouts.
- this.ReadWriteTimeout = TimeSpan.FromHours(1);
- this.Timeout = TimeSpan.FromHours(1);
- } else {
- this.ReadWriteTimeout = Configuration.ReadWriteTimeout;
- this.Timeout = Configuration.Timeout;
- }
- }
-
- /// <summary>
- /// Gets or sets the default maximum bytes to read in any given HTTP request.
- /// </summary>
- /// <value>Default is 1MB. Cannot be less than 2KB.</value>
- public int MaximumBytesToRead {
- get {
- return this.maximumBytesToRead;
- }
-
- set {
- Requires.Range(value >= 2048, "value");
- this.maximumBytesToRead = value;
- }
- }
-
- /// <summary>
- /// Gets or sets the total number of redirections to allow on any one request.
- /// Default is 10.
- /// </summary>
- public int MaximumRedirections {
- get {
- return this.maximumRedirections;
- }
-
- set {
- Requires.Range(value >= 0, "value");
- this.maximumRedirections = value;
- }
- }
-
- /// <summary>
- /// Gets or sets the time allowed to wait for single read or write operation to complete.
- /// Default is 500 milliseconds.
- /// </summary>
- public TimeSpan ReadWriteTimeout { get; set; }
-
- /// <summary>
- /// Gets or sets the time allowed for an entire HTTP request.
- /// Default is 5 seconds.
- /// </summary>
- public TimeSpan Timeout { get; set; }
-
- /// <summary>
- /// Gets a collection of host name literals that should be allowed even if they don't
- /// pass standard security checks.
- /// </summary>
- [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")]
- public ICollection<string> WhitelistHosts { get { return this.whitelistHosts; } }
-
- /// <summary>
- /// Gets a collection of host name regular expressions that indicate hosts that should
- /// be allowed even though they don't pass standard security checks.
- /// </summary>
- [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Whitelist", Justification = "Spelling as intended.")]
- public ICollection<Regex> WhitelistHostsRegex { get { return this.whitelistHostsRegex; } }
-
- /// <summary>
- /// Gets a collection of host name literals that should be rejected even if they
- /// pass standard security checks.
- /// </summary>
- public ICollection<string> BlacklistHosts { get { return this.blacklistHosts; } }
-
- /// <summary>
- /// Gets a collection of host name regular expressions that indicate hosts that should
- /// be rejected even if they pass standard security checks.
- /// </summary>
- public ICollection<Regex> BlacklistHostsRegex { get { return this.blacklistHostsRegex; } }
-
- /// <summary>
- /// Gets the configuration for this class that is specified in the host's .config file.
- /// </summary>
- private static UntrustedWebRequestElement Configuration {
- get { return DotNetOpenAuthSection.Messaging.UntrustedWebRequest; }
- }
-
- #region IDirectWebRequestHandler Members
-
- /// <summary>
- /// Determines whether this instance can support the specified options.
- /// </summary>
- /// <param name="options">The set of options that might be given in a subsequent web request.</param>
- /// <returns>
- /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>.
- /// </returns>
- [Pure]
- public bool CanSupport(DirectWebRequestOptions options) {
- // We support whatever our chained handler supports, plus RequireSsl.
- return this.chainedWebRequestHandler.CanSupport(options & ~DirectWebRequestOptions.RequireSsl);
- }
-
- /// <summary>
- /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
- /// <param name="options">The options to apply to this web request.</param>
- /// <returns>
- /// The writer the caller should write out the entity data to.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/>
- /// and any other appropriate properties <i>before</i> calling this method.</para>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch.</para>
- /// </remarks>
- public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) {
- this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0);
-
- this.PrepareRequest(request, true);
-
- // Submit the request and get the request stream back.
- return this.chainedWebRequestHandler.GetRequestStream(request, options & ~DirectWebRequestOptions.RequireSsl);
- }
-
- /// <summary>
- /// Processes an <see cref="HttpWebRequest"/> and converts the
- /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
- /// <param name="options">The options to apply to this web request.</param>
- /// <returns>
- /// An instance of <see cref="CachedDirectWebResponse"/> describing the response.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
- /// value, if set, should be Closed before throwing.</para>
- /// </remarks>
- [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")]
- public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) {
- // This request MAY have already been prepared by GetRequestStream, but
- // we have no guarantee, so do it just to be safe.
- this.PrepareRequest(request, false);
-
- // Since we may require SSL for every redirect, we handle each redirect manually
- // in order to detect and fail if any redirect sends us to an HTTP url.
- // We COULD allow automatic redirect in the cases where HTTPS is not required,
- // but our mock request infrastructure can't do redirects on its own either.
- Uri originalRequestUri = request.RequestUri;
- int i;
- for (i = 0; i < this.MaximumRedirections; i++) {
- this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0);
- CachedDirectWebResponse response = this.chainedWebRequestHandler.GetResponse(request, options & ~DirectWebRequestOptions.RequireSsl).GetSnapshot(this.MaximumBytesToRead);
- if (response.Status == HttpStatusCode.MovedPermanently ||
- response.Status == HttpStatusCode.Redirect ||
- response.Status == HttpStatusCode.RedirectMethod ||
- response.Status == HttpStatusCode.RedirectKeepVerb) {
- // We have no copy of the post entity stream to repeat on our manually
- // cloned HttpWebRequest, so we have to bail.
- ErrorUtilities.VerifyProtocol(request.Method != "POST", MessagingStrings.UntrustedRedirectsOnPOSTNotSupported);
- Uri redirectUri = new Uri(response.FinalUri, response.Headers[HttpResponseHeader.Location]);
- request = request.Clone(redirectUri);
- } else {
- if (response.FinalUri != request.RequestUri) {
- // Since we don't automatically follow redirects, there's only one scenario where this
- // can happen: when the server sends a (non-redirecting) Content-Location header in the response.
- // It's imperative that we do not trust that header though, so coerce the FinalUri to be
- // what we just requested.
- Logger.Http.WarnFormat("The response from {0} included an HTTP header indicating it's the same as {1}, but it's not a redirect so we won't trust that.", request.RequestUri, response.FinalUri);
- response.FinalUri = request.RequestUri;
- }
-
- return response;
- }
- }
-
- throw ErrorUtilities.ThrowProtocol(MessagingStrings.TooManyRedirects, originalRequestUri);
- }
-
- /// <summary>
- /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
- /// <returns>
- /// The writer the caller should write out the entity data to.
- /// </returns>
- Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) {
- return this.GetRequestStream(request, DirectWebRequestOptions.None);
- }
-
- /// <summary>
- /// Processes an <see cref="HttpWebRequest"/> and converts the
- /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance.
- /// </summary>
- /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
- /// <returns>
- /// An instance of <see cref="IncomingWebResponse"/> describing the response.
- /// </returns>
- /// <exception cref="ProtocolException">Thrown for any network error.</exception>
- /// <remarks>
- /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a
- /// <see cref="ProtocolException"/> to abstract away the transport and provide
- /// a single exception type for hosts to catch. The <see cref="WebException.Response"/>
- /// value, if set, should be Closed before throwing.</para>
- /// </remarks>
- IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) {
- return this.GetResponse(request, DirectWebRequestOptions.None);
- }
-
- #endregion
-
- /// <summary>
- /// Determines whether an IP address is the IPv6 equivalent of "localhost/127.0.0.1".
- /// </summary>
- /// <param name="ip">The ip address to check.</param>
- /// <returns>
- /// <c>true</c> if this is a loopback IP address; <c>false</c> otherwise.
- /// </returns>
- private static bool IsIPv6Loopback(IPAddress ip) {
- Requires.NotNull(ip, "ip");
- byte[] addressBytes = ip.GetAddressBytes();
- for (int i = 0; i < addressBytes.Length - 1; i++) {
- if (addressBytes[i] != 0) {
- return false;
- }
- }
- if (addressBytes[addressBytes.Length - 1] != 1) {
- return false;
- }
- return true;
- }
-
- /// <summary>
- /// Determines whether the given host name is in a host list or host name regex list.
- /// </summary>
- /// <param name="host">The host name.</param>
- /// <param name="stringList">The list of host names.</param>
- /// <param name="regexList">The list of regex patterns of host names.</param>
- /// <returns>
- /// <c>true</c> if the specified host falls within at least one of the given lists; otherwise, <c>false</c>.
- /// </returns>
- private static bool IsHostInList(string host, ICollection<string> stringList, ICollection<Regex> regexList) {
- Requires.NotNullOrEmpty(host, "host");
- Requires.NotNull(stringList, "stringList");
- Requires.NotNull(regexList, "regexList");
- foreach (string testHost in stringList) {
- if (string.Equals(host, testHost, StringComparison.OrdinalIgnoreCase)) {
- return true;
- }
- }
- foreach (Regex regex in regexList) {
- if (regex.IsMatch(host)) {
- return true;
- }
- }
- return false;
- }
-
- /// <summary>
- /// Determines whether a given host is whitelisted.
- /// </summary>
- /// <param name="host">The host name to test.</param>
- /// <returns>
- /// <c>true</c> if the host is whitelisted; otherwise, <c>false</c>.
- /// </returns>
- private bool IsHostWhitelisted(string host) {
- return IsHostInList(host, this.WhitelistHosts, this.WhitelistHostsRegex);
- }
-
- /// <summary>
- /// Determines whether a given host is blacklisted.
- /// </summary>
- /// <param name="host">The host name to test.</param>
- /// <returns>
- /// <c>true</c> if the host is blacklisted; otherwise, <c>false</c>.
- /// </returns>
- private bool IsHostBlacklisted(string host) {
- return IsHostInList(host, this.BlacklistHosts, this.BlacklistHostsRegex);
- }
-
- /// <summary>
- /// Verify that the request qualifies under our security policies
- /// </summary>
- /// <param name="requestUri">The request URI.</param>
- /// <param name="requireSsl">If set to <c>true</c>, only web requests that can be made entirely over SSL will succeed.</param>
- /// <exception cref="ProtocolException">Thrown when the URI is disallowed for security reasons.</exception>
- private void EnsureAllowableRequestUri(Uri requestUri, bool requireSsl) {
- ErrorUtilities.VerifyProtocol(this.IsUriAllowable(requestUri), MessagingStrings.UnsafeWebRequestDetected, requestUri);
- ErrorUtilities.VerifyProtocol(!requireSsl || string.Equals(requestUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), MessagingStrings.InsecureWebRequestWithSslRequired, requestUri);
- }
-
- /// <summary>
- /// Determines whether a URI is allowed based on scheme and host name.
- /// No requireSSL check is done here
- /// </summary>
- /// <param name="uri">The URI to test for whether it should be allowed.</param>
- /// <returns>
- /// <c>true</c> if [is URI allowable] [the specified URI]; otherwise, <c>false</c>.
- /// </returns>
- private bool IsUriAllowable(Uri uri) {
- Requires.NotNull(uri, "uri");
- if (!this.allowableSchemes.Contains(uri.Scheme)) {
- Logger.Http.WarnFormat("Rejecting URL {0} because it uses a disallowed scheme.", uri);
- return false;
- }
-
- // Allow for whitelist or blacklist to override our detection.
- Func<string, bool> failsUnlessWhitelisted = (string reason) => {
- if (IsHostWhitelisted(uri.DnsSafeHost)) {
- return true;
- }
- Logger.Http.WarnFormat("Rejecting URL {0} because {1}.", uri, reason);
- return false;
- };
-
- // Try to interpret the hostname as an IP address so we can test for internal
- // IP address ranges. Note that IP addresses can appear in many forms
- // (e.g. http://127.0.0.1, http://2130706433, http://0x0100007f, http://::1
- // So we convert them to a canonical IPAddress instance, and test for all
- // non-routable IP ranges: 10.*.*.*, 127.*.*.*, ::1
- // Note that Uri.IsLoopback is very unreliable, not catching many of these variants.
- IPAddress hostIPAddress;
- if (IPAddress.TryParse(uri.DnsSafeHost, out hostIPAddress)) {
- byte[] addressBytes = hostIPAddress.GetAddressBytes();
-
- // The host is actually an IP address.
- switch (hostIPAddress.AddressFamily) {
- case System.Net.Sockets.AddressFamily.InterNetwork:
- if (addressBytes[0] == 127 || addressBytes[0] == 10) {
- return failsUnlessWhitelisted("it is a loopback address.");
- }
- break;
- case System.Net.Sockets.AddressFamily.InterNetworkV6:
- if (IsIPv6Loopback(hostIPAddress)) {
- return failsUnlessWhitelisted("it is a loopback address.");
- }
- break;
- default:
- return failsUnlessWhitelisted("it does not use an IPv4 or IPv6 address.");
- }
- } else {
- // The host is given by name. We require names to contain periods to
- // help make sure it's not an internal address.
- if (!uri.Host.Contains(".")) {
- return failsUnlessWhitelisted("it does not contain a period in the host name.");
- }
- }
- if (this.IsHostBlacklisted(uri.DnsSafeHost)) {
- Logger.Http.WarnFormat("Rejected URL {0} because it is blacklisted.", uri);
- return false;
- }
- return true;
- }
-
- /// <summary>
- /// Prepares the request by setting timeout and redirect policies.
- /// </summary>
- /// <param name="request">The request to prepare.</param>
- /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param>
- private void PrepareRequest(HttpWebRequest request, bool preparingPost) {
- Requires.NotNull(request, "request");
-
- // Be careful to not try to change the HTTP headers that have already gone out.
- if (preparingPost || request.Method == "GET") {
- // Set/override a few properties of the request to apply our policies for untrusted requests.
- request.ReadWriteTimeout = (int)this.ReadWriteTimeout.TotalMilliseconds;
- request.Timeout = (int)this.Timeout.TotalMilliseconds;
- request.KeepAlive = false;
- }
-
- // If SSL is required throughout, we cannot allow auto redirects because
- // it may include a pass through an unprotected HTTP request.
- // We have to follow redirects manually.
- // It also allows us to ignore HttpWebResponse.FinalUri since that can be affected by
- // the Content-Location header and open security holes.
- request.AllowAutoRedirect = false;
- }
- }
-}