diff options
Diffstat (limited to 'src')
5 files changed, 57 insertions, 105 deletions
diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs index 26ce4cd..f445f01 100644 --- a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs @@ -11,8 +11,10 @@ namespace DotNetOpenAuth.Test.Messaging using System.Collections.Specialized; using System.IO; using System.Net; + using System.Text.RegularExpressions; using System.Web; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Test.Mocks; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -139,5 +141,33 @@ namespace DotNetOpenAuth.Test.Messaging Assert.AreEqual("%C2%80", MessagingUtilities.EscapeUriDataStringRfc3986("\u0080")); Assert.AreEqual("%E3%80%81", MessagingUtilities.EscapeUriDataStringRfc3986("\u3001")); } + + /// <summary> + /// Verifies the overall format of the multipart POST is correct. + /// </summary> + [TestMethod] + public void PostMultipart() { + var httpHandler = new TestWebRequestHandler(); + bool callbackTriggered = false; + httpHandler.Callback = req => { + Match m = Regex.Match(req.ContentType, "multipart/form-data; boundary=(.+)"); + Assert.IsTrue(m.Success, "Content-Type HTTP header not set correctly."); + string boundary = m.Groups[1].Value; + string expectedEntity = "--{0}\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\nb\r\n--{0}--\r\n"; + expectedEntity = string.Format(expectedEntity, boundary); + string actualEntity = httpHandler.RequestEntityAsString; + Assert.AreEqual(expectedEntity, actualEntity); + callbackTriggered = true; + Assert.AreEqual(req.ContentLength, actualEntity.Length); + IncomingWebResponse resp = new CachedDirectWebResponse(); + return resp; + }; + var request = (HttpWebRequest)WebRequest.Create("http://someserver"); + var parts = new[] { + MultipartPostPart.CreateFormPart("a", "b"), + }; + request.PostMultipart(httpHandler, parts); + Assert.IsTrue(callbackTriggered); + } } } diff --git a/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs b/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs deleted file mode 100644 index f87ae59..0000000 --- a/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -//----------------------------------------------------------------------- -// <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.Test/Messaging/MultipartPostPartTests.cs b/src/DotNetOpenAuth.Test/Messaging/MultipartPostPartTests.cs index f87ae59..57614ba 100644 --- a/src/DotNetOpenAuth.Test/Messaging/MultipartPostPartTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/MultipartPostPartTests.cs @@ -38,6 +38,15 @@ namespace DotNetOpenAuth.Test.Messaging { } /// <summary> + /// Verifies file multiparts identify themselves as files and not merely form-data. + /// </summary> + [TestMethod] + public void FilePartAsFile() { + var part = MultipartPostPart.CreateFormFilePart("somename", "somefile", "plain/text", new MemoryStream()); + Assert.AreEqual("file", part.ContentDisposition); + } + + /// <summary> /// Verifies MultiPartPost sends the right number of bytes. /// </summary> [TestMethod] diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index e545f8d..1c50c77 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -141,7 +141,7 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Sends an multipart HTTP POST request (useful for posting files). + /// Sends a multipart HTTP POST request (useful for posting files). /// </summary> /// <param name="request">The HTTP request.</param> /// <param name="requestHandler">The request handler.</param> @@ -193,13 +193,19 @@ namespace DotNetOpenAuth.Messaging { Contract.Requires<ArgumentNullException>(parts != null); Reporting.RecordFeatureUse("MessagingUtilities.PostMultipart"); + parts = parts.CacheGeneratedResults(); string boundary = Guid.NewGuid().ToString(); + string initialPartLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "--{0}\r\n", boundary); string partLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}\r\n", boundary); string finalTrailingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}--\r\n", boundary); request.Method = "POST"; request.ContentType = "multipart/form-data; boundary=" + boundary; - request.ContentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length; + long contentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length; + if (parts.Any()) { + contentLength -= 2; // the initial part leading boundary has no leading \r\n + } + request.ContentLength = contentLength; // Setting the content-encoding to "utf-8" causes Google to reply // with a 415 UnsupportedMediaType. But adding it doesn't buy us @@ -209,8 +215,10 @@ namespace DotNetOpenAuth.Messaging { var requestStream = requestHandler.GetRequestStream(request); try { StreamWriter writer = new StreamWriter(requestStream, Channel.PostEntityEncoding); + bool firstPart = true; foreach (var part in parts) { - writer.Write(partLeadingBoundary); + writer.Write(firstPart ? initialPartLeadingBoundary : partLeadingBoundary); + firstPart = false; part.Serialize(writer); part.Dispose(); } diff --git a/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs b/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs index 7ef89a4..f9a5988 100644 --- a/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs +++ b/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs @@ -116,6 +116,10 @@ namespace DotNetOpenAuth.Messaging { /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> /// <returns>The constructed part.</returns> public static MultipartPostPart CreateFormFilePart(string name, string filePath, string contentType) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(filePath)); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(contentType)); + string fileName = Path.GetFileName(filePath); return CreateFormFilePart(name, fileName, contentType, File.OpenRead(filePath)); } @@ -130,11 +134,11 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The constructed part.</returns> public static MultipartPostPart CreateFormFilePart(string name, string fileName, string contentType, Stream content) { Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); - Contract.Requires<ArgumentException>(fileName != null); - Contract.Requires<ArgumentException>(contentType != null); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(fileName)); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(contentType)); Contract.Requires<ArgumentException>(content != null); - var part = new MultipartPostPart("form-data"); + var part = new MultipartPostPart("file"); part.ContentAttributes["name"] = name; part.ContentAttributes["filename"] = fileName; part.PartHeaders[HttpRequestHeader.ContentType] = contentType; |