diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2009-09-18 00:05:32 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2009-09-18 00:05:32 -0700 |
commit | 25450f6c84ce9b889d1ed8d11cec624e42770783 (patch) | |
tree | b240dc316fc662c22c2bdd371714da775d74cf39 /src | |
parent | 913cb41912f16a412474ad42bfe2f2c4325c862d (diff) | |
download | DotNetOpenAuth-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.csproj | 1 | ||||
-rw-r--r-- | src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs | 99 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/Channel.cs | 10 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/MessagingUtilities.cs | 25 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/MultiPartPostPart.cs | 5 |
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; } |