Respond to RangeHelper refactor (#6348)

Respond to https://github.com/aspnet/StaticFiles/pull/200
This commit is contained in:
Jass Bagga 2017-06-06 11:22:35 -07:00 committed by GitHub
parent f1dd475eae
commit 7ffd88757d
7 changed files with 355 additions and 104 deletions

View File

@ -31,8 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Unspecified, Unspecified,
NotModified, NotModified,
ShouldProcess, ShouldProcess,
PreconditionFailed, PreconditionFailed
IgnoreRangeRequest
} }
protected ILogger Logger { get; } protected ILogger Logger { get; }
@ -61,15 +60,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var httpRequestHeaders = request.GetTypedHeaders(); var httpRequestHeaders = request.GetTypedHeaders();
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
var httpResponseHeaders = response.GetTypedHeaders(); var httpResponseHeaders = response.GetTypedHeaders();
if (fileLength.HasValue)
{
SetAcceptRangeHeader(context);
// Assuming the request is not a range request, the Content-Length header is set to the length of the entire file.
// If the request is a valid range request, this header is overwritten with the length of the range as part of the
// range processing (see method SetContentLength).
response.ContentLength = fileLength.Value;
}
if (lastModified.HasValue) if (lastModified.HasValue)
{ {
httpResponseHeaders.LastModified = lastModified; httpResponseHeaders.LastModified = lastModified;
@ -80,24 +70,35 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
var serveBody = !HttpMethods.IsHead(request.Method); var serveBody = !HttpMethods.IsHead(request.Method);
if (HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method)) var preconditionState = GetPreconditionState(context, httpRequestHeaders, lastModified, etag);
if (preconditionState == PreconditionState.NotModified)
{ {
var preconditionState = GetPreconditionState(context, httpRequestHeaders, lastModified, etag); serveBody = false;
if (request.Headers.ContainsKey(HeaderNames.Range) && response.StatusCode = StatusCodes.Status304NotModified;
(preconditionState == PreconditionState.Unspecified || }
preconditionState == PreconditionState.ShouldProcess)) else if (preconditionState == PreconditionState.PreconditionFailed)
{
serveBody = false;
response.StatusCode = StatusCodes.Status412PreconditionFailed;
}
if (fileLength.HasValue)
{
SetAcceptRangeHeader(context);
// Assuming the request is not a range request, the Content-Length header is set to the length of the entire file.
// If the request is a valid range request, this header is overwritten with the length of the range as part of the
// range processing (see method SetContentLength).
response.ContentLength = fileLength.Value;
if (HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))
{ {
return SetRangeHeaders(context, httpRequestHeaders, fileLength, lastModified, etag); if ((preconditionState == PreconditionState.Unspecified ||
} preconditionState == PreconditionState.ShouldProcess))
if (preconditionState == PreconditionState.NotModified) {
{ if (IfRangeValid(context, httpRequestHeaders, lastModified, etag))
serveBody = false; {
response.StatusCode = StatusCodes.Status304NotModified; return SetRangeHeaders(context, httpRequestHeaders, fileLength);
} }
else if (preconditionState == PreconditionState.PreconditionFailed) }
{
serveBody = false;
response.StatusCode = StatusCodes.Status412PreconditionFailed;
} }
} }
@ -155,6 +156,37 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return PreconditionState.Unspecified; return PreconditionState.Unspecified;
} }
internal static bool IfRangeValid(
ActionContext context,
RequestHeaders httpRequestHeaders,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue etag = null)
{
// 14.27 If-Range
var ifRange = httpRequestHeaders.IfRange;
if (ifRange != null)
{
// If the validator given in the If-Range header field matches the
// current validator for the selected representation of the target
// resource, then the server SHOULD process the Range header field as
// requested. If the validator does not match, the server MUST ignore
// the Range header field.
if (ifRange.LastModified.HasValue)
{
if (lastModified.HasValue && lastModified > ifRange.LastModified)
{
return false;
}
}
else if (etag != null && ifRange.EntityTag != null && !ifRange.EntityTag.Compare(etag, useStrongComparison: true))
{
return false;
}
}
return true;
}
// Internal for testing // Internal for testing
internal static PreconditionState GetPreconditionState( internal static PreconditionState GetPreconditionState(
ActionContext context, ActionContext context,
@ -166,7 +198,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var ifNoneMatchState = PreconditionState.Unspecified; var ifNoneMatchState = PreconditionState.Unspecified;
var ifModifiedSinceState = PreconditionState.Unspecified; var ifModifiedSinceState = PreconditionState.Unspecified;
var ifUnmodifiedSinceState = PreconditionState.Unspecified; var ifUnmodifiedSinceState = PreconditionState.Unspecified;
var ifRangeState = PreconditionState.Unspecified;
// 14.24 If-Match // 14.24 If-Match
var ifMatch = httpRequestHeaders.IfMatch; var ifMatch = httpRequestHeaders.IfMatch;
@ -208,28 +239,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed; ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
} }
var ifRange = httpRequestHeaders.IfRange; var state = GetMaxPreconditionState(ifMatchState, ifNoneMatchState, ifModifiedSinceState, ifUnmodifiedSinceState);
if (ifRange != null)
{
// If the validator given in the If-Range header field matches the
// current validator for the selected representation of the target
// resource, then the server SHOULD process the Range header field as
// requested. If the validator does not match, the server MUST ignore
// the Range header field.
if (ifRange.LastModified.HasValue)
{
if (lastModified.HasValue && lastModified > ifRange.LastModified)
{
ifRangeState = PreconditionState.IgnoreRangeRequest;
}
}
else if (etag != null && ifRange.EntityTag != null && !ifRange.EntityTag.Compare(etag, useStrongComparison: true))
{
ifRangeState = PreconditionState.IgnoreRangeRequest;
}
}
var state = GetMaxPreconditionState(ifMatchState, ifNoneMatchState, ifModifiedSinceState, ifUnmodifiedSinceState, ifRangeState);
return state; return state;
} }
@ -250,16 +260,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static (RangeItemHeaderValue range, long rangeLength, bool serveBody) SetRangeHeaders( private static (RangeItemHeaderValue range, long rangeLength, bool serveBody) SetRangeHeaders(
ActionContext context, ActionContext context,
RequestHeaders httpRequestHeaders, RequestHeaders httpRequestHeaders,
long? fileLength, long? fileLength)
DateTimeOffset? lastModified = null,
EntityTagHeaderValue etag = null)
{ {
var response = context.HttpContext.Response; var response = context.HttpContext.Response;
var httpResponseHeaders = response.GetTypedHeaders(); var httpResponseHeaders = response.GetTypedHeaders();
// Checked for presence of Range header explicitly before calling this method. // Range may be null for empty range header, invalid ranges, parsing errors, multiple ranges
// Range may be null for parsing errors, multiple ranges and when the file length is missing. // and when the file length is zero.
var range = fileLength.HasValue ? ParseRange(context, httpRequestHeaders, fileLength.Value, lastModified, etag) : null; var (isRangeRequest, range) = RangeHelper.ParseRange(
context.HttpContext,
httpRequestHeaders,
fileLength.Value);
if (!isRangeRequest)
{
return (range: null, rangeLength: 0, serveBody: true);
}
if (range == null) if (range == null)
{ {
// 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable) // 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
@ -295,32 +312,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return length; return length;
} }
private static RangeItemHeaderValue ParseRange(
ActionContext context,
RequestHeaders httpRequestHeaders,
long fileLength,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue etag = null)
{
var httpContext = context.HttpContext;
var response = httpContext.Response;
var range = RangeHelper.ParseRange(httpContext, httpRequestHeaders, lastModified, etag);
if (range != null)
{
var normalizedRanges = RangeHelper.NormalizeRanges(range, fileLength);
if (normalizedRanges == null || normalizedRanges.Count == 0)
{
return null;
}
return normalizedRanges.Single();
}
return null;
}
protected static ILogger CreateLogger<T>(ILoggerFactory factory) protected static ILogger CreateLogger<T>(ILoggerFactory factory)
{ {
if (factory == null) if (factory == null)

View File

@ -233,8 +233,47 @@ namespace Microsoft.AspNetCore.Mvc
[Theory] [Theory]
[InlineData("0-5")] [InlineData("0-5")]
[InlineData("bytes = 11-0")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(string rangeString)
{
// Arrange
var contentType = "text/plain";
var lastModified = new DateTimeOffset();
var entityTag = new EntityTagHeaderValue("\"Etag\"");
var byteArray = Encoding.ASCII.GetBytes("Hello World");
var result = new FileContentResult(byteArray, contentType)
{
LastModified = lastModified,
EntityTag = entityTag
};
var httpContext = GetHttpContext();
httpContext.Request.Headers[HeaderNames.Range] = rangeString;
httpContext.Request.Method = HttpMethods.Get;
httpContext.Response.Body = new MemoryStream();
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
// Act
await result.ExecuteResultAsync(actionContext);
// Assert
var httpResponse = actionContext.HttpContext.Response;
httpResponse.Body.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(httpResponse.Body);
var body = streamReader.ReadToEndAsync().Result;
Assert.Empty(httpResponse.Headers[HeaderNames.ContentRange]);
Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]);
Assert.Equal(lastModified.ToString("R"), httpResponse.Headers[HeaderNames.LastModified]);
Assert.Equal(entityTag.ToString(), httpResponse.Headers[HeaderNames.ETag]);
Assert.Equal("Hello World", body);
}
[Theory]
[InlineData("bytes = 12-13")]
[InlineData("bytes = -0")]
public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString) public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString)
{ {
// Arrange // Arrange

View File

@ -378,8 +378,10 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(FileResultExecutorBase.PreconditionState.NotModified, state); Assert.Equal(FileResultExecutorBase.PreconditionState.NotModified, state);
} }
[Fact] [Theory]
public void GetPreconditionState_ShouldNotProcess_IgnoreRangeRequest() [InlineData("\"NotEtag\"", false)]
[InlineData("\"Etag\"", true)]
public void IfRangeValid_IgnoreRangeRequest(string ifRangeString, bool expected)
{ {
// Arrange // Arrange
var actionContext = new ActionContext(); var actionContext = new ActionContext();
@ -389,19 +391,19 @@ namespace Microsoft.AspNetCore.Mvc
var lastModified = DateTimeOffset.MinValue; var lastModified = DateTimeOffset.MinValue;
lastModified = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0)); lastModified = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0));
var etag = new EntityTagHeaderValue("\"Etag\""); var etag = new EntityTagHeaderValue("\"Etag\"");
httpRequestHeaders.IfRange = new RangeConditionHeaderValue("\"NotEtag\""); httpRequestHeaders.IfRange = new RangeConditionHeaderValue(ifRangeString);
httpRequestHeaders.IfModifiedSince = lastModified; httpRequestHeaders.IfModifiedSince = lastModified;
actionContext.HttpContext = httpContext; actionContext.HttpContext = httpContext;
// Act // Act
var state = FileResultExecutorBase.GetPreconditionState( var ifRangeIsValid = FileResultExecutorBase.IfRangeValid(
actionContext, actionContext,
httpRequestHeaders, httpRequestHeaders,
lastModified, lastModified,
etag); etag);
// Assert // Assert
Assert.Equal(FileResultExecutorBase.PreconditionState.IgnoreRangeRequest, state); Assert.Equal(expected, ifRangeIsValid);
} }
private static IServiceCollection CreateServices() private static IServiceCollection CreateServices()

View File

@ -219,9 +219,9 @@ namespace Microsoft.AspNetCore.Mvc
[Theory] [Theory]
[InlineData("0-5")] [InlineData("0-5")]
[InlineData("bytes = 11-0")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString) public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestIgnored(string rangeString)
{ {
// Arrange // Arrange
var contentType = "text/plain"; var contentType = "text/plain";
@ -246,6 +246,46 @@ namespace Microsoft.AspNetCore.Mvc
// Act // Act
await result.ExecuteResultAsync(actionContext); await result.ExecuteResultAsync(actionContext);
// Assert
var httpResponse = actionContext.HttpContext.Response;
httpResponse.Body.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(httpResponse.Body);
var body = streamReader.ReadToEndAsync().Result;
Assert.Empty(httpResponse.Headers[HeaderNames.ContentRange]);
Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]);
Assert.Equal(lastModified.ToString("R"), httpResponse.Headers[HeaderNames.LastModified]);
Assert.Equal(entityTag.ToString(), httpResponse.Headers[HeaderNames.ETag]);
Assert.Equal("Hello World", body);
}
[Theory]
[InlineData("bytes = 12-13")]
[InlineData("bytes = -0")]
public async Task WriteFileAsync_PreconditionStateUnspecified_RangeRequestedNotSatisfiable(string rangeString)
{
// Arrange
var contentType = "text/plain";
var lastModified = new DateTimeOffset();
var entityTag = new EntityTagHeaderValue("\"Etag\"");
var byteArray = Encoding.ASCII.GetBytes("Hello World");
var readStream = new MemoryStream(byteArray);
var result = new FileStreamResult(readStream, contentType)
{
LastModified = lastModified,
EntityTag = entityTag,
};
var httpContext = GetHttpContext();
httpContext.Request.Headers[HeaderNames.Range] = rangeString;
httpContext.Request.Method = HttpMethods.Get;
httpContext.Response.Body = new MemoryStream();
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
// Act
await result.ExecuteResultAsync(actionContext);
// Assert // Assert
var httpResponse = actionContext.HttpContext.Response; var httpResponse = actionContext.HttpContext.Response;
httpResponse.Body.Seek(0, SeekOrigin.Begin); httpResponse.Body.Seek(0, SeekOrigin.Begin);

View File

@ -117,6 +117,7 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]); Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]);
var contentRange = new ContentRangeHeaderValue(0, 3, 34); var contentRange = new ContentRangeHeaderValue(0, 3, 34);
Assert.Equal(contentRange.ToString(), httpResponse.Headers[HeaderNames.ContentRange]); Assert.Equal(contentRange.ToString(), httpResponse.Headers[HeaderNames.ContentRange]);
Assert.NotEmpty(httpResponse.Headers[HeaderNames.LastModified]);
Assert.Equal(entityTag.ToString(), httpResponse.Headers[HeaderNames.ETag]); Assert.Equal(entityTag.ToString(), httpResponse.Headers[HeaderNames.ETag]);
Assert.Equal(4, httpResponse.ContentLength); Assert.Equal(4, httpResponse.ContentLength);
Assert.Equal("File", body); Assert.Equal("File", body);
@ -148,14 +149,46 @@ namespace Microsoft.AspNetCore.Mvc
var body = streamReader.ReadToEndAsync().Result; var body = streamReader.ReadToEndAsync().Result;
Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode); Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]); Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]);
Assert.NotEmpty(httpResponse.Headers[HeaderNames.LastModified]);
Assert.Equal("FilePathResultTestFile contents<74>", body); Assert.Equal("FilePathResultTestFile contents<74>", body);
} }
[Theory] [Theory]
[InlineData("0-5")] [InlineData("0-5")]
[InlineData("bytes = 11-0")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task WriteFileAsync_RangeRequested_NotSatisfiable(string rangeString) public async Task WriteFileAsync_RangeRequestIgnored(string rangeString)
{
// Arrange
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
var result = new TestPhysicalFileResult(path, "text/plain");
var httpContext = GetHttpContext();
var requestHeaders = httpContext.Request.GetTypedHeaders();
requestHeaders.IfModifiedSince = DateTimeOffset.MinValue;
httpContext.Request.Headers[HeaderNames.Range] = rangeString;
httpContext.Request.Method = HttpMethods.Get;
httpContext.Response.Body = new MemoryStream();
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
// Act
await result.ExecuteResultAsync(actionContext);
// Assert
var httpResponse = actionContext.HttpContext.Response;
httpResponse.Body.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(httpResponse.Body);
var body = streamReader.ReadToEndAsync().Result;
Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]);
Assert.Empty(httpResponse.Headers[HeaderNames.ContentRange]);
Assert.NotEmpty(httpResponse.Headers[HeaderNames.LastModified]);
Assert.Equal("FilePathResultTestFile contents<74>", body);
}
[Theory]
[InlineData("bytes = 35-36")]
[InlineData("bytes = -0")]
public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
{ {
// Arrange // Arrange
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt")); var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));

View File

@ -191,9 +191,9 @@ namespace Microsoft.AspNetCore.Mvc
[Theory] [Theory]
[InlineData("0-5")] [InlineData("0-5")]
[InlineData("bytes = 11-0")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task WriteFileAsync_RangeRequested_NotSatisfiable(string rangeString) public async Task WriteFileAsync_RangeRequestIgnored(string rangeString)
{ {
// Arrange // Arrange
var path = Path.GetFullPath("helllo.txt"); var path = Path.GetFullPath("helllo.txt");
@ -201,15 +201,58 @@ namespace Microsoft.AspNetCore.Mvc
var result = new TestVirtualFileResult(path, contentType); var result = new TestVirtualFileResult(path, contentType);
var appEnvironment = new Mock<IHostingEnvironment>(); var appEnvironment = new Mock<IHostingEnvironment>();
appEnvironment.Setup(app => app.WebRootFileProvider) appEnvironment.Setup(app => app.WebRootFileProvider)
.Returns(GetFileProvider(path)); .Returns(GetFileProvider(path));
var httpContext = GetHttpContext(); var httpContext = GetHttpContext();
httpContext.Response.Body = new MemoryStream(); httpContext.Response.Body = new MemoryStream();
httpContext.RequestServices = new ServiceCollection() httpContext.RequestServices = new ServiceCollection()
.AddSingleton(appEnvironment.Object) .AddSingleton(appEnvironment.Object)
.AddTransient<TestVirtualFileResultExecutor>() .AddTransient<TestVirtualFileResultExecutor>()
.AddTransient<ILoggerFactory, LoggerFactory>() .AddTransient<ILoggerFactory, LoggerFactory>()
.BuildServiceProvider(); .BuildServiceProvider();
var requestHeaders = httpContext.Request.GetTypedHeaders();
httpContext.Request.Headers[HeaderNames.Range] = rangeString;
requestHeaders.IfUnmodifiedSince = DateTimeOffset.MinValue.AddDays(1);
httpContext.Request.Method = HttpMethods.Get;
httpContext.Response.Body = new MemoryStream();
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
// Act
await result.ExecuteResultAsync(actionContext);
// Assert
var httpResponse = actionContext.HttpContext.Response;
httpResponse.Body.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(httpResponse.Body);
var body = streamReader.ReadToEndAsync().Result;
Assert.Equal(StatusCodes.Status200OK, httpResponse.StatusCode);
Assert.Equal("bytes", httpResponse.Headers[HeaderNames.AcceptRanges]);
Assert.Empty(httpResponse.Headers[HeaderNames.ContentRange]);
Assert.NotEmpty(httpResponse.Headers[HeaderNames.LastModified]);
Assert.Equal("FilePathResultTestFile contents¡", body);
}
[Theory]
[InlineData("bytes = 35-36")]
[InlineData("bytes = -0")]
public async Task WriteFileAsync_RangeRequestedNotSatisfiable(string rangeString)
{
// Arrange
var path = Path.GetFullPath("helllo.txt");
var contentType = "text/plain; charset=us-ascii; p1=p1-value";
var result = new TestVirtualFileResult(path, contentType);
var appEnvironment = new Mock<IHostingEnvironment>();
appEnvironment.Setup(app => app.WebRootFileProvider)
.Returns(GetFileProvider(path));
var httpContext = GetHttpContext();
httpContext.Response.Body = new MemoryStream();
httpContext.RequestServices = new ServiceCollection()
.AddSingleton(appEnvironment.Object)
.AddTransient<TestVirtualFileResultExecutor>()
.AddTransient<ILoggerFactory, LoggerFactory>()
.BuildServiceProvider();
var requestHeaders = httpContext.Request.GetTypedHeaders(); var requestHeaders = httpContext.Request.GetTypedHeaders();
httpContext.Request.Headers[HeaderNames.Range] = rangeString; httpContext.Request.Headers[HeaderNames.Range] = rangeString;

View File

@ -64,8 +64,28 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[Theory] [Theory]
[InlineData("0-6")] [InlineData("0-6")]
[InlineData("bytes = 11-6")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task FileFromDisk_CanBeEnabled_WithMiddleware_RangeRequestIgnored(string rangeString)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/DownloadFiles/DownloadFromDisk");
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
// Act
var response = await Client.SendAsync(httpRequestMessage);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("This is a sample text file", body);
}
[Theory]
[InlineData("bytes = 35-36")]
[InlineData("bytes = -0")]
public async Task FileFromDisk_CanBeEnabled_WithMiddleware_RangeRequestNotSatisfiable(string rangeString) public async Task FileFromDisk_CanBeEnabled_WithMiddleware_RangeRequestNotSatisfiable(string rangeString)
{ {
// Arrange // Arrange
@ -107,8 +127,28 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[Theory] [Theory]
[InlineData("0-6")] [InlineData("0-6")]
[InlineData("bytes = 11-6")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task FileFromDisk_CanBeEnabled_WithMiddleware_RangeRequestIgnored_WithLastModifiedAndEtag(string rangeString)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/DownloadFiles/DownloadFromDiskWithFileName_WithLastModifiedAndEtag");
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
// Act
var response = await Client.SendAsync(httpRequestMessage);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("This is a sample text file", body);
}
[Theory]
[InlineData("bytes = 35-36")]
[InlineData("bytes = -0")]
public async Task FileFromDisk_CanBeEnabled_WithMiddleware_RangeRequestNotSatisfiable_WithLastModifiedAndEtag(string rangeString) public async Task FileFromDisk_CanBeEnabled_WithMiddleware_RangeRequestNotSatisfiable_WithLastModifiedAndEtag(string rangeString)
{ {
// Arrange // Arrange
@ -229,8 +269,28 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[Theory] [Theory]
[InlineData("0-6")] [InlineData("0-6")]
[InlineData("bytes = 11-6")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task FileFromStream_ReturnsFile_RangeRequestIgnored(string rangeString)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/DownloadFiles/DownloadFromStream");
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
// Act
var response = await Client.SendAsync(httpRequestMessage);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("This is sample text from a stream", body);
}
[Theory]
[InlineData("bytes = 35-36")]
[InlineData("bytes = -0")]
public async Task FileFromStream_ReturnsFile_RangeRequestNotSatisfiable(string rangeString) public async Task FileFromStream_ReturnsFile_RangeRequestNotSatisfiable(string rangeString)
{ {
// Arrange // Arrange
@ -349,8 +409,28 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[Theory] [Theory]
[InlineData("0-6")] [InlineData("0-6")]
[InlineData("bytes = 11-6")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task FileFromBinaryData_ReturnsFile_RangeRequestIgnored(string rangeString)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/DownloadFiles/DownloadFromBinaryData");
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
// Act
var response = await Client.SendAsync(httpRequestMessage);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("This is a sample text from a binary array", body);
}
[Theory]
[InlineData("bytes = 45-46")]
[InlineData("bytes = -0")]
public async Task FileFromBinaryData_ReturnsFile_RangeRequestNotSatisfiable(string rangeString) public async Task FileFromBinaryData_ReturnsFile_RangeRequestNotSatisfiable(string rangeString)
{ {
// Arrange // Arrange
@ -524,8 +604,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
[Theory] [Theory]
[InlineData("0-6")] [InlineData("0-6")]
[InlineData("bytes = 11-6")] [InlineData("bytes = ")]
[InlineData("bytes = 1-4, 5-11")] [InlineData("bytes = 1-4, 5-11")]
public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeRequestIgnored(string rangeString)
{
// Arrange
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/EmbeddedFiles/DownloadFileWithFileName");
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
// Act
var response = await Client.SendAsync(httpRequestMessage);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content.Headers.ContentType);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Sample text file as embedded resource.", body);
var contentDisposition = response.Content.Headers.ContentDisposition.ToString();
Assert.NotNull(contentDisposition);
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
}
[Theory]
[InlineData("bytes = 45-46")]
[InlineData("bytes = -0")]
public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeRequestNotSatisfiable(string rangeString) public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeRequestNotSatisfiable(string rangeString)
{ {
// Arrange // Arrange