//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Text; /// /// Represents a single part in a HTTP multipart POST request. /// public class MultipartPostPart : IDisposable { /// /// The "Content-Disposition" string. /// private const string ContentDispositionHeader = "Content-Disposition"; /// /// The two-character \r\n newline character sequence to use. /// private const string NewLine = "\r\n"; /// /// Initializes a new instance of the class. /// /// The content disposition of the part. public MultipartPostPart(string contentDisposition) { Requires.NotNullOrEmpty(contentDisposition, "contentDisposition"); this.ContentDisposition = contentDisposition; this.ContentAttributes = new Dictionary(); this.PartHeaders = new WebHeaderCollection(); } /// /// Gets or sets the content disposition. /// /// The content disposition. public string ContentDisposition { get; set; } /// /// Gets the key=value attributes that appear on the same line as the Content-Disposition. /// /// The content attributes. public IDictionary ContentAttributes { get; private set; } /// /// Gets the headers that appear on subsequent lines after the Content-Disposition. /// public WebHeaderCollection PartHeaders { get; private set; } /// /// Gets or sets the content of the part. /// public Stream Content { get; set; } /// /// Gets the length of this entire part. /// /// Useful for calculating the ContentLength HTTP header to send before actually serializing the content. public long Length { get { ErrorUtilities.VerifyOperation(this.Content != null && this.Content.Length >= 0, MessagingStrings.StreamMustHaveKnownLength); long length = 0; length += ContentDispositionHeader.Length; length += ": ".Length; length += this.ContentDisposition.Length; foreach (var pair in this.ContentAttributes) { length += "; ".Length + pair.Key.Length + "=\"".Length + pair.Value.Length + "\"".Length; } length += NewLine.Length; foreach (string headerName in this.PartHeaders) { length += headerName.Length; length += ": ".Length; length += this.PartHeaders[headerName].Length; length += NewLine.Length; } length += NewLine.Length; length += this.Content.Length; return length; } } /// /// Creates a part that represents a simple form field. /// /// The name of the form field. /// The value. /// The constructed part. public static MultipartPostPart CreateFormPart(string name, string value) { Requires.NotNullOrEmpty(name, "name"); Requires.NotNull(value, "value"); var part = new MultipartPostPart("form-data"); try { part.ContentAttributes["name"] = name; part.Content = new MemoryStream(Encoding.UTF8.GetBytes(value)); return part; } catch { part.Dispose(); throw; } } /// /// Creates a part that represents a file attachment. /// /// The name of the form field. /// The path to the file to send. /// Type of the content in HTTP Content-Type format. /// The constructed part. public static MultipartPostPart CreateFormFilePart(string name, string filePath, string contentType) { Requires.NotNullOrEmpty(name, "name"); Requires.NotNullOrEmpty(filePath, "filePath"); Requires.NotNullOrEmpty(contentType, "contentType"); string fileName = Path.GetFileName(filePath); var fileStream = File.OpenRead(filePath); try { return CreateFormFilePart(name, fileName, contentType, fileStream); } catch { fileStream.Dispose(); throw; } } /// /// Creates a part that represents a file attachment. /// /// The name of the form field. /// Name of the file as the server should see it. /// Type of the content in HTTP Content-Type format. /// The content of the file. /// The constructed part. public static MultipartPostPart CreateFormFilePart(string name, string fileName, string contentType, Stream content) { Requires.NotNullOrEmpty(name, "name"); Requires.NotNullOrEmpty(fileName, "fileName"); Requires.NotNullOrEmpty(contentType, "contentType"); Requires.NotNull(content, "content"); var part = new MultipartPostPart("file"); try { part.ContentAttributes["name"] = name; part.ContentAttributes["filename"] = fileName; part.PartHeaders[HttpRequestHeader.ContentType] = contentType; if (!contentType.StartsWith("text/", StringComparison.Ordinal)) { part.PartHeaders["Content-Transfer-Encoding"] = "binary"; } part.Content = content; return part; } catch { part.Dispose(); throw; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Serializes the part to a stream. /// /// The stream writer. internal void Serialize(StreamWriter streamWriter) { // VERY IMPORTANT: any changes at all made to this must be kept in sync with the // Length property which calculates exactly how many bytes this method will write. streamWriter.NewLine = NewLine; streamWriter.Write("{0}: {1}", ContentDispositionHeader, this.ContentDisposition); foreach (var pair in this.ContentAttributes) { streamWriter.Write("; {0}=\"{1}\"", pair.Key, pair.Value); } streamWriter.WriteLine(); foreach (string headerName in this.PartHeaders) { streamWriter.WriteLine("{0}: {1}", headerName, this.PartHeaders[headerName]); } streamWriter.WriteLine(); streamWriter.Flush(); this.Content.CopyTo(streamWriter.BaseStream); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing) { this.Content.Dispose(); } } #if CONTRACTS_FULL /// /// Verifies conditions that should be true for any valid state of this object. /// [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void Invariant() { Contract.Invariant(!string.IsNullOrEmpty(this.ContentDisposition)); Contract.Invariant(this.PartHeaders != null); Contract.Invariant(this.ContentAttributes != null); } #endif } }