471 lines
19 KiB
C#
471 lines
19 KiB
C#
// 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;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Http.Features
|
|
{
|
|
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<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
|
|
context.Request.Body = new NonSeekableReadStream(formContent);
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
|
context.Features.Set<IFormFeature>(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<IFormFeature>();
|
|
Assert.NotNull(formFeature);
|
|
Assert.NotNull(formFeature.Form);
|
|
Assert.Same(formFeature.Form, formCollection);
|
|
|
|
// Cleanup
|
|
await responseFeature.CompleteAsync();
|
|
}
|
|
|
|
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 MultipartFormEnd = "--WebKitFormBoundary5pDRpGheQXaM8k3T--\r\n";
|
|
|
|
private const string MultipartFormField = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
|
"Content-Disposition: form-data; name=\"description\"\r\n" +
|
|
"\r\n" +
|
|
"Foo\r\n";
|
|
|
|
private const string MultipartFormFile = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
|
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
|
|
"Content-Type: text/html\r\n" +
|
|
"\r\n" +
|
|
"<html><body>Hello World</body></html>\r\n";
|
|
|
|
private const string MultipartFormEncodedFilename = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
|
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"; filename*=utf-8\'\'t%c3%a9mp.html\r\n" +
|
|
"Content-Type: text/html\r\n" +
|
|
"\r\n" +
|
|
"<html><body>Hello World</body></html>\r\n";
|
|
|
|
private const string MultipartFormWithField =
|
|
MultipartFormField +
|
|
MultipartFormEnd;
|
|
|
|
private const string MultipartFormWithFile =
|
|
MultipartFormFile +
|
|
MultipartFormEnd;
|
|
|
|
private const string MultipartFormWithFieldAndFile =
|
|
MultipartFormField +
|
|
MultipartFormFile +
|
|
MultipartFormEnd;
|
|
|
|
private const string MultipartFormWithEncodedFilename =
|
|
MultipartFormEncodedFilename +
|
|
MultipartFormEnd;
|
|
|
|
[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<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent);
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var formCollection = context.Request.Form;
|
|
|
|
Assert.NotNull(formCollection);
|
|
|
|
// Cached
|
|
formFeature = context.Features.Get<IFormFeature>();
|
|
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<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent);
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var formCollection = context.Request.Form;
|
|
|
|
Assert.NotNull(formCollection);
|
|
|
|
// Cached
|
|
formFeature = context.Features.Get<IFormFeature>();
|
|
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<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent);
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var formCollection = await context.Request.ReadFormAsync();
|
|
|
|
Assert.NotNull(formCollection);
|
|
|
|
// Cached
|
|
formFeature = context.Features.Get<IFormFeature>();
|
|
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, "<html><body>Hello World</body></html>");
|
|
}
|
|
|
|
await responseFeature.CompleteAsync();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public async Task ReadFormAsync_MultipartWithEncodedFilename_ReturnsParsedFormCollection(bool bufferRequest)
|
|
{
|
|
var formContent = Encoding.UTF8.GetBytes(MultipartFormWithEncodedFilename);
|
|
var context = new DefaultHttpContext();
|
|
var responseFeature = new FakeResponseFeature();
|
|
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent);
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var formCollection = await context.Request.ReadFormAsync();
|
|
|
|
Assert.NotNull(formCollection);
|
|
|
|
// Cached
|
|
formFeature = context.Features.Get<IFormFeature>();
|
|
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("t\u00e9mp.html", file.FileName);
|
|
Assert.Equal("text/html", file.ContentType);
|
|
Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""; filename*=utf-8''t%c3%a9mp.html", file.ContentDisposition);
|
|
var body = file.OpenReadStream();
|
|
using (var reader = new StreamReader(body))
|
|
{
|
|
Assert.True(body.CanSeek);
|
|
var content = reader.ReadToEnd();
|
|
Assert.Equal(content, "<html><body>Hello World</body></html>");
|
|
}
|
|
|
|
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<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent);
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var formCollection = await context.Request.ReadFormAsync();
|
|
|
|
Assert.NotNull(formCollection);
|
|
|
|
// Cached
|
|
formFeature = context.Features.Get<IFormFeature>();
|
|
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, "<html><body>Hello World</body></html>");
|
|
}
|
|
|
|
await responseFeature.CompleteAsync();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
|
|
{
|
|
var formContent = new List<byte>();
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
|
|
|
|
var context = new DefaultHttpContext();
|
|
var responseFeature = new FakeResponseFeature();
|
|
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
|
|
Assert.Equal(exception.Message, "Form value count limit 2 exceeded.");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public async Task ReadFormAsync_ValueCountLimitExceededWithFiles_Throw(bool bufferRequest)
|
|
{
|
|
var formContent = new List<byte>();
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
|
|
formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
|
|
|
|
|
|
var context = new DefaultHttpContext();
|
|
var responseFeature = new FakeResponseFeature();
|
|
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
|
|
Assert.Equal(exception.Message, "Form value count limit 2 exceeded.");
|
|
}
|
|
|
|
[Theory]
|
|
// FileBufferingReadStream transitions to disk storage after 30kb, and stops pooling buffers at 1mb.
|
|
[InlineData(true, 1024)]
|
|
[InlineData(false, 1024)]
|
|
[InlineData(true, 40 * 1024)]
|
|
[InlineData(false, 40 * 1024)]
|
|
[InlineData(true, 4 * 1024 * 1024)]
|
|
[InlineData(false, 4 * 1024 * 1024)]
|
|
public async Task ReadFormAsync_MultipartWithFieldAndMediumFile_ReturnsParsedFormCollection(bool bufferRequest, int fileSize)
|
|
{
|
|
var fileContents = CreateFile(fileSize);
|
|
var formContent = CreateMultipartWithFormAndFile(fileContents);
|
|
var context = new DefaultHttpContext();
|
|
var responseFeature = new FakeResponseFeature();
|
|
context.Features.Set<IHttpResponseFeature>(responseFeature);
|
|
context.Request.ContentType = MultipartContentType;
|
|
context.Request.Body = new NonSeekableReadStream(formContent);
|
|
|
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
|
context.Features.Set<IFormFeature>(formFeature);
|
|
|
|
var formCollection = await context.Request.ReadFormAsync();
|
|
|
|
Assert.NotNull(formCollection);
|
|
|
|
// Cached
|
|
formFeature = context.Features.Get<IFormFeature>();
|
|
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);
|
|
using (var body = file.OpenReadStream())
|
|
{
|
|
Assert.True(body.CanSeek);
|
|
CompareStreams(fileContents, body);
|
|
}
|
|
|
|
await responseFeature.CompleteAsync();
|
|
}
|
|
|
|
private Stream CreateFile(int size)
|
|
{
|
|
var stream = new MemoryStream(size);
|
|
var bytes = Encoding.ASCII.GetBytes("HelloWorld_ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz,0123456789;");
|
|
int written = 0;
|
|
while (written < size)
|
|
{
|
|
var toWrite = Math.Min(size - written, bytes.Length);
|
|
stream.Write(bytes, 0, toWrite);
|
|
written += toWrite;
|
|
}
|
|
stream.Position = 0;
|
|
return stream;
|
|
}
|
|
|
|
private Stream CreateMultipartWithFormAndFile(Stream fileContents)
|
|
{
|
|
var stream = new MemoryStream();
|
|
var header =
|
|
MultipartFormField +
|
|
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
|
|
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
|
|
"Content-Type: text/html\r\n" +
|
|
"\r\n";
|
|
var footer =
|
|
"\r\n--WebKitFormBoundary5pDRpGheQXaM8k3T--";
|
|
|
|
var bytes = Encoding.ASCII.GetBytes(header);
|
|
stream.Write(bytes, 0, bytes.Length);
|
|
|
|
fileContents.CopyTo(stream);
|
|
fileContents.Position = 0;
|
|
|
|
bytes = Encoding.ASCII.GetBytes(footer);
|
|
stream.Write(bytes, 0, bytes.Length);
|
|
stream.Position = 0;
|
|
return stream;
|
|
}
|
|
|
|
private void CompareStreams(Stream streamA, Stream streamB)
|
|
{
|
|
Assert.Equal(streamA.Length, streamB.Length);
|
|
byte[] bytesA = new byte[1024], bytesB = new byte[1024];
|
|
var readA = streamA.Read(bytesA, 0, bytesA.Length);
|
|
var readB = streamB.Read(bytesB, 0, bytesB.Length);
|
|
Assert.Equal(readA, readB);
|
|
var loops = 0;
|
|
while (readA > 0)
|
|
{
|
|
for (int i = 0; i < readA; i++)
|
|
{
|
|
if (bytesA[i] != bytesB[i])
|
|
{
|
|
throw new Exception($"Value mismatch at loop {loops}, index {i}; A:{bytesA[i]}, B:{bytesB[i]}");
|
|
}
|
|
}
|
|
|
|
readA = streamA.Read(bytesA, 0, bytesA.Length);
|
|
readB = streamB.Read(bytesB, 0, bytesB.Length);
|
|
Assert.Equal(readA, readB);
|
|
loops++;
|
|
}
|
|
}
|
|
}
|
|
}
|