public void TryParseList_WithSomeInvalidValues_ExcludesInvalidValues()
    {
        var inputs = new[]
        {
            "",
            "\"tag\", tag, \"tag\"",
            "tag, \"tag\"",
            "",
            " \"tag ",
            "\r\n tag\"\r\n ",
            "\"tag会\"",
            "\"tag\", \"tag\"",
            "W/\"tag\"",
        };

        Assert.True(EntityTagHeaderValue.TryParseList(inputs, out var results));
        var expectedResults = new[]
        {
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag会\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\"", true),
        }.ToList();

        Assert.Equal(expectedResults, results);
    }
    public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
    {
        var inputs = new[]
        {
            "",
            "\"tag\"",
            "",
            " \"tag\" ",
            "\r\n \"tag\"\r\n ",
            "\"tag会\"",
            "\"tag\",\"tag\"",
            "\"tag\", \"tag\"",
            "W/\"tag\"",
        };

        Assert.True(EntityTagHeaderValue.TryParseList(inputs, out var results));
        var expectedResults = new[]
        {
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag会\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\""),
            new EntityTagHeaderValue("\"tag\"", true),
        }.ToList();

        Assert.Equal(expectedResults, results);
    }
        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.LogNotModifiedIfNoneMatchStar();
                    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.LogNotModifiedIfNoneMatchMatched(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.LogNotModifiedIfModifiedSinceSatisfied(modified, modifiedSince);
                        return(true);
                    }
                }
            }

            return(false);
        }
        private static bool EtagEquals(HttpRequest request, EntityTagHeaderValue currentEtag)
        {
            // CORE TODO Double check this... before I found the GetTypedHeaders() extension method, I didn't think there was a typed implementation of the IfNoneMatch header
            // anymore, so I reimplemented based on the response caching middleware's implementation, leaving out the * match. See
            // https://github.com/aspnet/ResponseCaching/blob/52e8ffefb9b22c7c591aec15845baf07ed99356c/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs#L454-L478

            var ifNoneMatchHeader = request.Headers["If-None-Match"];

            if (StringValues.IsNullOrEmpty(ifNoneMatchHeader))
            {
                return(false);
            }
            return(EntityTagHeaderValue.TryParseList(ifNoneMatchHeader, out IList <Microsoft.Net.Http.Headers.EntityTagHeaderValue> ifNoneMatchEtags) &&
                   ifNoneMatchEtags.Any(t => currentEtag.Compare(t, useStrongComparison: false)));
        }
        private static bool RequestContainsMatchingETag(IHeaderDictionary requestHeaders, EntityTagHeaderValue responseETag)
        {
            if (requestHeaders.Keys.Contains(HeaderNames.IfNoneMatch) &&
                EntityTagHeaderValue.TryParseList(requestHeaders[HeaderNames.IfNoneMatch], out IList <EntityTagHeaderValue> requestETags))
            {
                foreach (EntityTagHeaderValue requestETag in requestETags)
                {
                    if (responseETag.Equals(requestETag))
                    {
                        return(true);
                    }
                }
            }

            return(false);
        }
Example #6
0
        public static async Task <ActionResult> GenerateActionResult(ControllerBase controller, ICacheableDataProvider provider, TimeSpan?maxAge = null)
        {
            const string CacheControlHeaderKey    = "Cache-Control";
            const string IfNonMatchHeaderKey      = "If-None-Match";
            const string IfModifiedSinceHeaderKey = "If-Modified-Since";
            const string ETagHeaderKey            = "ETag";
            const string LastModifiedHeaderKey    = "Last-Modified";

            string GenerateCacheControlHeaderValue()
            {
                var cacheControlHeader = new CacheControlHeaderValue()
                {
                    NoCache        = true,
                    NoStore        = false,
                    MaxAge         = maxAge ?? TimeSpan.FromDays(14),
                    Private        = true,
                    MustRevalidate = true
                };

                return(cacheControlHeader.ToString());
            }

            var digest = await provider.GetDigest();

            var eTagValue = $"\"{digest.ETag}\"";
            var eTag      = new EntityTagHeaderValue(eTagValue);

            ActionResult Generate304Result()
            {
                controller.Response.Headers.Add(ETagHeaderKey, eTagValue);
                controller.Response.Headers.Add(LastModifiedHeaderKey, digest.LastModified.ToString("R"));
                controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue());
                return(controller.StatusCode(StatusCodes.Status304NotModified, null));
            }

            if (controller.Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var ifNonMatchHeaderValue))
            {
                if (!EntityTagHeaderValue.TryParseList(ifNonMatchHeaderValue, out var eTagList))
                {
                    return(controller.BadRequest(new CommonResponse(ErrorCodes.Common.Header.IfNonMatch_BadFormat, "Header If-None-Match is of bad format.")));
                }

                if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null)
                {
                    return(Generate304Result());
                }
            }
            else if (controller.Request.Headers.TryGetValue(IfModifiedSinceHeaderKey, out var ifModifiedSinceHeaderValue))
            {
                if (!DateTime.TryParse(ifModifiedSinceHeaderValue, out var headerValue))
                {
                    return(controller.BadRequest(new CommonResponse(ErrorCodes.Common.Header.IfModifiedSince_BadFormat, "Header If-Modified-Since is of bad format.")));
                }

                if (headerValue > digest.LastModified)
                {
                    return(Generate304Result());
                }
            }

            var data = await provider.GetData();

            controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue());
            return(controller.File(data.Data, data.ContentType, digest.LastModified, eTag));
        }
 public void TryParseList_NullOrEmptyArray_ReturnsFalse()
 {
     Assert.False(EntityTagHeaderValue.TryParseList(null, out var results));
     Assert.False(EntityTagHeaderValue.TryParseList(new string[0], out results));
     Assert.False(EntityTagHeaderValue.TryParseList(new string[] { "" }, out results));
 }