internal static bool ContentIsNotModified(ResponseCachingContext context) { var cachedResponseHeaders = context.CachedResponseHeaders; var ifNoneMatchHeader = context.HttpContext.Request.Headers[HeaderNames.IfNoneMatch]; if (!StringValues.IsNullOrEmpty(ifNoneMatchHeader)) { if (ifNoneMatchHeader.Count == 1 && StringSegment.Equals(ifNoneMatchHeader[0], EntityTagHeaderValue.Any.Tag, StringComparison.OrdinalIgnoreCase)) { context.Logger.NotModifiedIfNoneMatchStar(); return(true); } EntityTagHeaderValue eTag; IList <EntityTagHeaderValue> ifNoneMatchEtags; if (!StringValues.IsNullOrEmpty(cachedResponseHeaders[HeaderNames.ETag]) && EntityTagHeaderValue.TryParse(cachedResponseHeaders[HeaderNames.ETag].ToString(), out eTag) && EntityTagHeaderValue.TryParseList(ifNoneMatchHeader, out ifNoneMatchEtags)) { for (var i = 0; i < ifNoneMatchEtags.Count; i++) { var requestETag = ifNoneMatchEtags[i]; if (eTag.Compare(requestETag, useStrongComparison: false)) { context.Logger.NotModifiedIfNoneMatchMatched(requestETag); return(true); } } } } else { var ifModifiedSince = context.HttpContext.Request.Headers[HeaderNames.IfModifiedSince]; if (!StringValues.IsNullOrEmpty(ifModifiedSince)) { DateTimeOffset modified; if (!HeaderUtilities.TryParseDate(cachedResponseHeaders[HeaderNames.LastModified].ToString(), out modified) && !HeaderUtilities.TryParseDate(cachedResponseHeaders[HeaderNames.Date].ToString(), out modified)) { return(false); } DateTimeOffset modifiedSince; if (HeaderUtilities.TryParseDate(ifModifiedSince.ToString(), out modifiedSince) && modified <= modifiedSince) { context.Logger.NotModifiedIfModifiedSinceSatisfied(modified, modifiedSince); return(true); } } } return(false); }
private static TimeSpan?GetCacheTtl(RequestContext requestContext) { var response = requestContext.Response; // Only consider kernel-mode caching if the Cache-Control response header is present. var cacheControlHeader = response.Headers[HeaderNames.CacheControl]; if (string.IsNullOrEmpty(cacheControlHeader)) { return(null); } // Before we check the header value, check for the existence of other headers which would // make us *not* want to cache the response. if (response.Headers.ContainsKey(HeaderNames.SetCookie) || response.Headers.ContainsKey(HeaderNames.Vary) || response.Headers.ContainsKey(HeaderNames.Pragma)) { return(null); } // We require 'public' and 's-max-age' or 'max-age' or the Expires header. CacheControlHeaderValue cacheControl; if (CacheControlHeaderValue.TryParse(cacheControlHeader, out cacheControl) && cacheControl.Public) { if (cacheControl.SharedMaxAge.HasValue) { return(cacheControl.SharedMaxAge); } else if (cacheControl.MaxAge.HasValue) { return(cacheControl.MaxAge); } DateTimeOffset expirationDate; if (HeaderUtilities.TryParseDate(response.Headers[HeaderNames.Expires], out expirationDate)) { var expiresOffset = expirationDate - DateTimeOffset.UtcNow; if (expiresOffset > TimeSpan.Zero) { return(expiresOffset); } } } return(null); }
public virtual bool IsCachedEntryFresh(ResponseCachingContext context) { var age = context.CachedEntryAge !.Value; var cachedCacheControlHeaders = context.CachedResponseHeaders.CacheControl; var requestCacheControlHeaders = context.HttpContext.Request.Headers.CacheControl; // Add min-fresh requirements if (HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MinFreshString, out var minFresh)) { age += minFresh.Value; context.Logger.ExpirationMinFreshAdded(minFresh.Value); } // Validate shared max age, this overrides any max age settings for shared caches TimeSpan?cachedSharedMaxAge; HeaderUtilities.TryParseSeconds(cachedCacheControlHeaders, CacheControlHeaderValue.SharedMaxAgeString, out cachedSharedMaxAge); if (age >= cachedSharedMaxAge) { // shared max age implies must revalidate context.Logger.ExpirationSharedMaxAgeExceeded(age, cachedSharedMaxAge.Value); return(false); } else if (!cachedSharedMaxAge.HasValue) { 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 or proxy revalidate if (HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.MustRevalidateString) || HeaderUtilities.ContainsCacheDirective(cachedCacheControlHeaders, CacheControlHeaderValue.ProxyRevalidateString)) { context.Logger.ExpirationMustRevalidate(age, lowestMaxAge.Value); return(false); } TimeSpan?requestMaxStale; var maxStaleExist = HeaderUtilities.ContainsCacheDirective(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString); HeaderUtilities.TryParseSeconds(requestCacheControlHeaders, CacheControlHeaderValue.MaxStaleString, out requestMaxStale); // Request allows stale values with no age limit if (maxStaleExist && !requestMaxStale.HasValue) { context.Logger.ExpirationInfiniteMaxStaleSatisfied(age, lowestMaxAge.Value); return(true); } // Request allows stale values with age limit if (requestMaxStale.HasValue && age - lowestMaxAge < requestMaxStale) { context.Logger.ExpirationMaxStaleSatisfied(age, lowestMaxAge.Value, requestMaxStale.Value); return(true); } context.Logger.ExpirationMaxAgeExceeded(age, lowestMaxAge.Value); return(false); } else if (!cachedMaxAge.HasValue && !requestMaxAge.HasValue) { // Validate expiration DateTimeOffset expires; if (HeaderUtilities.TryParseDate(context.CachedResponseHeaders.Expires.ToString(), out expires) && context.ResponseTime !.Value >= expires) { context.Logger.ExpirationExpiresExceeded(context.ResponseTime.Value, expires); return(false); } } } return(true); }
public void TryParse_SetOfInvalidValueStrings_ReturnsFalse(string input) { Assert.False(HeaderUtilities.TryParseDate(input, out var result)); Assert.Equal(new DateTimeOffset(), result); }