// 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()); } } }