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

Header parsing #70

Merged
merged 2 commits into from
Dec 9, 2016
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public MemoryResponseCache(IMemoryCache cache)
public Task<IResponseCacheEntry> GetAsync(string key)
{
var entry = _cache.Get(key);

var memoryCachedResponse = entry as MemoryCachedResponse;
if (memoryCachedResponse != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Headers;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
public class ResponseCachingContext
{
private static readonly CacheControlHeaderValue EmptyCacheControl = new CacheControlHeaderValue();

private RequestHeaders _requestHeaders;
private ResponseHeaders _responseHeaders;
private CacheControlHeaderValue _requestCacheControl;
private CacheControlHeaderValue _responseCacheControl;
private DateTimeOffset? _responseDate;
private bool _parsedResponseDate;
private DateTimeOffset? _responseExpires;
private bool _parsedResponseExpires;
private TimeSpan? _responseSharedMaxAge;
private bool _parsedResponseSharedMaxAge;
private TimeSpan? _responseMaxAge;
private bool _parsedResponseMaxAge;

internal ResponseCachingContext(HttpContext httpContext, ILogger logger)
{
Expand Down Expand Up @@ -58,85 +55,79 @@ internal ResponseCachingContext(HttpContext httpContext, ILogger logger)

internal IHttpSendFileFeature OriginalSendFileFeature { get; set; }

internal ResponseHeaders CachedResponseHeaders { get; set; }
internal IHeaderDictionary CachedResponseHeaders { get; set; }

internal RequestHeaders TypedRequestHeaders
{
get
{
if (_requestHeaders == null)
{
_requestHeaders = HttpContext.Request.GetTypedHeaders();
}
return _requestHeaders;
}
}

internal ResponseHeaders TypedResponseHeaders
internal DateTimeOffset? ResponseDate
{
get
{
if (_responseHeaders == null)
if (!_parsedResponseDate)
{
_responseHeaders = HttpContext.Response.GetTypedHeaders();
_parsedResponseDate = true;
DateTimeOffset date;
if (HeaderUtilities.TryParseDate(HttpContext.Response.Headers[HeaderNames.Date], out date))
{
_responseDate = date;
}
else
{
_responseDate = null;
}
}
return _responseHeaders;
return _responseDate;
}
}

internal CacheControlHeaderValue RequestCacheControlHeaderValue
{
get
set
{
if (_requestCacheControl == null)
{
_requestCacheControl = TypedRequestHeaders.CacheControl ?? EmptyCacheControl;
}
return _requestCacheControl;
// Don't reparse the response date again if it's explicitly set
_parsedResponseDate = true;
_responseDate = value;
}
}

internal CacheControlHeaderValue ResponseCacheControlHeaderValue
internal DateTimeOffset? ResponseExpires
{
get
{
if (_responseCacheControl == null)
if (!_parsedResponseExpires)
{
_responseCacheControl = TypedResponseHeaders.CacheControl ?? EmptyCacheControl;
_parsedResponseExpires = true;
DateTimeOffset expires;
if (HeaderUtilities.TryParseDate(HttpContext.Response.Headers[HeaderNames.Expires], out expires))
{
_responseExpires = expires;
}
else
{
_responseExpires = null;
}
}
return _responseCacheControl;
return _responseExpires;
}
}

internal DateTimeOffset? ResponseDate
internal TimeSpan? ResponseSharedMaxAge
{
get
{
if (!_parsedResponseDate)
if (!_parsedResponseSharedMaxAge)
{
_parsedResponseDate = true;
_responseDate = TypedResponseHeaders.Date;
_parsedResponseSharedMaxAge = true;
HeaderUtilities.TryParseSeconds(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.SharedMaxAgeString, out _responseSharedMaxAge);
}
return _responseDate;
}
set
{
// Don't reparse the response date again if it's explicitly set
_parsedResponseDate = true;
_responseDate = value;
return _responseSharedMaxAge;
}
}

internal DateTimeOffset? ResponseExpires
internal TimeSpan? ResponseMaxAge
{
get
{
if (!_parsedResponseExpires)
if (!_parsedResponseMaxAge)
{
_parsedResponseExpires = true;
_responseExpires = TypedResponseHeaders.Expires;
_parsedResponseMaxAge = true;
HeaderUtilities.TryParseSeconds(HttpContext.Response.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.MaxAgeString, out _responseMaxAge);
}
return _responseExpires;
return _responseMaxAge;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
public class ResponseCachingPolicyProvider : IResponseCachingPolicyProvider
{
private static readonly CacheControlHeaderValue EmptyCacheControl = new CacheControlHeaderValue();

public virtual bool IsRequestCacheable(ResponseCachingContext context)
{
// Verify the method
Expand All @@ -32,7 +30,7 @@ public virtual bool IsRequestCacheable(ResponseCachingContext context)
// Verify request cache-control parameters
if (!StringValues.IsNullOrEmpty(request.Headers[HeaderNames.CacheControl]))
{
if (context.RequestCacheControlHeaderValue.NoCache)
if (HeaderUtilities.ContainsCacheDirective(request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoCacheString))
{
context.Logger.LogRequestWithNoCacheNotCacheable();
return false;
Expand All @@ -42,13 +40,10 @@ public virtual bool IsRequestCacheable(ResponseCachingContext context)
{
// Support for legacy HTTP 1.0 cache directive
var pragmaHeaderValues = request.Headers[HeaderNames.Pragma];
foreach (var directive in pragmaHeaderValues)
if (HeaderUtilities.ContainsCacheDirective(request.Headers[HeaderNames.Pragma], CacheControlHeaderValue.NoCacheString))
{
if (string.Equals("no-cache", directive, StringComparison.OrdinalIgnoreCase))
{
context.Logger.LogRequestWithPragmaNoCacheNotCacheable();
return false;
}
context.Logger.LogRequestWithPragmaNoCacheNotCacheable();
return false;
}
}

Expand All @@ -57,22 +52,25 @@ public virtual bool IsRequestCacheable(ResponseCachingContext context)

public virtual bool IsResponseCacheable(ResponseCachingContext context)
{
var responseCacheControlHeader = context.HttpContext.Response.Headers[HeaderNames.CacheControl];

// Only cache pages explicitly marked with public
if (!context.ResponseCacheControlHeaderValue.Public)
if (!HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.PublicString))
{
context.Logger.LogResponseWithoutPublicNotCacheable();
return false;
}

// Check no-store
if (context.RequestCacheControlHeaderValue.NoStore || context.ResponseCacheControlHeaderValue.NoStore)
if (HeaderUtilities.ContainsCacheDirective(context.HttpContext.Request.Headers[HeaderNames.CacheControl], CacheControlHeaderValue.NoStoreString)
|| HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoStoreString))
{
context.Logger.LogResponseWithNoStoreNotCacheable();
return false;
}

// Check no-cache
if (context.ResponseCacheControlHeaderValue.NoCache)
if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.NoCacheString))
{
context.Logger.LogResponseWithNoCacheNotCacheable();
return false;
Expand All @@ -96,7 +94,7 @@ public virtual bool IsResponseCacheable(ResponseCachingContext context)
}

// Check private
if (context.ResponseCacheControlHeaderValue.Private)
if (HeaderUtilities.ContainsCacheDirective(responseCacheControlHeader, CacheControlHeaderValue.PrivateString))
{
context.Logger.LogResponseWithPrivateNotCacheable();
return false;
Expand All @@ -112,8 +110,8 @@ public virtual bool IsResponseCacheable(ResponseCachingContext context)
// Check response freshness
if (!context.ResponseDate.HasValue)
{
if (!context.ResponseCacheControlHeaderValue.SharedMaxAge.HasValue &&
!context.ResponseCacheControlHeaderValue.MaxAge.HasValue &&
if (!context.ResponseSharedMaxAge.HasValue &&
!context.ResponseMaxAge.HasValue &&
context.ResponseTime.Value >= context.ResponseExpires)
{
context.Logger.LogExpirationExpiresExceeded(context.ResponseTime.Value, context.ResponseExpires.Value);
Expand All @@ -125,22 +123,20 @@ public virtual bool IsResponseCacheable(ResponseCachingContext context)
var age = context.ResponseTime.Value - context.ResponseDate.Value;

// Validate shared max age
var sharedMaxAge = context.ResponseCacheControlHeaderValue.SharedMaxAge;
if (age >= sharedMaxAge)
if (age >= context.ResponseSharedMaxAge)
{
context.Logger.LogExpirationSharedMaxAgeExceeded(age, sharedMaxAge.Value);
context.Logger.LogExpirationSharedMaxAgeExceeded(age, context.ResponseSharedMaxAge.Value);
return false;
}
else if (!sharedMaxAge.HasValue)
else if (!context.ResponseSharedMaxAge.HasValue)
{
// Validate max age
var maxAge = context.ResponseCacheControlHeaderValue.MaxAge;
if (age >= maxAge)
if (age >= context.ResponseMaxAge)
{
context.Logger.LogExpirationMaxAgeExceeded(age, maxAge.Value);
context.Logger.LogExpirationMaxAgeExceeded(age, context.ResponseMaxAge.Value);
return false;
}
else if (!maxAge.HasValue)
else if (!context.ResponseMaxAge.HasValue)
{
// Validate expiration
if (context.ResponseTime.Value >= context.ResponseExpires)
Expand All @@ -158,44 +154,53 @@ public virtual bool IsResponseCacheable(ResponseCachingContext context)
public virtual bool IsCachedEntryFresh(ResponseCachingContext context)
{
var age = context.CachedEntryAge.Value;
var cachedControlHeaders = context.CachedResponseHeaders.CacheControl ?? EmptyCacheControl;
var cachedCacheControlHeaders = context.CachedResponseHeaders[HeaderNames.CacheControl];
var requestCacheControlHeaders = context.HttpContext.Request.Headers[HeaderNames.CacheControl];

// Add min-fresh requirements
var minFresh = context.RequestCacheControlHeaderValue.MinFresh;
if (minFresh.HasValue)
TimeSpan? minFresh;
if (HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MinFreshString, out minFresh))
{
age += minFresh.Value;
context.Logger.LogExpirationMinFreshAdded(minFresh.Value);
}

// Validate shared max age, this overrides any max age settings for shared caches
var sharedMaxAge = cachedControlHeaders.SharedMaxAge;
if (age >= sharedMaxAge)
TimeSpan? cachedSharedMaxAge;
HeaderUtilities.TryParseSeconds(cachedCacheControlHeaders, CacheControlHeaderValue.SharedMaxAgeString, out cachedSharedMaxAge);

if (age >= cachedSharedMaxAge)
{
// shared max age implies must revalidate
context.Logger.LogExpirationSharedMaxAgeExceeded(age, sharedMaxAge.Value);
context.Logger.LogExpirationSharedMaxAgeExceeded(age, cachedSharedMaxAge.Value);
return false;
}
else if (!sharedMaxAge.HasValue)
else if (!cachedSharedMaxAge.HasValue)
{
var cachedMaxAge = cachedControlHeaders.MaxAge;
var requestMaxAge = context.RequestCacheControlHeaderValue.MaxAge;
TimeSpan? requestMaxAge;
HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxAgeString, out requestMaxAge);

TimeSpan? cachedMaxAge;
HeaderUtilities.TryParseSeconds(cachedCacheControlHeaders, CacheControlHeaderValue.MaxAgeString, out cachedMaxAge);

var lowestMaxAge = cachedMaxAge < requestMaxAge ? cachedMaxAge : requestMaxAge ?? cachedMaxAge;
// Validate max age
if (age >= lowestMaxAge)
{
// Must revalidate
if (cachedControlHeaders.MustRevalidate)
if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString))
{
context.Logger.LogExpirationMustRevalidate(age, lowestMaxAge.Value);
return false;
}

TimeSpan? requestMaxStale;
HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString, out requestMaxStale);

// Request allows stale values
var maxStaleLimit = context.RequestCacheControlHeaderValue.MaxStaleLimit;
if (maxStaleLimit.HasValue && age - lowestMaxAge < maxStaleLimit)
if (requestMaxStale.HasValue && age - lowestMaxAge < requestMaxStale)
{
context.Logger.LogExpirationMaxStaleSatisfied(age, lowestMaxAge.Value, maxStaleLimit.Value);
context.Logger.LogExpirationMaxStaleSatisfied(age, lowestMaxAge.Value, requestMaxStale.Value);
return true;
}

Expand All @@ -205,11 +210,11 @@ public virtual bool IsCachedEntryFresh(ResponseCachingContext context)
else if (!cachedMaxAge.HasValue && !requestMaxAge.HasValue)
{
// Validate expiration
var responseTime = context.ResponseTime.Value;
var expires = context.CachedResponseHeaders.Expires;
if (responseTime >= expires)
DateTimeOffset expires;
if (HeaderUtilities.TryParseDate(context.CachedResponseHeaders[HeaderNames.Expires], out expires) &&
context.ResponseTime.Value >= expires)
{
context.Logger.LogExpirationExpiresExceeded(responseTime, expires.Value);
context.Logger.LogExpirationExpiresExceeded(context.ResponseTime.Value, expires);
return false;
}
}
Expand Down
Loading