//-----------------------------------------------------------------------
//
// 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
}
}