You've already forked linux-packaging-mono
381 lines
18 KiB
C#
381 lines
18 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
|
|
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http.Formatting.Parsers;
|
|
using System.Net.Http.Headers;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace System.Net.Http
|
|
{
|
|
/// <summary>
|
|
/// Extension methods to read <see cref="HttpRequestMessage"/> and <see cref="HttpResponseMessage"/> entities from <see cref="HttpContent"/> instances.
|
|
/// </summary>
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public static class HttpContentMessageExtensions
|
|
{
|
|
private const int MinBufferSize = 256;
|
|
private const int DefaultBufferSize = 32 * 1024;
|
|
|
|
/// <summary>
|
|
/// Determines whether the specified content is HTTP request message content.
|
|
/// </summary>
|
|
/// <param name="content">The content.</param>
|
|
/// <returns>
|
|
/// <c>true</c> if the specified content is HTTP message content; otherwise, <c>false</c>.
|
|
/// </returns>
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception translates to false.")]
|
|
public static bool IsHttpRequestMessageContent(this HttpContent content)
|
|
{
|
|
if (content == null)
|
|
{
|
|
throw new ArgumentNullException("content");
|
|
}
|
|
|
|
try
|
|
{
|
|
return HttpMessageContent.ValidateHttpMessageContent(content, true, false);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the specified content is HTTP response message content.
|
|
/// </summary>
|
|
/// <param name="content">The content.</param>
|
|
/// <returns>
|
|
/// <c>true</c> if the specified content is HTTP message content; otherwise, <c>false</c>.
|
|
/// </returns>
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception translates to false.")]
|
|
public static bool IsHttpResponseMessageContent(this HttpContent content)
|
|
{
|
|
if (content == null)
|
|
{
|
|
throw new ArgumentNullException("content");
|
|
}
|
|
|
|
try
|
|
{
|
|
return HttpMessageContent.ValidateHttpMessageContent(content, false, false);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the <see cref="HttpContent"/> as an <see cref="HttpRequestMessage"/>.
|
|
/// </summary>
|
|
/// <param name="content">The content to read.</param>
|
|
/// <returns>The parsed <see cref="HttpRequestMessage"/> instance.</returns>
|
|
public static Task<HttpRequestMessage> ReadAsHttpRequestMessageAsync(this HttpContent content)
|
|
{
|
|
return ReadAsHttpRequestMessageAsync(content, Uri.UriSchemeHttp, DefaultBufferSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the <see cref="HttpContent"/> as an <see cref="HttpRequestMessage"/>.
|
|
/// </summary>
|
|
/// <param name="content">The content to read.</param>
|
|
/// <param name="uriScheme">The URI scheme to use for the request URI.</param>
|
|
/// <returns>The parsed <see cref="HttpRequestMessage"/> instance.</returns>
|
|
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a full URI but only the URI scheme")]
|
|
public static Task<HttpRequestMessage> ReadAsHttpRequestMessageAsync(this HttpContent content, string uriScheme)
|
|
{
|
|
return ReadAsHttpRequestMessageAsync(content, uriScheme, DefaultBufferSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the <see cref="HttpContent"/> as an <see cref="HttpRequestMessage"/>.
|
|
/// </summary>
|
|
/// <param name="content">The content to read.</param>
|
|
/// <param name="uriScheme">The URI scheme to use for the request URI (the
|
|
/// URI scheme is not actually part of the HTTP Request URI and so must be provided externally).</param>
|
|
/// <param name="bufferSize">Size of the buffer.</param>
|
|
/// <returns>The parsed <see cref="HttpRequestMessage"/> instance.</returns>
|
|
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a full URI but only the URI scheme")]
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception translates to parser state.")]
|
|
public static Task<HttpRequestMessage> ReadAsHttpRequestMessageAsync(this HttpContent content, string uriScheme, int bufferSize)
|
|
{
|
|
if (content == null)
|
|
{
|
|
throw new ArgumentNullException("content");
|
|
}
|
|
|
|
if (uriScheme == null)
|
|
{
|
|
throw new ArgumentNullException("uriScheme");
|
|
}
|
|
|
|
if (!Uri.CheckSchemeName(uriScheme))
|
|
{
|
|
throw new ArgumentException(RS.Format(Properties.Resources.HttpMessageParserInvalidUriScheme, uriScheme, typeof(Uri).Name), "uriScheme");
|
|
}
|
|
|
|
if (bufferSize < MinBufferSize)
|
|
{
|
|
throw new ArgumentOutOfRangeException("bufferSize", bufferSize, RS.Format(Properties.Resources.ArgumentMustBeGreaterThanOrEqualTo, MinBufferSize));
|
|
}
|
|
|
|
HttpMessageContent.ValidateHttpMessageContent(content, true, true);
|
|
|
|
return content.ReadAsStreamAsync().Then(stream =>
|
|
{
|
|
HttpUnsortedRequest httpRequest = new HttpUnsortedRequest();
|
|
HttpRequestHeaderParser parser = new HttpRequestHeaderParser(httpRequest);
|
|
ParserState parseStatus;
|
|
|
|
byte[] buffer = new byte[bufferSize];
|
|
int bytesRead = 0;
|
|
int headerConsumed = 0;
|
|
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
bytesRead = stream.Read(buffer, 0, buffer.Length);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new IOException(Properties.Resources.HttpMessageErrorReading, e);
|
|
}
|
|
|
|
try
|
|
{
|
|
parseStatus = parser.ParseBuffer(buffer, bytesRead, ref headerConsumed);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
parseStatus = ParserState.Invalid;
|
|
}
|
|
|
|
if (parseStatus == ParserState.Done)
|
|
{
|
|
return CreateHttpRequestMessage(uriScheme, httpRequest, stream, bytesRead - headerConsumed);
|
|
}
|
|
else if (parseStatus != ParserState.NeedMoreData)
|
|
{
|
|
throw new IOException(RS.Format(Properties.Resources.HttpMessageParserError, headerConsumed, buffer));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the <see cref="HttpContent"/> as an <see cref="HttpResponseMessage"/>.
|
|
/// </summary>
|
|
/// <param name="content">The content to read.</param>
|
|
/// <returns>The parsed <see cref="HttpResponseMessage"/> instance.</returns>
|
|
public static Task<HttpResponseMessage> ReadAsHttpResponseMessageAsync(this HttpContent content)
|
|
{
|
|
return ReadAsHttpResponseMessageAsync(content, DefaultBufferSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the <see cref="HttpContent"/> as an <see cref="HttpResponseMessage"/>.
|
|
/// </summary>
|
|
/// <param name="content">The content to read.</param>
|
|
/// <param name="bufferSize">Size of the buffer.</param>
|
|
/// <returns>The parsed <see cref="HttpResponseMessage"/> instance.</returns>
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception translates to parser state.")]
|
|
public static Task<HttpResponseMessage> ReadAsHttpResponseMessageAsync(this HttpContent content, int bufferSize)
|
|
{
|
|
if (content == null)
|
|
{
|
|
throw new ArgumentNullException("content");
|
|
}
|
|
|
|
if (bufferSize < MinBufferSize)
|
|
{
|
|
throw new ArgumentOutOfRangeException("bufferSize", bufferSize, RS.Format(Properties.Resources.ArgumentMustBeGreaterThanOrEqualTo, MinBufferSize));
|
|
}
|
|
|
|
HttpMessageContent.ValidateHttpMessageContent(content, false, true);
|
|
|
|
return content.ReadAsStreamAsync().Then(stream =>
|
|
{
|
|
HttpUnsortedResponse httpResponse = new HttpUnsortedResponse();
|
|
HttpResponseHeaderParser parser = new HttpResponseHeaderParser(httpResponse);
|
|
ParserState parseStatus;
|
|
|
|
byte[] buffer = new byte[bufferSize];
|
|
int bytesRead = 0;
|
|
int headerConsumed = 0;
|
|
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
bytesRead = stream.Read(buffer, 0, buffer.Length);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new IOException(Properties.Resources.HttpMessageErrorReading, e);
|
|
}
|
|
|
|
try
|
|
{
|
|
parseStatus = parser.ParseBuffer(buffer, bytesRead, ref headerConsumed);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
parseStatus = ParserState.Invalid;
|
|
}
|
|
|
|
if (parseStatus == ParserState.Done)
|
|
{
|
|
// Create and return parsed HttpResponseMessage
|
|
return CreateHttpResponseMessage(httpResponse, stream, bytesRead - headerConsumed);
|
|
}
|
|
else if (parseStatus != ParserState.NeedMoreData)
|
|
{
|
|
throw new IOException(RS.Format(Properties.Resources.HttpMessageParserError, headerConsumed, buffer));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the request URI by combining scheme (provided) with parsed values of
|
|
/// host and path.
|
|
/// </summary>
|
|
/// <param name="uriScheme">The URI scheme to use for the request URI.</param>
|
|
/// <param name="httpRequest">The unsorted HTTP request.</param>
|
|
/// <returns>A fully qualified request URI.</returns>
|
|
private static Uri CreateRequestUri(string uriScheme, HttpUnsortedRequest httpRequest)
|
|
{
|
|
Contract.Assert(httpRequest != null, "httpRequest cannot be null.");
|
|
Contract.Assert(uriScheme != null, "uriScheme cannot be null");
|
|
|
|
IEnumerable<string> hostValues;
|
|
if (httpRequest.HttpHeaders.TryGetValues(FormattingUtilities.HttpHostHeader, out hostValues))
|
|
{
|
|
int hostCount = hostValues.Count();
|
|
if (hostCount != 1)
|
|
{
|
|
throw new IOException(RS.Format(Properties.Resources.HttpMessageParserInvalidHostCount, FormattingUtilities.HttpHostHeader, hostCount));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new IOException(RS.Format(Properties.Resources.HttpMessageParserInvalidHostCount, FormattingUtilities.HttpHostHeader, 0));
|
|
}
|
|
|
|
// We don't use UriBuilder as hostValues.ElementAt(0) contains 'host:port' and UriBuilder needs these split out into separate host and port.
|
|
string requestUri = String.Format(CultureInfo.InvariantCulture, "{0}://{1}{2}", uriScheme, hostValues.ElementAt(0), httpRequest.RequestUri);
|
|
return new Uri(requestUri);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the unsorted header fields to a sorted collection.
|
|
/// </summary>
|
|
/// <param name="source">The unsorted source headers</param>
|
|
/// <param name="destination">The destination <see cref="HttpRequestHeaders"/> or <see cref="HttpResponseHeaders"/>.</param>
|
|
/// <param name="contentStream">The input <see cref="Stream"/> used to form any <see cref="HttpContent"/> being part of this HTTP request.</param>
|
|
/// <param name="rewind">Start location of any request entity within the <paramref name="contentStream"/>.</param>
|
|
/// <returns>An <see cref="HttpContent"/> instance if header fields contained and <see cref="HttpContentHeaders"/>.</returns>
|
|
private static HttpContent CreateHeaderFields(HttpHeaders source, HttpHeaders destination, Stream contentStream, int rewind)
|
|
{
|
|
Contract.Assert(source != null, "source headers cannot be null");
|
|
Contract.Assert(destination != null, "destination headers cannot be null");
|
|
Contract.Assert(contentStream != null, "contentStream must be non null");
|
|
HttpContentHeaders contentHeaders = null;
|
|
HttpContent content = null;
|
|
|
|
// Set the header fields
|
|
foreach (KeyValuePair<string, IEnumerable<string>> header in source)
|
|
{
|
|
if (!destination.TryAddWithoutValidation(header.Key, header.Value))
|
|
{
|
|
if (contentHeaders == null)
|
|
{
|
|
contentHeaders = FormattingUtilities.CreateEmptyContentHeaders();
|
|
}
|
|
|
|
contentHeaders.TryAddWithoutValidation(header.Key, header.Value);
|
|
}
|
|
}
|
|
|
|
// If we have content headers then create an HttpContent for this Response
|
|
if (contentHeaders != null)
|
|
{
|
|
// Need to rewind the input stream to be at the position right after the HTTP header
|
|
// which we may already have parsed as we read the content stream.
|
|
if (!contentStream.CanSeek)
|
|
{
|
|
throw new IOException(RS.Format(Properties.Resources.HttpMessageContentStreamMustBeSeekable, "ContentReadStream", FormattingUtilities.HttpResponseMessageType.Name));
|
|
}
|
|
|
|
contentStream.Seek(0 - rewind, SeekOrigin.Current);
|
|
content = new StreamContent(contentStream);
|
|
contentHeaders.CopyTo(content.Headers);
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="HttpRequestMessage"/> based on information provided in <see cref="HttpUnsortedRequest"/>.
|
|
/// </summary>
|
|
/// <param name="uriScheme">The URI scheme to use for the request URI.</param>
|
|
/// <param name="httpRequest">The unsorted HTTP request.</param>
|
|
/// <param name="contentStream">The input <see cref="Stream"/> used to form any <see cref="HttpContent"/> being part of this HTTP request.</param>
|
|
/// <param name="rewind">Start location of any request entity within the <paramref name="contentStream"/>.</param>
|
|
/// <returns>A newly created <see cref="HttpRequestMessage"/> instance.</returns>
|
|
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "caller becomes owner.")]
|
|
private static HttpRequestMessage CreateHttpRequestMessage(string uriScheme, HttpUnsortedRequest httpRequest, Stream contentStream, int rewind)
|
|
{
|
|
Contract.Assert(uriScheme != null, "URI scheme must be non null");
|
|
Contract.Assert(httpRequest != null, "httpRequest must be non null");
|
|
Contract.Assert(contentStream != null, "contentStream must be non null");
|
|
|
|
HttpRequestMessage httpRequestMessage = new HttpRequestMessage();
|
|
|
|
// Set method, requestURI, and version
|
|
httpRequestMessage.Method = httpRequest.Method;
|
|
httpRequestMessage.RequestUri = CreateRequestUri(uriScheme, httpRequest);
|
|
httpRequestMessage.Version = httpRequest.Version;
|
|
|
|
// Set the header fields and content if any
|
|
httpRequestMessage.Content = CreateHeaderFields(httpRequest.HttpHeaders, httpRequestMessage.Headers, contentStream, rewind);
|
|
|
|
return httpRequestMessage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="HttpResponseMessage"/> based on information provided in <see cref="HttpUnsortedResponse"/>.
|
|
/// </summary>
|
|
/// <param name="httpResponse">The unsorted HTTP Response.</param>
|
|
/// <param name="contentStream">The input <see cref="Stream"/> used to form any <see cref="HttpContent"/> being part of this HTTP Response.</param>
|
|
/// <param name="rewind">Start location of any Response entity within the <paramref name="contentStream"/>.</param>
|
|
/// <returns>A newly created <see cref="HttpResponseMessage"/> instance.</returns>
|
|
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "caller becomes owner.")]
|
|
private static HttpResponseMessage CreateHttpResponseMessage(HttpUnsortedResponse httpResponse, Stream contentStream, int rewind)
|
|
{
|
|
Contract.Assert(httpResponse != null, "httpResponse must be non null");
|
|
Contract.Assert(contentStream != null, "contentStream must be non null");
|
|
|
|
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
|
|
|
|
// Set version, status code and reason phrase
|
|
httpResponseMessage.Version = httpResponse.Version;
|
|
httpResponseMessage.StatusCode = httpResponse.StatusCode;
|
|
httpResponseMessage.ReasonPhrase = httpResponse.ReasonPhrase;
|
|
|
|
// Set the header fields and content if any
|
|
httpResponseMessage.Content = CreateHeaderFields(httpResponse.HttpHeaders, httpResponseMessage.Headers, contentStream, rewind);
|
|
|
|
return httpResponseMessage;
|
|
}
|
|
}
|
|
}
|