diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json
index b153ab1515..4328f86b21 100644
--- a/NuGetPackageVerifier.json
+++ b/NuGetPackageVerifier.json
@@ -1,7 +1,13 @@
{
- "Default": {
- "rules": [
- "DefaultCompositeRule"
+ "adx-nonshipping": {
+ "rules": [],
+ "packages": {
+ "Microsoft.AspNetCore.RangeHelper.Sources": {}
+ }
+ },
+ "Default": {
+ "rules": [
+ "DefaultCompositeRule"
]
}
}
\ No newline at end of file
diff --git a/StaticFiles.sln b/StaticFiles.sln
index bead941055..3cd367a48e 100644
--- a/StaticFiles.sln
+++ b/StaticFiles.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26020.0
+VisualStudioVersion = 15.0.26228.9
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{40EE0889-960E-41B4-A3D3-9CE963EB0797}"
EndProject
@@ -16,6 +16,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Static
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.StaticFiles.FunctionalTests", "test\Microsoft.AspNetCore.StaticFiles.FunctionalTests\Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj", "{FDF0539C-1F62-4B78-91B1-C687886931CA}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.RangeHelper.Sources.Test", "test\Microsoft.AspNetCore.RangeHelper.Sources.Test\Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj", "{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{360DC2F8-EEB4-4C69-9784-C686EAD78279}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AspNetCore.RangeHelper.Sources", "Microsoft.AspNetCore.RangeHelper.Sources", "{DB6A1D14-B8A2-488F-9C4B-422FD45C8853}"
+ ProjectSection(SolutionItems) = preProject
+ shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs = shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +77,18 @@ Global
{FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.ActiveCfg = Release|Any CPU
{FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.Build.0 = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -77,5 +98,7 @@ Global
{092141D9-305A-4FC5-AE74-CB23982CA8D4} = {8B21A3A9-9CA6-4857-A6E0-1A3203404B60}
{CC87FE7D-8F42-4BE9-A152-9625E837C1E5} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
{FDF0539C-1F62-4B78-91B1-C687886931CA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
+ {D3D752C4-4CDF-4F18-AC7F-48CB980A69DA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
+ {DB6A1D14-B8A2-488F-9C4B-422FD45C8853} = {360DC2F8-EEB4-4C69-9784-C686EAD78279}
EndGlobalSection
EndGlobal
diff --git a/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs b/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs
new file mode 100644
index 0000000000..458389573f
--- /dev/null
+++ b/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs
@@ -0,0 +1,168 @@
+// 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.Diagnostics;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Headers;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Internal
+{
+ ///
+ /// Provides a parser for the Range Header in an .
+ ///
+ internal static class RangeHelper
+ {
+ ///
+ /// Returns the requested range if the Range Header in the is valid.
+ ///
+ /// The associated with the request.
+ /// The associated with the given .
+ /// The representation of the last modified date of the file.
+ /// The provided in the .
+ /// A collection of containing the ranges parsed from the .
+ public static ICollection ParseRange(HttpContext context, RequestHeaders requestHeaders, DateTimeOffset? lastModified = null, EntityTagHeaderValue etag = null)
+ {
+ var rawRangeHeader = context.Request.Headers[HeaderNames.Range];
+ if (StringValues.IsNullOrEmpty(rawRangeHeader))
+ {
+ return null;
+ }
+
+ // Perf: Check for a single entry before parsing it
+ if (rawRangeHeader.Count > 1 || rawRangeHeader[0].IndexOf(',') >= 0)
+ {
+ // The spec allows for multiple ranges but we choose not to support them because the client may request
+ // very strange ranges (e.g. each byte separately, overlapping ranges, etc.) that could negatively
+ // impact the server. Ignore the header and serve the response normally.
+ return null;
+ }
+
+ var rangeHeader = requestHeaders.Range;
+ if (rangeHeader == null)
+ {
+ // Invalid
+ return null;
+ }
+
+ // Already verified above
+ Debug.Assert(rangeHeader.Ranges.Count == 1);
+
+ // 14.27 If-Range
+ var ifRangeHeader = requestHeaders.IfRange;
+ if (ifRangeHeader != 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.
+ bool ignoreRangeHeader = false;
+ if (ifRangeHeader.LastModified.HasValue)
+ {
+ if (lastModified.HasValue && lastModified > ifRangeHeader.LastModified)
+ {
+ ignoreRangeHeader = true;
+ }
+ }
+ else if (etag != null && ifRangeHeader.EntityTag != null && !ifRangeHeader.EntityTag.Compare(etag, useStrongComparison: true))
+ {
+ ignoreRangeHeader = true;
+ }
+
+ if (ignoreRangeHeader)
+ {
+ return null;
+ }
+ }
+
+ return rangeHeader.Ranges;
+ }
+
+ ///
+ /// A helper method to normalize a collection of s.
+ ///
+ /// A collection of to normalize.
+ /// The length of the provided .
+ /// A normalized list of s.
+ // 14.35.1 Byte Ranges - If a syntactically valid byte-range-set includes at least one byte-range-spec whose
+ // first-byte-pos is less than the current length of the entity-body, or at least one suffix-byte-range-spec
+ // with a non-zero suffix-length, then the byte-range-set is satisfiable.
+ // Adjusts ranges to be absolute and within bounds.
+ public static IList NormalizeRanges(ICollection ranges, long length)
+ {
+ if (ranges == null)
+ {
+ return null;
+ }
+
+ if (ranges.Count == 0)
+ {
+ return Array.Empty();
+ }
+
+ if (length == 0)
+ {
+ return Array.Empty();
+ }
+
+ var normalizedRanges = new List(ranges.Count);
+ foreach (var range in ranges)
+ {
+ var normalizedRange = NormalizeRange(range, length);
+
+ if (normalizedRange != null)
+ {
+ normalizedRanges.Add(normalizedRange);
+ }
+ }
+
+ return normalizedRanges;
+ }
+
+ ///
+ /// A helper method to normalize a .
+ ///
+ /// The to normalize.
+ /// The length of the provided .
+ /// A normalized .
+ public static RangeItemHeaderValue NormalizeRange(RangeItemHeaderValue range, long length)
+ {
+ var start = range.From;
+ var end = range.To;
+
+ // X-[Y]
+ if (start.HasValue)
+ {
+ if (start.Value >= length)
+ {
+ // Not satisfiable, skip/discard.
+ return null;
+ }
+ if (!end.HasValue || end.Value >= length)
+ {
+ end = length - 1;
+ }
+ }
+ else
+ {
+ // suffix range "-X" e.g. the last X bytes, resolve
+ if (end.Value == 0)
+ {
+ // Not satisfiable, skip/discard.
+ return null;
+ }
+
+ var bytes = Math.Min(end.Value, length);
+ start = length - bytes;
+ end = start + bytes - 1;
+ }
+
+ var normalizedRange = new RangeItemHeaderValue(start, end);
+ return normalizedRange;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs b/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs
deleted file mode 100644
index 2423018545..0000000000
--- a/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-// 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 Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
-{
- internal static class RangeHelpers
- {
- // 14.35.1 Byte Ranges - If a syntactically valid byte-range-set includes at least one byte-range-spec whose
- // first-byte-pos is less than the current length of the entity-body, or at least one suffix-byte-range-spec
- // with a non-zero suffix-length, then the byte-range-set is satisfiable.
- // Adjusts ranges to be absolute and within bounds.
- internal static IList NormalizeRanges(ICollection ranges, long length)
- {
- IList normalizedRanges = new List(ranges.Count);
- if (length == 0)
- {
- return normalizedRanges;
- }
- foreach (var range in ranges)
- {
- long? start = range.From;
- long? end = range.To;
-
- // X-[Y]
- if (start.HasValue)
- {
- if (start.Value >= length)
- {
- // Not satisfiable, skip/discard.
- continue;
- }
- if (!end.HasValue || end.Value >= length)
- {
- end = length - 1;
- }
- }
- else
- {
- // suffix range "-X" e.g. the last X bytes, resolve
- if (end.Value == 0)
- {
- // Not satisfiable, skip/discard.
- continue;
- }
-
- long bytes = Math.Min(end.Value, length);
- start = length - bytes;
- end = start + bytes - 1;
- }
- normalizedRanges.Add(new RangeItemHeaderValue(start.Value, end.Value));
- }
- return normalizedRanges;
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs b/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs
index 726fb2eb00..7fa6e35083 100644
--- a/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs
+++ b/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs
@@ -24,7 +24,6 @@ namespace Microsoft.AspNetCore.StaticFiles
private static Action _logSendingFileRange;
private static Action _logCopyingFileRange;
private static Action _logCopyingBytesToResponse;
- private static Action _logMultipleFileRanges;
private static Action _logWriteCancelled;
static LoggerExtensions()
@@ -77,10 +76,6 @@ namespace Microsoft.AspNetCore.StaticFiles
logLevel: LogLevel.Debug,
eventId: 12,
formatString: "Copying bytes {Start}-{End} of file {Path} to response body");
- _logMultipleFileRanges = LoggerMessage.Define(
- logLevel: LogLevel.Warning,
- eventId: 13,
- formatString: "Multiple ranges are not allowed: '{Ranges}'");
_logWriteCancelled = LoggerMessage.Define(
logLevel: LogLevel.Debug,
eventId: 14,
@@ -156,11 +151,6 @@ namespace Microsoft.AspNetCore.StaticFiles
null);
}
- public static void LogMultipleFileRanges(this ILogger logger, string range)
- {
- _logMultipleFileRanges(logger, range, null);
- }
-
public static void LogWriteCancelled(this ILogger logger, Exception ex)
{
_logWriteCancelled(logger, ex);
diff --git a/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj b/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj
index 6e14838295..53c869c993 100644
--- a/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj
+++ b/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj
@@ -10,6 +10,10 @@
aspnetcore;staticfiles
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs b/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs
index 2a80e2c9d4..7c6e8f81b3 100644
--- a/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs
+++ b/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs
@@ -13,10 +13,9 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Headers;
-using Microsoft.AspNetCore.StaticFiles.Infrastructure;
+using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.StaticFiles
@@ -231,60 +230,8 @@ namespace Microsoft.AspNetCore.StaticFiles
return;
}
- var rawRangeHeader = _request.Headers[HeaderNames.Range];
- if (StringValues.IsNullOrEmpty(rawRangeHeader))
- {
- return;
- }
-
- // Perf: Check for a single entry before parsing it
- if (rawRangeHeader.Count > 1 || rawRangeHeader[0].IndexOf(',') >= 0)
- {
- // The spec allows for multiple ranges but we choose not to support them because the client may request
- // very strange ranges (e.g. each byte separately, overlapping ranges, etc.) that could negatively
- // impact the server. Ignore the header and serve the response normally.
- _logger.LogMultipleFileRanges(rawRangeHeader.ToString());
- return;
- }
-
- var rangeHeader = _requestHeaders.Range;
- if (rangeHeader == null)
- {
- // Invalid
- return;
- }
-
- // Already verified above
- Debug.Assert(rangeHeader.Ranges.Count == 1);
-
- // 14.27 If-Range
- var ifRangeHeader = _requestHeaders.IfRange;
- if (ifRangeHeader != 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.
- bool ignoreRangeHeader = false;
- if (ifRangeHeader.LastModified.HasValue)
- {
- if (_lastModified > ifRangeHeader.LastModified)
- {
- ignoreRangeHeader = true;
- }
- }
- else if (ifRangeHeader.EntityTag != null && !ifRangeHeader.EntityTag.Compare(_etag, useStrongComparison: true))
- {
- ignoreRangeHeader = true;
- }
- if (ignoreRangeHeader)
- {
- return;
- }
- }
-
- _ranges = RangeHelpers.NormalizeRanges(rangeHeader.Ranges, _length);
+ var parsedRange = RangeHelper.ParseRange(_context, _requestHeaders, _lastModified, _etag);
+ _ranges = RangeHelper.NormalizeRanges(parsedRange, _length);
}
public void ApplyResponseHeaders(int statusCode)
diff --git a/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj b/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj
new file mode 100644
index 0000000000..3d9ff67b4b
--- /dev/null
+++ b/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj
@@ -0,0 +1,29 @@
+
+
+
+
+
+ netcoreapp2.0;net46
+ netcoreapp2.0
+ win7-x64
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs b/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs
new file mode 100644
index 0000000000..2417b0dad7
--- /dev/null
+++ b/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs
@@ -0,0 +1,254 @@
+// 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 Microsoft.AspNetCore.Http;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Internal
+{
+ public class RangeHelperTests
+ {
+ [Fact]
+ public void NormalizeRanges_ReturnsEmptyArrayWhenRangeCountZero()
+ {
+ // Arrange
+ var ranges = new List();
+
+ // Act
+ var normalizedRanges = RangeHelper.NormalizeRanges(ranges, 2);
+
+ // Assert
+ Assert.Empty(normalizedRanges);
+ }
+
+ [Fact]
+ public void NormalizeRanges_ReturnsEmptyArrayWhenLengthZero()
+ {
+ // Arrange
+ var ranges = new[]
+ {
+ new RangeItemHeaderValue(0, 0),
+ };
+
+ // Act
+ var normalizedRanges = RangeHelper.NormalizeRanges(ranges, 0);
+
+ // Assert
+ Assert.Empty(normalizedRanges);
+ }
+
+ [Theory]
+ [InlineData(1, 2)]
+ [InlineData(2, 3)]
+ public void NormalizeRanges_SkipsItemWhenRangeStartEqualOrGreaterThanLength(long start, long end)
+ {
+ // Arrange
+ var ranges = new[]
+ {
+ new RangeItemHeaderValue(start, end),
+ };
+
+ // Act
+ var normalizedRanges = RangeHelper.NormalizeRanges(ranges, 1);
+
+ // Assert
+ Assert.Empty(normalizedRanges);
+ }
+
+ [Fact]
+ public void NormalizeRanges_SkipsItemWhenRangeEndEqualsZero()
+ {
+ // Arrange
+ var ranges = new[]
+ {
+ new RangeItemHeaderValue(null, 0),
+ };
+
+ // Act
+ var normalizedRanges = RangeHelper.NormalizeRanges(ranges, 1);
+
+ // Assert
+ Assert.Empty(normalizedRanges);
+ }
+
+ [Theory]
+ [InlineData(null, null)]
+ [InlineData(null, 0)]
+ [InlineData(0, null)]
+ [InlineData(0, 0)]
+ public void NormalizeRanges_ReturnsNormalizedRange(long start, long end)
+ {
+ // Arrange
+ var ranges = new[]
+ {
+ new RangeItemHeaderValue(start, end),
+ };
+
+ // Act
+ var normalizedRanges = RangeHelper.NormalizeRanges(ranges, 1);
+
+ // Assert
+ var range = Assert.Single(normalizedRanges);
+ Assert.Equal(0, range.From);
+ Assert.Equal(0, range.To);
+ }
+
+ [Fact]
+ public void NormalizeRanges_ReturnsRangeWithNoChange()
+ {
+ // Arrange
+ var ranges = new[]
+ {
+ new RangeItemHeaderValue(1, 3),
+ };
+
+ // Act
+ var normalizedRanges = RangeHelper.NormalizeRanges(ranges, 4);
+
+ // Assert
+ var range = Assert.Single(normalizedRanges);
+ Assert.Equal(1, range.From);
+ Assert.Equal(3, range.To);
+ }
+
+ [Theory]
+ [InlineData(null, null)]
+ [InlineData(null, 0)]
+ [InlineData(0, null)]
+ [InlineData(0, 0)]
+ public void NormalizeRanges_MultipleRanges_ReturnsNormalizedRange(long start, long end)
+ {
+ // Arrange
+ var ranges = new[]
+ {
+ new RangeItemHeaderValue(start, end),
+ new RangeItemHeaderValue(1, 2),
+ };
+
+ // Act
+ var normalizedRanges = RangeHelper.NormalizeRanges(ranges, 3);
+
+ // Assert
+ Assert.Collection(normalizedRanges,
+ range =>
+ {
+ Assert.Equal(0, range.From);
+ Assert.Equal(0, range.To);
+ },
+ range =>
+ {
+ Assert.Equal(1, range.From);
+ Assert.Equal(2, range.To);
+ });
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void ParseRange_ReturnsNullWhenRangeHeaderNotProvided(string range)
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers[HeaderNames.Range] = range;
+
+ // Act
+ var parsedRangeResult = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), new DateTimeOffset(), null);
+
+ // Assert
+ Assert.Null(parsedRangeResult);
+ }
+
+ [Theory]
+ [InlineData("1-2, 3-4")]
+ [InlineData("1-2, ")]
+ public void ParseRange_ReturnsNullWhenMultipleRangesProvidedInRangeHeader(string range)
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers[HeaderNames.Range] = range;
+
+ // Act
+ var parsedRangeResult = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), new DateTimeOffset(), null);
+
+ // Assert
+ Assert.Null(parsedRangeResult);
+ }
+
+ [Fact]
+ public void ParseRange_ReturnsNullWhenLastModifiedGreaterThanIfRangeHeaderLastModified()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var range = new RangeHeaderValue(1, 2);
+ httpContext.Request.Headers[HeaderNames.Range] = range.ToString();
+ var lastModified = new RangeConditionHeaderValue(DateTime.Now);
+ httpContext.Request.Headers[HeaderNames.IfRange] = lastModified.ToString();
+
+ // Act
+ var parsedRangeResult = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), DateTime.Now.AddMilliseconds(2), null);
+
+ // Assert
+ Assert.Null(parsedRangeResult);
+ }
+
+ [Fact]
+ public void ParseRange_ReturnsNullWhenETagNotEqualToIfRangeHeaderEntityTag()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var range = new RangeHeaderValue(1, 2);
+ httpContext.Request.Headers[HeaderNames.Range] = range.ToString();
+ var etag = new RangeConditionHeaderValue("\"tag\"");
+ httpContext.Request.Headers[HeaderNames.IfRange] = etag.ToString();
+
+ // Act
+ var parsedRangeResult = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), DateTime.Now, new EntityTagHeaderValue("\"etag\""));
+
+ // Assert
+ Assert.Null(parsedRangeResult);
+ }
+
+ [Fact]
+ public void ParseRange_ReturnsSingleRangeWhenInputValid()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var range = new RangeHeaderValue(1, 2);
+ httpContext.Request.Headers[HeaderNames.Range] = range.ToString();
+ var lastModified = new RangeConditionHeaderValue(DateTime.Now);
+ httpContext.Request.Headers[HeaderNames.IfRange] = lastModified.ToString();
+ var etag = new RangeConditionHeaderValue("\"etag\"");
+ httpContext.Request.Headers[HeaderNames.IfRange] = etag.ToString();
+
+ // Act
+ var parsedRangeResult = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders(), DateTime.Now, new EntityTagHeaderValue("\"etag\""));
+
+ // Assert
+ var parsedRange = Assert.Single(parsedRangeResult);
+ Assert.Equal(1, parsedRange.From);
+ Assert.Equal(2, parsedRange.To);
+ }
+
+ [Fact]
+ public void ParseRange_ReturnsRangeWhenLastModifiedAndEtagNull()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ var range = new RangeHeaderValue(1, 2);
+ httpContext.Request.Headers[HeaderNames.Range] = range.ToString();
+ var lastModified = new RangeConditionHeaderValue(DateTime.Now);
+ httpContext.Request.Headers[HeaderNames.IfRange] = lastModified.ToString();
+
+ // Act
+ var parsedRangeResult = RangeHelper.ParseRange(httpContext, httpContext.Request.GetTypedHeaders());
+
+ // Assert
+ var parsedRange = Assert.Single(parsedRangeResult);
+ Assert.Equal(1, parsedRange.From);
+ Assert.Equal(2, parsedRange.To);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj b/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj
index a7ddfc54f9..bd040ecd20 100644
--- a/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj
+++ b/test/Microsoft.AspNetCore.StaticFiles.FunctionalTests/Microsoft.AspNetCore.StaticFiles.FunctionalTests.csproj
@@ -36,4 +36,8 @@
+
+
+
+
diff --git a/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj b/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj
index e15ef238f3..f7d7201059 100644
--- a/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj
+++ b/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj
@@ -29,4 +29,8 @@
+
+
+
+