summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-09-18 00:05:32 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2009-09-18 00:05:32 -0700
commit25450f6c84ce9b889d1ed8d11cec624e42770783 (patch)
treeb240dc316fc662c22c2bdd371714da775d74cf39 /src
parent913cb41912f16a412474ad42bfe2f2c4325c862d (diff)
downloadDotNetOpenAuth-25450f6c84ce9b889d1ed8d11cec624e42770783.zip
DotNetOpenAuth-25450f6c84ce9b889d1ed8d11cec624e42770783.tar.gz
DotNetOpenAuth-25450f6c84ce9b889d1ed8d11cec624e42770783.tar.bz2
Added multi-part POST unit tests.
Still doesn't actually work against IIS though. Don't yet know why.
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj1
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs99
-rw-r--r--src/DotNetOpenAuth/Messaging/Channel.cs10
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs25
-rw-r--r--src/DotNetOpenAuth/Messaging/MultiPartPostPart.cs5
5 files changed, 130 insertions, 10 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
index 2142db5..f21e4dc 100644
--- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
+++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
@@ -137,6 +137,7 @@
<Compile Include="Messaging\EnumerableCacheTests.cs" />
<Compile Include="Messaging\ErrorUtilitiesTests.cs" />
<Compile Include="Messaging\MessageSerializerTests.cs" />
+ <Compile Include="Messaging\MultiPartPostPartTests.cs" />
<Compile Include="Messaging\Reflection\MessageDescriptionTests.cs" />
<Compile Include="Messaging\Reflection\MessageDictionaryTests.cs" />
<Compile Include="Messaging\MessagingTestBase.cs" />
diff --git a/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs b/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs
new file mode 100644
index 0000000..95404a4
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs
@@ -0,0 +1,99 @@
+//-----------------------------------------------------------------------
+// <copyright file="MultiPartPostPartTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging {
+ using System.CodeDom.Compiler;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.IO;
+ using System.Net;
+ using DotNetOpenAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MultiPartPostPartTests : TestBase {
+ /// <summary>
+ /// Verifies that the Length property matches the length actually serialized.
+ /// </summary>
+ [TestMethod]
+ public void FormDataSerializeMatchesLength() {
+ var part = MultiPartPostPart.CreateFormPart("a", "b");
+ VerifyLength(part);
+ }
+
+ /// <summary>
+ /// Verifies that the length property matches the length actually serialized.
+ /// </summary>
+ [TestMethod]
+ public void FileSerializeMatchesLength() {
+ using (TempFileCollection tfc = new TempFileCollection()) {
+ string file = tfc.AddExtension(".txt");
+ File.WriteAllText(file, "sometext");
+ var part = MultiPartPostPart.CreateFormFilePart("someformname", file, "text/plain");
+ VerifyLength(part);
+ }
+ }
+
+ /// <summary>
+ /// Verifies MultiPartPost sends the right number of bytes.
+ /// </summary>
+ [TestMethod]
+ public void MultiPartPostAscii() {
+ using (TempFileCollection tfc = new TempFileCollection()) {
+ string file = tfc.AddExtension("txt");
+ File.WriteAllText(file, "sometext");
+ this.VerifyFullPost(new List<MultiPartPostPart> {
+ MultiPartPostPart.CreateFormPart("a", "b"),
+ MultiPartPostPart.CreateFormFilePart("SomeFormField", file, "text/plain"),
+ });
+ }
+ }
+
+ /// <summary>
+ /// Verifies MultiPartPost sends the right number of bytes.
+ /// </summary>
+ [TestMethod]
+ public void MultiPartPostMultiByteCharacters() {
+ using (TempFileCollection tfc = new TempFileCollection()) {
+ string file = tfc.AddExtension("txt");
+ File.WriteAllText(file, "\x1020\x818");
+ this.VerifyFullPost(new List<MultiPartPostPart> {
+ MultiPartPostPart.CreateFormPart("a", "\x987"),
+ MultiPartPostPart.CreateFormFilePart("SomeFormField", file, "text/plain"),
+ });
+ }
+ }
+
+ private static void VerifyLength(MultiPartPostPart part) {
+ Contract.Requires(part != null);
+
+ var expectedLength = part.Length;
+ var ms = new MemoryStream();
+ var sw = new StreamWriter(ms);
+ part.Serialize(sw);
+ sw.Flush();
+ var actualLength = ms.Length;
+ Assert.AreEqual(expectedLength, actualLength);
+ }
+
+ private void VerifyFullPost(List<MultiPartPostPart> parts) {
+ var request = (HttpWebRequest)WebRequest.Create("http://localhost");
+ var handler = new Mocks.TestWebRequestHandler();
+ bool posted = false;
+ handler.Callback = req => {
+ foreach (string header in req.Headers) {
+ TestContext.WriteLine("{0}: {1}", header, req.Headers[header]);
+ }
+ TestContext.WriteLine(handler.RequestEntityAsString);
+ Assert.AreEqual(req.ContentLength, handler.RequestEntityStream.Length);
+ posted = true;
+ return null;
+ };
+ request.PostMultipart(handler, parts);
+ Assert.IsTrue(posted, "HTTP POST never sent.");
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs
index e2c1301..88ac338 100644
--- a/src/DotNetOpenAuth/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth/Messaging/Channel.cs
@@ -28,15 +28,15 @@ namespace DotNetOpenAuth.Messaging {
[ContractClass(typeof(ChannelContract))]
public abstract class Channel : IDisposable {
/// <summary>
- /// The content-type used on HTTP POST requests where the POST entity is a
- /// URL-encoded series of key=value pairs.
+ /// The encoding to use when writing out POST entity strings.
/// </summary>
- protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded";
+ internal static readonly Encoding PostEntityEncoding = new UTF8Encoding(false);
/// <summary>
- /// The encoding to use when writing out POST entity strings.
+ /// The content-type used on HTTP POST requests where the POST entity is a
+ /// URL-encoded series of key=value pairs.
/// </summary>
- private static readonly Encoding PostEntityEncoding = new UTF8Encoding(false);
+ protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded";
/// <summary>
/// The maximum allowable size for a 301 Redirect response before we send
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index ee84496..5f6ad5d 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -145,10 +145,13 @@ namespace DotNetOpenAuth.Messaging {
/// Sends an multipart HTTP POST request (useful for posting files).
/// </summary>
/// <param name="request">The HTTP request.</param>
+ /// <param name="requestHandler">The request handler.</param>
/// <param name="parts">The parts to include in the POST entity.</param>
/// <returns>The HTTP response.</returns>
- internal static HttpWebResponse PostMultipart(this HttpWebRequest request, IEnumerable<MultiPartPostPart> parts) {
+ public static IncomingWebResponse PostMultipart(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultiPartPostPart> parts) {
ErrorUtilities.VerifyArgumentNotNull(request, "request");
+ ErrorUtilities.VerifyArgumentNotNull(requestHandler, "requestHandler");
+ ErrorUtilities.VerifyArgumentNotNull(parts, "parts");
string boundary = Guid.NewGuid().ToString();
string partLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}\r\n", boundary);
@@ -158,17 +161,31 @@ namespace DotNetOpenAuth.Messaging {
request.ContentType = "multipart/form-data;boundary=" + boundary;
request.ContentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length;
- using (var requestStream = request.GetRequestStream()) {
- StreamWriter writer = new StreamWriter(requestStream);
+ // Setting the content-encoding to "utf-8" causes Google to reply
+ // with a 415 UnsupportedMediaType. But adding it doesn't buy us
+ // anything specific, so we disable it until we know how to get it right.
+ ////request.Headers[HttpRequestHeader.ContentEncoding] = Channel.PostEntityEncoding.WebName;
+
+ var requestStream = requestHandler.GetRequestStream(request);
+ try {
+ StreamWriter writer = new StreamWriter(requestStream, Channel.PostEntityEncoding);
foreach (var part in parts) {
writer.Write(partLeadingBoundary);
part.Serialize(writer);
}
writer.Write(finalTrailingBoundary);
+ writer.Flush();
+ } finally {
+ // We need to be sure to close the request stream...
+ // unless it is a MemoryStream, which is a clue that we're in
+ // a mock stream situation and closing it would preclude reading it later.
+ if (!(requestStream is MemoryStream)) {
+ requestStream.Dispose();
+ }
}
- return (HttpWebResponse)request.GetResponse();
+ return requestHandler.GetResponse(request);
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/MultiPartPostPart.cs b/src/DotNetOpenAuth/Messaging/MultiPartPostPart.cs
index 741d7d4..6cbde78 100644
--- a/src/DotNetOpenAuth/Messaging/MultiPartPostPart.cs
+++ b/src/DotNetOpenAuth/Messaging/MultiPartPostPart.cs
@@ -16,7 +16,7 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Represents a single part in a HTTP multipart POST request.
/// </summary>
- internal class MultiPartPostPart {
+ public class MultiPartPostPart {
/// <summary>
/// The "Content-Disposition" string.
/// </summary>
@@ -138,6 +138,9 @@ namespace DotNetOpenAuth.Messaging {
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;
}