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

Commit 033c8ad

Browse files
authored
Add RangeHelper
See aspnet/Mvc#3702
1 parent ff89987 commit 033c8ad

File tree

11 files changed

+499
-129
lines changed

11 files changed

+499
-129
lines changed

NuGetPackageVerifier.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
{
2-
"Default": {
3-
"rules": [
4-
"DefaultCompositeRule"
2+
"adx-nonshipping": {
3+
"rules": [],
4+
"packages": {
5+
"Microsoft.AspNetCore.RangeHelper.Sources": {}
6+
}
7+
},
8+
"Default": {
9+
"rules": [
10+
"DefaultCompositeRule"
511
]
612
}
713
}

StaticFiles.sln

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio 15
3-
VisualStudioVersion = 15.0.26020.0
3+
VisualStudioVersion = 15.0.26228.9
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{40EE0889-960E-41B4-A3D3-9CE963EB0797}"
66
EndProject
@@ -16,6 +16,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Static
1616
EndProject
1717
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}"
1818
EndProject
19+
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}"
20+
EndProject
21+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{360DC2F8-EEB4-4C69-9784-C686EAD78279}"
22+
EndProject
23+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AspNetCore.RangeHelper.Sources", "Microsoft.AspNetCore.RangeHelper.Sources", "{DB6A1D14-B8A2-488F-9C4B-422FD45C8853}"
24+
ProjectSection(SolutionItems) = preProject
25+
shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs = shared\Microsoft.AspNetCore.RangeHelper.Sources\RangeHelper.cs
26+
EndProjectSection
27+
EndProject
1928
Global
2029
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2130
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +77,18 @@ Global
6877
{FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
6978
{FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.ActiveCfg = Release|Any CPU
7079
{FDF0539C-1F62-4B78-91B1-C687886931CA}.Release|x86.Build.0 = Release|Any CPU
80+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
82+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
83+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
84+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.ActiveCfg = Debug|Any CPU
85+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Debug|x86.Build.0 = Debug|Any CPU
86+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
87+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Any CPU.Build.0 = Release|Any CPU
88+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
89+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
90+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.ActiveCfg = Release|Any CPU
91+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA}.Release|x86.Build.0 = Release|Any CPU
7192
EndGlobalSection
7293
GlobalSection(SolutionProperties) = preSolution
7394
HideSolutionNode = FALSE
@@ -77,5 +98,7 @@ Global
7798
{092141D9-305A-4FC5-AE74-CB23982CA8D4} = {8B21A3A9-9CA6-4857-A6E0-1A3203404B60}
7899
{CC87FE7D-8F42-4BE9-A152-9625E837C1E5} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
79100
{FDF0539C-1F62-4B78-91B1-C687886931CA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
101+
{D3D752C4-4CDF-4F18-AC7F-48CB980A69DA} = {EF02AFE8-7C15-4DDB-8B2C-58A676112A98}
102+
{DB6A1D14-B8A2-488F-9C4B-422FD45C8853} = {360DC2F8-EEB4-4C69-9784-C686EAD78279}
80103
EndGlobalSection
81104
EndGlobal
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Http.Headers;
9+
using Microsoft.Extensions.Primitives;
10+
using Microsoft.Net.Http.Headers;
11+
12+
namespace Microsoft.AspNetCore.Internal
13+
{
14+
/// <summary>
15+
/// Provides a parser for the Range Header in an <see cref="HttpContext.Request"/>.
16+
/// </summary>
17+
internal static class RangeHelper
18+
{
19+
/// <summary>
20+
/// Returns the requested range if the Range Header in the <see cref="HttpContext.Request"/> is valid.
21+
/// </summary>
22+
/// <param name="context">The <see cref="HttpContext"/> associated with the request.</param>
23+
/// <param name="requestHeaders">The <see cref="RequestHeaders"/> associated with the given <paramref name="context"/>.</param>
24+
/// <param name="lastModified">The <see cref="DateTimeOffset"/> representation of the last modified date of the file.</param>
25+
/// <param name="etag">The <see cref="EntityTagHeaderValue"/> provided in the <see cref="HttpContext.Request"/>.</param>
26+
/// <returns>A collection of <see cref="RangeItemHeaderValue"/> containing the ranges parsed from the <paramref name="requestHeaders"/>.</returns>
27+
public static ICollection<RangeItemHeaderValue> ParseRange(HttpContext context, RequestHeaders requestHeaders, DateTimeOffset? lastModified = null, EntityTagHeaderValue etag = null)
28+
{
29+
var rawRangeHeader = context.Request.Headers[HeaderNames.Range];
30+
if (StringValues.IsNullOrEmpty(rawRangeHeader))
31+
{
32+
return null;
33+
}
34+
35+
// Perf: Check for a single entry before parsing it
36+
if (rawRangeHeader.Count > 1 || rawRangeHeader[0].IndexOf(',') >= 0)
37+
{
38+
// The spec allows for multiple ranges but we choose not to support them because the client may request
39+
// very strange ranges (e.g. each byte separately, overlapping ranges, etc.) that could negatively
40+
// impact the server. Ignore the header and serve the response normally.
41+
return null;
42+
}
43+
44+
var rangeHeader = requestHeaders.Range;
45+
if (rangeHeader == null)
46+
{
47+
// Invalid
48+
return null;
49+
}
50+
51+
// Already verified above
52+
Debug.Assert(rangeHeader.Ranges.Count == 1);
53+
54+
// 14.27 If-Range
55+
var ifRangeHeader = requestHeaders.IfRange;
56+
if (ifRangeHeader != null)
57+
{
58+
// If the validator given in the If-Range header field matches the
59+
// current validator for the selected representation of the target
60+
// resource, then the server SHOULD process the Range header field as
61+
// requested. If the validator does not match, the server MUST ignore
62+
// the Range header field.
63+
bool ignoreRangeHeader = false;
64+
if (ifRangeHeader.LastModified.HasValue)
65+
{
66+
if (lastModified.HasValue && lastModified > ifRangeHeader.LastModified)
67+
{
68+
ignoreRangeHeader = true;
69+
}
70+
}
71+
else if (etag != null && ifRangeHeader.EntityTag != null && !ifRangeHeader.EntityTag.Compare(etag, useStrongComparison: true))
72+
{
73+
ignoreRangeHeader = true;
74+
}
75+
76+
if (ignoreRangeHeader)
77+
{
78+
return null;
79+
}
80+
}
81+
82+
return rangeHeader.Ranges;
83+
}
84+
85+
/// <summary>
86+
/// A helper method to normalize a collection of <see cref="RangeItemHeaderValue"/>s.
87+
/// </summary>
88+
/// <param name="ranges">A collection of <see cref="RangeItemHeaderValue"/> to normalize.</param>
89+
/// <param name="length">The length of the provided <see cref="RangeItemHeaderValue"/>.</param>
90+
/// <returns>A normalized list of <see cref="RangeItemHeaderValue"/>s.</returns>
91+
// 14.35.1 Byte Ranges - If a syntactically valid byte-range-set includes at least one byte-range-spec whose
92+
// first-byte-pos is less than the current length of the entity-body, or at least one suffix-byte-range-spec
93+
// with a non-zero suffix-length, then the byte-range-set is satisfiable.
94+
// Adjusts ranges to be absolute and within bounds.
95+
public static IList<RangeItemHeaderValue> NormalizeRanges(ICollection<RangeItemHeaderValue> ranges, long length)
96+
{
97+
if (ranges == null)
98+
{
99+
return null;
100+
}
101+
102+
if (ranges.Count == 0)
103+
{
104+
return Array.Empty<RangeItemHeaderValue>();
105+
}
106+
107+
if (length == 0)
108+
{
109+
return Array.Empty<RangeItemHeaderValue>();
110+
}
111+
112+
var normalizedRanges = new List<RangeItemHeaderValue>(ranges.Count);
113+
foreach (var range in ranges)
114+
{
115+
var normalizedRange = NormalizeRange(range, length);
116+
117+
if (normalizedRange != null)
118+
{
119+
normalizedRanges.Add(normalizedRange);
120+
}
121+
}
122+
123+
return normalizedRanges;
124+
}
125+
126+
/// <summary>
127+
/// A helper method to normalize a <see cref="RangeItemHeaderValue"/>.
128+
/// </summary>
129+
/// <param name="range">The <see cref="RangeItemHeaderValue"/> to normalize.</param>
130+
/// <param name="length">The length of the provided <see cref="RangeItemHeaderValue"/>.</param>
131+
/// <returns>A normalized <see cref="RangeItemHeaderValue"/>.</returns>
132+
public static RangeItemHeaderValue NormalizeRange(RangeItemHeaderValue range, long length)
133+
{
134+
var start = range.From;
135+
var end = range.To;
136+
137+
// X-[Y]
138+
if (start.HasValue)
139+
{
140+
if (start.Value >= length)
141+
{
142+
// Not satisfiable, skip/discard.
143+
return null;
144+
}
145+
if (!end.HasValue || end.Value >= length)
146+
{
147+
end = length - 1;
148+
}
149+
}
150+
else
151+
{
152+
// suffix range "-X" e.g. the last X bytes, resolve
153+
if (end.Value == 0)
154+
{
155+
// Not satisfiable, skip/discard.
156+
return null;
157+
}
158+
159+
var bytes = Math.Min(end.Value, length);
160+
start = length - bytes;
161+
end = start + bytes - 1;
162+
}
163+
164+
var normalizedRange = new RangeItemHeaderValue(start, end);
165+
return normalizedRange;
166+
}
167+
}
168+
}

src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ internal static class LoggerExtensions
2424
private static Action<ILogger, StringValues, string, Exception> _logSendingFileRange;
2525
private static Action<ILogger, StringValues, string, Exception> _logCopyingFileRange;
2626
private static Action<ILogger, long, string, string, Exception> _logCopyingBytesToResponse;
27-
private static Action<ILogger, string, Exception> _logMultipleFileRanges;
2827
private static Action<ILogger, Exception> _logWriteCancelled;
2928

3029
static LoggerExtensions()
@@ -77,10 +76,6 @@ static LoggerExtensions()
7776
logLevel: LogLevel.Debug,
7877
eventId: 12,
7978
formatString: "Copying bytes {Start}-{End} of file {Path} to response body");
80-
_logMultipleFileRanges = LoggerMessage.Define<string>(
81-
logLevel: LogLevel.Warning,
82-
eventId: 13,
83-
formatString: "Multiple ranges are not allowed: '{Ranges}'");
8479
_logWriteCancelled = LoggerMessage.Define(
8580
logLevel: LogLevel.Debug,
8681
eventId: 14,
@@ -156,11 +151,6 @@ public static void LogCopyingBytesToResponse(this ILogger logger, long start, lo
156151
null);
157152
}
158153

159-
public static void LogMultipleFileRanges(this ILogger logger, string range)
160-
{
161-
_logMultipleFileRanges(logger, range, null);
162-
}
163-
164154
public static void LogWriteCancelled(this ILogger logger, Exception ex)
165155
{
166156
_logWriteCancelled(logger, ex);

src/Microsoft.AspNetCore.StaticFiles/Microsoft.AspNetCore.StaticFiles.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
<PackageTags>aspnetcore;staticfiles</PackageTags>
1111
</PropertyGroup>
1212

13+
<ItemGroup>
14+
<Compile Include="..\..\shared\Microsoft.AspNetCore.RangeHelper.Sources\**\*.cs" />
15+
</ItemGroup>
16+
1317
<ItemGroup>
1418
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(AspNetCoreVersion)" />
1519
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(AspNetCoreVersion)" />

0 commit comments

Comments
 (0)