Round off file result's LastModifiedDate to whole seconds for correct comparison with http header dates

[Fixes #7572] PhysicalFileResult does not respond 304 Not Modified
This commit is contained in:
Kiran Challa 2018-04-09 05:07:46 -07:00
parent 3db924003e
commit 62272ad56a
2 changed files with 99 additions and 2 deletions

View File

@ -59,6 +59,14 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
var request = context.HttpContext.Request;
var httpRequestHeaders = request.GetTypedHeaders();
// Since the 'Last-Modified' and other similar http date headers are rounded down to whole seconds,
// round down current file's last modified to whole seconds for correct comparison.
if (lastModified.HasValue)
{
lastModified = RoundDownToWholeSeconds(lastModified.Value);
}
var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag);
var response = context.HttpContext.Response;
@ -219,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
matchNotFoundState: PreconditionState.ShouldProcess);
}
var now = DateTimeOffset.UtcNow;
var now = RoundDownToWholeSeconds(DateTimeOffset.UtcNow);
// 14.25 If-Modified-Since
var ifModifiedSince = httpRequestHeaders.IfModifiedSince;
@ -375,5 +383,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
}
}
}
private static DateTimeOffset RoundDownToWholeSeconds(DateTimeOffset dateTimeOffset)
{
var ticksToRemove = dateTimeOffset.Ticks % TimeSpan.TicksPerSecond;
return dateTimeOffset.Subtract(TimeSpan.FromTicks(ticksToRemove));
}
}
}

View File

@ -396,6 +396,84 @@ namespace Microsoft.AspNetCore.Mvc
Assert.Equal(expected, ifRangeIsValid);
}
public static TheoryData<DateTimeOffset, int> LastModifiedDateData
{
get
{
return new TheoryData<DateTimeOffset, int>()
{
{ new DateTimeOffset(2018, 4, 9, 11, 23, 22, TimeSpan.Zero), 200 },
{ new DateTimeOffset(2018, 4, 9, 11, 24, 21, TimeSpan.Zero), 200 },
{ new DateTimeOffset(2018, 4, 9, 11, 24, 22, TimeSpan.Zero), 304 },
{ new DateTimeOffset(2018, 4, 9, 11, 24, 23, TimeSpan.Zero), 304 },
{ new DateTimeOffset(2018, 4, 9, 11, 25, 22, TimeSpan.Zero), 304 },
};
}
}
[Theory]
[MemberData(nameof(LastModifiedDateData))]
public async Task IfModifiedSinceComparison_OnlyUsesWholeSeconds(
DateTimeOffset ifModifiedSince,
int expectedStatusCode)
{
// Arrange
var httpContext = GetHttpContext();
httpContext.Request.Headers[HeaderNames.IfModifiedSince] = HeaderUtilities.FormatDate(ifModifiedSince);
var actionContext = CreateActionContext(httpContext);
// Represents 4/9/2018 11:24:22 AM +00:00
// Ticks rounded down to seconds: 636588698620000000
var ticks = 636588698625969382;
var result = new EmptyFileResult("application/test")
{
LastModified = new DateTimeOffset(ticks, TimeSpan.Zero)
};
// Act
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
}
public static TheoryData<DateTimeOffset, int> IfUnmodifiedSinceDateData
{
get
{
return new TheoryData<DateTimeOffset, int>()
{
{ new DateTimeOffset(2018, 4, 9, 11, 23, 22, TimeSpan.Zero), 412 },
{ new DateTimeOffset(2018, 4, 9, 11, 24, 21, TimeSpan.Zero), 412 },
{ new DateTimeOffset(2018, 4, 9, 11, 24, 22, TimeSpan.Zero), 200 },
{ new DateTimeOffset(2018, 4, 9, 11, 24, 23, TimeSpan.Zero), 200 },
{ new DateTimeOffset(2018, 4, 9, 11, 25, 22, TimeSpan.Zero), 200 },
};
}
}
[Theory]
[MemberData(nameof(IfUnmodifiedSinceDateData))]
public async Task IfUnmodifiedSinceComparison_OnlyUsesWholeSeconds(DateTimeOffset ifUnmodifiedSince, int expectedStatusCode)
{
// Arrange
var httpContext = GetHttpContext();
httpContext.Request.Headers[HeaderNames.IfUnmodifiedSince] = HeaderUtilities.FormatDate(ifUnmodifiedSince);
var actionContext = CreateActionContext(httpContext);
// Represents 4/9/2018 11:24:22 AM +00:00
// Ticks rounded down to seconds: 636588698620000000
var ticks = 636588698625969382;
var result = new EmptyFileResult("application/test")
{
LastModified = new DateTimeOffset(ticks, TimeSpan.Zero)
};
// Act
await result.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
}
private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
@ -449,7 +527,12 @@ namespace Microsoft.AspNetCore.Mvc
public Task ExecuteAsync(ActionContext context, EmptyFileResult result)
{
SetHeadersAndLog(context, result, 0L, true);
SetHeadersAndLog(
context,
result,
fileLength: 0L,
enableRangeProcessing: true,
lastModified: result.LastModified);
result.WasWriteFileCalled = true;
return Task.FromResult(0);
}