Sort header and query values
This commit is contained in:
parent
1437f00e84
commit
d9778252d0
|
|
@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
}
|
||||
}
|
||||
|
||||
// BaseKey<delimiter>H<delimiter>HeaderName=HeaderValue<delimiter>Q<delimiter>QueryName=QueryValue
|
||||
// BaseKey<delimiter>H<delimiter>HeaderName=HeaderValue<delimiter>Q<delimiter>QueryName=QueryValue1<subdelimiter>QueryValue2
|
||||
public string CreateStorageVaryByKey(ResponseCachingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
|
|
@ -124,9 +124,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
.Append(header)
|
||||
.Append("=");
|
||||
|
||||
for (var j = 0; j < headerValues.Count; j++)
|
||||
var headerValuesArray = headerValues.ToArray();
|
||||
Array.Sort(headerValuesArray, StringComparer.Ordinal);
|
||||
|
||||
for (var j = 0; j < headerValuesArray.Length; j++)
|
||||
{
|
||||
builder.Append(headerValues[j]);
|
||||
builder.Append(headerValuesArray[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -141,19 +144,27 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
if (varyByRules.QueryKeys.Count == 1 && string.Equals(varyByRules.QueryKeys[0], "*", StringComparison.Ordinal))
|
||||
{
|
||||
// Vary by all available query keys
|
||||
foreach (var query in context.HttpContext.Request.Query.OrderBy(q => q.Key, StringComparer.OrdinalIgnoreCase))
|
||||
var queryArray = context.HttpContext.Request.Query.ToArray();
|
||||
// Query keys are aggregated case-insensitively whereas the query values are compared ordinally.
|
||||
Array.Sort(queryArray, QueryKeyComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (var i = 0; i < queryArray.Length; i++)
|
||||
{
|
||||
builder.Append(KeyDelimiter)
|
||||
.AppendUpperInvariant(query.Key)
|
||||
.AppendUpperInvariant(queryArray[i].Key)
|
||||
.Append("=");
|
||||
|
||||
for (var i = 0; i < query.Value.Count; i++)
|
||||
var queryValueArray = queryArray[i].Value.ToArray();
|
||||
Array.Sort(queryValueArray, StringComparer.Ordinal);
|
||||
|
||||
for (var j = 0; j < queryValueArray.Length; j++)
|
||||
{
|
||||
if (i > 0)
|
||||
if (j > 0)
|
||||
{
|
||||
builder.Append(KeySubDelimiter);
|
||||
}
|
||||
builder.Append(query.Value[i]);
|
||||
|
||||
builder.Append(queryValueArray[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,13 +178,17 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
.Append(queryKey)
|
||||
.Append("=");
|
||||
|
||||
for (var j = 0; j < queryKeyValues.Count; j++)
|
||||
var queryValueArray = queryKeyValues.ToArray();
|
||||
Array.Sort(queryValueArray, StringComparer.Ordinal);
|
||||
|
||||
for (var j = 0; j < queryValueArray.Length; j++)
|
||||
{
|
||||
if (j > 0)
|
||||
{
|
||||
builder.Append(KeySubDelimiter);
|
||||
}
|
||||
builder.Append(queryKeyValues[j]);
|
||||
|
||||
builder.Append(queryValueArray[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -186,5 +201,19 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
|||
_builderPool.Return(builder);
|
||||
}
|
||||
}
|
||||
|
||||
private class QueryKeyComparer : IComparer<KeyValuePair<string, StringValues>>
|
||||
{
|
||||
private StringComparer _stringComparer;
|
||||
|
||||
public static QueryKeyComparer OrdinalIgnoreCase { get; } = new QueryKeyComparer(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public QueryKeyComparer(StringComparer stringComparer)
|
||||
{
|
||||
_stringComparer = stringComparer;
|
||||
}
|
||||
|
||||
public int Compare(KeyValuePair<string, StringValues> x, KeyValuePair<string, StringValues> y) => _stringComparer.Compare(x.Key, y.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,22 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
cacheKeyProvider.CreateStorageVaryByKey(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseCachingKeyProvider_CreateStorageVaryKey_HeaderValuesAreSorted()
|
||||
{
|
||||
var cacheKeyProvider = TestUtils.CreateTestKeyProvider();
|
||||
var context = TestUtils.CreateTestContext();
|
||||
context.HttpContext.Request.Headers["HeaderA"] = "ValueB";
|
||||
context.HttpContext.Request.Headers.Append("HeaderA", "ValueA");
|
||||
context.CachedVaryByRules = new CachedVaryByRules()
|
||||
{
|
||||
Headers = new string[] { "HeaderA", "HeaderC" }
|
||||
};
|
||||
|
||||
Assert.Equal($"{context.CachedVaryByRules.VaryByKeyPrefix}{KeyDelimiter}H{KeyDelimiter}HeaderA=ValueAValueB{KeyDelimiter}HeaderC=",
|
||||
cacheKeyProvider.CreateStorageVaryByKey(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseCachingKeyProvider_CreateStorageVaryKey_IncludesListedQueryKeysOnly()
|
||||
{
|
||||
|
|
@ -162,6 +178,24 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|||
cacheKeyProvider.CreateStorageVaryByKey(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseCachingKeyProvider_CreateStorageVaryKey_QueryKeysValuesAreSorted()
|
||||
{
|
||||
var cacheKeyProvider = TestUtils.CreateTestKeyProvider();
|
||||
var context = TestUtils.CreateTestContext();
|
||||
context.HttpContext.Request.QueryString = new QueryString("?QueryA=ValueB&QueryA=ValueA");
|
||||
context.CachedVaryByRules = new CachedVaryByRules()
|
||||
{
|
||||
VaryByKeyPrefix = FastGuid.NewGuid().IdString,
|
||||
QueryKeys = new string[] { "*" }
|
||||
};
|
||||
|
||||
// To support case insensitivity, all query keys are converted to upper case.
|
||||
// Explicit query keys uses the casing specified in the setting.
|
||||
Assert.Equal($"{context.CachedVaryByRules.VaryByKeyPrefix}{KeyDelimiter}Q{KeyDelimiter}QUERYA=ValueA{KeySubDelimiter}ValueB",
|
||||
cacheKeyProvider.CreateStorageVaryByKey(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseCachingKeyProvider_CreateStorageVaryKey_IncludesListedHeadersAndQueryKeys()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue