//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; using System.Text; using Validation; /// /// Cached details on the response from a direct web request to a remote party. /// [DebuggerDisplay("{Status} {ContentType.MediaType}, length: {ResponseStream.Length}")] internal class CachedDirectWebResponse : IncomingWebResponse { /// /// A seekable, repeatable response stream. /// private MemoryStream responseStream; /// /// Initializes a new instance of the class. /// internal CachedDirectWebResponse() { } /// /// Initializes a new instance of the class. /// /// The request URI. /// The response. /// The maximum bytes to read. 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; } /// /// Initializes a new instance of the class. /// /// The request URI. /// The final URI to respond to the request. /// The headers. /// The status code. /// Type of the content. /// The content encoding. /// The response stream. 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; } /// /// Gets a value indicating whether the cached response stream was /// truncated to a maximum allowable length. /// public bool ResponseTruncated { get; private set; } /// /// Gets the body of the HTTP response. /// public override Stream ResponseStream { get { return this.responseStream; } } /// /// Gets or sets the cached response stream. /// internal MemoryStream CachedResponseStream { get { return this.responseStream; } set { this.responseStream = value; } } /// /// Creates a text reader for the response stream. /// /// The text reader, initialized for the proper encoding. 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); } /// /// Gets the body of the response as a string. /// /// The entire body of the response. internal string GetResponseString() { if (this.ResponseStream != null) { string value = this.GetResponseReader().ReadToEnd(); this.ResponseStream.Seek(0, SeekOrigin.Begin); return value; } else { return null; } } /// /// Gets an offline snapshot version of this instance. /// /// The maximum bytes from the response stream to cache. /// A snapshot version of this instance. /// /// If this instance is a creating a snapshot /// will automatically close and dispose of the underlying response stream. /// If this instance is a , the result will /// be the self same instance. /// internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { return this; } /// /// Sets the response to some string, encoded as UTF-8. /// /// The string to set the response to. 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); } /// /// Caches the network stream and closes it if it is open. /// /// The response whose stream is to be cloned. /// The maximum bytes to cache. /// The seekable Stream instance that contains a copy of what was returned in the HTTP response. [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; } } } }