summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs3
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs6
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs4
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs4
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj2
-rw-r--r--src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs158
-rw-r--r--src/DotNetOpenAuth/Messaging/Channel.cs10
-rw-r--r--src/DotNetOpenAuth/Messaging/DirectWebResponse.cs133
-rw-r--r--src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs108
-rw-r--r--src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs5
-rw-r--r--src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs5
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs3
-rw-r--r--src/DotNetOpenAuth/Yadis/DiscoveryResult.cs2
-rw-r--r--src/DotNetOpenAuth/Yadis/Yadis.cs18
14 files changed, 325 insertions, 136 deletions
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs
index f08b9ba..81589c7 100644
--- a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs
+++ b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs
@@ -129,7 +129,8 @@ namespace DotNetOpenAuth.ApplicationBlock {
}
var response = consumer.PrepareAuthorizedRequestAndSend(GetContactsEndpoint, accessToken);
- XDocument result = XDocument.Parse(response.Body);
+ string body = response.GetResponseReader().ReadToEnd();
+ XDocument result = XDocument.Parse(body);
return result;
}
diff --git a/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs
index 6c853af..78e66ca 100644
--- a/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs
@@ -72,7 +72,7 @@ namespace DotNetOpenAuth.Test.Mocks {
sw.Write(responseBody);
sw.Flush();
stream.Seek(0, SeekOrigin.Begin);
- this.RegisterMockResponse(new DirectWebResponse(responseUri, responseUri, headers ?? new WebHeaderCollection(), HttpStatusCode.OK, contentType, contentEncoding, stream));
+ this.RegisterMockResponse(new CachedDirectWebResponse(responseUri, responseUri, headers ?? new WebHeaderCollection(), HttpStatusCode.OK, contentType, contentEncoding, stream));
}
internal void RegisterMockXrdsResponses(IDictionary<string, string> requestUriAndResponseBody) {
@@ -169,7 +169,7 @@ namespace DotNetOpenAuth.Test.Mocks {
var redirectionHeaders = new WebHeaderCollection {
{ HttpResponseHeader.Location, redirectLocation.AbsoluteUri },
};
- DirectWebResponse response = new DirectWebResponse(origin, origin, redirectionHeaders, HttpStatusCode.Redirect, null, null, new MemoryStream());
+ DirectWebResponse response = new CachedDirectWebResponse(origin, origin, redirectionHeaders, HttpStatusCode.Redirect, null, null, new MemoryStream());
this.RegisterMockResponse(response);
}
@@ -182,7 +182,7 @@ namespace DotNetOpenAuth.Test.Mocks {
} else {
////Assert.Fail("Unexpected HTTP request: {0}", uri);
Logger.WarnFormat("Unexpected HTTP request: {0}", request.RequestUri);
- return new DirectWebResponse(request.RequestUri, request.RequestUri, new WebHeaderCollection(), HttpStatusCode.NotFound, "text/html", null, new MemoryStream());
+ return new CachedDirectWebResponse(request.RequestUri, request.RequestUri, new WebHeaderCollection(), HttpStatusCode.NotFound, "text/html", null, new MemoryStream());
}
}
}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs
index 07db3f6..10e9795 100644
--- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs
@@ -111,7 +111,7 @@ namespace DotNetOpenAuth.Test.ChannelElements {
writer.Flush();
ms.Seek(0, SeekOrigin.Begin);
Channel_Accessor channelAccessor = Channel_Accessor.AttachShadow(this.channel);
- IDictionary<string, string> deserializedFields = channelAccessor.ReadFromResponseInternal(new DirectWebResponse { ResponseStream = ms });
+ IDictionary<string, string> deserializedFields = channelAccessor.ReadFromResponseInternal(new CachedDirectWebResponse { CachedResponseStream = ms });
Assert.AreEqual(fields.Count, deserializedFields.Count);
foreach (string key in fields.Keys) {
Assert.AreEqual(fields[key], deserializedFields[key]);
@@ -246,7 +246,7 @@ namespace DotNetOpenAuth.Test.ChannelElements {
{ "Location", request.Location.AbsoluteUri },
{ "Timestamp", XmlConvert.ToString(request.Timestamp, XmlDateTimeSerializationMode.Utc) },
};
- rawResponse = new DirectWebResponse {
+ rawResponse = new CachedDirectWebResponse {
Body = MessagingUtilities.CreateQueryString(responseFields),
};
return rawResponse;
diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs
index 60cbe3e..ff601ea 100644
--- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs
@@ -93,8 +93,8 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements {
{ "var1", "value1" },
{ "var2", "value2" },
};
- var response = new DirectWebResponse {
- ResponseStream = new MemoryStream(KeyValueFormEncoding.GetBytes(fields)),
+ var response = new CachedDirectWebResponse {
+ CachedResponseStream = new MemoryStream(KeyValueFormEncoding.GetBytes(fields)),
};
Assert.IsTrue(MessagingUtilities.AreEquivalent(fields, this.accessor.ReadFromResponseInternal(response)));
}
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index a5b17bc..32f0834 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -72,6 +72,7 @@
<Compile Include="Configuration\UntrustedWebRequestSection.cs" />
<Compile Include="Configuration\HostNameOrRegexCollection.cs" />
<Compile Include="Configuration\HostNameElement.cs" />
+ <Compile Include="Messaging\CachedDirectWebResponse.cs" />
<Compile Include="Messaging\IExtensionMessage.cs" />
<Compile Include="Messaging\IMessage.cs" />
<Compile Include="Messaging\DirectWebResponse.cs" />
@@ -83,6 +84,7 @@
<Compile Include="Messaging\IDirectSslWebRequestHandler.cs" />
<Compile Include="Messaging\IProtocolMessageWithExtensions.cs" />
<Compile Include="Messaging\InternalErrorException.cs" />
+ <Compile Include="Messaging\NetworkDirectWebResponse.cs" />
<Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" />
<Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" />
<Compile Include="OAuth\ChannelElements\ITokenGenerator.cs" />
diff --git a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
new file mode 100644
index 0000000..0a70a70
--- /dev/null
+++ b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
@@ -0,0 +1,158 @@
+//-----------------------------------------------------------------------
+// <copyright file="CachedDirectWebResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+
+ /// <summary>
+ /// Cached details on the response from a direct web request to a remote party.
+ /// </summary>
+ [DebuggerDisplay("{Status} {ContentType.MediaType}: {Body.Substring(4,50)}")]
+ internal class CachedDirectWebResponse : DirectWebResponse {
+ /// <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) {
+ 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) {
+ ErrorUtilities.VerifyArgumentNotNull(responseStream, "responseStream");
+
+ this.responseStream = responseStream;
+ }
+
+ /// <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); }
+ }
+
+ /// <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];
+ 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) {
+ 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>
+ private static MemoryStream CacheNetworkStreamAndClose(HttpWebResponse response, int maximumBytesToRead) {
+ ErrorUtilities.VerifyArgumentNotNull(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));
+ cachedStream.Seek(0, SeekOrigin.Begin);
+
+ networkStream.Dispose();
+ response.Close();
+
+ return cachedStream;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs
index 6484baf..2171a4e 100644
--- a/src/DotNetOpenAuth/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth/Messaging/Channel.cs
@@ -23,6 +23,11 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
public abstract class Channel {
/// <summary>
+ /// The encoding to use when writing out POST entity strings.
+ /// </summary>
+ private static readonly Encoding PostEntityEncoding = new UTF8Encoding(false);
+
+ /// <summary>
/// The maximum allowable size for a 301 Redirect response before we send
/// a 200 OK response with a scripted form POST with the parameters instead
/// in order to ensure successfully sending a large payload to another server
@@ -50,11 +55,6 @@ namespace DotNetOpenAuth.Messaging {
";
/// <summary>
- /// The encoding to use when writing out POST entity strings.
- /// </summary>
- private static readonly Encoding PostEntityEncoding = new UTF8Encoding(false);
-
- /// <summary>
/// A tool that can figure out what kind of message is being received
/// so it can be deserialized.
/// </summary>
diff --git a/src/DotNetOpenAuth/Messaging/DirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/DirectWebResponse.cs
index 11d16b7..11e1f75 100644
--- a/src/DotNetOpenAuth/Messaging/DirectWebResponse.cs
+++ b/src/DotNetOpenAuth/Messaging/DirectWebResponse.cs
@@ -17,30 +17,16 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Details on the response from a direct web request to a remote party.
/// </summary>
- [Serializable]
- [DebuggerDisplay("{Status} {ContentType.MediaType}: {Body.Substring(4,50)}")]
- public class DirectWebResponse : IDisposable {
+ public abstract class DirectWebResponse : 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>
- /// The network response object, used to initialize this instance, that still needs
- /// to be closed if applicable.
- /// </summary>
- private HttpWebResponse httpWebResponse;
-
- /// <summary>
- /// An object to be locked whenever the <see cref="httpWebResponse"/> or the
- /// <see cref="ResponseStream"/> members are being accessed.
- /// </summary>
- private object responseLock = new object();
-
- /// <summary>
/// Initializes a new instance of the <see cref="DirectWebResponse"/> class.
/// </summary>
- internal DirectWebResponse() {
+ protected internal DirectWebResponse() {
this.Status = HttpStatusCode.OK;
this.Headers = new WebHeaderCollection();
}
@@ -50,7 +36,7 @@ namespace DotNetOpenAuth.Messaging {
/// </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>
- internal DirectWebResponse(Uri requestUri, HttpWebResponse response) {
+ protected DirectWebResponse(Uri requestUri, HttpWebResponse response) {
ErrorUtilities.VerifyArgumentNotNull(requestUri, "requestUri");
ErrorUtilities.VerifyArgumentNotNull(response, "response");
@@ -62,8 +48,6 @@ namespace DotNetOpenAuth.Messaging {
this.FinalUri = response.ResponseUri;
this.Status = response.StatusCode;
this.Headers = response.Headers;
- this.httpWebResponse = response;
- this.ResponseStream = response.GetResponseStream();
}
/// <summary>
@@ -75,12 +59,9 @@ namespace DotNetOpenAuth.Messaging {
/// <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 DirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, Stream responseStream) {
+ protected DirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding) {
ErrorUtilities.VerifyArgumentNotNull(requestUri, "requestUri");
- ErrorUtilities.VerifyArgumentNotNull(responseStream, "responseStream");
this.RequestUri = requestUri;
- this.ResponseStream = responseStream;
this.Status = statusCode;
if (!string.IsNullOrEmpty(contentType)) {
this.ContentType = new ContentType(contentType);
@@ -115,12 +96,6 @@ namespace DotNetOpenAuth.Messaging {
public Uri FinalUri { get; private 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 headers that must be included in the response to the user agent.
/// </summary>
/// <remarks>
@@ -138,15 +113,7 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Gets the body of the HTTP response.
/// </summary>
- public Stream ResponseStream { 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); }
- }
+ public abstract Stream ResponseStream { get; }
/// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
@@ -165,25 +132,8 @@ namespace DotNetOpenAuth.Messaging {
foreach (string header in this.Headers) {
sb.AppendLine(string.Format(CultureInfo.CurrentCulture, "\t{0}: {1}", header, this.Headers[header]));
}
- sb.AppendLine("Response:");
- sb.AppendLine(this.Body);
- return sb.ToString();
- }
- /// <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.CacheNetworkStreamAndClose();
- 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));
- }
+ return sb.ToString();
}
/// <summary>
@@ -191,71 +141,38 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
public void Dispose() {
this.Dispose(true);
- GC.SuppressFinalize(true);
- }
-
- /// <summary>
- /// Caches the network stream and closes it if it is open.
- /// </summary>
- internal void CacheNetworkStreamAndClose() {
- this.CacheNetworkStreamAndClose(int.MaxValue);
+ GC.SuppressFinalize(this);
}
/// <summary>
- /// Caches the network stream and closes it if it is open.
+ /// Creates a text reader for the response stream.
/// </summary>
- /// <param name="maximumBytesToRead">The maximum bytes to cache.</param>
- internal void CacheNetworkStreamAndClose(int maximumBytesToRead) {
- lock (this.responseLock) {
- if (this.httpWebResponse != null) {
- // Now read and cache the network stream
- Stream networkStream = this.ResponseStream;
- this.ResponseStream = new MemoryStream(this.httpWebResponse.ContentLength < 0 ? 4 * 1024 : Math.Min((int)this.httpWebResponse.ContentLength, maximumBytesToRead));
- //// BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here.
- this.IsResponseTruncated = networkStream.CopyTo(this.ResponseStream, maximumBytesToRead) == maximumBytesToRead;
- this.ResponseStream.Seek(0, SeekOrigin.Begin);
-
- networkStream.Dispose();
- this.httpWebResponse.Close();
- this.httpWebResponse = null;
- }
- }
- }
+ /// <returns>The text reader, initialized for the proper encoding.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")]
+ public abstract StreamReader GetResponseReader();
/// <summary>
- /// Sets the response to some string, encoded as UTF-8.
+ /// Gets an offline snapshot version of this instance.
/// </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);
- }
+ /// <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 void Dispose(bool disposing) {
+ protected virtual void Dispose(bool disposing) {
if (disposing) {
- lock (this.responseLock) {
- if (this.ResponseStream != null) {
- this.ResponseStream.Dispose();
- this.ResponseStream = null;
- }
- if (this.httpWebResponse != null) {
- this.httpWebResponse.Close();
- this.httpWebResponse = null;
- }
+ Stream responseStream = this.ResponseStream;
+ if (responseStream != null) {
+ responseStream.Dispose();
}
}
}
diff --git a/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs
new file mode 100644
index 0000000..d0618fa
--- /dev/null
+++ b/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs
@@ -0,0 +1,108 @@
+//-----------------------------------------------------------------------
+// <copyright file="NetworkDirectWebResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+
+ /// <summary>
+ /// A live network HTTP response
+ /// </summary>
+ [DebuggerDisplay("{Status} {ContentType.MediaType}")]
+ internal class NetworkDirectWebResponse : DirectWebResponse, 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) {
+ 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.");
+ 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;
+ }
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs
index 1567a64..1a28006 100644
--- a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs
+++ b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs
@@ -64,9 +64,8 @@ namespace DotNetOpenAuth.Messaging {
try {
Logger.DebugFormat("HTTP {0} {1}", request.Method, request.RequestUri);
- using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
- return new DirectWebResponse(request.RequestUri, response);
- }
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ return new NetworkDirectWebResponse(request.RequestUri, response);
} catch (WebException ex) {
if (Logger.IsErrorEnabled) {
if (ex.Response != null) {
diff --git a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs
index 19042b0..b24cad2 100644
--- a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs
+++ b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs
@@ -206,7 +206,7 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
/// <param name="requireSsl">if set to <c>true</c> all requests made with this instance must be completed using SSL.</param>
/// <returns>
- /// An instance of <see cref="DirectWebResponse"/> describing the response.
+ /// An instance of <see cref="CachedDirectWebResponse"/> describing the response.
/// </returns>
public DirectWebResponse GetResponse(HttpWebRequest request, bool requireSsl) {
ErrorUtilities.VerifyArgumentNotNull(request, "request");
@@ -223,8 +223,7 @@ namespace DotNetOpenAuth.Messaging {
int i;
for (i = 0; i < this.MaximumRedirections; i++) {
this.EnsureAllowableRequestUri(request.RequestUri, requireSsl);
- DirectWebResponse response = this.chainedWebRequestHandler.GetResponse(request);
- response.CacheNetworkStreamAndClose(this.MaximumBytesToRead);
+ CachedDirectWebResponse response = this.chainedWebRequestHandler.GetResponse(request).GetSnapshot(this.MaximumBytesToRead);
if (response.Status == HttpStatusCode.MovedPermanently ||
response.Status == HttpStatusCode.Redirect ||
response.Status == HttpStatusCode.RedirectMethod ||
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
index 2957db3..5dc96bb 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
@@ -155,7 +155,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
protected override IDictionary<string, string> ReadFromResponseInternal(DirectWebResponse response) {
ErrorUtilities.VerifyArgumentNotNull(response, "response");
- return HttpUtility.ParseQueryString(response.Body).ToDictionary();
+ string body = response.GetResponseReader().ReadToEnd();
+ return HttpUtility.ParseQueryString(body).ToDictionary();
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs b/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs
index 5d2550e..9b77215 100644
--- a/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs
+++ b/src/DotNetOpenAuth/Yadis/DiscoveryResult.cs
@@ -22,7 +22,7 @@ namespace DotNetOpenAuth.Yadis {
/// <param name="requestUri">The user-supplied identifier.</param>
/// <param name="initialResponse">The initial response.</param>
/// <param name="finalResponse">The final response.</param>
- public DiscoveryResult(Uri requestUri, DirectWebResponse initialResponse, DirectWebResponse finalResponse) {
+ public DiscoveryResult(Uri requestUri, CachedDirectWebResponse initialResponse, CachedDirectWebResponse finalResponse) {
this.RequestUri = requestUri;
this.NormalizedUri = initialResponse.FinalUri;
if (finalResponse == null) {
diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs
index 89a74f6..7d63a52 100644
--- a/src/DotNetOpenAuth/Yadis/Yadis.cs
+++ b/src/DotNetOpenAuth/Yadis/Yadis.cs
@@ -30,6 +30,12 @@ namespace DotNetOpenAuth.Yadis {
internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable);
/// <summary>
+ /// The maximum number of bytes to read from an HTTP response
+ /// in searching for a link to a YADIS document.
+ /// </summary>
+ private const int MaximumResultToScan = 1024 * 1024;
+
+ /// <summary>
/// Performs YADIS discovery on some identifier.
/// </summary>
/// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param>
@@ -42,14 +48,13 @@ namespace DotNetOpenAuth.Yadis {
/// is not protected by SSL.
/// </returns>
public static DiscoveryResult Discover(IDirectWebRequestHandler requestHandler, UriIdentifier uri, bool requireSsl) {
- DirectWebResponse response;
+ CachedDirectWebResponse response;
try {
if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
Logger.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri);
return null;
}
- response = Request(requestHandler, uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds);
- response.CacheNetworkStreamAndClose();
+ response = Request(requestHandler, uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan);
if (response.Status != System.Net.HttpStatusCode.OK) {
Logger.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response.Status, response.Status, uri);
return null;
@@ -59,7 +64,7 @@ namespace DotNetOpenAuth.Yadis {
Logger.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri, ex);
return null;
}
- DirectWebResponse response2 = null;
+ CachedDirectWebResponse response2 = null;
if (IsXrdsDocument(response)) {
Logger.Debug("An XRDS response was received from GET at user-supplied identifier.");
response2 = response;
@@ -79,8 +84,7 @@ namespace DotNetOpenAuth.Yadis {
}
if (url != null) {
if (!requireSsl || string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
- response2 = Request(requestHandler, url, requireSsl, ContentTypes.Xrds);
- response2.CacheNetworkStreamAndClose();
+ response2 = Request(requestHandler, url, requireSsl, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan);
if (response2.Status != HttpStatusCode.OK) {
Logger.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response2.Status, response2.Status, uri);
return null;
@@ -148,7 +152,7 @@ namespace DotNetOpenAuth.Yadis {
/// <returns>
/// <c>true</c> if the response constains an XRDS document; otherwise, <c>false</c>.
/// </returns>
- private static bool IsXrdsDocument(DirectWebResponse response) {
+ private static bool IsXrdsDocument(CachedDirectWebResponse response) {
if (response.ContentType.MediaType == ContentTypes.Xrds) {
return true;
}