From 6e7467caf344e6fe7f64efe4870d012b9d86c6e5 Mon Sep 17 00:00:00 2001 From: Jass Bagga Date: Fri, 7 Apr 2017 11:44:03 -0700 Subject: [PATCH 1/3] Add RangeHelper --- NuGetPackageVerifier.json | 12 +- StaticFiles.sln | 25 +- .../RangeHelper.cs | 163 +++++++++++ ...AspNetCore.RangeHelper.Sources.Test.csproj | 29 ++ .../RangeHelperTests.cs | 254 ++++++++++++++++++ ...NetCore.StaticFiles.FunctionalTests.csproj | 4 + ...rosoft.AspNetCore.StaticFiles.Tests.csproj | 4 + 7 files changed, 487 insertions(+), 4 deletions(-) create mode 100644 shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs create mode 100644 test/Microsoft.AspNetCore.RangeHelper.Sources.Test/Microsoft.AspNetCore.RangeHelper.Sources.Test.csproj create mode 100644 test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index b153ab1..4328f86 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 bead941..3cd367a 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 0000000..1d0dac2 --- /dev/null +++ b/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs @@ -0,0 +1,163 @@ +// 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.RangeHelper.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 != null && 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.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/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 0000000..3d9ff67 --- /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 0000000..45e8d9d --- /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.RangeHelper.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 a7ddfc5..bd040ec 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 e15ef23..f7d7201 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 @@ + + + + From 6d9c3110abbc76e98e707730e8f88a30f4b9b58a Mon Sep 17 00:00:00 2001 From: Jass Bagga Date: Fri, 7 Apr 2017 12:27:39 -0700 Subject: [PATCH 2/3] PR feedback addressed --- .../RangeHelper.cs | 9 ++- .../Infrastructure/RangeHelpers.cs | 59 ------------------- .../Microsoft.AspNetCore.StaticFiles.csproj | 4 ++ .../StaticFileContext.cs | 59 +------------------ .../RangeHelperTests.cs | 2 +- 5 files changed, 15 insertions(+), 118 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs diff --git a/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs b/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs index 1d0dac2..4583895 100644 --- a/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs +++ b/shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -namespace Microsoft.AspNetCore.RangeHelper.Internal +namespace Microsoft.AspNetCore.Internal { /// /// Provides a parser for the Range Header in an . @@ -63,7 +63,7 @@ public static ICollection ParseRange(HttpContext context, bool ignoreRangeHeader = false; if (ifRangeHeader.LastModified.HasValue) { - if (lastModified != null && lastModified > ifRangeHeader.LastModified) + if (lastModified.HasValue && lastModified > ifRangeHeader.LastModified) { ignoreRangeHeader = true; } @@ -94,6 +94,11 @@ public static ICollection ParseRange(HttpContext context, // 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(); diff --git a/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs b/src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs deleted file mode 100644 index 2423018..0000000 --- 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/Microsoft.AspNetCore.StaticFiles.csproj b/src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj index 6e14838..53c869c 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 2a80e2c..7c6e8f8 100644 --- a/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs +++ b/src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs @@ -13,10 +13,9 @@ 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 @@ private void ComputeRange() 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/RangeHelperTests.cs b/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs index 45e8d9d..2417b0d 100644 --- a/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs +++ b/test/Microsoft.AspNetCore.RangeHelper.Sources.Test/RangeHelperTests.cs @@ -7,7 +7,7 @@ using Microsoft.Net.Http.Headers; using Xunit; -namespace Microsoft.AspNetCore.RangeHelper.Internal +namespace Microsoft.AspNetCore.Internal { public class RangeHelperTests { From 68c39f2ee4568ab2017564f83d5c05c8482b41f3 Mon Sep 17 00:00:00 2001 From: Jass Bagga Date: Fri, 7 Apr 2017 12:43:38 -0700 Subject: [PATCH 3/3] Remove log message --- .../LoggerExtensions.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs b/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs index 726fb2e..7fa6e35 100644 --- a/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs @@ -24,7 +24,6 @@ internal static class LoggerExtensions 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 @@ static LoggerExtensions() 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 @@ public static void LogCopyingBytesToResponse(this ILogger logger, long start, lo 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);