Relocate improvements to HttpAbstractions

This commit is contained in:
John Luo 2016-12-09 14:55:53 -08:00
parent 9c94a7764b
commit e01431f33c
8 changed files with 54 additions and 228 deletions

View File

@ -1,21 +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.
namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
internal class CacheControlValues
{
public const string MaxAgeString = "max-age";
public const string MaxStaleString = "max-stale";
public const string MinFreshString = "min-fresh";
public const string MustRevalidateString = "must-revalidate";
public const string NoCacheString = "no-cache";
public const string NoStoreString = "no-store";
public const string NoTransformString = "no-transform";
public const string OnlyIfCachedString = "only-if-cached";
public const string PrivateString = "private";
public const string ProxyRevalidateString = "proxy-revalidate";
public const string PublicString = "public";
public const string SharedMaxAgeString = "s-maxage";
}
}

View File

@ -1,118 +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.Globalization;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
internal static class HttpHeaderParsingHelpers
{
private static readonly string[] DateFormats = new string[] {
// "r", // RFC 1123, required output format but too strict for input
"ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
"ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
"d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
"d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
"ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
"ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
"d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
"d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone
"dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850
"dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
"ddd MMM d H:m:s yyyy", // ANSI C's asctime() format
"ddd, d MMM yyyy H:m:s zzz", // RFC 5322
"ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
"d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
"d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
};
// Try the various date formats in the order listed above.
// We should accept a wide verity of common formats, but only output RFC 1123 style dates.
internal static bool TryParseHeaderDate(string input, out DateTimeOffset result) => DateTimeOffset.TryParseExact(input, DateFormats, DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out result);
// Try to get the value of a specific header from a list of headers
// e.g. "header1=10, header2=30"
internal static bool TryParseHeaderTimeSpan(StringValues headers, string headerName, out TimeSpan? value)
{
foreach (var header in headers)
{
var index = header.IndexOf(headerName, StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
index += headerName.Length;
int seconds;
if (!TryParseHeaderInt(index, header, out seconds))
{
break;
}
value = TimeSpan.FromSeconds(seconds);
return true;
}
}
value = null;
return false;
}
internal static bool HeaderContains(StringValues headers, string headerName)
{
foreach (var header in headers)
{
var index = header.IndexOf(headerName, StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
return true;
}
}
return false;
}
private static bool TryParseHeaderInt(int startIndex, string header, out int value)
{
var found = false;
while (startIndex != header.Length)
{
var c = header[startIndex];
if (c == '=')
{
found = true;
}
else if (c != ' ')
{
--startIndex;
break;
}
++startIndex;
}
if (found && startIndex != header.Length)
{
var endIndex = startIndex + 1;
while (endIndex < header.Length)
{
var c = header[endIndex];
if ((c >= '0') && (c <= '9'))
{
endIndex++;
}
else
{
break;
}
}
var length = endIndex - (startIndex + 1);
if (length > 0)
{
value = int.Parse(header.Substring(startIndex + 1, length), NumberStyles.None, NumberFormatInfo.InvariantInfo);
return true;
}
}
value = 0;
return false;
}
}
}

View File

@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{ {
_parsedResponseDate = true; _parsedResponseDate = true;
DateTimeOffset date; DateTimeOffset date;
if (HttpHeaderParsingHelpers.TryParseHeaderDate(HttpContext.Response.Headers[HeaderNames.Date], out date)) if (HeaderUtilities.TryParseDate(HttpContext.Response.Headers[HeaderNames.Date], out date))
{ {
_responseDate = date; _responseDate = date;
} }
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{ {
_parsedResponseExpires = true; _parsedResponseExpires = true;
DateTimeOffset expires; DateTimeOffset expires;
if (HttpHeaderParsingHelpers.TryParseHeaderDate(HttpContext.Response.Headers[HeaderNames.Expires], out expires)) if (HeaderUtilities.TryParseDate(HttpContext.Response.Headers[HeaderNames.Expires], out expires))
{ {
_responseExpires = expires; _responseExpires = expires;
} }
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
if (!_parsedResponseSharedMaxAge) if (!_parsedResponseSharedMaxAge)
{ {
_parsedResponseSharedMaxAge = true; _parsedResponseSharedMaxAge = true;
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlValues.SharedMaxAgeString, out _responseSharedMaxAge); HeaderUtilities.TryParseSeconds(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.SharedMaxAgeString, out _responseSharedMaxAge);
} }
return _responseSharedMaxAge; return _responseSharedMaxAge;
} }
@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
if (!_parsedResponseMaxAge) if (!_parsedResponseMaxAge)
{ {
_parsedResponseMaxAge = true; _parsedResponseMaxAge = true;
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlValues.MaxAgeString, out _responseMaxAge); HeaderUtilities.TryParseSeconds(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.MaxAgeString, out _responseMaxAge);
} }
return _responseMaxAge; return _responseMaxAge;
} }

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
// Verify request cache-control parameters // Verify request cache-control parameters
if (!StringValues.IsNullOrEmpty(request.Headers[HeaderNames.CacheControl])) if (!StringValues.IsNullOrEmpty(request.Headers[HeaderNames.CacheControl]))
{ {
if (HttpHeaderParsingHelpers.HeaderContains(request.Headers[HeaderNames.CacheControl], CacheControlValues.NoCacheString)) if (HeaderUtilities.ContainsCacheDirective(request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoCacheString))
{ {
context.Logger.LogRequestWithNoCacheNotCacheable(); context.Logger.LogRequestWithNoCacheNotCacheable();
return false; return false;
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{ {
// Support for legacy HTTP 1.0 cache directive // Support for legacy HTTP 1.0 cache directive
var pragmaHeaderValues = request.Headers[HeaderNames.Pragma]; var pragmaHeaderValues = request.Headers[HeaderNames.Pragma];
if (HttpHeaderParsingHelpers.HeaderContains(request.Headers[HeaderNames.Pragma], CacheControlValues.NoCacheString)) if (HeaderUtilities.ContainsCacheDirective(request.Headers[HeaderNames.Pragma], CacheControlHeaderValue.NoCacheString))
{ {
context.Logger.LogRequestWithPragmaNoCacheNotCacheable(); context.Logger.LogRequestWithPragmaNoCacheNotCacheable();
return false; return false;
@ -55,27 +55,22 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
var responseCacheControlHeader = context.HttpContext.Response.Headers[HeaderNames.CacheControl]; var responseCacheControlHeader = context.HttpContext.Response.Headers[HeaderNames.CacheControl];
// Only cache pages explicitly marked with public // Only cache pages explicitly marked with public
if (!HttpHeaderParsingHelpers.HeaderContains(responseCacheControlHeader, CacheControlValues.PublicString)) if (!HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.PublicString))
{ {
context.Logger.LogResponseWithoutPublicNotCacheable(); context.Logger.LogResponseWithoutPublicNotCacheable();
return false; return false;
} }
// Check no-store // Check no-store
if (HttpHeaderParsingHelpers.HeaderContains(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlValues.NoStoreString)) if (HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString)
{ || HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoStoreString))
context.Logger.LogResponseWithNoStoreNotCacheable();
return false;
}
if (HttpHeaderParsingHelpers.HeaderContains(responseCacheControlHeader, CacheControlValues.NoStoreString))
{ {
context.Logger.LogResponseWithNoStoreNotCacheable(); context.Logger.LogResponseWithNoStoreNotCacheable();
return false; return false;
} }
// Check no-cache // Check no-cache
if (HttpHeaderParsingHelpers.HeaderContains(responseCacheControlHeader, CacheControlValues.NoCacheString)) if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoCacheString))
{ {
context.Logger.LogResponseWithNoCacheNotCacheable(); context.Logger.LogResponseWithNoCacheNotCacheable();
return false; return false;
@ -99,7 +94,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
} }
// Check private // Check private
if (HttpHeaderParsingHelpers.HeaderContains(responseCacheControlHeader, CacheControlValues.PrivateString)) if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.PrivateString))
{ {
context.Logger.LogResponseWithPrivateNotCacheable(); context.Logger.LogResponseWithPrivateNotCacheable();
return false; return false;
@ -159,12 +154,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
public virtual bool IsCachedEntryFresh(ResponseCachingContext context) public virtual bool IsCachedEntryFresh(ResponseCachingContext context)
{ {
var age = context.CachedEntryAge.Value; var age = context.CachedEntryAge.Value;
var cachedControlHeaders = context.CachedResponseHeaders[HeaderNames.CacheControl]; var cachedCacheControlHeaders = context.CachedResponseHeaders[HeaderNames.CacheControl];
var requestCacheControlHeaders = context.HttpContext.Request.Headers[HeaderNames.CacheControl]; var requestCacheControlHeaders = context.HttpContext.Request.Headers[HeaderNames.CacheControl];
// Add min-fresh requirements // Add min-fresh requirements
TimeSpan? minFresh; TimeSpan? minFresh;
if (HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(requestCacheControlHeaders, CacheControlValues.MinFreshString, out minFresh)) if (HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MinFreshString, out minFresh))
{ {
age += minFresh.Value; age += minFresh.Value;
context.Logger.LogExpirationMinFreshAdded(minFresh.Value); context.Logger.LogExpirationMinFreshAdded(minFresh.Value);
@ -172,7 +167,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
// Validate shared max age, this overrides any max age settings for shared caches // Validate shared max age, this overrides any max age settings for shared caches
TimeSpan? cachedSharedMaxAge; TimeSpan? cachedSharedMaxAge;
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(cachedControlHeaders, CacheControlValues.SharedMaxAgeString, out cachedSharedMaxAge); HeaderUtilities.TryParseSeconds(cachedCacheControlHeaders, CacheControlHeaderValue.SharedMaxAgeString, out cachedSharedMaxAge);
if (age >= cachedSharedMaxAge) if (age >= cachedSharedMaxAge)
{ {
@ -183,24 +178,24 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
else if (!cachedSharedMaxAge.HasValue) else if (!cachedSharedMaxAge.HasValue)
{ {
TimeSpan? requestMaxAge; TimeSpan? requestMaxAge;
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(requestCacheControlHeaders, CacheControlValues.MaxAgeString, out requestMaxAge); HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxAgeString, out requestMaxAge);
TimeSpan? cachedMaxAge; TimeSpan? cachedMaxAge;
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(cachedControlHeaders, CacheControlValues.MaxAgeString, out cachedMaxAge); HeaderUtilities.TryParseSeconds(cachedCacheControlHeaders, CacheControlHeaderValue.MaxAgeString, out cachedMaxAge);
var lowestMaxAge = cachedMaxAge < requestMaxAge ? cachedMaxAge : requestMaxAge ?? cachedMaxAge; var lowestMaxAge = cachedMaxAge < requestMaxAge ? cachedMaxAge : requestMaxAge ?? cachedMaxAge;
// Validate max age // Validate max age
if (age >= lowestMaxAge) if (age >= lowestMaxAge)
{ {
// Must revalidate // Must revalidate
if (HttpHeaderParsingHelpers.HeaderContains(cachedControlHeaders, CacheControlValues.MustRevalidateString)) if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString))
{ {
context.Logger.LogExpirationMustRevalidate(age, lowestMaxAge.Value); context.Logger.LogExpirationMustRevalidate(age, lowestMaxAge.Value);
return false; return false;
} }
TimeSpan? requestMaxStale; TimeSpan? requestMaxStale;
HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(requestCacheControlHeaders, CacheControlValues.MaxStaleString, out requestMaxStale); HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString, out requestMaxStale);
// Request allows stale values // Request allows stale values
if (requestMaxStale.HasValue && age - lowestMaxAge < requestMaxStale) if (requestMaxStale.HasValue && age - lowestMaxAge < requestMaxStale)
@ -216,7 +211,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{ {
// Validate expiration // Validate expiration
DateTimeOffset expires; DateTimeOffset expires;
if (HttpHeaderParsingHelpers.TryParseHeaderDate(context.CachedResponseHeaders[HeaderNames.Expires], out expires) && if (HeaderUtilities.TryParseDate(context.CachedResponseHeaders[HeaderNames.Expires], out expires) &&
context.ResponseTime.Value >= expires) context.ResponseTime.Value >= expires)
{ {
context.Logger.LogExpirationExpiresExceeded(context.ResponseTime.Value, expires); context.Logger.LogExpirationExpiresExceeded(context.ResponseTime.Value, expires);

View File

@ -2,11 +2,11 @@
// 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.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.AspNetCore.ResponseCaching.Internal; using Microsoft.AspNetCore.ResponseCaching.Internal;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
} }
} }
if (HttpHeaderParsingHelpers.HeaderContains(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlValues.OnlyIfCachedString)) if (HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.OnlyIfCachedString))
{ {
_logger.LogGatewayTimeoutServed(); _logger.LogGatewayTimeoutServed();
context.HttpContext.Response.StatusCode = StatusCodes.Status504GatewayTimeout; context.HttpContext.Response.StatusCode = StatusCodes.Status504GatewayTimeout;
@ -359,26 +359,24 @@ namespace Microsoft.AspNetCore.ResponseCaching
if (!StringValues.IsNullOrEmpty(ifNoneMatchHeader)) if (!StringValues.IsNullOrEmpty(ifNoneMatchHeader))
{ {
if (ifNoneMatchHeader.Count == 1 && ifNoneMatchHeader[0].Equals(EntityTagHeaderValue.Any.Tag)) if (ifNoneMatchHeader.Count == 1 && string.Equals(ifNoneMatchHeader[0], EntityTagHeaderValue.Any.Tag, StringComparison.OrdinalIgnoreCase))
{ {
context.Logger.LogNotModifiedIfNoneMatchStar(); context.Logger.LogNotModifiedIfNoneMatchStar();
return true; return true;
} }
if (!StringValues.IsNullOrEmpty(cachedResponseHeaders[HeaderNames.ETag])) EntityTagHeaderValue eTag;
IList<EntityTagHeaderValue> ifNoneMatchEtags;
if (!StringValues.IsNullOrEmpty(cachedResponseHeaders[HeaderNames.ETag])
&& EntityTagHeaderValue.TryParse(cachedResponseHeaders[HeaderNames.ETag], out eTag)
&& EntityTagHeaderValue.TryParseList(ifNoneMatchHeader, out ifNoneMatchEtags))
{ {
EntityTagHeaderValue eTag; foreach (var requestETag in ifNoneMatchEtags)
if (EntityTagHeaderValue.TryParse(cachedResponseHeaders[HeaderNames.ETag], out eTag))
{ {
foreach (var tag in ifNoneMatchHeader) if (eTag.Compare(requestETag, useStrongComparison: false))
{ {
EntityTagHeaderValue requestETag; context.Logger.LogNotModifiedIfNoneMatchMatched(requestETag);
if (EntityTagHeaderValue.TryParse(tag, out requestETag) && return true;
eTag.Compare(requestETag, useStrongComparison: false))
{
context.Logger.LogNotModifiedIfNoneMatchMatched(requestETag);
return true;
}
} }
} }
} }
@ -389,14 +387,14 @@ namespace Microsoft.AspNetCore.ResponseCaching
if (!StringValues.IsNullOrEmpty(ifUnmodifiedSince)) if (!StringValues.IsNullOrEmpty(ifUnmodifiedSince))
{ {
DateTimeOffset modified; DateTimeOffset modified;
if (!HttpHeaderParsingHelpers.TryParseHeaderDate(cachedResponseHeaders[HeaderNames.LastModified], out modified) && if (!HeaderUtilities.TryParseDate(cachedResponseHeaders[HeaderNames.LastModified], out modified) &&
!HttpHeaderParsingHelpers.TryParseHeaderDate(cachedResponseHeaders[HeaderNames.Date], out modified)) !HeaderUtilities.TryParseDate(cachedResponseHeaders[HeaderNames.Date], out modified))
{ {
return false; return false;
} }
DateTimeOffset unmodifiedSince; DateTimeOffset unmodifiedSince;
if (HttpHeaderParsingHelpers.TryParseHeaderDate(ifUnmodifiedSince, out unmodifiedSince) && if (HeaderUtilities.TryParseDate(ifUnmodifiedSince, out unmodifiedSince) &&
modified <= unmodifiedSince) modified <= unmodifiedSince)
{ {
context.Logger.LogNotModifiedIfUnmodifiedSinceSatisfied(modified, unmodifiedSince); context.Logger.LogNotModifiedIfUnmodifiedSinceSatisfied(modified, unmodifiedSince);

View File

@ -1,39 +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 Microsoft.AspNetCore.ResponseCaching.Internal;
using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
public class ParsingHelpersTests
{
[Theory]
[InlineData("h=1", "h", 1)]
[InlineData("header1=3, header2=10", "header1", 3)]
[InlineData("header1 =45, header2=80", "header1", 45)]
[InlineData("header1= 89 , header2=22", "header1", 89)]
[InlineData("header1= 89 , header2= 42", "header2", 42)]
void TryGetHeaderValue_Succeeds(string headerValue, string headerName, int expectedValue)
{
TimeSpan? value;
Assert.True(HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(new StringValues(headerValue), headerName, out value));
Assert.Equal(TimeSpan.FromSeconds(expectedValue), value);
}
[Theory]
[InlineData("h=", "h")]
[InlineData("header1=, header2=10", "header1")]
[InlineData("header1 , header2=80", "header1")]
[InlineData("h=10", "header")]
[InlineData("", "")]
[InlineData(null, null)]
void TryGetHeaderValue_Fails(string headerValue, string headerName)
{
TimeSpan? value;
Assert.False(HttpHeaderParsingHelpers.TryParseHeaderTimeSpan(new StringValues(headerValue), headerName, out value));
}
}
}

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.AspNetCore.ResponseCaching.Internal; using Microsoft.AspNetCore.ResponseCaching.Internal;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -251,7 +250,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
context.HttpContext.Request.Headers[HeaderNames.IfUnmodifiedSince] = HeaderUtilities.FormatDate(utcNow); context.HttpContext.Request.Headers[HeaderNames.IfUnmodifiedSince] = HeaderUtilities.FormatDate(utcNow);
context.CachedResponseHeaders[HeaderNames.LastModified] = HeaderUtilities.FormatDate(utcNow - TimeSpan.FromSeconds(10)); context.CachedResponseHeaders[HeaderNames.LastModified] = HeaderUtilities.FormatDate(utcNow - TimeSpan.FromSeconds(10));
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = new List<EntityTagHeaderValue>(new[] { new EntityTagHeaderValue("\"E1\"") }).ToString(); context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = "\"E1\"";
Assert.False(ResponseCachingMiddleware.ContentIsNotModified(context)); Assert.False(ResponseCachingMiddleware.ContentIsNotModified(context));
Assert.Empty(sink.Writes); Assert.Empty(sink.Writes);
} }
@ -262,8 +261,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var sink = new TestSink(); var sink = new TestSink();
var context = TestUtils.CreateTestContext(sink); var context = TestUtils.CreateTestContext(sink);
context.CachedResponseHeaders = new HeaderDictionary(); context.CachedResponseHeaders = new HeaderDictionary();
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = "\"E1\"";
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = new List<EntityTagHeaderValue>(new[] { new EntityTagHeaderValue("\"E1\"") }).ToString();
Assert.False(ResponseCachingMiddleware.ContentIsNotModified(context)); Assert.False(ResponseCachingMiddleware.ContentIsNotModified(context));
Assert.Empty(sink.Writes); Assert.Empty(sink.Writes);
@ -291,7 +289,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var context = TestUtils.CreateTestContext(sink); var context = TestUtils.CreateTestContext(sink);
context.CachedResponseHeaders = new HeaderDictionary(); context.CachedResponseHeaders = new HeaderDictionary();
context.CachedResponseHeaders[HeaderNames.ETag] = responseETag.ToString(); context.CachedResponseHeaders[HeaderNames.ETag] = responseETag.ToString();
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = requestETag.ToString(); context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = requestETag.ToString();
Assert.True(ResponseCachingMiddleware.ContentIsNotModified(context)); Assert.True(ResponseCachingMiddleware.ContentIsNotModified(context));
@ -306,14 +303,28 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var sink = new TestSink(); var sink = new TestSink();
var context = TestUtils.CreateTestContext(sink); var context = TestUtils.CreateTestContext(sink);
context.CachedResponseHeaders = new HeaderDictionary(); context.CachedResponseHeaders = new HeaderDictionary();
context.HttpContext.Response.Headers[HeaderNames.ETag] = new EntityTagHeaderValue("\"E2\"").ToString(); context.CachedResponseHeaders[HeaderNames.ETag] = "\"E2\"";
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = "\"E1\"";
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = new List<EntityTagHeaderValue>(new[] { new EntityTagHeaderValue("\"E1\"") }).ToString();
Assert.False(ResponseCachingMiddleware.ContentIsNotModified(context)); Assert.False(ResponseCachingMiddleware.ContentIsNotModified(context));
Assert.Empty(sink.Writes); Assert.Empty(sink.Writes);
} }
[Fact]
public void ContentIsNotModified_IfNoneMatch_MatchesAtLeastOneValue_True()
{
var sink = new TestSink();
var context = TestUtils.CreateTestContext(sink);
context.CachedResponseHeaders = new HeaderDictionary();
context.CachedResponseHeaders[HeaderNames.ETag] = "\"E2\"";
context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch] = new string[] { "\"E0\", \"E1\"", "\"E1\", \"E2\"" };
Assert.True(ResponseCachingMiddleware.ContentIsNotModified(context));
TestUtils.AssertLoggedMessages(
sink.Writes,
LoggedMessage.NotModifiedIfNoneMatchMatched);
}
[Fact] [Fact]
public async Task FinalizeCacheHeaders_DoNotUpdateShouldCacheResponse_IfResponseIsNotCacheable() public async Task FinalizeCacheHeaders_DoNotUpdateShouldCacheResponse_IfResponseIsNotCacheable()
{ {