// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace System.Net.Http
{
///
/// Derived class which can encapsulate an
/// or an as an entity with media type "application/http".
///
public class HttpMessageContent : HttpContent
{
private const string SP = " ";
private const string CRLF = "\r\n";
private const string CommaSeparator = ", ";
private const int DefaultHeaderAllocation = 2 * 1024;
private const string DefaultMediaType = "application/http";
private const string MsgTypeParameter = "msgtype";
private const string DefaultRequestMsgType = "request";
private const string DefaultResponseMsgType = "response";
private const string DefaultRequestMediaType = DefaultMediaType + "; " + MsgTypeParameter + "=" + DefaultRequestMsgType;
private const string DefaultResponseMediaType = DefaultMediaType + "; " + MsgTypeParameter + "=" + DefaultResponseMsgType;
private static readonly Task _nullContentTask = TaskHelpers.FromResult(null);
private static readonly AsyncCallback _onWriteComplete = new AsyncCallback(OnWriteComplete);
///
/// Set of header fields that only support single values such as Set-Cookie.
///
private static readonly HashSet _singleValueHeaderFields = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"Set-Cookie",
"X-Powered-By",
};
private bool _contentConsumed;
private Lazy> _streamTask;
///
/// Initializes a new instance of the class encapsulating an
/// .
///
/// The instance to encapsulate.
public HttpMessageContent(HttpRequestMessage httpRequest)
{
if (httpRequest == null)
{
throw new ArgumentNullException("httpRequest");
}
HttpRequestMessage = httpRequest;
Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType);
Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultRequestMsgType));
InitializeStreamTask();
}
///
/// Initializes a new instance of the class encapsulating an
/// .
///
/// The instance to encapsulate.
public HttpMessageContent(HttpResponseMessage httpResponse)
{
if (httpResponse == null)
{
throw new ArgumentNullException("httpResponse");
}
HttpResponseMessage = httpResponse;
Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType);
Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultResponseMsgType));
InitializeStreamTask();
}
private HttpContent Content
{
get { return HttpRequestMessage != null ? HttpRequestMessage.Content : HttpResponseMessage.Content; }
}
///
/// Gets the HTTP request message.
///
public HttpRequestMessage HttpRequestMessage { get; private set; }
///
/// Gets the HTTP response message.
///
public HttpResponseMessage HttpResponseMessage { get; private set; }
private void InitializeStreamTask()
{
_streamTask = new Lazy>(() => Content == null ? null : Content.ReadAsStreamAsync());
}
///
/// Validates whether the content contains an HTTP Request or an HTTP Response.
///
/// The content to validate.
/// if set to true if the content is either an HTTP Request or an HTTP Response.
/// Indicates whether validation failure should result in an or not.
/// true if content is either an HTTP Request or an HTTP Response
internal static bool ValidateHttpMessageContent(HttpContent content, bool isRequest, bool throwOnError)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
MediaTypeHeaderValue contentType = content.Headers.ContentType;
if (contentType != null)
{
if (!contentType.MediaType.Equals(DefaultMediaType, StringComparison.OrdinalIgnoreCase))
{
if (throwOnError)
{
throw new ArgumentException(
RS.Format(Properties.Resources.HttpMessageInvalidMediaType, FormattingUtilities.HttpContentType.Name,
isRequest ? DefaultRequestMediaType : DefaultResponseMediaType),
"content");
}
else
{
return false;
}
}
foreach (NameValueHeaderValue parameter in contentType.Parameters)
{
if (parameter.Name.Equals(MsgTypeParameter, StringComparison.OrdinalIgnoreCase))
{
string msgType = FormattingUtilities.UnquoteToken(parameter.Value);
if (!msgType.Equals(isRequest ? DefaultRequestMsgType : DefaultResponseMsgType, StringComparison.OrdinalIgnoreCase))
{
if (throwOnError)
{
throw new ArgumentException(
RS.Format(Properties.Resources.HttpMessageInvalidMediaType, FormattingUtilities.HttpContentType.Name, isRequest ? DefaultRequestMediaType : DefaultResponseMediaType),
"content");
}
else
{
return false;
}
}
return true;
}
}
}
if (throwOnError)
{
throw new ArgumentException(
RS.Format(Properties.Resources.HttpMessageInvalidMediaType, FormattingUtilities.HttpContentType.Name, isRequest ? DefaultRequestMediaType : DefaultResponseMediaType),
"content");
}
else
{
return false;
}
}
///
/// Asynchronously serializes the object's content to the given .
///
/// The to which to write.
/// The associated .
/// A instance that is asynchronously serializing the object's content.
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is propagated.")]
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
Contract.Assert(stream != null);
// Serialize header
byte[] header = SerializeHeader();
TaskCompletionSource