зеркало из https://github.com/aspnet/StaticFiles.git
Родитель
ff89987243
Коммит
033c8adc3a
|
@ -1,7 +1,13 @@
|
|||
{
|
||||
"Default": {
|
||||
"rules": [
|
||||
"DefaultCompositeRule"
|
||||
"adx-nonshipping": {
|
||||
"rules": [],
|
||||
"packages": {
|
||||
"Microsoft.AspNetCore.RangeHelper.Sources": {}
|
||||
}
|
||||
},
|
||||
"Default": {
|
||||
"rules": [
|
||||
"DefaultCompositeRule"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a parser for the Range Header in an <see cref="HttpContext.Request"/>.
|
||||
/// </summary>
|
||||
internal static class RangeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the requested range if the Range Header in the <see cref="HttpContext.Request"/> is valid.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="HttpContext"/> associated with the request.</param>
|
||||
/// <param name="requestHeaders">The <see cref="RequestHeaders"/> associated with the given <paramref name="context"/>.</param>
|
||||
/// <param name="lastModified">The <see cref="DateTimeOffset"/> representation of the last modified date of the file.</param>
|
||||
/// <param name="etag">The <see cref="EntityTagHeaderValue"/> provided in the <see cref="HttpContext.Request"/>.</param>
|
||||
/// <returns>A collection of <see cref="RangeItemHeaderValue"/> containing the ranges parsed from the <paramref name="requestHeaders"/>.</returns>
|
||||
public static ICollection<RangeItemHeaderValue> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A helper method to normalize a collection of <see cref="RangeItemHeaderValue"/>s.
|
||||
/// </summary>
|
||||
/// <param name="ranges">A collection of <see cref="RangeItemHeaderValue"/> to normalize.</param>
|
||||
/// <param name="length">The length of the provided <see cref="RangeItemHeaderValue"/>.</param>
|
||||
/// <returns>A normalized list of <see cref="RangeItemHeaderValue"/>s.</returns>
|
||||
// 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<RangeItemHeaderValue> NormalizeRanges(ICollection<RangeItemHeaderValue> ranges, long length)
|
||||
{
|
||||
if (ranges == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ranges.Count == 0)
|
||||
{
|
||||
return Array.Empty<RangeItemHeaderValue>();
|
||||
}
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<RangeItemHeaderValue>();
|
||||
}
|
||||
|
||||
var normalizedRanges = new List<RangeItemHeaderValue>(ranges.Count);
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var normalizedRange = NormalizeRange(range, length);
|
||||
|
||||
if (normalizedRange != null)
|
||||
{
|
||||
normalizedRanges.Add(normalizedRange);
|
||||
}
|
||||
}
|
||||
|
||||
return normalizedRanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A helper method to normalize a <see cref="RangeItemHeaderValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="range">The <see cref="RangeItemHeaderValue"/> to normalize.</param>
|
||||
/// <param name="length">The length of the provided <see cref="RangeItemHeaderValue"/>.</param>
|
||||
/// <returns>A normalized <see cref="RangeItemHeaderValue"/>.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RangeItemHeaderValue> NormalizeRanges(ICollection<RangeItemHeaderValue> ranges, long length)
|
||||
{
|
||||
IList<RangeItemHeaderValue> normalizedRanges = new List<RangeItemHeaderValue>(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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ namespace Microsoft.AspNetCore.StaticFiles
|
|||
private static Action<ILogger, StringValues, string, Exception> _logSendingFileRange;
|
||||
private static Action<ILogger, StringValues, string, Exception> _logCopyingFileRange;
|
||||
private static Action<ILogger, long, string, string, Exception> _logCopyingBytesToResponse;
|
||||
private static Action<ILogger, string, Exception> _logMultipleFileRanges;
|
||||
private static Action<ILogger, Exception> _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<string>(
|
||||
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);
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
<PackageTags>aspnetcore;staticfiles</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\shared\Microsoft.AspNetCore.RangeHelper.Sources\**\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(AspNetCoreVersion)" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
|
||||
<RuntimeIdentifier Condition="'$(TargetFramework)'!='netcoreapp2.0'">win7-x64</RuntimeIdentifier>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\shared\Microsoft.AspNetCore.RangeHelper.Sources\**\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(AspNetCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(AspNetCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -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<RangeItemHeaderValue>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,4 +36,8 @@
|
|||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -29,4 +29,8 @@
|
|||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Загрузка…
Ссылка в новой задаче