Skip to content
This repository was archived by the owner on Nov 22, 2018. It is now read-only.

Commit 0256853

Browse files
committed
Make key generation case and order insensitive
Add tests to verify insensitivity rules
1 parent efdcb34 commit 0256853

File tree

3 files changed

+210
-2
lines changed

3 files changed

+210
-2
lines changed

src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ internal string CreateCacheKey(CachedVaryBy varyBy)
188188
foreach (var query in _httpContext.Request.Query.OrderBy(q => q.Key))
189189
{
190190
builder.Append(";")
191-
.Append(query.Key)
191+
.Append(query.Key.ToLower())
192192
.Append("=")
193193
.Append(query.Value);
194194
}
@@ -499,6 +499,11 @@ internal void FinalizeCachingHeaders()
499499
// Check if any VaryBy rules exist
500500
if (!StringValues.IsNullOrEmpty(varyHeaderValue) || !StringValues.IsNullOrEmpty(varyParamsValue))
501501
{
502+
if (varyParamsValue.Count > 1)
503+
{
504+
Array.Sort(varyParamsValue.ToArray(), StringComparer.OrdinalIgnoreCase);
505+
}
506+
502507
var cachedVaryBy = new CachedVaryBy
503508
{
504509
// TODO: VaryBy Encoding

test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCachingContextTests.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,21 @@ public void CreateCacheKey_Includes_ListedVaryByParamsOnly()
205205
}));
206206
}
207207

208+
[Fact]
209+
public void CreateCacheKey_Includes_VaryByParams_ParamNameCaseInsensitive_UseVaryByCasing()
210+
{
211+
var httpContext = new DefaultHttpContext();
212+
httpContext.Request.Method = "GET";
213+
httpContext.Request.Path = "/";
214+
httpContext.Request.QueryString = new QueryString("?parama=ValueA&paramB=ValueB");
215+
var context = CreateTestContext(httpContext);
216+
217+
Assert.Equal("GET;/;ParamA=ValueA;ParamC=null", context.CreateCacheKey(new CachedVaryBy()
218+
{
219+
Params = new string[] { "ParamA", "ParamC" }
220+
}));
221+
}
222+
208223
[Fact]
209224
public void CreateCacheKey_Includes_AllQueryParamsGivenAsterisk()
210225
{
@@ -214,7 +229,9 @@ public void CreateCacheKey_Includes_AllQueryParamsGivenAsterisk()
214229
httpContext.Request.QueryString = new QueryString("?ParamA=ValueA&ParamB=ValueB");
215230
var context = CreateTestContext(httpContext);
216231

217-
Assert.Equal("GET;/;ParamA=ValueA;ParamB=ValueB", context.CreateCacheKey(new CachedVaryBy()
232+
// To support case insensitivity, all param keys are converted to lower case.
233+
// Explicit VaryBy uses the casing specified in the setting.
234+
Assert.Equal("GET;/;parama=ValueA;paramb=ValueB", context.CreateCacheKey(new CachedVaryBy()
218235
{
219236
Params = new string[] { "*" }
220237
}));

test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCachingTests.cs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,136 @@ public async void ServesCachedContent_IfVaryByParams_Matches()
159159
}
160160
}
161161

162+
[Fact]
163+
public async void ServesCachedContent_IfVaryByParamsExplicit_Matches_ParamNameCaseInsensitive()
164+
{
165+
var builder = CreateBuilderWithResponseCaching(
166+
app =>
167+
{
168+
app.Use(async (context, next) =>
169+
{
170+
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
171+
await next.Invoke();
172+
});
173+
},
174+
async (context) =>
175+
{
176+
var uniqueId = Guid.NewGuid().ToString();
177+
var headers = context.Response.GetTypedHeaders();
178+
headers.CacheControl = new CacheControlHeaderValue()
179+
{
180+
Public = true,
181+
MaxAge = TimeSpan.FromSeconds(10)
182+
};
183+
headers.Date = DateTimeOffset.UtcNow;
184+
headers.Headers["X-Value"] = uniqueId;
185+
context.GetResponseCachingOptions().Params = new[] { "ParamA", "paramb" };
186+
await context.Response.WriteAsync(uniqueId);
187+
});
188+
189+
using (var server = new TestServer(builder))
190+
{
191+
var client = server.CreateClient();
192+
var initialResponse = await client.GetAsync("?parama=valuea&paramb=valueb");
193+
var subsequentResponse = await client.GetAsync("?ParamA=valuea&ParamB=valueb");
194+
195+
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
196+
}
197+
}
198+
199+
[Fact]
200+
public async void ServesCachedContent_IfVaryByParamsStar_Matches_ParamNameCaseInsensitive()
201+
{
202+
var builder = CreateBuilderWithResponseCaching(
203+
app =>
204+
{
205+
app.Use(async (context, next) =>
206+
{
207+
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
208+
await next.Invoke();
209+
});
210+
},
211+
async (context) =>
212+
{
213+
var uniqueId = Guid.NewGuid().ToString();
214+
var headers = context.Response.GetTypedHeaders();
215+
headers.CacheControl = new CacheControlHeaderValue()
216+
{
217+
Public = true,
218+
MaxAge = TimeSpan.FromSeconds(10)
219+
};
220+
headers.Date = DateTimeOffset.UtcNow;
221+
headers.Headers["X-Value"] = uniqueId;
222+
context.GetResponseCachingOptions().Params = new[] { "*" };
223+
await context.Response.WriteAsync(uniqueId);
224+
});
225+
226+
using (var server = new TestServer(builder))
227+
{
228+
var client = server.CreateClient();
229+
var initialResponse = await client.GetAsync("?parama=valuea&paramb=valueb");
230+
var subsequentResponse = await client.GetAsync("?ParamA=valuea&ParamB=valueb");
231+
232+
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
233+
}
234+
}
235+
236+
[Fact]
237+
public async void ServesCachedContent_IfVaryByParamsExplicit_Matches_OrderInsensitive()
238+
{
239+
var builder = CreateBuilderWithResponseCaching(async (context) =>
240+
{
241+
var uniqueId = Guid.NewGuid().ToString();
242+
var headers = context.Response.GetTypedHeaders();
243+
headers.CacheControl = new CacheControlHeaderValue()
244+
{
245+
Public = true,
246+
MaxAge = TimeSpan.FromSeconds(10)
247+
};
248+
headers.Date = DateTimeOffset.UtcNow;
249+
headers.Headers["X-Value"] = uniqueId;
250+
context.GetResponseCachingOptions().Params = new[] { "ParamB", "ParamA" };
251+
await context.Response.WriteAsync(uniqueId);
252+
});
253+
254+
using (var server = new TestServer(builder))
255+
{
256+
var client = server.CreateClient();
257+
var initialResponse = await client.GetAsync("?ParamA=ValueA&ParamB=ValueB");
258+
var subsequentResponse = await client.GetAsync("?ParamB=ValueB&ParamA=ValueA");
259+
260+
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
261+
}
262+
}
263+
264+
[Fact]
265+
public async void ServesCachedContent_IfVaryByParamsStar_Matches_OrderInsensitive()
266+
{
267+
var builder = CreateBuilderWithResponseCaching(async (context) =>
268+
{
269+
var uniqueId = Guid.NewGuid().ToString();
270+
var headers = context.Response.GetTypedHeaders();
271+
headers.CacheControl = new CacheControlHeaderValue()
272+
{
273+
Public = true,
274+
MaxAge = TimeSpan.FromSeconds(10)
275+
};
276+
headers.Date = DateTimeOffset.UtcNow;
277+
headers.Headers["X-Value"] = uniqueId;
278+
context.GetResponseCachingOptions().Params = new[] { "*" };
279+
await context.Response.WriteAsync(uniqueId);
280+
});
281+
282+
using (var server = new TestServer(builder))
283+
{
284+
var client = server.CreateClient();
285+
var initialResponse = await client.GetAsync("?ParamA=ValueA&ParamB=ValueB");
286+
var subsequentResponse = await client.GetAsync("?ParamB=ValueB&ParamA=ValueA");
287+
288+
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
289+
}
290+
}
291+
162292
[Fact]
163293
public async void ServesFreshContent_IfVaryByParams_Mismatches()
164294
{
@@ -187,6 +317,62 @@ public async void ServesFreshContent_IfVaryByParams_Mismatches()
187317
}
188318
}
189319

320+
[Fact]
321+
public async void ServesFreshContent_IfVaryByParamsExplicit_Mismatch_ParamValueCaseSensitive()
322+
{
323+
var builder = CreateBuilderWithResponseCaching(async (context) =>
324+
{
325+
var uniqueId = Guid.NewGuid().ToString();
326+
var headers = context.Response.GetTypedHeaders();
327+
headers.CacheControl = new CacheControlHeaderValue()
328+
{
329+
Public = true,
330+
MaxAge = TimeSpan.FromSeconds(10)
331+
};
332+
headers.Date = DateTimeOffset.UtcNow;
333+
headers.Headers["X-Value"] = uniqueId;
334+
context.GetResponseCachingOptions().Params = new[] { "ParamA", "ParamB" };
335+
await context.Response.WriteAsync(uniqueId);
336+
});
337+
338+
using (var server = new TestServer(builder))
339+
{
340+
var client = server.CreateClient();
341+
var initialResponse = await client.GetAsync("?parama=valuea&paramb=valueb");
342+
var subsequentResponse = await client.GetAsync("?parama=ValueA&paramb=ValueB");
343+
344+
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
345+
}
346+
}
347+
348+
[Fact]
349+
public async void ServesFreshContent_IfVaryByParamsStar_Mismatch_ParamValueCaseSensitive()
350+
{
351+
var builder = CreateBuilderWithResponseCaching(async (context) =>
352+
{
353+
var uniqueId = Guid.NewGuid().ToString();
354+
var headers = context.Response.GetTypedHeaders();
355+
headers.CacheControl = new CacheControlHeaderValue()
356+
{
357+
Public = true,
358+
MaxAge = TimeSpan.FromSeconds(10)
359+
};
360+
headers.Date = DateTimeOffset.UtcNow;
361+
headers.Headers["X-Value"] = uniqueId;
362+
context.GetResponseCachingOptions().Params = new[] { "*" };
363+
await context.Response.WriteAsync(uniqueId);
364+
});
365+
366+
using (var server = new TestServer(builder))
367+
{
368+
var client = server.CreateClient();
369+
var initialResponse = await client.GetAsync("?parama=valuea&paramb=valueb");
370+
var subsequentResponse = await client.GetAsync("?parama=ValueA&paramb=ValueB");
371+
372+
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
373+
}
374+
}
375+
190376
[Fact]
191377
public async void ServesFreshContent_IfRequestRequirements_NotMet()
192378
{

0 commit comments

Comments
 (0)