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); }
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); }