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

Add RangeHelper #185

Merged
merged 3 commits into from
Apr 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions NuGetPackageVerifier.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
{
"Default": {
"rules": [
"DefaultCompositeRule"
"adx-nonshipping": {
"rules": [],
"packages": {
"Microsoft.AspNetCore.RangeHelper.Sources": {}
}
},
"Default": {
"rules": [
"DefaultCompositeRule"
]
}
}
25 changes: 24 additions & 1 deletion StaticFiles.sln
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
168 changes: 168 additions & 0 deletions shared/Microsoft.AspNetCore.RangeHelper.Sources/RangeHelper.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

This file was deleted.

10 changes: 0 additions & 10 deletions src/Microsoft.AspNetCore.StaticFiles/LoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ internal static class LoggerExtensions
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()
Expand Down Expand Up @@ -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<string>(
logLevel: LogLevel.Warning,
eventId: 13,
formatString: "Multiple ranges are not allowed: '{Ranges}'");
_logWriteCancelled = LoggerMessage.Define(
logLevel: LogLevel.Debug,
eventId: 14,
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)" />
Expand Down
Loading