Jass Bagga 2017-04-07 14:16:05 -07:00 коммит произвёл GitHub
Родитель ff89987243
Коммит 033c8adc3a
11 изменённых файлов: 499 добавлений и 129 удалений

Просмотреть файл

@ -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>