// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net.Http.Formatting.Parsers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
namespace System.Net.Http
{
///
/// Extension methods to read MIME multipart entities from instances.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public static class HttpContentMultipartExtensions
{
private const int MinBufferSize = 256;
private const int DefaultBufferSize = 32 * 1024;
///
/// Determines whether the specified content is MIME multipart content.
///
/// The content.
///
/// true if the specified content is MIME multipart content; otherwise, false.
///
public static bool IsMimeMultipartContent(this HttpContent content)
{
if (content == null)
{
throw Error.ArgumentNull("content");
}
return MimeMultipartBodyPartParser.IsMimeMultipartContent(content);
}
///
/// Determines whether the specified content is MIME multipart content with the
/// specified subtype. For example, the subtype mixed would match content
/// with a content type of multipart/mixed.
///
/// The content.
/// The MIME multipart subtype to match.
///
/// true if the specified content is MIME multipart content with the specified subtype; otherwise, false.
///
public static bool IsMimeMultipartContent(this HttpContent content, string subtype)
{
if (String.IsNullOrWhiteSpace(subtype))
{
throw Error.ArgumentNull("subtype");
}
if (IsMimeMultipartContent(content))
{
if (content.Headers.ContentType.MediaType.Equals("multipart/" + subtype, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
///
/// Reads all body parts within a MIME multipart message into memory using a .
///
/// An existing instance to use for the object's content.
/// A representing the tasks of getting the result of reading the MIME content.
public static Task ReadAsMultipartAsync(this HttpContent content)
{
return ReadAsMultipartAsync(content, new MultipartMemoryStreamProvider(), DefaultBufferSize);
}
///
/// Reads all body parts within a MIME multipart message into memory using a .
///
/// An existing instance to use for the object's content.
/// The token to monitor for cancellation requests.
/// A representing the tasks of getting the result of reading the MIME content.
public static Task ReadAsMultipartAsync(this HttpContent content, CancellationToken cancellationToken)
{
return ReadAsMultipartAsync(content, new MultipartMemoryStreamProvider(), DefaultBufferSize, cancellationToken);
}
///
/// Reads all body parts within a MIME multipart message using the provided instance
/// to determine where the contents of each body part is written.
///
/// The with which to process the data.
/// An existing instance to use for the object's content.
/// A stream provider providing output streams for where to write body parts as they are parsed.
/// A representing the tasks of getting the result of reading the MIME content.
public static Task ReadAsMultipartAsync(this HttpContent content, T streamProvider) where T : MultipartStreamProvider
{
return ReadAsMultipartAsync(content, streamProvider, DefaultBufferSize);
}
///
/// Reads all body parts within a MIME multipart message using the provided instance
/// to determine where the contents of each body part is written.
///
/// The with which to process the data.
/// An existing instance to use for the object's content.
/// A stream provider providing output streams for where to write body parts as they are parsed.
/// The token to monitor for cancellation requests.
/// A representing the tasks of getting the result of reading the MIME content.
public static Task ReadAsMultipartAsync(this HttpContent content, T streamProvider, CancellationToken cancellationToken)
where T : MultipartStreamProvider
{
return ReadAsMultipartAsync(content, streamProvider, DefaultBufferSize, cancellationToken);
}
///
/// Reads all body parts within a MIME multipart message using the provided instance
/// to determine where the contents of each body part is written and as read buffer size.
///
/// The with which to process the data.
/// An existing instance to use for the object's content.
/// A stream provider providing output streams for where to write body parts as they are parsed.
/// Size of the buffer used to read the contents.
/// A representing the tasks of getting the result of reading the MIME content.
public static Task ReadAsMultipartAsync(this HttpContent content, T streamProvider, int bufferSize)
where T : MultipartStreamProvider
{
return ReadAsMultipartAsync(content, streamProvider, bufferSize, CancellationToken.None);
}
///
/// Reads all body parts within a MIME multipart message using the provided instance
/// to determine where the contents of each body part is written and as read buffer size.
///
/// The with which to process the data.
/// An existing instance to use for the object's content.
/// A stream provider providing output streams for where to write body parts as they are parsed.
/// Size of the buffer used to read the contents.
/// The token to monitor for cancellation requests.
/// A representing the tasks of getting the result of reading the MIME content.
public static async Task ReadAsMultipartAsync(this HttpContent content, T streamProvider, int bufferSize,
CancellationToken cancellationToken) where T : MultipartStreamProvider
{
if (content == null)
{
throw Error.ArgumentNull("content");
}
if (streamProvider == null)
{
throw Error.ArgumentNull("streamProvider");
}
if (bufferSize < MinBufferSize)
{
throw Error.ArgumentMustBeGreaterThanOrEqualTo("bufferSize", bufferSize, MinBufferSize);
}
Stream stream;
try
{
stream = await content.ReadAsStreamAsync();
}
catch (Exception e)
{
throw new IOException(Properties.Resources.ReadAsMimeMultipartErrorReading, e);
}
using (var parser = new MimeMultipartBodyPartParser(content, streamProvider))
{
byte[] data = new byte[bufferSize];
MultipartAsyncContext context = new MultipartAsyncContext(stream, parser, data, streamProvider.Contents);
// Start async read/write loop
await MultipartReadAsync(context, cancellationToken);
// Let the stream provider post-process when everything is complete
await streamProvider.ExecutePostProcessingAsync(cancellationToken);
return streamProvider;
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is propagated.")]
private static async Task MultipartReadAsync(MultipartAsyncContext context, CancellationToken cancellationToken)
{
Contract.Assert(context != null, "context cannot be null");
while (true)
{
int bytesRead;
try
{
bytesRead = await context.ContentStream.ReadAsync(context.Data, 0, context.Data.Length, cancellationToken);
}
catch (Exception e)
{
throw new IOException(Properties.Resources.ReadAsMimeMultipartErrorReading, e);
}
IEnumerable parts = context.MimeParser.ParseBuffer(context.Data, bytesRead);
foreach (MimeBodyPart part in parts)
{
foreach (ArraySegment segment in part.Segments)
{
try
{
await part.WriteSegment(segment, cancellationToken);
}
catch (Exception e)
{
part.Dispose();
throw new IOException(Properties.Resources.ReadAsMimeMultipartErrorWriting, e);
}
}
if (CheckIsFinalPart(part, context.Result))
{
return;
}
}
}
}
private static bool CheckIsFinalPart(MimeBodyPart part, ICollection result)
{
Contract.Assert(part != null, "part cannot be null.");
Contract.Assert(result != null, "result cannot be null.");
if (part.IsComplete)
{
HttpContent partContent = part.GetCompletedHttpContent();
if (partContent != null)
{
result.Add(partContent);
}
bool isFinal = part.IsFinal;
part.Dispose();
return isFinal;
}
return false;
}
///
/// Managing state for asynchronous read and write operations
///
private class MultipartAsyncContext
{
public MultipartAsyncContext(Stream contentStream, MimeMultipartBodyPartParser mimeParser, byte[] data, ICollection result)
{
Contract.Assert(contentStream != null);
Contract.Assert(mimeParser != null);
Contract.Assert(data != null);
ContentStream = contentStream;
Result = result;
MimeParser = mimeParser;
Data = data;
}
///
/// Gets the that we read from.
///
public Stream ContentStream { get; private set; }
///
/// Gets the collection of parsed instances.
///
public ICollection Result { get; private set; }
///
/// The data buffer that we use for reading data from the input stream into before processing.
///
public byte[] Data { get; private set; }
///
/// Gets the MIME parser instance used to parse the data
///
public MimeMultipartBodyPartParser MimeParser { get; private set; }
}
}
}