// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Xunit; namespace Microsoft.AspNetCore.Http.Features.Internal { public class FormFeatureTests { [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadFormAsync_SimpleData_ReturnsParsedFormCollection(bool bufferRequest) { var formContent = Encoding.UTF8.GetBytes("foo=bar&baz=2"); var context = new DefaultHttpContext(); var responseFeature = new FakeResponseFeature(); context.Features.Set(responseFeature); context.Request.ContentType = "application/x-www-form-urlencoded; charset=utf-8"; context.Request.Body = new NonSeekableReadStream(formContent); if (bufferRequest) { context.Request.EnableRewind(); } // Not cached yet var formFeature = context.Features.Get(); Assert.Null(formFeature); var formCollection = await context.Request.ReadFormAsync(); Assert.Equal("bar", formCollection["foo"]); Assert.Equal("2", formCollection["baz"]); Assert.Equal(bufferRequest, context.Request.Body.CanSeek); if (bufferRequest) { Assert.Equal(0, context.Request.Body.Position); } // Cached formFeature = context.Features.Get(); Assert.NotNull(formFeature); Assert.NotNull(formFeature.Form); Assert.Same(formFeature.Form, formCollection); // Cleanup await responseFeature.CompleteAsync(); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadFormAsync_EmptyKeyAtEndAllowed(bool bufferRequest) { var formContent = Encoding.UTF8.GetBytes("=bar"); Stream body = new MemoryStream(formContent); if (!bufferRequest) { body = new NonSeekableReadStream(body); } var formCollection = await FormReader.ReadFormAsync(body); Assert.Equal("bar", formCollection[""].FirstOrDefault()); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadFormAsync_EmptyKeyWithAdditionalEntryAllowed(bool bufferRequest) { var formContent = Encoding.UTF8.GetBytes("=bar&baz=2"); Stream body = new MemoryStream(formContent); if (!bufferRequest) { body = new NonSeekableReadStream(body); } var formCollection = await FormReader.ReadFormAsync(body); Assert.Equal("bar", formCollection[""].FirstOrDefault()); Assert.Equal("2", formCollection["baz"].FirstOrDefault()); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadFormAsync_EmptyValuedAtEndAllowed(bool bufferRequest) { // Arrange var formContent = Encoding.UTF8.GetBytes("foo="); Stream body = new MemoryStream(formContent); if (!bufferRequest) { body = new NonSeekableReadStream(body); } var formCollection = await FormReader.ReadFormAsync(body); // Assert Assert.Equal("", formCollection["foo"].FirstOrDefault()); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadFormAsync_EmptyValuedWithAdditionalEntryAllowed(bool bufferRequest) { // Arrange var formContent = Encoding.UTF8.GetBytes("foo=&baz=2"); Stream body = new MemoryStream(formContent); if (!bufferRequest) { body = new NonSeekableReadStream(body); } var formCollection = await FormReader.ReadFormAsync(body); // Assert Assert.Equal("", formCollection["foo"].FirstOrDefault()); Assert.Equal("2", formCollection["baz"].FirstOrDefault()); } private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T"; private const string EmptyMultipartForm = "--WebKitFormBoundary5pDRpGheQXaM8k3T--"; // Note that CRLF (\r\n) is required. You can't use multi-line C# strings here because the line breaks on Linux are just LF. private const string MultipartFormWithField = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" + "Content-Disposition: form-data; name=\"description\"\r\n" + "\r\n" + "Foo\r\n" + "--WebKitFormBoundary5pDRpGheQXaM8k3T--"; private const string MultipartFormWithFile = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" + "Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "Hello World\r\n" + "--WebKitFormBoundary5pDRpGheQXaM8k3T--"; private const string MultipartFormWithFieldAndFile = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" + "Content-Disposition: form-data; name=\"description\"\r\n" + "\r\n" + "Foo\r\n" + "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" + "Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "Hello World\r\n" + "--WebKitFormBoundary5pDRpGheQXaM8k3T--"; [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadForm_EmptyMultipart_ReturnsParsedFormCollection(bool bufferRequest) { var formContent = Encoding.UTF8.GetBytes(EmptyMultipartForm); var context = new DefaultHttpContext(); var responseFeature = new FakeResponseFeature(); context.Features.Set(responseFeature); context.Request.ContentType = MultipartContentType; context.Request.Body = new NonSeekableReadStream(formContent); if (bufferRequest) { context.Request.EnableRewind(); } // Not cached yet var formFeature = context.Features.Get(); Assert.Null(formFeature); var formCollection = context.Request.Form; Assert.NotNull(formCollection); // Cached formFeature = context.Features.Get(); Assert.NotNull(formFeature); Assert.NotNull(formFeature.Form); Assert.Same(formCollection, formFeature.Form); Assert.Same(formCollection, await context.Request.ReadFormAsync()); // Content Assert.Equal(0, formCollection.Count); Assert.NotNull(formCollection.Files); Assert.Equal(0, formCollection.Files.Count); // Cleanup await responseFeature.CompleteAsync(); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadForm_MultipartWithField_ReturnsParsedFormCollection(bool bufferRequest) { var formContent = Encoding.UTF8.GetBytes(MultipartFormWithField); var context = new DefaultHttpContext(); var responseFeature = new FakeResponseFeature(); context.Features.Set(responseFeature); context.Request.ContentType = MultipartContentType; context.Request.Body = new NonSeekableReadStream(formContent); if (bufferRequest) { context.Request.EnableRewind(); } // Not cached yet var formFeature = context.Features.Get(); Assert.Null(formFeature); var formCollection = context.Request.Form; Assert.NotNull(formCollection); // Cached formFeature = context.Features.Get(); Assert.NotNull(formFeature); Assert.NotNull(formFeature.Form); Assert.Same(formCollection, formFeature.Form); Assert.Same(formCollection, await context.Request.ReadFormAsync()); // Content Assert.Equal(1, formCollection.Count); Assert.Equal("Foo", formCollection["description"]); Assert.NotNull(formCollection.Files); Assert.Equal(0, formCollection.Files.Count); // Cleanup await responseFeature.CompleteAsync(); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadFormAsync_MultipartWithFile_ReturnsParsedFormCollection(bool bufferRequest) { var formContent = Encoding.UTF8.GetBytes(MultipartFormWithFile); var context = new DefaultHttpContext(); var responseFeature = new FakeResponseFeature(); context.Features.Set(responseFeature); context.Request.ContentType = MultipartContentType; context.Request.Body = new NonSeekableReadStream(formContent); if (bufferRequest) { context.Request.EnableRewind(); } // Not cached yet var formFeature = context.Features.Get(); Assert.Null(formFeature); var formCollection = await context.Request.ReadFormAsync(); Assert.NotNull(formCollection); // Cached formFeature = context.Features.Get(); Assert.NotNull(formFeature); Assert.NotNull(formFeature.Form); Assert.Same(formFeature.Form, formCollection); Assert.Same(formCollection, context.Request.Form); // Content Assert.Equal(0, formCollection.Count); Assert.NotNull(formCollection.Files); Assert.Equal(1, formCollection.Files.Count); var file = formCollection.Files["myfile1"]; Assert.Equal("myfile1", file.Name); Assert.Equal("temp.html", file.FileName); Assert.Equal("text/html", file.ContentType); Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""", file.ContentDisposition); var body = file.OpenReadStream(); using (var reader = new StreamReader(body)) { Assert.True(body.CanSeek); var content = reader.ReadToEnd(); Assert.Equal(content, "Hello World"); } await responseFeature.CompleteAsync(); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReadFormAsync_MultipartWithFieldAndFile_ReturnsParsedFormCollection(bool bufferRequest) { var formContent = Encoding.UTF8.GetBytes(MultipartFormWithFieldAndFile); var context = new DefaultHttpContext(); var responseFeature = new FakeResponseFeature(); context.Features.Set(responseFeature); context.Request.ContentType = MultipartContentType; context.Request.Body = new NonSeekableReadStream(formContent); if (bufferRequest) { context.Request.EnableRewind(); } // Not cached yet var formFeature = context.Features.Get(); Assert.Null(formFeature); var formCollection = await context.Request.ReadFormAsync(); Assert.NotNull(formCollection); // Cached formFeature = context.Features.Get(); Assert.NotNull(formFeature); Assert.NotNull(formFeature.Form); Assert.Same(formFeature.Form, formCollection); Assert.Same(formCollection, context.Request.Form); // Content Assert.Equal(1, formCollection.Count); Assert.Equal("Foo", formCollection["description"]); Assert.NotNull(formCollection.Files); Assert.Equal(1, formCollection.Files.Count); var file = formCollection.Files["myfile1"]; Assert.Equal("text/html", file.ContentType); Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""", file.ContentDisposition); var body = file.OpenReadStream(); using (var reader = new StreamReader(body)) { Assert.True(body.CanSeek); var content = reader.ReadToEnd(); Assert.Equal(content, "Hello World"); } await responseFeature.CompleteAsync(); } } }