private async Task <bool> ConditionalPUTorPATCHIsValid(HttpContext httpContext)
        {
            _logger.LogInformation("Checking for conditional PUT/PATCH.");

            // Preconditional checks are used for concurrency checks only,
            // on updates: PUT or PATCH
            if (!(httpContext.Request.Method == HttpMethod.Put.ToString() ||
                  httpContext.Request.Method == "PATCH"))
            {
                _logger.LogInformation("Not valid - method isn't PUT or PATCH.");
                // for all the other methods, return true (no 412 response)
                return(true);
            }

            // the precondition is valid if one of the ETags submitted through
            // IfMatch matches with the saved ETag, AND if the If-UnModified-Since
            // value is smaller than the saved date.  Both must be valid if both
            // are submitted.

            // If both headers are missing, we should
            // always return true (the precondition is missing, so it's valid)
            // We don't need to check anything, and can never return a 412 response
            if (!(httpContext.Request.Headers.Keys.Contains(HeaderNames.IfMatch) ||
                  httpContext.Request.Headers.Keys.Contains(HeaderNames.IfUnmodifiedSince)))
            {
                _logger.LogInformation("Not valid - no If Match or If Unmodified-Since headers.");
                return(true);
            }

            // generate the request key
            var requestKey = GenerateRequestKey(httpContext.Request);

            // find the validationValue with this key in the store
            var validationValue = await _store.GetAsync(requestKey);

            // if there is no validation value in the store, we return false:
            // there is nothing to compare to, so the precondition can
            // never be ok - return a 412 response
            if (validationValue == null || validationValue.ETag == null)
            {
                _logger.LogInformation("No saved response found in store.");
                return(false);
            }

            var eTagIsValid = false;
            var ifUnModifiedSinceIsValid = false;

            // check the ETags
            if (httpContext.Request.Headers.Keys.Contains(HeaderNames.IfMatch))
            {
                var ifMatchHeaderValue = httpContext.Request.Headers[HeaderNames.IfMatch].ToString().Trim();
                _logger.LogInformation($"Checking If-Match: {ifMatchHeaderValue}.");

                // if the value is *, the check is valid.
                if (ifMatchHeaderValue == "*")
                {
                    eTagIsValid = true;
                }
                else
                {
                    // otherwise, check the actual ETag(s)
                    var ETagsFromIfMatchHeader = ifMatchHeaderValue
                                                 .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (var ETag in ETagsFromIfMatchHeader)
                    {
                        // check the ETag.  If one of the ETags matches, the
                        // ETag precondition is valid.

                        // for concurrency checks, we use the strong
                        // comparison function.
                        if (ETagsMatch(validationValue.ETag,
                                       ETag.Trim(),
                                       true))
                        {
                            _logger.LogInformation($"ETag valid: {validationValue.ETag}.");
                            eTagIsValid = true;
                            break;
                        }
                    }
                }
            }
            else
            {
                _logger.LogInformation("No If-Match header, don't check ETag.");
                // if there is no IfMatch header, the tag precondition is valid.
                eTagIsValid = true;
            }

            // if there is an IfMatch header but none of the ETags match,
            // the precondition is already invalid.  We don't have to
            // continue checking.
            if (!eTagIsValid)
            {
                _logger.LogInformation("Not valid. No match found for ETag.");
                return(false);
            }

            // Either the ETag matches (or one of them), or there was no IfMatch header.
            // Continue with checking the IfUnModifiedSince header, if it exists.
            if (httpContext.Request.Headers.Keys.Contains(HeaderNames.IfUnmodifiedSince))
            {
                // if the LastModified date is smaller than the IfUnmodifiedSince date,
                // the precondition is valid.
                var ifUnModifiedSinceValue = httpContext.Request.Headers[HeaderNames.IfUnmodifiedSince].ToString();
                _logger.LogInformation($"Checking If-Unmodified-Since: {ifUnModifiedSinceValue}");

                DateTimeOffset parsedIfUnModifiedSince;

                if (DateTimeOffset.TryParseExact(ifUnModifiedSinceValue, "r",
                                                 CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AdjustToUniversal,
                                                 out parsedIfUnModifiedSince))
                {
                    // If the LastModified date is smaller than the IfUnmodifiedSince date,
                    // the precondition is valid.
                    ifUnModifiedSinceIsValid = validationValue.LastModified.CompareTo(parsedIfUnModifiedSince) < 0;
                }
                else
                {
                    // can only check if we can parse it.  Invalid values must
                    // be ignored.
                    ifUnModifiedSinceIsValid = true;
                    _logger.LogInformation("Cannot parse If-Unmodified-Since value as date, header is ignored.");
                }
            }
            else
            {
                _logger.LogInformation("No If-Unmodified-Since header.");
                // if there is no IfUnmodifiedSince header, the check is valid.
                ifUnModifiedSinceIsValid = true;
            }

            // return the combined result of all validators.
            return(ifUnModifiedSinceIsValid && eTagIsValid);
        }
        private async Task <bool> ConditionalGETorHEADIsValid(HttpContext httpContext)
        {
            _logger.LogInformation("Checking for conditional GET/HEAD.");

            if (!(httpContext.Request.Method == HttpMethod.Get.ToString()) ||
                httpContext.Request.Method == "HEAD")
            {
                _logger.LogInformation("Not valid - method isn't GET or HEAD.");
                return(false);
            }

            // we should check ALL If-None-Match values (can be multiple eTags) (if available),
            // and the If-Modified-Since date (if available AND an eTag matches).  See issue #2 @Github.
            // So, this is a valid conditional GET/HEAD (304) if one of the ETags match and, if it's
            // available, the If-Modified-Since date is larger than what's saved.

            // if both headers are missing, we should
            // always return false - we don't need to check anything, and
            // can never return a 304 response
            if (!(httpContext.Request.Headers.Keys.Contains(HeaderNames.IfNoneMatch) ||
                  httpContext.Request.Headers.Keys.Contains(HeaderNames.IfModifiedSince)))
            {
                _logger.LogInformation("Not valid - no If-None-Match or If-Modified-Since headers.");
                return(false);
            }

            // generate the request key
            var requestKey = GenerateRequestKey(httpContext.Request);

            // find the validationValue with this key in the store
            var validationValue = await _store.GetAsync(requestKey);

            // if there is no validation value in the store, always
            // return false - we have nothing to compare to, and can
            // never return a 304 response
            if (validationValue == null || validationValue.ETag == null)
            {
                _logger.LogInformation("No saved response found in store.");
                return(false);
            }

            bool eTagIsValid            = false;
            bool ifModifiedSinceIsValid = false;

            // check the ETags
            if (httpContext.Request.Headers.Keys.Contains(HeaderNames.IfNoneMatch))
            {
                _logger.LogInformation("Checking If-None-Match.");

                var ifNoneMatchHeaderValue = httpContext.Request.Headers[HeaderNames.IfNoneMatch].ToString().Trim();
                _logger.LogInformation($"Checking If-None-Match: {ifNoneMatchHeaderValue}.");

                // if the value is *, the check is valid.
                if (ifNoneMatchHeaderValue == "*")
                {
                    eTagIsValid = true;
                }
                else
                {
                    var ETagsFromIfNoneMatchHeader = ifNoneMatchHeaderValue
                                                     .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (var ETag in ETagsFromIfNoneMatchHeader)
                    {
                        // check the ETag.  If one of the ETags matches, we're good to
                        // go and can return a 304 Not Modified.
                        // For conditional GET/HEAD, we use weak comparison.
                        if (ETagsMatch(validationValue.ETag,
                                       ETag.Trim(),
                                       false))
                        {
                            eTagIsValid = true;
                            _logger.LogInformation($"ETag valid: {validationValue.ETag}.");
                            break;
                        }
                    }

                    // if there is an IfNoneMatch header, but none of the eTags match, we don't take the
                    // If-Modified-Since headers into account.
                    //
                    // cfr: "If none of the entity tags match, then the server MAY perform the requested method as if the
                    // If-None-Match header field did not exist, but MUST also ignore any If-Modified-Since header field(s)
                    // in the request. That is, if no entity tags match, then the server MUST NOT return a 304(Not Modified) response."
                    if (!eTagIsValid)
                    {
                        _logger.LogInformation("Not valid. No match found for ETag.");
                        return(false);
                    }
                }
            }
            else
            {
                _logger.LogInformation("No If-None-Match header, don't check ETag.");
                eTagIsValid = true;
            }

            if (httpContext.Request.Headers.Keys.Contains(HeaderNames.IfModifiedSince))
            {
                // if the LastModified date is smaller than the IfModifiedSince date,
                // we can return a 304 Not Modified (IF there's also a matching ETag).
                // By adding an If-Modified-Since date
                // to a GET/HEAD request, the consumer is stating that (s)he only wants the resource
                // to be returned if if has been modified after that.
                var ifModifiedSinceValue = httpContext.Request.Headers[HeaderNames.IfModifiedSince].ToString();
                _logger.LogInformation($"Checking If-Modified-Since: {ifModifiedSinceValue}");

                DateTimeOffset parsedIfModifiedSince;

                if (DateTimeOffset.TryParseExact(ifModifiedSinceValue, "r",
                                                 CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AdjustToUniversal,
                                                 out parsedIfModifiedSince))
                {
                    // can only check if we can parse it.
                    ifModifiedSinceIsValid = validationValue.LastModified.CompareTo(parsedIfModifiedSince) < 0;
                }
                else
                {
                    ifModifiedSinceIsValid = true;
                    _logger.LogInformation("Cannot parse If-Modified-Since value as date, header is ignored.");
                }
            }
            else
            {
                _logger.LogInformation("No If-Modified-Since header.");
                ifModifiedSinceIsValid = true;
            }

            return(eTagIsValid && ifModifiedSinceIsValid);
        }