Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 9438a45

Browse files
authored
Do not serve response body for HEAD requests (#7230)
Addresses #7208
1 parent 2aae877 commit 9438a45

File tree

3 files changed

+184
-33
lines changed

3 files changed

+184
-33
lines changed

src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,16 @@ protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody)
6464
var response = context.HttpContext.Response;
6565
SetLastModifiedAndEtagHeaders(response, lastModified, etag);
6666

67-
var serveBody = !HttpMethods.IsHead(request.Method);
68-
6967
// Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed)
7068
if (preconditionState == PreconditionState.NotModified)
7169
{
72-
serveBody = false;
7370
response.StatusCode = StatusCodes.Status304NotModified;
74-
return (range: null, rangeLength: 0, serveBody);
71+
return (range: null, rangeLength: 0, serveBody: false);
7572
}
7673
else if (preconditionState == PreconditionState.PreconditionFailed)
7774
{
78-
serveBody = false;
7975
response.StatusCode = StatusCodes.Status412PreconditionFailed;
80-
return (range: null, rangeLength: 0, serveBody);
76+
return (range: null, rangeLength: 0, serveBody: false);
8177
}
8278

8379
if (fileLength.HasValue)
@@ -86,10 +82,8 @@ protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody)
8682
// the length of the entire file.
8783
// If the request is a valid range request, this header is overwritten with the length of the range as part of the
8884
// range processing (see method SetContentLength).
89-
if (serveBody)
90-
{
91-
response.ContentLength = fileLength.Value;
92-
}
85+
86+
response.ContentLength = fileLength.Value;
9387

9488
// Handle range request
9589
if (enableRangeProcessing)
@@ -111,7 +105,7 @@ protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody)
111105
}
112106
}
113107

114-
return (range: null, rangeLength: 0, serveBody);
108+
return (range: null, rangeLength: 0, serveBody: !HttpMethods.IsHead(request.Method));
115109
}
116110

117111
private static void SetContentType(ActionContext context, FileResult result)
@@ -295,6 +289,7 @@ private static PreconditionState GetMaxPreconditionState(params PreconditionStat
295289
{
296290
var response = context.HttpContext.Response;
297291
var httpResponseHeaders = response.GetTypedHeaders();
292+
var serveBody = !HttpMethods.IsHead(context.HttpContext.Request.Method);
298293

299294
// Range may be null for empty range header, invalid ranges, parsing errors, multiple ranges
300295
// and when the file length is zero.
@@ -306,7 +301,7 @@ private static PreconditionState GetMaxPreconditionState(params PreconditionStat
306301

307302
if (!isRangeRequest)
308303
{
309-
return (range: null, rangeLength: 0, serveBody: true);
304+
return (range: null, rangeLength: 0, serveBody);
310305
}
311306

312307
// Requested range is not satisfiable
@@ -330,7 +325,7 @@ private static PreconditionState GetMaxPreconditionState(params PreconditionStat
330325
// Overwrite the Content-Length header for valid range requests with the range length.
331326
var rangeLength = SetContentLength(response, range);
332327

333-
return (range, rangeLength, serveBody: true);
328+
return (range, rangeLength, serveBody);
334329
}
335330

336331
private static long SetContentLength(HttpResponse response, RangeItemHeaderValue range)

test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
using Microsoft.AspNetCore.Http;
88
using Microsoft.AspNetCore.Mvc.Abstractions;
99
using Microsoft.AspNetCore.Mvc.Infrastructure;
10-
using Microsoft.AspNetCore.Mvc.Internal;
1110
using Microsoft.AspNetCore.Routing;
1211
using Microsoft.AspNetCore.Testing.xunit;
1312
using Microsoft.Extensions.DependencyInjection;
1413
using Microsoft.Extensions.Logging;
1514
using Microsoft.Extensions.Logging.Abstractions;
16-
using Microsoft.Extensions.Logging.Testing;
1715
using Microsoft.Net.Http.Headers;
1816
using Moq;
1917
using Xunit;

test/Microsoft.AspNetCore.Mvc.FunctionalTests/FileResultTests.cs

Lines changed: 176 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
54
using System.Net;
65
using System.Net.Http;
76
using System.Net.Http.Headers;
87
using System.Threading.Tasks;
9-
using Microsoft.AspNetCore.Mvc.Infrastructure;
108
using Microsoft.AspNetCore.Testing.xunit;
119
using Xunit;
1210

@@ -189,11 +187,13 @@ public async Task FileFromDisk_ReturnsFileWithFileName()
189187
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
190188
}
191189

192-
[Fact]
193-
public async Task FileFromDisk_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored()
190+
[Theory]
191+
[InlineData("GET", "This is a sample text file")]
192+
[InlineData("HEAD", "")]
193+
public async Task FileFromDisk_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored(string httpMethod, string expectedBody)
194194
{
195195
// Arrange
196-
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/DownloadFiles/DownloadFromDiskWithFileName");
196+
var httpRequestMessage = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/DownloadFiles/DownloadFromDiskWithFileName");
197197
httpRequestMessage.Headers.Range = new RangeHeaderValue(0, 6);
198198

199199
// Act
@@ -204,7 +204,7 @@ public async Task FileFromDisk_ReturnsFileWithFileName_RangeProcessingNotEnabled
204204
Assert.NotNull(response.Content.Headers.ContentType);
205205
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
206206
var body = await response.Content.ReadAsStringAsync();
207-
Assert.Equal("This is a sample text file", body);
207+
Assert.Equal(expectedBody, body);
208208
}
209209

210210
[Fact]
@@ -246,6 +246,44 @@ public async Task FileFromDisk_ReturnsFileWithFileName_IfRangeHeaderInvalid_Rang
246246
Assert.Equal("This is a sample text file", body);
247247
}
248248

249+
[Theory]
250+
[InlineData("", HttpStatusCode.OK, 26)]
251+
[InlineData("bytes = 0-6", HttpStatusCode.PartialContent, 7)]
252+
[InlineData("bytes = 17-25", HttpStatusCode.PartialContent, 9)]
253+
[InlineData("bytes = 0-50", HttpStatusCode.PartialContent, 26)]
254+
[InlineData("0-6", HttpStatusCode.OK, 26)]
255+
[InlineData("bytes = ", HttpStatusCode.OK, 26)]
256+
[InlineData("bytes = 1-4, 5-11", HttpStatusCode.OK, 26)]
257+
[InlineData("bytes = 35-36", HttpStatusCode.RequestedRangeNotSatisfiable, 26)]
258+
[InlineData("bytes = -0", HttpStatusCode.RequestedRangeNotSatisfiable, 26)]
259+
public async Task FileFromDisk_ReturnsFileWithFileName_DoesNotServeBody_ForHeadRequest_WithLastModifiedAndEtag(string rangeString, HttpStatusCode httpStatusCode, int expectedContentLength)
260+
{
261+
// Arrange
262+
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, "http://localhost/DownloadFiles/DownloadFromDiskWithFileName_WithLastModifiedAndEtag");
263+
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
264+
httpRequestMessage.Headers.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
265+
266+
// Act
267+
var response = await Client.SendAsync(httpRequestMessage);
268+
269+
// Assert
270+
Assert.Equal(httpStatusCode, response.StatusCode);
271+
272+
Assert.NotNull(response.Content.Headers.ContentType);
273+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
274+
275+
var body = await response.Content.ReadAsStringAsync();
276+
Assert.NotNull(body);
277+
Assert.Equal(string.Empty, body);
278+
279+
var contentLength = response.Content.Headers.ContentLength;
280+
Assert.Equal(expectedContentLength, contentLength);
281+
282+
var contentDisposition = response.Content.Headers.ContentDisposition.ToString();
283+
Assert.NotNull(contentDisposition);
284+
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
285+
}
286+
249287
[Fact]
250288
public async Task FileFromStream_ReturnsFile()
251289
{
@@ -347,11 +385,13 @@ public async Task FileFromStream_ReturnsFileWithFileName()
347385
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
348386
}
349387

350-
[Fact]
351-
public async Task FileFromStream_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored()
388+
[Theory]
389+
[InlineData("GET", "This is sample text from a stream")]
390+
[InlineData("HEAD", "")]
391+
public async Task FileFromStream_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored(string httpMethod, string expectedBody)
352392
{
353393
// Arrange
354-
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/DownloadFiles/DownloadFromStreamWithFileName");
394+
var httpRequestMessage = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/DownloadFiles/DownloadFromStreamWithFileName");
355395
httpRequestMessage.Headers.Range = new RangeHeaderValue(0, 6);
356396

357397
// Act
@@ -362,7 +402,7 @@ public async Task FileFromStream_ReturnsFileWithFileName_RangeProcessingNotEnabl
362402
Assert.NotNull(response.Content.Headers.ContentType);
363403
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
364404
var body = await response.Content.ReadAsStringAsync();
365-
Assert.Equal("This is sample text from a stream", body);
405+
Assert.Equal(expectedBody, body);
366406
}
367407

368408
[Fact]
@@ -402,6 +442,44 @@ public async Task FileFromStream_ReturnsFileWithFileName_IfRangeHeaderInvalid_Ra
402442
Assert.Equal("This is sample text from a stream", body);
403443
}
404444

445+
[Theory]
446+
[InlineData("", HttpStatusCode.OK, 33)]
447+
[InlineData("bytes = 0-6", HttpStatusCode.PartialContent, 7)]
448+
[InlineData("bytes = 17-25", HttpStatusCode.PartialContent, 9)]
449+
[InlineData("bytes = 0-50", HttpStatusCode.PartialContent, 33)]
450+
[InlineData("0-6", HttpStatusCode.OK, 33)]
451+
[InlineData("bytes = ", HttpStatusCode.OK, 33)]
452+
[InlineData("bytes = 1-4, 5-11", HttpStatusCode.OK, 33)]
453+
[InlineData("bytes = 35-36", HttpStatusCode.RequestedRangeNotSatisfiable, 33)]
454+
[InlineData("bytes = -0", HttpStatusCode.RequestedRangeNotSatisfiable, 33)]
455+
public async Task FileFromStream_ReturnsFileWithFileName_DoesNotServeBody_ForHeadRequest(string rangeString, HttpStatusCode httpStatusCode, int expectedContentLength)
456+
{
457+
// Arrange
458+
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, "http://localhost/DownloadFiles/DownloadFromStreamWithFileName_WithEtag");
459+
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
460+
httpRequestMessage.Headers.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
461+
462+
// Act
463+
var response = await Client.SendAsync(httpRequestMessage);
464+
465+
// Assert
466+
Assert.Equal(httpStatusCode, response.StatusCode);
467+
468+
Assert.NotNull(response.Content.Headers.ContentType);
469+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
470+
471+
var body = await response.Content.ReadAsStringAsync();
472+
Assert.NotNull(body);
473+
Assert.Equal(string.Empty, body);
474+
475+
var contentLength = response.Content.Headers.ContentLength;
476+
Assert.Equal(expectedContentLength, contentLength);
477+
478+
var contentDisposition = response.Content.Headers.ContentDisposition.ToString();
479+
Assert.NotNull(contentDisposition);
480+
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
481+
}
482+
405483
[Fact]
406484
public async Task FileFromBinaryData_ReturnsFile()
407485
{
@@ -506,11 +584,13 @@ public async Task FileFromBinaryData_ReturnsFileWithFileName()
506584
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
507585
}
508586

509-
[Fact]
510-
public async Task FileFromBinaryData_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored()
587+
[Theory]
588+
[InlineData("GET", "This is a sample text from a binary array")]
589+
[InlineData("HEAD", "")]
590+
public async Task FileFromBinaryData_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored(string httpMethod, string expectedBody)
511591
{
512592
// Arrange
513-
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/DownloadFiles/DownloadFromBinaryDataWithFileName");
593+
var httpRequestMessage = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/DownloadFiles/DownloadFromBinaryDataWithFileName");
514594
httpRequestMessage.Headers.Range = new RangeHeaderValue(0, 6);
515595

516596
// Act
@@ -521,7 +601,7 @@ public async Task FileFromBinaryData_ReturnsFileWithFileName_RangeProcessingNotE
521601
Assert.NotNull(response.Content.Headers.ContentType);
522602
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
523603
var body = await response.Content.ReadAsStringAsync();
524-
Assert.Equal("This is a sample text from a binary array", body);
604+
Assert.Equal(expectedBody, body);
525605
}
526606

527607
[Fact]
@@ -563,6 +643,44 @@ public async Task FileFromBinaryData_ReturnsFileWithFileName_IfRangeHeaderInvali
563643
Assert.Equal("This is a sample text from a binary array", body);
564644
}
565645

646+
[Theory]
647+
[InlineData("", HttpStatusCode.OK, 41)]
648+
[InlineData("bytes = 0-6", HttpStatusCode.PartialContent, 7)]
649+
[InlineData("bytes = 17-25", HttpStatusCode.PartialContent, 9)]
650+
[InlineData("bytes = 0-50", HttpStatusCode.PartialContent, 41)]
651+
[InlineData("0-6", HttpStatusCode.OK, 41)]
652+
[InlineData("bytes = ", HttpStatusCode.OK, 41)]
653+
[InlineData("bytes = 1-4, 5-11", HttpStatusCode.OK, 41)]
654+
[InlineData("bytes = 45-46", HttpStatusCode.RequestedRangeNotSatisfiable, 41)]
655+
[InlineData("bytes = -0", HttpStatusCode.RequestedRangeNotSatisfiable, 41)]
656+
public async Task FileFromBinaryData_ReturnsFileWithFileName_DoesNotServeBody_ForHeadRequest(string rangeString, HttpStatusCode httpStatusCode, int expectedContentLength)
657+
{
658+
// Arrange
659+
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, "http://localhost/DownloadFiles/DownloadFromBinaryDataWithFileName_WithEtag");
660+
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
661+
httpRequestMessage.Headers.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
662+
663+
// Act
664+
var response = await Client.SendAsync(httpRequestMessage);
665+
666+
// Assert
667+
Assert.Equal(httpStatusCode, response.StatusCode);
668+
669+
Assert.NotNull(response.Content.Headers.ContentType);
670+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
671+
672+
var body = await response.Content.ReadAsStringAsync();
673+
Assert.NotNull(body);
674+
Assert.Equal(string.Empty, body);
675+
676+
var contentLength = response.Content.Headers.ContentLength;
677+
Assert.Equal(expectedContentLength, contentLength);
678+
679+
var contentDisposition = response.Content.Headers.ContentDisposition.ToString();
680+
Assert.NotNull(contentDisposition);
681+
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
682+
}
683+
566684
[Fact]
567685
public async Task FileFromEmbeddedResources_ReturnsFileWithFileName()
568686
{
@@ -612,11 +730,13 @@ public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeRequest
612730
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
613731
}
614732

615-
[Fact]
616-
public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored()
733+
[Theory]
734+
[InlineData("GET", "Sample text file as embedded resource.")]
735+
[InlineData("HEAD", "")]
736+
public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeProcessingNotEnabled_RangeRequestedIgnored(string httpMethod, string expectedBody)
617737
{
618738
// Arrange
619-
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost/EmbeddedFiles/DownloadFileWithFileName_RangeProcessingNotEnabled");
739+
var httpRequestMessage = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/EmbeddedFiles/DownloadFileWithFileName_RangeProcessingNotEnabled");
620740
httpRequestMessage.Headers.Range = new RangeHeaderValue(0, 6);
621741

622742
// Act
@@ -627,7 +747,7 @@ public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeProcess
627747
Assert.NotNull(response.Content.Headers.ContentType);
628748
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
629749
var body = await response.Content.ReadAsStringAsync();
630-
Assert.Equal("Sample text file as embedded resource.", body);
750+
Assert.Equal(expectedBody, body);
631751
}
632752

633753
[Fact]
@@ -721,5 +841,43 @@ public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_RangeRequest
721841
Assert.NotNull(contentDisposition);
722842
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
723843
}
844+
845+
[Theory]
846+
[InlineData("", HttpStatusCode.OK, 38)]
847+
[InlineData("bytes = 0-6", HttpStatusCode.PartialContent, 7)]
848+
[InlineData("bytes = 17-25", HttpStatusCode.PartialContent, 9)]
849+
[InlineData("bytes = 0-50", HttpStatusCode.PartialContent, 38)]
850+
[InlineData("0-6", HttpStatusCode.OK, 38)]
851+
[InlineData("bytes = ", HttpStatusCode.OK, 38)]
852+
[InlineData("bytes = 1-4, 5-11", HttpStatusCode.OK, 38)]
853+
[InlineData("bytes = 45-46", HttpStatusCode.RequestedRangeNotSatisfiable, 38)]
854+
[InlineData("bytes = -0", HttpStatusCode.RequestedRangeNotSatisfiable, 38)]
855+
public async Task FileFromEmbeddedResources_ReturnsFileWithFileName_DoesNotServeBody_ForHeadRequest(string rangeString, HttpStatusCode httpStatusCode, int expectedContentLength)
856+
{
857+
// Arrange
858+
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, "http://localhost/EmbeddedFiles/DownloadFileWithFileName");
859+
httpRequestMessage.Headers.TryAddWithoutValidation("Range", rangeString);
860+
httpRequestMessage.Headers.IfRange = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"Etag\""));
861+
862+
// Act
863+
var response = await Client.SendAsync(httpRequestMessage);
864+
865+
// Assert
866+
Assert.Equal(httpStatusCode, response.StatusCode);
867+
868+
Assert.NotNull(response.Content.Headers.ContentType);
869+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
870+
871+
var body = await response.Content.ReadAsStringAsync();
872+
Assert.NotNull(body);
873+
Assert.Equal(string.Empty, body);
874+
875+
var contentLength = response.Content.Headers.ContentLength;
876+
Assert.Equal(expectedContentLength, contentLength);
877+
878+
var contentDisposition = response.Content.Headers.ContentDisposition.ToString();
879+
Assert.NotNull(contentDisposition);
880+
Assert.Equal("attachment; filename=downloadName.txt; filename*=UTF-8''downloadName.txt", contentDisposition);
881+
}
724882
}
725883
}

0 commit comments

Comments
 (0)