Use strongly typed headers.
This commit is contained in:
parent
82be0d3d4a
commit
212c264ed3
|
|
@ -11,21 +11,6 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
internal const string SendFileVersionKey = "sendfile.Version";
|
internal const string SendFileVersionKey = "sendfile.Version";
|
||||||
internal const string SendFileVersion = "1.0";
|
internal const string SendFileVersion = "1.0";
|
||||||
|
|
||||||
internal const string Location = "Location";
|
|
||||||
internal const string IfMatch = "If-Match";
|
|
||||||
internal const string IfNoneMatch = "If-None-Match";
|
|
||||||
internal const string IfModifiedSince = "If-Modified-Since";
|
|
||||||
internal const string IfUnmodifiedSince = "If-Unmodified-Since";
|
|
||||||
internal const string IfRange = "If-Range";
|
|
||||||
internal const string Range = "Range";
|
|
||||||
internal const string ContentRange = "Content-Range";
|
|
||||||
internal const string LastModified = "Last-Modified";
|
|
||||||
internal const string ETag = "ETag";
|
|
||||||
|
|
||||||
internal const string HttpDateFormat = "r";
|
|
||||||
|
|
||||||
internal const string TextHtmlUtf8 = "text/html; charset=utf-8";
|
|
||||||
|
|
||||||
internal const int Status200Ok = 200;
|
internal const int Status200Ok = 200;
|
||||||
internal const int Status206PartialContent = 206;
|
internal const int Status206PartialContent = 206;
|
||||||
internal const int Status304NotModified = 304;
|
internal const int Status304NotModified = 304;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.FileSystems;
|
|
||||||
using Microsoft.AspNet.Hosting;
|
using Microsoft.AspNet.Hosting;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.StaticFiles
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
{
|
{
|
||||||
|
|
@ -65,7 +63,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 301;
|
context.Response.StatusCode = 301;
|
||||||
context.Response.Headers[Constants.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
||||||
return Constants.CompletedTask;
|
return Constants.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.FileSystems;
|
using Microsoft.AspNet.FileSystems;
|
||||||
using Microsoft.AspNet.Hosting;
|
using Microsoft.AspNet.Hosting;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.StaticFiles
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
{
|
{
|
||||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 301;
|
context.Response.StatusCode = 301;
|
||||||
context.Response.Headers[Constants.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
||||||
return Constants.CompletedTask;
|
return Constants.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.StaticFiles
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
|
|
@ -45,15 +43,5 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool TryParseHttpDate(string dateString, out DateTimeOffset parsedDate)
|
|
||||||
{
|
|
||||||
return DateTimeOffset.TryParseExact(dateString, Constants.HttpDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string ResolveRootPath(string webRoot, PathString path)
|
|
||||||
{
|
|
||||||
return Path.GetFullPath(Path.Combine(webRoot, path.Value ?? string.Empty));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class HtmlDirectoryFormatter : IDirectoryFormatter
|
public class HtmlDirectoryFormatter : IDirectoryFormatter
|
||||||
{
|
{
|
||||||
|
private const string TextHtmlUtf8 = "text/html; charset=utf-8";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates an HTML view for a directory.
|
/// Generates an HTML view for a directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -32,7 +34,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
throw new ArgumentNullException("contents");
|
throw new ArgumentNullException("contents");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Response.ContentType = Constants.TextHtmlUtf8;
|
context.Response.ContentType = TextHtmlUtf8;
|
||||||
|
|
||||||
if (Helpers.IsHeadMethod(context.Request.Method))
|
if (Helpers.IsHeadMethod(context.Request.Method))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,107 +3,23 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using Microsoft.Net.Http.Headers;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
||||||
{
|
{
|
||||||
internal static class RangeHelpers
|
internal static class RangeHelpers
|
||||||
{
|
{
|
||||||
// Examples:
|
|
||||||
// bytes=0-499
|
|
||||||
// bytes=500-
|
|
||||||
// bytes=-500
|
|
||||||
// bytes=0-0,-1
|
|
||||||
// bytes=500-600,601-999
|
|
||||||
// Any individual bad range fails the whole parse and the header should be ignored.
|
|
||||||
internal static bool TryParseRanges(string rangeHeader, out IList<Tuple<long?, long?>> parsedRanges)
|
|
||||||
{
|
|
||||||
parsedRanges = null;
|
|
||||||
if (string.IsNullOrWhiteSpace(rangeHeader)
|
|
||||||
|| !rangeHeader.StartsWith("bytes=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] subRanges = rangeHeader.Substring("bytes=".Length).Replace(" ", string.Empty).Split(',');
|
|
||||||
|
|
||||||
List<Tuple<long?, long?>> ranges = new List<Tuple<long?, long?>>();
|
|
||||||
|
|
||||||
for (int i = 0; i < subRanges.Length; i++)
|
|
||||||
{
|
|
||||||
long? first = null, second = null;
|
|
||||||
string subRange = subRanges[i];
|
|
||||||
int dashIndex = subRange.IndexOf('-');
|
|
||||||
if (dashIndex < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (dashIndex == 0)
|
|
||||||
{
|
|
||||||
// -500
|
|
||||||
string remainder = subRange.Substring(1);
|
|
||||||
if (!TryParseLong(remainder, out second))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (dashIndex == (subRange.Length - 1))
|
|
||||||
{
|
|
||||||
// 500-
|
|
||||||
string remainder = subRange.Substring(0, subRange.Length - 1);
|
|
||||||
if (!TryParseLong(remainder, out first))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 0-499
|
|
||||||
string firstString = subRange.Substring(0, dashIndex);
|
|
||||||
string secondString = subRange.Substring(dashIndex + 1, subRange.Length - dashIndex - 1);
|
|
||||||
if (!TryParseLong(firstString, out first) || !TryParseLong(secondString, out second)
|
|
||||||
|| first.Value > second.Value)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges.Add(new Tuple<long?, long?>(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ranges.Count > 0)
|
|
||||||
{
|
|
||||||
parsedRanges = ranges;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryParseLong(string input, out long? result)
|
|
||||||
{
|
|
||||||
int temp;
|
|
||||||
if (!string.IsNullOrWhiteSpace(input)
|
|
||||||
&& int.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out temp))
|
|
||||||
{
|
|
||||||
result = temp;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
result = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 14.35.1 Byte Ranges - If a syntactically valid byte-range-set includes at least one byte-range-spec whose
|
// 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
|
// 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.
|
// with a non-zero suffix-length, then the byte-range-set is satisfiable.
|
||||||
// Adjusts ranges to be absolute and within bounds.
|
// Adjusts ranges to be absolute and within bounds.
|
||||||
internal static IList<Tuple<long, long>> NormalizeRanges(IList<Tuple<long?, long?>> ranges, long length)
|
internal static IList<RangeItemHeaderValue> NormalizeRanges(ICollection<RangeItemHeaderValue> ranges, long length)
|
||||||
{
|
{
|
||||||
IList<Tuple<long, long>> normalizedRanges = new List<Tuple<long, long>>(ranges.Count);
|
IList<RangeItemHeaderValue> normalizedRanges = new List<RangeItemHeaderValue>(ranges.Count);
|
||||||
for (int i = 0; i < ranges.Count; i++)
|
foreach (var range in ranges)
|
||||||
{
|
{
|
||||||
Tuple<long?, long?> range = ranges[i];
|
long? start = range.From;
|
||||||
long? start = range.Item1, end = range.Item2;
|
long? end = range.To;
|
||||||
|
|
||||||
// X-[Y]
|
// X-[Y]
|
||||||
if (start.HasValue)
|
if (start.HasValue)
|
||||||
|
|
@ -131,7 +47,7 @@ namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
||||||
start = length - bytes;
|
start = length - bytes;
|
||||||
end = start + bytes - 1;
|
end = start + bytes - 1;
|
||||||
}
|
}
|
||||||
normalizedRanges.Add(new Tuple<long, long>(start.Value, end.Value));
|
normalizedRanges.Add(new RangeItemHeaderValue(start.Value, end.Value));
|
||||||
}
|
}
|
||||||
return normalizedRanges;
|
return normalizedRanges;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
|
@ -14,4 +14,9 @@
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
<ProjectExtensions>
|
||||||
|
<VisualStudio>
|
||||||
|
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||||
|
</VisualStudio>
|
||||||
|
</ProjectExtensions>
|
||||||
|
</Project>
|
||||||
|
|
@ -4,14 +4,16 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.FileSystems;
|
using Microsoft.AspNet.FileSystems;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Http.Headers;
|
||||||
using Microsoft.AspNet.HttpFeature;
|
using Microsoft.AspNet.HttpFeature;
|
||||||
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.StaticFiles
|
namespace Microsoft.AspNet.StaticFiles
|
||||||
{
|
{
|
||||||
|
|
@ -31,16 +33,17 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
private IFileInfo _fileInfo;
|
private IFileInfo _fileInfo;
|
||||||
private long _length;
|
private long _length;
|
||||||
private DateTimeOffset _lastModified;
|
private DateTimeOffset _lastModified;
|
||||||
private string _lastModifiedString;
|
private EntityTagHeaderValue _etag;
|
||||||
private string _etag;
|
|
||||||
private string _etagQuoted;
|
private RequestHeaders _requestHeaders;
|
||||||
|
private ResponseHeaders _responseHeaders;
|
||||||
|
|
||||||
private PreconditionState _ifMatchState;
|
private PreconditionState _ifMatchState;
|
||||||
private PreconditionState _ifNoneMatchState;
|
private PreconditionState _ifNoneMatchState;
|
||||||
private PreconditionState _ifModifiedSinceState;
|
private PreconditionState _ifModifiedSinceState;
|
||||||
private PreconditionState _ifUnmodifiedSinceState;
|
private PreconditionState _ifUnmodifiedSinceState;
|
||||||
|
|
||||||
private IList<Tuple<long, long>> _ranges;
|
private IList<RangeItemHeaderValue> _ranges;
|
||||||
|
|
||||||
public StaticFileContext(HttpContext context, StaticFileOptions options, PathString matchUrl, ILogger logger)
|
public StaticFileContext(HttpContext context, StaticFileOptions options, PathString matchUrl, ILogger logger)
|
||||||
{
|
{
|
||||||
|
|
@ -50,6 +53,8 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
_request = context.Request;
|
_request = context.Request;
|
||||||
_response = context.Response;
|
_response = context.Response;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_requestHeaders = _request.GetTypedHeaders();
|
||||||
|
_responseHeaders = _response.GetTypedHeaders();
|
||||||
|
|
||||||
_method = null;
|
_method = null;
|
||||||
_isGet = false;
|
_isGet = false;
|
||||||
|
|
@ -60,8 +65,6 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
_length = 0;
|
_length = 0;
|
||||||
_lastModified = new DateTimeOffset();
|
_lastModified = new DateTimeOffset();
|
||||||
_etag = null;
|
_etag = null;
|
||||||
_etagQuoted = null;
|
|
||||||
_lastModifiedString = null;
|
|
||||||
_ifMatchState = PreconditionState.Unspecified;
|
_ifMatchState = PreconditionState.Unspecified;
|
||||||
_ifNoneMatchState = PreconditionState.Unspecified;
|
_ifNoneMatchState = PreconditionState.Unspecified;
|
||||||
_ifModifiedSinceState = PreconditionState.Unspecified;
|
_ifModifiedSinceState = PreconditionState.Unspecified;
|
||||||
|
|
@ -86,7 +89,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
{
|
{
|
||||||
get { return _ranges != null; }
|
get { return _ranges != null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SubPath
|
public string SubPath
|
||||||
{
|
{
|
||||||
get { return _subPath.Value; }
|
get { return _subPath.Value; }
|
||||||
|
|
@ -132,11 +135,9 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
DateTimeOffset last = _fileInfo.LastModified;
|
DateTimeOffset last = _fileInfo.LastModified;
|
||||||
// Truncate to the second.
|
// Truncate to the second.
|
||||||
_lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset);
|
_lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset);
|
||||||
_lastModifiedString = _lastModified.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
long etagHash = _lastModified.ToFileTime() ^ _length;
|
long etagHash = _lastModified.ToFileTime() ^ _length;
|
||||||
_etag = Convert.ToString(etagHash, 16);
|
_etag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
|
||||||
_etagQuoted = '\"' + _etag + '\"';
|
|
||||||
}
|
}
|
||||||
return _fileInfo.Exists;
|
return _fileInfo.Exists;
|
||||||
}
|
}
|
||||||
|
|
@ -153,14 +154,13 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
private void ComputeIfMatch()
|
private void ComputeIfMatch()
|
||||||
{
|
{
|
||||||
// 14.24 If-Match
|
// 14.24 If-Match
|
||||||
IList<string> ifMatch = _request.Headers.GetCommaSeparatedValues(Constants.IfMatch); // Removes quotes
|
var ifMatch = _requestHeaders.IfMatch;
|
||||||
if (ifMatch != null)
|
if (ifMatch != null && ifMatch.Any())
|
||||||
{
|
{
|
||||||
_ifMatchState = PreconditionState.PreconditionFailed;
|
_ifMatchState = PreconditionState.PreconditionFailed;
|
||||||
foreach (var segment in ifMatch)
|
foreach (var etag in ifMatch)
|
||||||
{
|
{
|
||||||
if (segment.Equals("*", StringComparison.Ordinal)
|
if (etag.Equals(EntityTagHeaderValue.Any) || etag.Equals(_etag))
|
||||||
|| segment.Equals(_etag, StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
_ifMatchState = PreconditionState.ShouldProcess;
|
_ifMatchState = PreconditionState.ShouldProcess;
|
||||||
break;
|
break;
|
||||||
|
|
@ -169,14 +169,13 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14.26 If-None-Match
|
// 14.26 If-None-Match
|
||||||
IList<string> ifNoneMatch = _request.Headers.GetCommaSeparatedValues(Constants.IfNoneMatch);
|
var ifNoneMatch = _requestHeaders.IfNoneMatch;
|
||||||
if (ifNoneMatch != null)
|
if (ifNoneMatch != null && ifNoneMatch.Any())
|
||||||
{
|
{
|
||||||
_ifNoneMatchState = PreconditionState.ShouldProcess;
|
_ifNoneMatchState = PreconditionState.ShouldProcess;
|
||||||
foreach (var segment in ifNoneMatch)
|
foreach (var etag in ifNoneMatch)
|
||||||
{
|
{
|
||||||
if (segment.Equals("*", StringComparison.Ordinal)
|
if (etag.Equals(EntityTagHeaderValue.Any) || etag.Equals(_etag))
|
||||||
|| segment.Equals(_etag, StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
_ifNoneMatchState = PreconditionState.NotModified;
|
_ifNoneMatchState = PreconditionState.NotModified;
|
||||||
break;
|
break;
|
||||||
|
|
@ -188,18 +187,16 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
private void ComputeIfModifiedSince()
|
private void ComputeIfModifiedSince()
|
||||||
{
|
{
|
||||||
// 14.25 If-Modified-Since
|
// 14.25 If-Modified-Since
|
||||||
string ifModifiedSinceString = _request.Headers.Get(Constants.IfModifiedSince);
|
var ifModifiedSince = _requestHeaders.IfModifiedSince;
|
||||||
DateTimeOffset ifModifiedSince;
|
if (ifModifiedSince.HasValue)
|
||||||
if (Helpers.TryParseHttpDate(ifModifiedSinceString, out ifModifiedSince))
|
|
||||||
{
|
{
|
||||||
bool modified = ifModifiedSince < _lastModified;
|
bool modified = ifModifiedSince < _lastModified;
|
||||||
_ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
|
_ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14.28 If-Unmodified-Since
|
// 14.28 If-Unmodified-Since
|
||||||
string ifUnmodifiedSinceString = _request.Headers.Get(Constants.IfUnmodifiedSince);
|
var ifUnmodifiedSince = _requestHeaders.IfUnmodifiedSince;
|
||||||
DateTimeOffset ifUnmodifiedSince;
|
if (ifUnmodifiedSince.HasValue)
|
||||||
if (Helpers.TryParseHttpDate(ifUnmodifiedSinceString, out ifUnmodifiedSince))
|
|
||||||
{
|
{
|
||||||
bool unmodified = ifUnmodifiedSince >= _lastModified;
|
bool unmodified = ifUnmodifiedSince >= _lastModified;
|
||||||
_ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
|
_ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
|
||||||
|
|
@ -218,44 +215,41 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string rangeHeader = _request.Headers.Get(Constants.Range);
|
var rangeHeader = _requestHeaders.Range;
|
||||||
IList<Tuple<long?, long?>> ranges;
|
if (rangeHeader == null)
|
||||||
if (!RangeHelpers.TryParseRanges(rangeHeader, out ranges))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ranges.Count > 1)
|
if (rangeHeader.Ranges.Count > 1)
|
||||||
{
|
{
|
||||||
// multiple range headers not yet supported
|
// The spec allows for multiple ranges but we choose not to support them because the client may request
|
||||||
_logger.WriteWarning("Multiple range headers not yet supported, {0} ranges in header", ranges.Count.ToString());
|
// 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.WriteWarning("Multiple ranges are not allowed: '{0}'", rangeHeader.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14.27 If-Range
|
// 14.27 If-Range
|
||||||
string ifRangeHeader = _request.Headers.Get(Constants.IfRange);
|
var ifRangeHeader = _requestHeaders.IfRange;
|
||||||
if (!string.IsNullOrWhiteSpace(ifRangeHeader))
|
if (ifRangeHeader != null)
|
||||||
{
|
{
|
||||||
// If the validator given in the If-Range header field matches the
|
// If the validator given in the If-Range header field matches the
|
||||||
// current validator for the selected representation of the target
|
// current validator for the selected representation of the target
|
||||||
// resource, then the server SHOULD process the Range header field as
|
// resource, then the server SHOULD process the Range header field as
|
||||||
// requested. If the validator does not match, the server MUST ignore
|
// requested. If the validator does not match, the server MUST ignore
|
||||||
// the Range header field.
|
// the Range header field.
|
||||||
DateTimeOffset ifRangeLastModified;
|
|
||||||
bool ignoreRangeHeader = false;
|
bool ignoreRangeHeader = false;
|
||||||
if (Helpers.TryParseHttpDate(ifRangeHeader, out ifRangeLastModified))
|
if (ifRangeHeader.LastModified.HasValue)
|
||||||
{
|
{
|
||||||
if (_lastModified > ifRangeLastModified)
|
if (_lastModified > ifRangeHeader.LastModified)
|
||||||
{
|
{
|
||||||
ignoreRangeHeader = true;
|
ignoreRangeHeader = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (ifRangeHeader.EntityTag != null && !_etag.Equals(ifRangeHeader.EntityTag))
|
||||||
{
|
{
|
||||||
if (!_etagQuoted.Equals(ifRangeHeader))
|
ignoreRangeHeader = true;
|
||||||
{
|
|
||||||
ignoreRangeHeader = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (ignoreRangeHeader)
|
if (ignoreRangeHeader)
|
||||||
{
|
{
|
||||||
|
|
@ -263,7 +257,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ranges = RangeHelpers.NormalizeRanges(ranges, _length);
|
_ranges = RangeHelpers.NormalizeRanges(rangeHeader.Ranges, _length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyResponseHeaders(int statusCode)
|
public void ApplyResponseHeaders(int statusCode)
|
||||||
|
|
@ -277,8 +271,9 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
{
|
{
|
||||||
_response.ContentType = _contentType;
|
_response.ContentType = _contentType;
|
||||||
}
|
}
|
||||||
_response.Headers.Set(Constants.LastModified, _lastModifiedString);
|
_responseHeaders.LastModified = _lastModified;
|
||||||
_response.Headers.Set(Constants.ETag, _etagQuoted);
|
_responseHeaders.ETag = _etag;
|
||||||
|
_responseHeaders.Headers[HeaderNames.AcceptRanges] = "bytes";
|
||||||
}
|
}
|
||||||
if (statusCode == Constants.Status200Ok)
|
if (statusCode == Constants.Status200Ok)
|
||||||
{
|
{
|
||||||
|
|
@ -361,7 +356,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
// 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
|
// 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
|
||||||
// SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
|
// SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
|
||||||
// the current length of the selected resource. e.g. */length
|
// the current length of the selected resource. e.g. */length
|
||||||
_response.Headers[Constants.ContentRange] = "bytes */" + _length.ToString(CultureInfo.InvariantCulture);
|
_responseHeaders.ContentRange = new ContentRangeHeaderValue(_length);
|
||||||
ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable);
|
ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable);
|
||||||
_logger.WriteWarning("Range not satisfiable for {0}", SubPath);
|
_logger.WriteWarning("Range not satisfiable for {0}", SubPath);
|
||||||
return;
|
return;
|
||||||
|
|
@ -371,7 +366,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
Debug.Assert(_ranges.Count == 1);
|
Debug.Assert(_ranges.Count == 1);
|
||||||
|
|
||||||
long start, length;
|
long start, length;
|
||||||
_response.Headers[Constants.ContentRange] = ComputeContentRange(_ranges[0], out start, out length);
|
_responseHeaders.ContentRange = ComputeContentRange(_ranges[0], out start, out length);
|
||||||
_response.ContentLength = length;
|
_response.ContentLength = length;
|
||||||
ApplyResponseHeaders(Constants.Status206PartialContent);
|
ApplyResponseHeaders(Constants.Status206PartialContent);
|
||||||
|
|
||||||
|
|
@ -381,7 +376,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
{
|
{
|
||||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
if (_logger.IsEnabled(LogLevel.Verbose))
|
||||||
{
|
{
|
||||||
_logger.WriteVerbose(string.Format("Sending {0} of file {1}", _response.Headers[Constants.ContentRange], physicalPath));
|
_logger.WriteVerbose(string.Format("Sending {0} of file {1}", _response.Headers[HeaderNames.ContentRange], physicalPath));
|
||||||
}
|
}
|
||||||
await sendFile.SendFileAsync(physicalPath, start, length, _context.RequestAborted);
|
await sendFile.SendFileAsync(physicalPath, start, length, _context.RequestAborted);
|
||||||
return;
|
return;
|
||||||
|
|
@ -393,7 +388,7 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
readStream.Seek(start, SeekOrigin.Begin); // TODO: What if !CanSeek?
|
readStream.Seek(start, SeekOrigin.Begin); // TODO: What if !CanSeek?
|
||||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
if (_logger.IsEnabled(LogLevel.Verbose))
|
||||||
{
|
{
|
||||||
_logger.WriteVerbose(string.Format("Copying {0} of file {1} to the response body", _response.Headers[Constants.ContentRange], SubPath));
|
_logger.WriteVerbose(string.Format("Copying {0} of file {1} to the response body", _response.Headers[HeaderNames.ContentRange], SubPath));
|
||||||
}
|
}
|
||||||
await StreamCopyOperation.CopyToAsync(readStream, _response.Body, length, _context.RequestAborted);
|
await StreamCopyOperation.CopyToAsync(readStream, _response.Body, length, _context.RequestAborted);
|
||||||
}
|
}
|
||||||
|
|
@ -404,12 +399,12 @@ namespace Microsoft.AspNet.StaticFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: This assumes ranges have been normalized to absolute byte offsets.
|
// Note: This assumes ranges have been normalized to absolute byte offsets.
|
||||||
private string ComputeContentRange(Tuple<long, long> range, out long start, out long length)
|
private ContentRangeHeaderValue ComputeContentRange(RangeItemHeaderValue range, out long start, out long length)
|
||||||
{
|
{
|
||||||
start = range.Item1;
|
start = range.From.Value;
|
||||||
long end = range.Item2;
|
long end = range.To.Value;
|
||||||
length = end - start + 1;
|
length = end - start + 1;
|
||||||
return string.Format(CultureInfo.InvariantCulture, "bytes {0}-{1}/{2}", start, end, _length);
|
return new ContentRangeHeaderValue(start, end, _length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue