public void GetResultForMutating__when__PUT_IfMatch_and_row_has_matching_ETag__then__precondition_Pass()
        {
            byte[] rowVersion = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
            string eTag;

            Assert.True(ETagUtility.TryCreate(rowVersion, out eTag));

            var request = new FakeHttpRequest()
            {
                Method      = HttpMethods.Put,
                FakeHeaders = new HeaderDictionary(new Dictionary <string, Microsoft.Extensions.Primitives.StringValues>()
                {
                    { "If-Match", (ETagUtility.FormatStandard(eTag)) }
                })
            };

            var rowWithMatchingETag = new ServerSideEntity()
            {
                RowVersion = rowVersion
            };

            var executor = new HttpPreconditionExecutor(null);

            // Act

            var result = executor.GetResult(request, PreconditionResult.BadRequest, rowWithMatchingETag);

            // Assert

            Assert.NotNull(result);
            Assert.Equal(PreconditionResultStatus.Passed, result.Status);
            Assert.Equal(0, result.StatusCode);
        }
예제 #2
0
        public void TryCreate__when__static_test_date__then__produces_10460DCE5E010000()
        {
            string eTagString;

            Assert.True(ETagUtility.TryCreate(TimeStamp, out eTagString));
            Assert.Equal("10460DCE5E010000", eTagString);
        }
        /// <summary>
        /// Creates aggregated precondition information from a collection of timestamps.
        /// </summary>
        /// <param name="timeStamps">The time stamps.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">timeStamps</exception>
        /// <exception cref="System.InvalidOperationException">Cannot create precondition information from the timestamps. This is unexpected as it is assumed that it is always possible.</exception>
        public static AggregatePreconditionInformation CreateFrom(IEnumerable <DateTimeOffset> timeStamps)
        {
            if (timeStamps == null)
            {
                throw new ArgumentNullException("timeStamps");
            }


            DateTimeOffset mostRecent = timeStamps.DefaultIfEmpty().Max();

            byte[] eTag;
            if (ETagUtility.TryCreate(mostRecent, out eTag))
            {
                return(new AggregatePreconditionInformation()
                {
                    RowVersion = eTag,
                    ModifiedOn = mostRecent
                });
            }
            else
            {
                throw new InvalidOperationException(
                          "Cannot create precondition information from the timestamps. This is unexpected as it is assumed that it is always possible.");
            }
        }
        /// <summary>
        /// Creates aggregated precondition information from a collection of precondition information.
        /// </summary>
        /// <param name="items">The items.</param>
        /// <exception cref="System.ArgumentNullException">items</exception>
        public static AggregatePreconditionInformation CreateFrom(IEnumerable <IPreconditionInformation> items)
        {
            if (items == null)
            {
                throw new ArgumentNullException("items");
            }


            var info = new AggregatePreconditionInformation();

            // Set the etag.

            byte[] eTag;
            var    rowVersions = items.Select(i => i.RowVersion).ToArray();

            if (ETagUtility.TryCreate(rowVersions, out eTag))
            {
                info.RowVersion = eTag;
            }

            // Set the modified.

            if (items.Any())
            {
                info.ModifiedOn = items.Max(i => i.ModifiedOn);
            }


            return(info);
        }
        /// <summary>
        /// Gets the result for a HTTP method that doesn't change server side state.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="defaultResult">The default result.</param>
        /// <param name="localInformation">The local information.</param>
        /// <returns>PreconditionResult.</returns>
        protected virtual PreconditionResult GetResultForNonMutating(HttpRequest request, PreconditionResult defaultResult, IPreconditionInformation localInformation)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (defaultResult == null)
            {
                throw new ArgumentNullException(nameof(defaultResult));
            }

            if (localInformation == null)
            {
                throw new ArgumentNullException(nameof(localInformation));
            }

            var httpRequestHeaders = request.GetTypedHeaders();

            // Predicate code.

            string localETag;

            if (ETagUtility.TryCreate(localInformation, out localETag))
            {
                localETag = ETagUtility.FormatStandard(localETag);

                // Favours eTags over timestamps.
                //
                if (httpRequestHeaders.IfNoneMatch != null && httpRequestHeaders.IfNoneMatch.Any())
                {
                    foreach (var eTag in httpRequestHeaders.IfNoneMatch)
                    {
                        _logger?.LogTrace($"Precondition If-None-Match: client {eTag} vs local {localETag}");

                        if (eTag.Tag == localETag)
                        {
                            _logger?.LogInformation("Precondition If-None-Match: false");

                            return(PreconditionResult.PreconditionFailedNotModified); // As soon as one matches, we've failed (if *none* match).
                        }
                    }

                    _logger?.LogInformation("Precondition If-None-Match: true");

                    return(PreconditionResult.PreconditionPassed);
                }

                // Falls through to try modified stamps.
                //
                if (httpRequestHeaders.IfModifiedSince.HasValue && localInformation.ModifiedOn > DateTimeOffset.MinValue)
                {
                    DateTimeOffset clientTimestamp = httpRequestHeaders.IfModifiedSince.Value;

                    _logger?.LogTrace($"Precondition If-None-Match: client {clientTimestamp} vs local {localInformation.ModifiedOn}");

                    if (IsTimeSignificantlyGreaterThan(clientTimestamp, localInformation.ModifiedOn))
                    {
                        _logger?.LogInformation("Precondition If-Modified-Since: true");

                        return(PreconditionResult.PreconditionPassed);
                    }
                    else
                    {
                        _logger?.LogInformation("Precondition If-Modified-Since: false");

                        return(PreconditionResult.PreconditionFailedNotModified);
                    }
                }

                _logger?.LogInformation("Precondition headers not found: " + defaultResult.ToString());
            }
            else
            {
                _logger?.LogInformation("Local version data not found: " + defaultResult.ToString());
            }

            _logger?.LogInformation("The information required to determine precondition is not present. Defaulting to HTTP " + defaultResult.StatusCode);

            return(defaultResult);
        }
        /// <summary>
        /// Gets the result for a HTTP method that changes server side state.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="defaultResult">The default result.</param>
        /// <param name="localInformation">The server side information.</param>
        /// <returns>PreconditionResult.</returns>
        /// <exception cref="System.NotSupportedException">
        /// The wildcard ETag conditional PUT|DELETE is not supported.
        /// or
        /// The wildcard ETag conditional PUT|DELETE is not supported.
        /// </exception>
        protected virtual PreconditionResult GetResultForMutating(HttpRequest request, PreconditionResult defaultResult, IPreconditionInformation localInformation)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (defaultResult == null)
            {
                throw new ArgumentNullException(nameof(defaultResult));
            }

            if (localInformation == null)
            {
                throw new ArgumentNullException(nameof(localInformation));
            }

            var httpRequestHeaders = request.GetTypedHeaders();

            // Predicate code.

            string localETag;

            if (ETagUtility.TryCreate(localInformation, out localETag))
            {
                localETag = ETagUtility.FormatStandard(localETag);

                // Favours eTags over timestamps.
                //
                if (httpRequestHeaders.IfMatch != null && httpRequestHeaders.IfMatch.Any())
                {
                    foreach (var eTag in httpRequestHeaders.IfMatch)
                    {
                        _logger?.LogTrace($"Precondition If-None-Match: client {eTag} vs local {localETag}");

                        if (eTag.Tag == "*")
                        {
                            throw new NotSupportedException("The wildcard ETag conditional PUT|DELETE is not supported.");
                        }

                        if (eTag.Tag == localETag)
                        {
                            _logger?.LogInformation("Precondition If-Match: true");

                            return(PreconditionResult.PreconditionPassed);
                        }
                    }

                    _logger?.LogInformation("Precondition If-Match: false");

                    return(PreconditionResult.PreconditionFailed);
                }

                if (httpRequestHeaders.IfNoneMatch != null && httpRequestHeaders.IfNoneMatch.Any())
                {
                    foreach (var eTag in httpRequestHeaders.IfNoneMatch)
                    {
                        _logger?.LogTrace($"Precondition If-None-Match: client {eTag} vs local {localETag}");

                        if (eTag.Tag == "*")
                        {
                            throw new NotSupportedException("The wildcard ETag conditional PUT|DELETE is not supported.");
                        }

                        if (eTag.Tag == localETag)
                        {
                            _logger?.LogInformation("Precondition If-None-Match: false");

                            return(PreconditionResult.PreconditionFailed); // As soon as one matches, we've failed (if *none* match).
                        }
                    }

                    _logger?.LogInformation("Precondition If-None-Match: true");

                    return(PreconditionResult.PreconditionPassed);
                }

                // Falls through to try modified stamps, note: *Un*modified since.
                //
                if (httpRequestHeaders.IfUnmodifiedSince.HasValue && localInformation.ModifiedOn > DateTimeOffset.MinValue)
                {
                    DateTimeOffset clientTimestamp = httpRequestHeaders.IfUnmodifiedSince.Value;

                    _logger?.LogTrace($"Precondition If-Unmodified-Since: client {clientTimestamp} vs local {localInformation.ModifiedOn}");

                    // Pass, only if the server resource has not been modified since the specified date/time, i.e. if the timestamp
                    // on the server's copy matches that which the client has.
                    //
                    if (AreTimesAlmostEqual(clientTimestamp, localInformation.ModifiedOn))
                    {
                        _logger?.LogInformation("Precondition If-Unmodified-Since: true");

                        return(PreconditionResult.PreconditionPassed);
                    }
                    else
                    {
                        _logger?.LogInformation("Precondition If-Unmodified-Since: false");

                        return(PreconditionResult.PreconditionFailed);
                    }
                }

                _logger?.LogInformation("Precondition headers not found: " + defaultResult.ToString());
            }
            else
            {
                _logger?.LogInformation("Local version data not found: " + defaultResult.ToString());
            }

            _logger?.LogInformation("The information required to determine precondition is not present. Defaulting to HTTP " + defaultResult.StatusCode);

            return(defaultResult);
        }