// 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.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Web.Http;
namespace System.Net.Http
{
///
/// An suited for reading MIME body parts following the
/// multipart/related media type as defined in RFC 2387 (see http://www.ietf.org/rfc/rfc2387.txt).
///
public class MultipartRelatedStreamProvider : MultipartStreamProvider
{
private const string RelatedSubType = "related";
private const string ContentID = "Content-ID";
private const string StartParameter = "Start";
private HttpContent _rootContent;
private HttpContent _parent;
///
/// Gets the instance that has been marked as the root content in the
/// MIME multipart related message using the start parameter. If no start parameter is
/// present then pick the first of the children.
///
public HttpContent RootContent
{
get
{
if (_rootContent == null)
{
_rootContent = FindRootContent(_parent, Contents);
}
return _rootContent;
}
}
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
if (parent == null)
{
throw Error.ArgumentNull("parent");
}
if (headers == null)
{
throw Error.ArgumentNull("headers");
}
// See if we need to remember the parent to be able to determine the root content
if (_parent == null)
{
_parent = parent;
}
return new MemoryStream();
}
///
/// Looks for the "start" parameter of the parent's content type and then finds the corresponding
/// child HttpContent with a matching Content-ID header field.
///
/// The matching child or null if none found.
private static HttpContent FindRootContent(HttpContent parent, IEnumerable children)
{
Contract.Assert(children != null);
// Find 'start' parameter from parent content type. The value is used
// to identify the MIME body with the corresponding Content-ID header value.
NameValueHeaderValue startNameValue = FindMultipartRelatedParameter(parent, StartParameter);
if (startNameValue == null)
{
// If we didn't find a "start" parameter then take the first child.
return children.FirstOrDefault();
}
// Look for the child with a Content-ID header that corresponds to the "start" value.
// If no matching child is found then we return null.
string startValue = FormattingUtilities.UnquoteToken(startNameValue.Value);
return children.FirstOrDefault(
content =>
{
IEnumerable values;
if (content.Headers.TryGetValues(ContentID, out values))
{
return String.Equals(
FormattingUtilities.UnquoteToken(values.ElementAt(0)),
startValue,
StringComparison.OrdinalIgnoreCase);
}
return false;
});
}
///
/// Looks for a parameter in the .
///
/// The matching parameter or null if none found.
private static NameValueHeaderValue FindMultipartRelatedParameter(HttpContent content, string parameterName)
{
// If no parent then we are done
if (content == null)
{
return null;
}
// Check that we have a parent content type and that it is indeed multipart/related
MediaTypeHeaderValue parentContentType = content.Headers.ContentType;
if (parentContentType == null || !content.IsMimeMultipartContent(RelatedSubType))
{
return null;
}
// Look for parameter
return parentContentType.Parameters.FirstOrDefault(nvp => String.Equals(nvp.Name, parameterName, StringComparison.OrdinalIgnoreCase));
}
}
}