Use strongly typed headers.

This commit is contained in:
Chris Ross 2015-01-09 11:31:59 -08:00
parent 82be0d3d4a
commit 212c264ed3
8 changed files with 70 additions and 181 deletions

View File

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

View File

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

View File

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

View File

@ -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));
}
} }
} }

View File

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

View File

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

View File

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

View File

@ -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);
} }
} }
} }