// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http.Headers;
namespace System.Net.Http
{
///
/// An suited for use with HTML file uploads for writing file
/// content to a . The stream provider looks at the Content-Disposition header
/// field and determines an output based on the presence of a filename parameter.
/// If a filename parameter is present in the Content-Disposition header field then the body
/// part is written to a , otherwise it is written to a .
/// This makes it convenient to process MIME Multipart HTML Form data which is a combination of form
/// data and file content.
///
public class MultipartFormDataStreamProvider : IMultipartStreamProvider
{
private const int MinBufferSize = 1;
private const int DefaultBufferSize = 0x1000;
private Dictionary _bodyPartFileNames = new Dictionary();
private readonly object _thisLock = new object();
private string _rootPath;
private int _bufferSize = DefaultBufferSize;
///
/// Initializes a new instance of the class.
///
/// The root path where the content of MIME multipart body parts are written to.
public MultipartFormDataStreamProvider(string rootPath)
: this(rootPath, DefaultBufferSize)
{
}
///
/// Initializes a new instance of the class.
///
/// The root path where the content of MIME multipart body parts are written to.
/// The number of bytes buffered for writes to the file.
public MultipartFormDataStreamProvider(string rootPath, int bufferSize)
{
if (String.IsNullOrWhiteSpace(rootPath))
{
throw new ArgumentNullException("rootPath");
}
if (bufferSize < MinBufferSize)
{
throw new ArgumentOutOfRangeException("bufferSize", bufferSize, RS.Format(Properties.Resources.ArgumentMustBeGreaterThanOrEqualTo, MinBufferSize));
}
_rootPath = Path.GetFullPath(rootPath);
_bufferSize = bufferSize;
}
///
/// Gets an instance containing mappings of each
/// filename parameter provided in a Content-Disposition header field
/// (represented as the keys) to a local file name where the contents of the body part is
/// stored (represented as the values).
///
public IDictionary BodyPartFileNames
{
get
{
lock (_thisLock)
{
return new Dictionary(_bodyPartFileNames);
}
}
}
///
/// This body part stream provider examines the headers provided by the MIME multipart parser
/// and decides whether it should return a file stream or a memory stream for the body part to be
/// written to.
///
/// Header fields describing the body part
/// The instance where the message body part is written to.
public virtual Stream GetStream(HttpContentHeaders headers)
{
if (headers == null)
{
throw new ArgumentNullException("headers");
}
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
if (contentDisposition != null)
{
// If we have a file name then write contents out to temporary file. Otherwise just write to MemoryStream
if (!String.IsNullOrEmpty(contentDisposition.FileName))
{
string localFilePath;
try
{
string filename = GetLocalFileName(headers);
localFilePath = Path.Combine(_rootPath, Path.GetFileName(filename));
}
catch (Exception e)
{
throw new InvalidOperationException(Properties.Resources.MultipartStreamProviderInvalidLocalFileName, e);
}
// Add mapping from Content-Disposition FileName parameter to local file name.
lock (_thisLock)
{
_bodyPartFileNames.Add(contentDisposition.FileName, localFilePath);
}
return File.Create(localFilePath, _bufferSize, FileOptions.Asynchronous);
}
// If no filename parameter was found in the Content-Disposition header then return a memory stream.
return new MemoryStream();
}
// If no Content-Disposition header was present.
throw new IOException(RS.Format(Properties.Resources.MultipartFormDataStreamProviderNoContentDisposition, "Content-Disposition"));
}
///
/// Gets the name of the local file which will be combined with the root path to
/// create an absolute file name where the contents of the current MIME body part
/// will be stored.
///
/// The headers for the current MIME body part.
/// A relative filename with no path component.
public virtual string GetLocalFileName(HttpContentHeaders headers)
{
if (headers == null)
{
throw new ArgumentNullException("headers");
}
return String.Format(CultureInfo.InvariantCulture, "BodyPart_{0}", Guid.NewGuid());
}
}
}